Skip to content

Commit a2909a0

Browse files
committed
feat:Supports adding charts to the DataEase canvas.
1 parent 5ec2a68 commit a2909a0

File tree

15 files changed

+180
-10
lines changed

15 files changed

+180
-10
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""empty message
2+
3+
Revision ID: cd23f594ac05
4+
Revises: c1b794a961ce
5+
Create Date: 2025-11-06 14:12:40.384804
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'cd23f594ac05'
15+
down_revision = 'c1b794a961ce'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.add_column('chat_record', sa.Column('sql_prase', sa.Text(), nullable=True))
23+
# ### end Alembic commands ###
24+
25+
26+
def downgrade():
27+
# ### commands auto generated by Alembic - please adjust! ###
28+
op.drop_column('chat_record', 'sql_prase')
29+
# ### end Alembic commands ###

backend/apps/chat/api/chat.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,14 @@ async def analysis_or_predict(session: SessionDep, current_user: CurrentUser, ch
172172

173173
stmt = select(ChatRecord.id, ChatRecord.question, ChatRecord.chat_id, ChatRecord.datasource,
174174
ChatRecord.engine_type,
175-
ChatRecord.ai_modal_id, ChatRecord.create_by, ChatRecord.chart, ChatRecord.data).where(
175+
ChatRecord.ai_modal_id, ChatRecord.create_by, ChatRecord.chart, ChatRecord.data,ChatRecord.sql_prase).where(
176176
and_(ChatRecord.id == chat_record_id))
177177
result = session.execute(stmt)
178178
for r in result:
179179
record = ChatRecord(id=r.id, question=r.question, chat_id=r.chat_id, datasource=r.datasource,
180180
engine_type=r.engine_type, ai_modal_id=r.ai_modal_id, create_by=r.create_by,
181181
chart=r.chart,
182-
data=r.data)
182+
data=r.data,sql_prase=r.sql_prase)
183183

184184
if not record:
185185
raise Exception(f"Chat record with id {chat_record_id} not found")

backend/apps/chat/curd/chat.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,17 +644,19 @@ def save_chart_answer(session: SessionDep, record_id: int, answer: str) -> ChatR
644644
return record
645645

646646

647-
def save_chart(session: SessionDep, record_id: int, chart: str) -> ChatRecord:
647+
def save_chart(session: SessionDep, record_id: int, chart: str, sql_prase: str) -> ChatRecord:
648648
if not record_id:
649649
raise Exception("Record id cannot be None")
650650
record = get_chat_record_by_id(session, record_id)
651651

652652
record.chart = chart
653+
record.sql_prase = sql_prase
653654

654655
result = ChatRecord(**record.model_dump())
655656

656657
stmt = update(ChatRecord).where(and_(ChatRecord.id == record.id)).values(
657-
chart=record.chart
658+
chart=record.chart,
659+
sql_prase=record.sql_prase
658660
)
659661

660662
session.execute(stmt)

backend/apps/chat/models/chat_model.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from apps.template.generate_guess_question.generator import get_guess_question_template
1818
from apps.template.generate_predict.generator import get_predict_template
1919
from apps.template.generate_sql.generator import get_sql_template, get_sql_example_template
20+
from apps.template.prase_sql.generator import get_prase_sql_template
2021
from apps.template.select_datasource.generator import get_datasource_template
2122

2223

@@ -40,6 +41,7 @@ class OperationEnum(Enum):
4041
GENERATE_SQL_WITH_PERMISSIONS = '5'
4142
CHOOSE_DATASOURCE = '6'
4243
GENERATE_DYNAMIC_SQL = '7'
44+
PRASE_SQL = '8'
4345

4446

4547
class ChatFinishStep(Enum):
@@ -109,6 +111,7 @@ class ChatRecord(SQLModel, table=True):
109111
error: str = Field(sa_column=Column(Text, nullable=True))
110112
analysis_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
111113
predict_record_id: int = Field(sa_column=Column(BigInteger, nullable=True))
114+
sql_prase: str = Field(sa_column=Column(Text, nullable=True))
112115

113116

114117
class ChatRecordResult(BaseModel):
@@ -229,6 +232,12 @@ def predict_sys_question(self):
229232
def predict_user_question(self):
230233
return get_predict_template()['user'].format(fields=self.fields, data=self.data)
231234

235+
def prase_sql_sys_question(self):
236+
return get_prase_sql_template()['system'].format(lang=self.lang)
237+
238+
def prase_sql_user_question(self,sql='',chart=''):
239+
return get_prase_sql_template()['user'].format(sql=sql,chart=chart)
240+
232241
def datasource_sys_question(self):
233242
return get_datasource_template()['system'].format(lang=self.lang)
234243

backend/apps/chat/task/llm.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ def check_save_sql(self, session: Session, res: str) -> str:
774774

775775
return sql
776776

777-
def check_save_chart(self, session: Session, res: str) -> Dict[str, Any]:
777+
def check_save_chart(self, session: Session, res: str, sql_prase: str) -> Dict[str, Any]:
778778

779779
json_str = extract_nested_json(res)
780780
if json_str is None:
@@ -814,7 +814,7 @@ def check_save_chart(self, session: Session, res: str) -> Dict[str, Any]:
814814
if error:
815815
raise SingleMessageError(message)
816816

817-
save_chart(session=session, chart=orjson.dumps(chart).decode(), record_id=self.record.id)
817+
save_chart(session=session, chart=orjson.dumps(chart).decode(), record_id=self.record.id,sql_prase=sql_prase)
818818

819819
return chart
820820

@@ -989,6 +989,7 @@ def run_task(self, in_chat: bool = True, stream: bool = True,
989989

990990
use_dynamic_ds: bool = self.current_assistant and self.current_assistant.type in dynamic_ds_types
991991
is_page_embedded: bool = self.current_assistant and self.current_assistant.type == 4
992+
is_assistant_embedded: bool = self.current_assistant and self.current_assistant.type == 1
992993
dynamic_sql_result = None
993994
sqlbot_temp_sql_text = None
994995
assistant_dynamic_sql = None
@@ -1092,7 +1093,16 @@ def run_task(self, in_chat: bool = True, stream: bool = True,
10921093

10931094
# filter chart
10941095
SQLBotLogUtil.info(full_chart_text)
1095-
chart = self.check_save_chart(session=_session, res=full_chart_text)
1096+
1097+
# sql prase
1098+
if is_assistant_embedded:
1099+
sql_prase = self.generate_sql_paras(_session,real_execute_sql,full_chart_text)
1100+
if in_chat:
1101+
yield 'data:' + orjson.dumps(
1102+
{'content': sql_prase,
1103+
'type': 'sql_prase'}).decode() + '\n\n'
1104+
1105+
chart = self.check_save_chart(session=_session, res=full_chart_text,sql_prase=sql_prase)
10961106
SQLBotLogUtil.info(chart)
10971107

10981108
if not stream:
@@ -1283,6 +1293,49 @@ def validate_history_ds(self, session: Session):
12831293
except Exception as e:
12841294
raise SingleMessageError(f"ds is invalid [{str(e)}]")
12851295

1296+
def generate_sql_paras(self, _session: Session, real_execute_sql: Optional[str] = '',chart: Optional[str] = ''):
1297+
# prase sql
1298+
prase_sql_msg: List[Union[BaseMessage, dict[str, Any]]] = []
1299+
prase_sql_msg.append(SystemMessage(self.chat_question.prase_sql_sys_question()))
1300+
prase_sql_msg.append(HumanMessage(self.chat_question.prase_sql_user_question(real_execute_sql,chart)))
1301+
self.current_logs[OperationEnum.PRASE_SQL] = start_log(session=_session,
1302+
ai_modal_id=self.chat_question.ai_modal_id,
1303+
ai_modal_name=self.chat_question.ai_modal_name,
1304+
operate=OperationEnum.PRASE_SQL,
1305+
record_id=self.record.id,
1306+
full_message=[{'type': msg.type,
1307+
'content': msg.content}
1308+
for
1309+
msg in prase_sql_msg])
1310+
1311+
token_usage = {}
1312+
prase_res = process_stream(self.llm.stream(prase_sql_msg), token_usage)
1313+
prase_full_thinking_text = ''
1314+
prase_full_text = ''
1315+
for chunk in prase_res:
1316+
if chunk.get('content'):
1317+
prase_full_text += chunk.get('content')
1318+
if chunk.get('reasoning_content'):
1319+
prase_full_thinking_text += chunk.get('reasoning_content')
1320+
prase_sql_msg.append(AIMessage(prase_full_text))
1321+
1322+
self.current_logs[OperationEnum.PRASE_SQL] = end_log(session=_session,
1323+
log=self.current_logs[
1324+
OperationEnum.PRASE_SQL],
1325+
full_message=[
1326+
{'type': msg.type,
1327+
'content': msg.content}
1328+
for msg in prase_sql_msg],
1329+
reasoning_content=prase_full_thinking_text,
1330+
token_usage=token_usage)
1331+
1332+
prase_json_str = extract_nested_json(prase_full_text)
1333+
return prase_json_str
1334+
# if prase_json_str is None:
1335+
# raise SingleMessageError(f'Cannot parse datasource from answer: {prase_full_text}')
1336+
# ds = orjson.loads(prase_json_str)
1337+
# return ds['info']
1338+
12861339

12871340
def execute_sql_with_db(db: SQLDatabase, sql: str) -> str:
12881341
"""Execute SQL query using SQLDatabase
@@ -1455,7 +1508,6 @@ def process_stream(res: Iterator[BaseMessageChunk],
14551508
}
14561509
get_token_usage(chunk, token_usage)
14571510

1458-
14591511
def get_lang_name(lang: str):
14601512
if not lang:
14611513
return '简体中文'

backend/apps/template/generate_sql/generator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ def get_sql_template():
1212
def get_sql_example_template(db_type: Union[str, DB]):
1313
template = get_base_sql_template(db_type)
1414
return template['template']
15+

backend/apps/template/prase_sql/__init__.py

Whitespace-only changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from typing import Union
2+
3+
from apps.db.constant import DB
4+
from apps.template.template import get_base_template, get_sql_template as get_base_sql_template
5+
6+
7+
def get_prase_sql_template():
8+
template = get_base_template()
9+
return template['template']['prase_sql']
10+

backend/templates/template.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,42 @@ template:
517517
518518
### 子查询映射表:
519519
{sub_query}
520+
prase_sql:
521+
system: |
522+
### 请使用语言:{lang} 回答
523+
524+
### 说明:
525+
提供给你一句SQL和根据这个SQL已经生成的图表信息,你需要解析出sql所有的原始字段名称和sql字段别名,并把他们归类为维度、指标,如果是指标需要给出他的聚合函数是什么,同时提取图表标题,图表类型。
526+
图表信息在chart字段内,SQL在sql字段内。
527+
图表对应字段显示名称为name,图表原始字段名称为value,可以在chart.axis,chart.columns等字段内找到对应关系。
528+
529+
你必须遵守以下规则:
530+
- 生成的数据使用JSON格式返回:
531+
{{"success":true,"info":{{"type":"图表类型","title":"图表标题","limit":"sql限制条数","xAxisSource":[{{"sourceName":"sql维度原始字段名称","showName":"图表对应字段显示name"}}],"yAxisSource":[{{"sourceName":"sql指标原始字段名称","showName":"图表对应字段显示name","summary":"聚合函数"}}]}}}}
532+
- 如果不能生成数据,回答:
533+
{{"success":false,"message":"无法生成结果的原因"}}
534+
- 如何SQL存在聚合字段且聚合字段是经过多个sql原始字段运算得到的 如: sum(a*b) as c,则只取第一个原始字段a,同时原始字段a对应的sql字段别名为c
535+
536+
### 以下<example>帮助你理解问题及返回格式的例子,不要将<example>内的表结构用来回答用户的问题
537+
<example>
538+
<chat-examples>
539+
<example>
540+
<input>
541+
<chart>{{"type":"pie","title":"门店销售数量分布","axis":{{"y":{{"name":"销售数量","value":"total_sales"}},"series":{{"name":"门店","value":"shop_name"}}}}}}</chart>
542+
<sql>SELECT `t1`.`shop` AS `shop_name`, COUNT(`t1`.`id`) AS `order_count` FROM `demo_tea_order` `t1` GROUP BY `t1`.`shop` LIMIT 1000</sql>
543+
</input>
544+
<output>
545+
{{"success":true,"info":{{"type":"pie","title":"门店订单分布","limit":1000,"xAxisSource":[{{"sourceName":"shop","showName":"门店"}}],"yAxisSource":[{{"sourceName":"id","showName":"订单数量","summary":"count"}}]}}}}
546+
</output>
547+
</example>
548+
</chat-examples>
549+
<example>
550+
551+
### 响应, 请直接返回JSON结果:
552+
```json
553+
554+
user: |
555+
### chart:
556+
{chart}
557+
### sql:
558+
{sql}

frontend/src/api/chat.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export class ChatRecord {
5151
recommended_question?: string
5252
analysis_record_id?: number
5353
predict_record_id?: number
54+
sql_prase?: string
5455

5556
constructor()
5657
constructor(

0 commit comments

Comments
 (0)