import { useState } from "react"; import { useAsyncData } from "../../lib/useAsyncData"; import { adminListSessions, createSubSession } from "../../lib/api"; import type { SessionMeta } from "../../lib/types"; import { useAutoReload } from "../hooks"; import { useAuditStore } from "../audit"; import { AdminTable, Notice, PageHeader, type AdminColumn } from "../components"; import { Button, Surface, Tag } from "../../components/primitives"; function exportMeta(sess: SessionMeta): void { const blob = new Blob([JSON.stringify(sess, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `session-${sess.id}.json`; a.click(); URL.revokeObjectURL(url); } export function SessionsModule(): React.ReactNode { const { data, loading, error, reload } = useAsyncData(adminListSessions, []); useAutoReload(reload, 10000); const [query, setQuery] = useState(""); const [busy, setBusy] = useState(null); const [fail, setFail] = useState(null); const log = useAuditStore((s) => s.log); const filtered = (data ?? []).filter((s) => { if (!query.trim()) return true; const q = query.toLowerCase(); return ( s.id.toLowerCase().includes(q) || s.type.toLowerCase().includes(q) || (s.title ?? "").toLowerCase().includes(q) ); }); const newSubSession = () => { setBusy("new"); setFail(null); createSubSession() .then(() => { log("session.create", "ok"); reload(); }) .catch((e: unknown) => { log("session.create", "error"); setFail(e instanceof Error ? e.message : "新建次级会话失败"); }) .finally(() => setBusy(null)); }; const columns: AdminColumn[] = [ { key: "type", title: "类型", render: (s) => {s.type === "main" ? "主会话" : "次级会话"} }, { key: "title", title: "标题", render: (s) => {s.title ? s.title : (未命名)} }, { key: "id", title: "会话 ID", render: (s) => {s.id}, }, { key: "createdAt", title: "创建时间", render: (s) => {new Date(s.createdAt).toLocaleString()}, }, { key: "actions", title: "操作", render: (s) => ( ), }, ]; return (
reload()}> 刷新 } /> {fail ? {fail} : null}
setQuery(e.target.value)} placeholder="按会话 ID / 类型 / 标题过滤…" className="min-w-0 flex-1 rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-[var(--mu-accent)]" />
s.id} />
列表来自后端持久化会话(主会话 + 次级会话),主会话 id 固定。导出为会话元数据 JSON;身份判定永远由 actor 元数据 + 白名单 + 策略引擎完成,不依赖提示词。
); }