From a99573c4c3f630212209211665bed9bcb5cc4f00 Mon Sep 17 00:00:00 2001 From: dataeaseShu Date: Fri, 19 Sep 2025 10:05:28 +0800 Subject: [PATCH 001/864] fix(SQL Example Library): Multiple selection count error --- frontend/src/views/system/embedded/Page.vue | 1 + frontend/src/views/system/member/index.vue | 1 + frontend/src/views/system/professional/index.vue | 1 + frontend/src/views/system/training/index.vue | 1 + 4 files changed, 4 insertions(+) diff --git a/frontend/src/views/system/embedded/Page.vue b/frontend/src/views/system/embedded/Page.vue index 8f85fae1b..3f3b08e1f 100644 --- a/frontend/src/views/system/embedded/Page.vue +++ b/frontend/src/views/system/embedded/Page.vue @@ -188,6 +188,7 @@ const search = () => { toggleRowLoading.value = true fieldList.value = res.items pageInfo.total = res.total + searchLoading.value = false nextTick(() => { handleToggleRowSelection() }) diff --git a/frontend/src/views/system/member/index.vue b/frontend/src/views/system/member/index.vue index e108f7fcf..fcabb76f6 100644 --- a/frontend/src/views/system/member/index.vue +++ b/frontend/src/views/system/member/index.vue @@ -141,6 +141,7 @@ const search = () => { toggleRowLoading.value = true fieldList.value = res.items pageInfo.total = res.total + searchLoading.value = false nextTick(() => { handleToggleRowSelection() }) diff --git a/frontend/src/views/system/professional/index.vue b/frontend/src/views/system/professional/index.vue index 55ad2859e..79bcca873 100644 --- a/frontend/src/views/system/professional/index.vue +++ b/frontend/src/views/system/professional/index.vue @@ -185,6 +185,7 @@ const search = () => { toggleRowLoading.value = true fieldList.value = res.data pageInfo.total = res.total_count + searchLoading.value = false nextTick(() => { handleToggleRowSelection() }) diff --git a/frontend/src/views/system/training/index.vue b/frontend/src/views/system/training/index.vue index e6e790558..91eec5336 100644 --- a/frontend/src/views/system/training/index.vue +++ b/frontend/src/views/system/training/index.vue @@ -201,6 +201,7 @@ const search = () => { toggleRowLoading.value = true fieldList.value = res.data pageInfo.total = res.total_count + searchLoading.value = false nextTick(() => { handleToggleRowSelection() }) From 3d3685e4be8506dbc900f2bbd4074a1bae9a9da5 Mon Sep 17 00:00:00 2001 From: ulleo Date: Fri, 19 Sep 2025 10:20:27 +0800 Subject: [PATCH 002/864] fix: always get terminology in default workspace after select ds --- backend/apps/chat/task/llm.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index f26da443b..967bdf620 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -16,7 +16,7 @@ from langchain.chat_models.base import BaseChatModel from langchain_community.utilities import SQLDatabase from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage, AIMessage, BaseMessageChunk -from sqlalchemy import select +from sqlalchemy import and_, select from sqlalchemy.orm import sessionmaker from sqlmodel import Session @@ -404,9 +404,8 @@ def select_datasource(self): if self.current_assistant and self.current_assistant.type != 4: _ds_list = get_assistant_ds(session=self.session, llm_service=self) else: - oid: str = self.current_user.oid stmt = select(CoreDatasource.id, CoreDatasource.name, CoreDatasource.description).where( - CoreDatasource.oid == oid) + and_(CoreDatasource.oid == self.current_user.oid)) _ds_list = [ { "id": ds.id, @@ -424,7 +423,7 @@ def select_datasource(self): full_thinking_text = '' full_text = '' - + json_str: Optional[str] = None if not ignore_auto_select: _ds_list_dict = [] for _ds in _ds_list: @@ -471,6 +470,8 @@ def select_datasource(self): token_usage=token_usage) json_str = extract_nested_json(full_text) + if json_str is None: + raise SingleMessageError(f'Cannot parse datasource from answer: {full_text}') _error: Exception | None = None _datasource: int | None = None @@ -522,11 +523,11 @@ def select_datasource(self): engine_type=_engine_type) if self.ds: oid = self.ds.oid if isinstance(self.ds, CoreDatasource) else 1 - dsId = self.ds.id if isinstance(self.ds, CoreDatasource) else None - - self.chat_question.terminologies = get_terminology_template(self.session, self.chat_question.question, 1) - self.chat_question.data_training = get_training_template(self.session, self.chat_question.question, dsId, oid) - + ds_id = self.ds.id if isinstance(self.ds, CoreDatasource) else None + + self.chat_question.terminologies = get_terminology_template(self.session, self.chat_question.question, oid) + self.chat_question.data_training = get_training_template(self.session, self.chat_question.question, ds_id, + oid) self.init_messages() @@ -938,10 +939,11 @@ def run_task(self, in_chat: bool = True): try: if self.ds: oid = self.ds.oid if isinstance(self.ds, CoreDatasource) else 1 - dsId = self.ds.id if isinstance(self.ds, CoreDatasource) else None - self.chat_question.terminologies = get_terminology_template(self.session, self.chat_question.question, oid) - self.chat_question.data_training = get_training_template(self.session, self.chat_question.question, dsId, oid) - + ds_id = self.ds.id if isinstance(self.ds, CoreDatasource) else None + self.chat_question.terminologies = get_terminology_template(self.session, self.chat_question.question, + oid) + self.chat_question.data_training = get_training_template(self.session, self.chat_question.question, + ds_id, oid) self.init_messages() From 350db1e321951f3883337616ee5cb4bfca132d71 Mon Sep 17 00:00:00 2001 From: xuwei-fit2cloud Date: Fri, 19 Sep 2025 10:59:22 +0800 Subject: [PATCH 003/864] Add privileged mode to sqlbot container --- installer/sqlbot/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/installer/sqlbot/docker-compose.yml b/installer/sqlbot/docker-compose.yml index 1e1aa8f8b..3e6492627 100644 --- a/installer/sqlbot/docker-compose.yml +++ b/installer/sqlbot/docker-compose.yml @@ -3,6 +3,7 @@ services: image: registry.cn-qingdao.aliyuncs.com/dataease/sqlbot:SQLBOT_TAG container_name: sqlbot restart: always + privileged: true networks: - sqlbot-network ports: From f074992de0588f0afb18dff82f0a9ad65acd271a Mon Sep 17 00:00:00 2001 From: xuwei-fit2cloud Date: Fri, 19 Sep 2025 11:00:34 +0800 Subject: [PATCH 004/864] Bump version from 1.1.3 to 1.1.4 --- backend/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/pyproject.toml b/backend/pyproject.toml index a1c5b4e32..8ccdd5851 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sqlbot" -version = "1.1.3" +version = "1.1.4" description = "" requires-python = "==3.11.*" dependencies = [ From 4285ec386bb5104d8029e52ac8299f4209ae7aee Mon Sep 17 00:00:00 2001 From: junjun Date: Fri, 19 Sep 2025 11:37:32 +0800 Subject: [PATCH 005/864] refactor: Optimization Elasticsearch error prompt --- backend/apps/db/es_engine.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/apps/db/es_engine.py b/backend/apps/db/es_engine.py index 75e7980b5..f8cf1f2f0 100644 --- a/backend/apps/db/es_engine.py +++ b/backend/apps/db/es_engine.py @@ -8,6 +8,7 @@ from elasticsearch import Elasticsearch from apps.datasource.models.datasource import DatasourceConf +from common.error import SingleMessageError def get_es_connect(conf: DatasourceConf): @@ -109,6 +110,8 @@ def get_es_data_by_http(conf: DatasourceConf, sql: str): # print(response.json()) res = response.json() + if res.get('error'): + raise SingleMessageError(json.dumps(res)) fields = res.get('columns') result = res.get('rows') return result, fields From a426c39328799e7fe0496911d60d5f571242b76c Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Fri, 19 Sep 2025 12:18:30 +0800 Subject: [PATCH 006/864] perf: Authentication Failure Prompt Optimization --- frontend/src/assets/svg/401.svg | 29 +++++++++++++++++++++++++ frontend/src/router/index.ts | 8 +++++++ frontend/src/router/watch.ts | 2 +- frontend/src/utils/request.ts | 11 ++++++++++ frontend/src/views/error/index.vue | 34 ++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 frontend/src/assets/svg/401.svg create mode 100644 frontend/src/views/error/index.vue diff --git a/frontend/src/assets/svg/401.svg b/frontend/src/assets/svg/401.svg new file mode 100644 index 000000000..d02779fae --- /dev/null +++ b/frontend/src/assets/svg/401.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 0b5adb2f1..00fad1666 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -21,6 +21,7 @@ import Appearance from '@/views/system/appearance/index.vue' import Permission from '@/views/system/permission/index.vue' import User from '@/views/system/user/User.vue' import Workspace from '@/views/system/workspace/index.vue' +import Page401 from '@/views/error/index.vue' import { i18n } from '@/i18n' import { watchRouter } from './watch' @@ -207,6 +208,13 @@ export const routes = [ name: 'assistantTest', component: assistantTest, }, + { + path: '/401', + name: '401', + hidden: true, + meta: {}, + component: Page401, + }, ] const router = createRouter({ history: createWebHashHistory(), diff --git a/frontend/src/router/watch.ts b/frontend/src/router/watch.ts index fe08edd4f..ae0d66318 100644 --- a/frontend/src/router/watch.ts +++ b/frontend/src/router/watch.ts @@ -8,7 +8,7 @@ const appearanceStore = useAppearanceStoreWithOut() const userStore = useUserStore() const { wsCache } = useCache() const whiteList = ['/login'] -const assistantWhiteList = ['/assistant', '/embeddedPage'] +const assistantWhiteList = ['/assistant', '/embeddedPage', '/401'] export const watchRouter = (router: any) => { router.beforeEach(async (to: any, from: any, next: any) => { await loadXpackStatic() diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts index f222fae5b..63b6ed709 100644 --- a/frontend/src/utils/request.ts +++ b/frontend/src/utils/request.ts @@ -11,10 +11,12 @@ import axios, { import { useCache } from '@/utils/useCache' import { getLocale } from './utils' import { useAssistantStore } from '@/stores/assistant' +import { useRouter } from 'vue-router' // import { i18n } from '@/i18n' // const t = i18n.global.t const assistantStore = useAssistantStore() const { wsCache } = useCache() +const router = useRouter() // Response data structure export interface ApiResponse { code: number @@ -193,6 +195,15 @@ class HttpService { ? error.response.data.toString() : 'Unauthorized, please login again' // Redirect to login page if needed + if (assistantStore.getAssistant) { + wsCache.delete('user.token') + if (router?.push) { + router.push(`/401?title=${encodeURIComponent(errorMessage)}`) + } else { + window.location.href = `/#/401?title=${encodeURIComponent(errorMessage)}` + } + return + } ElMessage({ message: errorMessage, type: 'error', diff --git a/frontend/src/views/error/index.vue b/frontend/src/views/error/index.vue new file mode 100644 index 000000000..ea9e7df11 --- /dev/null +++ b/frontend/src/views/error/index.vue @@ -0,0 +1,34 @@ + + + + From 919378ffc137415f95fa26cfb9c5d852abb51ab5 Mon Sep 17 00:00:00 2001 From: junjun Date: Fri, 19 Sep 2025 13:55:24 +0800 Subject: [PATCH 007/864] feat: Vector retrieval matches datasource --- backend/apps/chat/task/llm.py | 103 +++++++++--------- .../apps/datasource/embedding/ds_embedding.py | 5 +- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index 967bdf620..9024dfa99 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -32,6 +32,7 @@ from apps.data_training.curd.data_training import get_training_template from apps.datasource.crud.datasource import get_table_schema from apps.datasource.crud.permission import get_row_permission_filters, is_normal_user +from apps.datasource.embedding.ds_embedding import get_ds_embedding from apps.datasource.models.datasource import CoreDatasource from apps.db.db import exec_sql, get_version, check_connection from apps.system.crud.assistant import AssistantOutDs, AssistantOutDsFactory, get_assistant_ds @@ -423,61 +424,65 @@ def select_datasource(self): full_thinking_text = '' full_text = '' - json_str: Optional[str] = None if not ignore_auto_select: - _ds_list_dict = [] - for _ds in _ds_list: - _ds_list_dict.append(_ds) - datasource_msg.append( - HumanMessage(self.chat_question.datasource_user_question(orjson.dumps(_ds_list_dict).decode()))) - - self.current_logs[OperationEnum.CHOOSE_DATASOURCE] = start_log(session=self.session, - ai_modal_id=self.chat_question.ai_modal_id, - ai_modal_name=self.chat_question.ai_modal_name, - operate=OperationEnum.CHOOSE_DATASOURCE, - record_id=self.record.id, - full_message=[{'type': msg.type, - 'content': msg.content} - for - msg in datasource_msg]) - - token_usage = {} - res = self.llm.stream(datasource_msg) - for chunk in res: - SQLBotLogUtil.info(chunk) - reasoning_content_chunk = '' - if 'reasoning_content' in chunk.additional_kwargs: - reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') - # else: - # reasoning_content_chunk = chunk.get('reasoning_content') - if reasoning_content_chunk is None: + if settings.EMBEDDING_ENABLED: + ds = get_ds_embedding(self.session, self.current_user, _ds_list, self.chat_question.question) + yield {'content': '{"id":' + str(ds.get('id')) + '}'} + else: + _ds_list_dict = [] + for _ds in _ds_list: + _ds_list_dict.append(_ds) + datasource_msg.append( + HumanMessage(self.chat_question.datasource_user_question(orjson.dumps(_ds_list_dict).decode()))) + + self.current_logs[OperationEnum.CHOOSE_DATASOURCE] = start_log(session=self.session, + ai_modal_id=self.chat_question.ai_modal_id, + ai_modal_name=self.chat_question.ai_modal_name, + operate=OperationEnum.CHOOSE_DATASOURCE, + record_id=self.record.id, + full_message=[{'type': msg.type, + 'content': msg.content} + for + msg in datasource_msg]) + + token_usage = {} + res = self.llm.stream(datasource_msg) + for chunk in res: + SQLBotLogUtil.info(chunk) reasoning_content_chunk = '' - full_thinking_text += reasoning_content_chunk - - full_text += chunk.content - yield {'content': chunk.content, 'reasoning_content': reasoning_content_chunk} - get_token_usage(chunk, token_usage) - datasource_msg.append(AIMessage(full_text)) - - self.current_logs[OperationEnum.CHOOSE_DATASOURCE] = end_log(session=self.session, - log=self.current_logs[ - OperationEnum.CHOOSE_DATASOURCE], - full_message=[ - {'type': msg.type, - 'content': msg.content} - for msg in datasource_msg], - reasoning_content=full_thinking_text, - token_usage=token_usage) - - json_str = extract_nested_json(full_text) - if json_str is None: - raise SingleMessageError(f'Cannot parse datasource from answer: {full_text}') + if 'reasoning_content' in chunk.additional_kwargs: + reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '') + # else: + # reasoning_content_chunk = chunk.get('reasoning_content') + if reasoning_content_chunk is None: + reasoning_content_chunk = '' + full_thinking_text += reasoning_content_chunk + + full_text += chunk.content + yield {'content': chunk.content, 'reasoning_content': reasoning_content_chunk} + get_token_usage(chunk, token_usage) + datasource_msg.append(AIMessage(full_text)) + + self.current_logs[OperationEnum.CHOOSE_DATASOURCE] = end_log(session=self.session, + log=self.current_logs[ + OperationEnum.CHOOSE_DATASOURCE], + full_message=[ + {'type': msg.type, + 'content': msg.content} + for msg in datasource_msg], + reasoning_content=full_thinking_text, + token_usage=token_usage) + + json_str = extract_nested_json(full_text) + if json_str is None: + raise SingleMessageError(f'Cannot parse datasource from answer: {full_text}') + ds = orjson.loads(json_str) _error: Exception | None = None _datasource: int | None = None _engine_type: str | None = None try: - data: dict = _ds_list[0] if ignore_auto_select else orjson.loads(json_str) + data: dict = _ds_list[0] if ignore_auto_select else ds if data.get('id') and data.get('id') != 0: _datasource = data['id'] @@ -516,7 +521,7 @@ def select_datasource(self): except Exception as e: _error = e - if not ignore_auto_select: + if not ignore_auto_select and not settings.EMBEDDING_ENABLED: self.record = save_select_datasource_answer(session=self.session, record_id=self.record.id, answer=orjson.dumps({'content': full_text}).decode(), datasource=_datasource, diff --git a/backend/apps/datasource/embedding/ds_embedding.py b/backend/apps/datasource/embedding/ds_embedding.py index f0524b763..48ef07ef2 100644 --- a/backend/apps/datasource/embedding/ds_embedding.py +++ b/backend/apps/datasource/embedding/ds_embedding.py @@ -5,7 +5,8 @@ import traceback from apps.ai_model.embedding import EmbeddingModelCache -from apps.datasource.crud.datasource import get_table_schema, get_ds +from apps.datasource.crud.datasource import get_table_schema +from apps.datasource.models.datasource import CoreDatasource from common.core.deps import SessionDep, CurrentUser @@ -28,7 +29,7 @@ def get_ds_embedding(session: SessionDep, current_user: CurrentUser, _ds_list, q _list = [] for _ds in _ds_list: if _ds.get('id'): - ds = get_ds(session, _ds.get('id')) + ds = session.get(CoreDatasource, _ds.get('id')) table_schema = get_table_schema(session, current_user, ds) ds_info = f"{ds.name}, {ds.description}\n" From 336ffcf2a11f550579b065db0801929eb99103ee Mon Sep 17 00:00:00 2001 From: junjun Date: Fri, 19 Sep 2025 14:49:04 +0800 Subject: [PATCH 008/864] feat: Vector retrieval matches datasource --- backend/apps/datasource/embedding/ds_embedding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/apps/datasource/embedding/ds_embedding.py b/backend/apps/datasource/embedding/ds_embedding.py index 48ef07ef2..afa1ec693 100644 --- a/backend/apps/datasource/embedding/ds_embedding.py +++ b/backend/apps/datasource/embedding/ds_embedding.py @@ -50,7 +50,7 @@ def get_ds_embedding(session: SessionDep, current_user: CurrentUser, _ds_list, q _list[index]['cosine_similarity'] = cosine_similarity(q_embedding, item) _list.sort(key=lambda x: x['cosine_similarity'], reverse=True) - print(json.dumps(_list)) + print(len(_list)) ds = _list[0].get('ds') return {"id": ds.id, "name": ds.name, "description": ds.description} except Exception: From eebb601a6dd854733823c17010eb6860465b4a46 Mon Sep 17 00:00:00 2001 From: junjun Date: Fri, 19 Sep 2025 15:04:37 +0800 Subject: [PATCH 009/864] feat: Vector retrieval matches datasource --- backend/apps/datasource/embedding/ds_embedding.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/apps/datasource/embedding/ds_embedding.py b/backend/apps/datasource/embedding/ds_embedding.py index afa1ec693..2b637e181 100644 --- a/backend/apps/datasource/embedding/ds_embedding.py +++ b/backend/apps/datasource/embedding/ds_embedding.py @@ -8,6 +8,7 @@ from apps.datasource.crud.datasource import get_table_schema from apps.datasource.models.datasource import CoreDatasource from common.core.deps import SessionDep, CurrentUser +from common.utils.utils import SQLBotLogUtil def cosine_similarity(vec_a, vec_b): @@ -50,7 +51,10 @@ def get_ds_embedding(session: SessionDep, current_user: CurrentUser, _ds_list, q _list[index]['cosine_similarity'] = cosine_similarity(q_embedding, item) _list.sort(key=lambda x: x['cosine_similarity'], reverse=True) - print(len(_list)) + # print(len(_list)) + SQLBotLogUtil.info(json.dumps( + [{"id": ele.get("id"), "name": ele.get("name"), "cosine_similarity": ele.get("cosine_similarity")} + for ele in _list])) ds = _list[0].get('ds') return {"id": ds.id, "name": ds.name, "description": ds.description} except Exception: From 632fad4dec91cb9c96caecbe0f952c6e90c12a58 Mon Sep 17 00:00:00 2001 From: junjun Date: Fri, 19 Sep 2025 15:16:49 +0800 Subject: [PATCH 010/864] feat: Vector retrieval matches datasource --- backend/apps/datasource/embedding/ds_embedding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/apps/datasource/embedding/ds_embedding.py b/backend/apps/datasource/embedding/ds_embedding.py index 2b637e181..a8875744e 100644 --- a/backend/apps/datasource/embedding/ds_embedding.py +++ b/backend/apps/datasource/embedding/ds_embedding.py @@ -53,7 +53,7 @@ def get_ds_embedding(session: SessionDep, current_user: CurrentUser, _ds_list, q _list.sort(key=lambda x: x['cosine_similarity'], reverse=True) # print(len(_list)) SQLBotLogUtil.info(json.dumps( - [{"id": ele.get("id"), "name": ele.get("name"), "cosine_similarity": ele.get("cosine_similarity")} + [{"id": ele.get("id"), "name": ele.get("ds").name, "cosine_similarity": ele.get("cosine_similarity")} for ele in _list])) ds = _list[0].get('ds') return {"id": ds.id, "name": ds.name, "description": ds.description} From b566e695adaae357fe79e46760f4e7c24b10a8d8 Mon Sep 17 00:00:00 2001 From: ulleo Date: Fri, 19 Sep 2025 15:52:11 +0800 Subject: [PATCH 011/864] fix: improve export empty chat excel error info --- backend/apps/chat/api/chat.py | 12 ++++++--- backend/locales/en.json | 3 +++ backend/locales/zh-CN.json | 3 +++ frontend/src/api/chat.ts | 6 ++++- .../src/views/chat/chat-block/ChartBlock.vue | 25 +++++++++++++++++++ 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/backend/apps/chat/api/chat.py b/backend/apps/chat/api/chat.py index de53fd269..00f7def02 100644 --- a/backend/apps/chat/api/chat.py +++ b/backend/apps/chat/api/chat.py @@ -13,7 +13,7 @@ delete_chat, get_chat_chart_data, get_chat_predict_data, get_chat_with_records_with_data, get_chat_record_by_id from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, ChatQuestion, ExcelData from apps.chat.task.llm import LLMService -from common.core.deps import CurrentAssistant, SessionDep, CurrentUser +from common.core.deps import CurrentAssistant, SessionDep, CurrentUser, Trans router = APIRouter(tags=["Data Q&A"], prefix="/chat") @@ -106,7 +106,7 @@ async def start_chat(session: SessionDep, current_user: CurrentUser): async def recommend_questions(session: SessionDep, current_user: CurrentUser, chat_record_id: int, current_assistant: CurrentAssistant): def _return_empty(): - yield 'data:' + orjson.dumps( {'content': '[]', 'type': 'recommended_question'}).decode() + '\n\n' + yield 'data:' + orjson.dumps({'content': '[]', 'type': 'recommended_question'}).decode() + '\n\n' try: record = get_chat_record_by_id(session, chat_record_id) @@ -201,10 +201,16 @@ def _err(_e: Exception): @router.post("/excel/export") -async def export_excel(excel_data: ExcelData): +async def export_excel(excel_data: ExcelData, trans: Trans): def inner(): _fields_list = [] data = [] + if not excel_data.data: + raise HTTPException( + status_code=500, + detail=trans("i18n_excel_export.data_is_empty") + ) + for _data in excel_data.data: _row = [] for field in excel_data.axis: diff --git a/backend/locales/en.json b/backend/locales/en.json index 52fe00a57..df427789a 100644 --- a/backend/locales/en.json +++ b/backend/locales/en.json @@ -47,5 +47,8 @@ "datasource_cannot_be_none": "Datasource cannot be none", "data_training_not_exists": "Example does not exists", "exists_in_db": "Question exists" + }, + "i18n_excel_export": { + "data_is_empty": "The form data is empty, cannot export data" } } \ No newline at end of file diff --git a/backend/locales/zh-CN.json b/backend/locales/zh-CN.json index 812055e77..63961877e 100644 --- a/backend/locales/zh-CN.json +++ b/backend/locales/zh-CN.json @@ -47,5 +47,8 @@ "datasource_cannot_be_none": "数据源不能为空", "data_training_not_exists": "该示例不存在", "exists_in_db": "该问题已存在" + }, + "i18n_excel_export": { + "data_is_empty": "表单数据为空,无法导出数据" } } \ No newline at end of file diff --git a/frontend/src/api/chat.ts b/frontend/src/api/chat.ts index c1b526c78..76c436ddb 100644 --- a/frontend/src/api/chat.ts +++ b/frontend/src/api/chat.ts @@ -332,5 +332,9 @@ export const chatApi = { return request.fetchStream(`/chat/recommend_questions/${record_id}`, {}, controller) }, checkLLMModel: () => request.get('/system/aimodel/default', { requestOptions: { silent: true } }), - export2Excel: (data: any) => request.post('/chat/excel/export', data, { responseType: 'blob' }), + export2Excel: (data: any) => + request.post('/chat/excel/export', data, { + responseType: 'blob', + requestOptions: { customError: true }, + }), } diff --git a/frontend/src/views/chat/chat-block/ChartBlock.vue b/frontend/src/views/chat/chat-block/ChartBlock.vue index a8d8c502d..6220fb426 100644 --- a/frontend/src/views/chat/chat-block/ChartBlock.vue +++ b/frontend/src/views/chat/chat-block/ChartBlock.vue @@ -255,6 +255,31 @@ function exportToExcel() { link.click() document.body.removeChild(link) }) + .catch(async (error) => { + if (error.response) { + try { + let text = await error.response.data.text() + try { + text = JSON.parse(text) + } finally { + ElMessage({ + message: text, + type: 'error', + showClose: true, + }) + } + } catch (e) { + console.error('Error processing error response:', e) + } + } else { + console.error('Other error:', error) + ElMessage({ + message: error, + type: 'error', + showClose: true, + }) + } + }) .finally(() => { loading.value = false }) From 51c419df32fc1a365a73d8e49ce16a7baf007ff7 Mon Sep 17 00:00:00 2001 From: dataeaseShu Date: Fri, 19 Sep 2025 16:20:03 +0800 Subject: [PATCH 012/864] feat(Smart Question): In the conversation window, when there are many messages and a scroll bar appears, the scroll bar will automatically scroll to the bottom --- frontend/src/views/chat/index.vue | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/views/chat/index.vue b/frontend/src/views/chat/index.vue index 8f2e5bfe0..afb99444e 100644 --- a/frontend/src/views/chat/index.vue +++ b/frontend/src/views/chat/index.vue @@ -501,7 +501,7 @@ let scrollTopVal = 0 let scrolling = false const scrollBottom = () => { if (scrolling) return - if (!isTyping.value) { + if (!isTyping.value && !getRecommendQuestionsLoading.value) { clearInterval(scrollTime) } chatListRef.value!.setScrollTop(innerRef.value!.clientHeight) @@ -671,11 +671,14 @@ function quickAsk(question: string) { } const chartAnswerRef = ref() - +const getRecommendQuestionsLoading = ref(false) async function onChartAnswerFinish(id: number) { + getRecommendQuestionsLoading.value = true loading.value = false isTyping.value = false - getRecommendQuestions(id) + getRecommendQuestions(id).finally(() => { + getRecommendQuestionsLoading.value = false + }) } function onChartAnswerError() { From 97ffc34a422cb8b886aa09f3e57740f3bda1dd7d Mon Sep 17 00:00:00 2001 From: dataeaseShu Date: Fri, 19 Sep 2025 16:36:59 +0800 Subject: [PATCH 013/864] fix(Smart Question): In the conversation window, when there are many messages and a scroll bar appears, the scroll bar will automatically scroll to the bottom --- frontend/src/views/chat/RecommendQuestion.vue | 3 ++- frontend/src/views/chat/index.vue | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/frontend/src/views/chat/RecommendQuestion.vue b/frontend/src/views/chat/RecommendQuestion.vue index a192b0a43..5acb9012c 100644 --- a/frontend/src/views/chat/RecommendQuestion.vue +++ b/frontend/src/views/chat/RecommendQuestion.vue @@ -21,7 +21,7 @@ const props = withDefaults( } ) -const emits = defineEmits(['clickQuestion', 'update:currentChat', 'stop']) +const emits = defineEmits(['clickQuestion', 'update:currentChat', 'stop', 'loadingOver']) const loading = ref(false) @@ -134,6 +134,7 @@ async function getRecommendQuestions() { } } finally { loading.value = false + emits('loadingOver') } } diff --git a/frontend/src/views/chat/index.vue b/frontend/src/views/chat/index.vue index afb99444e..60accc1c5 100644 --- a/frontend/src/views/chat/index.vue +++ b/frontend/src/views/chat/index.vue @@ -195,6 +195,7 @@ :first-chat="message.first_chat" @click-question="quickAsk" @stop="onChatStop" + @loadingOver="loadingOver" /> @@ -676,9 +678,11 @@ async function onChartAnswerFinish(id: number) { getRecommendQuestionsLoading.value = true loading.value = false isTyping.value = false - getRecommendQuestions(id).finally(() => { - getRecommendQuestionsLoading.value = false - }) + getRecommendQuestions(id) +} + +const loadingOver = () => { + getRecommendQuestionsLoading.value = false } function onChartAnswerError() { From 313e1410c4b973d7b75055b94a075c20fd558a43 Mon Sep 17 00:00:00 2001 From: ulleo Date: Fri, 19 Sep 2025 17:03:15 +0800 Subject: [PATCH 014/864] fix: MCP chat run error while SQL execution result is empty --- backend/apps/chat/task/llm.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index 9024dfa99..5f9881990 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -1103,11 +1103,14 @@ def run_task(self, in_chat: bool = True): _fields_list.append(field if not _fields.get(field) else _fields.get(field)) data.append(_row) _fields_skip = True - df = pd.DataFrame(np.array(data), columns=_fields_list) - markdown_table = df.to_markdown(index=False) - yield markdown_table + '\n\n' - record = self.finish() + if not data or not _fields_list: + yield 'The SQL execution result is empty.\n\n' + else: + df = pd.DataFrame(np.array(data), columns=_fields_list) + markdown_table = df.to_markdown(index=False) + yield markdown_table + '\n\n' + if in_chat: yield 'data:' + orjson.dumps({'type': 'finish'}).decode() + '\n\n' else: @@ -1135,6 +1138,8 @@ def run_task(self, in_chat: bool = True): yield 'data:' + orjson.dumps({'content': error_msg, 'type': 'error'}).decode() + '\n\n' else: yield f'> ❌ **ERROR**\n\n> \n\n> {error_msg}。' + finally: + self.finish() def run_recommend_questions_task_async(self): self.future = executor.submit(self.run_recommend_questions_task_cache) From 22d989745ef43ca60d8d2fe824945480f206cb4f Mon Sep 17 00:00:00 2001 From: maninhill <41712985+maninhill@users.noreply.github.com> Date: Fri, 19 Sep 2025 20:27:20 +0800 Subject: [PATCH 015/864] Add Cordys CRM to the list of projects --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 95b224b79..c9768e394 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ docker run -d \ - [1Panel](https://github.com/1panel-dev/1panel/) - 现代化、开源的 Linux 服务器运维管理面板 - [MaxKB](https://github.com/1panel-dev/MaxKB/) - 强大易用的企业级智能体平台 - [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机 +- [Cordys CRM](https://github.com/1Panel-dev/CordysCRM) - 新一代的开源 AI CRM 系统 - [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具 - [MeterSphere](https://github.com/metersphere/metersphere/) - 新一代的开源持续测试工具 From 69550a89a1b08e8cb7c910ca5d7d9f3a64a5acad Mon Sep 17 00:00:00 2001 From: fit2cloud-chenyw Date: Mon, 22 Sep 2025 10:15:09 +0800 Subject: [PATCH 016/864] perf: Remove non-essential AES IV fields --- frontend/src/views/system/embedded/iframe.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/views/system/embedded/iframe.vue b/frontend/src/views/system/embedded/iframe.vue index e35f9da01..a12d7ebce 100644 --- a/frontend/src/views/system/embedded/iframe.vue +++ b/frontend/src/views/system/embedded/iframe.vue @@ -784,7 +784,7 @@ const saveHandler = () => { autocomplete="off" /> - +