refactor: 返璞归真 - 剥离 Markdown 注入,利用 Halo 原生层级回复

- 后端 CommentReplyPublisher 删除 buildQuoteMarkdown 和 Markdown 拼接逻辑
- AI 回复直接存入纯净文本,由 Halo 原生 quoteReply 字段渲染前台层级关系
- 前端 ConversationMessage 恢复 quoteOwner/quoteContent 字段
- 前端对话弹窗添加原生 Tailwind 引用摘要框(灰色 border-l-[3px])
- 简化 renderContent,删除 Markdown 引用正则匹配
This commit is contained in:
bbb-lsy07
2026-06-21 10:40:12 +08:00
parent 11620cc113
commit dd123ee2f2
2 changed files with 32 additions and 60 deletions
@@ -81,37 +81,12 @@ public class CommentReplyPublisher {
String postName, String quoteReplyName, boolean autoPublish,
String personaName) {
// 1. 获取被回复的对象信息,构建 Markdown 引用
Mono<String> 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: