From 6728d05c4cb9d41f370e6840e72923afa6a4148e Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Wed, 16 Jul 2025 13:13:28 +0800 Subject: [PATCH 1/8] perf: Assistant chat ui --- backend/apps/chat/task/llm.py | 4 +-- .../src/assets/svg/icon_new_chat_outlined.svg | 5 +-- frontend/src/views/chat/index.vue | 32 +++++++++++++------ frontend/src/views/embedded/assistant.vue | 26 +++++++++++---- 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index 060bacc9..e5d3a083 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -296,7 +296,7 @@ class LLMService: # get schema if self.ds and not self.chat_question.db_schema: - self.chat_question.db_schema = get_table_schema(session=self.session, ds=self.ds) + self.chat_question.db_schema = get_table_schema(session=self.session, current_user=self.current_user, ds=self.ds) guess_msg: List[Union[BaseMessage, dict[str, Any]]] = [] guess_msg.append(SystemMessage(content=self.chat_question.guess_sys_question())) @@ -726,7 +726,7 @@ def run_task(llm_service: LLMService, in_chat: bool = True): yield orjson.dumps({'id': llm_service.ds.id, 'datasource_name': llm_service.ds.name, 'engine_type': llm_service.ds.type_name, 'type': 'datasource'}).decode() + '\n\n' - llm_service.chat_question.db_schema = get_table_schema(session=llm_service.session, ds=llm_service.ds) + llm_service.chat_question.db_schema = get_table_schema(session=llm_service.session, current_user=llm_service.current_user, ds=llm_service.ds) # generate sql sql_res = llm_service.generate_sql() diff --git a/frontend/src/assets/svg/icon_new_chat_outlined.svg b/frontend/src/assets/svg/icon_new_chat_outlined.svg index c1ff4c6f..3bb65ed6 100644 --- a/frontend/src/assets/svg/icon_new_chat_outlined.svg +++ b/frontend/src/assets/svg/icon_new_chat_outlined.svg @@ -1,4 +1 @@ - - - - + \ No newline at end of file diff --git a/frontend/src/views/chat/index.vue b/frontend/src/views/chat/index.vue index 65446265..87f283ee 100644 --- a/frontend/src/views/chat/index.vue +++ b/frontend/src/views/chat/index.vue @@ -292,7 +292,7 @@ const createNewChat = async () => { if (isAssistant.value) { const assistantChat = await assistantStore.setChat() if (assistantChat) { - onChatCreated(assistantChat as any) + onChatCreatedQuick(assistantChat as any) } return } @@ -317,6 +317,26 @@ function onClickHistory(chat: Chat) { scrollToBottom() } +function toAssistantHistory(chat: Chat) { + currentChat.value = new ChatInfo(chat) + if (chat !== undefined && chat.id !== undefined && !loading.value) { + currentChatId.value = chat.id + loading.value = true + chatApi + .get(chat.id) + .then((res) => { + const info = chatApi.toChatInfo(res) + if (info) { + currentChat.value = info + onClickHistory(info) + } + }) + .finally(() => { + loading.value = false + }) + } +} + function onChatDeleted(id: number) { console.log('deleted', id) } @@ -434,11 +454,6 @@ const sendMessage = async () => { loading.value = true isTyping.value = true - /* const assistantChat = await assistantStore.setChat() - if (assistantChat) { - onChatCreated(assistantChat as any) - } */ - const currentRecord = new ChatRecord() currentRecord.create_time = new Date() currentRecord.chat_id = currentChatId.value @@ -833,10 +848,9 @@ const getCurrentChatId = () => { } defineExpose({ getHistoryList, - onClickHistory, - onChatDeleted, - onChatRenamed, + toAssistantHistory, getCurrentChatId, + createNewChat, }) diff --git a/frontend/src/views/embedded/assistant.vue b/frontend/src/views/embedded/assistant.vue index 1ee4d982..04316739 100644 --- a/frontend/src/views/embedded/assistant.vue +++ b/frontend/src/views/embedded/assistant.vue @@ -11,6 +11,9 @@
+ + + @@ -41,10 +44,10 @@ @click="onClickHistory(chat)" > {{ chat.brief ?? 'Untitled' }} -
+
@@ -58,8 +61,9 @@ import { onBeforeMount, ref } from 'vue' import ChatComponent from '@/views/chat/index.vue' import AssistantGif from '@/assets/img/assistant.gif' import history from '@/assets/svg/chart/history.svg' -import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg' -import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg' +import icon_new_chat_outlined from '@/assets/svg/icon_new_chat_outlined.svg' +/* import IconOpeEdit from '@/assets/svg/operate/ope-edit.svg' +import IconOpeDelete from '@/assets/svg/operate/ope-delete.svg' */ import { useRoute } from 'vue-router' import { assistantApi } from '@/api/assistant' import { useAssistantStore } from '@/stores/assistant' @@ -72,21 +76,24 @@ const chatList = ref>([]) const drawer = ref(false) const currentId = ref() +const createChat = () => { + chatRef.value?.createNewChat() +} const openHistory = () => { chatList.value = chatRef.value?.getHistoryList() currentId.value = chatRef.value?.getCurrentChatId() drawer.value = true } const onClickHistory = (chat: any) => { - chatRef.value?.onClickHistory(chat) + chatRef.value?.toAssistantHistory(chat) } -const EditPen = (chat: any) => { +/* const EditPen = (chat: any) => { chatRef.value?.onChatRenamed(chat) } const Delete = (chat: any) => { chatRef.value?.onChatDeleted(chat.id) -} +} */ const validator = ref({ id: '', valid: false, @@ -155,8 +162,13 @@ onBeforeMount(async () => { z-index: 2009; position: absolute; top: 16px; + display: flex; + column-gap: 16px; // right: 16px; font-size: 22px; + i:first-child { + color: var(--primary-color); + } } .sqlbot-history-container { width: 100%; From 4627de0fb02c603f2d6b2453188a0ba383512250 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Wed, 16 Jul 2025 13:19:31 +0800 Subject: [PATCH 2/8] style: Assistant btn --- frontend/src/views/embedded/assistant.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/views/embedded/assistant.vue b/frontend/src/views/embedded/assistant.vue index 04316739..d7b67bbd 100644 --- a/frontend/src/views/embedded/assistant.vue +++ b/frontend/src/views/embedded/assistant.vue @@ -160,7 +160,7 @@ onBeforeMount(async () => { .sqlbot-top-btn { right: 85px; z-index: 2009; - position: absolute; + position: fixed; top: 16px; display: flex; column-gap: 16px; From 4f58eb531c424a31621f0276811e848fa1b25055 Mon Sep 17 00:00:00 2001 From: xuwei-fit2cloud Date: Wed, 16 Jul 2025 14:20:00 +0800 Subject: [PATCH 3/8] chore: add typos config file --- .typos.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .typos.toml diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 00000000..bb9c4600 --- /dev/null +++ b/.typos.toml @@ -0,0 +1,5 @@ +[default.extend-words] +[default.extend-identifiers] +maintain_column_froms = "maintain_column_froms" +[files] +extend-exclude = ["frontend/public", "backend/alembic/versions"] From 89a5909c7d5afeec827d692df690043d9f49821c Mon Sep 17 00:00:00 2001 From: junjun Date: Wed, 16 Jul 2025 14:24:55 +0800 Subject: [PATCH 4/8] feat: field enum --- backend/apps/datasource/api/datasource.py | 7 ++++++- backend/apps/datasource/crud/datasource.py | 21 +++++++++++++++++-- backend/apps/db/constant.py | 24 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 backend/apps/db/constant.py diff --git a/backend/apps/datasource/api/datasource.py b/backend/apps/datasource/api/datasource.py index b0921673..6e2761f5 100644 --- a/backend/apps/datasource/api/datasource.py +++ b/backend/apps/datasource/api/datasource.py @@ -9,7 +9,7 @@ from fastapi import APIRouter, File, UploadFile, HTTPException from apps.db.engine import create_table, get_data_engine, insert_data from common.core.deps import SessionDep, CurrentUser from ..crud.datasource import get_datasource_list, check_status, create_ds, update_ds, delete_ds, getTables, getFields, \ - execSql, update_table_and_fields, getTablesByDs, chooseTables, preview, updateTable, updateField, get_ds + execSql, update_table_and_fields, getTablesByDs, chooseTables, preview, updateTable, updateField, get_ds, fieldEnum from ..crud.field import get_fields_by_table_id from ..crud.table import get_tables_by_ds_id from ..models.datasource import CoreDatasource, CreateDatasource, TableObj, CoreTable, CoreField @@ -103,6 +103,11 @@ async def edit_local(session: SessionDep, id: int, data: TableObj): return preview(session, id, data) +@router.post("/fieldEnum/{id}") +async def field_enum(session: SessionDep, id: int): + return fieldEnum(session, id) + + @router.post("/uploadExcel") async def upload_excel(session: SessionDep, file: UploadFile = File(...)): ALLOWED_EXTENSIONS = {"xlsx", "xls", "csv"} diff --git a/backend/apps/datasource/crud/datasource.py b/backend/apps/datasource/crud/datasource.py index 9430a79f..f2a31b82 100644 --- a/backend/apps/datasource/crud/datasource.py +++ b/backend/apps/datasource/crud/datasource.py @@ -9,9 +9,9 @@ from sqlbot_xpack.permissions.models.ds_rules import DsRules from sqlmodel import select from apps.datasource.utils.utils import aes_decrypt +from apps.db.constant import DB from apps.db.db import get_engine, get_tables, get_fields, exec_sql -from apps.db.engine import get_engine_config -from apps.db.engine import get_engine_conn +from apps.db.engine import get_engine_config, get_engine_conn from apps.db.type import db_type_relation from common.core.deps import SessionDep, CurrentUser from common.utils.utils import deepcopy_ignore_extra @@ -245,6 +245,23 @@ def preview(session: SessionDep, id: int, data: TableObj): return exec_sql(ds, sql) +def fieldEnum(session: SessionDep, id: int): + field = session.query(CoreField).filter(CoreField.id == id).first() + if field is None: + return [] + table = session.query(CoreTable).filter(CoreTable.id == field.table_id).first() + if table is None: + return [] + ds = session.query(CoreDatasource).filter(CoreDatasource.id == table.ds_id).first() + if ds is None: + return [] + + db = DB.get_db(ds.type) + sql = f"""SELECT DISTINCT {db.prefix}{field.field_name}{db.suffix} FROM {db.prefix}{table.table_name}{db.suffix}""" + res = exec_sql(ds, sql) + return [item.get(res.get('fields')[0]) for item in res.get('data')] + + def updateNum(session: SessionDep, ds: CoreDatasource): all_tables = get_tables(ds) selected_tables = get_tables_by_ds_id(session, ds.id) diff --git a/backend/apps/db/constant.py b/backend/apps/db/constant.py new file mode 100644 index 00000000..51126272 --- /dev/null +++ b/backend/apps/db/constant.py @@ -0,0 +1,24 @@ +# Author: Junjun +# Date: 2025/7/16 + +from enum import Enum + + +class DB(Enum): + mysql = ('mysql', '`', '`') + sqlServer = ('sqlServer', '[', ']') + pg = ('pg', '"', '"') + excel = ('excel', '"', '"') + oracle = ('oracle', '"', '"') + + def __init__(self, type, prefix, suffix): + self.type = type + self.prefix = prefix + self.suffix = suffix + + @classmethod + def get_db(cls, type): + for db in cls: + if db.type == type: + return db + raise ValueError(f"Invalid db type: {type}") From 9a02f6d44247f8fbe662175de6f8aaf2ed50d124 Mon Sep 17 00:00:00 2001 From: ulleo Date: Wed, 16 Jul 2025 14:33:23 +0800 Subject: [PATCH 5/8] feat: chat greeting css --- frontend/src/views/chat/index.vue | 39 +++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/frontend/src/views/chat/index.vue b/frontend/src/views/chat/index.vue index 87f283ee..b91f96b4 100644 --- a/frontend/src/views/chat/index.vue +++ b/frontend/src/views/chat/index.vue @@ -72,11 +72,14 @@ v-if="currentChatId === undefined" size="large" type="primary" + class="greeting-btn" @click="createNewChat" > - - - + + + + + {{ t('qa.start_sqlbot') }}
@@ -893,7 +896,6 @@ defineExpose({ .chat-record-list { padding: 0 0 20px 0; - background: rgba(255, 255, 255, 1); border-radius: 0 12px 12px 0; &.hide-sidebar { @@ -988,6 +990,35 @@ defineExpose({ font-size: 16px; line-height: 24px; } + + .greeting-btn { + width: 100%; + height: 88px; + + border-style: dashed; + + .inner-icon { + display: flex; + flex-direction: row; + align-items: center; + + margin-right: 6px; + } + + font-size: 16px; + line-height: 24px; + font-weight: 500; + + --ed-button-text-color: rgba(28, 186, 144, 1); + --ed-button-hover-text-color: rgba(28, 186, 144, 1); + --ed-button-active-text-color: rgba(28, 186, 144, 1); + --ed-button-bg-color: rgba(248, 249, 250, 1); + --ed-button-hover-bg-color: rgba(28, 186, 144, 0.1); + --ed-button-border-color: rgba(217, 220, 223, 1); + --ed-button-hover-border-color: rgba(28, 186, 144, 1); + --ed-button-active-bg-color: rgba(28, 186, 144, 0.2); + --ed-button-active-border-color: rgba(28, 186, 144, 1); + } } } From cac8dc8e0b9f53ba4ad6a68bdec7d88d227e741a Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Wed, 16 Jul 2025 14:39:53 +0800 Subject: [PATCH 6/8] feat: Search user api --- backend/apps/system/api/workspace.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/backend/apps/system/api/workspace.py b/backend/apps/system/api/workspace.py index ee67bfd3..8ef6866b 100644 --- a/backend/apps/system/api/workspace.py +++ b/backend/apps/system/api/workspace.py @@ -42,6 +42,29 @@ async def option_pager( stmt=stmt, pagination=pagination, ) + +@router.get("/uws/option", response_model=UserWsOption) +async def option_user( + session: SessionDep, + current_user: CurrentUser, + keyword: str = Query(description="搜索关键字") + ): + if (not current_user.isAdmin) and current_user.weight == 0: + raise RuntimeError("no permission to execute this api") + oid = current_user.oid + + stmt = select(UserModel.id, UserModel.account, UserModel.name).where( + ~exists().where(UserWsModel.uid == UserModel.id, UserWsModel.oid == oid) + ) + + if keyword: + stmt = stmt.where( + or_( + UserModel.account == int(keyword), + UserModel.name == keyword, + ) + ) + return session.exec(stmt).first() @router.get("/uws/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[WorkspaceUser]) async def pager( From 1702945fcdba426ddd920e28502dafa3fe40a537 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Wed, 16 Jul 2025 14:59:43 +0800 Subject: [PATCH 7/8] feat: Change user weight api --- backend/apps/system/api/workspace.py | 16 +++++++++++++++- backend/apps/system/schemas/system_schema.py | 6 +++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/apps/system/api/workspace.py b/backend/apps/system/api/workspace.py index 8ef6866b..ecedcca0 100644 --- a/backend/apps/system/api/workspace.py +++ b/backend/apps/system/api/workspace.py @@ -3,7 +3,7 @@ from fastapi import APIRouter, Query from sqlmodel import exists, or_, select from apps.system.models.system_model import UserWsModel, WorkspaceBase, WorkspaceEditor, WorkspaceModel from apps.system.models.user import UserModel -from apps.system.schemas.system_schema import UserWsBase, UserWsDTO, UserWsOption, WorkspaceUser +from apps.system.schemas.system_schema import UserWsBase, UserWsDTO, UserWsEditor, UserWsOption, WorkspaceUser from common.core.deps import CurrentUser, SessionDep, Trans from common.core.pagination import Paginator from common.core.schemas import PaginatedResponse, PaginationParams @@ -118,6 +118,20 @@ async def create(session: SessionDep, creator: UserWsDTO): session.add_all(db_model_list) session.commit() +@router.put("/uws") +async def edit(session: SessionDep, editor: UserWsEditor): + if not editor.oid or not editor.uid: + raise RuntimeError("param [oid, uid] miss") + db_model = session.exec(select(UserWsModel).where(UserWsModel.uid.in_(editor.uid), UserWsModel.oid == editor.oid)).first() + if not db_model: + raise RuntimeError("uws not exist") + if editor.weight == db_model.weight: + return + + db_model.weight = editor.weight + session.add(db_model) + session.commit() + @router.delete("/uws") async def delete(session: SessionDep, dto: UserWsBase): db_model_list: list[UserWsModel] = session.exec(select(UserWsModel).where(UserWsModel.uid.in_(dto.uid_list), UserWsModel.oid == dto.oid)).all() diff --git a/backend/apps/system/schemas/system_schema.py b/backend/apps/system/schemas/system_schema.py index 9ca08457..8b1f3b99 100644 --- a/backend/apps/system/schemas/system_schema.py +++ b/backend/apps/system/schemas/system_schema.py @@ -50,7 +50,11 @@ class UserWsBase(BaseModel): oid: int class UserWsDTO(UserWsBase): weight: int = 0 - + +class UserWsEditor(BaseModel): + uid: int + oid: int + weight: int = 0 class UserInfoDTO(UserEditor): language: str = "zh-CN" From 9b2aeadff9afa1dc385a10d27f276ef749b46a49 Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Wed, 16 Jul 2025 15:14:53 +0800 Subject: [PATCH 8/8] perf: Change user weight api --- backend/apps/system/api/workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/apps/system/api/workspace.py b/backend/apps/system/api/workspace.py index ecedcca0..8725cffc 100644 --- a/backend/apps/system/api/workspace.py +++ b/backend/apps/system/api/workspace.py @@ -122,7 +122,7 @@ async def create(session: SessionDep, creator: UserWsDTO): async def edit(session: SessionDep, editor: UserWsEditor): if not editor.oid or not editor.uid: raise RuntimeError("param [oid, uid] miss") - db_model = session.exec(select(UserWsModel).where(UserWsModel.uid.in_(editor.uid), UserWsModel.oid == editor.oid)).first() + db_model = session.exec(select(UserWsModel).where(UserWsModel.uid == editor.uid, UserWsModel.oid == editor.oid)).first() if not db_model: raise RuntimeError("uws not exist") if editor.weight == db_model.weight: