From dd123ee2f226cd60907f05260494c2b77d533ee2 Mon Sep 17 00:00:00 2001 From: bbb-lsy07 Date: Sun, 21 Jun 2026 10:40:12 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E8=BF=94=E7=92=9E=E5=BD=92?= =?UTF-8?q?=E7=9C=9F=20-=20=E5=89=A5=E7=A6=BB=20Markdown=20=E6=B3=A8?= =?UTF-8?q?=E5=85=A5=EF=BC=8C=E5=88=A9=E7=94=A8=20Halo=20=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E5=B1=82=E7=BA=A7=E5=9B=9E=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 CommentReplyPublisher 删除 buildQuoteMarkdown 和 Markdown 拼接逻辑 - AI 回复直接存入纯净文本,由 Halo 原生 quoteReply 字段渲染前台层级关系 - 前端 ConversationMessage 恢复 quoteOwner/quoteContent 字段 - 前端对话弹窗添加原生 Tailwind 引用摘要框(灰色 border-l-[3px]) - 简化 renderContent,删除 Markdown 引用正则匹配 --- .../service/CommentReplyPublisher.java | 54 ++++--------------- ui/src/views/LogsView.vue | 38 +++++++------ 2 files changed, 32 insertions(+), 60 deletions(-) diff --git a/src/main/java/top/nxxy335/commentaiautopilot/service/CommentReplyPublisher.java b/src/main/java/top/nxxy335/commentaiautopilot/service/CommentReplyPublisher.java index f7c5802..4c56b17 100644 --- a/src/main/java/top/nxxy335/commentaiautopilot/service/CommentReplyPublisher.java +++ b/src/main/java/top/nxxy335/commentaiautopilot/service/CommentReplyPublisher.java @@ -81,37 +81,12 @@ public class CommentReplyPublisher { String postName, String quoteReplyName, boolean autoPublish, String personaName) { - // 1. 获取被回复的对象信息,构建 Markdown 引用 - Mono quoteMarkdownMono = Mono.empty(); - - if (quoteReplyName != null && !quoteReplyName.isBlank()) { - // 如果是回复另一条回复 - quoteMarkdownMono = client.fetch(Reply.class, quoteReplyName) - .map(r -> buildQuoteMarkdown( - r.getSpec().getOwner() != null ? r.getSpec().getOwner().getDisplayName() : "匿名用户", - r.getSpec().getRaw() != null ? r.getSpec().getRaw() : r.getSpec().getContent() - )); - } else if (parentCommentName != null && !parentCommentName.isBlank()) { - // 如果是直接回复顶级评论 - quoteMarkdownMono = client.fetch(Comment.class, parentCommentName) - .map(c -> buildQuoteMarkdown( - c.getSpec().getOwner() != null ? c.getSpec().getOwner().getDisplayName() : "匿名用户", - c.getSpec().getRaw() != null ? c.getSpec().getRaw() : c.getSpec().getContent() - )); - } - - // 2. 解析 AI 角色并合并最终文本进行发布 - return Mono.zip(resolvePersona(personaName), quoteMarkdownMono.defaultIfEmpty("")) - .flatMap(tuple -> { - ResolvedPersona persona = tuple.getT1(); - String quoteMarkdown = tuple.getT2(); - + // 解析 AI 角色并直接发布纯净的回复内容 + return resolvePersona(personaName) + .flatMap(persona -> { String displayName = persona.displayName(); String email = persona.email(); - // 将引用文本拼接到 AI 回复内容的最前面 - String finalContent = quoteMarkdown.isBlank() ? replyContent : quoteMarkdown + "\n\n" + replyContent; - Reply reply = new Reply(); reply.setMetadata(new Metadata()); reply.getMetadata().setName(generateReplyName()); @@ -119,8 +94,11 @@ public class CommentReplyPublisher { var spec = reply.getSpec(); spec.setCommentName(parentCommentName); - spec.setRaw(finalContent); // 保存带有引用的完整 Markdown - spec.setContent(finalContent); // 同上 + + // 直接存入纯净的 AI 回复内容,不加任何 Markdown 前缀 + spec.setRaw(replyContent); + spec.setContent(replyContent); + spec.setApproved(autoPublish); if (autoPublish) { spec.setApprovedTime(Instant.now()); @@ -130,6 +108,7 @@ public class CommentReplyPublisher { spec.setAllowNotification(false); spec.setHidden(false); + // Halo 原生评论组件正是靠这个字段来渲染 "回复 @某人" 的 if (quoteReplyName != null && !quoteReplyName.isBlank()) { spec.setQuoteReply(quoteReplyName); } @@ -152,7 +131,7 @@ public class CommentReplyPublisher { owner.setAnnotations(ownerAnnotations); spec.setOwner(owner); - log.info("[Publisher] Creating reply for comment: {}, finalContent length: {}", parentCommentName, finalContent.length()); + log.info("[Publisher] Creating reply for comment: {}, content length: {}", parentCommentName, replyContent.length()); return client.create(reply) .doOnSuccess(created -> log.info("[Publisher] AI Persona '{}' reply published for comment: {}", displayName, parentCommentName)) @@ -160,19 +139,6 @@ public class CommentReplyPublisher { }); } - /** - * 新增辅助方法:构建 Markdown 引用块 - */ - private String buildQuoteMarkdown(String username, String rawText) { - if (rawText == null || rawText.isBlank()) return ""; - // 清除 HTML 标签,防止破坏 Markdown 结构 - String plainText = org.jsoup.Jsoup.clean(rawText, org.jsoup.safety.Safelist.none()).trim(); - // 截断过长的引用内容(超过 40 个字符加省略号) - String truncated = plainText.length() > 40 ? plainText.substring(0, 40) + "..." : plainText; - // 生成标准 Markdown Blockquote - return "> 💬 **@" + username + "** : " + truncated; - } - /** * Resolve persona info from AiPersona extension. * Priority: diff --git a/ui/src/views/LogsView.vue b/ui/src/views/LogsView.vue index 89d7a3d..9f0ee76 100644 --- a/ui/src/views/LogsView.vue +++ b/ui/src/views/LogsView.vue @@ -134,7 +134,17 @@
{{ msg.owner }}
-
+
+ +
+ @{{ msg.quoteOwner }}: + {{ truncateQuote(msg.quoteContent, 40) }} +
+ +
+
{{ formatDate(msg.time) }}
@@ -169,6 +179,8 @@ interface ConversationMessage { content: string time: string isAi: boolean + quoteOwner?: string + quoteContent?: string } const replies = ref([]) @@ -269,24 +281,18 @@ const stripHtml = (html: string) => { return html.replace(/<[^>]+>/g, "").replace(/\n+/g, " ").trim() } -const renderContent = (content: string, isAi: boolean) => { +const truncateQuote = (content: string, length = 40) => { + if (!content) return "" + const plain = stripHtml(content) + return plain.length > length ? plain.substring(0, length) + "..." : plain +} + +const renderContent = (content: string) => { if (!content) return "(空)" - let parsed = content + return content .replace(/]*>[\s\S]*?<\/script>/gi, "") .replace(/]*>[\s\S]*?<\/iframe>/gi, "") - - parsed = parsed.replace(/^>\s*(?:💬\s*)?\*\*(.*?)\*\*\s*[::]\s*(.*?)$/gm, (match, name, text) => { - const quoteBg = isAi ? 'bg-blue-50 border-blue-200' : 'bg-white/20 border-white/30' - const nameColor = isAi ? 'text-blue-700' : 'text-white font-semibold' - const textColor = isAi ? 'text-gray-600' : 'text-blue-100' - return `
- @${name.replace(/💬|🤖/g, '').trim()}: - ${text.trim()} -
` - }) - - parsed = parsed.replace(/\n/g, "
") - return parsed + .replace(/\n/g, "
") } const resetFilters = () => { filterStatus.value = ""; filterSentiment.value = ""; filterKeyword.value = ""; page.value = 1; fetchReplies() }