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>
|