基于 shiy-temp分支修改
This commit is contained in:
@@ -0,0 +1,574 @@
|
||||
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('🔄 更新聊天列表:', {
|
||||
// responseItemId: responseItem.id,
|
||||
// responseContent: responseItem.content,
|
||||
// originalResponseId,
|
||||
// questionId,
|
||||
// placeholderAnswerId
|
||||
// });
|
||||
|
||||
setChatList(produce(getChatList(), (draft) => {
|
||||
// console.log('📝 当前聊天列表:', draft.map(item => ({ id: item.id, content: item.content.substring(0, 20), isAnswer: item.isAnswer })));
|
||||
|
||||
// 移除占位符
|
||||
const placeholderIndex = draft.findIndex(item => item.id === placeholderAnswerId);
|
||||
if (placeholderIndex !== -1) {
|
||||
// console.log('🗑️ 移除占位符:', placeholderAnswerId);
|
||||
draft.splice(placeholderIndex, 1);
|
||||
}
|
||||
|
||||
// 确保问题存在
|
||||
const questionIndex = draft.findIndex(item => item.id === questionId);
|
||||
if (questionIndex === -1) {
|
||||
// console.log('➕ 添加问题:', questionId);
|
||||
draft.push({ ...questionItem });
|
||||
}
|
||||
|
||||
// 更新或添加响应 - 考虑ID可能已经改变的情况
|
||||
let responseIndex = draft.findIndex(item => item.id === responseItem.id);
|
||||
// console.log('🔍 查找响应索引 (当前ID):', { responseItemId: responseItem.id, responseIndex });
|
||||
|
||||
// 如果找不到当前ID的响应,尝试查找原始ID
|
||||
if (responseIndex === -1 && originalResponseId) {
|
||||
responseIndex = draft.findIndex(item => item.id === originalResponseId);
|
||||
// console.log('🔍 查找响应索引 (原始ID):', { originalResponseId, responseIndex });
|
||||
}
|
||||
|
||||
// 如果找不到任何匹配的响应,查找最后一个AI回答
|
||||
if (responseIndex === -1) {
|
||||
responseIndex = draft.findIndex((item, index) =>
|
||||
item.isAnswer &&
|
||||
index > draft.findIndex(q => q.id === questionId)
|
||||
);
|
||||
// console.log('🔍 查找响应索引 (最后AI回答):', { responseIndex });
|
||||
}
|
||||
|
||||
if (responseIndex !== -1) {
|
||||
// console.log('✏️ 更新现有响应:', { responseIndex, newContent: responseItem.content.substring(0, 20) });
|
||||
draft[responseIndex] = { ...responseItem };
|
||||
} else {
|
||||
// console.log('➕ 添加新响应:', { responseId: responseItem.id, content: responseItem.content.substring(0, 20) });
|
||||
draft.push({ ...responseItem });
|
||||
}
|
||||
|
||||
// console.log('📝 更新后聊天列表:', draft.map(item => ({ id: item.id, content: item.content.substring(0, 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('📤 发送消息:', { message, conversationId });
|
||||
|
||||
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('📨 收到流式数据:', { message, isFirstMessage, messageId, newConversationId });
|
||||
|
||||
if (!isAgentMode) {
|
||||
// 累积消息内容
|
||||
responseItem.content = responseItem.content + message;
|
||||
// console.log('📝 累积消息内容:', { currentContent: responseItem.content });
|
||||
} else {
|
||||
const lastThought = responseItem.agent_thoughts?.[responseItem.agent_thoughts?.length - 1];
|
||||
if (lastThought) {
|
||||
lastThought.thought = (lastThought.thought || '') + message;
|
||||
}
|
||||
}
|
||||
|
||||
if (messageId && !hasSetResponseId) {
|
||||
responseItem.id = messageId;
|
||||
hasSetResponseId = true;
|
||||
// console.log('🆔 设置响应ID:', { oldId: originalResponseId, newId: messageId });
|
||||
}
|
||||
|
||||
// 重要:确保正确获取新会话ID
|
||||
if (newConversationId && !tempNewConversationId) {
|
||||
tempNewConversationId = newConversationId;
|
||||
// console.log('🆔 获取到新会话ID:', tempNewConversationId);
|
||||
}
|
||||
|
||||
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
|
||||
// });
|
||||
|
||||
// 更新当前问答(使用防抖)
|
||||
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;
|
||||
if (tempNewConversationId && isNewConversation) {
|
||||
try {
|
||||
// console.log('🆕 处理新会话:', {
|
||||
// tempNewConversationId,
|
||||
// originalConversationId: conversationId,
|
||||
// isNewConversation
|
||||
// });
|
||||
|
||||
// 通知会话ID变更(这会触发localStorage更新)
|
||||
onConversationIdChange?.(tempNewConversationId);
|
||||
|
||||
// 生成会话名称
|
||||
const res = await generateConversationName(tempNewConversationId);
|
||||
const { data } = res as any;
|
||||
if (data?.name) {
|
||||
// console.log('📝 生成会话名称:', data.name);
|
||||
onUpdateConversationList?.(tempNewConversationId, { name: data.name });
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('生成会话名称失败:', err);
|
||||
}
|
||||
} else {
|
||||
// console.log('🔍 不是新会话,跳过处理:', {
|
||||
// tempNewConversationId,
|
||||
// conversationId,
|
||||
// isNewConversation
|
||||
// });
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user