feat: 5级情感分析、日志UI优化、页面链接支持、v1.0.0-b26cea

This commit is contained in:
sunny-335
2026-06-18 19:58:46 +08:00
parent 77e3bd36c5
commit a9dd1c14bc
14 changed files with 441 additions and 110 deletions
@@ -47,6 +47,8 @@ public class CommentAiAutopilotPlugin extends BasePlugin {
.indexFunc(ext -> ext.getSpec().getSentiment()));
indexSpecs.add(IndexSpecs.<AiCommentReply, String>single("spec.published", String.class)
.indexFunc(ext -> String.valueOf(ext.getSpec().getPublished())));
indexSpecs.add(IndexSpecs.<AiCommentReply, String>single("spec.postKind", String.class)
.indexFunc(ext -> ext.getSpec().getPostKind()));
});
schemeManager.register(AiPersona.class);
@@ -33,6 +33,9 @@ public class AiCommentReply extends AbstractExtension {
@Schema(description = "关联文章Slug,用于生成文章链接")
private String postSlug;
@Schema(description = "关联内容类型: Post/SinglePage")
private String postKind;
@Schema(description = "AI回复内容")
private String reply;
@@ -525,6 +525,7 @@ public class AiReplyOrchestrator {
record.getSpec().setCommentId(context.commentId());
record.getSpec().setPostId(context.postId());
record.getSpec().setPostSlug(context.postSlug());
record.getSpec().setPostKind(context.postKind());
record.getSpec().setReply("");
record.getSpec().setScore(0);
record.getSpec().setStatus("PENDING");
@@ -10,6 +10,7 @@ import run.halo.app.content.ContentWrapper;
import run.halo.app.content.PostContentService;
import run.halo.app.core.extension.content.Comment;
import run.halo.app.core.extension.content.Post;
import run.halo.app.core.extension.content.SinglePage;
import run.halo.app.core.extension.content.Reply;
import run.halo.app.extension.ReactiveExtensionClient;
@@ -113,7 +114,8 @@ public class ContextExtractor {
isAiConversation,
formatPostDate(post),
commentCount,
""
"",
"Post"
))
)
)
@@ -129,7 +131,47 @@ public class ContextExtractor {
isAiConversation,
"",
0,
""
"",
"Post"
));
}
if (subjectRef != null && "SinglePage".equals(subjectRef.getKind())) {
String postName = subjectRef.getName();
return client.fetch(SinglePage.class, postName)
.flatMap(singlePage -> getPostContent(postName)
.flatMap(content -> getCommentCount(comment.getMetadata().getName())
.map(commentCount -> new CommentContext(
comment.getMetadata().getName(),
postName,
singlePage.getSpec().getSlug(),
commentContent,
commentOwner,
singlePage.getSpec().getTitle(),
content,
null,
isAiConversation,
formatSinglePageDate(singlePage),
commentCount,
"",
"SinglePage"
))
)
)
.defaultIfEmpty(new CommentContext(
comment.getMetadata().getName(),
postName,
"",
commentContent,
commentOwner,
"",
"",
null,
isAiConversation,
"",
0,
"",
"SinglePage"
));
}
@@ -145,6 +187,7 @@ public class ContextExtractor {
isAiConversation,
"",
0,
"",
""
));
}
@@ -179,7 +222,8 @@ public class ContextExtractor {
isAiConversation,
formatPostDate(post),
commentCount,
history
history,
"Post"
))
)
)
@@ -196,7 +240,49 @@ public class ContextExtractor {
isAiConversation,
"",
0,
""
"",
"Post"
));
}
if (subjectRef != null && "SinglePage".equals(subjectRef.getKind())) {
String postName = subjectRef.getName();
return client.fetch(SinglePage.class, postName)
.flatMap(singlePage -> getPostContent(postName)
.flatMap(content -> getCommentCount(commentName)
.flatMap(commentCount -> historyMono
.map(history -> new CommentContext(
commentName,
postName,
singlePage.getSpec().getSlug(),
replyContent,
replyOwner,
singlePage.getSpec().getTitle(),
content,
replyName,
isAiConversation,
formatSinglePageDate(singlePage),
commentCount,
history,
"SinglePage"
))
)
)
)
.defaultIfEmpty(new CommentContext(
commentName,
postName,
"",
replyContent,
replyOwner,
"",
"",
replyName,
isAiConversation,
"",
0,
"",
"SinglePage"
));
}
@@ -213,7 +299,8 @@ public class ContextExtractor {
isAiConversation,
"",
0,
history
history,
""
));
}
@@ -290,6 +377,18 @@ public class ContextExtractor {
return "";
}
private String formatSinglePageDate(SinglePage singlePage) {
var publishTime = singlePage.getSpec().getPublishTime();
if (publishTime != null) {
return publishTime.toString().substring(0, 10);
}
var creationTimestamp = singlePage.getMetadata().getCreationTimestamp();
if (creationTimestamp != null) {
return creationTimestamp.toString().substring(0, 10);
}
return "";
}
private Mono<Integer> getCommentCount(String commentName) {
return client.list(Reply.class,
reply -> commentName.equals(reply.getSpec().getCommentName()),
@@ -311,6 +410,7 @@ public class ContextExtractor {
boolean isAiConversation,
String postDate,
int commentCount,
String conversationHistory
String conversationHistory,
String postKind
) {}
}
@@ -142,8 +142,10 @@ public class PromptBuilder {
return prompt;
}
String sentimentHint = switch (sentiment) {
case "VERY_POSITIVE" -> "\n\n【情感提示】评论者情绪非常正面积极,请用热情洋溢的语气回复,表达真诚的感谢和共鸣。";
case "POSITIVE" -> "\n\n【情感提示】评论者情绪正面积极,请用热情友好的语气回复,可以表达感谢和共鸣。";
case "NEGATIVE" -> "\n\n【情感提示】评论者情绪偏负面,请用理性温和的语气回复,避免激化矛盾,展现理解和包容。";
case "VERY_NEGATIVE" -> "\n\n【情感提示】评论者情绪非常负面,请用非常温和、理性的语气回复,避免任何可能激化矛盾的表达,展现充分的理解和耐心。";
default -> "";
};
return prompt + sentimentHint;
@@ -17,13 +17,17 @@ public class SentimentService {
}
public record SentimentResult(String sentiment, double confidence) {
public static final String VERY_POSITIVE = "VERY_POSITIVE";
public static final String POSITIVE = "POSITIVE";
public static final String NEUTRAL = "NEUTRAL";
public static final String NEGATIVE = "NEGATIVE";
public static final String VERY_NEGATIVE = "VERY_NEGATIVE";
}
private static final List<String> CHOICES = List.of(
SentimentResult.POSITIVE, SentimentResult.NEUTRAL, SentimentResult.NEGATIVE
SentimentResult.VERY_POSITIVE, SentimentResult.POSITIVE,
SentimentResult.NEUTRAL, SentimentResult.NEGATIVE,
SentimentResult.VERY_NEGATIVE
);
/**
@@ -31,7 +35,15 @@ public class SentimentService {
* ({@code OutputSpec.choice}) for reliable classification.
*/
public Mono<SentimentResult> analyzeSentiment(String commentContent, String modelName) {
String systemPrompt = "你是一个情感分析助手。请分析评论的情感倾向,只返回 POSITIVE、NEUTRAL 或 NEGATIVE 之一。";
String systemPrompt = "你是一个专业的情感分析助手。请根据以下标准分析评论的情感倾向\n"
+ "\n"
+ "- VERY_POSITIVE:非常正面,包含强烈的感谢、赞美或认同(如\"太棒了\"\"非常感谢\"\"写得太好了\"\n"
+ "- POSITIVE:正面,友好、肯定或支持的态度(如\"不错\"\"学习了\"\"支持\"\n"
+ "- NEUTRAL:中性,提问、讨论、陈述事实,无明显情感倾向(如\"请问...\"\"这个怎么用\"\"我觉得\"\n"
+ "- NEGATIVE:负面,不满、质疑或批评(如\"不好用\"\"有问题\"\"不太行\"\n"
+ "- VERY_NEGATIVE:非常负面,攻击、辱骂或极端负面情绪(如\"垃圾\"\"骗子\"\"太差了\"\n"
+ "\n"
+ "只返回 VERY_POSITIVE、POSITIVE、NEUTRAL、NEGATIVE 或 VERY_NEGATIVE 之一。";
String userPrompt = "分析以下评论的情感倾向:\n\n" + commentContent;
return aiFoundationClient.classify(systemPrompt, userPrompt, CHOICES, modelName)
+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-beta.3"
version: "1.0.0-b26cea"