feat(web): 前端接会话持久化——拉历史热分页 + 侧栏会话列表
- store: 消息真源移到后端(sessions元数据map + activeSessionId,partialize 移除 messages 改存会话元数据,version:2 migrate 清旧数据) - api: fetchSessions/createSubSession/fetchMessages(热分页before游标)/deleteSession + MessageDto→toChatMessage - Nav: 侧栏「会话」区——主会话置顶 + 次级列表 + +新建会话(Miuix) - 新增 HistoryList: 顶部「加载更早」向上分页 + 保守虚拟化(>200只渲染最近200) - useChat: send 用 activeSessionId;App: token 后 fetchSessions→激活主会话→拉近50条 - Docs/api-client: v0.10 补 WS sessionId + history 回放事件契约
This commit is contained in:
1 parent
48421923f6
commit
bcc53d68f3
9 files changed
+371
-32
No files matched your search
@@ -6,7 +6,13 @@ import { AdminApp } from "./admin/AdminApp";
|
||||
import { parseAdminHash } from "./admin/router";
|
||||
import { connectWs, disconnectWs } from "./lib/ws";
|
||||
import { initTheme } from "./lib/theme";
|
||||
import { fetchPersonas, getAuthToken } from "./lib/api";
|
||||
import {
|
||||
fetchMessages,
|
||||
fetchPersonas,
|
||||
fetchSessions,
|
||||
getAuthToken,
|
||||
} from "./lib/api";
|
||||
import type { SessionMeta } from "./lib/types";
|
||||
import { useAppStore } from "./store/useAppStore";
|
||||
|
||||
export default function App(): React.ReactNode {
|
||||
@@ -60,6 +66,55 @@ export default function App(): React.ReactNode {
|
||||
};
|
||||
}, [token, setCurrentPersona]);
|
||||
|
||||
const sessions = useAppStore((s) => s.sessions);
|
||||
const activeSessionId = useAppStore((s) => s.activeSessionId);
|
||||
const setSessions = useAppStore((s) => s.setSessions);
|
||||
const setActiveSession = useAppStore((s) => s.setActiveSession);
|
||||
const setMessages = useAppStore((s) => s.setMessages);
|
||||
|
||||
// Load the session list once we hold a token, then focus a session: a persisted
|
||||
// (still-present) session id, else the main session (design/16 §4.1). The main
|
||||
// session has `type === "main"`; fall back to the first entry, then clear.
|
||||
useEffect(() => {
|
||||
if (!token) return;
|
||||
let cancelled = false;
|
||||
fetchSessions()
|
||||
.then((list) => {
|
||||
if (cancelled) return;
|
||||
const byId = Object.fromEntries(
|
||||
list.map((s) => [s.id, s] as const)
|
||||
) as Record<string, SessionMeta>;
|
||||
setSessions(list);
|
||||
const main = list.find((s) => s.type === "main") ?? list[0];
|
||||
const next =
|
||||
activeSessionId && byId[activeSessionId] ? activeSessionId : main?.id;
|
||||
setActiveSession(next ?? "");
|
||||
})
|
||||
.catch(() => {
|
||||
// Backend not wired yet — leave the shell in its untouched state.
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [token, setSessions, setActiveSession]);
|
||||
|
||||
// Whenever the focused session changes (switch, new, or first load) and it is
|
||||
// a session we know about, pull its initial page of history into the panel.
|
||||
useEffect(() => {
|
||||
if (!activeSessionId || !sessions[activeSessionId]) return;
|
||||
let cancelled = false;
|
||||
fetchMessages(activeSessionId, undefined, 50)
|
||||
.then((list) => {
|
||||
if (!cancelled) setMessages(list);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setMessages([]);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [activeSessionId, sessions, setMessages]);
|
||||
|
||||
// The M9 modal UI and the admin subsystem (design/10) are two separate shells.
|
||||
// `/admin` is reached via `#/admin`; every admin module is a hash segment.
|
||||
if (route.admin) {
|
||||
|
||||
Reference in new issue
Block a user