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>
This commit was merged in pull request #6.
This commit is contained in:
@@ -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
@@ -5,7 +5,7 @@ plugins {
|
||||
}
|
||||
|
||||
group 'top.nxxy335.commentaiautopilot'
|
||||
version '1.0.0'
|
||||
version '1.0.4'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
+37
-10
@@ -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()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+274
-1432
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user