mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
feat: chat
This commit is contained in:
@@ -24,6 +24,7 @@ export interface ChatMessage {
|
||||
isTyping?: boolean
|
||||
first_chat?: boolean
|
||||
recommended_question?: string
|
||||
index: number
|
||||
}
|
||||
|
||||
export class ChatRecord {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00004 10.5759L13.4212 5.15476C13.5514 5.02459 13.7624 5.02459 13.8926 5.15476L14.364 5.62616C14.4942 5.75634 14.4942 5.96739 14.364 6.09757L8.47145 11.9901C8.2111 12.2505 7.78899 12.2505 7.52864 11.9901L1.63608 6.09757C1.50591 5.96739 1.50591 5.75634 1.63608 5.62616L2.10749 5.15476C2.23766 5.02459 2.44872 5.02459 2.57889 5.15476L8.00004 10.5759Z" fill="#646A73"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 481 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00004 6.6666L13.4212 12.0878C13.5514 12.2179 13.7624 12.2179 13.8926 12.0878L14.364 11.6164C14.4942 11.4862 14.4942 11.2751 14.364 11.1449L8.47145 5.25239C8.2111 4.99204 7.78899 4.99204 7.52864 5.25239L1.63608 11.1449C1.50591 11.2751 1.50591 11.4862 1.63608 11.6164L2.10749 12.0878C2.23766 12.2179 2.44872 12.2179 2.57889 12.0878L8.00004 6.6666Z" fill="#646A73"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 479 B |
@@ -82,7 +82,9 @@
|
||||
"continue_to_ask": "继续提问:",
|
||||
"data_analysis": "数据预测",
|
||||
"data_predict": "数据分析",
|
||||
"chat_search": "搜索"
|
||||
"chat_search": "搜索",
|
||||
"thinking": "思考中",
|
||||
"thinking_step": "思考过程"
|
||||
},
|
||||
"ds": {
|
||||
"title": "数据源",
|
||||
@@ -354,4 +356,4 @@
|
||||
"back_community": "还原至社区版",
|
||||
"confirm_tips": "确定还原至社区版?"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { ChatMessage } from '@/api/chat.ts'
|
||||
|
||||
defineProps<{
|
||||
msg?: ChatMessage
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chat-block-outer">
|
||||
<div class="chat-block">
|
||||
<slot>
|
||||
<div class="chat-block-content">
|
||||
<!-- <div v-dompurify-html="msg?.content"></div> -->
|
||||
<div v-if="msg?.role === 'user'" v-html="msg.content"></div>
|
||||
<div v-else v-dompurify-html="msg?.content"></div>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="chat-block-footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chat-block-outer {
|
||||
.chat-block {
|
||||
border-radius: 2px;
|
||||
background-color: white;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
|
||||
.chat-block-content {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-block-footer {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
// import ChatBlock from './ChatBlock.vue'
|
||||
// import ChatRecordFirst from './ChatRecordFirst.vue'
|
||||
import { ChatInfo, type ChatMessage } from '@/api/chat.ts'
|
||||
// import { UserFilled } from '@element-plus/icons-vue'
|
||||
import logo_fold from '@/assets/LOGO-fold.svg'
|
||||
|
||||
withDefaults(
|
||||
@@ -18,42 +15,52 @@ withDefaults(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="chat-row" :class="{ 'right-to-left': msg.role === 'user' }">
|
||||
<el-avatar v-if="msg.role === 'assistant'" class="ai-avatar" shape="square">
|
||||
<el-icon>
|
||||
<logo_fold v-if="!hideAvatar" />
|
||||
</el-icon>
|
||||
</el-avatar>
|
||||
<div :class="{ 'row-full': msg.role === 'assistant' }">
|
||||
<slot></slot>
|
||||
<div class="chat-row-container">
|
||||
<div class="chat-row" :class="{ 'right-to-left': msg.role === 'user' }">
|
||||
<div v-if="msg.role === 'assistant'" class="ai-avatar">
|
||||
<el-icon>
|
||||
<logo_fold v-if="!hideAvatar" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div :class="{ 'row-full': msg.role === 'assistant' }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chat-row {
|
||||
.chat-row-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
padding: 20px 0 0;
|
||||
max-width: 800px;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
|
||||
&.right-to-left {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.chat-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 20px 0 0;
|
||||
|
||||
.row-full {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
width: 100%;
|
||||
|
||||
.ai-avatar {
|
||||
font-size: 28px;
|
||||
background: transparent;
|
||||
width: 28px;
|
||||
&.right-to-left {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.row-full {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.ai-avatar {
|
||||
font-size: 28px;
|
||||
background: transparent;
|
||||
width: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -37,11 +37,16 @@ function clickQuestion(question: string): void {
|
||||
<template>
|
||||
<div v-if="computedQuestions.length > 0" class="recommend-questions">
|
||||
<div v-if="firstChat">{{ t('qa.guess_u_ask') }}</div>
|
||||
<div v-else>{{ t('qa.continue_to_ask') }}</div>
|
||||
<div v-for="(question, index) in computedQuestions" :key="index">
|
||||
<span class="question" @click="clickQuestion(question)">
|
||||
<div v-else class="continue-ask">{{ t('qa.continue_to_ask') }}</div>
|
||||
<div class="question-grid">
|
||||
<div
|
||||
v-for="(question, index) in computedQuestions"
|
||||
:key="index"
|
||||
class="question"
|
||||
@click="clickQuestion(question)"
|
||||
>
|
||||
{{ question }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -49,19 +54,34 @@ function clickQuestion(question: string): void {
|
||||
<style scoped lang="less">
|
||||
.recommend-questions {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
.continue-ask {
|
||||
color: rgba(100, 106, 115, 1);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.question-grid {
|
||||
display: grid;
|
||||
grid-gap: 12px;
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
}
|
||||
|
||||
.question {
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
background: lightgray;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
line-height: 20px;
|
||||
background: rgba(245, 246, 247, 1);
|
||||
min-height: 46px;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
line-height: 22px;
|
||||
&:hover {
|
||||
background: grey;
|
||||
background: rgba(31, 35, 41, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
<script setup lang="ts">
|
||||
import BaseAnswer from './BaseAnswer.vue'
|
||||
import { chatApi, ChatInfo, type ChatMessage, ChatRecord } from '@/api/chat.ts'
|
||||
import { computed, nextTick } from 'vue'
|
||||
import MdComponent from '@/views/chat/component/MdComponent.vue'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
chatList: Array<ChatInfo>
|
||||
currentChatId?: number
|
||||
currentChat: ChatInfo
|
||||
message?: ChatMessage
|
||||
loading?: boolean
|
||||
}>(),
|
||||
{
|
||||
chatList: () => [],
|
||||
currentChatId: undefined,
|
||||
currentChat: () => new ChatInfo(),
|
||||
message: undefined,
|
||||
loading: false,
|
||||
}
|
||||
)
|
||||
|
||||
const emits = defineEmits([
|
||||
'finish',
|
||||
'error',
|
||||
'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 sendMessage = async () => {
|
||||
_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 || currentRecord.analysis_record_id === undefined) {
|
||||
error = true
|
||||
}
|
||||
if (error) return
|
||||
|
||||
try {
|
||||
const response = await chatApi.analysis(currentRecord.analysis_record_id)
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
let analysis_answer = ''
|
||||
let analysis_answer_thinking = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
_loading.value = false
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(_list)
|
||||
|
||||
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,
|
||||
})
|
||||
_loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'info':
|
||||
console.log(data.msg)
|
||||
break
|
||||
case 'error':
|
||||
currentRecord.error = data.content
|
||||
emits('error')
|
||||
break
|
||||
case 'analysis-result':
|
||||
analysis_answer += data.content
|
||||
analysis_answer_thinking += data.reasoning_content
|
||||
_currentChat.value.records[index.value].analysis = analysis_answer
|
||||
_currentChat.value.records[index.value].analysis_thinking = analysis_answer_thinking
|
||||
break
|
||||
case 'analysis_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
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ sendMessage, index: index.value, chatList: _chatList })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseAnswer
|
||||
v-if="message"
|
||||
:message="message"
|
||||
:reasoning-name="['analysis_thinking']"
|
||||
:loading="_loading"
|
||||
>
|
||||
<MdComponent :message="message.record?.analysis" style="margin-top: 12px" />
|
||||
<slot name="footer"></slot>
|
||||
</BaseAnswer>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@@ -0,0 +1,159 @@
|
||||
<script setup lang="ts">
|
||||
import { type ChatMessage } from '@/api/chat.ts'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import MdComponent from '@/views/chat/component/MdComponent.vue'
|
||||
import icon_up_outlined from '@/assets/svg/icon_up_outlined.svg'
|
||||
import icon_down_outlined from '@/assets/svg/icon_down_outlined.svg'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
message: ChatMessage
|
||||
loading?: boolean
|
||||
reasoningName:
|
||||
| 'sql_answer'
|
||||
| 'chart_answer'
|
||||
| 'analysis_thinking'
|
||||
| 'predict'
|
||||
| Array<'sql_answer' | 'chart_answer' | 'analysis_thinking' | 'predict'>
|
||||
}>(),
|
||||
{
|
||||
loading: false,
|
||||
}
|
||||
)
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const show = ref<boolean>(false)
|
||||
|
||||
const reasoningContent = computed<Array<string>>(() => {
|
||||
const names: Array<'sql_answer' | 'chart_answer' | 'analysis_thinking' | 'predict'> = []
|
||||
if (typeof props.reasoningName === 'string') {
|
||||
names.push(props.reasoningName)
|
||||
} else {
|
||||
props.reasoningName.forEach((item) => {
|
||||
names.push(item)
|
||||
})
|
||||
}
|
||||
const result: Array<string> = []
|
||||
names.forEach((item) => {
|
||||
if (props.message?.record) {
|
||||
if (props.message?.record[item]) {
|
||||
result.push(props.message?.record[item] ?? '')
|
||||
}
|
||||
}
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
const hasReasoning = computed<boolean>(() => {
|
||||
if (reasoningContent.value.length > 0) {
|
||||
for (let i = 0; i < reasoningContent.value.length; i++) {
|
||||
if (reasoningContent.value[i] && reasoningContent.value[i].trim() !== '') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
function clickShow() {
|
||||
show.value = !show.value
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.loading) {
|
||||
show.value = true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="base-answer-block">
|
||||
<el-button v-if="loading || hasReasoning" class="thinking-btn" @click="clickShow">
|
||||
<div class="thinking-btn-inner">
|
||||
<span v-if="loading">{{ t('qa.thinking') }}</span>
|
||||
<span v-else>{{ t('qa.thinking_step') }}</span>
|
||||
<span class="btn-icon">
|
||||
<el-icon v-if="show">
|
||||
<icon_up_outlined />
|
||||
</el-icon>
|
||||
<el-icon v-else>
|
||||
<icon_down_outlined />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</el-button>
|
||||
<div v-if="hasReasoning && show" class="reasoning-content">
|
||||
<div v-for="(reason, _index) in reasoningContent" :key="_index" class="reasoning">
|
||||
<MdComponent :message="reason" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="answer-container">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.base-answer-block {
|
||||
.thinking-btn {
|
||||
height: 32px;
|
||||
padding: 5px 12px;
|
||||
|
||||
--ed-button-text-color: rgba(31, 35, 41, 1);
|
||||
--ed-button-hover-text-color: var(--ed-button-text-color);
|
||||
--ed-button-active-text-color: var(--ed-button-text-color);
|
||||
--ed-button-bg-color: rgba(255, 255, 255, 1);
|
||||
--ed-button-hover-bg-color: rgba(245, 246, 247, 1);
|
||||
--ed-button-active-bg-color: rgba(239, 240, 241, 1);
|
||||
--ed-button-border-color: rgba(217, 220, 223, 1);
|
||||
--ed-button-hover-border-color: var(--ed-button-border-color);
|
||||
--ed-button-active-border-color: var(--ed-button-border-color);
|
||||
|
||||
--ed-button-font-weight: 400;
|
||||
|
||||
.thinking-btn-inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
line-height: 22px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
||||
.btn-icon {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.reasoning-content {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 9px;
|
||||
border-left: 1px solid rgba(31, 35, 41, 0.15);
|
||||
gap: 8px;
|
||||
|
||||
.reasoning {
|
||||
width: 100%;
|
||||
line-height: 22px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
color: rgba(143, 149, 158, 1);
|
||||
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid rgba(31, 35, 41, 0.15);
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: unset;
|
||||
border-bottom: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.answer-container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,230 @@
|
||||
<script setup lang="ts">
|
||||
import BaseAnswer from './BaseAnswer.vue'
|
||||
import { Chat, chatApi, ChatInfo, type ChatMessage, ChatRecord, questionApi } from '@/api/chat.ts'
|
||||
import { computed, nextTick } from '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',
|
||||
'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 sendMessage = async () => {
|
||||
_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 response = await questionApi.add({
|
||||
question: currentRecord.question,
|
||||
chat_id: _currentChatId.value,
|
||||
})
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
let sql_answer = ''
|
||||
let chart_answer = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
_loading.value = false
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(_list)
|
||||
|
||||
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,
|
||||
})
|
||||
_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.log(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
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ sendMessage, index: index.value })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseAnswer v-if="message" :message="message" :reasoning-name="reasoningName" :loading="_loading">
|
||||
<slot></slot>
|
||||
<slot name="footer"></slot>
|
||||
</BaseAnswer>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@@ -0,0 +1,200 @@
|
||||
<script setup lang="ts">
|
||||
import BaseAnswer from './BaseAnswer.vue'
|
||||
import { chatApi, ChatInfo, type ChatMessage, ChatRecord } from '@/api/chat.ts'
|
||||
import { computed, nextTick } from 'vue'
|
||||
import MdComponent from '@/views/chat/component/MdComponent.vue'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
chatList: Array<ChatInfo>
|
||||
currentChatId?: number
|
||||
currentChat: ChatInfo
|
||||
message?: ChatMessage
|
||||
loading?: boolean
|
||||
}>(),
|
||||
{
|
||||
chatList: () => [],
|
||||
currentChatId: undefined,
|
||||
currentChat: () => new ChatInfo(),
|
||||
message: undefined,
|
||||
loading: false,
|
||||
}
|
||||
)
|
||||
|
||||
const emits = defineEmits([
|
||||
'finish',
|
||||
'error',
|
||||
'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 sendMessage = async () => {
|
||||
_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 || currentRecord.predict_record_id === undefined) {
|
||||
error = true
|
||||
}
|
||||
if (error) return
|
||||
|
||||
try {
|
||||
const response = await chatApi.predict(currentRecord.predict_record_id)
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
let predict_answer = ''
|
||||
let predict_content = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
_loading.value = false
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(_list)
|
||||
|
||||
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 'info':
|
||||
console.log(data.msg)
|
||||
break
|
||||
case 'error':
|
||||
currentRecord.error = data.content
|
||||
emits('error')
|
||||
break
|
||||
case 'predict-result':
|
||||
predict_answer += data.reasoning_content
|
||||
predict_content += data.content
|
||||
_currentChat.value.records[index.value].predict = predict_answer
|
||||
_currentChat.value.records[index.value].predict_content = predict_content
|
||||
break
|
||||
case 'predict-failed':
|
||||
emits('error')
|
||||
break
|
||||
case 'predict-success':
|
||||
//currentChat.value.records[_index].predict_data = data.content
|
||||
emits('finish', currentRecord.id)
|
||||
break
|
||||
case 'predict_finish':
|
||||
_loading.value = false
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ sendMessage, index: index.value, chatList: _chatList })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseAnswer v-if="message" :message="message" :reasoning-name="['predict']" :loading="_loading">
|
||||
<MdComponent :message="message.record?.predict_content" style="margin-top: 12px" />
|
||||
<slot name="footer"></slot>
|
||||
</BaseAnswer>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
+160
-366
@@ -99,70 +99,77 @@
|
||||
/>
|
||||
<UserChat v-if="message.role === 'user'" :message="message" />
|
||||
<template v-if="message.role === 'assistant' && !message.first_chat">
|
||||
<ChatAnswer
|
||||
<ChartAnswer
|
||||
v-if="
|
||||
message?.record?.analysis_record_id == undefined &&
|
||||
message?.record?.predict_record_id == undefined
|
||||
(message?.record?.analysis_record_id === undefined ||
|
||||
message?.record?.analysis_record_id === null) &&
|
||||
(message?.record?.predict_record_id === undefined ||
|
||||
message?.record?.predict_record_id === null)
|
||||
"
|
||||
ref="chartAnswerRef"
|
||||
:chat-list="chatList"
|
||||
:current-chat="currentChat"
|
||||
:current-chat-id="currentChatId"
|
||||
:loading="isTyping"
|
||||
:message="message"
|
||||
:reasoning-name="['sql_answer', 'chart_answer']"
|
||||
@finish="onChartAnswerFinish"
|
||||
@error="onChartAnswerError"
|
||||
>
|
||||
<template v-if="message.record?.chart">
|
||||
<div style="padding: 0 22px; display: flex; justify-content: flex-end">
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
:disabled="isTyping || isPredictTyping || isAnalysisTyping"
|
||||
@click="clickAnalysis(message.record?.id)"
|
||||
>
|
||||
{{ t('chat.data_analysis') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
:disabled="isTyping || isPredictTyping || isAnalysisTyping"
|
||||
@click="clickPredict(message.record?.id)"
|
||||
>
|
||||
{{ t('chat.data_predict') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div style="height: 100px; width: 100%; background: grey">todo</div>
|
||||
<div style="padding: 0 22px; display: flex; justify-content: flex-end">
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
:disabled="isTyping"
|
||||
@click="clickAnalysis(message.record?.id)"
|
||||
>
|
||||
{{ t('chat.data_analysis') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
text
|
||||
type="primary"
|
||||
:disabled="isTyping"
|
||||
@click="clickPredict(message.record?.id)"
|
||||
>
|
||||
{{ t('chat.data_predict') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<template #footer>
|
||||
<RecommendQuestion
|
||||
:questions="message.recommended_question"
|
||||
:first-chat="message.first_chat"
|
||||
@click-question="quickAsk"
|
||||
/>
|
||||
</template>
|
||||
</ChatAnswer>
|
||||
|
||||
<div
|
||||
</ChartAnswer>
|
||||
<AnalysisAnswer
|
||||
v-if="
|
||||
message?.record?.analysis_record_id != undefined ||
|
||||
message?.record?.predict_record_id != undefined
|
||||
message?.record?.analysis_record_id !== undefined &&
|
||||
message?.record?.analysis_record_id !== null
|
||||
"
|
||||
class="analysis-container"
|
||||
>
|
||||
<template v-if="message.record?.analysis || isAnalysisTyping">
|
||||
<MdComponent :message="message.record?.analysis_thinking" />
|
||||
<MdComponent :message="message.record?.analysis" />
|
||||
</template>
|
||||
|
||||
<el-divider
|
||||
v-if="
|
||||
(message.record?.analysis || isAnalysisTyping) &&
|
||||
(message.record?.predict || isPredictTyping)
|
||||
"
|
||||
/>
|
||||
<template v-if="message.record?.predict || isPredictTyping">
|
||||
<MdComponent :message="message.record?.predict" />
|
||||
<MdComponent :message="message.record?.predict_content" />
|
||||
<!-- <PredictChartBlock-->
|
||||
<!-- :id="message.record?.id + '-predict'"-->
|
||||
<!-- :data="message.record?.predict_data ?? '[]'"-->
|
||||
<!-- :message="message"-->
|
||||
<!-- :chart-type="data.chartType"-->
|
||||
<!-- />-->
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="message.role === 'assistant'" #footer>
|
||||
<RecommendQuestion
|
||||
:questions="message.recommended_question"
|
||||
:first-chat="message.first_chat"
|
||||
@click-question="quickAsk"
|
||||
ref="analysisAnswerRef"
|
||||
:chat-list="chatList"
|
||||
:current-chat="currentChat"
|
||||
:current-chat-id="currentChatId"
|
||||
:loading="isTyping"
|
||||
:message="message"
|
||||
@finish="onAnalysisAnswerFinish"
|
||||
@error="onAnalysisAnswerError"
|
||||
/>
|
||||
<PredictAnswer
|
||||
v-if="
|
||||
message?.record?.predict_record_id !== undefined &&
|
||||
message?.record?.predict_record_id !== null
|
||||
"
|
||||
ref="predictAnswerRef"
|
||||
:chat-list="chatList"
|
||||
:current-chat="currentChat"
|
||||
:current-chat-id="currentChatId"
|
||||
:loading="isTyping"
|
||||
:message="message"
|
||||
@finish="onPredictAnswerFinish"
|
||||
@error="onPredictAnswerError"
|
||||
/>
|
||||
</template>
|
||||
</ChatRow>
|
||||
@@ -182,7 +189,7 @@
|
||||
<div class="input-wrapper">
|
||||
<el-input
|
||||
v-model="inputMessage"
|
||||
:disabled="isTyping || isPredictTyping || isAnalysisTyping"
|
||||
:disabled="isTyping"
|
||||
class="input-area"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
@@ -195,7 +202,7 @@
|
||||
link
|
||||
type="primary"
|
||||
class="input-icon"
|
||||
:disabled="isTyping || isPredictTyping || isAnalysisTyping"
|
||||
:disabled="isTyping"
|
||||
@click="sendMessage"
|
||||
>
|
||||
<el-icon size="20">
|
||||
@@ -213,11 +220,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
||||
import { Position } from '@element-plus/icons-vue'
|
||||
import { Chat, chatApi, ChatInfo, type ChatMessage, ChatRecord, questionApi } from '@/api/chat'
|
||||
import { Chat, chatApi, ChatInfo, type ChatMessage, ChatRecord } from '@/api/chat'
|
||||
import ChatRow from './ChatRow.vue'
|
||||
import ChatAnswer from './ChatAnswer.vue'
|
||||
import ChartAnswer from './answer/ChartAnswer.vue'
|
||||
import AnalysisAnswer from './answer/AnalysisAnswer.vue'
|
||||
import PredictAnswer from './answer/PredictAnswer.vue'
|
||||
import UserChat from './chat-block/UserChat.vue'
|
||||
import MdComponent from './component/MdComponent.vue'
|
||||
import RecommendQuestion from './RecommendQuestion.vue'
|
||||
import ChatListContainer from './ChatListContainer.vue'
|
||||
import ChatCreator from '@/views/chat/ChatCreator.vue'
|
||||
@@ -255,8 +263,6 @@ const chatList = ref<Array<ChatInfo>>([])
|
||||
const currentChatId = ref<number | undefined>()
|
||||
const currentChat = ref<ChatInfo>(new ChatInfo())
|
||||
const isTyping = ref<boolean>(false)
|
||||
const isAnalysisTyping = ref<boolean>(false)
|
||||
const isPredictTyping = ref<boolean>(false)
|
||||
|
||||
const computedMessages = computed<Array<ChatMessage>>(() => {
|
||||
const messages: Array<ChatMessage> = []
|
||||
@@ -271,6 +277,7 @@ const computedMessages = computed<Array<ChatMessage>>(() => {
|
||||
create_time: record.create_time,
|
||||
record: record,
|
||||
content: record.question,
|
||||
index: i,
|
||||
})
|
||||
}
|
||||
messages.push({
|
||||
@@ -280,9 +287,11 @@ const computedMessages = computed<Array<ChatMessage>>(() => {
|
||||
isTyping: i === currentChat.value.records.length - 1 && isTyping.value,
|
||||
first_chat: record.first_chat,
|
||||
recommended_question: record.recommended_question,
|
||||
index: i,
|
||||
})
|
||||
}
|
||||
|
||||
console.log(messages)
|
||||
return messages
|
||||
})
|
||||
|
||||
@@ -378,7 +387,6 @@ async function getRecommendQuestions(record_id?: number) {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
isTyping.value = false
|
||||
break
|
||||
}
|
||||
|
||||
@@ -415,7 +423,6 @@ async function getRecommendQuestions(record_id?: number) {
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
isTyping.value = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -451,6 +458,19 @@ function quickAsk(question: string) {
|
||||
})
|
||||
}
|
||||
|
||||
const chartAnswerRef = ref()
|
||||
|
||||
async function onChartAnswerFinish(id: number) {
|
||||
loading.value = false
|
||||
isTyping.value = false
|
||||
await getRecommendQuestions(id)
|
||||
}
|
||||
|
||||
function onChartAnswerError() {
|
||||
loading.value = false
|
||||
isTyping.value = false
|
||||
}
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!inputMessage.value.trim()) return
|
||||
|
||||
@@ -469,138 +489,21 @@ const sendMessage = async () => {
|
||||
currentChat.value.records.push(currentRecord)
|
||||
inputMessage.value = ''
|
||||
|
||||
let error: boolean = false
|
||||
if (currentChatId.value === undefined) {
|
||||
error = true
|
||||
}
|
||||
if (error) return
|
||||
|
||||
try {
|
||||
const response = await questionApi.add({
|
||||
question: currentRecord.question,
|
||||
chat_id: currentChatId.value,
|
||||
})
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
let sql_answer = ''
|
||||
let chart_answer = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
isTyping.value = false
|
||||
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()
|
||||
nextTick(async () => {
|
||||
const index = currentChat.value.records.length - 1
|
||||
if (chartAnswerRef.value) {
|
||||
if (chartAnswerRef.value instanceof Array) {
|
||||
for (let i = 0; i < chartAnswerRef.value.length; i++) {
|
||||
const _index = chartAnswerRef.value[i].index
|
||||
if (index === _index) {
|
||||
await chartAnswerRef.value[i].sendMessage()
|
||||
break
|
||||
}
|
||||
if (!line.trim().endsWith('}')) {
|
||||
line = line.trim() + '}'
|
||||
}
|
||||
_list.push(line)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(_list)
|
||||
|
||||
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,
|
||||
})
|
||||
isTyping.value = false
|
||||
return
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'id':
|
||||
currentRecord.id = data.id
|
||||
currentChat.value.records[currentChat.value.records.length - 1].id = data.id
|
||||
break
|
||||
case 'info':
|
||||
console.log(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
|
||||
isTyping.value = false
|
||||
break
|
||||
case 'sql-result':
|
||||
sql_answer += data.reasoning_content
|
||||
currentChat.value.records[currentChat.value.records.length - 1].sql_answer = sql_answer
|
||||
break
|
||||
case 'sql':
|
||||
currentChat.value.records[currentChat.value.records.length - 1].sql = data.content
|
||||
break
|
||||
case 'sql-data':
|
||||
//currentChat.value.records[currentChat.value.records.length - 1].data = data.content
|
||||
getChatData(currentChat.value.records[currentChat.value.records.length - 1].id)
|
||||
break
|
||||
case 'chart-result':
|
||||
chart_answer += data.reasoning_content
|
||||
currentChat.value.records[currentChat.value.records.length - 1].chart_answer =
|
||||
chart_answer
|
||||
break
|
||||
case 'chart':
|
||||
currentChat.value.records[currentChat.value.records.length - 1].chart = data.content
|
||||
break
|
||||
case 'finish':
|
||||
isTyping.value = false
|
||||
await getRecommendQuestions(currentRecord.id)
|
||||
break
|
||||
}
|
||||
await nextTick()
|
||||
} else {
|
||||
await chartAnswerRef.value.sendMessage()
|
||||
}
|
||||
}
|
||||
} 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)
|
||||
isTyping.value = false
|
||||
} 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
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -614,12 +517,28 @@ function getChatPredictData(recordId?: number) {
|
||||
})
|
||||
}
|
||||
|
||||
const analysisAnswerRef = ref()
|
||||
|
||||
async function onAnalysisAnswerFinish(id: number) {
|
||||
loading.value = false
|
||||
isTyping.value = false
|
||||
console.log(id)
|
||||
//await getRecommendQuestions(id)
|
||||
}
|
||||
function onAnalysisAnswerError() {
|
||||
loading.value = false
|
||||
isTyping.value = false
|
||||
}
|
||||
|
||||
async function clickAnalysis(id?: number) {
|
||||
const baseRecord = find(currentChat.value.records, (value) => id === value.id)
|
||||
if (baseRecord == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
isTyping.value = true
|
||||
|
||||
const currentRecord = new ChatRecord()
|
||||
currentRecord.create_time = new Date()
|
||||
currentRecord.chat_id = baseRecord.chat_id
|
||||
@@ -627,96 +546,41 @@ async function clickAnalysis(id?: number) {
|
||||
currentRecord.chart = baseRecord.chart
|
||||
currentRecord.data = baseRecord.data
|
||||
currentRecord.analysis_record_id = id
|
||||
currentRecord.analysis = ''
|
||||
|
||||
currentChat.value.records.push(currentRecord)
|
||||
|
||||
let _index = currentChat.value.records.length - 1
|
||||
|
||||
isAnalysisTyping.value = true
|
||||
currentChat.value.records[_index].analysis = ''
|
||||
|
||||
try {
|
||||
const response = await chatApi.analysis(id)
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
let analysis_answer = ''
|
||||
let analysis_answer_thinking = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
isAnalysisTyping.value = false
|
||||
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()
|
||||
nextTick(async () => {
|
||||
const index = currentChat.value.records.length - 1
|
||||
if (analysisAnswerRef.value) {
|
||||
if (analysisAnswerRef.value instanceof Array) {
|
||||
for (let i = 0; i < analysisAnswerRef.value.length; i++) {
|
||||
const _index = analysisAnswerRef.value[i].index
|
||||
if (index === _index) {
|
||||
await analysisAnswerRef.value[i].sendMessage()
|
||||
break
|
||||
}
|
||||
if (!line.trim().endsWith('}')) {
|
||||
line = line.trim() + '}'
|
||||
}
|
||||
_list.push(line)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(_list)
|
||||
|
||||
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,
|
||||
})
|
||||
isAnalysisTyping.value = false
|
||||
return
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'info':
|
||||
console.log(data.msg)
|
||||
break
|
||||
case 'error':
|
||||
throw Error(data.content)
|
||||
case 'analysis-result':
|
||||
analysis_answer += data.content
|
||||
analysis_answer_thinking += data.reasoning_content
|
||||
currentChat.value.records[_index].analysis = analysis_answer
|
||||
currentChat.value.records[_index].analysis_thinking = analysis_answer_thinking
|
||||
break
|
||||
case 'analysis_finish':
|
||||
isAnalysisTyping.value = false
|
||||
break
|
||||
}
|
||||
await nextTick()
|
||||
} else {
|
||||
await analysisAnswerRef.value.sendMessage()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error)
|
||||
ElMessage({
|
||||
message: error + '',
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
isAnalysisTyping.value = false
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const predictAnswerRef = ref()
|
||||
|
||||
async function onPredictAnswerFinish(id: number) {
|
||||
loading.value = false
|
||||
isTyping.value = false
|
||||
getChatPredictData(id)
|
||||
//await getRecommendQuestions(id)
|
||||
}
|
||||
function onPredictAnswerError() {
|
||||
loading.value = false
|
||||
isTyping.value = false
|
||||
}
|
||||
|
||||
async function clickPredict(id?: number) {
|
||||
@@ -725,6 +589,9 @@ async function clickPredict(id?: number) {
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
isTyping.value = true
|
||||
|
||||
const currentRecord = new ChatRecord()
|
||||
currentRecord.create_time = new Date()
|
||||
currentRecord.chat_id = baseRecord.chat_id
|
||||
@@ -732,102 +599,29 @@ async function clickPredict(id?: number) {
|
||||
currentRecord.chart = baseRecord.chart
|
||||
currentRecord.data = baseRecord.data
|
||||
currentRecord.predict_record_id = id
|
||||
currentRecord.predict = ''
|
||||
currentRecord.predict_data = ''
|
||||
|
||||
currentChat.value.records.push(currentRecord)
|
||||
|
||||
let _index = currentChat.value.records.length - 1
|
||||
|
||||
isPredictTyping.value = true
|
||||
currentChat.value.records[_index].predict = ''
|
||||
currentChat.value.records[_index].predict_data = ''
|
||||
|
||||
try {
|
||||
const response = await chatApi.predict(id)
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
let predict_answer = ''
|
||||
let predict_content = ''
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
isPredictTyping.value = false
|
||||
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()
|
||||
nextTick(async () => {
|
||||
const index = currentChat.value.records.length - 1
|
||||
if (predictAnswerRef.value) {
|
||||
if (predictAnswerRef.value instanceof Array) {
|
||||
for (let i = 0; i < predictAnswerRef.value.length; i++) {
|
||||
const _index = predictAnswerRef.value[i].index
|
||||
if (index === _index) {
|
||||
await predictAnswerRef.value[i].sendMessage()
|
||||
break
|
||||
}
|
||||
if (!line.trim().endsWith('}')) {
|
||||
line = line.trim() + '}'
|
||||
}
|
||||
_list.push(line)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(_list)
|
||||
|
||||
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 'info':
|
||||
console.log(data.msg)
|
||||
break
|
||||
case 'error':
|
||||
throw Error(data.content)
|
||||
case 'predict-result':
|
||||
predict_answer += data.reasoning_content
|
||||
predict_content += data.content
|
||||
currentChat.value.records[_index].predict = predict_answer
|
||||
currentChat.value.records[_index].predict_content = predict_content
|
||||
break
|
||||
case 'predict-failed':
|
||||
break
|
||||
case 'predict-success':
|
||||
//currentChat.value.records[_index].predict_data = data.content
|
||||
getChatPredictData(currentChat.value.records[_index].id)
|
||||
break
|
||||
case 'predict_finish':
|
||||
isPredictTyping.value = false
|
||||
break
|
||||
}
|
||||
await nextTick()
|
||||
} else {
|
||||
await predictAnswerRef.value.sendMessage()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error)
|
||||
ElMessage({
|
||||
message: error + '',
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
isPredictTyping.value = false
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const handleCtrlEnter = (e: KeyboardEvent) => {
|
||||
|
||||
Reference in New Issue
Block a user