diff --git a/backend/alembic/versions/048_update_chat_record.py b/backend/alembic/versions/048_update_chat_record.py new file mode 100644 index 000000000..5a2a01f0d --- /dev/null +++ b/backend/alembic/versions/048_update_chat_record.py @@ -0,0 +1,29 @@ +"""empty message + +Revision ID: cd23f594ac05 +Revises: c1b794a961ce +Create Date: 2025-11-06 14:12:40.384804 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'cd23f594ac05' +down_revision = 'c1b794a961ce' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('chat_record', sa.Column('sql_prase', sa.Text(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('chat_record', 'sql_prase') + # ### end Alembic commands ### \ No newline at end of file diff --git a/backend/apps/chat/api/chat.py b/backend/apps/chat/api/chat.py index 6fe358f04..1b7beeeaf 100644 --- a/backend/apps/chat/api/chat.py +++ b/backend/apps/chat/api/chat.py @@ -172,14 +172,14 @@ async def analysis_or_predict(session: SessionDep, current_user: CurrentUser, ch stmt = select(ChatRecord.id, ChatRecord.question, ChatRecord.chat_id, ChatRecord.datasource, ChatRecord.engine_type, - ChatRecord.ai_modal_id, ChatRecord.create_by, ChatRecord.chart, ChatRecord.data).where( + ChatRecord.ai_modal_id, ChatRecord.create_by, ChatRecord.chart, ChatRecord.data,ChatRecord.sql_prase).where( and_(ChatRecord.id == chat_record_id)) result = session.execute(stmt) for r in result: record = ChatRecord(id=r.id, question=r.question, chat_id=r.chat_id, datasource=r.datasource, engine_type=r.engine_type, ai_modal_id=r.ai_modal_id, create_by=r.create_by, chart=r.chart, - data=r.data) + data=r.data,sql_prase=r.sql_prase) if not record: raise Exception(f"Chat record with id {chat_record_id} not found") diff --git a/backend/apps/chat/curd/chat.py b/backend/apps/chat/curd/chat.py index c18d9922c..f96ad625f 100644 --- a/backend/apps/chat/curd/chat.py +++ b/backend/apps/chat/curd/chat.py @@ -644,17 +644,19 @@ def save_chart_answer(session: SessionDep, record_id: int, answer: str) -> ChatR return record -def save_chart(session: SessionDep, record_id: int, chart: str) -> ChatRecord: +def save_chart(session: SessionDep, record_id: int, chart: str, sql_prase: str) -> ChatRecord: if not record_id: raise Exception("Record id cannot be None") record = get_chat_record_by_id(session, record_id) record.chart = chart + record.sql_prase = sql_prase result = ChatRecord(**record.model_dump()) stmt = update(ChatRecord).where(and_(ChatRecord.id == record.id)).values( - chart=record.chart + chart=record.chart, + sql_prase=record.sql_prase ) session.execute(stmt) diff --git a/backend/apps/chat/models/chat_model.py b/backend/apps/chat/models/chat_model.py index f20c45518..9e7968843 100644 --- a/backend/apps/chat/models/chat_model.py +++ b/backend/apps/chat/models/chat_model.py @@ -17,6 +17,7 @@ from apps.template.generate_guess_question.generator import get_guess_question_template from apps.template.generate_predict.generator import get_predict_template from apps.template.generate_sql.generator import get_sql_template, get_sql_example_template +from apps.template.prase_sql.generator import get_prase_sql_template from apps.template.select_datasource.generator import get_datasource_template @@ -40,6 +41,7 @@ class OperationEnum(Enum): GENERATE_SQL_WITH_PERMISSIONS = '5' CHOOSE_DATASOURCE = '6' GENERATE_DYNAMIC_SQL = '7' + PRASE_SQL = '8' class ChatFinishStep(Enum): @@ -109,6 +111,7 @@ class ChatRecord(SQLModel, table=True): error: str = Field(sa_column=Column(Text, nullable=True)) analysis_record_id: int = Field(sa_column=Column(BigInteger, nullable=True)) predict_record_id: int = Field(sa_column=Column(BigInteger, nullable=True)) + sql_prase: str = Field(sa_column=Column(Text, nullable=True)) class ChatRecordResult(BaseModel): @@ -229,6 +232,12 @@ def predict_sys_question(self): def predict_user_question(self): return get_predict_template()['user'].format(fields=self.fields, data=self.data) + def prase_sql_sys_question(self): + return get_prase_sql_template()['system'].format(lang=self.lang) + + def prase_sql_user_question(self,sql='',chart=''): + return get_prase_sql_template()['user'].format(sql=sql,chart=chart) + def datasource_sys_question(self): return get_datasource_template()['system'].format(lang=self.lang) diff --git a/backend/apps/chat/task/llm.py b/backend/apps/chat/task/llm.py index d8359f1cb..5cca2b1a2 100644 --- a/backend/apps/chat/task/llm.py +++ b/backend/apps/chat/task/llm.py @@ -774,7 +774,7 @@ def check_save_sql(self, session: Session, res: str) -> str: return sql - def check_save_chart(self, session: Session, res: str) -> Dict[str, Any]: + def check_save_chart(self, session: Session, res: str, sql_prase: str) -> Dict[str, Any]: json_str = extract_nested_json(res) if json_str is None: @@ -814,7 +814,7 @@ def check_save_chart(self, session: Session, res: str) -> Dict[str, Any]: if error: raise SingleMessageError(message) - save_chart(session=session, chart=orjson.dumps(chart).decode(), record_id=self.record.id) + save_chart(session=session, chart=orjson.dumps(chart).decode(), record_id=self.record.id,sql_prase=sql_prase) return chart @@ -989,6 +989,7 @@ def run_task(self, in_chat: bool = True, stream: bool = True, use_dynamic_ds: bool = self.current_assistant and self.current_assistant.type in dynamic_ds_types is_page_embedded: bool = self.current_assistant and self.current_assistant.type == 4 + is_assistant_embedded: bool = self.current_assistant and self.current_assistant.type == 1 dynamic_sql_result = None sqlbot_temp_sql_text = None assistant_dynamic_sql = None @@ -1092,7 +1093,16 @@ def run_task(self, in_chat: bool = True, stream: bool = True, # filter chart SQLBotLogUtil.info(full_chart_text) - chart = self.check_save_chart(session=_session, res=full_chart_text) + + # sql prase + if is_assistant_embedded: + sql_prase = self.generate_sql_paras(_session,real_execute_sql,full_chart_text) + if in_chat: + yield 'data:' + orjson.dumps( + {'content': sql_prase, + 'type': 'sql_prase'}).decode() + '\n\n' + + chart = self.check_save_chart(session=_session, res=full_chart_text,sql_prase=sql_prase) SQLBotLogUtil.info(chart) if not stream: @@ -1283,6 +1293,49 @@ def validate_history_ds(self, session: Session): except Exception as e: raise SingleMessageError(f"ds is invalid [{str(e)}]") + def generate_sql_paras(self, _session: Session, real_execute_sql: Optional[str] = '',chart: Optional[str] = ''): + # prase sql + prase_sql_msg: List[Union[BaseMessage, dict[str, Any]]] = [] + prase_sql_msg.append(SystemMessage(self.chat_question.prase_sql_sys_question())) + prase_sql_msg.append(HumanMessage(self.chat_question.prase_sql_user_question(real_execute_sql,chart))) + self.current_logs[OperationEnum.PRASE_SQL] = start_log(session=_session, + ai_modal_id=self.chat_question.ai_modal_id, + ai_modal_name=self.chat_question.ai_modal_name, + operate=OperationEnum.PRASE_SQL, + record_id=self.record.id, + full_message=[{'type': msg.type, + 'content': msg.content} + for + msg in prase_sql_msg]) + + token_usage = {} + prase_res = process_stream(self.llm.stream(prase_sql_msg), token_usage) + prase_full_thinking_text = '' + prase_full_text = '' + for chunk in prase_res: + if chunk.get('content'): + prase_full_text += chunk.get('content') + if chunk.get('reasoning_content'): + prase_full_thinking_text += chunk.get('reasoning_content') + prase_sql_msg.append(AIMessage(prase_full_text)) + + self.current_logs[OperationEnum.PRASE_SQL] = end_log(session=_session, + log=self.current_logs[ + OperationEnum.PRASE_SQL], + full_message=[ + {'type': msg.type, + 'content': msg.content} + for msg in prase_sql_msg], + reasoning_content=prase_full_thinking_text, + token_usage=token_usage) + + prase_json_str = extract_nested_json(prase_full_text) + return prase_json_str + # if prase_json_str is None: + # raise SingleMessageError(f'Cannot parse datasource from answer: {prase_full_text}') + # ds = orjson.loads(prase_json_str) + # return ds['info'] + def execute_sql_with_db(db: SQLDatabase, sql: str) -> str: """Execute SQL query using SQLDatabase @@ -1455,7 +1508,6 @@ def process_stream(res: Iterator[BaseMessageChunk], } get_token_usage(chunk, token_usage) - def get_lang_name(lang: str): if not lang: return '简体中文' diff --git a/backend/apps/template/generate_sql/generator.py b/backend/apps/template/generate_sql/generator.py index 07eb61972..fb5537280 100644 --- a/backend/apps/template/generate_sql/generator.py +++ b/backend/apps/template/generate_sql/generator.py @@ -12,3 +12,4 @@ def get_sql_template(): def get_sql_example_template(db_type: Union[str, DB]): template = get_base_sql_template(db_type) return template['template'] + diff --git a/backend/apps/template/prase_sql/__init__.py b/backend/apps/template/prase_sql/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/apps/template/prase_sql/generator.py b/backend/apps/template/prase_sql/generator.py new file mode 100644 index 000000000..b4debc286 --- /dev/null +++ b/backend/apps/template/prase_sql/generator.py @@ -0,0 +1,10 @@ +from typing import Union + +from apps.db.constant import DB +from apps.template.template import get_base_template, get_sql_template as get_base_sql_template + + +def get_prase_sql_template(): + template = get_base_template() + return template['template']['prase_sql'] + diff --git a/backend/templates/template.yaml b/backend/templates/template.yaml index be18f9e06..78d35e52d 100644 --- a/backend/templates/template.yaml +++ b/backend/templates/template.yaml @@ -517,3 +517,42 @@ template: ### 子查询映射表: {sub_query} + prase_sql: + system: | + ### 请使用语言:{lang} 回答 + + ### 说明: + 提供给你一句SQL和根据这个SQL已经生成的图表信息,你需要解析出sql所有的原始字段名称和sql字段别名,并把他们归类为维度、指标,如果是指标需要给出他的聚合函数是什么,同时提取图表标题,图表类型。 + 图表信息在chart字段内,SQL在sql字段内。 + 图表对应字段显示名称为name,图表原始字段名称为value,可以在chart.axis,chart.columns等字段内找到对应关系。 + + 你必须遵守以下规则: + - 生成的数据使用JSON格式返回: + {{"success":true,"info":{{"type":"图表类型","title":"图表标题","limit":"sql限制条数","xAxisSource":[{{"sourceName":"sql维度原始字段名称","showName":"图表对应字段显示name"}}],"yAxisSource":[{{"sourceName":"sql指标原始字段名称","showName":"图表对应字段显示name","summary":"聚合函数"}}]}}}} + - 如果不能生成数据,回答: + {{"success":false,"message":"无法生成结果的原因"}} + - 如何SQL存在聚合字段且聚合字段是经过多个sql原始字段运算得到的 如: sum(a*b) as c,则只取第一个原始字段a,同时原始字段a对应的sql字段别名为c + + ### 以下帮助你理解问题及返回格式的例子,不要将内的表结构用来回答用户的问题 + + + + + {{"type":"pie","title":"门店销售数量分布","axis":{{"y":{{"name":"销售数量","value":"total_sales"}},"series":{{"name":"门店","value":"shop_name"}}}}}} + SELECT `t1`.`shop` AS `shop_name`, COUNT(`t1`.`id`) AS `order_count` FROM `demo_tea_order` `t1` GROUP BY `t1`.`shop` LIMIT 1000 + + + {{"success":true,"info":{{"type":"pie","title":"门店订单分布","limit":1000,"xAxisSource":[{{"sourceName":"shop","showName":"门店"}}],"yAxisSource":[{{"sourceName":"id","showName":"订单数量","summary":"count"}}]}}}} + + + + + + ### 响应, 请直接返回JSON结果: + ```json + + user: | + ### chart: + {chart} + ### sql: + {sql} diff --git a/frontend/src/api/chat.ts b/frontend/src/api/chat.ts index 38003b39a..7ce00e9c1 100644 --- a/frontend/src/api/chat.ts +++ b/frontend/src/api/chat.ts @@ -51,6 +51,7 @@ export class ChatRecord { recommended_question?: string analysis_record_id?: number predict_record_id?: number + sql_prase?: string constructor() constructor( diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index 253c76a30..b63feeb3e 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -621,6 +621,7 @@ "excel": "Excel", "picture": "Image", "add_to_dashboard": "Add to Dashboard", + "add_to_dataV_en": "Add to Canvas", "full_screen": "Full Screen", "exit_full_screen": "Exit Full Screen", "sql_generation": "Generate SQL", diff --git a/frontend/src/i18n/ko-KR.json b/frontend/src/i18n/ko-KR.json index 52c642ca0..cd87eb923 100644 --- a/frontend/src/i18n/ko-KR.json +++ b/frontend/src/i18n/ko-KR.json @@ -621,6 +621,7 @@ "excel": "Excel", "picture": "이미지", "add_to_dashboard": "대시보드에 추가", + "add_to_dataV_ko": "캔버스에 추가", "full_screen": "전체 화면", "exit_full_screen": "전체 화면 종료", "sql_generation": "SQL 생성", @@ -693,4 +694,4 @@ "setting_successfully": "설정 성공", "customize_theme_color": "사용자 정의 테마 색상" } -} \ No newline at end of file +} diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index 46b8da208..5d332c573 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -621,6 +621,7 @@ "excel": "Excel", "picture": "图片", "add_to_dashboard": "添加到仪表板", + "add_to_dataV": "添加到画布", "full_screen": "全屏", "exit_full_screen": "退出全屏", "sql_generation": "生成SQL", @@ -693,4 +694,4 @@ "setting_successfully": "设置成功", "customize_theme_color": "自定义主题色" } -} \ No newline at end of file +} diff --git a/frontend/src/views/chat/answer/ChartAnswer.vue b/frontend/src/views/chat/answer/ChartAnswer.vue index 1069cadec..65b97c7ad 100644 --- a/frontend/src/views/chat/answer/ChartAnswer.vue +++ b/frontend/src/views/chat/answer/ChartAnswer.vue @@ -181,6 +181,9 @@ const sendMessage = async () => { case 'sql': _currentChat.value.records[index.value].sql = data.content break + case 'sql_prase': + _currentChat.value.records[index.value].sql_prase = data.content + break case 'sql-data': getChatData(_currentChat.value.records[index.value].id) break diff --git a/frontend/src/views/chat/chat-block/ChartBlock.vue b/frontend/src/views/chat/chat-block/ChartBlock.vue index 6220fb426..2c123f6f4 100644 --- a/frontend/src/views/chat/chat-block/ChartBlock.vue +++ b/frontend/src/views/chat/chat-block/ChartBlock.vue @@ -207,6 +207,18 @@ function showSql() { sqlShow.value = true } +function addToResource() { + // @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment + const readyData = { + eventName: 'sqlbot_add_to_resource', + busi: props.message?.record?.sql_prase, + ready: true, + messageId: 100001, + } + console.log('sqlbot-readyData', JSON.stringify(readyData)) + window.parent.postMessage(readyData, '*') +} + function addToDashboard() { const recordeInfo = { id: '1-1', @@ -422,6 +434,15 @@ watch( +
+ + + + + + + +