From 330fd2ddfdefd3b664f2b61680d91a074ce2e3e5 Mon Sep 17 00:00:00 2001 From: junjun Date: Sun, 28 Sep 2025 14:20:16 +0800 Subject: [PATCH 001/796] feat: support kingbase datasource --- backend/apps/datasource/crud/datasource.py | 2 +- backend/apps/db/constant.py | 1 + backend/apps/db/db.py | 66 +++++++++++++++++++++- backend/apps/db/db_sql.py | 33 ++++++++++- 4 files changed, 97 insertions(+), 5 deletions(-) diff --git a/backend/apps/datasource/crud/datasource.py b/backend/apps/datasource/crud/datasource.py index 4380b3cf4..c541a1520 100644 --- a/backend/apps/datasource/crud/datasource.py +++ b/backend/apps/datasource/crud/datasource.py @@ -279,7 +279,7 @@ def preview(session: SessionDep, current_user: CurrentUser, id: int, data: Table sql = f"""SELECT TOP 100 [{"], [".join(fields)}] FROM [{conf.dbSchema}].[{data.table.table_name}] {where} """ - elif ds.type == "pg" or ds.type == "excel" or ds.type == "redshift": + elif ds.type == "pg" or ds.type == "excel" or ds.type == "redshift" or ds.type == "kingbase": sql = f"""SELECT "{'", "'.join(fields)}" FROM "{conf.dbSchema}"."{data.table.table_name}" {where} LIMIT 100""" diff --git a/backend/apps/db/constant.py b/backend/apps/db/constant.py index 4194d8b7e..28d1ad0b1 100644 --- a/backend/apps/db/constant.py +++ b/backend/apps/db/constant.py @@ -23,6 +23,7 @@ class DB(Enum): doris = ('doris', 'Apache Doris', '`', '`', ConnectType.py_driver) redshift = ('redshift', 'AWS Redshift', '"', '"', ConnectType.py_driver) es = ('es', 'Elasticsearch', '"', '"', ConnectType.py_driver) + kingbase = ('kingbase', 'Kingbase', '"', '"', ConnectType.py_driver) def __init__(self, type, db_name, prefix, suffix, connect_type: ConnectType): self.type = type diff --git a/backend/apps/db/db.py b/backend/apps/db/db.py index 1a22dc355..c428a576c 100644 --- a/backend/apps/db/db.py +++ b/backend/apps/db/db.py @@ -5,8 +5,8 @@ from decimal import Decimal from typing import Optional +import psycopg2 import pymssql - from apps.db.db_sql import get_table_sql, get_field_sql, get_version_sql from common.error import ParseSQLResultError @@ -191,6 +191,20 @@ def check_connection(trans: Optional[Trans], ds: CoreDatasource | AssistantOutDs if is_raise: raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}') return False + elif ds.type == 'kingbase': + with psycopg2.connect(host=conf.host, port=conf.port, database=conf.database, + user=conf.username, + password=conf.password, + connect_timeout=10, **extra_config_dict) as conn, conn.cursor() as cursor: + try: + cursor.execute('select 1') + SQLBotLogUtil.info("success") + return True + except Exception as e: + SQLBotLogUtil.error(f"Datasource {ds.id} connection failed: {e}") + if is_raise: + raise HTTPException(status_code=500, detail=trans('i18n_ds_invalid') + f': {e.args}') + return False elif ds.type == 'es': es_conn = get_es_connect(conf) if es_conn.ping(): @@ -269,8 +283,7 @@ def get_schema(ds: CoreDatasource): if ds.type == "sqlServer": sql = """select name from sys.schemas""" elif ds.type == "pg" or ds.type == "excel": - sql = """SELECT nspname - FROM pg_namespace""" + sql = """SELECT nspname FROM pg_namespace""" elif ds.type == "oracle": sql = """select * from all_users""" with session.execute(text(sql)) as result: @@ -294,6 +307,15 @@ def get_schema(ds: CoreDatasource): res = cursor.fetchall() res_list = [item[0] for item in res] return res_list + elif ds.type == 'kingbase': + with psycopg2.connect(host=conf.host, port=conf.port, database=conf.database, user=conf.username, + password=conf.password, + options=f"-c statement_timeout={conf.timeout * 1000}", + **extra_config_dict) as conn, conn.cursor() as cursor: + cursor.execute("""SELECT nspname FROM pg_namespace""") + res = cursor.fetchall() + res_list = [item[0] for item in res] + return res_list def get_tables(ds: CoreDatasource): @@ -331,6 +353,15 @@ def get_tables(ds: CoreDatasource): res = cursor.fetchall() res_list = [TableSchema(*item) for item in res] return res_list + elif ds.type == 'kingbase': + with psycopg2.connect(host=conf.host, port=conf.port, database=conf.database, user=conf.username, + password=conf.password, + options=f"-c statement_timeout={conf.timeout * 1000}", + **extra_config_dict) as conn, conn.cursor() as cursor: + cursor.execute(sql.format(sql_param)) + res = cursor.fetchall() + res_list = [TableSchema(*item) for item in res] + return res_list elif ds.type == 'es': res = get_es_index(conf) res_list = [TableSchema(*item) for item in res] @@ -372,6 +403,15 @@ def get_fields(ds: CoreDatasource, table_name: str = None): res = cursor.fetchall() res_list = [ColumnSchema(*item) for item in res] return res_list + elif ds.type == 'kingbase': + with psycopg2.connect(host=conf.host, port=conf.port, database=conf.database, user=conf.username, + password=conf.password, + options=f"-c statement_timeout={conf.timeout * 1000}", + **extra_config_dict) as conn, conn.cursor() as cursor: + cursor.execute(sql.format(p1, p2)) + res = cursor.fetchall() + res_list = [ColumnSchema(*item) for item in res] + return res_list elif ds.type == 'es': res = get_es_fields(conf, table_name) res_list = [ColumnSchema(*item) for item in res] @@ -457,6 +497,26 @@ def exec_sql(ds: CoreDatasource | AssistantOutDsSchema, sql: str, origin_column= "sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))} except Exception as ex: raise ParseSQLResultError(str(ex)) + elif ds.type == 'kingbase': + with psycopg2.connect(host=conf.host, port=conf.port, database=conf.database, user=conf.username, + password=conf.password, + options=f"-c statement_timeout={conf.timeout * 1000}", + **extra_config_dict) as conn, conn.cursor() as cursor: + try: + cursor.execute(sql) + res = cursor.fetchall() + columns = [field[0] for field in cursor.description] if origin_column else [field[0].lower() for + field in + cursor.description] + result_list = [ + {str(columns[i]): float(value) if isinstance(value, Decimal) else value for i, value in + enumerate(tuple_item)} + for tuple_item in res + ] + return {"fields": columns, "data": result_list, + "sql": bytes.decode(base64.b64encode(bytes(sql, 'utf-8')))} + except Exception as ex: + raise ParseSQLResultError(str(ex)) elif ds.type == 'es': try: res, columns = get_es_data_by_http(conf, sql) diff --git a/backend/apps/db/db_sql.py b/backend/apps/db/db_sql.py index 66616b70e..bced9a22f 100644 --- a/backend/apps/db/db_sql.py +++ b/backend/apps/db/db_sql.py @@ -12,7 +12,7 @@ def get_version_sql(ds: CoreDatasource, conf: DatasourceConf): return """ select SERVERPROPERTY('ProductVersion') """ - elif ds.type == "pg" or ds.type == "excel": + elif ds.type == "pg" or ds.type == "kingbase" or ds.type == "excel": return """ SELECT current_setting('server_version') """ @@ -140,6 +140,21 @@ def get_table_sql(ds: CoreDatasource, conf: DatasourceConf, db_version: str = '' WHERE TABLE_SCHEMA = %s """, conf.database + elif ds.type == "kingbase": + return """ + SELECT c.relname AS TABLE_NAME, + COALESCE(d.description, obj_description(c.oid)) AS TABLE_COMMENT + FROM pg_class c + LEFT JOIN + pg_namespace n ON n.oid = c.relnamespace + LEFT JOIN + pg_description d ON d.objoid = c.oid AND d.objsubid = 0 + WHERE n.nspname = '{0}' + AND c.relkind IN ('r', 'v', 'p', 'm') + AND c.relname NOT LIKE 'pg_%' + AND c.relname NOT LIKE 'sql_%' + ORDER BY c.relname \ + """, conf.dbSchema elif ds.type == "es": return "", None @@ -275,5 +290,21 @@ def get_field_sql(ds: CoreDatasource, conf: DatasourceConf, table_name: str = No """ sql2 = " AND TABLE_NAME = %s" if table_name is not None and table_name != "" else "" return sql1 + sql2, conf.database, table_name + elif ds.type == "kingbase": + sql1 = """ + SELECT a.attname AS COLUMN_NAME, + pg_catalog.format_type(a.atttypid, a.atttypmod) AS DATA_TYPE, + col_description(c.oid, a.attnum) AS COLUMN_COMMENT + FROM pg_catalog.pg_attribute a + JOIN + pg_catalog.pg_class c ON a.attrelid = c.oid + JOIN + pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = '{0}' + AND a.attnum > 0 + AND NOT a.attisdropped \ + """ + sql2 = " AND c.relname = '{1}'" if table_name is not None and table_name != "" else "" + return sql1 + sql2, conf.dbSchema, table_name elif ds.type == "es": return "", None, None From 213d318ed3394c2a38ca856da32b08106e80cc12 Mon Sep 17 00:00:00 2001 From: junjun Date: Sun, 28 Sep 2025 14:53:26 +0800 Subject: [PATCH 002/796] feat: support kingbase datasource --- .../src/assets/datasource/icon_kingbase.png | Bin 0 -> 24818 bytes frontend/src/views/ds/js/ds-type.ts | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 frontend/src/assets/datasource/icon_kingbase.png diff --git a/frontend/src/assets/datasource/icon_kingbase.png b/frontend/src/assets/datasource/icon_kingbase.png new file mode 100644 index 0000000000000000000000000000000000000000..79b171f6b4b2a40b06189d34e1f9acb66f1daa1a GIT binary patch literal 24818 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 CQDfZz literal 0 HcmV?d00001 diff --git a/frontend/src/views/ds/js/ds-type.ts b/frontend/src/views/ds/js/ds-type.ts index beeb7f435..f99a03a44 100644 --- a/frontend/src/views/ds/js/ds-type.ts +++ b/frontend/src/views/ds/js/ds-type.ts @@ -8,6 +8,7 @@ import dm from '@/assets/datasource/icon_dm.png' import doris from '@/assets/datasource/icon_doris.png' import redshift from '@/assets/datasource/icon_redshift.png' import es from '@/assets/datasource/icon_es.png' +import kingbase from '@/assets/datasource/icon_kingbase.png' import { i18n } from '@/i18n' const t = i18n.global.t @@ -22,6 +23,7 @@ export const dsType = [ { label: 'Apache Doris', value: 'doris' }, { label: 'AWS Redshift', value: 'redshift' }, { label: 'Elasticsearch', value: 'es' }, + { label: 'Kingbase', value: 'kingbase' }, ] export const dsTypeWithImg = [ @@ -35,6 +37,7 @@ export const dsTypeWithImg = [ { name: 'Apache Doris', type: 'doris', img: doris }, { name: 'AWS Redshift', type: 'redshift', img: redshift }, { name: 'Elasticsearch', type: 'es', img: es }, + { name: 'Kingbase', type: 'kingbase', img: kingbase }, ] -export const haveSchema = ['sqlServer', 'pg', 'oracle', 'dm', 'redshift'] +export const haveSchema = ['sqlServer', 'pg', 'oracle', 'dm', 'redshift', 'kingbase'] From 3c35a3a8e403eb93298d5cf0b77308f3cf48b314 Mon Sep 17 00:00:00 2001 From: dataeaseShu Date: Sun, 28 Sep 2025 15:14:57 +0800 Subject: [PATCH 003/796] feat(prompt): add prompt --- frontend/src/api/prompt.ts | 11 + frontend/src/components/layout/Menu.vue | 1 + frontend/src/i18n/en.json | 16 + frontend/src/i18n/zh-CN.json | 16 + frontend/src/router/index.ts | 7 + frontend/src/views/system/prompt/index.vue | 864 +++++++++++++++++++++ 6 files changed, 915 insertions(+) create mode 100644 frontend/src/api/prompt.ts create mode 100644 frontend/src/views/system/prompt/index.vue diff --git a/frontend/src/api/prompt.ts b/frontend/src/api/prompt.ts new file mode 100644 index 000000000..01d73488d --- /dev/null +++ b/frontend/src/api/prompt.ts @@ -0,0 +1,11 @@ +import { request } from '@/utils/request' + +export const promptApi = { + getList: (pageNum: any, pageSize: any, type: any, params: any) => + request.get(`/system/custom_prompt/${type}/page/${pageNum}/${pageSize}`, { + params, + }), + updateEmbedded: (data: any) => request.put(`/system/custom_prompt`, data), + deleteEmbedded: (params: any) => request.delete('/system/custom_prompt', { data: params }), + getOne: (id: any) => request.get(`/system/custom_prompt/${id}`), +} diff --git a/frontend/src/components/layout/Menu.vue b/frontend/src/components/layout/Menu.vue index 24143e951..0cd0425e8 100644 --- a/frontend/src/components/layout/Menu.vue +++ b/frontend/src/components/layout/Menu.vue @@ -51,6 +51,7 @@ const routerList = computed(() => { !route.path.includes('member') && !route.path.includes('professional') && !route.path.includes('training') && + !route.path.includes('prompt') && !route.path.includes('permission') && !route.path.includes('preview') && route.path !== '/login' && diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 7c6a2df91..5c6847a3d 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -6,6 +6,22 @@ "Dashboard": "Dashboard", "AI Model Configuration": "AI Model Configuration" }, + "prompt": { + "no_prompt_words": "No prompt words", + "customize_prompt_words": "Customize prompt words", + "ask_sql": "Ask SQL", + "data_analysis": "Data Analysis", + "data_prediction": "Data Prediction", + "add_prompt_word": "Add prompt word", + "prompt_word_name": "Prompt word name", + "prompt_word_content": "Prompt word content", + "training_data_details": "Training data details", + "selected_prompt_words": "Do you want to delete the {msg} selected prompt words?", + "replaced_with": "Please enter a prompt word. Example: When returning names, only the last name will be displayed, and the first name will be replaced with *.", + "loss_exercise_caution": "Entering high-risk operations such as deleting a database or table or modifying the table structure may result in permanent data loss. Exercise caution!", + "edit_prompt_word": "Edit prompt word", + "prompt_word_name_de": "Do you want to delete the prompt word: {msg}?" + }, "training": { "effective_data_sources": "Effective data sources", "all_data_sources": "All data sources", diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index dc77bf278..8511a3f3d 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -6,6 +6,22 @@ "Dashboard": "仪表盘", "AI Model Configuration": "模型配置" }, + "prompt": { + "no_prompt_words": "暂无提示词", + "customize_prompt_words": "自定义提示词", + "ask_sql": "问数 SQL", + "data_analysis": "数据分析", + "data_prediction": "数据预测", + "add_prompt_word": "添加提示词", + "prompt_word_name": "提示词名称", + "prompt_word_content": "提示词内容", + "training_data_details": "训练数据详情", + "selected_prompt_words": "是否删除选中的 {msg} 条提示词?", + "replaced_with": "请输入提示词,示例:返回姓名时,只显示姓氏,名字用*代替", + "loss_exercise_caution": "若输入删除数据库、数据表或修改表结构等高风险的操作指令,可能导致数据永久丢失,请务必谨慎!", + "edit_prompt_word": "编辑提示词", + "prompt_word_name_de": "是否删除提示词:{msg}?" + }, "training": { "effective_data_sources": "生效数据源", "all_data_sources": "所有数据源", diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 00fad1666..84e298596 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -17,6 +17,7 @@ import EmbeddedPage from '@/views/embedded/page.vue' import Member from '@/views/system/member/index.vue' import Professional from '@/views/system/professional/index.vue' import Training from '@/views/system/training/index.vue' +import Prompt from '@/views/system/prompt/index.vue' import Appearance from '@/views/system/appearance/index.vue' import Permission from '@/views/system/permission/index.vue' import User from '@/views/system/user/User.vue' @@ -120,6 +121,12 @@ export const routes = [ component: Training, meta: { title: t('training.data_training') }, }, + { + path: '/set/prompt', + name: 'prompt', + component: Prompt, + meta: { title: t('prompt.customize_prompt_words') }, + }, ], }, { diff --git a/frontend/src/views/system/prompt/index.vue b/frontend/src/views/system/prompt/index.vue new file mode 100644 index 000000000..1e3d39ce2 --- /dev/null +++ b/frontend/src/views/system/prompt/index.vue @@ -0,0 +1,864 @@ + + + + + + From 1b25652ba49099b3c441886b83eee460a6fc655b Mon Sep 17 00:00:00 2001 From: dataeaseShu Date: Sun, 28 Sep 2025 15:45:46 +0800 Subject: [PATCH 004/796] feat(prompt): add prompt --- frontend/src/views/system/prompt/index.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/views/system/prompt/index.vue b/frontend/src/views/system/prompt/index.vue index 1e3d39ce2..041110ae5 100644 --- a/frontend/src/views/system/prompt/index.vue +++ b/frontend/src/views/system/prompt/index.vue @@ -322,6 +322,8 @@ const handleChange = () => { const typeChange = (val: any) => { currentType.value = val + pageInfo.currentPage = 0 + search() } From 75bd13a54f104e9d53b1c882f9dfe80acde46f95 Mon Sep 17 00:00:00 2001 From: ulleo Date: Sun, 28 Sep 2025 19:18:38 +0800 Subject: [PATCH 005/796] feat(X-Pack): add custom prompt #213 --- backend/alembic/env.py | 1 + .../alembic/versions/046_add_custom_prompt.py | 39 ++++++++++++ backend/apps/chat/models/chat_model.py | 10 ++- backend/apps/chat/task/llm.py | 18 ++++++ backend/apps/terminology/curd/terminology.py | 4 +- backend/locales/en.json | 4 ++ backend/locales/zh-CN.json | 4 ++ backend/pyproject.toml | 2 +- backend/template.yaml | 61 ++++++++++++++----- frontend/src/views/system/prompt/index.vue | 2 +- 10 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 backend/alembic/versions/046_add_custom_prompt.py diff --git a/backend/alembic/env.py b/backend/alembic/env.py index 79dfbdc68..16ef1c3e9 100755 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -26,6 +26,7 @@ # from apps.settings.models.setting_models import SQLModel # from apps.chat.models.chat_model import SQLModel from apps.terminology.models.terminology_model import SQLModel +#from apps.custom_prompt.models.custom_prompt_model import SQLModel # from apps.data_training.models.data_training_model import SQLModel # from apps.dashboard.models.dashboard_model import SQLModel from common.core.config import settings # noqa diff --git a/backend/alembic/versions/046_add_custom_prompt.py b/backend/alembic/versions/046_add_custom_prompt.py new file mode 100644 index 000000000..692c0f3f5 --- /dev/null +++ b/backend/alembic/versions/046_add_custom_prompt.py @@ -0,0 +1,39 @@ +"""046_add_custom_prompt + +Revision ID: 8855aea2dd61 +Revises: 45e7e52bf2b8 +Create Date: 2025-09-28 13:57:01.509249 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '8855aea2dd61' +down_revision = '45e7e52bf2b8' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('custom_prompt', + sa.Column('id', sa.BigInteger(), sa.Identity(always=True), nullable=False), + sa.Column('oid', sa.BigInteger(), nullable=True), + sa.Column('type', sa.Enum('GENERATE_SQL', 'ANALYSIS', 'PREDICT_DATA', name='customprompttypeenum', native_enum=False, length=20), nullable=True), + sa.Column('create_time', sa.DateTime(), nullable=True), + sa.Column('name', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=True), + sa.Column('prompt', sa.Text(), nullable=True), + sa.Column('specific_ds', sa.Boolean(), nullable=True), + sa.Column('datasource_ids', postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('custom_prompt') + # ### end Alembic commands ### diff --git a/backend/apps/chat/models/chat_model.py b/backend/apps/chat/models/chat_model.py index 6cc30b839..d24e19e15 100644 --- a/backend/apps/chat/models/chat_model.py +++ b/backend/apps/chat/models/chat_model.py @@ -40,11 +40,13 @@ class OperationEnum(Enum): CHOOSE_DATASOURCE = '6' GENERATE_DYNAMIC_SQL = '7' + class ChatFinishStep(Enum): GENERATE_SQL = 1 QUERY_DATA = 2 GENERATE_CHART = 3 + # TODO choose table / check connection / generate description class ChatLog(SQLModel, table=True): @@ -177,12 +179,13 @@ class AiModelQuestion(BaseModel): sub_query: Optional[list[dict]] = None terminologies: str = "" data_training: str = "" + custom_prompt: str = "" error_msg: str = "" def sql_sys_question(self): return get_sql_template()['system'].format(engine=self.engine, schema=self.db_schema, question=self.question, lang=self.lang, terminologies=self.terminologies, - data_training=self.data_training) + data_training=self.data_training, custom_prompt=self.custom_prompt) def sql_user_question(self, current_time: str): return get_sql_template()['user'].format(engine=self.engine, schema=self.db_schema, question=self.question, @@ -196,13 +199,14 @@ def chart_user_question(self, chart_type: Optional[str] = None): chart_type=chart_type) def analysis_sys_question(self): - return get_analysis_template()['system'].format(lang=self.lang, terminologies=self.terminologies) + return get_analysis_template()['system'].format(lang=self.lang, terminologies=self.terminologies, + custom_prompt=self.custom_prompt) def analysis_user_question(self): return get_analysis_template()['user'].format(fields=self.fields, data=self.data) def predict_sys_question(self): - return get_predict_template()['system'].format(lang=self.lang) + return get_predict_template()['system'].format(lang=self.lang, custom_prompt=self.custom_prompt) def predict_user_question(self): return get_predict_template()['user'].format(fields=self.fields, data=self.data) diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index a30a741dd..9dce46a4f 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -30,6 +30,9 @@ get_last_execute_sql_error from apps.chat.models.chat_model import ChatQuestion, ChatRecord, Chat, RenameChat, ChatLog, OperationEnum, \ ChatFinishStep +from sqlbot_xpack.license.license_manage import SQLBotLicenseUtil +from sqlbot_xpack.custom_prompt.curd.custom_prompt import find_custom_prompts +from sqlbot_xpack.custom_prompt.models.custom_prompt_model import CustomPromptTypeEnum 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 @@ -244,6 +247,9 @@ def generate_analysis(self): 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, self.current_user.oid, ds_id) + if SQLBotLicenseUtil.valid(): + self.chat_question.custom_prompt = find_custom_prompts(self.session, CustomPromptTypeEnum.ANALYSIS, + self.current_user.oid, ds_id) analysis_msg.append(SystemMessage(content=self.chat_question.analysis_sys_question())) analysis_msg.append(HumanMessage(content=self.chat_question.analysis_user_question())) @@ -288,6 +294,12 @@ def generate_predict(self): self.chat_question.fields = orjson.dumps(fields).decode() data = get_chat_chart_data(self.session, self.record.id) self.chat_question.data = orjson.dumps(data.get('data')).decode() + + if SQLBotLicenseUtil.valid(): + ds_id = self.ds.id if isinstance(self.ds, CoreDatasource) else None + self.chat_question.custom_prompt = find_custom_prompts(self.session, CustomPromptTypeEnum.PREDICT_DATA, + self.current_user.oid, ds_id) + predict_msg: List[Union[BaseMessage, dict[str, Any]]] = [] predict_msg.append(SystemMessage(content=self.chat_question.predict_sys_question())) predict_msg.append(HumanMessage(content=self.chat_question.predict_user_question())) @@ -509,6 +521,9 @@ def select_datasource(self): ds_id) self.chat_question.data_training = get_training_template(self.session, self.chat_question.question, ds_id, oid) + if SQLBotLicenseUtil.valid(): + self.chat_question.custom_prompt = find_custom_prompts(self.session, CustomPromptTypeEnum.GENERATE_SQL, + oid, ds_id) self.init_messages() @@ -902,6 +917,9 @@ def run_task(self, in_chat: bool = True, stream: bool = True, oid, ds_id) self.chat_question.data_training = get_training_template(self.session, self.chat_question.question, ds_id, oid) + if SQLBotLicenseUtil.valid(): + self.chat_question.custom_prompt = find_custom_prompts(self.session, CustomPromptTypeEnum.GENERATE_SQL, + oid, ds_id) self.init_messages() diff --git a/backend/apps/terminology/curd/terminology.py b/backend/apps/terminology/curd/terminology.py index 355f483e7..baa0df0aa 100644 --- a/backend/apps/terminology/curd/terminology.py +++ b/backend/apps/terminology/curd/terminology.py @@ -5,9 +5,7 @@ from xml.dom.minidom import parseString import dicttoxml -from sqlalchemy import BigInteger -from sqlalchemy import and_, or_, select, func, delete, update, union -from sqlalchemy import text +from sqlalchemy import and_, or_, select, func, delete, update, union, text, BigInteger from sqlalchemy.orm import aliased from sqlalchemy.orm.session import Session diff --git a/backend/locales/en.json b/backend/locales/en.json index df427789a..79c4c6f40 100644 --- a/backend/locales/en.json +++ b/backend/locales/en.json @@ -47,6 +47,10 @@ "datasource_cannot_be_none": "Datasource cannot be none", "data_training_not_exists": "Example does not exists", "exists_in_db": "Question exists" + }, + "i18n_custom_prompt": { + "exists_in_db": "Prompt name exists", + "not_exists": "Prompt does not exists" }, "i18n_excel_export": { "data_is_empty": "The form data is empty, cannot export data" diff --git a/backend/locales/zh-CN.json b/backend/locales/zh-CN.json index 63961877e..963ea8382 100644 --- a/backend/locales/zh-CN.json +++ b/backend/locales/zh-CN.json @@ -48,6 +48,10 @@ "data_training_not_exists": "该示例不存在", "exists_in_db": "该问题已存在" }, + "i18n_custom_prompt": { + "exists_in_db": "模版名称已存在", + "not_exists": "该模版不存在" + }, "i18n_excel_export": { "data_is_empty": "表单数据为空,无法导出数据" } diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 8ccdd5851..1bd7fab40 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "pyyaml (>=6.0.2,<7.0.0)", "fastapi-mcp (>=0.3.4,<0.4.0)", "tabulate>=0.9.0", - "sqlbot-xpack>=0.0.3.31,<1.0.0", + "sqlbot-xpack>=0.0.3.36,<1.0.0", "fastapi-cache2>=0.2.2", "sqlparse>=0.5.3", "redis>=6.2.0", diff --git a/backend/template.yaml b/backend/template.yaml index a9b8d7e4b..b28bcf172 100644 --- a/backend/template.yaml +++ b/backend/template.yaml @@ -14,8 +14,9 @@ template: 内有等信息; 其中,:提供数据库引擎及版本信息; :以 M-Schema 格式提供数据库表结构信息; - :提供一组术语,块内每一个就是术语,其中同一个内的多个代表术语的多种叫法,也就是术语与它的同义词,即该术语对应的描述,其中也可能是能够用来参考的计算公式,或者是一些其他的查询条件 - :提供一组SQL示例,你可以参考这些示例来生成你的回答,其中内是提问,内是对于该提问的解释或者对应应该回答的SQL示例 + :提供一组术语,块内每一个就是术语,其中同一个内的多个代表术语的多种叫法,也就是术语与它的同义词,即该术语对应的描述,其中也可能是能够用来参考的计算公式,或者是一些其他的查询条件; + :提供一组SQL示例,你可以参考这些示例来生成你的回答,其中内是提问,内是对于该提问的解释或者对应应该回答的SQL示例。 + 若有块,它会提供一组,可能会是额外添加的背景信息,或者是额外的生成SQL的要求,请结合额外信息或要求后生成你的回答。 用户的提问在内,内则会提供上次执行你提供的SQL时会出现的错误信息,内的会告诉你用户当前提问的时间 @@ -219,7 +220,6 @@ template: - ### 下面是提供的信息 {engine} @@ -229,6 +229,7 @@ template: {terminologies} {data_training} + {custom_prompt} ### 响应, 请根据上述要求直接返回JSON结果: ```json @@ -394,7 +395,11 @@ template: 你当前的任务是根据给定的数据分析数据,并给出你的分析结果。 我们会在块内提供给你信息,帮助你进行分析: 内有等信息; - :提供一组术语,块内每一个就是术语,其中同一个内的多个代表术语的多种叫法,也就是术语与它的同义词,即该术语对应的描述,其中也可能是能够用来参考的计算公式,或者是一些其他的查询条件 + :提供一组术语,块内每一个就是术语,其中同一个内的多个代表术语的多种叫法,也就是术语与它的同义词,即该术语对应的描述,其中也可能是能够用来参考的计算公式,或者是一些其他的查询条件。 + 若有块,它会提供一组,可能会是额外添加的背景信息,或者是额外的分析要求,请结合额外信息或要求后生成你的回答。 + 用户会在提问中提供给你信息: + 块内是提供给你的数,以JSON格式给出; + 块内提供给你对应的字段或字段别名。 你必须遵守以下规则: @@ -404,32 +409,60 @@ template: - ### 下面是提供的信息 {terminologies} + {custom_prompt} user: | - ### 字段(字段别名): + {fields} + - ### 数据: + {data} + predict: system: | - ### 请使用语言:{lang} 回答,若有深度思考过程,则思考过程也需要使用 {lang} 输出 + + 你是"SQLBOT",智能问数小助手,可以根据用户提问,专业生成SQL与可视化图表。 + 你当前的任务是根据给定的数据进行数据预测,并给出你的预测结果。 + 若有块,它会提供一组,可能会是额外添加的背景信息,或者是额外的分析要求,请结合额外信息或要求后生成你的回答。 + 用户会在提问中提供给你信息: + 块内是提供给你的数据,以JSON格式给出; + 块内提供给你对应的字段或字段别名。 + - ### 说明: - 你是一个数据分析师,你的任务是根据给定的数据进行数据预测,我将以JSON格式给你一组数据,你帮我预测之后的数据(一段可以展示趋势的数据,至少2个周期),用json数组的格式返回,返回的格式需要与传入的数据格式保持一致。 + 你必须遵守以下规则: + + + 请使用语言:{lang} 回答,若有深度思考过程,则思考过程也需要使用 {lang} 输出 + + + 预测的数据是一段可以展示趋势的数据,至少2个周期 + + + 返回的预测数据必须与用户提供的数据同样的格式,使用JSON数组的形式返回 + + + 无法预测或者不支持预测的数据请直接返回(不需要返回JSON格式):"抱歉,该数据无法进行预测。"(若有原因,则额外返回无法预测的原因) + + + 预测的数据不需要返回用户提供的原有数据,请直接返回你预测的部份 + + + {custom_prompt} + + ### 响应, 请根据上述要求直接返回JSON结果: ```json - 无法预测或者不支持预测的数据请直接返回(不需要返回JSON格式,需要翻译为 {lang} 输出):"抱歉,该数据无法进行预测。(有原因则返回无法预测的原因)" - 如果可以预测,则不需要返回原有数据,直接返回预测的部份 user: | - ### 字段(字段别名): + {fields} + - ### 数据: + {data} + datasource: system: | ### 请使用语言:{lang} 回答 diff --git a/frontend/src/views/system/prompt/index.vue b/frontend/src/views/system/prompt/index.vue index 041110ae5..b56193894 100644 --- a/frontend/src/views/system/prompt/index.vue +++ b/frontend/src/views/system/prompt/index.vue @@ -202,7 +202,7 @@ const search = () => { oldKeywords.value = keywords.value promptApi .getList(pageInfo.currentPage, pageInfo.pageSize, currentType.value, { - question: keywords.value, + name: keywords.value, }) .then((res: any) => { toggleRowLoading.value = true From 0bf4c828595e12e66603970c6c15539236420db0 Mon Sep 17 00:00:00 2001 From: dataeaseShu Date: Mon, 29 Sep 2025 10:22:10 +0800 Subject: [PATCH 006/796] fix: Style optimization --- frontend/src/views/system/prompt/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/views/system/prompt/index.vue b/frontend/src/views/system/prompt/index.vue index b56193894..bcc1156f6 100644 --- a/frontend/src/views/system/prompt/index.vue +++ b/frontend/src/views/system/prompt/index.vue @@ -851,7 +851,7 @@ const typeChange = (val: any) => { font-weight: 400; font-size: 14px; line-height: 22px; - background: #ff8800; + color: #ff8800; } .no-error.no-error { .ed-form-item__error { From c2531c33f008c8d489a01d1f6aa4c09522e7557c Mon Sep 17 00:00:00 2001 From: ParkMinsu Date: Sat, 27 Sep 2025 21:26:05 +0900 Subject: [PATCH 007/796] korean update --- backend/apps/chat/task/llm.py | 11 +- backend/apps/datasource/crud/datasource.py | 3 +- backend/apps/system/api/user.py | 2 +- backend/apps/system/schemas/system_schema.py | 6 +- backend/common/core/config.py | 1 - backend/locales/ko-KR.json | 54 +++++++ .../components/Language-selector/index.vue | 28 ++-- frontend/src/components/layout/Person.vue | 15 +- frontend/src/i18n/en.json | 4 +- frontend/src/i18n/index.ts | 18 ++- frontend/src/i18n/ko-KR.json | 140 ++++++++++++++++++ frontend/src/i18n/zh-CN.json | 2 + frontend/src/stores/user.ts | 6 +- 13 files changed, 260 insertions(+), 30 deletions(-) create mode 100644 backend/locales/ko-KR.json create mode 100644 frontend/src/i18n/ko-KR.json diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index 9dce46a4f..bcffdc112 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -418,7 +418,7 @@ def select_datasource(self): full_thinking_text = '' full_text = '' if not ignore_auto_select: - if settings.TABLE_EMBEDDING_ENABLED: + if settings.EMBEDDING_ENABLED: ds = get_ds_embedding(self.session, self.current_user, _ds_list, self.out_ds_instance, self.chat_question.question, self.current_assistant) yield {'content': '{"id":' + str(ds.get('id')) + '}'} @@ -508,7 +508,7 @@ def select_datasource(self): except Exception as e: _error = e - if not ignore_auto_select and not settings.TABLE_EMBEDDING_ENABLED: + 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, @@ -1443,6 +1443,11 @@ def process_stream(res: Iterator[BaseMessageChunk], def get_lang_name(lang: str): - if lang and lang == 'en': + if not lang: + return '简体中文' + normalized = lang.lower() + if normalized.startswith('en'): return '英文' + if normalized.startswith('ko'): + return '韩语' return '简体中文' diff --git a/backend/apps/datasource/crud/datasource.py b/backend/apps/datasource/crud/datasource.py index c541a1520..1ec08079c 100644 --- a/backend/apps/datasource/crud/datasource.py +++ b/backend/apps/datasource/crud/datasource.py @@ -13,7 +13,6 @@ from apps.db.constant import DB from apps.db.db import get_tables, get_fields, exec_sql, check_connection from apps.db.engine import get_engine_config, get_engine_conn -from common.core.config import settings from common.core.deps import SessionDep, CurrentUser, Trans from common.utils.utils import deepcopy_ignore_extra from .table import get_tables_by_ds_id @@ -403,7 +402,7 @@ def get_table_schema(session: SessionDep, current_user: CurrentUser, ds: CoreDat all_tables.append(t_obj) # do table embedding - if embedding and tables and settings.TABLE_EMBEDDING_ENABLED: + if embedding and tables: tables = get_table_embedding(session, current_user, tables, question) # splice schema if tables: diff --git a/backend/apps/system/api/user.py b/backend/apps/system/api/user.py index d6f33cda7..7453e94e0 100644 --- a/backend/apps/system/api/user.py +++ b/backend/apps/system/api/user.py @@ -193,7 +193,7 @@ async def batch_del(session: SessionDep, id_list: list[int]): @clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id") async def langChange(session: SessionDep, current_user: CurrentUser, trans: Trans, language: UserLanguage): lang = language.language - if lang not in ["zh-CN", "en"]: + if lang not in ["zh-CN", "en", "ko-KR"]: raise Exception(trans('i18n_user.language_not_support', key = lang)) db_user: UserModel = get_db_user(session=session, user_id=current_user.id) db_user.language = lang diff --git a/backend/apps/system/schemas/system_schema.py b/backend/apps/system/schemas/system_schema.py index 728ca4806..6505e8fdd 100644 --- a/backend/apps/system/schemas/system_schema.py +++ b/backend/apps/system/schemas/system_schema.py @@ -31,7 +31,7 @@ class BaseUser(BaseModel): class BaseUserDTO(BaseUser, BaseCreatorDTO): - language: str = Field(pattern=r"^(zh-CN|en)$", default="zh-CN", description="用户语言") + language: str = Field(pattern=r"^(zh-CN|en|ko-KR)$", default="zh-CN", description="用户语言") password: str status: int = 1 @@ -44,8 +44,8 @@ def to_dict(self): @field_validator("language") def validate_language(cls, lang: str) -> str: - if not re.fullmatch(r"^(zh-CN|en)$", lang): - raise ValueError("Language must be 'zh-CN' or 'en'") + if not re.fullmatch(r"^(zh-CN|en|ko-KR)$", lang): + raise ValueError("Language must be 'zh-CN', 'en', or 'ko-KR'") return lang diff --git a/backend/common/core/config.py b/backend/common/core/config.py index ca0468b85..e528d1cfd 100644 --- a/backend/common/core/config.py +++ b/backend/common/core/config.py @@ -105,7 +105,6 @@ def SQLALCHEMY_DATABASE_URI(self) -> PostgresDsn | str: PG_POOL_RECYCLE: int = 3600 PG_POOL_PRE_PING: bool = True - TABLE_EMBEDDING_ENABLED: bool = False TABLE_EMBEDDING_COUNT: int = 10 diff --git a/backend/locales/ko-KR.json b/backend/locales/ko-KR.json new file mode 100644 index 000000000..59f561895 --- /dev/null +++ b/backend/locales/ko-KR.json @@ -0,0 +1,54 @@ +{ + "i18n_default_workspace": "기본 워크스페이스", + "i18n_ds_name_exist": "이미 존재하는 이름입니다", + "i18n_concat_admin": "관리자에게 문의해 주세요!", + "i18n_exist": "{msg}이(가) 이미 존재합니다!", + "i18n_name": "이름", + "i18n_not_exist": "{msg}을(를) 찾을 수 없습니다", + "i18n_error": "{key} 오류!", + "i18n_miss_args": "{key} 매개변수가 없습니다!", + "i18n_format_invalid": "{key} 형식이 올바르지 않습니다!", + "i18n_login": { + "account_pwd_error": "계정 또는 비밀번호가 올바르지 않습니다!", + "no_associated_ws": "연결된 워크스페이스가 없습니다. {msg}", + "user_disable": "계정이 비활성화되었습니다. {msg}" + }, + "i18n_user": { + "account": "계정", + "email": "이메일", + "password": "비밀번호", + "language_not_support": "[{key}] 언어는 지원하지 않습니다!", + "ws_miss": "현재 사용자는 워크스페이스 [{ws}]에 속해 있지 않습니다!" + }, + "i18n_ws": { + "title": "워크스페이스" + }, + "i18n_permission": { + "only_admin": "관리자만 호출할 수 있습니다!", + "no_permission": "{url}{msg}에 접근 권한이 없습니다", + "authenticate_invalid": "인증이 실패했습니다 [{msg}]", + "token_expired": "토큰이 만료되었습니다" + }, + "i18n_llm": { + "validate_error": "검증에 실패했습니다 [{msg}]", + "delete_default_error": "기본 모델 [{key}]은 삭제할 수 없습니다!", + "miss_default": "기본 LLM이 아직 설정되지 않았습니다" + }, + "i18n_ds_invalid": "데이터 소스가 유효하지 않습니다", + "i18n_embedded": { + "invalid_origin": "도메인 검증에 실패했습니다 [{origin}]" + }, + "i18n_terminology": { + "terminology_not_exists": "용어를 찾을 수 없습니다", + "cannot_be_repeated": "용어 이름과 동의어는 중복될 수 없습니다", + "exists_in_db": "용어 이름 또는 동의어가 이미 존재합니다" + }, + "i18n_data_training": { + "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/components/Language-selector/index.vue b/frontend/src/components/Language-selector/index.vue index 5ab19f0ae..7d9ee8d16 100644 --- a/frontend/src/components/Language-selector/index.vue +++ b/frontend/src/components/Language-selector/index.vue @@ -1,21 +1,20 @@