Skip to content

Commit ee44f1e

Browse files
committed
feat(chatroom): 添加聊天室AI功能和回溯功能
- 添加聊天室AI配置选项,包括启用开关和回溯功能开关 - 实现聊天室AI对话功能,支持@马库斯触发AI回复 - 添加AI对话限流器,限制每用户每分钟最多5次对话 - 实现聊天室回溯功能,可总结近2小时的聊天记录 - 添加AI Provider工厂类的聊天室相关方法和配置项 - 在聊天室处理器中集成AI回复功能调用 - 添加执法命令支持,可通过"执法 回溯"触发聊天总结
1 parent 53c8545 commit ee44f1e

File tree

2 files changed

+73
-3
lines changed

2 files changed

+73
-3
lines changed

src/main/java/org/b3log/symphony/ai/OpenAIProvider.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,11 @@ private JSONWriter getContentType(JSONWriter write, List<ContentType> types) {
5353
for (ContentType type : types) {
5454
write = write.object();
5555
write = switch (type) {
56-
case ContentType.Text(String text) -> write.key("text").value(text);
56+
case ContentType.Text(String text) -> write
57+
.key("type").value("text")
58+
.key("text").value(text);
5759
case ContentType.Image(String data, String _) -> write
60+
.key("type").value("image_url")
5861
.key("image_url").object()
5962
.key("url").value(data)
6063
.endObject();

src/main/java/org/b3log/symphony/processor/bot/ChatRoomBot.java

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.b3log.latke.repository.jdbc.JdbcRepository;
3333
import org.b3log.latke.service.ServiceException;
3434
import org.b3log.symphony.ai.AIProviderFactory;
35+
import org.b3log.symphony.ai.OpenAIProvider;
36+
import org.b3log.symphony.ai.Provider;
3537
import org.b3log.symphony.model.*;
3638
import org.b3log.symphony.processor.ApiProcessor;
3739
import org.b3log.symphony.processor.ChatroomProcessor;
@@ -1089,6 +1091,7 @@ public static void nightDisableCheck() {
10891091
/**
10901092
* 处理 @马库斯 AI 回复
10911093
* 异步调用 AI 生成回复,不阻塞主线程
1094+
* 支持识别消息中的图片
10921095
*
10931096
* @param userName 发送消息的用户名
10941097
* @param content 消息内容
@@ -1121,9 +1124,70 @@ public static void handleAIChat(String userName, String content, String oId) {
11211124
sendBotMsg("@" + userName + " 你的对话过于频繁,稍候再试吧~");
11221125
return;
11231126
}
1124-
// 调用 AI 生成回复
1127+
11251128
String systemPrompt = "你是摸鱼派社区的智能助手马库斯,友好、幽默、乐于助人。回复要简洁明了,不要太长。不要在回复中使用@提及任何用户。";
1126-
String response = AIProviderFactory.chatSync(systemPrompt, question);
1129+
String response;
1130+
1131+
// 提取 Markdown 图片 URL: ![alt](url)
1132+
java.util.regex.Pattern imgPattern = java.util.regex.Pattern.compile("!\\[[^\\]]*\\]\\(([^)]+)\\)");
1133+
java.util.regex.Matcher imgMatcher = imgPattern.matcher(question);
1134+
List<String> imageUrls = new ArrayList<>();
1135+
while (imgMatcher.find()) {
1136+
imageUrls.add(imgMatcher.group(1));
1137+
}
1138+
1139+
if (!imageUrls.isEmpty()) {
1140+
// 有图片,使用多模态消息
1141+
// 移除图片标记,保留纯文本问题
1142+
String textQuestion = question.replaceAll("!\\[[^\\]]*\\]\\([^)]+\\)", "").trim();
1143+
if (textQuestion.isEmpty()) {
1144+
textQuestion = "请描述这张图片";
1145+
}
1146+
1147+
LOGGER.log(Level.INFO, "Processing image chat for user: {}, images: {}, question: {}",
1148+
userName, imageUrls.size(), textQuestion);
1149+
1150+
// 构建多模态内容
1151+
List<Provider.ContentType> contentTypes = new ArrayList<>();
1152+
contentTypes.add(new Provider.ContentType.Text(textQuestion));
1153+
for (String imageUrl : imageUrls) {
1154+
LOGGER.log(Level.INFO, "Adding image URL: {}", imageUrl);
1155+
contentTypes.add(new Provider.ContentType.Image(imageUrl, "image/jpeg"));
1156+
}
1157+
1158+
var messages = OpenAIProvider.Message.of(
1159+
new OpenAIProvider.Message.System(systemPrompt),
1160+
new OpenAIProvider.Message.User(new Provider.Content.Array(contentTypes))
1161+
);
1162+
1163+
// 使用自定义消息调用 AI
1164+
var sb = new StringBuilder();
1165+
var provider = AIProviderFactory.createProvider(messages);
1166+
AIProviderFactory.send(provider).forEach(json -> {
1167+
LOGGER.log(Level.DEBUG, "AI response chunk: {}", json.toString());
1168+
var choices = json.optJSONArray("choices");
1169+
if (choices != null && choices.length() > 0) {
1170+
var delta = choices.getJSONObject(0).optJSONObject("delta");
1171+
if (delta != null) {
1172+
sb.append(delta.optString("content", ""));
1173+
}
1174+
var message = choices.getJSONObject(0).optJSONObject("message");
1175+
if (message != null) {
1176+
sb.append(message.optString("content", ""));
1177+
}
1178+
}
1179+
// 检查是否有错误信息
1180+
if (json.has("error")) {
1181+
LOGGER.log(Level.ERROR, "AI API error: {}", json.optJSONObject("error"));
1182+
}
1183+
});
1184+
response = sb.toString();
1185+
LOGGER.log(Level.INFO, "AI image response length: {}", response.length());
1186+
} else {
1187+
// 无图片,使用普通文本对话
1188+
response = AIProviderFactory.chatSync(systemPrompt, question);
1189+
}
1190+
11271191
if (response != null && !response.isEmpty()) {
11281192
// 清理 AI 回复中可能的 @用户名,避免重复艾特
11291193
response = response.replaceAll("^@[a-zA-Z0-9_\\-]+\\s*", "").trim();
@@ -1134,9 +1198,12 @@ public static void handleAIChat(String userName, String content, String oId) {
11341198
+ "##### 引用 @" + userName + " [↩](" + Latkes.getServePath() + "/cr#chatroom" + oId + " \"跳转至原消息\") \n"
11351199
+ quotedContent;
11361200
sendBotMsg(replyMsg);
1201+
} else {
1202+
LOGGER.log(Level.WARN, "AI returned empty response for user: " + userName);
11371203
}
11381204
} catch (Exception e) {
11391205
LOGGER.log(Level.ERROR, "AI chat failed for user: " + userName, e);
1206+
sendBotMsg("@" + userName + " 抱歉,AI 处理失败:" + e.getMessage());
11401207
}
11411208
});
11421209
}

0 commit comments

Comments
 (0)