feat(web): W3-1 shared console routes, nav, api/sse/query-keys and shared components

- routes.ts registers all workspaces under /console (+ detail routes, engine, audit, login)
- api.ts: cookie session first (credentials include, CSRF header), Bearer only with stored key, 401 -> /login
- sse.ts: shared EventSource with ?after=seq catch-up, backoff reconnect, per-type subscribe
- shared components: data-table, filter-bar, detail-sheet, timeline, json-view, confirm-dialog,
  form-field, kv-list, stat-tile, error/loading states; ui tabs and textarea

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 20:21:29 +08:00
1 parent a24bda952b
commit e5e42b1f0d
32 files changed
+1286 -71

No files matched your search

+73 -15
View File
@@ -1,4 +1,5 @@
import {
Activity,
Bot,
CalendarClock,
Download,
@@ -10,6 +11,7 @@ import {
Library,
type LucideIcon,
Rss,
ScrollText,
Settings,
Tags,
} from "lucide-react";
@@ -17,58 +19,114 @@ import {
export type NavItem = { to: string; label: string; icon: LucideIcon; description: string };
export type NavGroup = { title: string; items: NavItem[] };
/** 控制台 12 个工作区(任务书 §3.1 第 10 条 / REQ §13.1) */
/** 控制台路由前缀 */
export const CONSOLE_BASE = "/console";
/** 拼控制台路径:consolePath("library", id) → /console/library/<id> */
export function consolePath(...segments: (string | number)[]): string {
const rest = segments.map((s) => encodeURIComponent(String(s))).join("/");
return rest ? `${CONSOLE_BASE}/${rest}` : CONSOLE_BASE;
}
/** 控制台 12 个工作区(任务书 §3.1 第 10 条 / REQ §13.1)+ 引擎面板与审计两个运维页 */
export const NAV_GROUPS: NavGroup[] = [
{
title: "概览",
items: [
{
to: "/",
to: "/console",
label: "首页",
icon: House,
description: "自动化概览、当前工作、需要我处理、缓存与保留、来源健康、最近产出",
},
{ to: "/inbox", label: "待处理中心", icon: Inbox, description: "按作品与根因合并的待决定事项,Agent 已先行调查" },
{
to: "/console/inbox",
label: "待处理中心",
icon: Inbox,
description: "按作品与根因合并的待决定事项,Agent 已先行调查",
},
],
},
{
title: "媒体",
items: [
{ to: "/library", label: "媒体库", icon: Library, description: "作品、内容单元、版本目标与实际路径" },
{ to: "/identity", label: "身份与元数据", icon: Fingerprint, description: "外部身份映射、待识别池与元数据来源" },
{ to: "/organize", label: "整理记录", icon: FileStack, description: "Copy/Move 入库前后路径与逐文件结果" },
{ to: "/console/library", label: "媒体库", icon: Library, description: "作品、内容单元、版本目标与实际路径" },
{
to: "/console/identity",
label: "身份与元数据",
icon: Fingerprint,
description: "外部身份映射、待识别池与元数据来源",
},
{
to: "/console/organize",
label: "整理记录",
icon: FileStack,
description: "Copy/Move 入库前后路径与逐文件结果",
},
],
},
{
title: "获取",
items: [
{
to: "/subscriptions",
to: "/console/subscriptions",
label: "订阅与收录规则",
icon: CalendarClock,
description: "单次收录与持续追踪,版本目标与预算",
},
{
to: "/preferences",
to: "/console/preferences",
label: "偏好标签",
icon: Tags,
description: "软偏好与硬规则,手动取消的偏好不会被自动加回",
},
{ to: "/sites", label: "PT 站点工作台", icon: Globe, description: "统一格式浏览、搜索站点资源并关联本地库存" },
{ to: "/sources", label: "来源 / Feed / 快照", icon: Rss, description: "RSS 与站点快照的抓取状态、新鲜度与健康" },
{ to: "/tasks", label: "下载与任务", icon: Download, description: "下载、整理、核验的业务时间线与子任务" },
{
to: "/console/sites",
label: "PT 站点工作台",
icon: Globe,
description: "统一格式浏览、搜索站点资源并关联本地库存",
},
{
to: "/console/sources",
label: "来源 / Feed / 快照",
icon: Rss,
description: "RSS 与站点快照的抓取状态、新鲜度与健康",
},
{
to: "/console/tasks",
label: "下载与任务",
icon: Download,
description: "下载、整理、核验的业务时间线与子任务",
},
],
},
{
title: "助手与系统",
items: [
{ to: "/agent", label: "Agent 会话", icon: Bot, description: "用自然语言交代目标,Agent 在授权内执行并汇报" },
{
to: "/settings",
to: "/console/agent",
label: "Agent 会话",
icon: Bot,
description: "用自然语言交代目标,Agent 在授权内执行并汇报",
},
{
to: "/console/settings",
label: "系统设置",
icon: Settings,
description: "下载器与媒体服务器实例、路径映射、API Key、Pi 上游与自动化开关",
},
{
to: "/console/engine",
label: "工作流引擎",
icon: Activity,
description: "持久任务引擎的运行记录、步骤尝试与信号(只读诊断,仅管理员)",
},
{
to: "/console/audit",
label: "审计日志",
icon: ScrollText,
description: "登录、设置、密钥与实例变更的操作留痕",
},
],
},
];
@@ -78,8 +136,8 @@ export const NAV_ITEMS = NAV_GROUPS.flatMap((g) => g.items);
/** 按路径找当前导航项(最长前缀匹配) */
export function findNavItem(pathname: string): NavItem | undefined {
return (
NAV_ITEMS.find((i) => i.to === pathname) ??
NAV_ITEMS.filter((i) => i.to !== "/" && pathname.startsWith(`${i.to}/`)).sort(
NAV_ITEMS.find((i) => i.to === pathname.replace(/\/+$/, "")) ??
NAV_ITEMS.filter((i) => i.to !== CONSOLE_BASE && pathname.startsWith(`${i.to}/`)).sort(
(a, b) => b.to.length - a.to.length,
)[0]
);