基于 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,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useParams } from '@remix-run/react';
|
||||
import { produce } from 'immer';
|
||||
import { useGetState, useLocalStorageState } from 'ahooks';
|
||||
import type { ConversationItem } from '../types/dify_chat';
|
||||
import { CHAT_CONFIG } from '../config/chat';
|
||||
|
||||
// 本地存储键名
|
||||
const storageConversationIdKey = 'conversationIdInfo';
|
||||
|
||||
// 会话信息类型(排除inputs和id)
|
||||
type ConversationInfoType = Omit<ConversationItem, 'inputs' | 'id'>;
|
||||
|
||||
/**
|
||||
* 获取完整的应用URL键名(与webapp-conversation保持一致)
|
||||
* @param appId 应用ID
|
||||
* @returns 完整的URL键名
|
||||
*/
|
||||
function getAppUrlKey(appId: string): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
// 在客户端,构建完整的URL键名
|
||||
const { protocol, host } = window.location;
|
||||
return `${protocol}//${host}/app/${appId}`;
|
||||
}
|
||||
// 在服务端,使用简化的键名
|
||||
return appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话管理钩子
|
||||
* 用于管理聊天会话、当前会话状态以及会话输入
|
||||
* 采用单页面应用模式,不使用URL路由导航
|
||||
*/
|
||||
export default function useConversation() {
|
||||
const params = useParams();
|
||||
|
||||
// 会话列表
|
||||
const [conversationList, setConversationList] = useState<ConversationItem[]>([]);
|
||||
|
||||
// 当前会话ID - 使用ahooks的useGetState来获得更好的状态管理
|
||||
// 初始值从URL参数或localStorage获取,如果都没有则为'-1'(新会话)
|
||||
const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState<string>(() => {
|
||||
// 首先尝试从URL参数获取
|
||||
if (params.id) {
|
||||
return params.id;
|
||||
}
|
||||
// 然后尝试从localStorage获取
|
||||
const storedId = getConversationIdFromStorage(CHAT_CONFIG.APP_ID);
|
||||
return storedId || '-1';
|
||||
});
|
||||
|
||||
// 新会话输入状态
|
||||
const [newConversationInputs, setNewConversationInputs] = useState<Record<string, any> | null>(null);
|
||||
|
||||
// 现有会话输入状态
|
||||
const [existConversationInputs, setExistConversationInputs] = useState<Record<string, any> | null>(null);
|
||||
|
||||
// 新会话信息
|
||||
const [newConversationInfo, setNewConversationInfo] = useState<ConversationInfoType | null>(null);
|
||||
|
||||
// 现有会话信息
|
||||
const [existConversationInfo, setExistConversationInfo] = useState<ConversationInfoType | null>(null);
|
||||
|
||||
// 判断是否为新会话
|
||||
const isNewConversation = currConversationId === '-1';
|
||||
|
||||
// 当前输入状态(根据是否为新会话来决定使用哪个状态)
|
||||
const currInputs = isNewConversation ? newConversationInputs : existConversationInputs;
|
||||
const setCurrInputs = isNewConversation ? setNewConversationInputs : setExistConversationInputs;
|
||||
|
||||
// 当前会话信息(根据是否为新会话来决定使用哪个信息)
|
||||
const currConversationInfo = isNewConversation ? newConversationInfo : existConversationInfo;
|
||||
|
||||
/**
|
||||
* 从本地存储获取会话ID
|
||||
* @param appId 应用ID
|
||||
* @returns 会话ID
|
||||
*/
|
||||
function getConversationIdFromStorage(appId: string): string {
|
||||
try {
|
||||
const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey)
|
||||
? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '{}')
|
||||
: {};
|
||||
|
||||
// 使用完整的URL键名获取会话ID
|
||||
const appUrlKey = getAppUrlKey(appId);
|
||||
const conversationId = conversationIdInfo[appUrlKey];
|
||||
|
||||
// console.log('📖 从localStorage获取会话ID:', {
|
||||
// appUrlKey,
|
||||
// conversationId,
|
||||
// allKeys: Object.keys(conversationIdInfo)
|
||||
// });
|
||||
|
||||
return conversationId || '-1';
|
||||
} catch (error) {
|
||||
console.error('获取本地存储会话ID失败:', error);
|
||||
return '-1';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前会话ID并保存到本地存储
|
||||
* 纯状态管理模式,不进行URL导航
|
||||
* @param id 会话ID
|
||||
* @param appId 应用ID
|
||||
* @param isSetToLocalStorage 是否保存到本地存储
|
||||
* @param newConversationName 新会话名称(暂未使用)
|
||||
*/
|
||||
const setCurrConversationId = (
|
||||
id: string,
|
||||
appId: string,
|
||||
isSetToLocalStorage = true,
|
||||
newConversationName = ''
|
||||
) => {
|
||||
console.log('🔄 设置当前会话ID:', { id, appId, isSetToLocalStorage });
|
||||
|
||||
doSetCurrConversationId(id);
|
||||
|
||||
if (isSetToLocalStorage && id !== '-1') {
|
||||
try {
|
||||
// 获取现有的conversationIdInfo
|
||||
const conversationIdInfo = globalThis.localStorage?.getItem(storageConversationIdKey)
|
||||
? JSON.parse(globalThis.localStorage?.getItem(storageConversationIdKey) || '{}')
|
||||
: {};
|
||||
|
||||
// 使用完整的URL键名保存会话ID(与webapp-conversation保持一致)
|
||||
const appUrlKey = getAppUrlKey(appId);
|
||||
conversationIdInfo[appUrlKey] = id;
|
||||
|
||||
globalThis.localStorage?.setItem(storageConversationIdKey, JSON.stringify(conversationIdInfo));
|
||||
|
||||
console.log('💾 会话ID已保存到localStorage:', {
|
||||
appUrlKey,
|
||||
conversationId: id,
|
||||
fullStorage: conversationIdInfo
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('保存会话ID到本地存储失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 不进行URL导航,保持单页面应用模式
|
||||
console.log('✅ 会话切换完成,当前会话ID:', id);
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置新会话输入
|
||||
* 使用immer来安全地重置状态
|
||||
*/
|
||||
const resetNewConversationInputs = () => {
|
||||
if (!newConversationInputs) {
|
||||
return;
|
||||
}
|
||||
setNewConversationInputs(produce(newConversationInputs, (draft) => {
|
||||
Object.keys(draft).forEach((key) => {
|
||||
draft[key] = '';
|
||||
});
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新会话列表中的特定会话
|
||||
* @param id 会话ID
|
||||
* @param updates 要更新的字段
|
||||
*/
|
||||
const updateConversationInList = (id: string, updates: Partial<ConversationItem>) => {
|
||||
setConversationList(produce(conversationList, (draft) => {
|
||||
const index = draft.findIndex(item => item.id === id);
|
||||
if (index !== -1) {
|
||||
Object.assign(draft[index], updates);
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加新会话到列表
|
||||
* @param conversation 新会话
|
||||
*/
|
||||
const addConversationToList = (conversation: ConversationItem) => {
|
||||
setConversationList(produce(conversationList, (draft) => {
|
||||
// 检查是否已存在,避免重复添加
|
||||
const exists = draft.some(item => item.id === conversation.id);
|
||||
if (!exists) {
|
||||
draft.unshift(conversation);
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 从列表中移除会话
|
||||
* @param id 会话ID
|
||||
*/
|
||||
const removeConversationFromList = (id: string) => {
|
||||
setConversationList(produce(conversationList, (draft) => {
|
||||
const index = draft.findIndex(item => item.id === id);
|
||||
if (index !== -1) {
|
||||
draft.splice(index, 1);
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建新会话项
|
||||
* @param name 会话名称
|
||||
* @param introduction 会话介绍
|
||||
* @returns 新会话项
|
||||
*/
|
||||
const createNewConversationItem = (name: string = '新对话', introduction: string = ''): ConversationItem => {
|
||||
return {
|
||||
id: '-1',
|
||||
name,
|
||||
inputs: newConversationInputs || {},
|
||||
introduction,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
// 基础状态
|
||||
conversationList,
|
||||
setConversationList,
|
||||
currConversationId,
|
||||
getCurrConversationId,
|
||||
setCurrConversationId,
|
||||
getConversationIdFromStorage,
|
||||
isNewConversation,
|
||||
|
||||
// 输入状态
|
||||
currInputs,
|
||||
newConversationInputs,
|
||||
existConversationInputs,
|
||||
resetNewConversationInputs,
|
||||
setCurrInputs,
|
||||
|
||||
// 会话信息
|
||||
currConversationInfo,
|
||||
setNewConversationInfo,
|
||||
setExistConversationInfo,
|
||||
|
||||
// 辅助方法
|
||||
updateConversationInList,
|
||||
addConversationToList,
|
||||
removeConversationFromList,
|
||||
createNewConversationItem,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user