Add File
This commit is contained in:
211
frontend/src/views/chat/RecommendQuestion.vue
Normal file
211
frontend/src/views/chat/RecommendQuestion.vue
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, nextTick, onBeforeUnmount, 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
|
||||||
|
disabled?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
recordId: undefined,
|
||||||
|
currentChat: () => new ChatInfo(),
|
||||||
|
questions: '[]',
|
||||||
|
firstChat: false,
|
||||||
|
disabled: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const emits = defineEmits(['clickQuestion', 'update:currentChat', 'stop'])
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const _currentChat = computed({
|
||||||
|
get() {
|
||||||
|
return props.currentChat
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
emits('update:currentChat', v)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const computedQuestions = computed<string>(() => {
|
||||||
|
if (
|
||||||
|
props.questions &&
|
||||||
|
props.questions.length > 0 &&
|
||||||
|
startsWith(props.questions.trim(), '[') &&
|
||||||
|
endsWith(props.questions.trim(), ']')
|
||||||
|
) {
|
||||||
|
return JSON.parse(props.questions)
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
function clickQuestion(question: string): void {
|
||||||
|
if (!props.disabled) {
|
||||||
|
emits('clickQuestion', question)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopFlag = ref(false)
|
||||||
|
|
||||||
|
async function getRecommendQuestions() {
|
||||||
|
stopFlag.value = false
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const controller: AbortController = new AbortController()
|
||||||
|
const response = await chatApi.recommendQuestions(props.recordId, controller)
|
||||||
|
const reader = response.body.getReader()
|
||||||
|
const decoder = new TextDecoder('utf-8')
|
||||||
|
|
||||||
|
let tempResult = ''
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (stopFlag.value) {
|
||||||
|
controller.abort()
|
||||||
|
loading.value = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const { done, value } = await reader.read()
|
||||||
|
if (done) {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
stopFlag.value = true
|
||||||
|
loading.value = false
|
||||||
|
emits('stop')
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stop()
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({ getRecommendQuestions, id: () => props.recordId, stop })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="computedQuestions.length > 0 || loading" class="recommend-questions">
|
||||||
|
<div v-if="firstChat" style="margin-bottom: 8px">{{ t('qa.guess_u_ask') }}</div>
|
||||||
|
<div v-else class="continue-ask">{{ t('qa.continue_to_ask') }}</div>
|
||||||
|
<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"
|
||||||
|
class="question"
|
||||||
|
:class="{ disabled: disabled }"
|
||||||
|
@click="clickQuestion(question)"
|
||||||
|
>
|
||||||
|
{{ question }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.recommend-questions {
|
||||||
|
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, calc(50% - 6px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.question {
|
||||||
|
font-weight: 400;
|
||||||
|
cursor: pointer;
|
||||||
|
background: rgba(245, 246, 247, 1);
|
||||||
|
min-height: 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
line-height: 22px;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(31, 35, 41, 0.1);
|
||||||
|
}
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background: rgba(245, 246, 247, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user