1 Commits
Author SHA1 Message Date
bbb-lsy07andbbb-lsy07 1a2732fe19 feat: 彻底重构后台 UI 与对话上下文引用模块,全面优化移动端适配 (v1.0.4) (#6)
* feat: 对话气泡增加引用摘要模块,解决多用户混杂交谈上下文不清晰问题

- 后端 ConversationMessage Record 新增 quoteOwner/quoteContent 字段
- 后端 getConversation 方法重写,构建 Reply 映射字典溯源引用关系
- 前端 ConversationMessage 类型定义新增 quoteOwner/quoteContent
- 前端新增 truncateQuote 截断方法(复用 stripHtml,限30字符)
- 前端对话气泡模板渲染灰色引用条(bg-black/5 + border-l-2)

* chore: 补充 .gitignore 规则(*.jar、ui/dist 等)

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

- 后端 CommentReplyPublisher.doPublish 重写,发布前查询被回复对象并拼接 Markdown Blockquote
- 新增 buildQuoteMarkdown 辅助方法,Jsoup 清除 HTML 后截断 40 字符生成引用
- 前端 LogsView 恢复简洁气泡模板,移除 quoteOwner/quoteContent 前端引用逻辑
- renderContent 新增换行符处理,确保 Markdown 引用块正确渲染

* style: 重写 LogsView.vue - 纯 Tailwind 标签替代 Emoji,移除 300+ 行自定义 CSS

- 状态/情感标签改用纯色 Tailwind 背景标签,去除所有 Emoji
- 删除 300+ 行自定义 CSS,全部替换为 Tailwind 原子类
- 对话弹窗 Markdown 引用块正则提取,去除气泡和机器人 Emoji
- 优化移动端响应式布局,解决排版错位问题

* style: 重写 SettingsView.vue - 纯 Tailwind 栅格布局,移除自定义 CSS

- 标签导航改用 Tailwind flex + overflow-x-auto
- 所有设置面板(basic/persona/model/prompt/cleanup)改用 Tailwind 原子类
- 开关改用 peer-checked 伪类实现,移除自定义 toggle CSS
- 滑块刻度改用 flex justify-between 实现
- 侧边栏 lg:sticky lg:top-24,移动端自然折叠到底部
- 弹窗(评论者选择/角色编辑)改用 fixed inset-0 + backdrop-blur
- 删除 600+ 行自定义 CSS

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

- 后端 CommentReplyPublisher 删除 buildQuoteMarkdown 和 Markdown 拼接逻辑
- AI 回复直接存入纯净文本,由 Halo 原生 quoteReply 字段渲染前台层级关系
- 前端 ConversationMessage 恢复 quoteOwner/quoteContent 字段
- 前端对话弹窗添加原生 Tailwind 引用摘要框(灰色 border-l-[3px])
- 简化 renderContent,删除 Markdown 引用正则匹配

* chore: 版本号升级至 1.0.1,强制刷新 Halo 前端缓存

- plugin.yaml version: 1.0.0 -> 1.0.1
- build.gradle version: 1.0.0 -> 1.0.1
- LogsView truncateQuote/renderContent 增加历史 Markdown 引用文本清理正则
- 防止旧版测试数据 (💬 **@某人**:) 在界面套娃显示

* style: 彻底重写 LogsView & SettingsView - 原生 Scoped CSS 替代 Tailwind

- LogsView.vue: 移除所有 Tailwind 类,改用 <style scoped> 原生 CSS
- SettingsView.vue: 移除所有 Tailwind 类,改用 <style scoped> 原生 CSS
- 标签配色、气泡样式、引用框全部使用纯 CSS 实现,避免 Halo 主题冲突
- 版本号升级至 1.0.2 强制刷新前端缓存

* feat: SettingsView 完整功能版 - AI角色/数据清理/导入导出

- 5个设置面板:基本设置、AI角色、模型设置、Prompt、数据清理
- AI角色:CRUD、Gravatar头像、性别/唤醒词/默认角色
- 数据清理:自动清理开关、保留天数滑块、手动清理
- 导入导出:JSON配置导入导出
- 评论者黑名单弹窗选择
- 全部使用原生 Scoped CSS

* chore: 版本号升级至 1.0.3

* v1.0.4: 美化 LogsView 和 SettingsView UI,优化引用框样式与移动端适配

---------

Co-authored-by: bbb-lsy07 <bbb-lsy07@users.noreply.github.com>
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
### Package Files
*.jar
*.war
*.nar
*.ear
@@ -70,6 +71,12 @@ lerna-debug.log*
*.tar.gz
*.rar
### UI build output
ui/dist/
ui/dist-ssr/
ui/*.local
ui/.eslintcache
### Local file
application-local.yml
application-local.yaml
+1 -1
View File
@@ -5,7 +5,7 @@ plugins {
}
group 'top.nxxy335.commentaiautopilot'
version '1.0.0'
version '1.0.4'
repositories {
mavenCentral()
@@ -291,26 +291,51 @@ public class CommentAiAutopilotEndpoint implements CustomEndpoint {
var commentTime = String.valueOf(comment.getMetadata().getCreationTimestamp());
var isCommentAi = isAiOwner(comment.getSpec().getOwner());
// 首条评论没有引用对象
var commentMsg = new ConversationMessage(
"comment", commentOwner, commentContent, commentTime, isCommentAi
"comment", commentOwner, commentContent, commentTime, isCommentAi, null, null
);
return client.list(Reply.class,
reply -> commentName.equals(reply.getSpec().getCommentName()),
null)
.sort(Comparator.comparing(r -> r.getMetadata().getCreationTimestamp()))
.map(reply -> {
.collectList() // 收集为List以便统一处理引用映射
.map(replyList -> {
List<ConversationMessage> messages = new ArrayList<>();
messages.add(commentMsg);
// 构建 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());
return new ConversationMessage("reply", replyOwner, replyContent, replyTime, isAi);
})
.collectList()
.map(replyList -> {
List<ConversationMessage> messages = new ArrayList<>();
messages.add(commentMsg);
messages.addAll(replyList);
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;
});
})
@@ -712,7 +737,9 @@ public class CommentAiAutopilotEndpoint implements CustomEndpoint {
String owner,
String content,
String time,
boolean isAi
boolean isAi,
String quoteOwner,
String quoteContent
) {}
public record CommenterInfo(
@@ -80,7 +80,10 @@ public class CommentReplyPublisher {
private Mono<Reply> doPublish(String parentCommentName, String replyContent,
String postName, String quoteReplyName, boolean autoPublish,
String personaName) {
return resolvePersona(personaName).flatMap(persona -> {
// 解析 AI 角色并直接发布纯净的回复内容
return resolvePersona(personaName)
.flatMap(persona -> {
String displayName = persona.displayName();
String email = persona.email();
@@ -91,8 +94,11 @@ public class CommentReplyPublisher {
var spec = reply.getSpec();
spec.setCommentName(parentCommentName);
// 直接存入纯净的 AI 回复内容,不加任何 Markdown 前缀
spec.setRaw(replyContent);
spec.setContent(replyContent);
spec.setApproved(autoPublish);
if (autoPublish) {
spec.setApprovedTime(Instant.now());
@@ -102,6 +108,7 @@ public class CommentReplyPublisher {
spec.setAllowNotification(false);
spec.setHidden(false);
// Halo 原生评论组件正是靠这个字段来渲染 "回复 @某人" 的
if (quoteReplyName != null && !quoteReplyName.isBlank()) {
spec.setQuoteReply(quoteReplyName);
}
@@ -117,7 +124,6 @@ public class CommentReplyPublisher {
Map<String, String> ownerAnnotations = new HashMap<>();
ownerAnnotations.put("comment-ai-autopilot.nxxy335.top/is-ai", "true");
// 使用Gravatar邮箱头像
if (email != null && !email.isBlank()) {
String gravatarUrl = GravatarUtil.generateUrl(email);
ownerAnnotations.put(Comment.CommentOwner.AVATAR_ANNO, gravatarUrl);
@@ -125,16 +131,10 @@ public class CommentReplyPublisher {
owner.setAnnotations(ownerAnnotations);
spec.setOwner(owner);
log.info("[Publisher] Creating reply for comment: {}, owner: kind={}, name={}, displayName={}, annotations={}",
parentCommentName, owner.getKind(), owner.getName(), owner.getDisplayName(), ownerAnnotations);
log.info("[Publisher] Creating reply for comment: {}, content length: {}", parentCommentName, replyContent.length());
return client.create(reply)
.doOnSuccess(created -> {
var createdOwner = created.getSpec().getOwner();
log.info("[Publisher] AI Persona '{}' reply published for comment: {}, quoteReply: {}, owner annotations after create: {}",
displayName, parentCommentName, quoteReplyName,
createdOwner != null ? createdOwner.getAnnotations() : "null");
})
.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"
settingName: "comment-ai-autopilot-settings"
configMapName: "comment-ai-autopilot-configmap"
version: "1.0.0"
version: "1.0.4"
+201 -868
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff