511 lines
18 KiB
Java
511 lines
18 KiB
Java
package com.emotion.service.impl;
|
|
|
|
import com.alibaba.fastjson2.JSON;
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
import com.emotion.entity.Message;
|
|
import com.emotion.entity.Conversation;
|
|
import com.emotion.service.AIChatService;
|
|
import com.emotion.service.MessageService;
|
|
import com.emotion.service.ConversationService;
|
|
import com.emotion.service.CozeApiCallService;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.http.HttpEntity;
|
|
import org.springframework.http.HttpHeaders;
|
|
import org.springframework.http.HttpMethod;
|
|
import org.springframework.http.ResponseEntity;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.web.client.RestTemplate;
|
|
|
|
import java.time.LocalDateTime;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.UUID;
|
|
|
|
/**
|
|
* AI聊天服务实现类
|
|
*
|
|
* @author emotion-museum
|
|
* @date 2025-07-24
|
|
*/
|
|
@Slf4j
|
|
@Service
|
|
public class AiChatServiceImpl implements AIChatService {
|
|
|
|
@Autowired
|
|
private RestTemplate restTemplate;
|
|
|
|
@Autowired
|
|
private MessageService messageService;
|
|
|
|
@Autowired
|
|
private ConversationService conversationService;
|
|
|
|
@Autowired
|
|
private CozeApiCallService cozeApiCallService;
|
|
|
|
@Value("${emotion.coze.api.token:}")
|
|
private String cozeApiToken;
|
|
|
|
@Value("${emotion.coze.api.base-url:https://api.coze.cn}")
|
|
private String cozeBaseUrl;
|
|
|
|
@Value("${emotion.coze.api.chat.talk.bot-id:}")
|
|
private String chatBotId;
|
|
|
|
@Value("${emotion.coze.api.chat.talk.workflow-id:}")
|
|
private String chatWorkflowId;
|
|
|
|
@Value("${emotion.coze.api.chat.summary.bot-id:}")
|
|
private String summaryBotId;
|
|
|
|
@Value("${emotion.coze.api.chat.summary.workflow-id:}")
|
|
private String summaryWorkflowId;
|
|
|
|
@Value("${emotion.coze.api.timeout:30000}")
|
|
private int timeout;
|
|
|
|
@Value("${emotion.coze.api.retry-count:3}")
|
|
private int retryCount;
|
|
|
|
@Value("${emotion.coze.api.retry-delay:1000}")
|
|
private int retryDelay;
|
|
|
|
private static final String DEFAULT_USER_ID = "emotion-museum-user";
|
|
|
|
@Override
|
|
public String sendChatMessage(String conversationId, String message, String userId) {
|
|
log.info("发送聊天消息: conversationId={}, userId={}, message={}", conversationId, userId, message);
|
|
|
|
try {
|
|
// 调用Coze API
|
|
String aiReply = sendMessage(conversationId, message, userId);
|
|
|
|
// 保存用户消息
|
|
Message userMessage = messageService.createMessage(
|
|
conversationId,
|
|
userId,
|
|
message,
|
|
"text",
|
|
"user",
|
|
userId);
|
|
|
|
// 保存AI回复
|
|
Message aiMessage = messageService.createMessage(
|
|
conversationId,
|
|
"ai",
|
|
aiReply,
|
|
"text",
|
|
"ai",
|
|
"ai");
|
|
|
|
log.info("聊天消息处理完成: userMessageId={}, aiMessageId={}",
|
|
userMessage.getId(), aiMessage.getId());
|
|
|
|
return aiReply;
|
|
|
|
} catch (Exception e) {
|
|
log.error("发送聊天消息失败", e);
|
|
return "抱歉,我暂时无法回复,请稍后再试。";
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String generateConversationSummary(String conversationId, String userId) {
|
|
log.info("生成对话总结: conversationId={}, userId={}", conversationId, userId);
|
|
|
|
try {
|
|
// 获取对话历史
|
|
String conversationHistory = getConversationHistory(conversationId);
|
|
|
|
// 构建总结请求
|
|
String summaryPrompt = "请为以下对话生成一个简洁的总结:\n\n" + conversationHistory;
|
|
|
|
// 调用AI生成总结 - 使用专门的总结bot
|
|
String summary = sendSummaryMessage(conversationId, summaryPrompt, userId);
|
|
|
|
log.info("对话总结生成完成: conversationId={}", conversationId);
|
|
|
|
return summary;
|
|
|
|
} catch (Exception e) {
|
|
log.error("生成对话总结失败", e);
|
|
return "无法生成对话总结,请稍后再试。";
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean isServiceAvailable() {
|
|
try {
|
|
// 简单的健康检查
|
|
return cozeApiToken != null && !cozeApiToken.isEmpty() &&
|
|
chatBotId != null && !chatBotId.isEmpty();
|
|
} catch (Exception e) {
|
|
log.error("检查AI服务可用性失败", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getServiceStatus() {
|
|
if (isServiceAvailable()) {
|
|
return "available";
|
|
} else {
|
|
return "unavailable";
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String sendMessage(String conversationId, String userMessage, String userId) {
|
|
log.info("发送消息到Coze AI: conversationId={}, userId={}", conversationId, userId);
|
|
|
|
try {
|
|
// 构建请求头
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", "Bearer " + cozeApiToken);
|
|
headers.set("Content-Type", "application/json");
|
|
|
|
// 构建请求体 - 参考backend-distributed的实现
|
|
Map<String, Object> requestBody = buildCozeRequest(conversationId, userMessage, userId);
|
|
|
|
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
|
|
|
|
// 构建完整的API URL
|
|
String cozeApiUrl = cozeBaseUrl + "/api/message";
|
|
|
|
// 发送请求
|
|
ResponseEntity<String> response = restTemplate.exchange(
|
|
cozeApiUrl,
|
|
HttpMethod.POST,
|
|
request,
|
|
String.class);
|
|
|
|
// 解析响应
|
|
JSONObject responseJson = JSON.parseObject(response.getBody());
|
|
String aiReply = extractContentFromCozeResponse(responseJson);
|
|
|
|
log.info("Coze AI响应成功: reply={}", aiReply);
|
|
|
|
return aiReply;
|
|
|
|
} catch (Exception e) {
|
|
log.error("发送消息到Coze AI失败", e);
|
|
return "抱歉,AI服务暂时不可用,请稍后再试。";
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Object> guestChat(String message, String clientIp) {
|
|
log.info("访客聊天: message={}, clientIp={}", message, clientIp);
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
|
try {
|
|
// 生成访客会话ID
|
|
String guestConversationId = "guest_" + clientIp.replace(".", "_") + "_" + System.currentTimeMillis();
|
|
|
|
// 调用AI服务
|
|
String aiReply = sendMessage(guestConversationId, message, "guest");
|
|
|
|
// 保存访客消息
|
|
Message guestMessage = messageService.createMessage(
|
|
guestConversationId,
|
|
"guest",
|
|
message,
|
|
"text",
|
|
"guest",
|
|
clientIp);
|
|
|
|
// 保存AI回复
|
|
Message aiMessage = messageService.createMessage(
|
|
guestConversationId,
|
|
"ai",
|
|
aiReply,
|
|
"text",
|
|
"ai",
|
|
"ai");
|
|
|
|
result.put("message", aiReply);
|
|
result.put("messageId", aiMessage.getId());
|
|
result.put("timestamp", System.currentTimeMillis());
|
|
result.put("error", false);
|
|
|
|
log.info("访客聊天处理完成: guestMessageId={}, aiMessageId={}",
|
|
guestMessage.getId(), aiMessage.getId());
|
|
|
|
} catch (Exception e) {
|
|
log.error("访客聊天失败", e);
|
|
result.put("message", "抱歉,服务暂时不可用,请稍后再试。");
|
|
result.put("messageId", null);
|
|
result.put("timestamp", System.currentTimeMillis());
|
|
result.put("error", true);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Object> createConversation(String userId, String title) {
|
|
log.info("创建对话: userId={}, title={}", userId, title);
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
|
try {
|
|
// 创建数据库对话记录
|
|
String conversationId = UUID.randomUUID().toString();
|
|
|
|
// 调用数据库服务创建对话
|
|
Conversation conversation = conversationService.createConversation(userId, title, "user");
|
|
|
|
result.put("conversationId", conversation.getId());
|
|
result.put("title", title);
|
|
result.put("userId", userId);
|
|
result.put("createTime", System.currentTimeMillis());
|
|
result.put("success", true);
|
|
|
|
log.info("对话创建成功: conversationId={}", conversation.getId());
|
|
|
|
} catch (Exception e) {
|
|
log.error("创建对话失败", e);
|
|
result.put("success", false);
|
|
result.put("error", "创建对话失败");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public Map<String, Object> getGuestUserInfo(String clientIp) {
|
|
log.info("获取访客用户信息: clientIp={}", clientIp);
|
|
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
|
try {
|
|
// 生成访客用户信息
|
|
String guestId = "guest_" + clientIp.replace(".", "_");
|
|
String guestUsername = "访客_" + clientIp.substring(clientIp.lastIndexOf(".") + 1);
|
|
|
|
result.put("id", guestId);
|
|
result.put("username", guestUsername);
|
|
result.put("nickname", guestUsername);
|
|
result.put("type", "guest");
|
|
result.put("clientIp", clientIp);
|
|
result.put("createTime", System.currentTimeMillis());
|
|
|
|
log.info("访客用户信息获取成功: guestId={}", guestId);
|
|
|
|
} catch (Exception e) {
|
|
log.error("获取访客用户信息失败", e);
|
|
result.put("error", "获取用户信息失败");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public String streamChat(String conversationId, String message, String userId) {
|
|
log.info("流式聊天: conversationId={}, userId={}", conversationId, userId);
|
|
|
|
try {
|
|
// 构建流式请求
|
|
Map<String, Object> requestBody = buildCozeRequest(conversationId, message, userId);
|
|
requestBody.put("stream", true);
|
|
|
|
// 这里应该实现流式处理,暂时降级到普通聊天
|
|
return sendMessage(conversationId, message, userId);
|
|
|
|
} catch (Exception e) {
|
|
log.error("流式聊天失败", e);
|
|
return "抱歉,流式聊天暂时不可用,请稍后再试。";
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean healthCheck() {
|
|
try {
|
|
// 调用Coze bot信息接口检查健康状态
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", "Bearer " + cozeApiToken);
|
|
|
|
HttpEntity<String> request = new HttpEntity<>(headers);
|
|
|
|
ResponseEntity<String> response = restTemplate.exchange(
|
|
cozeBaseUrl + "/v1/bot/get_online_info?bot_id=" + chatBotId,
|
|
HttpMethod.GET,
|
|
request,
|
|
String.class);
|
|
|
|
JSONObject responseJson = JSON.parseObject(response.getBody());
|
|
return responseJson != null && responseJson.get("code") != null;
|
|
|
|
} catch (Exception e) {
|
|
log.error("健康检查失败: {}", e.getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 构建Coze API请求 - 参考backend-distributed的实现
|
|
*/
|
|
private Map<String, Object> buildCozeRequest(String conversationId, String userMessage, String userId) {
|
|
Map<String, Object> cozeRequest = new HashMap<>();
|
|
cozeRequest.put("bot_id", chatBotId);
|
|
|
|
// 如果有workflow_id,则添加
|
|
if (chatWorkflowId != null && !chatWorkflowId.trim().isEmpty()) {
|
|
cozeRequest.put("workflow_id", chatWorkflowId);
|
|
}
|
|
|
|
cozeRequest.put("user_id", userId != null ? userId : DEFAULT_USER_ID);
|
|
cozeRequest.put("stream", false);
|
|
|
|
// 构建消息内容
|
|
String message = userMessage;
|
|
if (conversationId != null && !conversationId.trim().isEmpty()) {
|
|
// 可以在这里添加上下文信息
|
|
message = "会话ID: " + conversationId + "\n\n用户消息: " + message;
|
|
}
|
|
|
|
// 添加聊天历史(简化版本)
|
|
java.util.List<Map<String, Object>> messages = new java.util.ArrayList<>();
|
|
|
|
// 添加当前消息
|
|
Map<String, Object> currentMsg = new HashMap<>();
|
|
currentMsg.put("role", "user");
|
|
currentMsg.put("content", message);
|
|
currentMsg.put("content_type", "text");
|
|
currentMsg.put("type", "question");
|
|
messages.add(currentMsg);
|
|
|
|
cozeRequest.put("additional_messages", messages);
|
|
cozeRequest.put("parameters", new HashMap<>());
|
|
|
|
return cozeRequest;
|
|
}
|
|
|
|
/**
|
|
* 从Coze响应中提取内容
|
|
*/
|
|
private String extractContentFromCozeResponse(JSONObject responseJson) {
|
|
try {
|
|
if (responseJson != null && responseJson.get("data") != null) {
|
|
JSONObject data = responseJson.getJSONObject("data");
|
|
|
|
// 根据Coze API响应格式解析内容
|
|
if (data.get("messages") != null) {
|
|
java.util.List<JSONObject> messages = data.getJSONArray("messages").toJavaList(JSONObject.class);
|
|
for (JSONObject message : messages) {
|
|
if ("assistant".equals(message.getString("role")) &&
|
|
"answer".equals(message.getString("type"))) {
|
|
return message.getString("content");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 兼容旧格式
|
|
if (data.getString("reply") != null) {
|
|
return data.getString("reply");
|
|
}
|
|
}
|
|
return "抱歉,我现在无法理解您的消息。";
|
|
} catch (Exception e) {
|
|
log.error("解析Coze响应失败: {}", e.getMessage());
|
|
return "抱歉,响应解析出现问题。";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 发送总结消息到Coze AI
|
|
*/
|
|
private String sendSummaryMessage(String conversationId, String userMessage, String userId) {
|
|
log.info("发送总结消息到Coze AI: conversationId={}, userId={}", conversationId, userId);
|
|
|
|
try {
|
|
// 构建请求头
|
|
HttpHeaders headers = new HttpHeaders();
|
|
headers.set("Authorization", "Bearer " + cozeApiToken);
|
|
headers.set("Content-Type", "application/json");
|
|
|
|
// 构建请求体 - 使用总结专用的bot和workflow
|
|
Map<String, Object> requestBody = buildSummaryRequest(conversationId, userMessage, userId);
|
|
|
|
HttpEntity<Map<String, Object>> request = new HttpEntity<>(requestBody, headers);
|
|
|
|
// 构建完整的API URL
|
|
String cozeApiUrl = cozeBaseUrl + "/api/message";
|
|
|
|
// 发送请求
|
|
ResponseEntity<String> response = restTemplate.exchange(
|
|
cozeApiUrl,
|
|
HttpMethod.POST,
|
|
request,
|
|
String.class);
|
|
|
|
// 解析响应
|
|
JSONObject responseJson = JSON.parseObject(response.getBody());
|
|
String aiReply = extractContentFromCozeResponse(responseJson);
|
|
|
|
log.info("Coze AI总结响应成功: reply={}", aiReply);
|
|
|
|
return aiReply;
|
|
|
|
} catch (Exception e) {
|
|
log.error("发送总结消息到Coze AI失败", e);
|
|
return "抱歉,AI总结服务暂时不可用,请稍后再试。";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 构建总结请求 - 使用专门的总结bot和workflow
|
|
*/
|
|
private Map<String, Object> buildSummaryRequest(String conversationId, String userMessage, String userId) {
|
|
Map<String, Object> cozeRequest = new HashMap<>();
|
|
cozeRequest.put("bot_id", summaryBotId);
|
|
|
|
// 如果有总结workflow_id,则添加
|
|
if (summaryWorkflowId != null && !summaryWorkflowId.trim().isEmpty()) {
|
|
cozeRequest.put("workflow_id", summaryWorkflowId);
|
|
}
|
|
|
|
cozeRequest.put("user_id", userId != null ? userId : DEFAULT_USER_ID);
|
|
cozeRequest.put("stream", false);
|
|
|
|
// 构建消息内容
|
|
String message = userMessage;
|
|
if (conversationId != null && !conversationId.trim().isEmpty()) {
|
|
// 可以在这里添加上下文信息
|
|
message = "会话ID: " + conversationId + "\n\n总结内容: " + message;
|
|
}
|
|
|
|
// 添加聊天历史(简化版本)
|
|
java.util.List<Map<String, Object>> messages = new java.util.ArrayList<>();
|
|
|
|
// 添加当前消息
|
|
Map<String, Object> currentMsg = new HashMap<>();
|
|
currentMsg.put("role", "user");
|
|
currentMsg.put("content", message);
|
|
currentMsg.put("content_type", "text");
|
|
currentMsg.put("type", "question");
|
|
messages.add(currentMsg);
|
|
|
|
cozeRequest.put("additional_messages", messages);
|
|
cozeRequest.put("parameters", new HashMap<>());
|
|
|
|
return cozeRequest;
|
|
}
|
|
|
|
/**
|
|
* 获取对话历史
|
|
*/
|
|
private String getConversationHistory(String conversationId) {
|
|
try {
|
|
// 这里应该从数据库获取对话历史
|
|
// 暂时返回空字符串
|
|
return "";
|
|
} catch (Exception e) {
|
|
log.error("获取对话历史失败", e);
|
|
return "";
|
|
}
|
|
}
|
|
} |