Files
sionrui/frontend/app/web-gold/src/components/ChatMessageRenderer.vue

110 lines
2.4 KiB
Vue
Raw Normal View History

2025-11-10 23:53:05 +08:00
<template>
<div class="prompt-display" v-html="renderedContent"></div>
</template>
<script setup>
2025-11-19 00:12:47 +08:00
import { ref, watch } from 'vue'
2025-11-10 23:53:05 +08:00
import { renderMarkdown } from '@/utils/markdown'
const props = defineProps({
content: {
type: String,
default: ''
},
isStreaming: {
type: Boolean,
default: false
}
})
2025-11-19 00:12:47 +08:00
// 当前渲染的内容(避免重复渲染)
const currentContent = ref('')
// 渲染的 HTML 内容
2025-11-13 01:06:28 +08:00
const renderedContent = ref('')
2025-11-10 23:53:05 +08:00
/**
2025-11-13 01:06:28 +08:00
* 更新渲染内容
2025-11-19 00:12:47 +08:00
* 只有当内容真正改变时才更新避免重复渲染
2025-11-10 23:53:05 +08:00
*/
2025-11-13 01:06:28 +08:00
function updateRenderedContent() {
2025-11-19 00:12:47 +08:00
const content = currentContent.value
// 避免重复渲染相同内容
if (content === renderedContent.value.replace(/<[^>]*>/g, '')) {
return
}
2025-11-10 23:53:05 +08:00
if (!content) {
2025-11-13 01:06:28 +08:00
renderedContent.value = ''
2025-11-10 23:53:05 +08:00
return
}
2025-11-19 00:12:47 +08:00
2025-11-13 01:06:28 +08:00
// 渲染 markdown 为 HTML
renderedContent.value = renderMarkdown(content)
2025-11-10 23:53:05 +08:00
}
/**
* 处理内容更新
2025-11-22 17:17:15 +08:00
* 流式渲染需要拼接增量内容
2025-11-10 23:53:05 +08:00
*/
2025-11-13 01:06:28 +08:00
function handleContentUpdate(newContent) {
if (!newContent) {
2025-11-19 00:12:47 +08:00
currentContent.value = ''
updateRenderedContent()
2025-11-10 23:53:05 +08:00
return
}
2025-11-19 00:12:47 +08:00
2025-11-22 17:17:15 +08:00
// 流式模式下拼接增量内容
if (props.isStreaming) {
currentContent.value += newContent
} else {
// 非流式模式下直接替换
currentContent.value = newContent
}
2025-11-19 00:12:47 +08:00
updateRenderedContent()
2025-11-10 23:53:05 +08:00
}
2025-11-19 00:12:47 +08:00
// 监听 content 变化,使用防抖处理避免频繁更新
let updateTimeout = null
2025-11-10 23:53:05 +08:00
watch(() => props.content, (newContent) => {
2025-11-19 00:12:47 +08:00
// 清除之前的定时器
if (updateTimeout) {
clearTimeout(updateTimeout)
}
// 延迟更新,避免流式传输时频繁更新导致的性能问题
updateTimeout = setTimeout(() => {
handleContentUpdate(newContent)
}, 50) // 50ms 防抖
})
2025-11-10 23:53:05 +08:00
2025-11-13 01:06:28 +08:00
// 监听 isStreaming 变化
2025-11-10 23:53:05 +08:00
watch(() => props.isStreaming, (newVal, oldVal) => {
2025-11-22 17:17:15 +08:00
// 流式传输开始时,清空之前的内容
if (newVal && !oldVal) {
currentContent.value = ''
renderedContent.value = ''
}
2025-11-19 00:12:47 +08:00
// 流式传输结束时,确保显示完整内容
if (!newVal && oldVal && props.content) {
currentContent.value = props.content
updateRenderedContent()
2025-11-10 23:53:05 +08:00
}
})
2025-11-13 01:06:28 +08:00
2025-11-19 00:12:47 +08:00
// 立即渲染初始内容
handleContentUpdate(props.content)
2025-11-10 23:53:05 +08:00
</script>
<style scoped>
/* 修复 pre 标签撑开容器的问题 */
.prompt-display :deep(pre) {
max-width: 100%;
overflow-x: auto;
word-break: break-word;
white-space: pre-wrap;
word-wrap: break-word;
}
</style>