1170 lines
69 KiB
Markdown
1170 lines
69 KiB
Markdown
# 前端开发文档(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>/` 下方便复用与测试。
|
||
|
||
---
|
||
|
||
> 本文档与代码同步演进。若发现内容与实现不符,请优先以代码为准并更新本文。
|