236 lines
8.1 KiB
TypeScript
236 lines
8.1 KiB
TypeScript
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 = ''
|
|
) => {
|
|
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));
|
|
|
|
} catch (error) {
|
|
console.error('保存会话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<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,
|
|
};
|
|
}
|