feat: 彻底重构后台 UI 与对话上下文引用模块,全面优化移动端适配 (v1.0.4) #6
+38
-11
@@ -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(
|
||||||
|
|||||||
@@ -299,8 +299,19 @@
|
|||||||
:class="msg.isAi
|
:class="msg.isAi
|
||||||
? 'bg-blue-50 text-gray-800 rounded-tl-md'
|
? 'bg-blue-50 text-gray-800 rounded-tl-md'
|
||||||
: 'bg-gray-100 text-gray-800 rounded-tr-md'"
|
: 'bg-gray-100 text-gray-800 rounded-tr-md'"
|
||||||
v-html="renderContent(msg.content)"
|
>
|
||||||
></div>
|
<!-- 引用摘要模块(仅当存在引用时显示) -->
|
||||||
|
<div
|
||||||
|
v-if="msg.quoteOwner && msg.quoteContent"
|
||||||
|
class="mb-2 px-3 py-1.5 bg-black/5 rounded-lg border-l-2 border-gray-300 text-xs text-gray-500"
|
||||||
|
>
|
||||||
|
<span class="font-medium text-gray-600">@{{ msg.quoteOwner }}</span>:
|
||||||
|
{{ truncateQuote(msg.quoteContent, 30) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 实际回复内容 -->
|
||||||
|
<div v-html="renderContent(msg.content)"></div>
|
||||||
|
</div>
|
||||||
<!-- Time -->
|
<!-- Time -->
|
||||||
<div class="text-[10px] text-gray-300 mt-1" :class="msg.isAi ? 'text-left' : 'text-right'">
|
<div class="text-[10px] text-gray-300 mt-1" :class="msg.isAi ? 'text-left' : 'text-right'">
|
||||||
{{ formatDate(msg.time) }}
|
{{ formatDate(msg.time) }}
|
||||||
@@ -358,6 +369,8 @@ interface ConversationMessage {
|
|||||||
content: string
|
content: string
|
||||||
time: string
|
time: string
|
||||||
isAi: boolean
|
isAi: boolean
|
||||||
|
quoteOwner?: string
|
||||||
|
quoteContent?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const replies = ref<AiCommentReplyItem[]>([])
|
const replies = ref<AiCommentReplyItem[]>([])
|
||||||
@@ -639,6 +652,15 @@ const stripHtml = (html: string) => {
|
|||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate quote content for preview
|
||||||
|
*/
|
||||||
|
const truncateQuote = (content: string, length = 30) => {
|
||||||
|
if (!content) return ""
|
||||||
|
const plain = stripHtml(content)
|
||||||
|
return plain.length > length ? plain.substring(0, length) + "..." : plain
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize and render HTML content for conversation bubbles.
|
* Sanitize and render HTML content for conversation bubbles.
|
||||||
* Only allows safe inline tags, strips dangerous elements.
|
* Only allows safe inline tags, strips dangerous elements.
|
||||||
|
|||||||
Reference in New Issue
Block a user