976 lines
31 KiB
Markdown
976 lines
31 KiB
Markdown
# DeerFlow 前端开发文档
|
||
|
||
## 目录
|
||
|
||
1. [整体目录结构](#1-整体目录结构)
|
||
2. [路由结构](#2-路由结构)
|
||
3. [核心数据流与状态管理](#3-核心数据流与状态管理)
|
||
4. [主要页面组件](#4-主要页面组件)
|
||
5. [核心业务组件](#5-核心业务组件)
|
||
6. [核心 Hooks 与工具函数](#6-核心-hooks-与工具函数)
|
||
7. [API 调用层](#7-api-调用层)
|
||
8. [国际化机制](#8-国际化机制)
|
||
9. [主题与样式体系](#9-主题与样式体系)
|
||
10. [关键业务流程](#10-关键业务流程)
|
||
11. [项目配置与依赖](#11-项目配置与依赖)
|
||
12. [常见开发模式](#12-常见开发模式)
|
||
|
||
---
|
||
|
||
## 1. 整体目录结构
|
||
|
||
```
|
||
frontend-web/src/
|
||
├── App.tsx # 应用入口,顶层路由 + Provider
|
||
├── env.ts # 环境变量类型定义
|
||
├── pages/ # 路由级页面组件
|
||
│ ├── PageRoutes.tsx # /page/* 根路由容器(含外部系统预留位)
|
||
│ ├── WorkspaceRoutes.tsx # /page/workspace/* 工作区路由
|
||
│ ├── LoginPage.tsx # 登录页
|
||
│ ├── LandingPage.tsx # 营销落地页(强制 dark 主题)
|
||
│ ├── ChatsPage.tsx # 对话列表
|
||
│ ├── ChatPage.tsx # 单条对话(核心页面)
|
||
│ ├── AgentsPage.tsx # 智能体库
|
||
│ ├── AgentChatPage.tsx # 与智能体对话
|
||
│ ├── NewAgentPage.tsx # 创建智能体(两步流程)
|
||
│ ├── ScheduledTasksPage.tsx # 定时任务管理
|
||
│ ├── ScheduledTaskRunDetailPage.tsx # 定时任务执行详情
|
||
│ ├── ScheduledChatPage.tsx # 定时任务对话跳转页
|
||
│ └── NotFoundPage.tsx # 404 页面
|
||
├── components/ # 业务组件库
|
||
│ ├── ui/ # shadcn/ui 基础组件(Button、Card、Dialog 等 40+)
|
||
│ ├── ai-elements/ # AI 交互可视化组件
|
||
│ │ ├── prompt-input/ # 输入框 Context + 子组件系统
|
||
│ │ ├── artifact.tsx # 制品展示
|
||
│ │ ├── canvas.tsx
|
||
│ │ ├── conversation.tsx # 虚拟滚动对话容器
|
||
│ │ ├── message.tsx
|
||
│ │ ├── model-selector.tsx
|
||
│ │ ├── code-block.tsx
|
||
│ │ └── shimmer.tsx
|
||
│ ├── workspace/ # 工作区业务组件(详见第 5 节)
|
||
│ └── landing/ # 落地页组件
|
||
├── core/ # 核心业务逻辑层(非 UI)
|
||
│ ├── api/ # API 客户端(LangGraph SDK + Fetch)
|
||
│ ├── auth/ # 认证管理
|
||
│ ├── config/ # 环境配置 & Base URL
|
||
│ ├── i18n/ # 国际化(上下文、翻译文件、语言检测)
|
||
│ ├── threads/ # 对话线程管理(最核心)
|
||
│ ├── agents/ # 智能体 CRUD
|
||
│ ├── models/ # 模型列表
|
||
│ ├── settings/ # 本地设置(LocalStorage 发布订阅)
|
||
│ ├── uploads/ # 文件上传
|
||
│ ├── artifacts/ # 制品管理
|
||
│ ├── memory/ # 用户记忆
|
||
│ ├── skills/ # 技能管理
|
||
│ ├── scheduled-tasks/ # 定时任务
|
||
│ ├── messages/ # 消息处理工具函数
|
||
│ ├── tasks/ # 子任务 Context
|
||
│ ├── notification/ # 系统通知
|
||
│ └── utils/ # 通用工具(文件下载、日期格式化等)
|
||
└── hooks/ # 全局自定义 hooks
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 路由结构
|
||
|
||
> 项目使用 **HashRouter**,所有路由访问形如 `http://host/#/xxx`
|
||
|
||
### 2.1 App.tsx 顶层路由
|
||
|
||
| 路径 | 组件 | 说明 |
|
||
|------|------|------|
|
||
| `/` | Navigate | 重定向到 `/login` |
|
||
| `/login` | LoginPage | 登录(自动登录,无表单) |
|
||
| `/login/:username` | LoginPage | 带用户名登录 |
|
||
| `/landing` | LandingPage | 营销落地页(固定 dark 主题) |
|
||
| `/page/*` | PageRoutes | 应用内容根容器 |
|
||
| `*` | NotFoundPage | 404 |
|
||
|
||
### 2.2 PageRoutes.tsx(`/page/*`)
|
||
|
||
```
|
||
/page/
|
||
├── index → Navigate to "workspace"
|
||
├── workspace/* → WorkspaceRoutes
|
||
└── [TODO 预留] → 外部系统路由挂载位置
|
||
```
|
||
|
||
### 2.3 WorkspaceRoutes.tsx(`/page/workspace/*`)
|
||
|
||
| 路径 | 页面组件 | 布局包裹 |
|
||
|------|---------|--------|
|
||
| `index` | Navigate → `chats/new` | - |
|
||
| `chats` | ChatsPage | WorkspaceLayout |
|
||
| `chats/:thread_id` | ChatPage | WorkspaceLayout + ChatRuntime |
|
||
| `agents` | AgentsPage | WorkspaceLayout |
|
||
| `agents/new` | NewAgentPage | WorkspaceLayout |
|
||
| `agents/:agent_id/chats` | Navigate → `/page/workspace/agents` | - |
|
||
| `agents/:agent_id/chats/:thread_id` | AgentChatPage | WorkspaceLayout + ChatRuntime |
|
||
| `scheduled` | Navigate → `.../scheduled/tasks` | - |
|
||
| `scheduled/tasks` | ScheduledTasksPage | WorkspaceLayout |
|
||
| `scheduled/runs/:run_id` | ScheduledTaskRunDetailPage | WorkspaceLayout |
|
||
|
||
### 2.4 布局 Provider 层级结构
|
||
|
||
```
|
||
WorkspaceLayout
|
||
├── SidebarProvider(侧边栏展开/收起状态)
|
||
│ ├── WorkspaceSidebar
|
||
│ │ ├── WorkspaceHeader(Logo、新建按钮)
|
||
│ │ ├── WorkspaceNavChatList(Chats / Agents / Scheduled 导航)
|
||
│ │ ├── RecentChatList(最近会话列表)
|
||
│ │ └── WorkspaceNavMenu(Settings / About / GitHub)
|
||
│ ├── SidebarInset(主内容区)
|
||
│ │ └── [Page Content]
|
||
│ ├── CommandPalette(快捷命令面板)
|
||
│ └── Toaster(全局通知)
|
||
|
||
ChatRuntime(仅 ChatPage / AgentChatPage 使用)
|
||
├── SubtasksProvider(子任务执行状态)
|
||
├── ArtifactsProvider(制品选中/打开状态)
|
||
└── PromptInputProvider(输入框内容/附件状态)
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 核心数据流与状态管理
|
||
|
||
### 3.1 全局 Provider 架构
|
||
|
||
```
|
||
App
|
||
└── HashRouter
|
||
└── AppProviders(useLocation 驱动主题判断)
|
||
├── NextThemesProvider
|
||
│ └── attribute="class",/landing 强制 forcedTheme="dark"
|
||
├── I18nProvider
|
||
│ └── initialLocale = detectLocale()(从 cookie 或浏览器语言)
|
||
└── QueryClientProvider(TanStack React Query 全局缓存)
|
||
```
|
||
|
||
### 3.2 全局 Context 列表
|
||
|
||
| Context | 文件 | 职责 | 消费 Hook |
|
||
|---------|------|------|-----------|
|
||
| `I18nContext` | `core/i18n/context.tsx` | 当前语言 + 翻译对象 | `useI18n()` |
|
||
| `SubtaskContext` | `core/tasks/context.tsx` | 实时子任务执行状态 | `useSubtaskContext()` |
|
||
| `ArtifactsContext` | `components/workspace/artifacts/context.tsx` | 制品选中/面板开关 | `useArtifacts()` |
|
||
| `PromptInputContext` | `components/ai-elements/prompt-input/` | 输入框内容、附件列表 | `usePromptInputController()` |
|
||
| `ThreadContext` | `components/workspace/messages/context.tsx` | 当前 thread 对象下传 | 直接读 context.thread |
|
||
|
||
### 3.3 LocalStorage 数据
|
||
|
||
**操作层:** `core/settings/store.ts`(发布-订阅,`useSyncExternalStore` 驱动响应式)
|
||
|
||
| Key | 内容 |
|
||
|-----|------|
|
||
| `deerflow.auth` | `{ access_token, token_type, user_id, email }` |
|
||
| `deerflow.local-settings` | `{ notification, context: { model_name, mode, reasoning_effort } }` |
|
||
| `deerflow.thread-model.{threadId}` | 每条线程的模型覆盖 |
|
||
|
||
### 3.4 SessionStorage 数据
|
||
|
||
| Key | 内容 |
|
||
|-----|------|
|
||
| `deerflow.run-id.{threadId}` | WebSocket run_id,用于断线重连 |
|
||
|
||
### 3.5 React Query 缓存 Key
|
||
|
||
| Query Key | 数据 |
|
||
|-----------|------|
|
||
| `["threads"]` | 线程列表 |
|
||
| `["thread", threadId]` | 单条线程 |
|
||
| `["agents"]` | 智能体列表 |
|
||
| `["models"]` | 模型列表 |
|
||
| `["skills"]` | 技能列表 |
|
||
| `["memory"]` | 用户记忆 |
|
||
| `["scheduled-tasks"]` | 定时任务列表 |
|
||
| `["scheduled-runs", taskId]` | 任务执行记录 |
|
||
|
||
---
|
||
|
||
## 4. 主要页面组件
|
||
|
||
### 4.1 ChatPage.tsx — 单条对话页面
|
||
|
||
**职责:** 管理单条聊天线程的完整交互,包括消息流、输入、工具调用展示、制品等。
|
||
|
||
**关键状态:**
|
||
|
||
```typescript
|
||
const [threadId, setThreadId] = useState(routeThreadId || uuid());
|
||
const [isNewThread, setIsNewThread] = useState(!routeThreadId || routeThreadId === "new");
|
||
const [hasStartedConversation, setHasStartedConversation] = useState(false);
|
||
const [showFollowups, setShowFollowups] = useState(false);
|
||
const [settings, setSettings] = useThreadSettings(threadId);
|
||
```
|
||
|
||
**核心 Hook:**
|
||
|
||
```typescript
|
||
const [thread, sendMessage, isUploading] = useThreadStream({
|
||
threadId: isNewThread ? undefined : threadId,
|
||
context: settings.context,
|
||
onStart: (createdThreadId) => {
|
||
setThreadId(createdThreadId);
|
||
setIsNewThread(false);
|
||
// 不刷新页面,直接更新 URL hash
|
||
history.replaceState(null, "", `...#/page/workspace/chats/${createdThreadId}`);
|
||
},
|
||
onFinish: (state) => {
|
||
// 后台通知
|
||
showNotification(state.title, { body: lastMessageText });
|
||
}
|
||
});
|
||
```
|
||
|
||
**主要子组件:**
|
||
|
||
| 组件 | 职责 |
|
||
|------|------|
|
||
| `<MessageList>` | 消息渲染(含工具调用、子任务、制品) |
|
||
| `<Welcome>` | 新对话欢迎屏 |
|
||
| `<InputBox>` | 输入框(模式选择、附件、发送) |
|
||
| `<ThreadTitle>` | 线程标题(自动生成后同步更新) |
|
||
| `<ExportTrigger>` | 导出对话按钮 |
|
||
| `<ArtifactTrigger>` | 制品面板开关 |
|
||
| `<TokenUsageIndicator>` | Token 用量展示(可选) |
|
||
| `<TodoList>` | 任务清单 |
|
||
| `<Suggestions>` | 追问建议 |
|
||
|
||
**数据流:**
|
||
|
||
```
|
||
用户输入 (InputBox)
|
||
↓ handleSubmit(message: PromptInputMessage)
|
||
↓ sendMessage(threadId, message)
|
||
↓ [内部] 上传文件 → 提交到 LangGraph API
|
||
↓ WebSocket 流返回消息
|
||
↓ thread.messages 更新 → MessageList 重渲染
|
||
```
|
||
|
||
---
|
||
|
||
### 4.2 AgentChatPage.tsx — 与智能体对话
|
||
|
||
与 ChatPage 结构基本一致,差异点:
|
||
|
||
- 从路由获取 `agent_id`,传入 `useThreadStream({ assistantId: agent_id })`
|
||
- Header 展示智能体名称 Badge
|
||
- "新建对话"跳转到 `/page/workspace/agents/${agent_id}/chats/new`
|
||
- URL 更新为 `#/page/workspace/agents/${agent_id}/chats/${threadId}`
|
||
|
||
---
|
||
|
||
### 4.3 NewAgentPage.tsx — 创建智能体(两步流程)
|
||
|
||
**Step 1:name(命名 + 选技能)**
|
||
|
||
```typescript
|
||
// 自动预选前 3 个启用的技能
|
||
useEffect(() => {
|
||
if (!isSkillSelectionTouched) {
|
||
setSelectedSkills(enabledSkills.slice(0, 3).map(s => s.name));
|
||
}
|
||
}, [enabledSkills]);
|
||
|
||
// 提交:验证名称 → 创建智能体
|
||
await checkAgentName(name); // 检查重名
|
||
await createAgent(request); // 创建,返回 agent.id
|
||
setStep("chat"); // 进入 Step 2
|
||
```
|
||
|
||
**Step 2:chat(初始化对话)**
|
||
|
||
```typescript
|
||
// 发送 bootstrap 消息触发智能体初始化
|
||
sendMessage(threadId, bootstrapMessage, { agent_id: agentId });
|
||
|
||
// 监听 setup_agent 工具完成事件
|
||
onToolEnd: (event) => {
|
||
if (event.name === "setup_agent") {
|
||
// 带重试轮询加载 agent 最新信息
|
||
retryLoadAgent(agentId);
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4.4 ChatsPage.tsx — 对话列表
|
||
|
||
```typescript
|
||
const { data: threads } = useThreads({ limit: 50, sortBy: "updated_at" });
|
||
|
||
// 客户端过滤:排除定时任务线程 + 搜索关键词
|
||
const filteredThreads = useMemo(() =>
|
||
threads?.filter(t =>
|
||
t.metadata?.thread_type !== "scheduler" &&
|
||
titleOfThread(t).toLowerCase().includes(search)
|
||
), [threads, search]
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
### 4.5 LoginPage.tsx — 登录
|
||
|
||
```typescript
|
||
// 自动登录,无需用户操作
|
||
useEffect(() => {
|
||
loginByUsername(username || "guest")
|
||
.then(auth => {
|
||
setStoredAuth(auth);
|
||
router.replace("/page/workspace/chats/new");
|
||
})
|
||
.catch(err => setStatus("error"));
|
||
}, [username]);
|
||
```
|
||
|
||
---
|
||
|
||
### 4.6 ScheduledTasksPage.tsx — 定时任务
|
||
|
||
**功能:** 创建/编辑/发布/暂停/恢复定时任务,查看执行历史。
|
||
|
||
**Cron 表达式构建:**
|
||
|
||
```typescript
|
||
type ScheduleFreq = "hourly" | "daily" | "weekly" | "monthly";
|
||
|
||
function buildCron(config): string {
|
||
// hourly → "15 * * * *"
|
||
// daily → "0 9 * * *"
|
||
// weekly → "0 9 * * 1"
|
||
// monthly → "0 9 1 * *"
|
||
}
|
||
```
|
||
|
||
**核心操作:**
|
||
|
||
```typescript
|
||
const actions = useScheduledTaskActions();
|
||
actions.create.mutateAsync(payload);
|
||
actions.publish.mutateAsync(taskId);
|
||
actions.pause.mutateAsync(taskId);
|
||
actions.trigger.mutateAsync(taskId); // 立即执行一次
|
||
actions.subscribe.mutateAsync(params); // 订阅推送
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 核心业务组件
|
||
|
||
### 5.1 消息系统(`components/workspace/messages/`)
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `message-list.tsx` | 消息列表容器,调用 `groupMessages` 分组后渲染 |
|
||
| `message-list-item.tsx` | 单条消息渲染(含 role 判断) |
|
||
| `message-group.tsx` | 消息组(human / assistant / processing 等) |
|
||
| `markdown-content.tsx` | Markdown 渲染(rehype 系列插件) |
|
||
| `subtask-card.tsx` | 子任务状态卡片 |
|
||
| `message-token-usage.tsx` | 单条消息 Token 用量 |
|
||
| `skeleton.tsx` | 流式加载骨架屏 |
|
||
| `context.tsx` | ThreadContext,向下传递 thread 对象 |
|
||
|
||
**消息分组类型(`groupMessages`):**
|
||
|
||
```typescript
|
||
type MessageGroup =
|
||
| "human" // 用户输入
|
||
| "assistant:processing" // 推理/工具调用中
|
||
| "assistant" // 最终响应
|
||
| "assistant:clarification" // 澄清请求
|
||
| "assistant:present-files" // 文件呈现
|
||
| "assistant:subagent"; // 子智能体消息
|
||
```
|
||
|
||
---
|
||
|
||
### 5.2 输入框(`components/workspace/input-box.tsx`)
|
||
|
||
**子组件层级:**
|
||
|
||
```
|
||
InputBox
|
||
└── PromptInput(Context Provider)
|
||
├── PromptInputBody
|
||
│ ├── PromptInputTextarea(文本输入)
|
||
│ ├── PromptInputAttachments(附件列表)
|
||
│ └── PromptInputTools(工具栏:附件上传、模式选择)
|
||
├── PromptInputFooter
|
||
│ ├── PromptInputSubmit(发送/停止按钮)
|
||
│ └── Suggestions(追问建议列表)
|
||
└── ModelSelector(模型下拉选择)
|
||
```
|
||
|
||
**四种会话模式:**
|
||
|
||
| Mode | 说明 |
|
||
|------|------|
|
||
| `flash` | 快速模式,无推理步骤 |
|
||
| `thinking` | 推理模式,开启深度思考 |
|
||
| `pro` | 专业模式,多模态 + 规划 |
|
||
| `ultra` | 超级模式,启用子智能体 |
|
||
|
||
**推理努力等级(mode ≠ flash 时可选):** `minimal` / `low` / `medium` / `high`
|
||
|
||
---
|
||
|
||
### 5.3 侧边栏(`components/workspace/workspace-sidebar.tsx`)
|
||
|
||
```
|
||
WorkspaceSidebar
|
||
├── SidebarHeader → WorkspaceHeader
|
||
│ ├── Logo(可点击,跳转 /page/workspace/chats/new)
|
||
│ └── 新建对话按钮
|
||
├── SidebarContent
|
||
│ ├── WorkspaceNavChatList
|
||
│ │ ├── 新建对话(/page/workspace/chats/new)
|
||
│ │ ├── 所有对话(/page/workspace/chats)
|
||
│ │ ├── 智能体(/page/workspace/agents)
|
||
│ │ └── 任务管理(/page/workspace/scheduled/tasks)
|
||
│ └── RecentChatList(最近会话,含搜索)
|
||
└── SidebarFooter → WorkspaceNavMenu
|
||
├── Settings(打开设置 Dialog)
|
||
├── About
|
||
└── GitHub
|
||
```
|
||
|
||
---
|
||
|
||
### 5.4 制品管理(`components/workspace/artifacts/`)
|
||
|
||
```typescript
|
||
interface ArtifactsContextType {
|
||
artifacts: string[]; // artifact ID 列表
|
||
selectedArtifact: string | null;
|
||
autoSelect: boolean; // 是否自动选中最新制品
|
||
open: boolean; // 面板是否展开
|
||
autoOpen: boolean;
|
||
select(artifact: string, autoSelect?: boolean): void;
|
||
deselect(): void;
|
||
setOpen(open: boolean): void;
|
||
}
|
||
```
|
||
|
||
**文件:**
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `context.tsx` | ArtifactsContext + Provider |
|
||
| `artifact-trigger.tsx` | 面板展开/收起按钮 |
|
||
| `artifact-file-list.tsx` | 制品文件列表 |
|
||
| `artifact-file-detail.tsx` | 单个制品详情(含代码编辑器) |
|
||
|
||
---
|
||
|
||
### 5.5 设置面板(`components/workspace/settings/`)
|
||
|
||
| 页面文件 | 内容 |
|
||
|---------|------|
|
||
| `appearance-settings-page.tsx` | 主题切换(light/dark/system)、语言切换 |
|
||
| `memory-settings-page.tsx` | 用户记忆(事实列表、总结管理) |
|
||
| `notification-settings-page.tsx` | 桌面通知开关 |
|
||
| `skill-settings-page.tsx` | 技能启用/禁用 |
|
||
| `tool-settings-page.tsx` | 工具配置 |
|
||
| `about-settings-page.tsx` | 版本号、仓库链接 |
|
||
|
||
---
|
||
|
||
### 5.6 命令面板(`components/workspace/command-palette.tsx`)
|
||
|
||
通过 `Cmd/Ctrl + K` 触发,提供快速导航和操作入口。
|
||
|
||
```typescript
|
||
const handleNewChat = useCallback(() => {
|
||
router.push("/page/workspace/chats/new");
|
||
setOpen(false);
|
||
}, [router]);
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 核心 Hooks 与工具函数
|
||
|
||
### 6.1 useThreadStream(最核心 Hook)
|
||
|
||
**位置:** `core/threads/hooks.ts`
|
||
|
||
管理 LangGraph WebSocket 连接、消息流、乐观更新。
|
||
|
||
**签名:**
|
||
|
||
```typescript
|
||
const [thread, sendMessage, isUploading] = useThreadStream({
|
||
threadId?: string, // 有值则连接现有线程,无值表示新建
|
||
context: LocalSettings["context"], // 模型、模式等
|
||
isMock?: boolean,
|
||
assistantId?: string, // 默认 "lead_agent"
|
||
onStart?: (threadId: string) => void,
|
||
onFinish?: (state: AgentThreadState) => void,
|
||
onToolEnd?: (event: ToolEndEvent) => void,
|
||
});
|
||
```
|
||
|
||
**thread 对象关键属性:**
|
||
|
||
```typescript
|
||
thread: {
|
||
messages: Message[], // 含乐观消息的完整列表
|
||
isLoading: boolean, // 流是否进行中
|
||
isThreadLoading: boolean, // 初始线程数据是否加载中
|
||
values: AgentThreadState, // 线程最终状态
|
||
error?: Error,
|
||
stop: async () => void, // 终止当前流
|
||
}
|
||
```
|
||
|
||
**sendMessage 签名:**
|
||
|
||
```typescript
|
||
sendMessage(
|
||
threadId: string | undefined,
|
||
message: PromptInputMessage,
|
||
extraContext?: Partial<AgentThreadContext>,
|
||
options?: { isMock?: boolean }
|
||
): Promise<void>
|
||
```
|
||
|
||
**内部机制:**
|
||
- **乐观更新:** 用户发送后立即本地展示消息,不等服务端响应
|
||
- **文件上传:** 自动调用 `uploadFiles()` 后再提交
|
||
- **run_id 持久化:** sessionStorage 保存,支持页面刷新后断线重连
|
||
- **消息同步:** 流结束后通过 `queryClient.invalidateQueries(["threads"])` 刷新列表
|
||
|
||
---
|
||
|
||
### 6.2 useThreadSettings
|
||
|
||
**位置:** `core/settings/hooks.ts`
|
||
|
||
```typescript
|
||
const [settings, setSettings] = useThreadSettings(threadId);
|
||
|
||
// settings 结构
|
||
{
|
||
notification: { enabled: boolean },
|
||
context: {
|
||
model_name?: string,
|
||
mode: "flash" | "thinking" | "pro" | "ultra",
|
||
reasoning_effort?: "minimal" | "low" | "medium" | "high"
|
||
}
|
||
}
|
||
|
||
// 修改(合并更新)
|
||
setSettings("context", { mode: "pro" });
|
||
setSettings("notification", { enabled: true });
|
||
```
|
||
|
||
**工作原理:** `useSyncExternalStore` 监听 localStorage 变化,跨标签页同步。
|
||
|
||
---
|
||
|
||
### 6.3 useI18n
|
||
|
||
**位置:** `core/i18n/hooks.ts`
|
||
|
||
```typescript
|
||
const { t, locale, changeLocale } = useI18n();
|
||
|
||
t.sidebar.newChat // "New Chat"
|
||
t.agents.createPageTitle // "Create Agent"
|
||
t.toolCalls.moreSteps(3) // "Show 3 more steps"
|
||
|
||
changeLocale("zh-CN"); // 切换语言,自动存 cookie
|
||
```
|
||
|
||
---
|
||
|
||
### 6.4 useScheduledTaskActions
|
||
|
||
**位置:** `core/scheduled-tasks/hooks.ts`
|
||
|
||
```typescript
|
||
const actions = useScheduledTaskActions();
|
||
|
||
// 所有返回值均为 TanStack Query Mutation
|
||
actions.create // 创建定时任务
|
||
actions.update // 更新任务配置
|
||
actions.remove // 删除任务
|
||
actions.removeRun // 删除执行记录
|
||
actions.pause // 暂停
|
||
actions.resume // 恢复
|
||
actions.trigger // 立即触发一次
|
||
actions.publish // 发布(激活)
|
||
actions.unpublish // 取消发布
|
||
actions.subscribe // 订阅推送
|
||
actions.updateSubscription
|
||
actions.unsubscribe
|
||
```
|
||
|
||
---
|
||
|
||
### 6.5 工具函数
|
||
|
||
**消息处理(`core/messages/utils.ts`):**
|
||
|
||
```typescript
|
||
groupMessages(messages, mapper) // 按类型分组消息列表
|
||
extractTextFromMessage(message) // 提取纯文本
|
||
extractContentFromMessage(message) // 提取 Markdown/图像内容
|
||
extractReasoningContentFromMessage(msg) // 提取推理过程
|
||
hasToolCalls(message) // 是否含工具调用
|
||
hasPresentFiles(message) // 是否包含文件呈现
|
||
extractPresentFilesFromMessage(message) // 获取文件列表
|
||
isClarificationToolMessage(message) // 是否为澄清请求
|
||
```
|
||
|
||
**线程路由(`core/threads/utils.ts`):**
|
||
|
||
```typescript
|
||
pathOfThread(thread, context?)
|
||
// → "/page/workspace/agents/{agentRef}/chats/{threadId}"
|
||
// 或 "/page/workspace/chats/{threadId}"
|
||
|
||
titleOfThread(thread)
|
||
// → thread.values?.title ?? "Untitled"
|
||
|
||
textOfMessage(message)
|
||
// → 消息文本内容(string 或 content 数组中第一个 text)
|
||
```
|
||
|
||
**其他工具:**
|
||
|
||
```typescript
|
||
// core/utils/files.tsx
|
||
downloadFile(blob: Blob, filename: string)
|
||
|
||
// core/utils/datetime.ts
|
||
formatTimeAgo(dateString: string) // → "2 小时前" / "3 days ago"
|
||
```
|
||
|
||
---
|
||
|
||
## 7. API 调用层
|
||
|
||
### 7.1 API 客户端架构
|
||
|
||
**两层设计:**
|
||
|
||
**第一层:LangGraph SDK Client**(`core/api/api-client.ts`)
|
||
|
||
```typescript
|
||
const client = new LangGraphClient({
|
||
apiUrl: getLangGraphBaseURL(isMock),
|
||
onRequest(url, init) {
|
||
const headers = new Headers(init.headers ?? {});
|
||
headers.set("Authorization", getAuthorizationHeaderValue());
|
||
return { ...init, headers };
|
||
}
|
||
});
|
||
|
||
export function getAPIClient(isMock?: boolean): LangGraphClient
|
||
```
|
||
|
||
**第二层:Fetch Client**(`core/api/fetch-client.ts`)
|
||
|
||
```typescript
|
||
function apiFetch(input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||
return fetch(input, {
|
||
...init,
|
||
headers: withAuthHeaders(init?.headers)
|
||
});
|
||
}
|
||
```
|
||
|
||
### 7.2 主要 API 模块
|
||
|
||
| 模块 | 文件 | 关键函数 |
|
||
|------|------|---------|
|
||
| **Auth** | `core/auth/api.ts` | `loginByUsername(username)` → `LoginByUsernameResponse` |
|
||
| **Agents** | `core/agents/api.ts` | `createAgent()`, `updateAgent()`, `listAgents()`, `deleteAgent()`, `checkAgentName()` |
|
||
| **Models** | `core/models/api.ts` | `loadModels()` → `Model[]` |
|
||
| **Threads** | (via LangGraph SDK) | `useThreadStream()`, `useThreads()`, `useDeleteThread()`, `useRenameThread()` |
|
||
| **Skills** | `core/skills/api.ts` | `loadSkills()`, `enableSkill(name, enabled)` |
|
||
| **Uploads** | `core/uploads/api.ts` | `uploadFiles(threadId, files[])`, `listUploadedFiles()`, `deleteUploadedFile()` |
|
||
| **Memory** | `core/memory/api.ts` | `loadMemory()`, `createMemoryFact()`, `updateMemoryFact()`, `deleteMemoryFact()` |
|
||
| **Scheduled Tasks** | `core/scheduled-tasks/api.ts` | CRUD、publish、pause、resume、trigger、subscribe |
|
||
|
||
### 7.3 Base URL 配置
|
||
|
||
**文件:** `core/config/index.ts`
|
||
|
||
```typescript
|
||
export function getBackendBaseURL(): string {
|
||
// env.NEXT_PUBLIC_BACKEND_BASE_URL
|
||
// 默认:""(当前域名根路径)
|
||
}
|
||
|
||
export function getLangGraphBaseURL(isMock?: boolean): string {
|
||
// mock 模式:${origin}/mock/api
|
||
// 正常:${origin}/api/langgraph
|
||
// 或读取 env.NEXT_PUBLIC_LANGGRAPH_BASE_URL
|
||
}
|
||
```
|
||
|
||
### 7.4 认证流程
|
||
|
||
```
|
||
LoginPage 调用 loginByUsername(username)
|
||
↓ POST /api/auth/login
|
||
↓ 返回 { access_token, token_type, user_id, email }
|
||
↓ setStoredAuth() 存入 localStorage["deerflow.auth"]
|
||
↓
|
||
后续所有 API 请求
|
||
↓ getAuthorizationHeaderValue()
|
||
↓ → "Bearer {access_token}"
|
||
↓ 自动添加到 Authorization header
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 国际化机制
|
||
|
||
### 8.1 文件结构
|
||
|
||
```
|
||
core/i18n/
|
||
├── context.tsx # I18nProvider(包裹 App)
|
||
├── hooks.ts # useI18n()
|
||
├── locale.ts # detectLocale(),Locale = "en-US" | "zh-CN"
|
||
├── cookies.ts # cookie 读写(持久化语言选择)
|
||
├── translations.ts # 翻译映射表入口
|
||
└── locales/
|
||
├── types.ts # Translations 接口定义(所有翻译 key 的类型)
|
||
├── en-US.ts # 英文翻译
|
||
└── zh-CN.ts # 简体中文翻译
|
||
```
|
||
|
||
### 8.2 Translations 接口分类
|
||
|
||
```typescript
|
||
interface Translations {
|
||
locale: { localName: string } // 语言自身名称
|
||
common: { ... } // 通用词汇(确认、取消等)
|
||
welcome: { ... } // 欢迎屏
|
||
inputBox: { ... } // 输入框(模式、占位符)
|
||
sidebar: { ... } // 侧边栏导航
|
||
agents: { ... } // 智能体相关
|
||
chats: { ... } // 对话相关
|
||
workspace: { ... } // 工作区通用
|
||
settings: { ... } // 设置面板
|
||
toolCalls: { ... } // 工具调用展示
|
||
uploads: { ... } // 文件上传
|
||
subtasks: { ... } // 子任务
|
||
tokenUsage: { ... } // Token 用量
|
||
shortcuts: { ... } // 快捷键说明
|
||
}
|
||
```
|
||
|
||
### 8.3 语言检测优先级
|
||
|
||
1. 读取 cookie `deerflow.locale`
|
||
2. 读取浏览器 `navigator.language`
|
||
3. 回退到 `en-US`
|
||
|
||
---
|
||
|
||
## 9. 主题与样式体系
|
||
|
||
### 9.1 主题系统
|
||
|
||
**库:** `next-themes`
|
||
|
||
```typescript
|
||
// App.tsx
|
||
<NextThemesProvider
|
||
attribute="class" // 通过 <html class="dark"> 应用主题
|
||
enableSystem // 跟随系统偏好
|
||
disableTransitionOnChange // 切换时禁用过渡动画
|
||
forcedTheme={pathname === "/landing" ? "dark" : undefined}
|
||
>
|
||
```
|
||
|
||
**可选主题值:** `light` / `dark` / `system`
|
||
|
||
### 9.2 样式方案
|
||
|
||
**基础:** Tailwind CSS
|
||
|
||
**组件库:** shadcn/ui(`components/ui/` 目录,40+ 基础组件)
|
||
|
||
**主要 CSS 自定义属性:**
|
||
|
||
```css
|
||
--container-width-sm /* 小容器宽度 */
|
||
--container-width-md /* 中等容器宽度(对话主区域) */
|
||
--primary /* 主色 */
|
||
--background /* 页面背景 */
|
||
--foreground /* 主文本色 */
|
||
--muted-foreground /* 次要文本色 */
|
||
--border /* 边框色 */
|
||
--sidebar-foreground /* 侧边栏文本 */
|
||
```
|
||
|
||
### 9.3 样式使用规范
|
||
|
||
- 工具类优先(Tailwind)
|
||
- 动态样式用 `cn()` 工具函数合并(基于 `clsx` + `tailwind-merge`)
|
||
- 主题感知样式使用 CSS 变量(如 `text-muted-foreground`)
|
||
|
||
---
|
||
|
||
## 10. 关键业务流程
|
||
|
||
### 10.1 新建对话流程
|
||
|
||
```
|
||
1. 点击"新建对话" → navigate("/page/workspace/chats/new")
|
||
2. ChatPage 挂载,threadId = uuid(),isNewThread = true
|
||
3. 用户输入消息,点击发送
|
||
4. sendMessage(undefined, message) 调用
|
||
5. useThreadStream 内部:
|
||
a. 上传文件(若有)
|
||
b. 创建 LangGraph 线程,后端返回 threadId
|
||
c. onStart(threadId) 回调:
|
||
- setIsNewThread(false)
|
||
- history.replaceState 更新 URL hash
|
||
6. WebSocket 流开始接收 AI 消息
|
||
7. MessageList 实时渲染消息
|
||
8. 流结束,onFinish 回调,可能触发桌面通知
|
||
```
|
||
|
||
### 10.2 创建智能体流程
|
||
|
||
```
|
||
Step 1(name 步骤)
|
||
↓ 用户输入名称
|
||
↓ checkAgentName(name) → 验证是否重名
|
||
↓ createAgent({ name, skills: selectedSkills })
|
||
↓ 后端返回 agent.id,setStep("chat")
|
||
|
||
Step 2(chat 步骤)
|
||
↓ 生成临时 threadId
|
||
↓ useThreadStream({ assistantId: agent.id })
|
||
↓ 发送 bootstrap 消息(触发智能体自我设置)
|
||
↓ onToolEnd 监听:event.name === "setup_agent"
|
||
↓ 指数退避重试加载 agent 信息,确认已保存
|
||
↓ 显示"Agent Created"完成界面
|
||
↓ 用户可点击"开始对话"或"返回列表"
|
||
```
|
||
|
||
### 10.3 文件上传流程
|
||
|
||
```
|
||
1. 用户在 InputBox 选择/粘贴文件
|
||
2. PromptInputAttachments 展示预览列表
|
||
3. 用户点击发送
|
||
4. sendMessage 内部调用 uploadFiles(threadId, files)
|
||
5. POST /api/threads/{threadId}/upload
|
||
6. 后端返回 { files: [{ filename, size, virtual_path }] }
|
||
7. 构建 Message additional_kwargs.files
|
||
8. 完整 Message 提交到 LangGraph
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 项目配置与依赖
|
||
|
||
### 11.1 主要依赖
|
||
|
||
| 包 | 版本 | 用途 |
|
||
|----|------|------|
|
||
| `react` | ^18 | 核心框架 |
|
||
| `react-router-dom` | ^6 | 客户端路由(HashRouter) |
|
||
| `@tanstack/react-query` | - | 服务端状态缓存 |
|
||
| `@langchain/langgraph-sdk` | - | LangGraph WebSocket 连接 |
|
||
| `next-themes` | - | 主题管理 |
|
||
| `sonner` | - | Toast 通知 |
|
||
| `lucide-react` | - | 图标库 |
|
||
| `tailwindcss` | - | CSS 框架 |
|
||
| `rehype` 系列 | - | Markdown 渲染 |
|
||
| `uuid` | - | 生成临时 thread ID |
|
||
|
||
### 11.2 环境变量
|
||
|
||
| 变量 | 说明 | 默认值 |
|
||
|------|------|--------|
|
||
| `NEXT_PUBLIC_BACKEND_BASE_URL` | 后端接口基础 URL | `""` (当前域名) |
|
||
| `NEXT_PUBLIC_LANGGRAPH_BASE_URL` | LangGraph API URL | `/api/langgraph` |
|
||
| `NEXT_PUBLIC_STATIC_WEBSITE_ONLY` | 纯静态演示模式 | `"false"` |
|
||
|
||
---
|
||
|
||
## 12. 常见开发模式
|
||
|
||
### 12.1 添加新工作区页面
|
||
|
||
```
|
||
1. 创建 src/pages/MyPage.tsx
|
||
2. 在 WorkspaceRoutes.tsx 中添加路由:
|
||
<Route path="my-page" element={<WorkspaceLayout><MyPage /></WorkspaceLayout>} />
|
||
3. 在 workspace-nav-chat-list.tsx 中添加导航入口(如需要)
|
||
4. 更新相关的跳转路径
|
||
```
|
||
|
||
### 12.2 添加新 API 接口
|
||
|
||
```
|
||
1. 在 core/xxx/api.ts 中定义 API 函数(使用 apiFetch 或 getAPIClient())
|
||
2. 在 core/xxx/hooks.ts 中封装 useQuery / useMutation
|
||
3. 在页面或组件中调用 hook
|
||
```
|
||
|
||
### 12.3 添加新 Context
|
||
|
||
```
|
||
1. 创建 core/xxx/context.tsx:定义 Context + Provider + 默认值
|
||
2. 创建消费 hook(如 useXxx())
|
||
3. 在合适的组件树根部插入 Provider(WorkspaceRoutes 或 ChatRuntime)
|
||
```
|
||
|
||
### 12.4 添加国际化文本
|
||
|
||
```
|
||
1. 在 core/i18n/locales/types.ts 的 Translations 接口中添加字段
|
||
2. 在 core/i18n/locales/en-US.ts 添加英文内容
|
||
3. 在 core/i18n/locales/zh-CN.ts 添加中文内容
|
||
4. 组件中通过 useI18n() 访问:const { t } = useI18n(); t.xxx.yyy
|
||
```
|
||
|
||
### 12.5 引入外部系统路由
|
||
|
||
在 `src/pages/PageRoutes.tsx` 中找到 TODO 注释位置,填入:
|
||
|
||
```typescript
|
||
import AnotherSystemRoutes from "外部系统路径";
|
||
|
||
// 在 Routes 中添加:
|
||
<Route path="other/*" element={<AnotherSystemRoutes />} />
|
||
```
|
||
|
||
如外部系统有独立 Provider 依赖,在此文件或 App.tsx 中包裹。
|
||
|
||
---
|
||
|
||
## 附:关键文件速查
|
||
|
||
| 需求 | 关键文件 |
|
||
|------|---------|
|
||
| 修改路由结构 | `App.tsx`, `PageRoutes.tsx`, `WorkspaceRoutes.tsx` |
|
||
| 修改侧边栏导航 | `components/workspace/workspace-nav-chat-list.tsx` |
|
||
| 修改对话逻辑 | `pages/ChatPage.tsx`, `core/threads/hooks.ts` |
|
||
| 修改输入框 | `components/workspace/input-box.tsx`, `components/ai-elements/prompt-input/` |
|
||
| 修改消息渲染 | `components/workspace/messages/` |
|
||
| 添加 API 接口 | `core/xxx/api.ts` + `core/xxx/hooks.ts` |
|
||
| 修改全局设置 | `core/settings/store.ts`, `core/settings/hooks.ts` |
|
||
| 修改翻译文本 | `core/i18n/locales/en-US.ts`, `core/i18n/locales/zh-CN.ts` |
|
||
| 修改主题 | `App.tsx` (NextThemesProvider) |
|
||
| 修改认证逻辑 | `core/auth/api.ts`, `pages/LoginPage.tsx` |
|
||
| 定时任务 | `pages/ScheduledTasksPage.tsx`, `core/scheduled-tasks/` |
|
||
| 智能体管理 | `pages/NewAgentPage.tsx`, `core/agents/` |
|