deerflow-code/frontend-web/docs/前端开发.md
2026-09-07 18:24:55 +08:00

1170 lines
69 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 前端开发文档(frontend-web)
> 本文档面向参与 `frontend-web/` 开发的工程师,覆盖工程结构、技术栈、环境与启动、路由与页面、状态体系、与后端 LangGraph 的实时流式交互、上传/产物(Artifacts)、模型与设置、鉴权、主题与国际化、构建与发布等所有关键开发点,力求从零到一帮助新成员快速上手。
---
## 1. 概览
- 项目名:`deer-flow-frontend-vite`(package.json)
- 类型:单页应用(SPA),React 19 + TypeScript 5 + Vite 6
- 包管理器:pnpm(`packageManager: pnpm@10.26.2`)
- 路由:`react-router-dom` v7(采用 HashRouter)
- 状态:TanStack Query(服务端状态)+ React Context(流式会话/Artifacts/Subtasks/PromptInput)+ `localStorage`/`sessionStorage`(用户偏好与流式恢复)
- UI 体系:TailwindCSS v4 + Radix UI 原语 + shadcn 风格自封装(`src/components/ui/`)+ TDesign(少量页面)+ Lucide 图标
- LLM 流式交互:[`@langchain/langgraph-sdk`](https://www.npmjs.com/package/@langchain/langgraph-sdk),与后端 Gateway 上的 LangGraph 运行时(默认 `lead_agent`)建立 SSE 连接
- 渲染:`react-markdown` + `streamdown` + `rehype-*` + `shiki/react-syntax-highlighter` + `katex`
- 富文本:`@tiptap/*`(用于 Open Canvas 写作)
- 编辑器:`@uiw/react-codemirror`(代码片段编辑)
- 流程图:`@xyflow/react`(节点连线类组件)
> 这是一个由 Next.js 历史代码迁移而来的 Vite 工程:保留 `next/link`、`next/navigation`、`use client` 等遗留写法,并通过 [src/shims/](src/shims/) 提供运行时兼容。新开发中可以继续按 Next.js 习惯写 `useRouter()/useParams()/usePathname()`,由 shim 转译为 react-router 实现。
---
## 2. 目录结构
```
frontend-web/
├─ index.html 入口模板(含黑/白主题快速注入脚本)
├─ vite.config.ts Vite 配置(alias + sourceIdentifier 插件)
├─ tsconfig.json TS 编译选项
├─ postcss.config.js PostCSS + tailwindcss v4
├─ .env / .env.development / .env.production 后端地址等环境变量
├─ docs/ 前端文档(本目录)
└─ src/
├─ main.tsx React 入口(StrictMode + 全局样式)
├─ App.tsx 根组件:HashRouter + 顶层 Provider 嵌套
├─ env.ts 读 import.meta.env(兼容旧 NEXT_PUBLIC_ 命名)
├─ styles/globals.css 全局 + Tailwind 入口
├─ shims/ next/link、next/navigation 的 react-router 适配
├─ pages/ 路由页面(HashRouter 下的所有 Route 元素)
│ ├─ PageRoutes.tsx /page/* 一级布局:左侧 PageSidebar + Header
│ ├─ WorkspaceRoutes.tsx /page/workspace/* 二级路由(聊天/Agents/Skills/Memory/Admin…)
│ ├─ StrategyRoutes.tsx /page/strategy/* 策略页
│ ├─ RoundtableRoutes.tsx /page/roundtable/*
│ ├─ ChatPage.tsx 主聊天页(绑定 lead_agent)
│ ├─ AgentChatPage.tsx 基于自定义 Agent 的会话
│ ├─ ScheduledTasksPage.tsx 定时任务
│ ├─ ScheduledTaskRunDetailPage.tsx
│ ├─ PublicSharePage.tsx 免登录分享只读
│ ├─ PublicScheduledTaskPage.tsx
│ ├─ EmbedChatPage.tsx iframe 嵌入问答(见 docs/embed-iframe-chat.md)
│ ├─ LoginPage.tsx 用户名直登(开发/内网用)
│ └─ ...
├─ core/ 「领域核心」: API 客户端、领域 hooks、类型
│ ├─ api/ langgraph-sdk client + 鉴权 fetch
│ ├─ auth/ 访问令牌存取(localStorage)
│ ├─ config/ 后端/LangGraph URL 解析
│ ├─ threads/ 会话流(核心 useThreadStream)
│ ├─ uploads/ 线程上传/校验/解析
│ ├─ settings/ 本地偏好与线程级模型覆盖
│ ├─ tasks/ 子任务 SubtaskContext
│ ├─ models/ 可用模型 + 用量开关
│ ├─ agents/ 自定义 Agent CRUD + 选择器
│ ├─ skills/、memory/、tags/ 其他领域
│ ├─ scheduled-tasks/ 定时任务
│ ├─ artifacts/ 产物(代码/文档)读取
│ ├─ recommended-questions/、notifications/、admin/、curator/、tool-metrics/、llm-metrics/
│ ├─ note/ 轻应用(笔记)API(独立后端 VITE_NOTE_API_BASE_URL)
│ ├─ i18n/ 语言上下文、词条、检测
│ ├─ rehype/、streamdown/、tools/、messages/、page-layout/、route-base.tsx
│ └─ utils/ uuid、json、files、markdown、datetime
├─ contexts/ 全局 React 上下文(Chat/Theme)
├─ components/
│ ├─ ui/ shadcn 风格基础组件(button、dialog、sidebar、resizable…)
│ ├─ ai-elements/ AI 业务组件(prompt-input、conversation、message、plan、reasoning…)
│ ├─ workspace/ 工作台模块(chats、messages、artifacts、settings、admin、agents…)
│ ├─ landing/ 营销页(Landing)
│ ├─ query-client-provider.tsx TanStack Query 容器
│ └─ theme-provider.tsx next-themes 容器
├─ open-canvas/ 开放画布/AI 写作(独立路由:/page/canvas/*)
├─ roundtable-planning/ 圆桌规划(独立路由:/page/roundtable/*)
├─ strategy-components/ 策略相关组件 + Header
├─ hooks/ 通用工具 hooks(debounce、async、localStorage…)
└─ lib/ 通用工具:cn、desensitize、ime
```
---
## 3. 启动、构建、脚本
### 3.1 NPM Scripts([package.json](../package.json))
| 脚本 | 命令 | 说明 |
|---|---|---|
| `pnpm dev` | `vite` | 启动开发服务器(默认端口 5173;项目实际通过仓库根脚本统一为 5174) |
| `pnpm build` | `node --max-old-space-size=8192 vite build` | 生产构建(提升 Node 内存阈值,避免大型依赖 OOM) |
| `pnpm preview` | `vite preview` | 本地预览 dist 构建产物 |
| `pnpm typecheck` | `tsc --noEmit` | 仅做 TS 类型检查,不输出文件 |
### 3.2 仓库根脚本
`scripts/start-frontend.sh`(与后端联动)会自动注入 `VITE_BACKEND_BASE_URL=http://127.0.0.1:8001` 与 `VITE_LANGGRAPH_BASE_URL=http://127.0.0.1:8001/api`,并将日志/PID 写入 `.runtime/`。详细命令见根目录 [CLAUDE.md](../../CLAUDE.md):
```bash
./scripts/start-all.sh # 同时启动 backend + frontend
./scripts/stop-all.sh
./scripts/restart-all.sh
./scripts/status.sh
```
### 3.3 Vite 别名([vite.config.ts](../vite.config.ts))
| 别名 | 真实路径 |
|---|---|
| `@/` | `src/` |
| `@vite/` | `src/` |
| `@/env` | `src/env.ts` |
| `next/link` | `src/shims/next-link.tsx` |
| `next/navigation` | `src/shims/next-navigation.ts` |
工程同时配置了 `vite-plugin-source-identifier`:构建产物会带源文件标记,便于线上排错对应到源文件。
`base: "./"` 表示打包后使用相对路径,方便部署到任意子路径或本地 file:// 调试。
### 3.4 浏览器与构建目标
- TS `target: ES2022`、`module: ESNext`、`moduleResolution: Bundler`
- `useDefineForClassFields: true`、`isolatedModules: true`、`jsx: react-jsx`
- 严格模式开启,但 `noImplicitAny: false`
---
## 4. 环境变量
入口在 [src/env.ts](../src/env.ts),所有公共变量使用 `VITE_` 前缀,运行时通过 `import.meta.env` 读取。出于兼容旧 Next.js 代码,导出的对象保留 `NEXT_PUBLIC_` 命名:
```ts
env.NEXT_PUBLIC_BACKEND_BASE_URL // VITE_BACKEND_BASE_URL
env.NEXT_PUBLIC_LANGGRAPH_BASE_URL // VITE_LANGGRAPH_BASE_URL
env.NEXT_PUBLIC_CANVAS_LANGGRAPH_BASE_URL // 画布独立 LangGraph(可选,默认回退主地址)
env.NEXT_PUBLIC_NOTE_API_BASE_URL // 轻应用「笔记」API
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY // 静态演示模式(禁用上传/发送)
env.GITHUB_OAUTH_TOKEN // 备用:landing 中拉取仓库信息
```
`.env` 文件加载优先级(Vite 标准):
```
.env.development.local > .env.development > .env (开发)
.env.production.local > .env.production > .env (生产)
```
个人本机覆盖请使用 `.env.development.local`(已 gitignore)。
`getBackendBaseURL()` / `getLangGraphBaseURL()` / `getCanvasLangGraphBaseURL()` 均在 [src/core/config/index.ts](../src/core/config/index.ts) 中,会基于当前 `window.location.origin` 兜底为同源 `/api/langgraph`。
---
## 5. 渲染入口与 Provider 体系
### 5.1 启动栈
```
ReactDOM.createRoot(#root)
└─ <React.StrictMode>
└─ <App>
└─ <HashRouter>
└─ <AppProviders>
├─ <NextThemesProvider> // next-themes,attribute=class,存储键 "strategy-theme"
│ ├─ <I18nProvider> // 语言上下文(locale)
│ │ └─ <ThemeProvider> // 自定义主题(与 next-themes 协作)
│ │ └─ <ChatProvider> // QA 历史(localStorage: qa-chat-history)
│ │ └─ <QueryClientProvider> // TanStack Query
│ │ └─ <Routes>...
```
`index.html` 头部内联了一段脚本,会在 React 启动前根据 `localStorage["strategy-theme"]` 决定是否给 `<html>` 添加 `dark` 类,避免暗色主题闪烁。
### 5.2 路由
外层([App.tsx](../src/App.tsx)):
```
/ -> Redirect to /login
/login LoginPage
/login/:username LoginPage(按用户名直登)
/landing LandingPage
/share/:shareCode PublicSharePage(免登录只读)
/embed/chats/:thread_id EmbedChatPage(iframe 嵌入问答,见 docs/embed-iframe-chat.md)
/public/scheduled-tasks/:taskId PublicScheduledTaskPage(免登录)
/page/* PageRoutes(含侧边栏与 Header)
* NotFoundPage
```
`/page/*` 内([PageRoutes.tsx](../src/pages/PageRoutes.tsx)):
```
/page/workspace/* -> WorkspaceRoutes(默认)
/page/strategy/* -> StrategyRoutes
/page/canvas/* -> open-canvas/routes/OpenCanvasRoutes
/page/roundtable/* -> RoundtableRoutes
```
`/page/workspace/*`([WorkspaceRoutes.tsx](../src/pages/WorkspaceRoutes.tsx)):
```
chats -> Navigate to chats/new
chats/:thread_id -> <WorkspaceLayout><ChatRuntime><ChatPage>
agents -> AgentsPage
agents/new -> NewAgentPage
agents/:agent_id/chats/:thread_id -> AgentChatPage
skills -> SkillsPage
skills/evolution -> SkillEvolutionPage
memory -> MemoryPage
ai-writing -> AIWritingPage(来自 open-canvas)
roundtable/planning -> RoundtablePlanningPage
scheduled/tasks -> ScheduledTasksPage
scheduled/runs/:run_id -> ScheduledTaskRunDetailPage
admin -> AdminUserHubPage (system_role=admin)
admin/scheduled-tasks -> AdminScheduledTasksPage (admin)
admin/tags -> TagsPage (admin)
settings/recommended-questions (admin)
settings/prompt-prefix (admin)
settings/llm-metrics (admin)
```
`WorkspaceRoutes` 通过 `<RouteBaseProvider value="/page/workspace">` 提供基址上下文,让兼容自 Next.js 的 `useRouter().push("/page/workspace/...")` 在不同的二级路由下都能映射到正确的 base。
> **重要约定**:使用 HashRouter,URL 形如 `/#/page/workspace/chats/new`。这是为了便于在静态文件部署(包括打包后 `file://`)下使用。新建一条聊天时,前端会用 `history.replaceState(...)` 直接改写 hash,而不让 router 重渲染整棵子树 —— 这一点在 [ChatPage.tsx:75-79](../src/pages/ChatPage.tsx#L75-L79) 中体现,依赖了 `useThreadChat()` 的特殊兜底逻辑(避免 useParams 把 `new` 误判为真实 thread_id)。
### 5.3 Workspace 双层布局
- `<WorkspaceLayout>` 提供 `<SidebarProvider>` + `<WorkspaceSidebar>` + `<SidebarInset>` + `<CommandPalette>` + `<Toaster>`,并通过 CSS 变量 `--page-sidebar-width` 把内层侧栏紧贴外层 PageSidebar。
- 对于「聊天类」路由 (`chats/:thread_id`、`agents/.../chats/:thread_id`),额外包一层 `<ChatRuntime>`:
```tsx
<SubtasksProvider>
<ArtifactsProvider>
<PromptInputProvider>
{children}
</PromptInputProvider>
</ArtifactsProvider>
</SubtasksProvider>
```
这三个上下文只在聊天会话内生效,避免污染列表/管理类页面。
---
## 6. 鉴权
### 6.1 登录流程
[src/pages/LoginPage.tsx](../src/pages/LoginPage.tsx) 通过 `loginByUsername(username)` 直接向后端发起登录(默认 `guest`),返回结构存在 [src/core/auth/index.ts](../src/core/auth/index.ts):
```ts
interface LoginByUsernameResponse {
access_token: string;
token_type: string; // 默认 "Bearer"
expires_in: number;
user_id: string;
email: string;
system_role: string; // "admin" 控制管理员菜单
needs_setup: boolean;
created: boolean;
}
```
存储键:`localStorage["deerflow.auth"]`。
工具方法:
- `getStoredAuth()` / `setStoredAuth(auth)` / `clearStoredAuth()`
- `getAccessToken()` —— 返回 token 字符串
- `getAuthorizationHeaderValue()` —— 返回 `"Bearer xxx"`,缺失时 undefined
### 6.2 鉴权注入
所有出站请求都通过两条路径之一注入 Token:
1. **裸 fetch** —— 使用 [src/core/api/fetch-client.ts](../src/core/api/fetch-client.ts) 提供的 `apiFetch(input, init)`,自动在 headers 上加 `Authorization`。
2. **LangGraph SDK** —— [src/core/api/api-client.ts](../src/core/api/api-client.ts) 在 `new LangGraphClient` 的 `onRequest` 钩子里塞 `Authorization`,并对 `runs.stream / runs.joinStream` 做 `sanitizeRunStreamOptions` 包装(见 `core/api/stream-mode.ts`),确保流式参数合法。
> **务必使用 `apiFetch` 而不是裸 `fetch`**,否则鉴权头不会带上,私有接口会 401。
### 6.3 路由级守护
WorkspaceRoutes 内的 `/admin/*` 与 `/settings/*` 路由在元素层面做了判定:
```tsx
getStoredAuth()?.system_role === "admin"
? <WorkspaceLayout>...</WorkspaceLayout>
: <Navigate to="/page/workspace/chats/new" replace />
```
非管理员被静默重定向到聊天页。
---
## 7. 与后端的通信
### 7.1 三种入口
| 通道 | 函数/客户端 | 用途 |
|---|---|---|
| LangGraph SDK | `getAPIClient(isMock?)` | LangGraph runtime:`threads.search/get/update/delete`,`runs.stream/joinStream` |
| HTTP fetch | `apiFetch(url, init)` | Gateway 业务接口(Agents/Memory/Skills/Tags/Uploads/Scheduled-tasks…) |
| 独立服务 | `getNoteApiBaseURL()` | 轻应用「笔记」服务 |
### 7.2 LangGraph 客户端缓存
`getAPIClient(isMock?)` 通过模块级 `Map` 进行客户端实例缓存(`default` / `mock`),避免重复构造。
### 7.3 流式聊天的核心:`useThreadStream`
定义于 [src/core/threads/hooks.ts](../src/core/threads/hooks.ts)。这是「整套前端的心脏」,所有聊天页都基于它:
```ts
const [thread, sendMessage, isUploading] = useThreadStream({
threadId,
context, // LocalSettings["context"]
isMock,
assistantId: "lead_agent",
onStart(threadId) { /* 第一次创建会话时回调,用于改写 URL */ },
onFinish(state) { /* 流式收束,浏览器后台时弹通知 */ },
onToolEnd(evt) { /* 工具调用结束钩子 */ },
});
```
关键能力:
1. **断线重连**:通过 `sessionStorage["lg:stream:<threadId>"]` 存储正在进行的 run_id,刷新页面后 SDK 自动 `joinStream` 续接。`normalizeStoredRunId()` 容错处理历史 URL/带 query 的情况。
2. **乐观更新**:发送即在本地 messages 后追加临时 `human` 消息;当文件正在上传时还追加占位 `ai` 消息("uploadingFiles");服务端有响应(messages 数量增加)后自动清空。
3. **首条消息特例**:仅当线程已持久化时立即触发 `onStart`;新会话需要等到 SDK `onCreated(meta.thread_id)`,确保 URL 同步用的是真正的服务端 thread_id。
4. **Context 装配**:每次提交时把 `thinking_enabled / is_plan_mode / subagent_enabled / reasoning_effort / memory_injection_enabled / thread_id` 等基于「四种 mode」推导:
| mode | thinking_enabled | is_plan_mode | subagent_enabled | reasoning_effort |
|---|---|---|---|---|
| `flash` | false | false | false | undefined |
| `thinking` | true | false | false | low |
| `pro` | true | true | false | medium |
| `ultra` | true | true | true | high |
5. **附件上传**:先 `uploadFiles(threadId, files)` 上传到 Gateway `/api/threads/:threadId/uploads`,然后将 `path/filename/size` 写入 `messages[0].additional_kwargs.files`,与 lead_agent 后端约定一致。
6. **TanStack Query 失效**:流式完成或新建会话时调用 `queryClient.invalidateQueries({ queryKey: ["threads", "search"] })`,让侧边栏「最近会话」自动刷新。
7. **错误降级**:`onError` 弹 sonner toast;`getStreamErrorMessage(error)` 兜底解析对象/嵌套字段。
### 7.4 重要回调事件
| SDK 事件 | 处理 |
|---|---|
| `onCreated(meta)` | 新线程创建:触发 `onStart`、把 agent_id 写入 thread metadata(非 mock) |
| `onLangChainEvent` | 监听 `on_tool_end` 并外抛 `onToolEnd` |
| `onUpdateEvent` | 若 `update.title` 出现,则更新 TanStack Query 缓存里的 thread 标题 |
| `onCustomEvent` | `task_running`:更新 SubtaskContext;`llm_retry`:弹 toast 提示 |
| `onError` | 清空乐观消息,弹 toast |
| `onFinish` | 触发外部 `onFinish` + 刷新 threads.search |
### 7.5 其他领域 hook 约定
所有领域目录(`agents/`、`skills/`、`memory/`、`tags/`、`scheduled-tasks/`、`notifications/`、`admin/`、`curator/`、`llm-metrics/`、`tool-metrics/`、`system-settings/`、`thread-shares/`、`recommended-questions/`)遵循统一模板:
```
core/<domain>/
├─ api.ts 底层调用(fetch + apiFetch)
├─ hooks.ts useQuery/useMutation 包装
├─ types.ts 接口类型
└─ index.ts re-export
```
写新领域时也按此模板维持一致。
---
## 8. 全局状态与本地存储
### 8.1 React Context(运行时状态)
| Context | 文件 | 作用域 | 状态 |
|---|---|---|---|
| `ChatContext` | [contexts/ChatContext.tsx](../src/contexts/ChatContext.tsx) | App 顶层 | 通用 QA 消息历史(localStorage: `qa-chat-history`) |
| `ThemeContext` | `contexts/ThemeContext.tsx` | App 顶层 | 主题,与 next-themes 协作 |
| `I18nContext` | [core/i18n/context.tsx](../src/core/i18n/context.tsx) | App 顶层 | 当前语言(Cookie: `locale`) |
| `SubtaskContext` | [core/tasks/context.tsx](../src/core/tasks/context.tsx) | ChatRuntime | 子任务最新 message |
| `ArtifactsContext` | [components/workspace/artifacts/context.tsx](../src/components/workspace/artifacts/context.tsx) | ChatRuntime | 当前会话产物列表与选中 |
| `PromptInputContext` | [components/ai-elements/prompt-input.tsx](../src/components/ai-elements/prompt-input.tsx) | ChatRuntime | 输入框附件/状态 |
| `RouteBaseContext` | [core/route-base.tsx](../src/core/route-base.tsx) | Workspace/Strategy/Roundtable | 给 `useRouter` shim 提供 base prefix |
| `ThreadContext` | `components/workspace/messages/context.ts` | 每个聊天页内 | 共享 `thread` 与 `isMock` 给 MessageList 等子组件 |
### 8.2 LocalSettings(用户偏好)
[core/settings/local.ts](../src/core/settings/local.ts) + [core/settings/store.ts](../src/core/settings/store.ts) + [core/settings/hooks.ts](../src/core/settings/hooks.ts):
```ts
LocalSettings = {
notification: { enabled: boolean },
display: { streamSpeedMeterEnabled: boolean },
context: {
model_name?: string,
mode?: "flash" | "thinking" | "pro" | "ultra",
reasoning_effort?: "minimal"|"low"|"medium"|"high",
writing_mode?: boolean,
memory_injection_enabled?: boolean,
agent_id?: string,
}
}
```
- 持久化:`localStorage["deerflow.local-settings"]`
- 线程级模型覆盖:`localStorage["deerflow.thread-model.<threadId>"]`,通过 `useThreadSettings(threadId)` 自动合并
- 实现:自定义的「订阅 + storage 事件」store,借助 `useSyncExternalStore` 让多标签页/多组件同步
### 8.3 sessionStorage
| Key | 写入位置 | 说明 |
|---|---|---|
| `lg:stream:<threadId>` | useThreadStream(SDK 内部) | 当前 run_id,用于 reconnect |
| `pendingAgentMessage` | ChatPage 提交时(含 agentId) | 跳转 AgentChatPage 后续发首条消息 |
---
## 9. 组件层次
### 9.1 `src/components/ui/`
shadcn 风格的「无业务」基础组件:`button`、`badge`、`avatar`、`card`、`carousel`、`command`、`dialog`、`dropdown-menu`、`hover-card`、`input`、`input-group`、`item`、`progress`、`resizable`、`scroll-area`、`select`、`separator`、`sidebar`、`skeleton`、`sonner`、`switch`、`tabs`、`textarea`、`toggle`、`toggle-group`、`tooltip` 等。
特色组件:
- `aurora-text` / `flickering-grid` / `magic-bento` / `spotlight-card` / `shine-border` / `confetti-button` —— Landing 视觉效果
- `terminal` —— 终端样式
- `number-ticker` / `word-rotate` —— 动画文本(基于 `motion`)
### 9.2 `src/components/ai-elements/`
AI 业务可复用组件,是聊天页的「乐高」:
| 组件 | 用途 |
|---|---|
| `prompt-input` | 输入区域容器、附件、提交按钮、模式选择 |
| `conversation` | 消息流容器(自动滚动到底,依赖 `use-stick-to-bottom`) |
| `message` / `message-content` | 消息气泡 |
| `reasoning` | 折叠的「思考」区段 |
| `plan` | Plan-Mode 任务计划 |
| `chain-of-thought` | 步骤链路 |
| `task` | 单步任务展示 |
| `checkpoint` | 流程节点 |
| `sources` | 来源引用列表 |
| `suggestion` | 建议追问/快捷动作 |
| `code-block` | 代码块(shiki/syntax highlighter) |
| `model-selector` | 模型下拉 |
| `web-preview` / `image` | 网页/图片预览 |
| `canvas` / `node` / `edge` / `controls` | xyflow 流程图原语 |
| `loader` / `shimmer` / `toolbar` / `panel` / `queue` | 辅助 UI |
| `open-in-chat` | 「在聊天中打开」入口 |
### 9.3 `src/components/workspace/`
工作台模块,承载 90% 业务页面:
```
workspace/
├─ workspace-sidebar.tsx 右栏导航
├─ workspace-header.tsx 顶部
├─ workspace-nav-menu.tsx
├─ workspace-nav-chat-list.tsx 最近会话
├─ workspace-container.tsx 业务包裹器
├─ command-palette.tsx ⌘K
├─ thread-title.tsx
├─ welcome.tsx / agent-welcome.tsx
├─ todo-list.tsx
├─ token-usage-indicator.tsx
├─ streaming-indicator.tsx
├─ input-box.tsx 基于 PromptInput 的实际输入框(模式、Agent、附件)
├─ export-trigger.tsx 导出会话 docx/md
├─ thread-share-dialogs.tsx 创建/管理只读分享链接
├─ recent-chat-list.tsx / recent-writing-list.tsx
├─ chats/
│ ├─ chat-box.tsx 左右双栏 (聊天 + Artifacts),react-resizable-panels
│ ├─ use-thread-chat.ts 解析 :thread_id(new -> uuid)
│ └─ use-chat-mode.ts 特定 mode hook
├─ messages/
│ ├─ message-list.tsx 渲染所有消息、groupMessages
│ ├─ message-group.tsx
│ ├─ message-list-item.tsx
│ ├─ markdown-content.tsx react-markdown + rehype-katex + rehype-raw + remark-gfm + remark-math
│ ├─ subtask-card.tsx
│ ├─ message-token-usage.tsx
│ ├─ use-message-metrics.ts
│ ├─ download-word-button.tsx
│ ├─ context.ts ThreadContext
│ └─ skeleton.tsx
├─ artifacts/
│ ├─ context.tsx ArtifactsContext + Provider
│ ├─ artifact-file-list.tsx
│ ├─ artifact-file-detail.tsx
│ └─ artifact-trigger.tsx 顶部「打开 Artifacts」按钮
├─ citations/ 引用链接渲染
├─ settings/ 设置弹窗与子页(appearance/account/skill/memory/tool/about)
├─ admin/ 管理员页(user-management / tag-management / admin-user-hub / tool-metrics / memory)
├─ agents/ Agent gallery、selector、manage dialog
├─ skills/ skill-detail-dialog、skill-evolution-panel
├─ scheduled-tasks/ schedule-builder
├─ tags/ tag-picker-dialog、tag-chip、tag-search-bar、tag-colors
└─ ...
```
### 9.4 `src/open-canvas/`
AI 写作 / 画布编辑器(基于 `@tiptap/*`):
- `routes/OpenCanvasRoutes.tsx` 二级路由
- `pages/`:CanvasHomePage / CanvasThreadPage / AIWritingPage / ArticleTypesConfigPage
- `editor/TiptapEditor.tsx` + `TiptapExtensions.ts` + `AiHighlightExtension.ts`
- `contexts/`:Canvas 自有的 Stream/Thread/Graph/UI Context(与主聊天页解耦)
- `hooks/`:`useCanvasStream`、`useCanvasArtifact`、`useCanvasThread`、`useWritingRewrite`、`useDocumentOutline` 等
- `api/`:自己封装 `/canvas/runs` `/canvas/threads` `/rewrite` `/writing-export` `/ai-writing-sessions`
- 独立 LangGraph 服务地址通过 `VITE_CANVAS_LANGGRAPH_BASE_URL` 配置;未配置则与主聊天同源(仅 assistantId 不同)
### 9.5 `src/roundtable-planning/`
圆桌规划页(多 Agent 协同生成方案):
- `pages/RoundtablePlanningPage.tsx` 主页
- `components/`:Step1/Step2/Step3 面板、MessageBubble、PersonaDrawer、HighFidelityReport、RecommendAgentsDialog
- `hooks/`:`useStep1Intent`、`useStep2Orchestration`、`useAutoScroll`、`useDraftPersistence`
- `api/`:intent / multi-agent / recommend
- `lib/`:clarification 规则、reasoning 工具、step-display
- 自带 `styles/roundtable-planning.css`
### 9.6 `src/strategy-components/`
策略类页面(含 QA 中心 / Agent QA / Collaborative QA / MopeChat),其中部分仍保留 `.jsx` 旧文件。新增功能优先迁移到 `.tsx`。
---
## 10. 样式与主题
### 10.1 TailwindCSS v4
- `postcss.config.js` 引入 `@tailwindcss/postcss`
- 入口样式:[src/styles/globals.css](../src/styles/globals.css)
- 配合 `tw-animate-css` 提供常用动效
- `cn(...)` 工具([src/lib/utils.ts](../src/lib/utils.ts))= `twMerge(clsx(...))`,是合并 className 的「事实标准」
### 10.2 主题
- 顶层用 [next-themes](https://github.com/pacocoursey/next-themes),`attribute="class"`,`storageKey="strategy-theme"`,默认 `light`,`/landing` 强制 light
- 自定义 `ThemeProvider`(`contexts/ThemeContext.tsx`)暴露 `theme` 给少量组件做白底 sidebar 等微调(见 [workspace-sidebar.tsx:27](../src/components/workspace/workspace-sidebar.tsx#L27))
- `index.html` 早期注入脚本读取 storage,避免 FOUC
### 10.3 暗色样式
- `.dark` 类位于 `<html>`,Tailwind v4 通过 `darkMode: class`(约定)匹配
---
## 11. 国际化(i18n)
[src/core/i18n/](../src/core/i18n/):
- `locale.ts` 提供 `detectLocale()`,按 navigator 推断
- `translations.ts` / `locales/index.ts` 汇总词条
- `context.tsx` 暴露 `I18nProvider` + `useI18nContext()`
- `hooks.ts` 提供 `useI18n()`,返回 `{ t, locale, setLocale }`
- 切换语言写 Cookie(`locale=xxx`),下次访问加载对应词条
业务里固定写法:
```tsx
const { t } = useI18n();
return <span>{t.common.loading}</span>;
```
---
## 12. 路由层的 Next.js 兼容
[src/shims/next-navigation.ts](../src/shims/next-navigation.ts) 通过 react-router-dom 实现了:
- `useRouter()`:`push/replace/back/forward/refresh/prefetch`(其中 push/replace 会将旧 base `/page/workspace` 替换成 `RouteBaseContext` 值)
- `usePathname()`:`useLocation().pathname`
- `useParams<T>()`:原样转发
- `useSearchParams()`:只返回 `searchParams`(注意:**不是 tuple**,与 Next.js 习惯一致)
[src/shims/next-link.tsx](../src/shims/next-link.tsx) 把 `<Link href=...>` 桥接到 `<a>`/`<NavLink>`。
> 新代码可继续使用 `next/link`、`next/navigation`,但请避免依赖 SSR/数据获取等 Next.js 高阶特性。
---
## 13. 渲染与 Markdown
[src/components/workspace/messages/markdown-content.tsx](../src/components/workspace/messages/markdown-content.tsx) 使用 `react-markdown` + 以下插件:
- `remark-gfm` —— GFM 表格、删除线
- `remark-math` + `rehype-katex` —— 数学公式
- `rehype-raw` —— 渲染内嵌 HTML
- [core/rehype](../src/core/rehype/index.ts) 中的 `useRehypeSplitWordsIntoSpans()`:流式追加时按词包 `<span>`,配合 CSS 渐入动画
- [core/streamdown](../src/core/streamdown/index.ts) —— 流式 Markdown 解析(`streamdown` 包)
- 代码块用 `shiki`(高亮)+ `react-syntax-highlighter`(兜底)
引用渲染:[components/workspace/citations/citation-link.tsx](../src/components/workspace/citations/citation-link.tsx) 和 `artifact-link.tsx` 将 `[[uuid]]` 风格引用转成可悬浮的链接卡片。
---
## 14. 上传与产物(Artifacts)
### 14.1 上传
- `uploadFiles(threadId, files)`:POST 到 `/api/threads/:threadId/uploads`(FormData,`files` 字段重复多份)
- 大小/类型/数量校验:[core/uploads/file-validation.ts](../src/core/uploads/file-validation.ts)
- PromptInput 附件解析:[core/uploads/prompt-input-files.ts](../src/core/uploads/prompt-input-files.ts) 提供 `promptInputFilePartToFile()`,把组件 UI Part 转为 `File`
- 失败容错:`useThreadStream` 中若有任意 file 转换失败,会抛出 toast 并清掉乐观消息
### 14.2 产物
- 服务端在 `thread.values.artifacts: string[]` 里报路径
- `ArtifactsProvider`([components/workspace/artifacts/context.tsx](../src/components/workspace/artifacts/context.tsx))做三层状态:所有产物、选中产物、面板开关
- `ChatBox` 用 `react-resizable-panels` 拉出右侧 40% 面板,展示 `ArtifactFileList` / `ArtifactFileDetail`
- 选中 Markdown 产物时,「写作模式」会把 `writing_artifact_path` 注入 context([ChatPage.tsx:157-159](../src/pages/ChatPage.tsx#L157-L159)),后端 PromptPrefix/Writing 中间件会接管
---
## 15. 模型/模式/Token 用量
### 15.1 useModels
[core/models/hooks.ts](../src/core/models/hooks.ts):
```ts
const { models, tokenUsageEnabled, isLoading } = useModels();
```
- `loadModels()` 调 Gateway `/api/models`(具体见 `core/models/api.ts`)
- `tokenUsageEnabled` 控制顶部 `<TokenUsageIndicator>` 是否显示
### 15.2 模式 (mode)
`flash | thinking | pro | ultra` 四档(见 §7.3 表),由 `<InputBox>` 中的 mode 切换器修改 `LocalSettings.context.mode`。`ModeHoverGuide` 提供悬浮说明。
### 15.3 Token 用量
- 顶部全局:[components/workspace/token-usage-indicator.tsx](../src/components/workspace/token-usage-indicator.tsx) 累计当前线程
- 单条消息:[components/workspace/messages/message-token-usage.tsx](../src/components/workspace/messages/message-token-usage.tsx) + `use-message-metrics.ts`,依赖 `tokenlens` 包
---
## 16. 通知、快捷键、命令面板
- 浏览器通知:[core/notification/hooks.ts](../src/core/notification/hooks.ts) 提供 `useNotification().showNotification(title, opts)`,在 `document.hidden || !hasFocus()` 时调用
- 全局快捷键:[hooks/use-global-shortcuts.ts](../src/hooks/use-global-shortcuts.ts),统一注册到 `window keydown`,过滤掉 input/textarea/contentEditable,但 `⌘K` 例外(用于唤起命令面板)
- 命令面板:[components/workspace/command-palette.tsx](../src/components/workspace/command-palette.tsx)(基于 `cmdk`)
---
## 17. 定时任务、分享
### 17.1 定时任务
- [core/scheduled-tasks/](../src/core/scheduled-tasks/) 提供 cron / one-shot 任务的 CRUD 与运行历史
- [components/workspace/scheduled-tasks/schedule-builder.tsx](../src/components/workspace/scheduled-tasks/schedule-builder.tsx) 是创建/编辑 UI
- 详情页 `ScheduledTaskRunDetailPage` 复用 MessageList 渲染历史一次运行
- `markdown-docx.ts` 用 `docx` 包导出 Word
### 17.2 只读分享
- 创建分享:[components/workspace/thread-share-dialogs.tsx](../src/components/workspace/thread-share-dialogs.tsx) + `core/thread-shares/`
- 只读页 `PublicSharePage`(路由 `/share/:shareCode`)/ `PublicScheduledTaskPage`(`/public/scheduled-tasks/:taskId`),不需要登录
- iframe 嵌入问答 `EmbedChatPage`(`/embed/chats/:thread_id`):第三方用普通 iframe 嵌套,父页面用 `sessionId` 轮询公开 API 判断问答是否结束,详见 [embed-iframe-chat.md](./embed-iframe-chat.md)
- 后端会用 share token 做权限校验,前端通过 `apiFetch` 自动带
---
## 18. 调试与测试
### 18.1 类型检查
```bash
pnpm typecheck
```
工程没有内置 ESLint/Prettier 配置(依赖 IDE/CI),但有少量 `// eslint-disable-next-line react-hooks/exhaustive-deps` 注释提示 dependencies 故意省略。
### 18.2 Mock 模式
URL 加 `?mock=true` 时,`useThreadChat()` 设 `isMock=true`,`getAPIClient(true)` 使用 `mock` 客户端,`getLangGraphBaseURL(true)` 回退到 `${origin}/mock/api`,便于离线复现 UI(需要后端或本地 mock server 配合)。
### 18.3 演示模式
`VITE_STATIC_WEBSITE_ONLY=true` 时:
- 输入框 disabled,提示「演示模式不可用」
- 自动展开 Artifacts 面板并选中第一个产物
- 用于把构建产物嵌入营销页 / 静态主页
### 18.4 在浏览器调试 SSE
LangGraph SDK 走标准 SSE。开发期通过 Chrome DevTools `Network -> EventSource` 可以直接查看 frames。失败原因(如 422)可在 sonner toast 看到 `getStreamErrorMessage()` 的解析结果。
---
## 19. 常见开发任务指南
### 19.1 新增一个聊天页内的子组件
1. 在 `src/components/workspace/` 下建目录与 `index.ts`
2. 通过 `useThread()`(来自 `messages/context.ts`)拿到当前 `thread`
3. 业务展示用 `<MessageList>` / `<MarkdownContent>` 现有组件,避免自行 Markdown 解析
4. 若依赖产物,使用 `useArtifacts()`
### 19.2 新增一个 LangGraph Assistant(自定义 Agent)
1. 后端注册新的 graph
2. 前端 `core/agents/api.ts` 增 CRUD 接口;`hooks.ts` 增 `useAgents/useAgent/useCreateAgent`
3. 路由:复用 `AgentChatPage`,URL `/page/workspace/agents/:agent_id/chats/:thread_id`
4. 提交时只需把 `assistantId` 传给 `useThreadStream`(参考 AgentChatPage 中传 `agent_id` 给后端 graph 的方式)
### 19.3 新增一个领域 API
1. 在 `core/<domain>/` 建 `api.ts/hooks.ts/types.ts/index.ts`
2. `api.ts` 函数全部使用 `apiFetch(`${getBackendBaseURL()}/api/...`)`
3. `hooks.ts` 用 `useQuery({ queryKey: ["<domain>", ...] })` / `useMutation({ onSuccess: () => invalidateQueries })`
4. 在使用方 import `useXxx` 即可
### 19.4 新增一个全局快捷键
```tsx
useGlobalShortcuts([
{ key: "k", meta: true, action: openCommandPalette },
{ key: "/", meta: false, shift: true, action: showHelp },
]);
```
### 19.5 修改本地偏好
```tsx
const [settings, setSettings] = useLocalSettings();
setSettings("display", { streamSpeedMeterEnabled: false });
```
线程级:
```tsx
const [settings, setSettings] = useThreadSettings(threadId);
setSettings("context", { model_name: "gpt-5" }); // 仅当前线程
```
### 19.6 新增 i18n 词条
1. 在 [core/i18n/translations.ts](../src/core/i18n/translations.ts) 与 `locales/` 下补充
2. `useI18n().t.xxx.yyy` 使用
3. 不要硬编码中英文文案到组件里
---
## 20. 性能与稳定性约定
- **TanStack Query**:键统一以「领域 + 操作 + params」组合(如 `["threads", "search", params]`、`["agents", id]`);mutate 之后 invalidate;写时支持 `setQueriesData` 立即生效更新缓存(参考 `useDeleteThread`/`useRenameThread`)
- **流式断线续连**:保持 `useThreadStream` 的 `reconnectOnMount` 启用(已默认);切换线程时会 reset `runMetadataStorageRef` 以确保不串
- **乐观更新**:发送 message 立即出现 human 气泡,避免空白等待;附件场景额外提供 uploadingFiles 占位
- **避免重复发送**:`sendInFlightRef` 在 `useThreadStream` 里做单飞锁
- **不阻塞主线程**:长文 markdown 拆词、渲染时分批;`use-stick-to-bottom` 自动暂停跟随
- **大依赖懒加载**:建议对低频路由(如 `Open Canvas`、`RoundtablePlanning`)使用 `React.lazy` + `Suspense`(当前已大致按模块切分,文件级可继续优化)
- **构建内存**:`pnpm build` 已 `--max-old-space-size=8192`,新增大依赖请关注 dist 体积与构建时长
---
## 21. 部署
- 默认构建产物 `dist/`,因 `base: "./"` 可直接置于任何静态文件路径
- HashRouter 不需要后端 fallback,但建议把所有未匹配请求回退到 `index.html`,便于直接访问深链
- 后端地址通过环境变量在 build-time 注入(Vite 把 `VITE_*` 内联到 bundle 中),不同环境请构建不同产物或使用窗口注入的方式
- 内网部署时常配合 Nginx 反代 `/api -> 后端 Gateway`、`/api/langgraph -> LangGraph Server`,让前端使用同源相对路径无需额外 CORS
---
## 22. 已知历史包袱与注意事项
1. **HashRouter + replaceState**:新建线程时 `history.replaceState` 直接改写 hash,不再触发 router 渲染。若以后改用 BrowserRouter,需要重写此处(参考 [ChatPage.tsx:75-79](../src/pages/ChatPage.tsx#L75-L79) 与 [chats/use-thread-chat.ts](../src/components/workspace/chats/use-thread-chat.ts) 的兜底)
2. **`"use client"` 指令**:迁移自 Next.js,在 Vite 中无意义但保留可读性,新文件可省略
3. **Next.js shim**:尽量使用 `useRouteNavigate()`([core/route-base.tsx](../src/core/route-base.tsx))代替原始的 `useRouter().push("/page/workspace/...")`,因为后者依赖 base 替换,与你所在的路由模块强相关
4. **混合 jsx/tsx**:`strategy-components/**` 内仍有 `.jsx`,新增请直接用 `.tsx` 并补全类型
5. **TDesign**:仅在少量页面被使用,避免与 shadcn UI 风格混用;优先复用 `components/ui/`
6. **Open Canvas / Roundtable**:各自有独立的 Stream/Thread Context 与 API;不要直接在它们里复用主聊天的 `useThreadStream`,反之亦然
7. **生产模式默认是 light,但 `index.html` 早期脚本默认 dark**:如果改默认主题,要同时改 [App.tsx:29](../src/App.tsx#L29) 的 `defaultTheme` 和 [index.html](../index.html) 的脚本
---
## 23. 速查表
| 我想… | 去哪里看 / 怎么写 |
|---|---|
| 改后端地址 | `.env.development[.local]` 中的 `VITE_BACKEND_BASE_URL` / `VITE_LANGGRAPH_BASE_URL` |
| 新增路由 | `pages/WorkspaceRoutes.tsx`(工作台)或 `pages/PageRoutes.tsx`(一级) |
| 发起 GET/POST | `apiFetch(`${getBackendBaseURL()}/api/...`, { method, body })` |
| 发起 SSE 聊天 | `useThreadStream({ threadId, context, ... })` |
| 跨标签同步偏好 | `useLocalSettings()` / `useThreadSettings(threadId)` |
| 弹 toast | `import { toast } from "sonner"; toast.success("done")` |
| 弹浏览器通知 | `useNotification().showNotification(title, { body })` |
| 国际化 | `const { t } = useI18n(); t.common.loading` |
| 合并 className | `cn("base", cond && "x-y")` |
| 获取登录态 | `getStoredAuth()` / `getAccessToken()` |
| 命令面板 | `⌘K` 唤起 `<CommandPalette/>` |
---
## 24. 后续建议
- **统一目录命名**:`workspace/messages/`、`workspace/artifacts/`、`workspace/chats/` 现都用 kebab-case 文件夹 + kebab-case 文件名;建议在新增文件时延续此约定
- **抽离配置常量**:与服务端契约相关的硬编码(如 `assistantId = "lead_agent"`)建议集中到 `core/config/`
- **测试**:当前缺单测;优先为 `core/threads/hooks.ts` 的 `normalizeStoredRunId` 等纯函数补 vitest
- **包体积**:landing 视觉特效(`ogl`、`gsap`、`canvas-confetti`、`embla-carousel`)较重,可考虑路由级 code-split
- **lint/format**:建议引入 ESLint + Prettier + lint-staged,统一团队风格
---
## 25. 页面清单(Page Catalog)
> 本节按一级路径分组,列出 [src/pages/](../src/pages/)、[src/open-canvas/pages/](../src/open-canvas/pages/) 与 [src/roundtable-planning/pages/](../src/roundtable-planning/pages/) 下的每一个页面:**做什么** + **挂载在哪里** + **关键依赖与组件**。
### 25.1 顶层路由(无 `/page` 前缀)
#### LandingPage —— `/landing`
- 文件:[LandingPage.tsx](../src/pages/LandingPage.tsx)
- 作用:营销/介绍主页,深色背景;与登录后的工作台无关
- 强制 `light` 主题被 [App.tsx:23](../src/App.tsx#L23) 关掉,单独使用 `bg-[#0a0a0a]`
- 包含组件:
- [Header](../src/components/landing/header.tsx)、[Footer](../src/components/landing/footer.tsx)
- [Hero](../src/components/landing/hero.tsx) —— 首屏 Slogan + CTA
- [CaseStudySection](../src/components/landing/sections/case-study-section.tsx) —— 案例
- [SkillsSection](../src/components/landing/sections/skills-section.tsx) + [ProgressiveSkillsAnimation](../src/components/landing/progressive-skills-animation.tsx)
- [SandboxSection](../src/components/landing/sections/sandbox-section.tsx) + [Terminal](../src/components/ui/terminal.tsx)
- [WhatsNewSection](../src/components/landing/sections/whats-new-section.tsx) + [PostList](../src/components/landing/post-list.tsx)
- [CommunitySection](../src/components/landing/sections/community-section.tsx)
- 视觉特效依赖:`aurora-text`、`flickering-grid`、`magic-bento`、`spotlight-card`、`shine-border`、`confetti-button`、`number-ticker`、`word-rotate`、`gsap`、`ogl`、`canvas-confetti`、`embla-carousel`
#### LoginPage —— `/login`、`/login/:username`
- 文件:[LoginPage.tsx](../src/pages/LoginPage.tsx)
- 作用:以用户名直登(默认 `guest`),从 query 解析 `pageSidebarEnabled` 偏好,登录成功后写入 `localStorage["deerflow.auth"]` 并跳到 `/page/workspace/chats/new`
- 关键依赖:[loginByUsername](../src/core/auth/api.ts)、[setStoredAuth](../src/core/auth/index.ts)、[persistSidebarEnabled](../src/core/page-layout/sidebar-enabled.ts)
- UI:极简卡片(Tailwind),仅在 `error` 时显示失败原因
#### PublicSharePage —— `/share/:shareCode`
- 文件:[PublicSharePage.tsx](../src/pages/PublicSharePage.tsx)
- 作用:通过分享码免登录查看一段会话快照(含产物面板)
- 数据:[getPublicShareSnapshot](../src/core/thread-shares/api.ts)
- 内部布局:自建左右双栏(不依赖 WorkspaceLayout)
- 左:[MessageList](../src/components/workspace/messages/message-list.tsx)
- 右:[ArtifactFileDetail](../src/components/workspace/artifacts/artifact-file-detail.tsx)(点击文件后才显示)
- 自带 `<ArtifactsProvider>` + `<SubtasksProvider>` + `<ThreadContext.Provider>`,用快照模拟 `BaseStream<AgentThreadState>`
#### PublicScheduledTaskPage —— `/public/scheduled-tasks/:taskId`
- 文件:[PublicScheduledTaskPage.tsx](../src/pages/PublicScheduledTaskPage.tsx)
- 作用:免登录查看「已发布」的定时任务详情、最近运行结果、可下载文件
- 包含组件:`Card`、`Tabs`、`Dialog`、`Tooltip`、`Badge`、[MarkdownContent](../src/components/workspace/messages/markdown-content.tsx)、[downloadMarkdownAsDocx](../src/core/scheduled-tasks/markdown-docx.ts)
#### NotFoundPage —— `*`
- 文件:[NotFoundPage.tsx](../src/pages/NotFoundPage.tsx)
- 作用:404 兜底,给一个 "Open workspace" 链接回到 `/page/workspace/chats/new`
### 25.2 `/page/workspace/*`(工作台主区)
> 所有页面外层套 `<WorkspaceLayout>`(侧栏 + 内容区)。聊天类页面再多一层 `<ChatRuntime>`(SubtasksProvider/ArtifactsProvider/PromptInputProvider)。
#### ChatsPage —— `/page/workspace/chats`
- 文件:[ChatsPage.tsx](../src/pages/ChatsPage.tsx)
- 作用:会话列表与搜索;可触发「导入聊天记录」对话框
- 数据:[useThreads()](../src/core/threads/hooks.ts) —— 过滤掉 `thread_type === "scheduler"`
- 包含组件:
- [WorkspaceContainer/Header/Body](../src/components/workspace/workspace-container.tsx)
- `Input`(搜索)、`Button`、`ScrollArea`、`Link`
- [ImportConversationsDialog](../src/components/workspace/thread-share-dialogs.tsx)
- 工具:`titleOfThread`、`usePathOfThread`、`formatTimeAgo`、`desensitize`
#### ChatPage —— `/page/workspace/chats/:thread_id`
- 文件:[ChatPage.tsx](../src/pages/ChatPage.tsx)
- 作用:主聊天页(绑定 `assistantId="lead_agent"`),支持新会话与已有会话;首条消息时若选中了 Agent,会跳转到 AgentChatPage
- Hook:[useThreadStream](../src/core/threads/hooks.ts)、[useThreadChat](../src/components/workspace/chats/use-thread-chat.ts)、[useThreadSettings](../src/core/settings/hooks.ts)、[useModels](../src/core/models/hooks.ts)、[useArtifacts](../src/components/workspace/artifacts/context.tsx)、[usePromptPrefixSettings](../src/core/system-settings/hooks.ts)
- 主要组件:
- [ChatBox](../src/components/workspace/chats/chat-box.tsx)(左聊天 / 右产物 双栏)
- [MessageList](../src/components/workspace/messages/message-list.tsx)
- [InputBox](../src/components/workspace/input-box.tsx) + [AgentSelector](../src/components/workspace/agents/agent-selector.tsx)
- [ThreadTitle](../src/components/workspace/thread-title.tsx)
- [TodoList](../src/components/workspace/todo-list.tsx)
- [TokenUsageIndicator](../src/components/workspace/token-usage-indicator.tsx)
- [ExportTrigger](../src/components/workspace/export-trigger.tsx)、[ArtifactTrigger](../src/components/workspace/artifacts/artifact-trigger.tsx)
- [Welcome](../src/components/workspace/welcome.tsx)(落地态欢迎)
#### AgentChatPage —— `/page/workspace/agents/:agent_id/chats/:thread_id`
- 文件:[AgentChatPage.tsx](../src/pages/AgentChatPage.tsx)
- 作用:与自定义 Agent 对话。`assistantId=agent_id`,context 注入 `agent_id`。会消费 `sessionStorage["pendingAgentMessage"]` 来自动发送 ChatPage 转交的首条消息
- Hook:与 ChatPage 类似,但额外 `useAgent(agent_id)`
- 关键组件:与 ChatPage 同一套;顶栏额外渲染 Agent 名称 Badge;落地态使用 [AgentWelcome](../src/components/workspace/agent-welcome.tsx)
#### AgentsPage —— `/page/workspace/agents`
- 文件:[AgentsPage.tsx](../src/pages/AgentsPage.tsx)(薄壳,直接渲染 [AgentGallery](../src/components/workspace/agents/agent-gallery.tsx))
- 作用:Agent 画廊;含分类、标签、特色、搜索、最近使用
- 子组件:[AgentCard](../src/components/workspace/agents/agent-card.tsx)、[AgentManageDialog](../src/components/workspace/agents/agent-manage-dialog.tsx)、[TagSearchBar](../src/components/workspace/tags/tag-search-bar.tsx)、[TagPickerDialog](../src/components/workspace/tags/tag-picker-dialog.tsx)、[TagChipList](../src/components/workspace/tags/tag-chip.tsx)
#### NewAgentPage —— `/page/workspace/agents/new`
- 文件:[NewAgentPage.tsx](../src/pages/NewAgentPage.tsx)
- 作用:以「对话」的方式引导用户创建 Agent —— 复用 lead_agent 的 `is_bootstrap=true` 模式 + `SetupAgentApprovalMiddleware` 中拦截的 `setup_agent` 工具调用,让 LLM 给出 setup_agent 调用并由用户点「批准」最终落库
- Hook:[useThreadStream](../src/core/threads/hooks.ts) + [useSkills](../src/core/skills/hooks.ts) + [useModels](../src/core/models/hooks.ts) + [checkAgentName/createAgent/updateAgent](../src/core/agents/api.ts)
- 组件:
- 左:表单(名称/描述/灵魂 prompt/模型/技能多选)
- 右:`<PromptInput>` + `<MessageList>`(带 SetupAgentApprovalPayload 审批 UI)
- [SkillDetailDialog](../src/components/workspace/skills/skill-detail-dialog.tsx)
- 包了 `<ArtifactsProvider>` + `<ThreadContext.Provider>` 与 ChatPage 同源
#### ScheduledTasksPage —— `/page/workspace/scheduled/tasks`
- 文件:[ScheduledTasksPage.tsx](../src/pages/ScheduledTasksPage.tsx)
- 作用:当前用户的定时任务列表 + Cron 配置 + 已发布任务/可订阅、运行历史快览
- Hook:[useScheduledTasks](../src/core/scheduled-tasks/hooks.ts)、`useScheduledTaskRuns`、`useScheduledTaskActions`、`usePublishedScheduledTasks`、`useScheduledTaskDeliveryProfile`、[useAgents](../src/core/agents/index.ts)
- 关键组件:
- [WorkspaceContainer/Header/Body](../src/components/workspace/workspace-container.tsx)
- [ScheduleBuilder](../src/components/workspace/scheduled-tasks/schedule-builder.tsx) + `buildCron/parseCron`
- [TagChipList](../src/components/workspace/tags/tag-chip.tsx) + [TagPickerDialog](../src/components/workspace/tags/tag-picker-dialog.tsx) + [TagSearchBar](../src/components/workspace/tags/tag-search-bar.tsx)
- `Card / Dialog / Tabs / Select / Switch / Textarea / Badge / Button`
#### ScheduledTaskRunDetailPage —— `/page/workspace/scheduled/runs/:run_id`
- 文件:[ScheduledTaskRunDetailPage.tsx](../src/pages/ScheduledTaskRunDetailPage.tsx)
- 作用:单次定时任务运行的详情;展示消息时间线、产物文件、状态、可重命名/删除/导出 docx
- Hook:[useScheduledTaskRun](../src/core/scheduled-tasks/hooks.ts)、`useScheduledTaskRunMessages`、`useScheduledTaskActions`、`getScheduledTaskRunFileContent`、`updateScheduledTaskRunFile`、`downloadScheduledTaskRunFile`
- 组件:`Tabs`、`Card`、[MarkdownContent](../src/components/workspace/messages/markdown-content.tsx)、`Textarea`(编辑结果文件)
#### ScheduledChatPage(未在路由中显式注册,但留作入口跳转)
- 文件:[ScheduledChatPage.tsx](../src/pages/ScheduledChatPage.tsx)
- 作用:从 `useSchedulerThread()` 拿到「调度器助手」线程 id 后立即 `Navigate` 到 `/page/workspace/chats/<id>`,相当于一个智能跳板
- 组件:[WorkspaceContainer/Body](../src/components/workspace/workspace-container.tsx) + Loading 提示文案
#### SkillsPage —— `/page/workspace/skills`
- 文件:[SkillsPage.tsx](../src/pages/SkillsPage.tsx)(薄壳)→ [SkillSettingsPage](../src/components/workspace/settings/skill-settings-page.tsx)
- 作用:技能开关 + MCP Server 编辑入口
- 子组件:[McpServerEditDialog](../src/components/workspace/settings/mcp-server-edit-dialog.tsx)、[SkillStateSheet](../src/components/workspace/settings/skill-state-sheet.tsx)
#### SkillEvolutionPage —— `/page/workspace/skills/evolution`
- 文件:[SkillEvolutionPage.tsx](../src/pages/SkillEvolutionPage.tsx)(薄壳)→ [SkillEvolutionPanel](../src/components/workspace/skills/skill-evolution-panel.tsx)
- 作用:技能演化(实验/进化曲线)展示
#### MemoryPage —— `/page/workspace/memory`
- 文件:[MemoryPage.tsx](../src/pages/MemoryPage.tsx)(薄壳)→ [MemorySettingsPage](../src/components/workspace/settings/memory-settings-page.tsx)
- 作用:当前用户的记忆桶(事实/偏好/项目笔记)管理
- 依赖 [core/memory/api.ts](../src/core/memory/api.ts) + `hooks.ts`
#### AIWritingPage —— `/page/workspace/ai-writing`
- 文件:[open-canvas/pages/AIWritingPage.tsx](../src/open-canvas/pages/AIWritingPage.tsx)(带 `hideAdvancedFields`)
- 作用:AI 写作主界面(左草稿/中聊天/右时间线),WorkspaceLayout 内嵌入
- 见 §25.4 详解
#### RoundtablePlanningPage —— `/page/workspace/roundtable/planning`
- 文件:[roundtable-planning/pages/RoundtablePlanningPage.tsx](../src/roundtable-planning/pages/RoundtablePlanningPage.tsx)
- 作用:圆桌规划三步流程(任务理解 → 编排 → 高保真报告)
- 见 §25.5 详解
### 25.3 管理员页(`/page/workspace/admin/*` 与 `settings/*`)
> 路由元素层做了 `getStoredAuth()?.system_role === "admin"` 判断,非管理员被重定向到 `chats/new`。
#### AdminUserHubPage —— `/page/workspace/admin`
- 文件:[AdminUserHubPage.tsx](../src/pages/AdminUserHubPage.tsx)(薄壳)→ [AdminUserHub](../src/components/workspace/admin/admin-user-hub.tsx)
- 作用:统一的「用户中心」聚合页(用户列表、活动、记忆查看),替代旧的多分支管理路由
- 旧路径 `admin/users` / `admin/user-activity` / `admin/memory` 均 `Navigate` 到此页
#### AdminScheduledTasksPage —— `/page/workspace/admin/scheduled-tasks`
- 文件:[AdminScheduledTasksPage.tsx](../src/pages/AdminScheduledTasksPage.tsx)
- 作用:全平台定时任务总览、暂停/恢复/编辑/删除/导出
- Hook:[useAllScheduledTasks](../src/core/scheduled-tasks/hooks.ts)、`useAdminScheduledTaskActions`、`useAdminScheduledTaskRuns`
- 组件:[WorkspaceContainer/Header/Body](../src/components/workspace/workspace-container.tsx)、[ScheduleBuilder](../src/components/workspace/scheduled-tasks/schedule-builder.tsx)、`Card`、`Dialog`、`Badge`、`Input`、`Textarea`
#### AdminUserActivityPage(路由已并入 AdminUserHub,但文件保留)
- 文件:[AdminUserActivityPage.tsx](../src/pages/AdminUserActivityPage.tsx)
- 作用:按用户钻取其线程与消息(独立窗口模式)
- Hook:[useAllUsers](../src/core/auth/hooks.ts)、[useUserMemory / useUserThreadMessages / useUserThreads](../src/core/admin/hooks.ts)
- 组件:`Tabs`、`Input`、`ScrollArea`、`Badge`、左右两栏布局
#### AdminMemoryPage(已并入 Hub)
- 文件:[AdminMemoryPage.tsx](../src/pages/AdminMemoryPage.tsx)(薄壳)→ [admin-memory-page.tsx](../src/components/workspace/admin/admin-memory-page.tsx)
#### UsersPage(已并入 Hub)
- 文件:[UsersPage.tsx](../src/pages/UsersPage.tsx)(薄壳)→ [UserManagementPage](../src/components/workspace/admin/user-management-page.tsx)
- 作用:账号管理(系统角色变更、密码重置等)
#### TagsPage —— `/page/workspace/admin/tags`
- 文件:[TagsPage.tsx](../src/pages/TagsPage.tsx)(薄壳)→ [TagManagementPage](../src/components/workspace/admin/tag-management-page.tsx)
- 作用:全局标签 CRUD(颜色、可见性、排序)
#### LlmMetricsPage —— `/page/workspace/settings/llm-metrics`
- 文件:[LlmMetricsPage.tsx](../src/pages/LlmMetricsPage.tsx)
- 作用:LLM 调用计量与工具计量
- Hook:[useLlmMetrics](../src/core/llm-metrics/hooks.ts)、`downloadLlmMetricsExcel`
- 组件:`Tabs`、`Input`、`Select`、`Badge`、[ToolMetricsPanel](../src/components/workspace/admin/tool-metrics-panel.tsx)
#### RecommendedQuestionsPage —— `/page/workspace/settings/recommended-questions`
- 文件:[RecommendedQuestionsPage.tsx](../src/pages/RecommendedQuestionsPage.tsx)
- 作用:管理欢迎页/新会话落地的「推荐问题」(增删改、拖拽排序)
- Hook:[useRecommendedQuestions / useCreateRecommendedQuestion / useUpdateRecommendedQuestion / useDeleteRecommendedQuestion / useReorderRecommendedQuestions](../src/core/recommended-questions/hooks.ts)
- 组件:`Dialog`、`Input`、`Textarea`、`Button` + 自定义拖拽(HTML5 DnD)
#### PromptPrefixSettingsPage —— `/page/workspace/settings/prompt-prefix`
- 文件:[PromptPrefixSettingsPage.tsx](../src/pages/PromptPrefixSettingsPage.tsx)
- 作用:全局「前缀模式」开关、开关名称、prompt 内容;用户在新会话首轮可勾选附加该 prefix
- Hook:[usePromptPrefixSettings / useUpdatePromptPrefixSettings](../src/core/system-settings/hooks.ts)
- 组件:`Switch`、`Input`、`Textarea`、`Button`
### 25.4 `/page/canvas/*`(开放画布 / AI 写作)
> 由 [OpenCanvasRoutes.tsx](../src/open-canvas/routes/OpenCanvasRoutes.tsx) 注册,不在 WorkspaceLayout 内(直接挂在 PageRoutes 内容区)。
#### CanvasHomePage —— `/page/canvas`
- 文件:[open-canvas/pages/CanvasHomePage.tsx](../src/open-canvas/pages/CanvasHomePage.tsx)
- 作用:画布首页,选择「新建文本文档」或「新建代码」→ 调 [createCanvasThread](../src/open-canvas/api/threads.ts) → 跳转到 `:thread_id`
- 组件:`Button` + Lucide 图标(`FileText/Code2/Sparkles/PenLine`)
#### CanvasThreadPage —— `/page/canvas/:thread_id`
- 文件:[open-canvas/pages/CanvasThreadPage.tsx](../src/open-canvas/pages/CanvasThreadPage.tsx)
- 作用:画布工作区,左右分栏:聊天 + 富文本/代码编辑
- Provider 栈:[CanvasThreadProvider](../src/open-canvas/contexts/CanvasThreadContext.tsx) → [CanvasGraphProvider](../src/open-canvas/contexts/CanvasGraphContext.tsx) → [CanvasUIProvider](../src/open-canvas/contexts/CanvasUIContext.tsx)
- 子组件:[CanvasShell](../src/open-canvas/components/layout/CanvasShell.tsx)
- [CanvasHeader](../src/open-canvas/components/layout/CanvasHeader.tsx)
- [CanvasResizableLayout](../src/open-canvas/components/layout/CanvasResizableLayout.tsx)
- [CanvasChatPanel](../src/open-canvas/components/chat/CanvasChatPanel.tsx)
- [CanvasArtifactPanel](../src/open-canvas/components/artifact/CanvasArtifactPanel.tsx) + 各 renderer:
- [TextArtifactRenderer](../src/open-canvas/components/artifact/TextArtifactRenderer.tsx)
- [CodeArtifactRenderer](../src/open-canvas/components/artifact/CodeArtifactRenderer.tsx)
- [TiptapEditor](../src/open-canvas/editor/TiptapEditor.tsx) + [WritingBubbleMenu](../src/open-canvas/components/artifact/WritingBubbleMenu.tsx) / [WritingParagraphMenu](../src/open-canvas/components/artifact/WritingParagraphMenu.tsx) / [WritingAiMenu](../src/open-canvas/components/artifact/WritingAiMenu.tsx) / [WritingToolbar](../src/open-canvas/components/artifact/WritingToolbar.tsx) / [WritingRewritePreview](../src/open-canvas/components/artifact/WritingRewritePreview.tsx)
- [CanvasQuickActionBar](../src/open-canvas/components/artifact/CanvasQuickActionBar.tsx) / [CanvasArtifactVersionNav](../src/open-canvas/components/artifact/CanvasArtifactVersionNav.tsx) / [CanvasEmptyState](../src/open-canvas/components/artifact/CanvasEmptyState.tsx)
- [CanvasModelSelector](../src/open-canvas/components/selectors/CanvasModelSelector.tsx)
- Hook:[useCanvasStream](../src/open-canvas/hooks/useCanvasStream.ts)、`useCanvasArtifact`、`useCanvasThread`、`useCanvasQuickActions`、`useCanvasSelection`、`useWritingEditor`、`useDocumentOutline`、`useWritingRewrite`
- 与主聊天的隔离:使用 Canvas 自有的 LangGraph base URL(可选)、独立 thread/run API、独立编辑器扩展([AiHighlightExtension](../src/open-canvas/editor/AiHighlightExtension.ts))
#### AIWritingPage —— `/page/canvas/ai-writing` 与 `/page/workspace/ai-writing`
- 文件:[open-canvas/pages/AIWritingPage.tsx](../src/open-canvas/pages/AIWritingPage.tsx)
- 作用:受控的「AI 写作流程」 —— 启动写作 → SSE 进度 → 用户干预(intervention)→ 继续 → 评审 → 导出 docx
- 子组件:
- [AIWritingPageHeader](../src/open-canvas/components/ai-writing/AIWritingPageHeader.tsx)
- [AIWritingForm](../src/open-canvas/components/ai-writing/AIWritingForm.tsx)
- [AIWritingDraftPanel](../src/open-canvas/components/ai-writing/AIWritingDraftPanel.tsx) + [AIWritingPanel](../src/open-canvas/components/ai-writing/AIWritingPanel.tsx)
- [AIWritingTimeline](../src/open-canvas/components/ai-writing/AIWritingTimeline.tsx) + [TimelineStep](../src/open-canvas/components/ai-writing/TimelineStep.tsx)
- [AIWritingHistoryDropdown](../src/open-canvas/components/ai-writing/AIWritingHistoryDropdown.tsx) + [AIWritingHistoryView](../src/open-canvas/components/ai-writing/AIWritingHistoryView.tsx)
- [IntentStreamingView](../src/open-canvas/components/ai-writing/IntentStreamingView.tsx) / [IntentResultCard](../src/open-canvas/components/ai-writing/IntentResultCard.tsx)
- [OutlineStreamingView](../src/open-canvas/components/ai-writing/OutlineStreamingView.tsx) / [OutlineCard](../src/open-canvas/components/ai-writing/OutlineCard.tsx) / [OutlineSection](../src/open-canvas/components/ai-writing/OutlineSection.tsx)
- [DraftStreamingView](../src/open-canvas/components/ai-writing/DraftStreamingView.tsx)
- [ReviewStreamingView](../src/open-canvas/components/ai-writing/ReviewStreamingView.tsx) / [ReviewCard](../src/open-canvas/components/ai-writing/ReviewCard.tsx) / [ScoreBar](../src/open-canvas/components/ai-writing/ScoreBar.tsx) / [IssueList](../src/open-canvas/components/ai-writing/IssueList.tsx)
- [InterventionCard](../src/open-canvas/components/ai-writing/InterventionCard.tsx) / [CompletedInterventionCard](../src/open-canvas/components/ai-writing/CompletedInterventionCard.tsx) / [RevisionSummaryCard](../src/open-canvas/components/ai-writing/RevisionSummaryCard.tsx)
- [MaterialsToolbar](../src/open-canvas/components/ai-writing/MaterialsToolbar.tsx) / [MaterialsCard](../src/open-canvas/components/ai-writing/MaterialsCard.tsx) / [MaterialItem](../src/open-canvas/components/ai-writing/MaterialItem.tsx)
- [DraftOutlineSidebar](../src/open-canvas/components/artifact/DraftOutlineSidebar.tsx) / [WritingSkeletonCard](../src/open-canvas/components/ai-writing/WritingSkeletonCard.tsx) / [CollapseTransition](../src/open-canvas/components/ai-writing/CollapseTransition.tsx)
- Hook:[useAIWritingSessions](../src/open-canvas/hooks/useAIWritingSessions.ts)、[useAIWritingStream](../src/open-canvas/hooks/useAIWritingStream.ts)、[useArticleTypes](../src/open-canvas/hooks/useArticleTypes.ts)、[useWritingRewrite](../src/open-canvas/hooks/useWritingRewrite.ts)、[useDocumentOutline](../src/open-canvas/hooks/useDocumentOutline.ts)
- API:[ai-writing-sessions.ts](../src/open-canvas/api/ai-writing-sessions.ts)、[rewrite.ts](../src/open-canvas/api/rewrite.ts)、[writing-export.ts](../src/open-canvas/api/writing-export.ts)、[ai-writing-article-types.ts](../src/open-canvas/api/ai-writing-article-types.ts)
- 文件头有大段 Docker 内网部署排错指南(transcript 500 / resume 400 / 会话丢失),新成员部署前请先读
#### ArticleTypesConfigPage —— `/page/canvas/article-types`
- 文件:[open-canvas/pages/ArticleTypesConfigPage.tsx](../src/open-canvas/pages/ArticleTypesConfigPage.tsx)
- 作用:AI 写作「文章类型」配置(标题/描述/默认 prompt 等)
- Hook:[useArticleTypes](../src/open-canvas/hooks/useArticleTypes.ts)
- 组件:`Button`、`Separator`、`Dialog` + 自定义 input/textarea 样式常量
### 25.5 `/page/roundtable/*`(圆桌规划)
#### RoundtablePlanningPage —— `/page/roundtable/planning`
- 文件:[roundtable-planning/pages/RoundtablePlanningPage.tsx](../src/roundtable-planning/pages/RoundtablePlanningPage.tsx)
- 作用:三步式多 Agent 圆桌规划(任务理解 → 编排 → 高保真报告)
- Hook:
- [useStep1Intent](../src/roundtable-planning/hooks/useStep1Intent.ts) —— 任务理解、INTENT_READY、澄清卡
- [useStep2Orchestration](../src/roundtable-planning/hooks/useStep2Orchestration.ts) —— 编排循环、对话流、Persona 覆盖、澄清回复
- [useDraftPersistence](../src/roundtable-planning/hooks/useDraftPersistence.ts) —— 本地草稿(自动保存/手动/加载/重命名/删除)
- [useAutoScroll](../src/roundtable-planning/hooks/useAutoScroll.ts)
- 组件:
- [Step1Panel](../src/roundtable-planning/components/Step1Panel.tsx) / [Step2Panel](../src/roundtable-planning/components/Step2Panel.tsx) / [Step3Panel](../src/roundtable-planning/components/Step3Panel.tsx)
- [PersonaDrawer](../src/roundtable-planning/components/PersonaDrawer.tsx)
- [Avatars](../src/roundtable-planning/components/Avatars.tsx)(含 BUILTIN_AGENT_META)
- [MessageBubble](../src/roundtable-planning/components/MessageBubble.tsx) / [MessageStepsCard](../src/roundtable-planning/components/MessageStepsCard.tsx)
- [IntentClarificationCard](../src/roundtable-planning/components/IntentClarificationCard.tsx)
- [RecommendAgentsDialog](../src/roundtable-planning/components/RecommendAgentsDialog.tsx)
- [HighFidelityReport](../src/roundtable-planning/components/HighFidelityReport.tsx) —— Step3 报告呈现
- [ArtifactPreviewModal](../src/roundtable-planning/components/ArtifactPreviewModal.tsx)
- [ScrollToBottomButton](../src/roundtable-planning/components/ScrollToBottomButton.tsx)
- API:[intent.ts](../src/roundtable-planning/api/intent.ts) / [multi-agent.ts](../src/roundtable-planning/api/multi-agent.ts) / [recommend.ts](../src/roundtable-planning/api/recommend.ts)
- 工具:[clarification.ts](../src/roundtable-planning/lib/clarification.ts)、[step-display.tsx](../src/roundtable-planning/lib/step-display.tsx)、[reasoning.ts](../src/roundtable-planning/lib/reasoning.ts)、[roundtable-constants.ts](../src/roundtable-planning/lib/roundtable-constants.ts)、[drafts.ts](../src/roundtable-planning/utils/drafts.ts)
- 注意(来自文件头注释):
1. special 顺序派活,**不要**改回 `Promise.all`
2. `step1InitGenRef / step2InitRef` 闸门用于规避 StrictMode 双 mount 副作用
3. 离开 Step 2 不销毁 threadIds,允许返回继续看
4. 草稿自动保存仅在 1→2、2→3 前进时触发
5. Persona 抽屉改的 model 不立即重启编排,下一轮才生效
6. 默认 `availableModels[0].name`,Persona 覆盖更高优先级
### 25.6 `/page/strategy/*`(策略路由)
> [StrategyRoutes.tsx](../src/pages/StrategyRoutes.tsx) 复用了 WorkspaceRoutes 内大多数页面,**不带** WorkspaceLayout/PageSidebar,由 SidebarProvider 单独维持。`<RouteBaseProvider value="/page/strategy">` 让 `useRouter` 跳转保持在策略前缀下。
- `qa/general` → [GeneralQA](../src/strategy-components/components/qa/GeneralQA.tsx)(综合问答中心)
- 子模块:[AgentQA](../src/strategy-components/components/qa/AgentQA.tsx)、[CollaborativeQA](../src/strategy-components/components/qa/CollaborativeQA.tsx)、[MopeChat](../src/strategy-components/components/qa/MopeChat.tsx)、[QA](../src/strategy-components/components/qa/QA.tsx)、[QACenterPage](../src/strategy-components/components/qa/QACenterPage.tsx)、[QACommon](../src/strategy-components/components/qa/QACommon.tsx)、[QALayout](../src/strategy-components/components/qa/QALayout.tsx)、[QATypeSwitch](../src/strategy-components/components/qa/QATypeSwitch.tsx)、[Template6BEditor](../src/strategy-components/components/qa/Template6BEditor.tsx)、[TemplateEditor](../src/strategy-components/components/qa/TemplateEditor.tsx)、[GeneralQAConfigSidebar](../src/strategy-components/components/qa/GeneralQAConfigSidebar.tsx)、[GeneralQAHeader](../src/strategy-components/components/qa/GeneralQAHeader.tsx)
- 子组件:[AgentCard](../src/strategy-components/components/qa/components/AgentCard.tsx)、[ConfigPanel](../src/strategy-components/components/qa/components/ConfigPanel.tsx)、[FinalReport](../src/strategy-components/components/qa/components/FinalReport.tsx)、[MarkdownContent](../src/strategy-components/components/qa/components/MarkdownContent.tsx)、[ProgressSidebar](../src/strategy-components/components/qa/components/ProgressSidebar.tsx)、[ScenarioConfigPage](../src/strategy-components/components/qa/components/ScenarioConfigPage.tsx)、[Sidebar](../src/strategy-components/components/qa/components/Sidebar.tsx)、[Timeline](../src/strategy-components/components/qa/components/Timeline.tsx)
- `light-app/new-note` → [LightAppNewNotePage.tsx](../src/pages/LightAppNewNotePage.tsx)
- 作用:轻应用「新建笔记」入口;调用独立的笔记服务 `VITE_NOTE_API_BASE_URL`,使用 [useNoteAuth](../src/hooks/useNoteAuth.ts) 完成笔记服务自有的鉴权
- API:[createSearchSpace](../src/core/note/api.ts)
- `light-app/notes` → [LightAppNoteListPage.tsx](../src/pages/LightAppNoteListPage.tsx)
- 作用:笔记列表,支持网格/列表视图切换
- API:[listSearchSpaces](../src/core/note/api.ts)
- `light-app/note-qa` → [LightAppNoteQAPage.tsx](../src/pages/LightAppNoteQAPage.tsx)
- 作用:基于笔记的提问入口(UI 雏形,详细问答尚在开发)
- 其余路径(chats / agents / skills / memory / scheduled / settings / admin)**直接复用** WorkspaceRoutes 中的页面文件,仅 base 不同
### 25.7 路由与页面对照速查
| 一级路径 | 进入路由文件 | 主要页面文件 |
|---|---|---|
| `/login`、`/login/:username` | App.tsx | LoginPage.tsx |
| `/landing` | App.tsx | LandingPage.tsx |
| `/share/:shareCode` | App.tsx | PublicSharePage.tsx |
| `/public/scheduled-tasks/:taskId` | App.tsx | PublicScheduledTaskPage.tsx |
| `/embed/chats/:thread_id` | App.tsx | EmbedChatPage.tsx([embed-iframe-chat.md](./embed-iframe-chat.md)) |
| `/page/workspace/*` | PageRoutes.tsx → WorkspaceRoutes.tsx | ChatsPage / ChatPage / AgentsPage / NewAgentPage / AgentChatPage / SkillsPage / SkillEvolutionPage / MemoryPage / ScheduledTasksPage / ScheduledTaskRunDetailPage / AdminUserHubPage / AdminScheduledTasksPage / TagsPage / RecommendedQuestionsPage / PromptPrefixSettingsPage / LlmMetricsPage |
| `/page/strategy/*` | PageRoutes.tsx → StrategyRoutes.tsx | GeneralQA + LightApp* + 复用 Workspace 页面 |
| `/page/canvas/*` | PageRoutes.tsx → open-canvas/routes/OpenCanvasRoutes.tsx | CanvasHomePage / CanvasThreadPage / AIWritingPage / ArticleTypesConfigPage |
| `/page/roundtable/*` | PageRoutes.tsx → RoundtableRoutes.tsx | RoundtablePlanningPage |
| `*` | App.tsx | NotFoundPage |
### 25.8 "薄壳页面"与"重逻辑页面"
| 页面 | 类型 | 真正实现位置 |
|---|---|---|
| AgentsPage | 薄壳 | components/workspace/agents/agent-gallery.tsx |
| AdminUserHubPage | 薄壳 | components/workspace/admin/admin-user-hub.tsx |
| AdminMemoryPage | 薄壳 | components/workspace/admin/admin-memory-page.tsx |
| UsersPage | 薄壳 | components/workspace/admin/user-management-page.tsx |
| TagsPage | 薄壳 | components/workspace/admin/tag-management-page.tsx |
| SkillsPage | 薄壳 | components/workspace/settings/skill-settings-page.tsx |
| SkillEvolutionPage | 薄壳 | components/workspace/skills/skill-evolution-panel.tsx |
| MemoryPage | 薄壳 | components/workspace/settings/memory-settings-page.tsx |
| 其余 | 重逻辑 | 直接在 pages/ 内实现 |
> 维护建议:薄壳页面只负责 layout/insets,业务逻辑请始终下沉到 `components/workspace/<domain>/` 下方便复用与测试。
---
> 本文档与代码同步演进。若发现内容与实现不符,请优先以代码为准并更新本文。