feat: 对话暂停

This commit is contained in:
shaohuzhang1 2023-11-30 17:12:39 +08:00
parent 463cdd2ae1
commit 4bb4b7c2dd
3 changed files with 180 additions and 23 deletions

View File

@ -1,3 +1,4 @@
import { type Dict } from '@/api/type/common'
import { type Ref } from 'vue' import { type Ref } from 'vue'
interface ApplicationFormType { interface ApplicationFormType {
name?: string name?: string
@ -13,20 +14,38 @@ interface chatType {
problem_text: string problem_text: string
answer_text: string answer_text: string
buffer: Array<String> buffer: Array<String>
/**
*
*/
write_ed?: boolean
/**
*
*/
is_stop?: boolean
} }
export class ChatManage { export class ChatRecordManage {
id?: NodeJS.Timer id?: NodeJS.Timer
ms: number ms: number
chat: chatType chat: chatType
is_close?: boolean is_close?: boolean
write_ed?: boolean
is_stop?: boolean
loading?: Ref<boolean> loading?: Ref<boolean>
constructor(chat: chatType, ms?: number, loading?: Ref<boolean>) { constructor(chat: chatType, ms?: number, loading?: Ref<boolean>) {
this.ms = ms ? ms : 10 this.ms = ms ? ms : 10
this.chat = chat this.chat = chat
this.loading = loading this.loading = loading
this.is_stop = false
this.is_close = false
this.write_ed = false
} }
write() { write() {
this.chat.is_stop = false
this.is_stop = false
if (this.loading) {
this.loading.value = true
}
this.id = setInterval(() => { this.id = setInterval(() => {
const s = this.chat.buffer.shift() const s = this.chat.buffer.shift()
if (s !== undefined) { if (s !== undefined) {
@ -34,6 +53,8 @@ export class ChatManage {
} else { } else {
if (this.is_close) { if (this.is_close) {
clearInterval(this.id) clearInterval(this.id)
this.chat.write_ed = true
this.write_ed = true
if (this.loading) { if (this.loading) {
this.loading.value = false this.loading.value = false
} }
@ -41,6 +62,14 @@ export class ChatManage {
} }
}, this.ms) }, this.ms)
} }
stop() {
clearInterval(this.id)
this.is_stop = true
this.chat.is_stop = true
if (this.loading) {
this.loading.value = false
}
}
close() { close() {
this.is_close = true this.is_close = true
} }
@ -50,4 +79,78 @@ export class ChatManage {
} }
} }
} }
export class ChatManagement {
static chatMessageContainer: Dict<ChatRecordManage> = {}
static addChatRecord(chat: chatType, ms: number, loading?: Ref<boolean>) {
this.chatMessageContainer[chat.id] = new ChatRecordManage(chat, ms, loading)
}
static append(chatRecordId: string, content: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.append(content)
}
}
/**
*
* @param chatRecordId id
*/
static write(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.write()
}
}
/**
*
* @param chatRecordId id
* @returns boolean
*/
static close(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.close()
}
}
/**
*
* @param chatRecordId id
* @returns boolean
*/
static stop(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.stop()
}
}
/**
*
* @param chatRecordId id
* @returns boolean
*/
static isClose(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
return chatRecord ? chatRecord.is_close && chatRecord.write_ed : false
}
/**
*
* @param chatRecordId id
* @returns
*/
static isStop(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
return chatRecord ? chatRecord.is_stop : false
}
/**
* close掉的和stop的数据
*/
static clean() {
for (const key in Object.keys(this.chatMessageContainer)) {
if (this.chatMessageContainer[key].is_close) {
delete this.chatMessageContainer[key]
}
}
}
}
export type { ApplicationFormType, chatType } export type { ApplicationFormType, chatType }

View File

@ -59,9 +59,27 @@
<el-card shadow="always" class="dialog-card"> 回答中... </el-card> <el-card shadow="always" class="dialog-card"> 回答中... </el-card>
</div> </div>
<el-card v-else shadow="always" class="dialog-card"> <el-card v-else shadow="always" class="dialog-card">
<MarkdownRenderer :source="item.answer_text"></MarkdownRenderer> <MarkdownRenderer
:source="item.answer_text"
:inner_suffix="false"
></MarkdownRenderer>
</el-card> </el-card>
<el-button type="primary" link class="mt-8">停止回答</el-button> <el-button
type="primary"
v-if="item.is_stop && !item.write_ed"
@click="startChat(item)"
link
class="mt-8"
>继续</el-button
>
<el-button
type="primary"
v-else-if="!item.write_ed"
@click="stopChat(item)"
link
class="mt-8"
>停止回答</el-button
>
</div> </div>
</div> </div>
</template> </template>
@ -90,7 +108,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, nextTick, onUpdated, computed } from 'vue' import { ref, nextTick, onUpdated, computed } from 'vue'
import applicationApi from '@/api/application' import applicationApi from '@/api/application'
import { ChatManage, type chatType } from '@/api/type/application' import { ChatManagement, type chatType } from '@/api/type/application'
import { randomId } from '@/utils/utils' import { randomId } from '@/utils/utils'
const props = defineProps({ const props = defineProps({
data: { data: {
@ -126,7 +144,12 @@ function sendChatHandle(event: any) {
inputValue.value += '\n' inputValue.value += '\n'
} }
} }
const stopChat = (chat: chatType) => {
ChatManagement.stop(chat.id)
}
const startChat = (chat: chatType) => {
ChatManagement.write(chat.id)
}
/** /**
* 对话 * 对话
*/ */
@ -147,41 +170,41 @@ function getChartOpenId() {
loading.value = false loading.value = false
}) })
} }
function chatMessage() { function chatMessage() {
loading.value = true loading.value = true
if (!chartOpenId.value) { if (!chartOpenId.value) {
getChartOpenId() getChartOpenId()
} else { } else {
const problem_text = inputValue.value const problem_text = inputValue.value
const id = randomId()
chatList.value.push({
id: id,
problem_text: problem_text,
answer_text: '',
buffer: [],
write_ed: false,
is_stop: false
})
applicationApi.postChatMessage(chartOpenId.value, problem_text).then(async (response) => { applicationApi.postChatMessage(chartOpenId.value, problem_text).then(async (response) => {
const id = randomId()
chatList.value.push({
id: id,
problem_text: problem_text,
answer_text: '',
buffer: []
})
inputValue.value = '' inputValue.value = ''
const row = chatList.value.find((item) => item.id === id) const row = chatList.value.find((item) => item.id === id)
if (row) { if (row) {
const chatMange = new ChatManage(row, 50, loading) ChatManagement.addChatRecord(row, 50, loading)
chatMange.write() ChatManagement.write(id)
const reader = response.body.getReader() const reader = response.body.getReader()
while (true) { while (true) {
const { done, value } = await reader.read() const { done, value } = await reader.read()
if (done) { if (done) {
chatMange.close() ChatManagement.close(id)
break break
} }
try { try {
const decoder = new TextDecoder('utf-8') const decoder = new TextDecoder('utf-8')
const str = decoder.decode(value, { stream: true }) const str = decoder.decode(value, { stream: true })
if (str && str.startsWith('data:')) { if (str && str.startsWith('data:')) {
// console.log(JSON?.parse(str.replace('data:', '')))
const content = JSON?.parse(str.replace('data:', ''))?.content const content = JSON?.parse(str.replace('data:', ''))?.content
if (content) { if (content) {
chatMange.append(content) ChatManagement.append(id, content)
} }
} }
} catch (e) {} } catch (e) {}
@ -301,6 +324,11 @@ onUpdated(() => {
} }
.dialog-card { .dialog-card {
border: none; border: none;
display: flex;
:deep(.el-card__body) {
display: flex;
align-items: center;
}
} }
} }
</style> </style>

View File

@ -1,8 +1,9 @@
<template> <template>
<div v-html="markdownIt.render(source)" /> <div v-html="inner" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'
import MarkdownIt from 'markdown-it' import MarkdownIt from 'markdown-it'
import MarkdownItAbbr from 'markdown-it-abbr' import MarkdownItAbbr from 'markdown-it-abbr'
import MarkdownItAnchor from 'markdown-it-anchor' import MarkdownItAnchor from 'markdown-it-anchor'
@ -31,10 +32,35 @@ markdownIt
.use(MarkdownItSup) .use(MarkdownItSup)
.use(MarkdownItTOC) .use(MarkdownItTOC)
defineProps({ const props = withDefaults(defineProps<{ source?: string; inner_suffix?: boolean }>(), {
source: { source: '',
type: String, inner_suffix: false
default: '' })
const suffix = '{inner_suffix_' + new Date().getTime() + '}'
const inner = computed(() => {
if (props.inner_suffix) {
return markdownIt.render(props.source + suffix).replace(suffix, "<span class='loading'></span>")
} else {
return markdownIt.render(props.source)
} }
}) })
</script> </script>
<style>
.loading:after {
overflow: hidden;
display: inline-block;
vertical-align: bottom;
animation: ellipsis 0.5s infinite;
content: '\2026'; /* ascii code for the ellipsis character */
}
@keyframes ellipsis {
from {
width: 2px;
}
to {
width: 20px;
}
}
</style>