From 0f63416b9bdc0f162b7ed8ac512a6eaa0eabb6b7 Mon Sep 17 00:00:00 2001 From: junjun Date: Wed, 11 Jun 2025 10:52:46 +0800 Subject: [PATCH 1/5] refactor: template --- backend/apps/datasource/crud/datasource.py | 2 +- backend/template.yaml | 10 +++++----- frontend/src/views/chat/WelcomeBlock.vue | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/apps/datasource/crud/datasource.py b/backend/apps/datasource/crud/datasource.py index 5982b6f0..54c28f7d 100644 --- a/backend/apps/datasource/crud/datasource.py +++ b/backend/apps/datasource/crud/datasource.py @@ -244,7 +244,7 @@ def get_table_schema(session: SessionDep, ds: CoreDatasource) -> str: db_name = table_objs[0].schema schema_str += f"【DB_ID】 {db_name}\n【Schema】\n" for obj in table_objs: - schema_str += f"# Table: {db_name}.{obj.table.table_name}" + schema_str += f"# Table: {db_name}.{obj.table.table_name}" if ds.type != "mysql" else f"# Table: {obj.table.table_name}" table_comment = '' if obj.table.custom_comment: table_comment = obj.table.custom_comment.strip() diff --git a/backend/template.yaml b/backend/template.yaml index 300f7a82..4427ec5d 100644 --- a/backend/template.yaml +++ b/backend/template.yaml @@ -46,19 +46,19 @@ template: 您的任务是通过给定的问题和SQL生成 JSON 以进行数据可视化。 请遵守以下规则: - 如果需要表格,则生成的 JSON 格式应为: - {{"type":"table", "title": "标题", "columns": [{{"name":"中文字段名1", "value": "SQL 查询列 1(有别名用别名)"}}, {{"name": "中文字段名 2", "value": "SQL 查询列 2(有别名用别名)"}}]}} + {{"type":"table", "title": "标题", "columns": [{{"name":"中文字段名1", "value": "SQL 查询列 1(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, {{"name": "中文字段名 2", "value": "SQL 查询列 2(有别名用别名,去掉外层的反引号、双引号、方括号)"}}]}} 必须从 SQL 查询列中提取“columns”。 - 如果需要柱状图,则生成的 JSON 格式应为: - {{"type":"column", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称", "value": "SQL 查询 x 轴的列(有别名用别名)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名)"}}}} + {{"type":"column", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称", "value": "SQL 查询 x 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}}} 必须从 SQL 查询列中提取“x”和“y”。 - 如果需要条形图,则生成的 JSON 格式应为: - {{"type":"bar", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称", "value": "SQL 查询 x 轴的列(有别名用别名)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名)"}}}} + {{"type":"bar", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称", "value": "SQL 查询 x 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}}} 必须从 SQL 查询列中提取“x”和“y”。 - 如果需要折线图,则生成的 JSON 格式应为: - {{"type":"line", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称","value": "SQL 查询 x 轴的列(有别名用别名)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名)"}}}} + {{"type":"line", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称","value": "SQL 查询 x 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}}} 其中“x”和“y”必须从SQL查询列中提取。 - 如果需要饼图,则生成的 JSON 格式应为: - {{"type":"pie", "title": "标题", "axis": {{"y": {{"值轴的中文名称","value":"SQL 查询数值的列(有别名用别名)"}}, "series": {{"name":"分类的中文名称","value":"SQL 查询分类的列(有别名用别名)"}}}} + {{"type":"pie", "title": "标题", "axis": {{"y": {{"值轴的中文名称","value":"SQL 查询数值的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}, "series": {{"name":"分类的中文名称","value":"SQL 查询分类的列(有别名用别名,去掉外层的反引号、双引号、方括号)"}}}} 其中“column”必须从SQL查询列中提取。 - 如果答案未知或者与生成JSON无关,则生成的 JSON 格式应为: {{"type":"error", "reason": "抱歉,我无法回答您的问题。"}} diff --git a/frontend/src/views/chat/WelcomeBlock.vue b/frontend/src/views/chat/WelcomeBlock.vue index 7fdc90ce..ba90a7d8 100644 --- a/frontend/src/views/chat/WelcomeBlock.vue +++ b/frontend/src/views/chat/WelcomeBlock.vue @@ -75,7 +75,7 @@ onMounted(() => {
From 2a74b4d69e28db02f2961c1ef40270dd675512fa Mon Sep 17 00:00:00 2001 From: junjun Date: Wed, 11 Jun 2025 14:26:31 +0800 Subject: [PATCH 2/5] feat: mcp --- backend/apps/chat/api/chat.py | 2 +- backend/apps/system/api/aimodel.py | 43 ++++++++++++++++++------------ backend/common/utils/whitelist.py | 5 +++- backend/main.py | 5 ++-- 4 files changed, 34 insertions(+), 21 deletions(-) diff --git a/backend/apps/chat/api/chat.py b/backend/apps/chat/api/chat.py index e1f1d4dc..536bb9d3 100644 --- a/backend/apps/chat/api/chat.py +++ b/backend/apps/chat/api/chat.py @@ -68,7 +68,7 @@ async def start_chat(session: SessionDep, current_user: CurrentUser, create_chat ) -@router.post("/question") +@router.post("/question", operation_id="question") async def stream_sql(session: SessionDep, current_user: CurrentUser, request_question: ChatQuestion): """Stream SQL analysis results diff --git a/backend/apps/system/api/aimodel.py b/backend/apps/system/api/aimodel.py index 7a039e62..a3bf6f21 100644 --- a/backend/apps/system/api/aimodel.py +++ b/backend/apps/system/api/aimodel.py @@ -1,6 +1,6 @@ -from datetime import datetime from fastapi import APIRouter -from sqlmodel import select +from sqlmodel import select + from apps.system.models.system_model import AiModelDetail from apps.system.schemas.system_schema import model_status from common.core.deps import SessionDep @@ -13,9 +13,9 @@ router = APIRouter(tags=["system/aimodel"], prefix="/system/aimodel") @router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[AiModelDetail]) async def pager( - session: SessionDep, - pageNum: int, - pageSize: int + session: SessionDep, + pageNum: int, + pageSize: int ): pagination = PaginationParams(page=pageNum, size=pageSize) paginator = Paginator(session) @@ -24,19 +24,21 @@ async def pager( model=AiModelDetail, pagination=pagination, **filters) - + + @router.get("/{id}", response_model=AiModelDetail) async def get_model_by_id( - session: SessionDep, - id: int + session: SessionDep, + id: int ): term = session.get(AiModelDetail, id) return term - + + @router.post("", response_model=AiModelDetail) async def add_model( - session: SessionDep, - creator: AiModelDetail + session: SessionDep, + creator: AiModelDetail ): data = AiModelDetail.model_validate(creator) data.create_time = get_timestamp() @@ -44,10 +46,11 @@ async def add_model( session.commit() return creator + @router.put("", response_model=AiModelDetail) async def update_terminology( - session: SessionDep, - model: AiModelDetail + session: SessionDep, + model: AiModelDetail ): model.id = int(model.id) term = session.exec(select(AiModelDetail).where(AiModelDetail.id == model.id)).first() @@ -58,10 +61,11 @@ async def update_terminology( session.commit() return model + @router.delete("/{id}", response_model=AiModelDetail) async def delete_terminology( - session: SessionDep, - id: int + session: SessionDep, + id: int ): term = session.exec(select(AiModelDetail).where(AiModelDetail.id == id)).first() session.delete(term) @@ -69,7 +73,7 @@ async def delete_terminology( return { "message": f"AiModel with ID {id} deleted successfully." } - + @router.patch("/status", response_model=dict) async def status(session: SessionDep, dto: model_status): @@ -83,4 +87,9 @@ async def status(session: SessionDep, dto: model_status): term.status = status session.add(term) session.commit() - return {"message": f"AiModel with IDs {ids} updated successfully."} \ No newline at end of file + return {"message": f"AiModel with IDs {ids} updated successfully."} + + +@router.get("/list", operation_id="get_model_list") +async def get_model_list(session: SessionDep): + return session.query(AiModelDetail).all() diff --git a/backend/common/utils/whitelist.py b/backend/common/utils/whitelist.py index 78010184..f3543019 100644 --- a/backend/common/utils/whitelist.py +++ b/backend/common/utils/whitelist.py @@ -22,7 +22,10 @@ wlist = [ "*.eot", "*.otf", "/mcp", - "/mcp/message" + "/mcp/message", + "/datasource/list", + "/system/aimodel/list", + "/chat/question" ] class WhitelistChecker: diff --git a/backend/main.py b/backend/main.py index bef058cb..df21bfc6 100644 --- a/backend/main.py +++ b/backend/main.py @@ -49,7 +49,7 @@ mcp = FastApiMCP( app, name="SQLBot MCP Server", description="SQLBot MCP Server", - include_operations=["get_datasource_list"] + include_operations=["get_datasource_list", "get_model_list", "question"] ) mcp.mount(mcp_app) @@ -76,7 +76,7 @@ frontend_dist = os.path.abspath("../frontend/dist") if not os.path.exists(frontend_dist): logging.warning(f"The front-end build directory does not exist: {frontend_dist}") logging.warning("Please make sure you have built the front-end project") - + else: @app.get("/", include_in_schema=False) @@ -92,3 +92,4 @@ if __name__ == "__main__": import uvicorn uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) + # uvicorn.run("main:mcp_app", host="0.0.0.0", port=8001) # mcp server From 80943796ad597587fc474f84636c5ddb07ffaa10 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Wed, 11 Jun 2025 14:43:27 +0800 Subject: [PATCH 3/5] perf: TS Error --- frontend/src/components/layout/index.vue | 7 ++-- frontend/src/router/index.ts | 2 +- frontend/src/stores/user.ts | 33 ++++++++++++------- frontend/src/views/dashboard/editor/index.vue | 1 - .../views/dashboard/preview/SQPreviewShow.vue | 2 +- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/layout/index.vue b/frontend/src/components/layout/index.vue index c25b4663..6e3b5cde 100644 --- a/frontend/src/components/layout/index.vue +++ b/frontend/src/components/layout/index.vue @@ -149,7 +149,6 @@ import {ref, computed, onMounted} from 'vue' import {useRouter, useRoute} from 'vue-router' import {useUserStore} from '@/stores/user' -import folder from '@/assets/svg/folder.svg' import ds from '@/assets/svg/ds.svg' import dashboard from '@/assets/svg/dashboard.svg' import chat from '@/assets/svg/chat.svg' @@ -184,12 +183,12 @@ const sysRouterList = computed(() => { const showSubmenu = computed(() => { return route.path.includes('/system') }) -const workspace = ref('1') -const options = [ +// const workspace = ref('1') +/* const options = [ {value: '1', label: 'Default workspace'}, {value: '2', label: 'Workspace 2'}, {value: '3', label: 'Workspace 3'} -] +] */ const currentPageTitle = computed(() => { if (route.path.includes('/system')) { return 'System Settings' diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 8fba4d81..51cefc81 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -8,7 +8,7 @@ import ds from "@/views/ds/index.vue"; import DashboardEditor from "@/views/dashboard/editor/index.vue"; import Dashboard from "@/views/dashboard/index.vue"; import Model from "@/views/system/model/index.vue"; -import User from "@/views/system/user/index.vue"; +// import User from "@/views/system/user/index.vue"; import { watchRouter } from "./watch"; const router = createRouter({ history: createWebHashHistory(), diff --git a/frontend/src/stores/user.ts b/frontend/src/stores/user.ts index a22a6237..e5cc79d0 100644 --- a/frontend/src/stores/user.ts +++ b/frontend/src/stores/user.ts @@ -1,5 +1,5 @@ import { defineStore } from 'pinia' -import { ref } from 'vue' +// import { ref } from 'vue' import { AuthApi } from '@/api/login' import { useCache } from '@/utils/useCache' import { i18n } from '@/i18n' @@ -17,18 +17,19 @@ interface UserState { language: string exp: number time: number + [key: string]: string | number } export const UserStore = defineStore('user', { state: (): UserState => { return { - token: null, - uid: null, - name: null, - oid: null, + token: '', + uid: '', + name: '', + oid: '', language: 'zh-CN', - exp: null, - time: null + exp: 0, + time: 0 } }, getters: { @@ -69,13 +70,23 @@ export const UserStore = defineStore('user', { const res: any = await AuthApi.info() const res_data = res || {} - const keys: string[] = ['uid', 'name', 'oid', 'language', 'exp', 'time'] - + // 指定 keys 类型为 UserState 的键 + const keys = ['uid', 'name', 'oid', 'language', 'exp', 'time'] as const + keys.forEach(key => { const dkey = key === 'uid' ? 'id' : key - this[key] = res_data[dkey] - wsCache.set('user.' + key, this[key]) + const value = res_data[dkey] + + // 使用类型守卫区分数字和字符串类型 + if (key === 'exp' || key === 'time') { + this[key] = Number(value) + wsCache.set('user.' + key, value.toString()) + } else { + this[key] = String(value) + wsCache.set('user.' + key, value) + } }) + this.setLanguage(this.language) }, setToken(token: string) { diff --git a/frontend/src/views/dashboard/editor/index.vue b/frontend/src/views/dashboard/editor/index.vue index 2861c417..4be1147b 100644 --- a/frontend/src/views/dashboard/editor/index.vue +++ b/frontend/src/views/dashboard/editor/index.vue @@ -8,7 +8,6 @@ import cloneDeep from 'lodash/cloneDeep'; import {storeToRefs} from "pinia"; import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts"; import router from '@/router' -import {dashboardApi} from "@/api/dashboard.ts"; import {initCanvasData} from "@/views/dashboard/utils/canvasUtils.ts"; const dashboardStore = dashboardStoreWithOut() diff --git a/frontend/src/views/dashboard/preview/SQPreviewShow.vue b/frontend/src/views/dashboard/preview/SQPreviewShow.vue index b364624c..0834c246 100644 --- a/frontend/src/views/dashboard/preview/SQPreviewShow.vue +++ b/frontend/src/views/dashboard/preview/SQPreviewShow.vue @@ -1,7 +1,7 @@