feat: 优化打字机效果和消息展示

This commit is contained in:
huangzhe
2025-07-30 18:09:23 +08:00
parent 1e16ba0497
commit 1f6bf9a865
2 changed files with 42 additions and 35 deletions

View File

@@ -108,6 +108,7 @@ export default {
is_complete: false.toString(), is_complete: false.toString(),
information: '', information: '',
}, },
currentMessageID: "",
// 打字机相关 // 打字机相关
typingText: '', typingText: '',
typingQueue: [], typingQueue: [],
@@ -115,7 +116,6 @@ export default {
isTyping: false, isTyping: false,
typingSpeed: 30, typingSpeed: 30,
typingTimeout: null, typingTimeout: null,
conversationId: ''
} }
}, },
watch: { watch: {
@@ -138,6 +138,8 @@ export default {
// this.$emit('update:messages', []) // this.$emit('update:messages', [])
this.$emit('update:conversationId', '') this.$emit('update:conversationId', '')
this.$emit('update:productName', '') this.$emit('update:productName', '')
this.$emit('update:messageStatus', 'stop')
this.typingQueue = []
}, },
async startRecording() { async startRecording() {
if (this.messageStatus === 'send') return if (this.messageStatus === 'send') return
@@ -220,7 +222,7 @@ export default {
// 重置 answerMap // 重置 answerMap
this.answerMap = '' this.answerMap = ''
this.typingQueue = [] // this.typingQueue = []
this.currentMessage = JSON.parse(JSON.stringify(this.messageInfo)) this.currentMessage = JSON.parse(JSON.stringify(this.messageInfo))
let params = { let params = {
appType: "gwcsHelper", appType: "gwcsHelper",
@@ -242,8 +244,8 @@ export default {
} }
if (this.single) { if (this.single) {
this.messages.length -= 1 this.$set(this.messages, this.messages.length - 1, this.currentMessage)
this.messages.push(this.currentMessage)
} else { } else {
this.messages.push(this.currentMessage) this.messages.push(this.currentMessage)
} }
@@ -306,28 +308,24 @@ export default {
if (!cleanLine) return null if (!cleanLine) return null
const data = JSON.parse(cleanLine) const data = JSON.parse(cleanLine)
// debugger/ // debugger/
this.conversationId = data.conversation_id // this.conversationId = data.conversation_id
// console.log(data) this.$emit('update:conversationId', data.conversation_id)
// console.log(data)
if (data.answer) { if (data.answer) {
this.answerMap += data.answer this.answerMap += data.answer
const is_complete = /<is_complete>([^<]*)(?:<\/is_complete>)?/.exec(this.answerMap) const is_complete = /<is_complete>([^<]*)(?:<\/is_complete>)?/.exec(this.answerMap)
const information = /<information>([^<]*)(?:<\/information>)?/.exec(this.answerMap) const information = /<information>([^<]*)(?:<\/information>)?/.exec(this.answerMap)
const text = /<text>([^<]*)(?:<\/text>)?/.exec(this.answerMap) const text = /<text>([^<]*)(?:<\/text>)?/.exec(this.answerMap)
// console.log(this.answerMap);
this.messageInfo.information = information ? information[1].trim() : this.newMessage this.messageInfo.information = information ? information[1].trim() : this.newMessage
this.messageInfo.is_complete = is_complete ? is_complete[1].trim() : 'false' this.messageInfo.is_complete = is_complete ? is_complete[1].trim() : 'false'
if (is_complete && is_complete[1] === 'true' && text && text[1].trim() === '') { if (is_complete && is_complete[1] === 'true' && text && text[1].trim() === '') {
this.requestSingle.abort() this.requestSingle.abort()
// setTimeout(() => {
// debugger
// this.$emit('update:messageStatus', 'send')
this.single = true this.single = true
// debugger
this.axiosGetAiChat() this.axiosGetAiChat()
// }, 1000) // this.typingQueue = []
return null
} }
} }
@@ -359,7 +357,8 @@ export default {
return data return data
}, },
updateMessageContent(parse) { updateMessageContent(parse) {
let { event, answer, isThink } = parse let { event, answer, isThink, message_id } = parse
this.currentMessageID = message_id
// 会导致发送按钮提前高亮展示 // 会导致发送按钮提前高亮展示
// if (event === 'message_end') { // if (event === 'message_end') {
// this.$emit('update:messageStatus', 'stop') // this.$emit('update:messageStatus', 'stop')
@@ -371,6 +370,7 @@ export default {
const chars = { const chars = {
answer: answer, answer: answer,
isThink: isThink, isThink: isThink,
message_id
} }
// debugger // debugger
this.typingQueue.push(chars) this.typingQueue.push(chars)
@@ -388,6 +388,11 @@ export default {
// 取出一个完整文本块 // 取出一个完整文本块
const chunk = this.typingQueue.shift() const chunk = this.typingQueue.shift()
if (chunk.message_id !== this.currentMessageID) {
console.log('message_id !== this.currentMessageID');
typeNextChar()
return
}
// console.log(this.messages); // console.log(this.messages);
const chars = Array.from(chunk.answer) const chars = Array.from(chunk.answer)

View File

@@ -1,6 +1,5 @@
<template> <template>
<section> <section>
<!-- {{ messagesList }} -->
<div v-for="(message, index) in messagesList" :key="index" class="message-item"> <div v-for="(message, index) in messagesList" :key="index" class="message-item">
<!-- {{ message }} --> <!-- {{ message }} -->
<!--用户消息--> <!--用户消息-->
@@ -21,6 +20,8 @@
<p v-html="md.render(message.think)" v-if="message.think && message.showThink" class="thinkText" /> <p v-html="md.render(message.think)" v-if="message.think && message.showThink" class="thinkText" />
</span> </span>
<div style="width: 100%"> <div style="width: 100%">
<!-- {{ (message) }} -->
<!-- <hr> -->
<p v-html="render(message)" class="render-container"></p> <p v-html="render(message)" class="render-container"></p>
<span class="speakLoadingToast pv10" v-if="!filterVisible(message)"> <span class="speakLoadingToast pv10" v-if="!filterVisible(message)">
<van-loading type="spinner" :color="primaryColor" size="20px" /> <van-loading type="spinner" :color="primaryColor" size="20px" />
@@ -79,33 +80,34 @@ export default {
}, },
methods: { methods: {
render(message) { render(message) {
return md.render(this.filterVisible(message)) const text = this.filterVisible(message)
return md.render(text)
}, },
setProductName(e) { setProductName(e) {
this.$emit('setProductName', e) this.$emit('setProductName', e)
}, },
filterVisible(message) { filterVisible(message) {
if (!message.text.startsWith('<')) { // if (!message.text.startsWith('<')) {
let text = message.text let text = message.text
// 如果开头是中文,直接返回 // 如果开头是中文,直接返回
if (new RegExp('^[\u4e00-\u9fa5]+', 'g').test(text)) return text if (new RegExp('^[\u4e00-\u9fa5]+', 'g').test(text)) return text
// 捕获 不包含 < 的后置标签 span> /span> a</span> // 捕获 不包含 < 的后置标签 span> /span> a</span>
text = text.replace(/^[/]?[a-zA-z0-9]+[</\w>]+/g, '') text = text.replace(/^[/]?[a-zA-z0-9]+[</\w>]+/g, '')
// 尝试匹配 </abc> 标签 // 尝试匹配 </abc> 标签
text = text.replace(/^<\w+>/g, '') text = text.replace(/^<\w+>/g, '')
// 尝试把 </ 标签去除 // <\/?([\w\s]+)?(?!>)$
// text = text.replace(/[</]+$/g, '') text = text.replace(/<\/?([\w\s='"]+)?(?!>)$/g, '')
// <\/?([\w\s]+)?(?!>)$ text = text.replace(/<information>([^<]*)(?:<\/information>)?/g, '')
text = text.replace(/<\/?([\w\s='"]+)?(?!>)$/g, '') text = text.replace(/<is_complete>([^<]*)(?:<\/is_complete>)?/g, '')
text = text.replace(/^\w+/, "")
return text
}
// 只把 text 标签内容渲染
let match = /<text>([^<]*)(?:<\/text>)?/.exec(message.text)
let text = match ? match[1] : ''
return text return text
// }
// 只把 text 标签内容渲染
// let match = /<text>([^<]*)(?:<\/text>)?/.exec(message.text)
// let text = match ? match[1] : ''
// text = text.replace(/<\/?([\w\s='"]+)?(?!>)$/g, '')
// return text
}, },
showThink(message) { showThink(message) {
this.$set(message, 'showThink', !message.showThink) this.$set(message, 'showThink', !message.showThink)