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'
interface ApplicationFormType {
name?: string
@ -13,20 +14,38 @@ interface chatType {
problem_text: string
answer_text: string
buffer: Array<String>
/**
*
*/
write_ed?: boolean
/**
*
*/
is_stop?: boolean
}
export class ChatManage {
export class ChatRecordManage {
id?: NodeJS.Timer
ms: number
chat: chatType
is_close?: boolean
write_ed?: boolean
is_stop?: boolean
loading?: Ref<boolean>
constructor(chat: chatType, ms?: number, loading?: Ref<boolean>) {
this.ms = ms ? ms : 10
this.chat = chat
this.loading = loading
this.is_stop = false
this.is_close = false
this.write_ed = false
}
write() {
this.chat.is_stop = false
this.is_stop = false
if (this.loading) {
this.loading.value = true
}
this.id = setInterval(() => {
const s = this.chat.buffer.shift()
if (s !== undefined) {
@ -34,6 +53,8 @@ export class ChatManage {
} else {
if (this.is_close) {
clearInterval(this.id)
this.chat.write_ed = true
this.write_ed = true
if (this.loading) {
this.loading.value = false
}
@ -41,6 +62,14 @@ export class ChatManage {
}
}, this.ms)
}
stop() {
clearInterval(this.id)
this.is_stop = true
this.chat.is_stop = true
if (this.loading) {
this.loading.value = false
}
}
close() {
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 }

View File

@ -59,9 +59,27 @@
<el-card shadow="always" class="dialog-card"> 回答中... </el-card>
</div>
<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-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>
</template>
@ -90,7 +108,7 @@
<script setup lang="ts">
import { ref, nextTick, onUpdated, computed } from 'vue'
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'
const props = defineProps({
data: {
@ -126,7 +144,12 @@ function sendChatHandle(event: any) {
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
})
}
function chatMessage() {
loading.value = true
if (!chartOpenId.value) {
getChartOpenId()
} else {
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) => {
const id = randomId()
chatList.value.push({
id: id,
problem_text: problem_text,
answer_text: '',
buffer: []
})
inputValue.value = ''
const row = chatList.value.find((item) => item.id === id)
if (row) {
const chatMange = new ChatManage(row, 50, loading)
chatMange.write()
ChatManagement.addChatRecord(row, 50, loading)
ChatManagement.write(id)
const reader = response.body.getReader()
while (true) {
const { done, value } = await reader.read()
if (done) {
chatMange.close()
ChatManagement.close(id)
break
}
try {
const decoder = new TextDecoder('utf-8')
const str = decoder.decode(value, { stream: true })
if (str && str.startsWith('data:')) {
// console.log(JSON?.parse(str.replace('data:', '')))
const content = JSON?.parse(str.replace('data:', ''))?.content
if (content) {
chatMange.append(content)
ChatManagement.append(id, content)
}
}
} catch (e) {}
@ -301,6 +324,11 @@ onUpdated(() => {
}
.dialog-card {
border: none;
display: flex;
:deep(.el-card__body) {
display: flex;
align-items: center;
}
}
}
</style>

View File

@ -1,8 +1,9 @@
<template>
<div v-html="markdownIt.render(source)" />
<div v-html="inner" />
</template>
<script setup lang="ts">
import { computed } from 'vue'
import MarkdownIt from 'markdown-it'
import MarkdownItAbbr from 'markdown-it-abbr'
import MarkdownItAnchor from 'markdown-it-anchor'
@ -31,10 +32,35 @@ markdownIt
.use(MarkdownItSup)
.use(MarkdownItTOC)
defineProps({
source: {
type: String,
default: ''
const props = withDefaults(defineProps<{ source?: string; inner_suffix?: boolean }>(), {
source: '',
inner_suffix: false
})
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>
<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>