feat: 彻底重构后台 UI 与对话上下文引用模块,全面优化移动端适配 (v1.0.4) #6

Merged
bbb-lsy07 merged 11 commits from main into main 2026-06-21 12:20:25 +08:00
7 changed files with 636 additions and 2427 deletions
+7
View File
@@ -63,6 +63,7 @@ lerna-debug.log*
*.ctxt *.ctxt
### Package Files ### Package Files
*.jar
*.war *.war
*.nar *.nar
*.ear *.ear
@@ -70,6 +71,12 @@ lerna-debug.log*
*.tar.gz *.tar.gz
*.rar *.rar
### UI build output
ui/dist/
ui/dist-ssr/
ui/*.local
ui/.eslintcache
### Local file ### Local file
application-local.yml application-local.yml
application-local.yaml application-local.yaml
+1 -1
View File
@@ -5,7 +5,7 @@ plugins {
} }
group 'top.nxxy335.commentaiautopilot' group 'top.nxxy335.commentaiautopilot'
version '1.0.0' version '1.0.4'
repositories { repositories {
mavenCentral() mavenCentral()
@@ -291,26 +291,51 @@ public class CommentAiAutopilotEndpoint implements CustomEndpoint {
var commentTime = String.valueOf(comment.getMetadata().getCreationTimestamp()); var commentTime = String.valueOf(comment.getMetadata().getCreationTimestamp());
var isCommentAi = isAiOwner(comment.getSpec().getOwner()); var isCommentAi = isAiOwner(comment.getSpec().getOwner());
// 首条评论没有引用对象
var commentMsg = new ConversationMessage( var commentMsg = new ConversationMessage(
"comment", commentOwner, commentContent, commentTime, isCommentAi "comment", commentOwner, commentContent, commentTime, isCommentAi, null, null
); );
return client.list(Reply.class, return client.list(Reply.class,
reply -> commentName.equals(reply.getSpec().getCommentName()), reply -> commentName.equals(reply.getSpec().getCommentName()),
null) null)
.sort(Comparator.comparing(r -> r.getMetadata().getCreationTimestamp())) .sort(Comparator.comparing(r -> r.getMetadata().getCreationTimestamp()))
.map(reply -> { .collectList() // 收集为List以便统一处理引用映射
var replyOwner = extractOwnerName(reply.getSpec().getOwner());
var replyContent = extractContent(reply.getSpec().getRaw(), reply.getSpec().getContent());
var replyTime = String.valueOf(reply.getMetadata().getCreationTimestamp());
var isAi = isAiOwner(reply.getSpec().getOwner());
return new ConversationMessage("reply", replyOwner, replyContent, replyTime, isAi);
})
.collectList()
.map(replyList -> { .map(replyList -> {
List<ConversationMessage> messages = new ArrayList<>(); List<ConversationMessage> messages = new ArrayList<>();
messages.add(commentMsg); messages.add(commentMsg);
messages.addAll(replyList);
// 构建 Reply 的映射字典,方便查找引用关系
Map<String, Reply> replyMap = new HashMap<>();
for (Reply r : replyList) {
replyMap.put(r.getMetadata().getName(), r);
}
for (Reply reply : replyList) {
var replyOwner = extractOwnerName(reply.getSpec().getOwner());
var replyContent = extractContent(reply.getSpec().getRaw(), reply.getSpec().getContent());
var replyTime = String.valueOf(reply.getMetadata().getCreationTimestamp());
var isAi = isAiOwner(reply.getSpec().getOwner());
String quoteOwner = null;
String quoteContent = null;
// 获取引用的 Reply 名称 (Halo中如果为空,代表直接回复顶级 Comment)
String quoteReplyName = reply.getSpec().getQuoteReply();
if (quoteReplyName != null && !quoteReplyName.isBlank()) {
Reply quotedReply = replyMap.get(quoteReplyName);
if (quotedReply != null) {
quoteOwner = extractOwnerName(quotedReply.getSpec().getOwner());
quoteContent = extractContent(quotedReply.getSpec().getRaw(), quotedReply.getSpec().getContent());
}
} else {
// 没有 quoteReply 表示直接回复首条评论
quoteOwner = commentOwner;
quoteContent = commentContent;
}
messages.add(new ConversationMessage("reply", replyOwner, replyContent, replyTime, isAi, quoteOwner, quoteContent));
}
return messages; return messages;
}); });
}) })
@@ -712,7 +737,9 @@ public class CommentAiAutopilotEndpoint implements CustomEndpoint {
String owner, String owner,
String content, String content,
String time, String time,
boolean isAi boolean isAi,
String quoteOwner,
String quoteContent
) {} ) {}
public record CommenterInfo( public record CommenterInfo(
@@ -80,63 +80,63 @@ public class CommentReplyPublisher {
private Mono<Reply> doPublish(String parentCommentName, String replyContent, private Mono<Reply> doPublish(String parentCommentName, String replyContent,
String postName, String quoteReplyName, boolean autoPublish, String postName, String quoteReplyName, boolean autoPublish,
String personaName) { String personaName) {
return resolvePersona(personaName).flatMap(persona -> {
String displayName = persona.displayName();
String email = persona.email();
Reply reply = new Reply(); // 解析 AI 角色并直接发布纯净的回复内容
reply.setMetadata(new Metadata()); return resolvePersona(personaName)
reply.getMetadata().setName(generateReplyName()); .flatMap(persona -> {
reply.setSpec(new Reply.ReplySpec()); String displayName = persona.displayName();
String email = persona.email();
var spec = reply.getSpec(); Reply reply = new Reply();
spec.setCommentName(parentCommentName); reply.setMetadata(new Metadata());
spec.setRaw(replyContent); reply.getMetadata().setName(generateReplyName());
spec.setContent(replyContent); reply.setSpec(new Reply.ReplySpec());
spec.setApproved(autoPublish);
if (autoPublish) {
spec.setApprovedTime(Instant.now());
}
spec.setPriority(0);
spec.setTop(false);
spec.setAllowNotification(false);
spec.setHidden(false);
if (quoteReplyName != null && !quoteReplyName.isBlank()) { var spec = reply.getSpec();
spec.setQuoteReply(quoteReplyName); spec.setCommentName(parentCommentName);
}
var owner = new Comment.CommentOwner(); // 直接存入纯净的 AI 回复内容,不加任何 Markdown 前缀
owner.setKind(Comment.CommentOwner.KIND_EMAIL); spec.setRaw(replyContent);
if (email != null && !email.isBlank()) { spec.setContent(replyContent);
owner.setName(email);
} else {
owner.setName(AI_PERSONA_OWNER_PREFIX + displayName);
}
owner.setDisplayName(displayName + " AI");
Map<String, String> ownerAnnotations = new HashMap<>(); spec.setApproved(autoPublish);
ownerAnnotations.put("comment-ai-autopilot.nxxy335.top/is-ai", "true"); if (autoPublish) {
// 使用Gravatar邮箱头像 spec.setApprovedTime(Instant.now());
if (email != null && !email.isBlank()) { }
String gravatarUrl = GravatarUtil.generateUrl(email); spec.setPriority(0);
ownerAnnotations.put(Comment.CommentOwner.AVATAR_ANNO, gravatarUrl); spec.setTop(false);
} spec.setAllowNotification(false);
owner.setAnnotations(ownerAnnotations); spec.setHidden(false);
spec.setOwner(owner);
log.info("[Publisher] Creating reply for comment: {}, owner: kind={}, name={}, displayName={}, annotations={}", // Halo 原生评论组件正是靠这个字段来渲染 "回复 @某人" 的
parentCommentName, owner.getKind(), owner.getName(), owner.getDisplayName(), ownerAnnotations); if (quoteReplyName != null && !quoteReplyName.isBlank()) {
spec.setQuoteReply(quoteReplyName);
}
return client.create(reply) var owner = new Comment.CommentOwner();
.doOnSuccess(created -> { owner.setKind(Comment.CommentOwner.KIND_EMAIL);
var createdOwner = created.getSpec().getOwner(); if (email != null && !email.isBlank()) {
log.info("[Publisher] AI Persona '{}' reply published for comment: {}, quoteReply: {}, owner annotations after create: {}", owner.setName(email);
displayName, parentCommentName, quoteReplyName, } else {
createdOwner != null ? createdOwner.getAnnotations() : "null"); owner.setName(AI_PERSONA_OWNER_PREFIX + displayName);
}) }
.doOnError(e -> log.error("[Publisher] Failed to publish AI reply: {}", e.getMessage())); owner.setDisplayName(displayName + " AI");
});
Map<String, String> ownerAnnotations = new HashMap<>();
ownerAnnotations.put("comment-ai-autopilot.nxxy335.top/is-ai", "true");
if (email != null && !email.isBlank()) {
String gravatarUrl = GravatarUtil.generateUrl(email);
ownerAnnotations.put(Comment.CommentOwner.AVATAR_ANNO, gravatarUrl);
}
owner.setAnnotations(ownerAnnotations);
spec.setOwner(owner);
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))
.doOnError(e -> log.error("[Publisher] Failed to publish AI reply: {}", e.getMessage()));
});
} }
/** /**
+1 -1
View File
@@ -30,4 +30,4 @@ spec:
url: "https://github.com/sunny-335/plugin-comment-ai-autopilot/blob/main/LICENSE" url: "https://github.com/sunny-335/plugin-comment-ai-autopilot/blob/main/LICENSE"
settingName: "comment-ai-autopilot-settings" settingName: "comment-ai-autopilot-settings"
configMapName: "comment-ai-autopilot-configmap" configMapName: "comment-ai-autopilot-configmap"
version: "1.0.0" version: "1.0.4"
+202 -869
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff