feat: Markdown 引用注入法 - AI回复自动拼接引用块(主题无关通用方案)

- 后端 CommentReplyPublisher.doPublish 重写,发布前查询被回复对象并拼接 Markdown Blockquote
- 新增 buildQuoteMarkdown 辅助方法,Jsoup 清除 HTML 后截断 40 字符生成引用
- 前端 LogsView 恢复简洁气泡模板,移除 quoteOwner/quoteContent 前端引用逻辑
- renderContent 新增换行符处理,确保 Markdown 引用块正确渲染
This commit is contained in:
bbb-lsy07
2026-06-21 10:16:05 +08:00
parent 0477f532bb
commit 058d562e7e
2 changed files with 88 additions and 74 deletions
+4 -24
View File
@@ -299,19 +299,8 @@
:class="msg.isAi
? 'bg-blue-50 text-gray-800 rounded-tl-md'
: 'bg-gray-100 text-gray-800 rounded-tr-md'"
>
<!-- 引用摘要模块仅当存在引用时显示 -->
<div
v-if="msg.quoteOwner && msg.quoteContent"
class="mb-2 px-3 py-1.5 bg-black/5 rounded-lg border-l-2 border-gray-300 text-xs text-gray-500"
>
<span class="font-medium text-gray-600">@{{ msg.quoteOwner }}</span>:
{{ truncateQuote(msg.quoteContent, 30) }}
</div>
<!-- 实际回复内容 -->
<div v-html="renderContent(msg.content)"></div>
</div>
v-html="renderContent(msg.content)"
></div>
<!-- Time -->
<div class="text-[10px] text-gray-300 mt-1" :class="msg.isAi ? 'text-left' : 'text-right'">
{{ formatDate(msg.time) }}
@@ -369,8 +358,6 @@ interface ConversationMessage {
content: string
time: string
isAi: boolean
quoteOwner?: string
quoteContent?: string
}
const replies = ref<AiCommentReplyItem[]>([])
@@ -652,15 +639,6 @@ const stripHtml = (html: string) => {
.trim()
}
/**
* Truncate quote content for preview
*/
const truncateQuote = (content: string, length = 30) => {
if (!content) return ""
const plain = stripHtml(content)
return plain.length > length ? plain.substring(0, length) + "..." : plain
}
/**
* Sanitize and render HTML content for conversation bubbles.
* Only allows safe inline tags, strips dangerous elements.
@@ -668,6 +646,8 @@ const truncateQuote = (content: string, length = 30) => {
const renderContent = (content: string) => {
if (!content) return "<span class='text-gray-400'>(空)</span>"
return content
// 将 Markdown 的换行符替换为 HTML 的换行
.replace(/\n/g, "<br/>")
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
.replace(/<iframe[^>]*>[\s\S]*?<\/iframe>/gi, "")
.replace(/<object[^>]*>[\s\S]*?<\/object>/gi, "")