Files
leaudit-platform-frontend/app/hooks/use-conversation.ts
T

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,
};
}