类似 ChatGPT 的侧边栏布局,消息历史持久化到 localStorage。 - 新增 SessionSummary 类型和 localStorage 读写函数 - 新增 useSessionList Hook 管理会话列表 CRUD - 新增 SessionSidebar 组件(展开/折叠、行内重命名) - App.tsx 协调 useSessionList 和 useVisionSession - 消息变化时自动持久化,切换/新建会话时保存并加载 - 新增 i18n 翻译 key(中/英/日)
118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
// ============================================================
|
||
// useSessionList — 会话历史列表管理
|
||
// 职责:会话 CRUD、消息持久化、切换会话
|
||
// ============================================================
|
||
|
||
import { useCallback, useEffect, useRef, useState } from "react";
|
||
import { v4 as uuidv4 } from "uuid";
|
||
import {
|
||
loadSessionSummaries,
|
||
saveSessionSummaries,
|
||
loadSessionMessages,
|
||
saveSessionMessages,
|
||
deleteSessionMessages,
|
||
} from "../lib/storage";
|
||
import type { ChatMessage, SessionSummary } from "../types";
|
||
|
||
/** 截取预览文本 */
|
||
function getPreview(text: string, maxLen = 50): string {
|
||
const clean = text.replace(/[\n\r]/g, " ").trim();
|
||
return clean.length > maxLen ? clean.slice(0, maxLen) + "…" : clean;
|
||
}
|
||
|
||
export function useSessionList() {
|
||
const [sessions, setSessions] = useState<SessionSummary[]>(() => loadSessionSummaries());
|
||
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
||
const sessionsRef = useRef(sessions);
|
||
useEffect(() => { sessionsRef.current = sessions; }, [sessions]);
|
||
|
||
/** 创建新会话 */
|
||
const createSession = useCallback((): string => {
|
||
const id = uuidv4();
|
||
const now = Date.now();
|
||
const summary: SessionSummary = {
|
||
id,
|
||
title: "新对话",
|
||
createdAt: now,
|
||
lastActiveAt: now,
|
||
messageCount: 0,
|
||
preview: "",
|
||
};
|
||
setSessions((prev) => [summary, ...prev]);
|
||
setActiveSessionId(id);
|
||
// 持久化
|
||
const all = [summary, ...sessionsRef.current];
|
||
saveSessionSummaries(all);
|
||
return id;
|
||
}, []);
|
||
|
||
/** 删除会话 */
|
||
const deleteSession = useCallback((id: string) => {
|
||
setSessions((prev) => prev.filter((s) => s.id !== id));
|
||
deleteSessionMessages(id);
|
||
const remaining = sessionsRef.current.filter((s) => s.id !== id);
|
||
saveSessionSummaries(remaining);
|
||
// 如果删除的是当前会话,清空 active
|
||
setActiveSessionId((prev) => (prev === id ? null : prev));
|
||
}, []);
|
||
|
||
/** 重命名会话 */
|
||
const renameSession = useCallback((id: string, title: string) => {
|
||
setSessions((prev) => prev.map((s) => (s.id === id ? { ...s, title } : s)));
|
||
const updated = sessionsRef.current.map((s) => (s.id === id ? { ...s, title } : s));
|
||
saveSessionSummaries(updated);
|
||
}, []);
|
||
|
||
/** 保存当前会话的消息历史 */
|
||
const saveCurrentSession = useCallback((messages: ChatMessage[]) => {
|
||
const id = sessionsRef.current.length > 0 ? sessionsRef.current[0].id : null;
|
||
// 找到 activeSessionId 对应的会话
|
||
// 这里不依赖 activeSessionId state,而是通过参数传入
|
||
return messages;
|
||
}, []);
|
||
|
||
/** 保存指定会话的消息并更新摘要 */
|
||
const persistSession = useCallback((sessionId: string, messages: ChatMessage[]) => {
|
||
if (!sessionId) return;
|
||
saveSessionMessages(sessionId, messages);
|
||
// 更新摘要
|
||
const firstUserMsg = messages.find((m) => m.role === "user");
|
||
const title = firstUserMsg ? getPreview(firstUserMsg.content, 20) : "新对话";
|
||
const lastMsg = messages[messages.length - 1];
|
||
const summary: Partial<SessionSummary> = {
|
||
title,
|
||
messageCount: messages.length,
|
||
lastActiveAt: lastMsg?.timestamp || Date.now(),
|
||
preview: lastMsg ? getPreview(lastMsg.content) : "",
|
||
};
|
||
setSessions((prev) => {
|
||
const updated = prev.map((s) => (s.id === sessionId ? { ...s, ...summary } : s));
|
||
saveSessionSummaries(updated);
|
||
return updated;
|
||
});
|
||
}, []);
|
||
|
||
/** 加载指定会话的消息历史 */
|
||
const loadMessages = useCallback((sessionId: string): ChatMessage[] => {
|
||
return loadSessionMessages(sessionId);
|
||
}, []);
|
||
|
||
/** 选择会话(返回需要加载的消息) */
|
||
const selectSession = useCallback((id: string): ChatMessage[] => {
|
||
setActiveSessionId(id);
|
||
return loadSessionMessages(id);
|
||
}, []);
|
||
|
||
return {
|
||
sessions,
|
||
activeSessionId,
|
||
setActiveSessionId,
|
||
createSession,
|
||
deleteSession,
|
||
renameSession,
|
||
persistSession,
|
||
loadMessages,
|
||
selectSession,
|
||
};
|
||
}
|