diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..bdd36c0f --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(grep \"\\\\.tsx$\")", + "Bash(awk '{print $NF}')", + "Bash(xargs -I {} basename {})" + ] + } +} diff --git a/frontend-web/docs/hebing-1.md b/frontend-web/docs/hebing-1.md new file mode 100644 index 00000000..e69de29b diff --git a/frontend-web/docs/hebing.md b/frontend-web/docs/hebing.md index e69de29b..4bbe4153 100644 --- a/frontend-web/docs/hebing.md +++ b/frontend-web/docs/hebing.md @@ -0,0 +1,140 @@ +# App.tsx 路由与页面结构重构需求(首页与业务页分离) + +## 一、需求背景 + +当前 `frontend-web/src/App.tsx` 同时承载了: + +1. 登录/首页相关路由(如 `/login`、`/landing`) +2. 工作台业务相关路由(如 `/workspace/chats`、`/workspace/agents`、`/workspace/scheduled/*`) +3. 业务页所需布局与运行时包装(`WorkspaceLayout`、`ChatRuntime`) + +这会导致 `App.tsx` 过重,职责不够单一,不利于后续维护和扩展。 + +--- + +## 二、目标(你提出的核心诉求) + +你希望: + +1. **除了登录与首页入口内容外**,将 `App.tsx` 中其余业务页面路由整体迁移到 `pages` 下一个新组件中。 +2. 再创建一个“承接导航/入口”的页面(或路由容器),用于放置这些导航逻辑。 +3. `App.tsx` 最终只保留顶层路由分发和基础 Provider,变得更薄。 + +> 你提到“`App.tsx 74-75 login`”,可理解为:保留登录/首页相关入口,其他工作台内容抽离。 + +--- + +## 三、重构范围 + +### 1)保留在 `App.tsx` 的内容 + +- 全局 Provider:`HashRouter`、`AppProviders` +- 登录/首页路由: + - `/` + - `/login` + - `/login/:username` + - `/landing` +- 404 路由(可留在 `App.tsx` 或交给新路由容器统一处理,二选一) + +### 2)迁移出 `App.tsx` 的内容 + +将以下与工作台有关的路由从 `App.tsx` 抽离: + +- `/workspace`(重定向) +- `/workspace/chats` +- `/workspace/chats/:thread_id` +- `/workspace/agents` +- `/workspace/agents/new` +- `/workspace/agents/:agent_id/chats`(重定向) +- `/workspace/agents/:agent_id/chats/:thread_id` +- `/workspace/scheduled` +- `/workspace/scheduled/tasks` +- `/workspace/scheduled/runs/:run_id` + +以及配套的包装组件职责: + +- `WorkspaceLayout` +- `ChatRuntime` + +--- + +## 四、建议的新结构(方案) + +## 方案 A(推荐):新增“业务路由容器组件” + +在 `pages` 下新增一个路由容器组件,例如: + +- `src/pages/WorkspaceRoutes.tsx`(或 `src/pages/AppWorkspaceRoutes.tsx`) + +职责: +- 专门维护 `/workspace/*` 相关路由 +- 内部统一使用 `WorkspaceLayout` 与 `ChatRuntime` +- 输出一个可被 `App.tsx` 挂载的路由节点 + +同时在 `App.tsx` 里只保留: +- 登录/首页/404 +- 一个 `Route path="/workspace/*" element={} />` + +--- + +## 五、“再建立一个页面放置导航”的落地解释 + +你说的“建立一个页面放置这些导航”,可以有两种理解: + +1. **路由容器页(推荐)**: + - 不强调视觉 UI,重点是集中管理导航与路由跳转规则。 + - 即上面的 `WorkspaceRoutes.tsx`。 + +2. **可见的导航页(可选)**: + - 例如新建 `WorkspaceHomePage.tsx`,展示聊天/Agents/定时任务入口按钮。 + - 路由上把 `/workspace` 指向该页,而不是直接重定向到 `/workspace/chats/new`。 + +当前从你的描述看,优先建议第 1 种(先做结构整理),之后若有产品需求再做第 2 种可见导航页。 + +--- + +## 六、实施步骤(开发任务拆解) + +1. 新建 `WorkspaceRoutes` 组件(`pages` 目录下)。 +2. 把所有 `/workspace/*` 路由从 `App.tsx` 搬入该组件。 +3. 把 `WorkspaceLayout` 与 `ChatRuntime` 移入该组件(或抽到共享 layout/runtime 文件)。 +4. 在 `App.tsx` 中保留登录/首页路由,并挂载 `/workspace/*` 到新组件。 +5. 校验以下访问路径行为一致: + - `/workspace` 重定向 + - chats/agents/scheduled 各页面可正常进入 + - 动态参数路由(`thread_id`、`agent_id`、`run_id`)正常 + - 404 不受影响 +6. 运行前端并做手工回归。 + +--- + +## 七、验收标准 + +1. `App.tsx` 明显瘦身,主要负责顶层入口路由。 +2. `/workspace/*` 全部由新建组件统一管理。 +3. 功能行为与改造前一致(或符合新定义)。 +4. 代码可读性提升:登录入口与业务入口职责分离。 + +--- + +## 八、注意事项 + +1. 当前项目使用 `HashRouter`,路由写法需保持兼容。 +2. `Navigate` 重定向逻辑要完整迁移,避免出现死循环或空白页。 +3. `WorkspaceLayout` 与 `ChatRuntime` 的包裹层级不能错,否则可能影响侧边栏、输入区、子任务/Artifacts 状态。 +4. 若后续再加模块(例如 `/workspace/settings`),优先在 `WorkspaceRoutes` 中扩展。 + +--- + +## 九、建议的命名(可直接采用) + +- 新增组件文件:`src/pages/WorkspaceRoutes.tsx` +- (可选)可见导航页:`src/pages/WorkspaceHomePage.tsx` + +--- + +## 十、后续可选优化 + +1. 将 `WorkspaceLayout` 抽到 `src/layouts/WorkspaceLayout.tsx`。 +2. 将 `ChatRuntime` 抽到 `src/runtime/ChatRuntime.tsx`。 +3. 把路由常量集中到 `src/core/router/paths.ts`,减少硬编码路径。 diff --git a/frontend-web/docs/kaifawendnag.md b/frontend-web/docs/kaifawendnag.md new file mode 100644 index 00000000..1221753b --- /dev/null +++ b/frontend-web/docs/kaifawendnag.md @@ -0,0 +1,975 @@ +# 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 }); + } +}); +``` + +**主要子组件:** + +| 组件 | 职责 | +|------|------| +| `` | 消息渲染(含工具调用、子任务、制品) | +| `` | 新对话欢迎屏 | +| `` | 输入框(模式选择、附件、发送) | +| `` | 线程标题(自动生成后同步更新) | +| `` | 导出对话按钮 | +| `` | 制品面板开关 | +| `` | Token 用量展示(可选) | +| `` | 任务清单 | +| `` | 追问建议 | + +**数据流:** + +``` +用户输入 (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, + options?: { isMock?: boolean } +): Promise +``` + +**内部机制:** +- **乐观更新:** 用户发送后立即本地展示消息,不等服务端响应 +- **文件上传:** 自动调用 `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 { + 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 + 应用主题 + 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 中添加路由: + } /> +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 中添加: +} /> +``` + +如外部系统有独立 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/` | diff --git a/frontend-web/src/App.tsx b/frontend-web/src/App.tsx index 5b907bf4..c1122c19 100644 --- a/frontend-web/src/App.tsx +++ b/frontend-web/src/App.tsx @@ -3,27 +3,14 @@ import { Navigate, Route, Routes } from "react-router-dom"; import { ThemeProvider as NextThemesProvider } from "next-themes"; import { QueryClientProvider } from "@/components/query-client-provider"; -import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; -import { PromptInputProvider } from "@/components/ai-elements/prompt-input"; -import { WorkspaceSidebar } from "@/components/workspace/workspace-sidebar"; -import { CommandPalette } from "@/components/workspace/command-palette"; -import { Toaster } from "sonner"; import { I18nProvider } from "@/core/i18n/context"; import { detectLocale } from "@/core/i18n/locale"; -import { SubtasksProvider } from "@/core/tasks/context"; -import { ArtifactsProvider } from "@/components/workspace/artifacts"; import { HashRouter, useLocation } from "react-router-dom"; import LandingPage from "./pages/LandingPage"; -import ChatsPage from "./pages/ChatsPage"; -import ChatPage from "./pages/ChatPage"; -import AgentsPage from "./pages/AgentsPage"; -import AgentChatPage from "./pages/AgentChatPage"; -import NewAgentPage from "./pages/NewAgentPage"; import LoginPage from "./pages/LoginPage"; -import ScheduledTaskRunDetailPage from "./pages/ScheduledTaskRunDetailPage"; -import ScheduledTasksPage from "./pages/ScheduledTasksPage"; +import PageRoutes from "./pages/PageRoutes"; import { NotFoundPage } from "./pages/NotFoundPage"; function AppProviders({ children }: { children: React.ReactNode }) { @@ -45,27 +32,6 @@ function AppProviders({ children }: { children: React.ReactNode }) { ); } -function WorkspaceLayout({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - - - ); -} - -function ChatRuntime({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - - ); -} - export function App() { return ( @@ -75,78 +41,7 @@ export function App() { } /> } /> } /> - } - /> - - - - } - /> - - - - - - } - /> - - - - } - /> - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - } - /> - - - - - - } - /> + } /> } /> diff --git a/frontend-web/src/components/workspace/agents/agent-card.tsx b/frontend-web/src/components/workspace/agents/agent-card.tsx index 27dda218..42b4a1e5 100644 --- a/frontend-web/src/components/workspace/agents/agent-card.tsx +++ b/frontend-web/src/components/workspace/agents/agent-card.tsx @@ -84,7 +84,7 @@ export function AgentCard({ agent }: AgentCardProps) { ]); function handleChat() { - router.push(`/workspace/agents/${agent.id}/chats/new`); + router.push(`/page/workspace/agents/${agent.id}/chats/new`); } async function handleDelete() { diff --git a/frontend-web/src/components/workspace/agents/agent-gallery.tsx b/frontend-web/src/components/workspace/agents/agent-gallery.tsx index 4d62331e..a8e14376 100644 --- a/frontend-web/src/components/workspace/agents/agent-gallery.tsx +++ b/frontend-web/src/components/workspace/agents/agent-gallery.tsx @@ -15,7 +15,7 @@ export function AgentGallery() { const router = useRouter(); const handleNewAgent = () => { - router.push("/workspace/agents/new"); + router.push("/page/workspace/agents/new"); }; return ( diff --git a/frontend-web/src/components/workspace/command-palette.tsx b/frontend-web/src/components/workspace/command-palette.tsx index 1d754f61..855db3f1 100644 --- a/frontend-web/src/components/workspace/command-palette.tsx +++ b/frontend-web/src/components/workspace/command-palette.tsx @@ -38,7 +38,7 @@ export function CommandPalette() { const [isMac, setIsMac] = useState(false); const handleNewChat = useCallback(() => { - router.push("/workspace/chats/new"); + router.push("/page/workspace/chats/new"); setOpen(false); }, [router]); diff --git a/frontend-web/src/components/workspace/workspace-header.tsx b/frontend-web/src/components/workspace/workspace-header.tsx index 38c0fd5c..11eb5b31 100644 --- a/frontend-web/src/components/workspace/workspace-header.tsx +++ b/frontend-web/src/components/workspace/workspace-header.tsx @@ -83,7 +83,7 @@ export function WorkspaceHeader({ className }: { className?: string }) {
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" ? ( - + {titleBlock} ) : ( diff --git a/frontend-web/src/components/workspace/workspace-nav-chat-list.tsx b/frontend-web/src/components/workspace/workspace-nav-chat-list.tsx index 6528071a..f6dc0858 100644 --- a/frontend-web/src/components/workspace/workspace-nav-chat-list.tsx +++ b/frontend-web/src/components/workspace/workspace-nav-chat-list.tsx @@ -20,18 +20,18 @@ export function WorkspaceNavChatList() { - + {t.sidebar.newChat} - - + + {t.sidebar.chats} @@ -39,10 +39,10 @@ export function WorkspaceNavChatList() { - + {t.sidebar.agents} @@ -50,10 +50,10 @@ export function WorkspaceNavChatList() { - + 任务管理 diff --git a/frontend-web/src/core/threads/utils.ts b/frontend-web/src/core/threads/utils.ts index fc31b859..1c2b7ab5 100644 --- a/frontend-web/src/core/threads/utils.ts +++ b/frontend-web/src/core/threads/utils.ts @@ -36,8 +36,8 @@ export function pathOfThread( : pickAgentRef(thread.context, thread.metadata); return agentRef - ? `/workspace/agents/${encodeURIComponent(agentRef)}/chats/${threadId}` - : `/workspace/chats/${threadId}`; + ? `/page/workspace/agents/${encodeURIComponent(agentRef)}/chats/${threadId}` + : `/page/workspace/chats/${threadId}`; } export function textOfMessage(message: Message) { diff --git a/frontend-web/src/pages/AgentChatPage.tsx b/frontend-web/src/pages/AgentChatPage.tsx index 6bf9e897..19bd0918 100644 --- a/frontend-web/src/pages/AgentChatPage.tsx +++ b/frontend-web/src/pages/AgentChatPage.tsx @@ -66,7 +66,7 @@ export default function AgentChatPage() { history.replaceState( null, "", - `${window.location.pathname}${window.location.search}#/workspace/agents/${agent_id}/chats/${createdThreadId}`, + `${window.location.pathname}${window.location.search}#/page/workspace/agents/${agent_id}/chats/${createdThreadId}`, ); }, onFinish: (state) => { @@ -137,7 +137,7 @@ export default function AgentChatPage() { size="sm" variant="secondary" onClick={() => { - router.push(`/workspace/agents/${agent_id}/chats/new`); + router.push(`/page/workspace/agents/${agent_id}/chats/new`); }} > {t.agents.newChat} diff --git a/frontend-web/src/pages/ChatPage.tsx b/frontend-web/src/pages/ChatPage.tsx index 78da500b..8a2cd504 100644 --- a/frontend-web/src/pages/ChatPage.tsx +++ b/frontend-web/src/pages/ChatPage.tsx @@ -64,7 +64,7 @@ export default function ChatPage() { history.replaceState( null, "", - `${window.location.pathname}${window.location.search}#/workspace/chats/${createdThreadId}`, + `${window.location.pathname}${window.location.search}#/page/workspace/chats/${createdThreadId}`, ); }, onFinish: (state) => { diff --git a/frontend-web/src/pages/LoginPage.tsx b/frontend-web/src/pages/LoginPage.tsx index 826c4f2b..fce89afa 100644 --- a/frontend-web/src/pages/LoginPage.tsx +++ b/frontend-web/src/pages/LoginPage.tsx @@ -33,7 +33,7 @@ export default function LoginPage() { } setStoredAuth(auth); setStatus("success"); - router.replace("/workspace/chats/new"); + router.replace("/page/workspace/chats/new"); } catch (error) { if (cancelled) { return; diff --git a/frontend-web/src/pages/NewAgentPage.tsx b/frontend-web/src/pages/NewAgentPage.tsx index a9d4f531..e345b639 100644 --- a/frontend-web/src/pages/NewAgentPage.tsx +++ b/frontend-web/src/pages/NewAgentPage.tsx @@ -317,7 +317,7 @@ export default function NewAgentPage() { @@ -478,7 +478,7 @@ export default function NewAgentPage() { diff --git a/frontend-web/src/pages/NotFoundPage.tsx b/frontend-web/src/pages/NotFoundPage.tsx index d26ffd87..f41e0035 100644 --- a/frontend-web/src/pages/NotFoundPage.tsx +++ b/frontend-web/src/pages/NotFoundPage.tsx @@ -10,7 +10,7 @@ export function NotFoundPage() { dedicated migration pass.

Open workspace diff --git a/frontend-web/src/pages/PageRoutes.tsx b/frontend-web/src/pages/PageRoutes.tsx new file mode 100644 index 00000000..30f53d58 --- /dev/null +++ b/frontend-web/src/pages/PageRoutes.tsx @@ -0,0 +1,20 @@ +import { Navigate, Route, Routes } from "react-router-dom"; + +import WorkspaceRoutes from "./WorkspaceRoutes"; + +// 根路由容器,统一承接 /page/* 下的所有子系统路由 +export default function PageRoutes() { + return ( + + } /> + + {/* 现有系统:DeerFlow Workspace */} + } /> + + {/* TODO: 引入外部系统路由 + import AnotherSystemRoutes from "外部系统路径"; + } /> + */} + + ); +} diff --git a/frontend-web/src/pages/ScheduledChatPage.tsx b/frontend-web/src/pages/ScheduledChatPage.tsx index df19b271..84adc17f 100644 --- a/frontend-web/src/pages/ScheduledChatPage.tsx +++ b/frontend-web/src/pages/ScheduledChatPage.tsx @@ -12,7 +12,7 @@ export default function ScheduledChatPage() { const { data, isLoading, error } = useSchedulerThread(); if (data?.thread_id) { - return ; + return ; } return ( diff --git a/frontend-web/src/pages/ScheduledTaskRunDetailPage.tsx b/frontend-web/src/pages/ScheduledTaskRunDetailPage.tsx index 15e543fd..4071b1e8 100644 --- a/frontend-web/src/pages/ScheduledTaskRunDetailPage.tsx +++ b/frontend-web/src/pages/ScheduledTaskRunDetailPage.tsx @@ -174,7 +174,7 @@ export default function ScheduledTaskRunDetailPage() { try { await actions.removeRun.mutateAsync(run.id); toast.success("执行记录已删除"); - navigate("/workspace/scheduled/tasks"); + navigate("/page/workspace/scheduled/tasks"); } catch (error) { toast.error(error instanceof Error ? error.message : "删除失败"); } @@ -198,7 +198,7 @@ export default function ScheduledTaskRunDetailPage() { 执行信息 - diff --git a/frontend-web/src/pages/ScheduledTasksPage.tsx b/frontend-web/src/pages/ScheduledTasksPage.tsx index a85c7b69..b0481fbb 100644 --- a/frontend-web/src/pages/ScheduledTasksPage.tsx +++ b/frontend-web/src/pages/ScheduledTasksPage.tsx @@ -686,7 +686,7 @@ export default function ScheduledTasksPage() {