Merge remote-tracking branch 'origin/main'

This commit is contained in:
wangjiahao
2025-07-21 17:44:06 +08:00
12 changed files with 262 additions and 99 deletions
+102 -5
View File
@@ -1,18 +1,37 @@
<script setup lang="ts">
import { computed } from 'vue'
import { computed, nextTick, ref } from 'vue'
import { endsWith, startsWith } from 'lodash-es'
import { useI18n } from 'vue-i18n'
import { chatApi, ChatInfo } from '@/api/chat.ts'
const props = withDefaults(
defineProps<{
recordId?: number
currentChat?: ChatInfo
questions?: string
firstChat?: boolean
}>(),
{
recordId: undefined,
currentChat: () => new ChatInfo(),
questions: '[]',
firstChat: false,
}
)
const emits = defineEmits(['clickQuestion', 'update:currentChat'])
const loading = ref(false)
const _currentChat = computed({
get() {
return props.currentChat
},
set(v) {
emits('update:currentChat', v)
},
})
const computedQuestions = computed<string>(() => {
if (
props.questions &&
@@ -25,20 +44,98 @@ const computedQuestions = computed<string>(() => {
return []
})
const emits = defineEmits(['clickQuestion'])
const { t } = useI18n()
function clickQuestion(question: string): void {
emits('clickQuestion', question)
}
async function getRecommendQuestions() {
loading.value = true
try {
const response = await chatApi.recommendQuestions(props.recordId)
const reader = response.body.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) {
break
}
const chunk = decoder.decode(value)
let _list = [chunk]
const lines = chunk.trim().split('}\n\n{')
if (lines.length > 1) {
_list = []
for (let line of lines) {
if (!line.trim().startsWith('{')) {
line = '{' + line.trim()
}
if (!line.trim().endsWith('}')) {
line = line.trim() + '}'
}
_list.push(line)
}
}
for (const str of _list) {
let data
try {
data = JSON.parse(str)
} catch (err) {
console.error('JSON string:', str)
throw err
}
if (data.code && data.code !== 200) {
ElMessage({
message: data.msg,
type: 'error',
showClose: true,
})
return
}
switch (data.type) {
case 'recommended_question':
if (
data.content &&
data.content.length > 0 &&
startsWith(data.content.trim(), '[') &&
endsWith(data.content.trim(), ']')
) {
if (_currentChat.value?.records) {
for (let record of _currentChat.value.records) {
if (record.id === props.recordId) {
record.recommended_question = data.content
await nextTick()
}
}
}
}
}
}
}
} finally {
loading.value = false
}
}
defineExpose({ getRecommendQuestions, id: () => props.recordId })
</script>
<template>
<div v-if="computedQuestions.length > 0" class="recommend-questions">
<div v-if="computedQuestions.length > 0 || loading" class="recommend-questions">
<div v-if="firstChat">{{ t('qa.guess_u_ask') }}</div>
<div v-else class="continue-ask">{{ t('qa.continue_to_ask') }}</div>
<div class="question-grid">
<div v-if="loading">
<el-button style="min-width: unset" type="primary" link loading />
</div>
<div v-else class="question-grid">
<div
v-for="(question, index) in computedQuestions"
:key="index"
@@ -5,9 +5,9 @@ import { computed, nextTick } from 'vue'
import MdComponent from '@/views/chat/component/MdComponent.vue'
const props = withDefaults(
defineProps<{
chatList: Array<ChatInfo>
chatList?: Array<ChatInfo>
currentChatId?: number
currentChat: ChatInfo
currentChat?: ChatInfo
message?: ChatMessage
loading?: boolean
}>(),
@@ -4,9 +4,9 @@ import { Chat, chatApi, ChatInfo, type ChatMessage, ChatRecord, questionApi } fr
import { computed, nextTick } from 'vue'
const props = withDefaults(
defineProps<{
chatList: Array<ChatInfo>
chatList?: Array<ChatInfo>
currentChatId?: number
currentChat: ChatInfo
currentChat?: ChatInfo
message?: ChatMessage
loading?: boolean
reasoningName: 'sql_answer' | 'chart_answer' | Array<'sql_answer' | 'chart_answer'>
@@ -5,9 +5,9 @@ import { computed, nextTick } from 'vue'
import MdComponent from '@/views/chat/component/MdComponent.vue'
const props = withDefaults(
defineProps<{
chatList: Array<ChatInfo>
chatList?: Array<ChatInfo>
currentChatId?: number
currentChat: ChatInfo
currentChat?: ChatInfo
message?: ChatMessage
loading?: boolean
}>(),
@@ -193,8 +193,9 @@ function addToDashboard() {
<template>
<div
v-if="
(!isPredict && (message?.record?.sql || message?.record?.chart)) ||
(isPredict && message?.record?.chart && data.length > 0)
!message.isTyping &&
((!isPredict && (message?.record?.sql || message?.record?.chart)) ||
(isPredict && message?.record?.chart && data.length > 0))
"
class="chart-component-container"
:class="{ 'full-screen': enlarge }"
+23 -64
View File
@@ -93,6 +93,9 @@
<ChatRow :current-chat="currentChat" :msg="message" :hide-avatar="message.first_chat">
<RecommendQuestion
v-if="message.role === 'assistant' && message.first_chat"
ref="recommendQuestionRef"
:current-chat="currentChat"
:record-id="message.record?.id"
:questions="message.recommended_question"
:first-chat="message.first_chat"
@click-question="quickAsk"
@@ -181,6 +184,9 @@
</template>
<template #footer>
<RecommendQuestion
ref="recommendQuestionRef"
:current-chat="currentChat"
:record-id="message.record?.id"
:questions="message.recommended_question"
:first-chat="message.first_chat"
@click-question="quickAsk"
@@ -303,7 +309,7 @@ import ChatListContainer from './ChatListContainer.vue'
import ChatCreator from '@/views/chat/ChatCreator.vue'
import ChatToolBar from './ChatToolBar.vue'
import { useI18n } from 'vue-i18n'
import { endsWith, find, startsWith } from 'lodash-es'
import { find } from 'lodash-es'
import icon_new_chat_outlined from '@/assets/svg/icon_new_chat_outlined.svg'
import icon_sidebar_outlined from '@/assets/svg/icon_sidebar_outlined.svg'
import icon_replace_outlined from '@/assets/svg/icon_replace_outlined.svg'
@@ -373,6 +379,7 @@ const computedMessages = computed<Array<ChatMessage>>(() => {
})
}
console.log(messages)
return messages
})
@@ -465,72 +472,24 @@ function onChatCreated(chat: ChatInfo) {
}
}
async function getRecommendQuestions(record_id?: number) {
const response = await chatApi.recommendQuestions(record_id)
const reader = response.body.getReader()
const decoder = new TextDecoder()
const recommendQuestionRef = ref()
while (true) {
const { done, value } = await reader.read()
if (done) {
break
}
const chunk = decoder.decode(value)
let _list = [chunk]
const lines = chunk.trim().split('}\n\n{')
if (lines.length > 1) {
_list = []
for (let line of lines) {
if (!line.trim().startsWith('{')) {
line = '{' + line.trim()
}
if (!line.trim().endsWith('}')) {
line = line.trim() + '}'
}
_list.push(line)
}
}
for (const str of _list) {
let data
try {
data = JSON.parse(str)
} catch (err) {
console.error('JSON string:', str)
throw err
}
if (data.code && data.code !== 200) {
ElMessage({
message: data.msg,
type: 'error',
showClose: true,
})
return
}
switch (data.type) {
case 'recommended_question':
if (
data.content &&
data.content.length > 0 &&
startsWith(data.content.trim(), '[') &&
endsWith(data.content.trim(), ']')
) {
if (currentChat.value?.records) {
for (let record of currentChat.value.records) {
if (record.id === record_id) {
record.recommended_question = data.content
}
}
}
function getRecommendQuestions(id?: number) {
nextTick(() => {
if (recommendQuestionRef.value) {
if (recommendQuestionRef.value instanceof Array) {
for (let i = 0; i < recommendQuestionRef.value.length; i++) {
const _id = recommendQuestionRef.value[i].id()
if (_id === id) {
recommendQuestionRef.value[i].getRecommendQuestions()
break
}
}
} else {
recommendQuestionRef.value.getRecommendQuestions()
}
}
}
})
}
onMounted(() => {
@@ -549,7 +508,7 @@ const chartAnswerRef = ref()
async function onChartAnswerFinish(id: number) {
loading.value = false
isTyping.value = false
await getRecommendQuestions(id)
getRecommendQuestions(id)
}
function onChartAnswerError() {