AI对话bug修复
This commit is contained in:
@@ -86,6 +86,11 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
private static final String USAGE_SCENARIO_SUMMARY = "summary";
|
private static final String USAGE_SCENARIO_SUMMARY = "summary";
|
||||||
private static final String DEFAULT_ENVIRONMENT = "production";
|
private static final String DEFAULT_ENVIRONMENT = "production";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coze工作流配置键 - 聊天场景
|
||||||
|
*/
|
||||||
|
private static final String COZE_CHAT_CONFIG_KEY = "coze.chat.default";
|
||||||
|
|
||||||
// API 相关常量
|
// API 相关常量
|
||||||
private static final String CONTENT_KEY = "content";
|
private static final String CONTENT_KEY = "content";
|
||||||
private static final String ROLE_KEY = "role";
|
private static final String ROLE_KEY = "role";
|
||||||
@@ -744,23 +749,98 @@ public class AiChatServiceImpl implements AiChatService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送消息到Coze AI(带messageId)
|
* 发送消息到Coze AI(带messageId)
|
||||||
|
* 使用通用工作流调用方式,通过配置键获取AI配置
|
||||||
|
*
|
||||||
|
* @param conversationId 会话ID
|
||||||
|
* @param messageId 消息ID
|
||||||
|
* @param userMessage 用户消息
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return AI回复内容
|
||||||
*/
|
*/
|
||||||
private String sendMessageWithMessageId(String conversationId, String messageId, String userMessage,
|
private String sendMessageWithMessageId(String conversationId, String messageId, String userMessage,
|
||||||
String userId) {
|
String userId) {
|
||||||
log.info("发送消息到Coze AI: conversationId={}, messageId={}, userId={}", conversationId, messageId, userId);
|
log.info("发送消息到Coze AI: conversationId={}, messageId={}, userId={}", conversationId, messageId, userId);
|
||||||
|
|
||||||
// 创建API调用记录(包含messageId)
|
// 1. 获取AI配置
|
||||||
CozeApiCall apiCall = createApiCallRecord(conversationId, messageId, userMessage, userId, "chat");
|
AiConfig config = aiConfigService.getByConfigKey(COZE_CHAT_CONFIG_KEY);
|
||||||
|
if (config == null) {
|
||||||
|
log.error("未找到聊天场景的AI配置或配置已禁用: configKey={}", COZE_CHAT_CONFIG_KEY);
|
||||||
|
return "抱歉,AI服务暂时不可用,请稍后再试。";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 创建API调用记录(包含conversationId和messageId)
|
||||||
|
CozeApiCall apiCall = createChatWorkflowApiCallRecord(config, conversationId, messageId, userMessage, userId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return executeCozeApiCall(apiCall, conversationId, userMessage, userId);
|
// 3. 构建工作流请求参数
|
||||||
|
Map<String, Object> parameters = new HashMap<>();
|
||||||
|
parameters.put("input", userMessage);
|
||||||
|
parameters.put("user_id", userId);
|
||||||
|
|
||||||
|
// 4. 构建工作流请求体
|
||||||
|
Map<String, Object> requestBody = buildWorkflowRequest(config, parameters, userId);
|
||||||
|
|
||||||
|
// 5. 执行工作流调用(带API调用记录)
|
||||||
|
return executeWorkflowCallWithRecord(config, requestBody, COZE_CHAT_CONFIG_KEY, userId, apiCall);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("发送消息失败", e);
|
log.error("发送消息失败: conversationId={}, messageId={}, error={}",
|
||||||
|
conversationId, messageId, e.getMessage(), e);
|
||||||
updateApiCallFailure(apiCall, e.getMessage());
|
updateApiCallFailure(apiCall, e.getMessage());
|
||||||
return "抱歉,AI服务暂时不可用,请稍后再试。";
|
return "抱歉,AI服务暂时不可用,请稍后再试。";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建聊天工作流API调用记录(包含conversationId和messageId)
|
||||||
|
*
|
||||||
|
* @param config AI配置
|
||||||
|
* @param conversationId 会话ID
|
||||||
|
* @param messageId 消息ID
|
||||||
|
* @param userMessage 用户消息
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return API调用记录
|
||||||
|
*/
|
||||||
|
private CozeApiCall createChatWorkflowApiCallRecord(AiConfig config, String conversationId,
|
||||||
|
String messageId, String userMessage, String userId) {
|
||||||
|
CozeApiCall apiCall = CozeApiCall.builder()
|
||||||
|
.id(snowflakeIdGenerator.nextIdAsString())
|
||||||
|
.conversationId(conversationId)
|
||||||
|
.messageId(messageId)
|
||||||
|
.workflowId(config.getWorkflowId())
|
||||||
|
.botId(config.getBotId())
|
||||||
|
.userId(userId)
|
||||||
|
.requestType("workflow")
|
||||||
|
.userMessage(userMessage)
|
||||||
|
.userMessageType("text")
|
||||||
|
.status("pending")
|
||||||
|
.startTime(LocalDateTime.now())
|
||||||
|
.traceId(UUID.randomUUID().toString().replace("-", ""))
|
||||||
|
.metadata(JSON.toJSONString(Map.of("configKey", COZE_CHAT_CONFIG_KEY)))
|
||||||
|
.createBy(userId)
|
||||||
|
.updateBy(userId)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 获取客户端信息
|
||||||
|
try {
|
||||||
|
HttpServletRequest request = getCurrentRequest();
|
||||||
|
if (request != null) {
|
||||||
|
apiCall.setClientIp(getClientIp(request));
|
||||||
|
apiCall.setUserAgent(request.getHeader("User-Agent"));
|
||||||
|
apiCall.setSessionId(request.getSession().getId());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("获取客户端信息失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存API调用记录
|
||||||
|
cozeApiCallService.save(apiCall);
|
||||||
|
log.info("创建聊天工作流API调用记录: id={}, conversationId={}, messageId={}, workflowId={}, traceId={}",
|
||||||
|
apiCall.getId(), conversationId, messageId, config.getWorkflowId(), apiCall.getTraceId());
|
||||||
|
|
||||||
|
return apiCall;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行Coze API调用的公共逻辑
|
* 执行Coze API调用的公共逻辑
|
||||||
*/
|
*/
|
||||||
|
|||||||
+69
-18
@@ -59,6 +59,9 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 已处理的消息ID集合 - 用于防止重复处理
|
||||||
|
const processedMessageIds = ref<Set<string>>(new Set())
|
||||||
|
|
||||||
// 处理消息队列 - 批量处理消息以避免竞态条件
|
// 处理消息队列 - 批量处理消息以避免竞态条件
|
||||||
const processMessageQueue = async () => {
|
const processMessageQueue = async () => {
|
||||||
if (isProcessingQueue.value || messageQueue.value.length === 0) {
|
if (isProcessingQueue.value || messageQueue.value.length === 0) {
|
||||||
@@ -70,11 +73,24 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
// 一次性取出所有待处理消息
|
// 一次性取出所有待处理消息
|
||||||
const batch = messageQueue.value.splice(0, messageQueue.value.length)
|
const batch = messageQueue.value.splice(0, messageQueue.value.length)
|
||||||
|
|
||||||
// 批量添加消息到数组
|
// 过滤掉已存在的消息(基于ID去重)
|
||||||
const newMessages = batch.map(item => item.message)
|
const newMessages = batch
|
||||||
messages.value.push(...newMessages)
|
.map(item => item.message)
|
||||||
|
.filter(msg => {
|
||||||
|
// 检查是否已存在于当前消息列表中
|
||||||
|
const exists = messages.value.some(m => m.id === msg.id)
|
||||||
|
if (exists) {
|
||||||
|
console.log(`⚠️ 消息已存在,跳过: ${msg.id}`)
|
||||||
|
}
|
||||||
|
return !exists
|
||||||
|
})
|
||||||
|
|
||||||
|
if (newMessages.length > 0) {
|
||||||
|
messages.value.push(...newMessages)
|
||||||
console.log(`✅ 消息队列处理完成,添加了 ${newMessages.length} 条消息,当前总数: ${messages.value.length}`)
|
console.log(`✅ 消息队列处理完成,添加了 ${newMessages.length} 条消息,当前总数: ${messages.value.length}`)
|
||||||
|
} else {
|
||||||
|
console.log(`⏭️ 队列中的消息都已存在,无需添加`)
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isProcessingQueue.value = false
|
isProcessingQueue.value = false
|
||||||
}
|
}
|
||||||
@@ -82,6 +98,15 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
|
|
||||||
// 将消息加入队列而不是直接添加
|
// 将消息加入队列而不是直接添加
|
||||||
const queueMessage = (message: ChatMessage) => {
|
const queueMessage = (message: ChatMessage) => {
|
||||||
|
// 先检查是否已处理过该消息ID
|
||||||
|
if (processedMessageIds.value.has(message.id)) {
|
||||||
|
console.log(`⚠️ 消息已处理过,跳过队列: ${message.id}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记消息ID为已处理
|
||||||
|
processedMessageIds.value.add(message.id)
|
||||||
|
|
||||||
messageQueue.value.push({
|
messageQueue.value.push({
|
||||||
message,
|
message,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
@@ -90,11 +115,11 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加消息 - 现在返回消息但不直接添加到数组
|
// 添加消息 - 现在返回消息但不直接添加到数组
|
||||||
const addMessage = (message: Omit<ChatMessage, 'id' | 'timestamp'>) => {
|
const addMessage = (message: Omit<ChatMessage, 'id' | 'timestamp'> & { id?: string, timestamp?: string }) => {
|
||||||
const newMessage: ChatMessage = {
|
const newMessage: ChatMessage = {
|
||||||
...message,
|
...message,
|
||||||
id: Date.now().toString(),
|
id: message.id || Date.now().toString(), // 优先使用传入的ID
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: message.timestamp || new Date().toISOString(), // 优先使用传入的时间戳
|
||||||
status: message.type === 'user' ? 'sending' : 'sent'
|
status: message.type === 'user' ? 'sending' : 'sent'
|
||||||
}
|
}
|
||||||
// 将消息加入队列而不是直接添加
|
// 将消息加入队列而不是直接添加
|
||||||
@@ -228,12 +253,17 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
// 如果需要过滤特定会话的消息,可以在这里添加过滤逻辑
|
// 如果需要过滤特定会话的消息,可以在这里添加过滤逻辑
|
||||||
// const sessionMessages = sortedMessages.filter(msg => msg.sessionId === sessionId)
|
// const sessionMessages = sortedMessages.filter(msg => msg.sessionId === sessionId)
|
||||||
|
|
||||||
|
// 清空并重新设置已处理消息ID集合
|
||||||
|
processedMessageIds.value.clear()
|
||||||
|
sortedMessages.forEach(msg => processedMessageIds.value.add(msg.id))
|
||||||
|
|
||||||
messages.value = sortedMessages
|
messages.value = sortedMessages
|
||||||
console.log('📨 会话消息加载完成,消息数量:', messages.value.length)
|
console.log('📨 会话消息加载完成,消息数量:', messages.value.length, '已处理ID数量:', processedMessageIds.value.size)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 加载会话消息失败:', error)
|
console.error('❌ 加载会话消息失败:', error)
|
||||||
messages.value = []
|
messages.value = []
|
||||||
|
processedMessageIds.value.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +291,9 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
// 清空消息
|
// 清空消息
|
||||||
const clearMessages = () => {
|
const clearMessages = () => {
|
||||||
messages.value = []
|
messages.value = []
|
||||||
|
// 同时清空已处理消息ID集合,避免后续加载时被错误过滤
|
||||||
|
processedMessageIds.value.clear()
|
||||||
|
console.log('✅ 消息和已处理ID集合已清空')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索消息:支持本地搜索和远程搜索
|
// 搜索消息:支持本地搜索和远程搜索
|
||||||
@@ -390,31 +423,47 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
// 使用优化的排序和去重方法
|
// 使用优化的排序和去重方法
|
||||||
const sortedMessages = MessageService.sortAndDeduplicateMessages(chatMessages)
|
const sortedMessages = MessageService.sortAndDeduplicateMessages(chatMessages)
|
||||||
|
|
||||||
|
// 清空并重新设置已处理消息ID集合
|
||||||
|
processedMessageIds.value.clear()
|
||||||
|
sortedMessages.forEach(msg => processedMessageIds.value.add(msg.id))
|
||||||
|
|
||||||
// 直接设置消息数组(初始加载时不使用队列)
|
// 直接设置消息数组(初始加载时不使用队列)
|
||||||
messages.value = sortedMessages
|
messages.value = sortedMessages
|
||||||
console.log('📝 最近消息已加载并排序,消息总数:', messages.value.length)
|
console.log('📝 最近消息已加载并排序,消息总数:', messages.value.length, '已处理ID数量:', processedMessageIds.value.size)
|
||||||
|
|
||||||
return chatMessages
|
return chatMessages
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 加载最近消息失败:', error)
|
console.error('❌ 加载最近消息失败:', error)
|
||||||
messages.value = []
|
messages.value = []
|
||||||
|
processedMessageIds.value.clear()
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加AI回复消息(使用队列处理)
|
// 添加AI回复消息(使用队列处理)
|
||||||
const addAiReplyMessages = async (content: string) => {
|
const addAiReplyMessages = async (wsMessage: WebSocketMessage) => {
|
||||||
// 停止输入状态
|
// 停止输入状态
|
||||||
isTyping.value = false
|
isTyping.value = false
|
||||||
|
|
||||||
// 添加AI消息到队列
|
// 使用后端提供的messageId,确保去重能正常工作
|
||||||
|
const messageId = wsMessage.messageId || Date.now().toString()
|
||||||
|
|
||||||
|
// 检查是否已处理过该消息(基于后端messageId去重)
|
||||||
|
if (processedMessageIds.value.has(messageId)) {
|
||||||
|
console.log(`⚠️ AI消息已处理过,跳过: ${messageId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加AI消息到队列,使用后端的messageId
|
||||||
const aiMessage = addMessage({
|
const aiMessage = addMessage({
|
||||||
content: content.trim(),
|
id: messageId,
|
||||||
|
content: wsMessage.content.trim(),
|
||||||
type: 'ai',
|
type: 'ai',
|
||||||
conversationId: currentSession.value?.id
|
conversationId: wsMessage.conversationId || currentSession.value?.id,
|
||||||
|
timestamp: wsMessage.createTime || new Date().toISOString()
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('✅ AI消息已加入队列,消息ID:', aiMessage.id)
|
console.log('✅ AI消息已加入队列,消息ID:', aiMessage.id, '(后端ID:', wsMessage.messageId, ')')
|
||||||
|
|
||||||
// 立即处理队列
|
// 立即处理队列
|
||||||
await processMessageQueue()
|
await processMessageQueue()
|
||||||
@@ -422,13 +471,13 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
|
|
||||||
// WebSocket消息处理 - 使用队列处理所有消息
|
// WebSocket消息处理 - 使用队列处理所有消息
|
||||||
let handleWebSocketMessage = async (wsMessage: WebSocketMessage) => {
|
let handleWebSocketMessage = async (wsMessage: WebSocketMessage) => {
|
||||||
console.log('收到WebSocket消息:', wsMessage.type, wsMessage.senderType)
|
console.log('收到WebSocket消息:', wsMessage.type, wsMessage.senderType, '消息ID:', wsMessage.messageId)
|
||||||
|
|
||||||
switch (wsMessage.type) {
|
switch (wsMessage.type) {
|
||||||
case 'TEXT':
|
case 'TEXT':
|
||||||
if (wsMessage.senderType === 'AI') {
|
if (wsMessage.senderType === 'AI') {
|
||||||
// AI回复消息 - 加入队列处理
|
// AI回复消息 - 传递完整的wsMessage以获取后端messageId
|
||||||
await addAiReplyMessages(wsMessage.content)
|
await addAiReplyMessages(wsMessage)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -443,8 +492,9 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case 'ERROR':
|
case 'ERROR':
|
||||||
// 错误消息 - 加入队列处理
|
// 错误消息 - 加入队列处理,使用后端messageId
|
||||||
addMessage({
|
addMessage({
|
||||||
|
id: wsMessage.messageId || Date.now().toString(),
|
||||||
content: wsMessage.content,
|
content: wsMessage.content,
|
||||||
type: 'ai',
|
type: 'ai',
|
||||||
sessionId: currentSession.value?.id
|
sessionId: currentSession.value?.id
|
||||||
@@ -453,8 +503,9 @@ export const useChatStore = defineStore('chat', () => {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case 'SYSTEM':
|
case 'SYSTEM':
|
||||||
// 系统消息 - 加入队列处理
|
// 系统消息 - 加入队列处理,使用后端messageId
|
||||||
addMessage({
|
addMessage({
|
||||||
|
id: wsMessage.messageId || Date.now().toString(),
|
||||||
content: wsMessage.content,
|
content: wsMessage.content,
|
||||||
type: 'ai',
|
type: 'ai',
|
||||||
sessionId: currentSession.value?.id
|
sessionId: currentSession.value?.id
|
||||||
|
|||||||
Reference in New Issue
Block a user