Merge remote-tracking branch 'origin/main'

This commit is contained in:
wangjiahao
2025-07-18 18:17:09 +08:00
12 changed files with 145 additions and 435 deletions
+1
View File
@@ -66,6 +66,7 @@ template:
其中“y”和“series”必须从SQL查询列中提取。
- 如果答案未知或者与生成JSON无关,则生成的 JSON 格式应为:
{{"type":"error", "reason": "抱歉,我无法回答您的问题。"}}
- JSON中生成的标题需要尽量精简
### 示例:
如果 SQL 为: SELECT products_sales_data.category, AVG(products_sales_data.price) AS average_price FROM products_sales_data GROUP BY products_sales_data.category;
+33 -17
View File
@@ -75,20 +75,28 @@
"qa": {
"new_chat": "New Chat",
"start_sqlbot": "Start SQLBot",
"title": "Smart Query",
"placeholder": "Enter your question",
"title": "Smart Data Query",
"placeholder": "Please enter your question",
"ask": "Ask",
"loading": "Loading...",
"no_data": "No Data",
"no_data": "No data available",
"error": "An error occurred, please try again later",
"question_placeholder": "Press Enter to submit, or use Ctrl + Enter for new line",
"greeting": "Hi, I'm SQLBot, nice to serve you",
"hint_description": "I can help query data, generate charts, detect data anomalies, predict data, etc. Please select a data source to start smart querying~",
"question_placeholder": "Press Enter to submit, or use Ctrl + Enter for a new line",
"greeting": "Hello, I'm SQLBot. How can I assist you today?",
"hint_description": "I can query data, generate charts, detect anomalies, predict trends, and more. Please select a data source to begin!",
"select_datasource": "Select Data Source",
"view_more": "View More",
"selected_datasource": "Selected Data Source",
"empty_datasource": "No data source available, please create one first!",
"datasource_not_exist": "Data source does not exist"
"empty_datasource": "No data sources available. Please create one before starting Smart Data Query!",
"datasource_not_exist": "Data source does not exist",
"guess_u_ask": "You might want to ask:",
"continue_to_ask": "Continue asking:",
"data_analysis": "Data Analysis",
"data_predict": "Data Prediction",
"chat_search": "Search",
"thinking": "Thinking",
"thinking_step": "Thought Process",
"ask_again": "Regenerate"
},
"ds": {
"title": "Data Source",
@@ -353,19 +361,27 @@
"no_fields_yet": "No fields yet"
},
"chat": {
"type": "Chart Type",
"chart_type": {
"table": "Table",
"bar": "Bar",
"column": "Column",
"line": "Line",
"pie": "Pie"
"table": "Detail Table",
"bar": "Bar Chart",
"column": "Column Chart",
"line": "Line Chart",
"pie": "Pie Chart"
},
"sql_generation": "SQL Generation",
"chart_generation": "Chart Generation",
"inference_process": "Inference process",
"show_sql": "View SQL",
"export_to": "Export As",
"excel": "Excel",
"picture": "Image",
"add_to_dashboard": "Add to Dashboard",
"full_screen": "Full Screen",
"exit_full_screen": "Exit Full Screen",
"sql_generation": "Generate SQL",
"chart_generation": "Generate Chart",
"inference_process": "Thought Process",
"thinking": "Thinking",
"data_analysis": "Data Analysis",
"data_predict": "Data Predict"
"data_predict": "Data Prediction"
},
"about": {
"title": "About",
+2 -2
View File
@@ -98,8 +98,8 @@
"datasource_not_exist": "数据源不存在",
"guess_u_ask": "猜你想问:",
"continue_to_ask": "继续提问:",
"data_analysis": "数据预测",
"data_predict": "数据分析",
"data_analysis": "数据分析",
"data_predict": "数据预测",
"chat_search": "搜索",
"thinking": "思考中",
"thinking_step": "思考过程",
-357
View File
@@ -1,357 +0,0 @@
<script setup lang="ts">
import type { ChatMessage } from '@/api/chat.ts'
import { computed, ref } from 'vue'
import { Loading } from '@element-plus/icons-vue'
import MdComponent from './component/MdComponent.vue'
import DisplayChartBlock from './component/DisplayChartBlock.vue'
import type { ChartTypes } from '@/views/chat/component/BaseChart.ts'
import { ArrowDown } from '@element-plus/icons-vue'
import ICON_BAR from '@/assets/svg/chart/icon_bar_outlined.svg'
import ICON_COLUMN from '@/assets/svg/chart/icon_dashboard_outlined.svg'
import ICON_LINE from '@/assets/svg/chart/icon_chart-line.svg'
import ICON_PIE from '@/assets/svg/chart/icon_pie_outlined.svg'
import ICON_TABLE from '@/assets/svg/chart/icon_form_outlined.svg'
import { find } from 'lodash-es'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps<{
message?: ChatMessage
}>()
const settings = ref<{
type: 'chart' | 'sql'
showAnswer: boolean
}>({
type: 'chart',
showAnswer: false,
})
const renderSqlThinking = computed(() => {
return props.message?.record?.sql_answer ?? ''
})
const renderChartThinking = computed(() => {
return props.message?.record?.chart_answer ?? ''
})
const renderSQL = computed(() => {
return props.message?.record?.sql
? `\`\`\`sql
${props.message?.record?.sql}
`
: ''
})
const dataObject = computed<{
fields: Array<string>
data: Array<{ [key: string]: any }>
}>(() => {
if (props.message?.record?.data) {
if (typeof props.message?.record?.data === 'string') {
return JSON.parse(props.message.record.data)
} else {
return props.message.record.data
}
}
return {}
})
const chartObject = computed<{
type: ChartTypes
title: string
axis: {
x: { name: string; value: string }
y: { name: string; value: string }
series: { name: string; value: string }
}
columns: Array<{ name: string; value: string }>
}>(() => {
if (props.message?.record?.chart) {
return JSON.parse(props.message.record.chart)
}
return {}
})
const currentChartType = ref<ChartTypes | undefined>(undefined)
const chartType = computed<ChartTypes>({
get() {
if (currentChartType.value) {
return currentChartType.value
}
return chartObject.value.type ?? 'table'
},
set(v) {
currentChartType.value = v
},
})
function clickTab(type: 'chart' | 'sql') {
settings.value.type = type
}
const chartSelectRef = ref()
function openChartSelect() {
chartSelectRef.value.toggleMenu()
}
const chartTypeList = computed(() => {
const _list = [
{
value: 'table',
name: t('chat.chart_type.table'),
icon: ICON_TABLE,
},
]
if (chartObject.value) {
switch (chartObject.value.type) {
case 'table':
break
case 'column':
case 'bar':
case 'line':
_list.push({
value: 'column',
name: t('chat.chart_type.column'),
icon: ICON_COLUMN,
})
_list.push({
value: 'bar',
name: t('chat.chart_type.bar'),
icon: ICON_BAR,
})
_list.push({
value: 'line',
name: t('chat.chart_type.line'),
icon: ICON_LINE,
})
break
case 'pie':
_list.push({
value: 'pie',
name: t('chat.chart_type.pie'),
icon: ICON_PIE,
})
}
}
return _list
})
const currentChartTypeIcon = computed(() => {
return find(chartTypeList.value, (c) => c.value === chartType.value)?.icon ?? ICON_TABLE
})
const chartRef = ref()
function onTypeChange() {
chartRef.value?.onTypeChange()
}
</script>
<template>
<el-container v-if="message">
<el-header style="display: flex; align-items: center; flex-direction: row">
<div style="flex: 1">
<div v-if="message.isTyping">{{ t('chat.thinking') }} ...</div>
<div v-if="chartObject.title && !message.isTyping">{{ chartObject.title }}</div>
</div>
<div class="tab-container">
<div class="base-chart-choose-btn" @click="clickTab('chart')">
<div
class="chart-choose-btn"
:class="{ active: settings.type === 'chart', 'no-click': settings.type !== 'chart' }"
@click="openChartSelect"
>
<el-icon size="34">
<component :is="currentChartTypeIcon" />
</el-icon>
<el-icon>
<ArrowDown />
</el-icon>
</div>
<el-select
ref="chartSelectRef"
v-model="chartType"
class="inner-select"
:disabled="settings.type !== 'chart'"
@change="onTypeChange"
>
<el-option v-for="t in chartTypeList" :key="t.value" :value="t.value">
<div class="inner-chart-option">
<el-icon size="34">
<component :is="t.icon" />
</el-icon>
{{ t.name }}
</div>
</el-option>
</el-select>
</div>
<div
class="chart-choose-btn"
:class="{ active: settings.type !== 'chart' }"
@click="clickTab('sql')"
>
SQL
</div>
</div>
</el-header>
<el-container direction="vertical">
<template v-if="message.record">
<el-collapse expand-icon-position="left">
<el-collapse-item name="1" class="md-collapse">
<template #title>
{{ t('chat.inference_process') }}
<el-icon v-if="props.message?.isTyping">
<Loading />
</el-icon>
</template>
<div>
<template v-if="message.record.sql_answer">
<div style="font-weight: 500">{{ t('chat.sql_generation') }}:</div>
<MdComponent
v-if="message.record.sql_answer"
:message="renderSqlThinking"
></MdComponent>
</template>
<template v-if="message.record.chart_answer">
<el-divider></el-divider>
<div style="font-weight: 500">{{ t('chat.chart_generation') }}:</div>
<MdComponent :message="renderChartThinking"></MdComponent>
</template>
</div>
</el-collapse-item>
</el-collapse>
<div class="answer-content">
<template v-if="settings.type === 'sql'">
<div>
<MdComponent v-if="message.record.sql" :message="renderSQL"></MdComponent>
</div>
</template>
<template v-else-if="settings.type === 'chart'">
<DisplayChartBlock
:id="message.record.id"
ref="chartRef"
:chart-type="chartType"
:message="message"
:data="dataObject.data"
/>
<div v-if="message.record.error" style="color: red">
{{ message.record.error }}
</div>
</template>
</div>
</template>
</el-container>
<slot :data="{ id: message.record?.id, chartType: chartType, chartObject: chartObject }"></slot>
</el-container>
</template>
<style scoped lang="less">
:deep(.ed-tabs__nav-scroll) {
display: flex;
justify-content: flex-end;
}
.answer-content {
padding: 12px;
}
.type-tabs {
margin-right: 24px;
}
.chart-base-container {
padding: 20px;
background: rgba(224, 224, 226, 0.29);
}
.tab-container {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
}
.md-collapse {
:deep(.ed-collapse-item__content) {
padding: 16px 22px;
}
}
.base-chart-choose-btn {
cursor: pointer;
color: var(--ed-color-primary);
font-size: 16px;
font-weight: 500;
height: 34px;
border-radius: 4px;
position: relative;
&:hover {
background: var(--ed-color-primary-light-5);
color: var(--ed-color-primary-light-9);
}
.inner-select {
position: absolute;
:deep(.ed-select__wrapper) {
height: 0;
padding: unset;
min-height: unset;
line-height: unset;
}
:deep(.ed-select__suffix) {
display: none;
}
:deep(.ed-select__selected-item) {
display: none;
}
}
}
.inner-chart-option {
display: flex;
align-items: center;
flex-direction: row;
padding-right: 4px;
}
.chart-choose-btn {
cursor: pointer;
color: var(--ed-color-primary);
font-size: 16px;
font-weight: 500;
height: 34px;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 0 4px;
&.no-click {
pointer-events: none;
}
&.active {
background: var(--ed-color-primary-light-7);
color: var(--ed-color-primary);
}
&:hover {
background: var(--ed-color-primary-light-5);
color: var(--ed-color-primary-light-9);
}
}
</style>
@@ -193,7 +193,12 @@ defineExpose({ sendMessage, index: index.value, chatList: _chatList })
>
<MdComponent :message="message.record?.analysis" style="margin-top: 12px" />
<slot></slot>
<slot name="footer"></slot>
<template #tool>
<slot name="tool"></slot>
</template>
<template #footer>
<slot name="footer"></slot>
</template>
</BaseAnswer>
</template>
@@ -62,9 +62,9 @@ function clickShow() {
}
onMounted(() => {
if (props.loading) {
show.value = true
}
// if (props.loading) {
// show.value = true
// }
})
</script>
@@ -91,6 +91,9 @@ onMounted(() => {
</div>
<div class="answer-container">
<slot></slot>
<el-button v-if="message.isTyping" style="min-width: unset" type="primary" link loading />
<slot name="tool"></slot>
<slot name="footer"></slot>
</div>
</div>
</template>
@@ -223,7 +223,12 @@ defineExpose({ sendMessage, index: index.value })
<template>
<BaseAnswer v-if="message" :message="message" :reasoning-name="reasoningName" :loading="_loading">
<slot></slot>
<slot name="footer"></slot>
<template #tool>
<slot name="tool"></slot>
</template>
<template #footer>
<slot name="footer"></slot>
</template>
</BaseAnswer>
</template>
@@ -194,7 +194,12 @@ defineExpose({ sendMessage, index: index.value, chatList: _chatList })
<BaseAnswer v-if="message" :message="message" :reasoning-name="['predict']" :loading="_loading">
<MdComponent :message="message.record?.predict_content" style="margin-top: 12px" />
<slot></slot>
<slot name="footer"></slot>
<template #tool>
<slot name="tool"></slot>
</template>
<template #footer>
<slot name="footer"></slot>
</template>
</BaseAnswer>
</template>
@@ -15,6 +15,7 @@ import icon_into_item_outlined from '@/assets/svg/icon_into-item_outlined.svg'
import icon_window_max_outlined from '@/assets/svg/icon_window-max_outlined.svg'
import icon_window_mini_outlined from '@/assets/svg/icon_window-mini_outlined.svg'
import { useI18n } from 'vue-i18n'
import MdComponent from '@/views/chat/component/MdComponent.vue'
const props = withDefaults(
defineProps<{
@@ -178,9 +179,12 @@ function onExitFullScreen() {
dialogVisible.value = false
}
const sqlShow = ref(false)
function showSql() {
console.log('todo')
sqlShow.value = true
}
function addToDashboard() {
console.log('todo')
}
@@ -234,7 +238,7 @@ function addToDashboard() {
<div>
<el-tooltip effect="dark" :content="t('chat.show_sql')" placement="top">
<el-button class="tool-btn" text @click="showSql">
<el-button v-if="message?.record?.sql" class="tool-btn" text @click="showSql">
<el-icon size="16">
<icon_sql_outlined />
</el-icon>
@@ -309,6 +313,21 @@ function addToDashboard() {
@exit-full-screen="onExitFullScreen"
/>
</el-dialog>
<el-drawer
v-model="sqlShow"
size="600"
:title="t('chat.show_sql')"
direction="rtl"
body-class="chart-sql-drawer-body"
>
<div>
<MdComponent
:message="'```sql\n' + message.record?.sql + '\n```'"
style="margin-top: 12px"
/>
</div>
</el-drawer>
</div>
</template>
@@ -322,6 +341,9 @@ function addToDashboard() {
.chart-fullscreen-dialog-body {
padding: 0;
}
.chart-sql-drawer-body {
padding: 24px;
}
</style>
<style scoped lang="less">
.chart-component-container {
@@ -358,6 +380,7 @@ function addToDashboard() {
align-items: center;
flex-direction: row;
gap: 16px;
.tool-btn {
width: 24px;
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted } from 'vue'
import { computed, nextTick, onMounted, onUnmounted } from 'vue'
import { getChartInstance } from '@/views/chat/component/index.ts'
import type { BaseChart, ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
import { useEmitt } from '@/utils/useEmitt.ts'
@@ -78,7 +78,9 @@ defineExpose({
})
onMounted(() => {
renderChart()
nextTick(() => {
renderChart()
})
})
onUnmounted(() => {
@@ -1,6 +1,6 @@
<script setup lang="ts">
import md from '@/utils/markdown.ts'
import 'highlight.js/styles/github-dark-dimmed.min.css'
import 'highlight.js/styles/github.min.css'
import 'github-markdown-css/github-markdown-light.css'
import { computed } from 'vue'
+55 -48
View File
@@ -123,59 +123,62 @@
>
{{ message.record?.error }}
</div>
<ChatToolBar :message="message">
<div class="tool-btns">
<el-tooltip effect="dark" :content="t('qa.ask_again')" placement="top">
<el-button
class="tool-btn"
text
:disabled="isTyping"
@click="askAgain(message)"
>
<el-icon size="18">
<icon_replace_outlined />
</el-icon>
</el-button>
</el-tooltip>
<template v-if="message.record?.chart">
<div class="divider"></div>
<div>
<template #tool>
<ChatToolBar :message="message">
<div class="tool-btns">
<el-tooltip effect="dark" :content="t('qa.ask_again')" placement="top">
<el-button
class="tool-btn"
text
:disabled="isTyping"
@click="clickAnalysis(message.record?.id)"
@click="askAgain(message)"
>
<span class="tool-btn-inner">
<el-icon size="18">
<icon_screen_outlined />
</el-icon>
<span class="btn-text">
{{ t('chat.data_analysis') }}
</span>
</span>
<el-icon size="18">
<icon_replace_outlined />
</el-icon>
</el-button>
</div>
<div>
<el-button
class="tool-btn"
text
:disabled="isTyping"
@click="clickPredict(message.record?.id)"
>
<span class="tool-btn-inner">
<el-icon size="18">
<icon_start_outlined />
</el-icon>
<span class="btn-text">
{{ t('chat.data_predict') }}
</el-tooltip>
<template v-if="message.record?.chart">
<div class="divider"></div>
<div>
<el-button
class="tool-btn"
text
:disabled="isTyping"
@click="clickAnalysis(message.record?.id)"
>
<span class="tool-btn-inner">
<el-icon size="18">
<icon_screen_outlined />
</el-icon>
<span class="btn-text">
{{ t('chat.data_analysis') }}
</span>
</span>
</span>
</el-button>
</div>
</template>
</div>
</ChatToolBar>
</el-button>
</div>
<div>
<el-button
class="tool-btn"
text
:disabled="isTyping"
@click="clickPredict(message.record?.id)"
>
<span class="tool-btn-inner">
<el-icon size="18">
<icon_start_outlined />
</el-icon>
<span class="btn-text">
{{ t('chat.data_predict') }}
</span>
</span>
</el-button>
</div>
</template>
</div>
</ChatToolBar>
</template>
<template #footer>
<RecommendQuestion
:questions="message.recommended_question"
@@ -204,7 +207,9 @@
>
{{ message.record?.error }}
</div>
<ChatToolBar :message="message" />
<template #tool>
<ChatToolBar :message="message" />
</template>
</AnalysisAnswer>
<PredictAnswer
v-if="
@@ -227,7 +232,9 @@
>
{{ message.record?.error }}
</div>
<ChatToolBar :message="message" />
<template #tool>
<ChatToolBar :message="message" />
</template>
</PredictAnswer>
</template>
</ChatRow>