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([]); // 是否正在响应中 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(null); // 当前流式响应项的引用 const currentResponseRef = useRef(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, ) => { 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 = {}; 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 = { 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, }; }