From a3844211fce46d373924631f276f0f9480b7a4d4 Mon Sep 17 00:00:00 2001 From: ulleo Date: Tue, 10 Jun 2025 11:09:27 +0800 Subject: [PATCH 01/11] feat: add table chart --- frontend/package.json | 1 + frontend/src/i18n/index.ts | 4 +- .../src/views/chat/component/charts/Bar.ts | 2 +- .../src/views/chat/component/charts/Column.ts | 2 +- .../src/views/chat/component/charts/Line.ts | 2 +- .../src/views/chat/component/charts/Table.ts | 57 +++++++++++++++++++ frontend/src/views/chat/component/index.ts | 3 + 7 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 frontend/src/views/chat/component/charts/Table.ts diff --git a/frontend/package.json b/frontend/package.json index ae6836ec..2d474153 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@antv/g2": "^5.3.3", + "@antv/s2": "^2.4.3", "@npkg/tinymce-plugins": "^0.0.7", "@tinymce/tinymce-vue": "^5.1.0", "dayjs": "^1.11.13", diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index 9fb39600..10cd6420 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -1,8 +1,8 @@ import { createI18n } from 'vue-i18n' import en from './en.json' import zhCN from './zh-CN.json' -import elementEnLocale from 'element-plus/es/locale/lang/en' -import elementZhLocale from 'element-plus/es/locale/lang/zh-cn' +import elementEnLocale from 'element-plus-secondary/es/locale/lang/en' +import elementZhLocale from 'element-plus-secondary/es/locale/lang/zh-cn' const getDefaultLocale = () => { /* const savedLang = localStorage.getItem('lang') diff --git a/frontend/src/views/chat/component/charts/Bar.ts b/frontend/src/views/chat/component/charts/Bar.ts index c1ff190e..91813210 100644 --- a/frontend/src/views/chat/component/charts/Bar.ts +++ b/frontend/src/views/chat/component/charts/Bar.ts @@ -6,7 +6,7 @@ export class Bar extends BaseG2Chart { super(id, "bar"); } - init(axis: Array<{ name: string; value: string; type: "x" | "y" }>, data: Array<{ name: string; value: string }>) { + init(axis: Array<{ name: string; value: string; type: "x" | "y" }>, data: Array<{ [key: string]: any }>) { super.init(axis, data); const x = this.axis.filter(item => item.type === "x"); const y = this.axis.filter(item => item.type === "y"); diff --git a/frontend/src/views/chat/component/charts/Column.ts b/frontend/src/views/chat/component/charts/Column.ts index 54d6c577..041e750c 100644 --- a/frontend/src/views/chat/component/charts/Column.ts +++ b/frontend/src/views/chat/component/charts/Column.ts @@ -6,7 +6,7 @@ export class Column extends BaseG2Chart { super(id, "column"); } - init(axis: Array<{ name: string; value: string; type: "x" | "y" }>, data: Array<{ name: string; value: string }>) { + init(axis: Array<{ name: string; value: string; type: "x" | "y" }>, data: Array<{ [key: string]: any }>) { super.init(axis, data); const x = this.axis.filter(item => item.type === "x"); diff --git a/frontend/src/views/chat/component/charts/Line.ts b/frontend/src/views/chat/component/charts/Line.ts index 8269215d..45a5f777 100644 --- a/frontend/src/views/chat/component/charts/Line.ts +++ b/frontend/src/views/chat/component/charts/Line.ts @@ -6,7 +6,7 @@ export class Line extends BaseG2Chart { super(id, "line"); } - init(axis: Array<{ name: string; value: string; type: "x" | "y" }>, data: Array<{ name: string; value: string }>) { + init(axis: Array<{ name: string; value: string; type: "x" | "y" }>, data: Array<{ [key: string]: any }>) { super.init(axis, data); const x = this.axis.filter(item => item.type === "x"); diff --git a/frontend/src/views/chat/component/charts/Table.ts b/frontend/src/views/chat/component/charts/Table.ts new file mode 100644 index 00000000..68cea9ca --- /dev/null +++ b/frontend/src/views/chat/component/charts/Table.ts @@ -0,0 +1,57 @@ +import {BaseChart} from "@/views/chat/component/BaseChart.ts"; +import {TableSheet, type S2Options, type S2DataConfig, type S2MountContainer} from "@antv/s2"; + +export class Table extends BaseChart { + + table?: TableSheet = undefined + + container: S2MountContainer | null = null + + constructor(id: string) { + super(id, "table"); + this.container = document.getElementById(id); + } + + init(axis: Array<{ name: string; value: string; type: "x" | "y" }>, data: Array<{ [key: string]: any }>) { + super.init(axis, data); + + const s2DataConfig: S2DataConfig = { + fields: { + columns: this.axis?.map(a => a.value) ?? [], + }, + meta: this.axis?.map(a => { + return { + field: a.value, + name: a.name + } + }) ?? [], + data: this.data, + }; + + const s2Options: S2Options = { + width: 600, + height: 480, + placeholder: { + cell: '-', + empty: { + icon: 'Empty', + description: 'No Data', + }, + }, + }; + + if (this.container) { + this.table = new TableSheet(this.container, s2DataConfig, s2Options); + } + + } + + render() { + this.table?.render() + } + + destroy() { + this.table?.destroy() + } + +} \ No newline at end of file diff --git a/frontend/src/views/chat/component/index.ts b/frontend/src/views/chat/component/index.ts index 85cb1b15..8d6366d6 100644 --- a/frontend/src/views/chat/component/index.ts +++ b/frontend/src/views/chat/component/index.ts @@ -2,11 +2,14 @@ import {BaseChart} from "@/views/chat/component/BaseChart.ts"; import {Bar} from "@/views/chat/component/charts/Bar.ts"; import {Column} from "@/views/chat/component/charts/Column.ts"; import {Line} from "@/views/chat/component/charts/Line.ts"; +import {Table} from "@/views/chat/component/charts/Table.ts"; const CHART_TYPE_MAP: { [key: string]: any } = { + "table": Table, "column": Column, "bar": Bar, "line": Line, + "pie": undefined } const isParent = (type: any, parentType: any) => { From b520a4ee83d1e4cd6e435f1091417e0522837e4b Mon Sep 17 00:00:00 2001 From: junjun Date: Tue, 10 Jun 2025 12:07:44 +0800 Subject: [PATCH 02/11] feat: template --- backend/apps/template/template.py | 2 +- backend/template.yaml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/apps/template/template.py b/backend/apps/template/template.py index 7dd5b1cc..e342a84f 100644 --- a/backend/apps/template/template.py +++ b/backend/apps/template/template.py @@ -4,7 +4,7 @@ base_template = None def load(): - with open('./template.yaml', 'r') as f: + with open('./template.yaml', 'r', encoding='utf-8') as f: global base_template base_template = yaml.load(f, Loader=yaml.SafeLoader) diff --git a/backend/template.yaml b/backend/template.yaml index 5256e6f2..6cb4a885 100644 --- a/backend/template.yaml +++ b/backend/template.yaml @@ -7,8 +7,12 @@ template: - 生成的SQL必须符合{engine}的规范。 - 根据表结构生成SQL语句。 - SQL查询中不能使用星号(*),必须明确指定字段名. - - SQL查询的字段名和别名必须为英文。 + - SQL查询的字段名不要自动翻译,别名必须为英文。 - 计算占比,百分比类型字段,保留两位小数,以%结尾。 + - 生成SQL时,必须避免关键字冲突: + - 如{engine}是 PostgreSQL、Oracle,则在schema、表名、字段名、别名外层加双引号; + - 如{engine}是 MySQL,则在表名、字段名、别名外层加反引号; + - 如{engine}是 Microsoft SQL Server,则在schema、表名、字段名、别名外层加方括号。 - 生成的SQL使用JSON格式返回: {{"success":true,"sql":"生成的SQL语句"}} - 问题与生成SQL无关时,直接回答: From ecb7df995c19e155c44877a1dadae70416b52f28 Mon Sep 17 00:00:00 2001 From: ulleo Date: Tue, 10 Jun 2025 13:41:19 +0800 Subject: [PATCH 03/11] feat: chat list css --- frontend/src/views/chat/ChatList.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/views/chat/ChatList.vue b/frontend/src/views/chat/ChatList.vue index b6066df1..b19902cf 100644 --- a/frontend/src/views/chat/ChatList.vue +++ b/frontend/src/views/chat/ChatList.vue @@ -165,6 +165,10 @@ function handleCommand(command: string | number | object, chat: Chat) { .title { flex: 1; + width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .icon-more { From 676edeb628babdf82ff071ed4c4e1438bc83e9c4 Mon Sep 17 00:00:00 2001 From: junjun Date: Tue, 10 Jun 2025 13:44:37 +0800 Subject: [PATCH 04/11] feat: json parse --- backend/apps/chat/api/chat.py | 3 ++- backend/apps/chat/task/llm.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/apps/chat/api/chat.py b/backend/apps/chat/api/chat.py index ed632f3a..e1f1d4dc 100644 --- a/backend/apps/chat/api/chat.py +++ b/backend/apps/chat/api/chat.py @@ -1,4 +1,5 @@ import json +import orjson import traceback from typing import List @@ -138,7 +139,7 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que # execute sql result = llm_service.execute_sql(sql=sql) llm_service.save_sql_data(session=session, data_obj=result) - yield json.dumps({'content': json.dumps(result, ensure_ascii=False), 'type': 'sql-data'}) + '\n\n' + yield json.dumps({'content': orjson.dumps(result).decode(), 'type': 'sql-data'}) + '\n\n' # generate chart chart_res = llm_service.generate_chart(session=session) diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index dee73241..c6fa330d 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -1,4 +1,5 @@ import json +import orjson import logging import warnings from typing import Any, List, Union, Dict @@ -218,7 +219,7 @@ class LLMService: def save_sql_data(self, session: SessionDep, data_obj: Dict[str, Any]): return save_sql_exec_data(session=session, record_id=self.record.id, - data=json.dumps(data_obj, ensure_ascii=False)) + data=orjson.dumps(data_obj).decode()) def finish(self, session: SessionDep): return finish_record(session=session, record_id=self.record.id) From 0afccc9267b606bf88c051109bdfe9a322c5b6a4 Mon Sep 17 00:00:00 2001 From: junjun Date: Tue, 10 Jun 2025 14:36:26 +0800 Subject: [PATCH 05/11] feat: mcp --- backend/apps/datasource/api/datasource.py | 2 +- backend/common/utils/whitelist.py | 4 +++- backend/main.py | 24 +++++++++++++++++++++-- backend/pyproject.toml | 1 + 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/backend/apps/datasource/api/datasource.py b/backend/apps/datasource/api/datasource.py index b0921673..08f2e6f7 100644 --- a/backend/apps/datasource/api/datasource.py +++ b/backend/apps/datasource/api/datasource.py @@ -18,7 +18,7 @@ router = APIRouter(tags=["datasource"], prefix="/datasource") path = "/opt/sqlbot/data/excel" -@router.get("/list") +@router.get("/list", operation_id="get_datasource_list") async def datasource_list(session: SessionDep): return get_datasource_list(session=session) diff --git a/backend/common/utils/whitelist.py b/backend/common/utils/whitelist.py index c02dfe44..78010184 100644 --- a/backend/common/utils/whitelist.py +++ b/backend/common/utils/whitelist.py @@ -20,7 +20,9 @@ wlist = [ "*.woff2", "*.ttf", "*.eot", - "*.otf" + "*.otf", + "/mcp", + "/mcp/message" ] class WhitelistChecker: diff --git a/backend/main.py b/backend/main.py index 2cb6b8d3..bef058cb 100644 --- a/backend/main.py +++ b/backend/main.py @@ -14,15 +14,20 @@ 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 + def run_migrations(): alembic_cfg = Config("alembic.ini") command.upgrade(alembic_cfg, "head") + + @asynccontextmanager async def lifespan(app: FastAPI): run_migrations() yield + 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}" @@ -38,6 +43,17 @@ app = FastAPI( lifespan=lifespan ) +mcp_app = FastAPI() + +mcp = FastApiMCP( + app, + name="SQLBot MCP Server", + description="SQLBot MCP Server", + include_operations=["get_datasource_list"] +) + +mcp.mount(mcp_app) + # Set all CORS enabled origins if settings.all_cors_origins: app.add_middleware( @@ -66,9 +82,13 @@ else: @app.get("/", include_in_schema=False) async def read_index(): return FileResponse(path=os.path.join(frontend_dist, "index.html")) - + + app.mount("/", StaticFiles(directory=frontend_dist), name="static") +mcp.setup_server() + if __name__ == "__main__": import uvicorn - uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) \ No newline at end of file + + uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 535c219b..41aaea6a 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -34,6 +34,7 @@ dependencies = [ "psycopg2-binary (>=2.9.10,<3.0.0)", "oracledb (>=3.1.1,<4.0.0)", "pyyaml (>=6.0.2,<7.0.0)", + "fastapi-mcp (>=0.3.4,<0.4.0)", ] [[tool.uv.index]] url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" From 81751d3c01e316b47d54f85033c1541fd35e7058 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Tue, 10 Jun 2025 16:18:17 +0800 Subject: [PATCH 06/11] perf: User Language --- .../8dc3b1bdbfef_010_upgrade_user_language.py | 33 +++++ backend/apps/system/api/user.py | 25 +++- backend/apps/system/crud/user.py | 6 + backend/apps/system/models/user.py | 1 + backend/apps/system/schemas/system_schema.py | 6 +- frontend/src/api/auth.ts | 3 +- frontend/src/api/login.ts | 3 +- .../components/Language-selector/index.vue | 63 ++++++++ frontend/src/components/layout/index.vue | 32 +--- frontend/src/i18n/index.ts | 7 +- frontend/src/router/watch.ts | 20 ++- frontend/src/stores/user.ts | 137 +++++++++++++++--- 12 files changed, 272 insertions(+), 64 deletions(-) create mode 100644 backend/alembic/versions/8dc3b1bdbfef_010_upgrade_user_language.py create mode 100644 frontend/src/components/Language-selector/index.vue diff --git a/backend/alembic/versions/8dc3b1bdbfef_010_upgrade_user_language.py b/backend/alembic/versions/8dc3b1bdbfef_010_upgrade_user_language.py new file mode 100644 index 00000000..cca99de1 --- /dev/null +++ b/backend/alembic/versions/8dc3b1bdbfef_010_upgrade_user_language.py @@ -0,0 +1,33 @@ +"""010_upgrade_user_language + +Revision ID: 8dc3b1bdbfef +Revises: 804b08ac329d +Create Date: 2025-06-10 11:21:35.257604 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '8dc3b1bdbfef' +down_revision = '804b08ac329d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + op.add_column('sys_user', sa.Column('language', sa.VARCHAR(length=255), server_default=sa.text("'zh-CN'::character varying"), nullable=False)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + + op.drop_column('sys_user', 'language') + + # ### end Alembic commands ### diff --git a/backend/apps/system/api/user.py b/backend/apps/system/api/user.py index 6992c883..754850e1 100644 --- a/backend/apps/system/api/user.py +++ b/backend/apps/system/api/user.py @@ -1,5 +1,7 @@ from fastapi import APIRouter, Depends, Request +from apps.system.crud.user import get_user_info from apps.system.models.user import user_grid +from apps.system.schemas.system_schema import UserLanguage from common.core.deps import CurrentUser, SessionDep from common.core.pagination import Paginator from common.core.schemas import PaginatedResponse, PaginationParams @@ -8,8 +10,12 @@ router = APIRouter(tags=["user"], prefix="/user") @router.get("/info") -async def user_info(current_user: CurrentUser): - return current_user.to_dict() +async def user_info(session: SessionDep, current_user: CurrentUser): + db_user = get_user_info(session=session, user_id=current_user.id) + if not db_user: + return {"message": "User not found"} + db_user.password = None + return db_user @router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[user_grid]) @@ -24,4 +30,17 @@ async def pager( return await paginator.get_paginated_response( model=user_grid, pagination=pagination, - **filters) \ No newline at end of file + **filters) + +@router.put("/language") +async def langChange(session: SessionDep, current_user: CurrentUser, language: UserLanguage): + lang = language.language + if lang not in ["zh-CN", "en"]: + return {"message": "Language not supported"} + db_user = get_user_info(session=session, user_id=current_user.id) + if not db_user: + return {"message": "User not found"} + db_user.language = lang + session.add(db_user) + session.commit() + return {"message": "Language changed successfully", "language": lang} \ No newline at end of file diff --git a/backend/apps/system/crud/user.py b/backend/apps/system/crud/user.py index a522fe24..89125544 100644 --- a/backend/apps/system/crud/user.py +++ b/backend/apps/system/crud/user.py @@ -10,6 +10,12 @@ def get_user_by_account(*, session: Session, account: str) -> sys_user | None: result_user = sys_user.model_validate(session_user) return result_user +def get_user_info(*, session: Session, user_id: int) -> sys_user | None: + db_user = session.get(user_grid, user_id) + if not db_user: + return None + return db_user + def authenticate(*, session: Session, account: str, password: str) -> sys_user | None: db_user = get_user_by_account(session=session, account=account) if not db_user: diff --git a/backend/apps/system/models/user.py b/backend/apps/system/models/user.py index 7fc12dd7..9d2eddf1 100644 --- a/backend/apps/system/models/user.py +++ b/backend/apps/system/models/user.py @@ -21,4 +21,5 @@ class user_grid(sys_user, table=True): email: str status: int create_time: int + language: str = Field(max_length=255, default="zh-CN") diff --git a/backend/apps/system/schemas/system_schema.py b/backend/apps/system/schemas/system_schema.py index 237612cd..f7eab2f7 100644 --- a/backend/apps/system/schemas/system_schema.py +++ b/backend/apps/system/schemas/system_schema.py @@ -2,4 +2,8 @@ from pydantic import BaseModel class model_status(BaseModel): status: bool - ids: list[int] \ No newline at end of file + ids: list[int] + + +class UserLanguage(BaseModel): + language: str \ No newline at end of file diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index d3ebe6e8..3850463c 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -5,5 +5,6 @@ export const userApi = { add: (data: any) => request.post('/settings/terminology', data), edit: (data: any) => request.put('/settings/terminology', data), delete: (id: number) => request.delete(`/settings/terminology/${id}`), - query: (id: number) => request.get(`/settings/terminology/${id}`) + query: (id: number) => request.get(`/settings/terminology/${id}`), + language: (data: any) => request.put('/user/language', data), } \ No newline at end of file diff --git a/frontend/src/api/login.ts b/frontend/src/api/login.ts index ccd6284a..64c80cef 100644 --- a/frontend/src/api/login.ts +++ b/frontend/src/api/login.ts @@ -8,5 +8,6 @@ export const AuthApi = { 'Content-Type': 'application/x-www-form-urlencoded' } }), - logout: () => request.post('/auth/logout') + logout: () => request.post('/auth/logout'), + info: () => request.get('/user/info') } \ No newline at end of file diff --git a/frontend/src/components/Language-selector/index.vue b/frontend/src/components/Language-selector/index.vue new file mode 100644 index 00000000..d9aaefda --- /dev/null +++ b/frontend/src/components/Language-selector/index.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/layout/index.vue b/frontend/src/components/layout/index.vue index e91f5931..68cbf836 100644 --- a/frontend/src/components/layout/index.vue +++ b/frontend/src/components/layout/index.vue @@ -77,21 +77,7 @@ Switch Layout Logout - - -
- Language - - - -
- -
+
@@ -121,6 +107,8 @@ Switch Layout Logout + + @@ -170,8 +158,9 @@ import icon_ai from '@/assets/svg/icon_ai.svg' import {ArrowLeftBold} from '@element-plus/icons-vue' import {useCache} from '@/utils/useCache' import { useI18n } from 'vue-i18n' +import LanguageSelector from '@/components/Language-selector/index.vue' -const { locale, t } = useI18n() +const { t } = useI18n() const {wsCache} = useCache() const topLayout = ref(false) const router = useRouter() @@ -234,9 +223,6 @@ const switchLayout = () => { wsCache.set('sqlbot-topbar-layout', topLayout.value) } -const changeLanguage = (lang: string) => { - locale.value = lang -} onMounted(() => { topLayout.value = wsCache.get('sqlbot-topbar-layout') || true }) @@ -558,12 +544,4 @@ onMounted(() => { } } } -.lang-switch { - cursor: pointer; - padding: 0 12px; - - &:hover { - color: var(--el-color-primary); - } -} \ No newline at end of file diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index 10cd6420..2166eede 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -3,11 +3,12 @@ import en from './en.json' import zhCN from './zh-CN.json' import elementEnLocale from 'element-plus-secondary/es/locale/lang/en' import elementZhLocale from 'element-plus-secondary/es/locale/lang/zh-cn' +import { useCache } from '@/utils/useCache' +const { wsCache } = useCache() const getDefaultLocale = () => { - /* const savedLang = localStorage.getItem('lang') - return savedLang || 'zh-CN' */ - return 'zh-CN' + const language = wsCache.get('user.language') + return language || 'zh-CN' } const messages = { diff --git a/frontend/src/router/watch.ts b/frontend/src/router/watch.ts index b13adfef..710d3c1d 100644 --- a/frontend/src/router/watch.ts +++ b/frontend/src/router/watch.ts @@ -1,19 +1,25 @@ import { ElMessage } from 'element-plus-secondary' import { useCache } from '@/utils/useCache' +import { useUserStore } from '@/stores/user' + +const userStore = useUserStore() const { wsCache } = useCache() const whiteList = ['/login'] export const watchRouter = (router: any) => { router.beforeEach(async (to: any, from: any, next: any) => { const token = wsCache.get('user.token') - if (!token) { - if (whiteList.includes(to.path)) { - next() - } else { - ElMessage.error('Please login first') - next('/login') - } + if (whiteList.includes(to.path)) { + next() return } + if (!token) { + ElMessage.error('Please login first') + next('/login') + return + } + if (!wsCache.get('user.uid')) { + await userStore.info() + } if (to.path === '/') { next('/chat') return diff --git a/frontend/src/stores/user.ts b/frontend/src/stores/user.ts index f4990698..a22a6237 100644 --- a/frontend/src/stores/user.ts +++ b/frontend/src/stores/user.ts @@ -2,29 +2,124 @@ import { defineStore } from 'pinia' import { ref } from 'vue' import { AuthApi } from '@/api/login' import { useCache } from '@/utils/useCache' +import { i18n } from '@/i18n' +import { store } from './index' + const { wsCache } = useCache() -export const useUserStore = defineStore('user', () => { - const token = ref('') - const username = ref('') - const login = async (formData: { username: string; password: string }) => { - username.value = formData.username - const res: any = await AuthApi.login(formData) - token.value = res.access_token - wsCache.set('user.token', token.value) - username.value = formData.username - } - const logout = () => { - token.value = '' - username.value = '' - wsCache.delete('user.token') - } - return { - token, - username, - login, - logout +interface UserState { + token: string + uid: string + name: string + oid: string + language: string + exp: number + time: number +} + +export const UserStore = defineStore('user', { + state: (): UserState => { + return { + token: null, + uid: null, + name: null, + oid: null, + language: 'zh-CN', + exp: null, + time: null + } + }, + getters: { + getToken(): string { + return this.token + }, + getUid(): string { + return this.uid + }, + getName(): string { + return this.name + }, + getOid(): string { + return this.oid + }, + getLanguage(): string { + return this.language + }, + getExp(): number { + return this.exp + }, + getTime(): number { + return this.time + } + }, + actions: { + + async login(formData: { username: string; password: string }) { + const res: any = await AuthApi.login(formData) + this.setToken(res.access_token) + }, + + logout() { + this.clear() + }, + + async info () { + const res: any = await AuthApi.info() + const res_data = res || {} + + const keys: string[] = ['uid', 'name', 'oid', 'language', 'exp', 'time'] + + keys.forEach(key => { + const dkey = key === 'uid' ? 'id' : key + this[key] = res_data[dkey] + wsCache.set('user.' + key, this[key]) + }) + this.setLanguage(this.language) + }, + setToken(token: string) { + wsCache.set('user.token', token) + this.token = token + }, + setExp(exp: number) { + wsCache.set('user.exp', exp) + this.exp = exp + }, + setTime(time: number) { + wsCache.set('user.time', time) + this.time = time + }, + setUid(uid: string) { + wsCache.set('user.uid', uid) + this.uid = uid + }, + setName(name: string) { + wsCache.set('user.name', name) + this.name = name + }, + setOid(oid: string) { + wsCache.set('user.oid', oid) + this.oid = oid + }, + setLanguage(language: string) { + if (!language || language === 'zh_CN') { + language = 'zh-CN' + } + wsCache.set('user.language', language) + this.language = language + i18n.global.locale.value = language + /* const { locale } = useI18n() + locale.value = language */ + // locale.setLang(language) + }, + clear() { + const keys: string[] = ['token', 'uid', 'name', 'oid', 'language', 'exp', 'time'] + keys.forEach(key => wsCache.delete('user.' + key)) + } } -}) \ No newline at end of file +}) + +export const useUserStore = () => { + return UserStore(store) +} \ No newline at end of file From 487474b52537a97acbbb83bf8163689196538a27 Mon Sep 17 00:00:00 2001 From: ulleo Date: Tue, 10 Jun 2025 16:20:12 +0800 Subject: [PATCH 07/11] feat: chart style --- backend/template.yaml | 7 +++-- frontend/package.json | 1 + frontend/src/views/chat/ChatAnswer.vue | 27 ++++++++++++------- .../src/views/chat/component/BaseG2Chart.ts | 6 +++++ .../src/views/chat/component/Component.vue | 4 +-- .../src/views/chat/component/charts/Table.ts | 24 ++++++++++++++++- 6 files changed, 54 insertions(+), 15 deletions(-) diff --git a/backend/template.yaml b/backend/template.yaml index 6cb4a885..40a18017 100644 --- a/backend/template.yaml +++ b/backend/template.yaml @@ -20,8 +20,8 @@ template: - 如果根据提供的表结构不能生成符合问题与条件的SQL,回答: {{"success":false,"message":"无法生成SQL的原因"}} - 如果问题是图表展示相关且与生成SQL查询无关时,请参考上一次回答的SQL来生成SQL - - 如果问题是图表展示相关,可参考的图表类型为表格(table)、条形图(bar)、折线图(line)或饼图(pie),返回的JSON: - {{"success":true,"sql":"生成的SQL语句","chart-type":"选择的图表类型(table/bar/line/pie)"}} + - 如果问题是图表展示相关,可参考的图表类型为表格(table)、柱状图(column)、条形图(bar)、折线图(line)或饼图(pie),返回的JSON: + {{"success":true,"sql":"生成的SQL语句","chart-type":"选择的图表类型(table/column/bar/line/pie)"}} ### 请遵守以下输出要求: 请逐步推理后给出答案 @@ -48,6 +48,9 @@ template: - 如果需要表格,则生成的 JSON 格式应为: {{"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 轴的列(有别名用别名)"}}}} + 必须从 SQL 查询列中提取“x”和“y”。 - 如果需要条形图,则生成的 JSON 格式应为: {{"type":"bar", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称", "value": "SQL 查询 x 轴的列(有别名用别名)"}}, "y": {{"name":"y轴的中文名称","value": "SQL 查询 y 轴的列(有别名用别名)"}}}} 必须从 SQL 查询列中提取“x”和“y”。 diff --git a/frontend/package.json b/frontend/package.json index 2d474153..75690d46 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "dayjs": "^1.11.13", "element-plus-secondary": "^1.0.0", "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "snowflake-id": "^1.1.0", "tinymce": "^5.8.2", "vue": "^3.5.13", diff --git a/frontend/src/views/chat/ChatAnswer.vue b/frontend/src/views/chat/ChatAnswer.vue index 8630b785..0b00e273 100644 --- a/frontend/src/views/chat/ChatAnswer.vue +++ b/frontend/src/views/chat/ChatAnswer.vue @@ -148,16 +148,18 @@ function onTypeChange(type: string) {