From 86abe25e27c4bbb2fad6e17e4513bc28efffe475 Mon Sep 17 00:00:00 2001 From: ulleo Date: Tue, 5 Aug 2025 10:56:46 +0800 Subject: [PATCH] feat: export --- backend/apps/chat/api/chat.py | 14 +++++++---- backend/apps/datasource/api/datasource.py | 3 ++- backend/common/core/config.py | 3 +-- backend/main.py | 23 +++++++++---------- .../src/views/chat/chat-block/ChartBlock.vue | 9 ++++++-- 5 files changed, 30 insertions(+), 22 deletions(-) diff --git a/backend/apps/chat/api/chat.py b/backend/apps/chat/api/chat.py index 1d745f92..2ce81ad3 100644 --- a/backend/apps/chat/api/chat.py +++ b/backend/apps/chat/api/chat.py @@ -1,5 +1,8 @@ import datetime +import hashlib +import io import traceback +import uuid import numpy as np import pandas as pd @@ -12,6 +15,7 @@ from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, Chat from apps.chat.task.llm import LLMService from common.core.config import settings from common.core.deps import CurrentAssistant, SessionDep, CurrentUser +from starlette.responses import FileResponse router = APIRouter(tags=["Data Q&A"], prefix="/chat") @@ -202,12 +206,12 @@ async def export_excel(excel_data: ExcelData): _fields_list.append(field.name) df = pd.DataFrame(np.array(data), columns=_fields_list) - file_name = f"{excel_data.name}-{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.xlsx" + file_name = f"{excel_data.name}-{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}_{hashlib.sha256(uuid.uuid4().bytes).hexdigest()[:10]}.xlsx" file_path = f'{(settings.EXCEL_PATH if settings.EXCEL_PATH[-1] == "/" else (settings.EXCEL_PATH + "/"))}{file_name}' - file_download_path = f'{(settings.SERVER_EXCEL_HOST if settings.SERVER_EXCEL_HOST[-1] == "/" else (settings.SERVER_EXCEL_HOST + "/"))}{file_name}' + buffer = io.BytesIO() + df.to_excel(buffer, index=False) + buffer.seek(0) - df.to_excel(file_path, index=False) - - return file_download_path + return StreamingResponse(io.BytesIO(buffer.getvalue())) diff --git a/backend/apps/datasource/api/datasource.py b/backend/apps/datasource/api/datasource.py index dd8ed5ea..900248a5 100644 --- a/backend/apps/datasource/api/datasource.py +++ b/backend/apps/datasource/api/datasource.py @@ -8,6 +8,7 @@ import pandas as pd from fastapi import APIRouter, File, UploadFile, HTTPException from apps.db.engine import create_table, get_data_engine, insert_data +from common.core.config import settings from common.core.deps import SessionDep, CurrentUser, Trans from common.utils.utils import SQLBotLogUtil from ..crud.datasource import get_datasource_list, check_status, create_ds, update_ds, delete_ds, getTables, getFields, \ @@ -17,7 +18,7 @@ from ..crud.table import get_tables_by_ds_id from ..models.datasource import CoreDatasource, CreateDatasource, TableObj, CoreTable, CoreField router = APIRouter(tags=["datasource"], prefix="/datasource") -path = "/opt/sqlbot/data/excel" +path = settings.EXCEL_PATH @router.get("/ws/{oid}", include_in_schema=False) diff --git a/backend/common/core/config.py b/backend/common/core/config.py index d39b6542..ff198247 100644 --- a/backend/common/core/config.py +++ b/backend/common/core/config.py @@ -79,9 +79,8 @@ class Settings(BaseSettings): ) MCP_IMAGE_PATH: str = '/opt/sqlbot/images' - EXCEL_PATH: str + EXCEL_PATH: str = '/opt/sqlbot/data/excel' MCP_IMAGE_HOST: str = 'http://localhost:3000' SERVER_IMAGE_HOST: str - SERVER_EXCEL_HOST: str settings = Settings() # type: ignore diff --git a/backend/main.py b/backend/main.py index 085062db..7a639e5a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,21 +1,22 @@ -from fastapi.concurrency import asynccontextmanager +import sqlbot_xpack +from alembic.config import Config from fastapi import FastAPI +from fastapi.concurrency import asynccontextmanager from fastapi.routing import APIRoute -from starlette.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi_mcp import FastApiMCP from starlette.exceptions import HTTPException as StarletteHTTPException +from starlette.middleware.cors import CORSMiddleware +from alembic import command +from apps.api import api_router from apps.system.crud.assistant import init_dynamic_cors from apps.system.middleware.auth import TokenMiddleware from common.core.config import settings from common.core.response_middleware import ResponseMiddleware, exception_handler -from alembic.config import Config -from alembic import command -from fastapi_mcp import FastApiMCP -from fastapi.staticfiles import StaticFiles -import sqlbot_xpack -from common.utils.utils import SQLBotLogUtil from common.core.sqlbot_cache import init_sqlbot_cache -from apps.api import api_router +from common.utils.utils import SQLBotLogUtil + def run_migrations(): alembic_cfg = Config("alembic.ini") @@ -32,6 +33,7 @@ async def lifespan(app: FastAPI): yield SQLBotLogUtil.info("SQLBot 应用关闭") + def custom_generate_unique_id(route: APIRoute) -> str: tag = route.tags[0] if route.tags and len(route.tags) > 0 else "" return f"{tag}-{route.name}" @@ -44,9 +46,6 @@ app = FastAPI( lifespan=lifespan ) -app.mount("/excel", StaticFiles(directory=settings.EXCEL_PATH), name="excel") - - mcp_app = FastAPI() # mcp server, images path mcp_app.mount("/images", StaticFiles(directory=settings.MCP_IMAGE_PATH), name="images") diff --git a/frontend/src/views/chat/chat-block/ChartBlock.vue b/frontend/src/views/chat/chat-block/ChartBlock.vue index e6701211..855ed99d 100644 --- a/frontend/src/views/chat/chat-block/ChartBlock.vue +++ b/frontend/src/views/chat/chat-block/ChartBlock.vue @@ -232,8 +232,13 @@ function exportToExcel() { chatApi .export2Excel({ ...chartRef.value?.getExcelData(), name: chartObject.value.title }) .then((res) => { - console.log('download_path: ' + res) - //todo download + const blob = new Blob([res.data]) + const link = document.createElement('a') + link.href = URL.createObjectURL(blob) + link.download = `${chartObject.value.title ?? 'Excel'}.xlsx` + document.body.appendChild(link) + link.click() + document.body.removeChild(link) }) exportRef.value?.hide() }