This commit is contained in:
2025-09-08 16:37:50 +08:00
parent 00dfe3b44b
commit a930d56d5f

View File

@@ -0,0 +1,258 @@
<script setup lang="ts">
import BaseAnswer from './BaseAnswer.vue'
import { Chat, chatApi, ChatInfo, type ChatMessage, ChatRecord, questionApi } from '@/api/chat.ts'
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import ChartBlock from '@/views/chat/chat-block/ChartBlock.vue'
const props = withDefaults(
defineProps<{
chatList?: Array<ChatInfo>
currentChatId?: number
currentChat?: ChatInfo
message?: ChatMessage
loading?: boolean
reasoningName: 'sql_answer' | 'chart_answer' | Array<'sql_answer' | 'chart_answer'>
}>(),
{
chatList: () => [],
currentChatId: undefined,
currentChat: () => new ChatInfo(),
message: undefined,
loading: false,
}
)
const emits = defineEmits([
'finish',
'error',
'stop',
'update:loading',
'update:chatList',
'update:currentChat',
'update:currentChatId',
])
const index = computed(() => {
if (props.message?.index) {
return props.message.index
}
if (props.message?.index === 0) {
return 0
}
return -1
})
const _currentChatId = computed({
get() {
return props.currentChatId
},
set(v) {
emits('update:currentChatId', v)
},
})
const _currentChat = computed({
get() {
return props.currentChat
},
set(v) {
emits('update:currentChat', v)
},
})
const _chatList = computed({
get() {
return props.chatList
},
set(v) {
emits('update:chatList', v)
},
})
const _loading = computed({
get() {
return props.loading
},
set(v) {
emits('update:loading', v)
},
})
const stopFlag = ref(false)
const sendMessage = async () => {
stopFlag.value = false
_loading.value = true
if (index.value < 0) {
_loading.value = false
return
}
const currentRecord: ChatRecord = _currentChat.value.records[index.value]
let error: boolean = false
if (_currentChatId.value === undefined) {
error = true
}
if (error) return
try {
const controller: AbortController = new AbortController()
const param = {
question: currentRecord.question,
chat_id: _currentChatId.value,
}
const response = await questionApi.add(param, controller)
const reader = response.body.getReader()
const decoder = new TextDecoder('utf-8')
let sql_answer = ''
let chart_answer = ''
let tempResult = ''
while (true) {
if (stopFlag.value) {
controller.abort()
break
}
const { done, value } = await reader.read()
if (done) {
_loading.value = false
break
}
let chunk = decoder.decode(value, { stream: true })
tempResult += chunk
const split = tempResult.match(/data:.*}\n\n/g)
if (split) {
chunk = split.join('')
tempResult = tempResult.replace(chunk, '')
} else {
continue
}
if (chunk && chunk.startsWith('data:{')) {
if (split) {
for (const str of split) {
let data
try {
data = JSON.parse(str.replace('data:{', '{'))
} catch (err) {
console.error('JSON string:', str)
throw err
}
if (data.code && data.code !== 200) {
ElMessage({
message: data.msg,
type: 'error',
showClose: true,
})
_loading.value = false
return
}
switch (data.type) {
case 'id':
currentRecord.id = data.id
_currentChat.value.records[index.value].id = data.id
break
case 'info':
console.info(data.msg)
break
case 'brief':
_currentChat.value.brief = data.brief
_chatList.value.forEach((c: Chat) => {
if (c.id === _currentChat.value.id) {
c.brief = _currentChat.value.brief
}
})
break
case 'error':
currentRecord.error = data.content
emits('error')
break
case 'sql-result':
sql_answer += data.reasoning_content
_currentChat.value.records[index.value].sql_answer = sql_answer
break
case 'sql':
_currentChat.value.records[index.value].sql = data.content
break
case 'sql-data':
getChatData(_currentChat.value.records[index.value].id)
break
case 'chart-result':
chart_answer += data.reasoning_content
_currentChat.value.records[index.value].chart_answer = chart_answer
break
case 'chart':
_currentChat.value.records[index.value].chart = data.content
break
case 'finish':
emits('finish', currentRecord.id)
break
}
await nextTick()
}
}
}
}
} catch (error) {
if (!currentRecord.error) {
currentRecord.error = ''
}
if (currentRecord.error.trim().length !== 0) {
currentRecord.error = currentRecord.error + '\n'
}
currentRecord.error = currentRecord.error + 'Error:' + error
console.error('Error:', error)
emits('error')
} finally {
_loading.value = false
}
}
function getChatData(recordId?: number) {
chatApi.get_chart_data(recordId).then((response) => {
_currentChat.value.records.forEach((record) => {
if (record.id === recordId) {
record.data = response
}
})
})
}
function stop() {
stopFlag.value = true
_loading.value = false
emits('stop')
}
onBeforeUnmount(() => {
stop()
})
onMounted(() => {
if (props.message?.record?.id && props.message?.record?.finish) {
getChatData(props.message.record.id)
}
})
defineExpose({ sendMessage, index: () => index.value, stop })
</script>
<template>
<BaseAnswer v-if="message" :message="message" :reasoning-name="reasoningName" :loading="_loading">
<ChartBlock style="margin-top: 6px" :message="message" />
<slot></slot>
<template #tool>
<slot name="tool"></slot>
</template>
<template #footer>
<slot name="footer"></slot>
</template>
</BaseAnswer>
</template>
<style scoped lang="less"></style>