import { useParams } from '@remix-run/react'; import { useGetState } from 'ahooks'; import { produce } from 'immer'; import { useState } from 'react'; import type { ConversationItem } from '~/api/dify-chat'; import { CHAT_CONFIG } from '../config/chat'; // 本地存储键名 const storageConversationIdKey = 'conversationIdInfo'; // 会话信息类型(排除inputs和id) type ConversationInfoType = Omit; /** * 获取完整的应用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([]); // 当前会话ID - 使用ahooks的useGetState来获得更好的状态管理 // 初始值从URL参数或localStorage获取,如果都没有则为'-1'(新会话) const [currConversationId, doSetCurrConversationId, getCurrConversationId] = useGetState(() => { // 首先尝试从URL参数获取 if (params.id) { return params.id; } // 然后尝试从localStorage获取 const storedId = getConversationIdFromStorage(CHAT_CONFIG.APP_ID); return storedId || '-1'; }); // 新会话输入状态 const [newConversationInputs, setNewConversationInputs] = useState | null>(null); // 现有会话输入状态 const [existConversationInputs, setExistConversationInputs] = useState | null>(null); // 新会话信息 const [newConversationInfo, setNewConversationInfo] = useState(null); // 现有会话信息 const [existConversationInfo, setExistConversationInfo] = useState(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('💾 [useConversation] setCurrConversationId:', { id, appId, isSetToLocalStorage, willSaveToStorage: isSetToLocalStorage && id !== '-1' }); 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('✅ [useConversation] 已保存到localStorage:', { appUrlKey, conversationId: id, allKeys: Object.keys(conversationIdInfo) }); } catch (error) { console.error('❌ [useConversation] 保存会话ID到本地存储失败:', error); } } }; /** * 重置新会话输入 * 使用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) => { 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, }; }