Files
leaudit-platform-frontend/app/hooks/use-chat-message.ts
T
TanWenyan 5cff5f2a5d 添加对话记录保存详细日志,诊断conversation_id传递问题
添加日志位置:
1. chat/index.tsx: 发送消息、接收会话ID变更、初始化读取localStorage
2. use-chat-message.ts: 发送消息、接收新会话ID、处理新会话
3. use-conversation.ts: setCurrConversationId保存到localStorage

帮助诊断为什么对话记录没有固定(每次都创建新会话)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 15:06:24 +08:00

627 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useCallback, useRef } from 'react';
import { produce } from 'immer';
import { useBoolean, useGetState } from 'ahooks';
import { sendChatMessage, updateFeedback, generateConversationName } from '../services/api.client';
import type { ChatItem, Feedbacktype, ThoughtItem, VisionFile, MessageEnd, MessageReplace } from '../types/dify_chat';
import { CHAT_CONFIG } from '../config/chat';
/**
* 聊天消息处理钩子
*/
export default function useChatMessage({
onUpdateConversationList,
onConversationIdChange,
}: {
onUpdateConversationList?: (conversationId: string, data: { name: string }) => void;
onConversationIdChange?: (conversationId: string) => void;
}) {
// 聊天消息列表
const [chatList, setChatList, getChatList] = useGetState<ChatItem[]>([]);
// 是否正在响应中
const [isResponding, { setTrue: setResponding, setFalse: setNotResponding }] = useBoolean(false);
// 是否已停止响应
const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false);
// 响应的会话是否为当前会话
const [isRespondingConIsCurrCon, setIsRespondingConCurrCon, getIsRespondingConIsCurrCon] = useGetState(true);
// 消息任务ID
const [messageTaskId, setMessageTaskId] = useState('');
// 用户查询
const [userQuery, setUserQuery] = useState('');
// 中止控制器
const abortControllerRef = useRef<AbortController | null>(null);
// 当前流式响应项的引用
const currentResponseRef = useRef<ChatItem | null>(null);
/**
* 记录错误日志
*/
const logError = useCallback((message: string) => {
console.error(`[Chat Error]: ${message}`);
}, []);
/**
* 检查是否可以发送消息
*/
const checkCanSend = useCallback(() => {
if (isResponding) {
console.warn('机器人正在回复中,请稍后再试');
return false;
}
return true;
}, [isResponding]);
/**
* 立即更新聊天列表 - 移除防抖机制
*/
const updateChatList = useCallback((
responseItem: ChatItem,
questionId: string,
placeholderAnswerId: string,
questionItem: ChatItem,
originalResponseId?: string
) => {
// console.log('🔄 [useChatMessage] 更新聊天列表:', {
// responseItemId: responseItem.id,
// responseContentLength: responseItem.content.length,
// responsePreview: responseItem.content.substring(0, 50) + (responseItem.content.length > 50 ? '...' : ''),
// originalResponseId,
// questionId,
// placeholderAnswerId
// });
setChatList(produce(getChatList(), (draft) => {
// console.log('📝 [useChatMessage] 当前聊天列表:', draft.map(item => ({
// id: item.id,
// contentLength: item.content.length,
// contentPreview: item.content.substring(0, 20) + (item.content.length > 20 ? '...' : ''),
// isAnswer: item.isAnswer
// })));
// 移除占位符
const placeholderIndex = draft.findIndex(item => item.id === placeholderAnswerId);
if (placeholderIndex !== -1) {
// console.log('🗑️ [useChatMessage] 移除占位符:', placeholderAnswerId, 'at index:', placeholderIndex);
draft.splice(placeholderIndex, 1);
}
// 确保问题存在
const questionIndex = draft.findIndex(item => item.id === questionId);
if (questionIndex === -1) {
console.log(' [useChatMessage] 添加问题:', questionId);
draft.push({ ...questionItem });
}
// 更新或添加响应 - 考虑ID可能已经改变的情况
let responseIndex = draft.findIndex(item => item.id === responseItem.id);
// console.log('🔍 [useChatMessage] 查找响应索引 (当前ID):', { responseItemId: responseItem.id, responseIndex });
// 如果找不到当前ID的响应,尝试查找原始ID
if (responseIndex === -1 && originalResponseId) {
responseIndex = draft.findIndex(item => item.id === originalResponseId);
// console.log('🔍 [useChatMessage] 查找响应索引 (原始ID):', { originalResponseId, responseIndex });
}
// 如果找不到任何匹配的响应,查找最后一个AI回答
if (responseIndex === -1) {
responseIndex = draft.findIndex((item, index) =>
item.isAnswer &&
index > draft.findIndex(q => q.id === questionId)
);
// console.log('🔍 [useChatMessage] 查找响应索引 (最后AI回答):', { responseIndex });
}
if (responseIndex !== -1) {
// console.log('✏️ [useChatMessage] 更新现有响应:', {
// responseIndex,
// oldContentLength: draft[responseIndex].content.length,
// newContentLength: responseItem.content.length
// });
draft[responseIndex] = { ...responseItem };
} else {
// console.log(' [useChatMessage] 添加新响应:', {
// responseId: responseItem.id,
// contentLength: responseItem.content.length
// });
draft.push({ ...responseItem });
}
// console.log('📝 [useChatMessage] 更新后聊天列表:', draft.map(item => ({
// id: item.id,
// contentLength: item.content.length,
// contentPreview: item.content.substring(0, 20) + (item.content.length > 20 ? '...' : ''),
// isAnswer: item.isAnswer
// })));
}));
}, [getChatList, setChatList]);
/**
* 更新当前问答 - 移除防抖,立即更新
*/
const updateCurrentQA = useCallback(({
responseItem,
questionId,
placeholderAnswerId,
questionItem,
originalResponseId,
}: {
responseItem: ChatItem;
questionId: string;
placeholderAnswerId: string;
questionItem: ChatItem;
originalResponseId?: string;
}) => {
// 更新当前响应引用
currentResponseRef.current = responseItem;
// 立即更新,不使用防抖
updateChatList(responseItem, questionId, placeholderAnswerId, questionItem, originalResponseId);
}, [updateChatList]);
/**
* 转换文件格式为服务器格式
*/
const transformToServerFile = useCallback((fileItem: any) => {
return {
type: 'image',
transfer_method: fileItem.transferMethod || fileItem.transfer_method,
url: fileItem.url,
upload_file_id: fileItem.id || fileItem.upload_file_id,
};
}, []);
/**
* 发送消息
*/
const handleSend = useCallback(async (
message: string,
conversationId: string | null,
files?: VisionFile[],
inputs?: Record<string, any>,
) => {
if (!checkCanSend() || !message.trim()) {
return;
}
console.log('📤 [useChatMessage] 发送消息:', {
messageLength: message.length,
messagePreview: message.substring(0, 50) + (message.length > 50 ? '...' : ''),
conversationId,
isNewConversation: conversationId === null || conversationId === '-1'
});
setUserQuery(message);
setResponding();
setHasStopResponded(false);
setIsRespondingConCurrCon(true);
// 处理输入参数
const toServerInputs: Record<string, any> = {};
if (inputs) {
Object.keys(inputs).forEach((key) => {
const value = inputs[key];
if (value?.supportFileType) {
toServerInputs[key] = transformToServerFile(value);
} else if (Array.isArray(value) && value[0]?.supportFileType) {
toServerInputs[key] = value.map((item: any) => transformToServerFile(item));
} else {
toServerInputs[key] = value;
}
});
}
// 准备请求数据
const data: Record<string, any> = {
inputs: toServerInputs,
query: message,
conversation_id: conversationId === '-1' ? null : conversationId,
};
// 添加文件数据
if (files && files.length > 0) {
data.files = files.map((item) => {
if (item.transfer_method === 'local_file') {
return {
...item,
url: '',
};
}
return item;
});
}
// 生成唯一ID
const questionId = `question-${Date.now()}`;
const placeholderAnswerId = `answer-placeholder-${Date.now()}`;
// 创建问题项
const questionItem: ChatItem = {
id: questionId,
content: message,
isAnswer: false,
message_files: files,
};
// 创建答案占位符
const placeholderAnswerItem: ChatItem = {
id: placeholderAnswerId,
content: '',
isAnswer: true,
};
// 更新聊天列表
const newList = [...getChatList(), questionItem, placeholderAnswerItem];
setChatList(newList);
let isAgentMode = false;
let hasSetResponseId = false;
const prevTempNewConversationId = conversationId || '-1';
let tempNewConversationId = '';
// 创建响应项
const responseItem: ChatItem = {
id: `response-${Date.now()}`,
content: '',
agent_thoughts: [],
message_files: [],
isAnswer: true,
};
// 保存原始响应ID
const originalResponseId = responseItem.id;
try {
// 发送消息
await sendChatMessage(data, {
onData: (message: string, isFirstMessage: boolean, { conversationId: newConversationId, messageId, taskId }) => {
// console.log('📨 [useChatMessage] 收到流式数据:', {
// messageLength: message.length,
// message: message.substring(0, 100) + (message.length > 100 ? '...' : ''),
// isFirstMessage,
// messageId,
// newConversationId,
// taskId,
// isAgentMode,
// currentContentLength: responseItem.content.length
// });
if (!isAgentMode) {
// 累积消息内容
const oldContent = responseItem.content;
responseItem.content = responseItem.content + message;
// console.log('📝 [useChatMessage] 累积消息内容:', {
// oldLength: oldContent.length,
// newLength: responseItem.content.length,
// addedLength: message.length,
// preview: responseItem.content.substring(0, 50) + '...'
// });
} else {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1];
if (lastThought) {
lastThought.thought = (lastThought.thought || '') + message;
console.log('🤔 [useChatMessage] 累积思考内容:', {
thoughtLength: lastThought.thought.length,
addedLength: message.length
});
}
}
if (messageId && !hasSetResponseId) {
responseItem.id = messageId;
hasSetResponseId = true;
// console.log('🆔 设置响应ID:', { oldId: originalResponseId, newId: messageId });
}
// 重要:确保正确获取新会话ID
if (newConversationId && !tempNewConversationId) {
tempNewConversationId = newConversationId;
console.log('🆔 [useChatMessage] 首次获取到新会话ID:', {
newConversationId: tempNewConversationId,
originalConversationId: conversationId
});
}
setMessageTaskId(taskId || '');
// 检查是否切换到其他会话
// console.log('🔍 会话检查:', {
// prevTempNewConversationId,
// conversationId,
// isEqual: prevTempNewConversationId === conversationId
// });
// 修复新会话的匹配逻辑
const isNewConversationMatch = (prevTempNewConversationId === '-1' && conversationId === null) ||
(prevTempNewConversationId === conversationId);
if (!isNewConversationMatch) {
// console.log('⚠️ 会话不匹配,跳过更新');
setIsRespondingConCurrCon(false);
return;
}
// console.log('🔄 准备调用updateCurrentQA:', {
// responseItemId: responseItem.id,
// responseContent: responseItem.content,
// questionId,
// placeholderAnswerId,
// originalResponseId
// });
// console.log('🔄 [useChatMessage] 准备调用updateCurrentQA:', {
// responseItemId: responseItem.id,
// responseContentLength: responseItem.content.length,
// responsePreview: responseItem.content.substring(0, 100) + (responseItem.content.length > 100 ? '...' : ''),
// questionId,
// placeholderAnswerId,
// originalResponseId,
// isAgentMode,
// agentThoughtsCount: responseItem.agent_thoughts?.length || 0
// });
// 更新当前问答(使用防抖)
updateCurrentQA({
responseItem: { ...responseItem }, // 创建副本避免引用问题
questionId,
placeholderAnswerId,
questionItem,
originalResponseId,
});
},
onCompleted: async (hasError?: boolean) => {
// console.log('✅ 消息发送完成:', { hasError });
// 立即更新最终状态
if (currentResponseRef.current) {
updateCurrentQA({
responseItem: { ...currentResponseRef.current },
questionId,
placeholderAnswerId,
questionItem,
originalResponseId,
});
}
if (hasError) {
setNotResponding();
return;
}
// 如果是新会话,处理会话ID更新和名称生成
// 检查原始传入的conversationId是否为新会话标识
const isNewConversation = conversationId === '-1' || conversationId === null;
console.log('🔍 [useChatMessage] 检查是否需要处理新会话:', {
tempNewConversationId,
originalConversationId: conversationId,
isNewConversation,
willProcess: !!(tempNewConversationId && isNewConversation)
});
if (tempNewConversationId && isNewConversation) {
try {
console.log('🆕 [useChatMessage] 处理新会话,调用onConversationIdChange:', tempNewConversationId);
// 通知会话ID变更(这会触发localStorage更新)
onConversationIdChange?.(tempNewConversationId);
// 生成会话名称
const res = await generateConversationName(tempNewConversationId);
const { data } = res as any;
if (data?.name) {
console.log('📝 [useChatMessage] 生成会话名称:', data.name);
onUpdateConversationList?.(tempNewConversationId, { name: data.name });
}
} catch (err) {
console.error('❌ [useChatMessage] 生成会话名称失败:', err);
}
}
setNotResponding();
setUserQuery('');
},
onFile: (file) => {
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1];
if (lastThought) {
lastThought.message_files = [...(lastThought.message_files || []), { ...file }];
} else {
if (!responseItem.message_files) {
responseItem.message_files = [];
}
responseItem.message_files.push(file);
}
updateCurrentQA({
responseItem: { ...responseItem },
questionId,
placeholderAnswerId,
questionItem,
originalResponseId,
});
},
onThought: (thought) => {
isAgentMode = true;
if (thought.message_id && !hasSetResponseId) {
responseItem.id = thought.message_id;
hasSetResponseId = true;
}
if (!responseItem.agent_thoughts) {
responseItem.agent_thoughts = [];
}
if (responseItem.agent_thoughts.length === 0) {
responseItem.agent_thoughts.push(thought);
} else {
const lastThought = responseItem.agent_thoughts[responseItem.agent_thoughts.length - 1];
// 相同思考ID,更新现有思考
if (lastThought.id === thought.id) {
thought.thought = lastThought.thought;
thought.message_files = lastThought.message_files;
responseItem.agent_thoughts[responseItem.agent_thoughts.length - 1] = thought;
} else {
responseItem.agent_thoughts.push(thought);
}
}
// 检查是否切换到其他会话
if (prevTempNewConversationId !== conversationId) {
setIsRespondingConCurrCon(false);
return;
}
updateCurrentQA({
responseItem: { ...responseItem },
questionId,
placeholderAnswerId,
questionItem,
originalResponseId,
});
},
onMessageEnd: (messageEnd: MessageEnd) => {
// 处理消息结束事件
// console.log('Message ended:', messageEnd);
},
onMessageReplace: (messageReplace: MessageReplace) => {
// 处理消息替换事件
responseItem.content = messageReplace.answer;
updateCurrentQA({
responseItem: { ...responseItem },
questionId,
placeholderAnswerId,
questionItem,
originalResponseId,
});
},
onError: (error) => {
logError(`聊天消息请求错误: ${error}`);
setChatList(produce(getChatList(), (draft) => {
const placeholderIndex = draft.findIndex(item => item.id === placeholderAnswerId);
if (placeholderIndex !== -1) {
draft[placeholderIndex].content = `错误: ${error}`;
draft[placeholderIndex].isError = true;
}
}));
setNotResponding();
},
getAbortController: (controller) => {
abortControllerRef.current = controller;
},
});
} catch (err: any) {
logError(`发送消息时出错: ${err.message}`);
setNotResponding();
}
}, [
checkCanSend,
getChatList,
setChatList,
logError,
onUpdateConversationList,
onConversationIdChange,
setNotResponding,
setResponding,
updateCurrentQA,
transformToServerFile,
setHasStopResponded,
setIsRespondingConCurrCon,
setMessageTaskId,
setUserQuery
]);
/**
* 处理反馈
*/
const handleFeedback = useCallback(async (messageId: string, feedback: Feedbacktype) => {
try {
await updateFeedback({
url: `messages/${messageId}/feedbacks`,
body: feedback,
});
// 更新聊天列表中的反馈
setChatList(produce(getChatList(), (draft) => {
const messageIndex = draft.findIndex(item => item.id === messageId);
if (messageIndex !== -1) {
draft[messageIndex].feedback = feedback;
}
}));
} catch (err) {
logError(`提交反馈时出错: ${err}`);
}
}, [logError, getChatList, setChatList]);
/**
* 停止响应
*/
const stopResponding = useCallback(() => {
if (abortControllerRef.current) {
abortControllerRef.current.abort();
abortControllerRef.current = null;
}
setHasStopResponded(true);
setNotResponding();
}, [setNotResponding, setHasStopResponded]);
/**
* 重新生成回答
*/
const regenerateResponse = useCallback(async (messageId: string) => {
// 找到要重新生成的消息
const messageIndex = getChatList().findIndex(item => item.id === messageId);
if (messageIndex === -1) return;
const message = getChatList()[messageIndex];
if (!message.isAnswer) return;
// 找到对应的问题
const questionIndex = messageIndex - 1;
if (questionIndex < 0) return;
const question = getChatList()[questionIndex];
if (question.isAnswer) return;
// 重新发送问题
await handleSend(question.content, null, question.message_files);
}, [getChatList, handleSend]);
/**
* 清空聊天记录
*/
const clearChatList = useCallback(() => {
setChatList([]);
currentResponseRef.current = null;
}, [setChatList]);
return {
// 基础状态
chatList,
setChatList,
getChatList,
isResponding,
hasStopResponded,
isRespondingConIsCurrCon,
messageTaskId,
userQuery,
// 核心方法
handleSend,
handleFeedback,
stopResponding,
regenerateResponse,
clearChatList,
// 辅助方法
checkCanSend,
logError,
updateCurrentQA,
};
}