Skip to content

Commit 09a0bc8

Browse files
committed
feat: init chart
1 parent e5ae9b5 commit 09a0bc8

File tree

14 files changed

+370
-61
lines changed

14 files changed

+370
-61
lines changed

backend/apps/ai_model/model_factory.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def _init_llm(self) -> LangchainBaseLLM:
4141
model=self.config.model_name,
4242
api_key=self.config.api_key,
4343
base_url=self.config.api_base_url,
44+
stream_usage=True,
4445
**self.config.additional_params
4546
)
4647

backend/apps/chat/api/chat.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ async def stream_sql(session: SessionDep, current_user: CurrentUser, request_que
118118

119119
def run_task():
120120
try:
121+
# return id
122+
yield json.dumps({'type': 'id', 'id': llm_service.get_record().id}) + '\n\n'
123+
121124
# generate sql
122125
sql_res = llm_service.generate_sql(session=session)
123126
full_sql_text = ''

backend/apps/chat/task/llm.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ def init_record(self, session: SessionDep, current_user: CurrentUser) -> ChatRec
105105
self.record = save_question(session=session, current_user=current_user, question=self.chat_question)
106106
return self.record
107107

108+
def get_record(self):
109+
return self.record
110+
108111
def generate_sql(self, session: SessionDep):
109112
# append current question
110113
self.sql_message.append(HumanMessage(self.chat_question.sql_user_question()))

backend/template.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ template:
3434
{question}
3535
3636
### 其他规则:
37-
请使用英语(English)回答
37+
请使用英语(English)输出
3838
{rule}
3939
chart:
4040
system: |
@@ -51,7 +51,7 @@ template:
5151
{{"type":"line", "title": "标题", "axis": {{"x": {{"name":"x轴的中文名称","value": "x轴的SQL查询列(有别名用别名)"}}, "y": {{"name":"y轴的中文名称","value": "y轴的SQL查询列(有别名用别名)"}}}}
5252
其中“x”和“y”必须从SQL查询列中提取。
5353
- 如果需要饼图,则生成的 JSON 格式应为:
54-
{{"type":"pie", "title": "标题", "column": [{{"name":"中文字段名1","value":"SQL查询列1(有别名用别名)"}},{{"name":"中文字段名2","value":"SQL查询列2(有别名用别名)"}}]}}
54+
{{"type":"pie", "title": "标题", "columns": [{{"name":"中文字段名1","value":"SQL查询列1(有别名用别名)"}},{{"name":"中文字段名2","value":"SQL查询列2(有别名用别名)"}}]}}
5555
其中“column”必须从SQL查询列中提取。
5656
- 如果答案未知或者与生成JSON无关,则生成的 JSON 格式应为:
5757
{{"type":"error", "reason": "抱歉,我无法回答您的问题。"}}
@@ -80,5 +80,5 @@ template:
8080
{question}
8181
8282
### 其他规则:
83-
请使用英语(English)回答
83+
请使用英语(English)输出
8484
{rule}

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"preview": "vite preview"
1010
},
1111
"dependencies": {
12+
"@antv/g2": "^5.3.3",
1213
"@npkg/tinymce-plugins": "^0.0.7",
1314
"@tinymce/tinymce-vue": "^5.1.0",
1415
"dayjs": "^1.11.13",
Lines changed: 89 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
<script setup lang="ts">
22
33
import type {ChatMessage} from "@/api/chat.ts";
4-
import {computed, ref} from "vue";
4+
import {computed, nextTick, ref} from "vue";
55
import type {TabsPaneContext} from 'element-plus'
66
import {Loading} from "@element-plus/icons-vue";
7+
import Component from "./component/Component.vue"
78
89
const props = defineProps<{
910
message?: ChatMessage
@@ -53,9 +54,22 @@ const chartObject = computed<{
5354
return {}
5455
})
5556
56-
const currentChartType = ref<"table" | "bar" | "line" | "pie" | undefined>(undefined)
57+
const xAxis = computed(() => {
58+
if (chartObject.value?.axis?.x) {
59+
return [chartObject.value.axis.x]
60+
}
61+
return []
62+
})
63+
const yAxis = computed(() => {
64+
if (chartObject.value?.axis?.y) {
65+
return [chartObject.value.axis.y]
66+
}
67+
return []
68+
})
5769
58-
const chartType = computed<"table" | "bar" | "line" | "pie">({
70+
const currentChartType = ref<"table" | "bar" | 'column' | "line" | "pie" | undefined>(undefined)
71+
72+
const chartType = computed<"table" | "bar" | 'column' | "line" | "pie">({
5973
get() {
6074
if (currentChartType.value) {
6175
return currentChartType.value
@@ -67,19 +81,30 @@ const chartType = computed<"table" | "bar" | "line" | "pie">({
6781
}
6882
})
6983
84+
const chartRef = ref()
85+
86+
function onTypeChange(type: string) {
87+
console.log(type)
88+
nextTick(()=>{
89+
chartRef.value?.destroyChart()
90+
chartRef.value?.renderChart()
91+
})
92+
}
93+
7094
7195
</script>
7296

7397
<template>
74-
<div v-if="message">
75-
<div>
76-
<div v-if="message.isTyping">Thinking ...</div>
77-
<div v-if="chartObject.title && !message.isTyping">{{ chartObject.title }}</div>
78-
<el-tabs v-model="settings.type" class="demo-tabs" @tab-click="handleClick" tab-position="top">
98+
<el-container v-if="message">
99+
<el-header style="display: flex; align-items: center; flex-direction: row;">
100+
<div style="flex:1" v-if="message.isTyping">Thinking ...</div>
101+
<div style="flex:1" v-if="chartObject.title && !message.isTyping">{{ chartObject.title }}</div>
102+
<el-tabs v-model="settings.type" class="type-tabs" @tab-click="handleClick" tab-position="top">
79103
<el-tab-pane label="Chart" name="chart">
80104
<template #label>
81-
<el-select v-model="chartType" style="width: 80px" :disabled="settings.type!== 'chart'">
105+
<el-select v-model="chartType" style="width: 80px" :disabled="settings.type!== 'chart'" @change="onTypeChange">
82106
<el-option value="table">table</el-option>
107+
<el-option value="cloumn">cloumn</el-option>
83108
<el-option value="bar">bar</el-option>
84109
<el-option value="line">line</el-option>
85110
<el-option value="pie">pie</el-option>
@@ -88,59 +113,61 @@ const chartType = computed<"table" | "bar" | "line" | "pie">({
88113
</el-tab-pane>
89114
<el-tab-pane label="SQL" name="sql"></el-tab-pane>
90115
</el-tabs>
91-
</div>
92-
<template v-if="message.record">
93-
<el-collapse expand-icon-position="left">
94-
<el-collapse-item name="1">
95-
<template #title>
96-
Inference process
97-
<el-icon v-if="props.message?.isTyping">
98-
<Loading/>
99-
</el-icon>
100-
</template>
101-
<div>
102-
<template v-if="message.record.sql_answer">
103-
<div>SQL Generation:</div>
104-
<div v-if="message.record.sql_answer" v-html="renderSqlThinking"></div>
105-
</template>
106-
<template v-if="message.record.chart_answer">
107-
<el-divider></el-divider>
108-
<div>Chart Generation:</div>
109-
<div v-html="renderChartThinking"></div>
116+
</el-header>
117+
<el-container direction="vertical">
118+
<template v-if="message.record">
119+
<el-collapse expand-icon-position="left">
120+
<el-collapse-item name="1">
121+
<template #title>
122+
Inference process
123+
<el-icon v-if="props.message?.isTyping">
124+
<Loading/>
125+
</el-icon>
110126
</template>
111-
</div>
112-
</el-collapse-item>
113-
</el-collapse>
114-
<div class="answer-content">
115-
<template v-if="settings.type === 'sql'">
116-
<div>
117-
<div v-if="message.record.sql">
118-
{{ message.record.sql }}
127+
<div>
128+
<template v-if="message.record.sql_answer">
129+
<div>SQL Generation:</div>
130+
<div v-if="message.record.sql_answer" v-html="renderSqlThinking"></div>
131+
</template>
132+
<template v-if="message.record.chart_answer">
133+
<el-divider></el-divider>
134+
<div>Chart Generation:</div>
135+
<div v-html="renderChartThinking"></div>
136+
</template>
119137
</div>
120-
</div>
121-
</template>
122-
<template v-else-if="settings.type === 'chart'">
123-
<div>
124-
125-
<div v-if="message.record.chart">
126-
{{ chartObject }}
138+
</el-collapse-item>
139+
</el-collapse>
140+
<div class="answer-content">
141+
<template v-if="settings.type === 'sql'">
142+
<div>
143+
<div v-if="message.record.sql">
144+
{{ message.record.sql }}
145+
</div>
146+
</div>
147+
</template>
148+
<template v-else-if="settings.type === 'chart'">
149+
<div>
150+
<div v-if="message.record.chart">
151+
<Component
152+
ref="chartRef"
153+
v-if="message.record.id"
154+
:id="message.record.id"
155+
:type="chartType"
156+
:columns="chartObject?.columns"
157+
:x="xAxis"
158+
:y="yAxis"
159+
:data="dataObject.data"/>
160+
</div>
127161
</div>
128-
</div>
129-
<div v-if="message.record.error" style="color: red">
130-
{{ message.record.error }}
131-
</div>
132-
</template>
133-
<template v-else>
134-
<div>
135-
<div v-if="dataObject.fields">
136-
{{ dataObject.fields }}
137-
{{ dataObject.data }}
162+
<div v-if="message.record.error" style="color: red">
163+
{{ message.record.error }}
138164
</div>
139-
</div>
140-
</template>
141-
</div>
142-
</template>
143-
</div>
165+
</template>
166+
</div>
167+
</template>
168+
</el-container>
169+
<!--<el-footer></el-footer>-->
170+
</el-container>
144171
</template>
145172

146173
<style scoped lang="less">
@@ -152,4 +179,8 @@ const chartType = computed<"table" | "bar" | "line" | "pie">({
152179
.answer-content {
153180
padding: 12px;
154181
}
182+
183+
.type-tabs {
184+
margin-right: 24px;
185+
}
155186
</style>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export abstract class BaseChart {
2+
id: string;
3+
_name: string = 'base-chart';
4+
axis: Array<{ name: string, value: string, type?: 'x' | 'y' }> = [];
5+
data: Array<{ [key: string]: any }> = [];
6+
7+
constructor(id: string, name: string) {
8+
this.id = id;
9+
this._name = name;
10+
}
11+
12+
init(axis: Array<{ name: string, value: string, type?: 'x' | 'y' }>, data: Array<{ [key: string]: any }>): void {
13+
this.axis = axis;
14+
this.data = data;
15+
}
16+
17+
abstract render(): void
18+
19+
abstract destroy(): void
20+
21+
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import {BaseChart} from "@/views/chat/component/BaseChart.ts";
2+
import {Chart} from '@antv/g2';
3+
4+
export abstract class BaseG2Chart extends BaseChart {
5+
6+
chart: Chart;
7+
8+
constructor(id: string, name: string) {
9+
super(id, name)
10+
this.chart = new Chart({
11+
container: id,
12+
autoFit: true,
13+
})
14+
}
15+
16+
render() {
17+
this.chart?.render()
18+
}
19+
20+
destroy() {
21+
this.chart?.destroy()
22+
}
23+
24+
25+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<script setup lang="ts">
2+
import {computed, onMounted, onUnmounted, ref} from "vue";
3+
import {getChartInstance} from "@/views/chat/component/index.ts";
4+
import type {BaseChart} from "@/views/chat/component/BaseChart.ts";
5+
6+
const params = withDefaults(defineProps<{
7+
id: string | number
8+
type: string
9+
data?: Array<{ [key: string]: any }>
10+
columns?: Array<{ name: string, value: string }>
11+
x?: Array<{ name: string, value: string }>
12+
y?: Array<{ name: string, value: string }>
13+
}>(), {
14+
data: () => [],
15+
columns: () => [],
16+
x: () => [],
17+
y: () => [],
18+
})
19+
20+
const chartId = computed(() => {
21+
return "chart-component-" + params.id
22+
})
23+
24+
const axis = computed(() => {
25+
const _list: Array<{ name: string, value: string, type?: 'x' | 'y' }> = []
26+
params.columns.forEach(column => {
27+
_list.push({name: column.name, value: column.value})
28+
})
29+
params.x.forEach(column => {
30+
_list.push({name: column.name, value: column.value, type: 'x'})
31+
})
32+
params.y.forEach(column => {
33+
_list.push({name: column.name, value: column.value, type: 'y'})
34+
})
35+
return _list
36+
})
37+
38+
const chartInstance = ref<BaseChart>()
39+
40+
function renderChart() {
41+
chartInstance.value = getChartInstance(params.type, chartId.value)
42+
console.log(chartInstance.value)
43+
if (chartInstance.value) {
44+
chartInstance.value.init(axis.value, params.data)
45+
chartInstance.value.render()
46+
}
47+
}
48+
49+
function destroyChart() {
50+
if (chartInstance.value) {
51+
chartInstance.value.destroy()
52+
chartInstance.value = undefined
53+
}
54+
}
55+
56+
defineExpose({
57+
renderChart,
58+
destroyChart,
59+
})
60+
61+
onMounted(() => {
62+
renderChart()
63+
})
64+
65+
onUnmounted(() => {
66+
destroyChart()
67+
})
68+
69+
</script>
70+
71+
<template>
72+
<div class="chart-container" :id="chartId"></div>
73+
</template>
74+
75+
<style scoped lang="less">
76+
.chart-container {
77+
height: 100%;
78+
width: 100%;
79+
min-height: 80px;
80+
min-width: 100px;
81+
}
82+
</style>

0 commit comments

Comments
 (0)