31 KiB
DeerFlow 前端开发文档
目录
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 — 单条对话页面
职责: 管理单条聊天线程的完整交互,包括消息流、输入、工具调用展示、制品等。
关键状态:
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:
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(命名 + 选技能)
// 自动预选前 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(初始化对话)
// 发送 bootstrap 消息触发智能体初始化
sendMessage(threadId, bootstrapMessage, { agent_id: agentId });
// 监听 setup_agent 工具完成事件
onToolEnd: (event) => {
if (event.name === "setup_agent") {
// 带重试轮询加载 agent 最新信息
retryLoadAgent(agentId);
}
}
4.4 ChatsPage.tsx — 对话列表
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 — 登录
// 自动登录,无需用户操作
useEffect(() => {
loginByUsername(username || "guest")
.then(auth => {
setStoredAuth(auth);
router.replace("/page/workspace/chats/new");
})
.catch(err => setStatus("error"));
}, [username]);
4.6 ScheduledTasksPage.tsx — 定时任务
功能: 创建/编辑/发布/暂停/恢复定时任务,查看执行历史。
Cron 表达式构建:
type ScheduleFreq = "hourly" | "daily" | "weekly" | "monthly";
function buildCron(config): string {
// hourly → "15 * * * *"
// daily → "0 9 * * *"
// weekly → "0 9 * * 1"
// monthly → "0 9 1 * *"
}
核心操作:
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):
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/)
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 触发,提供快速导航和操作入口。
const handleNewChat = useCallback(() => {
router.push("/page/workspace/chats/new");
setOpen(false);
}, [router]);
6. 核心 Hooks 与工具函数
6.1 useThreadStream(最核心 Hook)
位置: core/threads/hooks.ts
管理 LangGraph WebSocket 连接、消息流、乐观更新。
签名:
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 对象关键属性:
thread: {
messages: Message[], // 含乐观消息的完整列表
isLoading: boolean, // 流是否进行中
isThreadLoading: boolean, // 初始线程数据是否加载中
values: AgentThreadState, // 线程最终状态
error?: Error,
stop: async () => void, // 终止当前流
}
sendMessage 签名:
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
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
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
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):
groupMessages(messages, mapper) // 按类型分组消息列表
extractTextFromMessage(message) // 提取纯文本
extractContentFromMessage(message) // 提取 Markdown/图像内容
extractReasoningContentFromMessage(msg) // 提取推理过程
hasToolCalls(message) // 是否含工具调用
hasPresentFiles(message) // 是否包含文件呈现
extractPresentFilesFromMessage(message) // 获取文件列表
isClarificationToolMessage(message) // 是否为澄清请求
线程路由(core/threads/utils.ts):
pathOfThread(thread, context?)
// → "/page/workspace/agents/{agentRef}/chats/{threadId}"
// 或 "/page/workspace/chats/{threadId}"
titleOfThread(thread)
// → thread.values?.title ?? "Untitled"
textOfMessage(message)
// → 消息文本内容(string 或 content 数组中第一个 text)
其他工具:
// 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)
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)
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
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 接口分类
interface Translations {
locale: { localName: string } // 语言自身名称
common: { ... } // 通用词汇(确认、取消等)
welcome: { ... } // 欢迎屏
inputBox: { ... } // 输入框(模式、占位符)
sidebar: { ... } // 侧边栏导航
agents: { ... } // 智能体相关
chats: { ... } // 对话相关
workspace: { ... } // 工作区通用
settings: { ... } // 设置面板
toolCalls: { ... } // 工具调用展示
uploads: { ... } // 文件上传
subtasks: { ... } // 子任务
tokenUsage: { ... } // Token 用量
shortcuts: { ... } // 快捷键说明
}
8.3 语言检测优先级
- 读取 cookie
deerflow.locale - 读取浏览器
navigator.language - 回退到
en-US
9. 主题与样式体系
9.1 主题系统
库: next-themes
// 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 自定义属性:
--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 注释位置,填入:
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/ |