# Open Canvas 集成进度文档 > 更新时间:2026-05-16 > 当前分支:dhk-1 --- ## 一、整体进度概览 | 阶段 | 名称 | 状态 | |------|------|------| | 前端阶段一 | 路由与目录骨架 | ✅ 已完成 | | 前端阶段二 | Canvas 双栏布局 | ✅ 已完成 | | 前端阶段三 | 类型与状态协议 | ✅ 已完成(随阶段一一并完成) | | 后端阶段一 | ThreadState 扩展 | ✅ 已完成 | | 后端阶段二 | canvas_agent 注册 + 最小节点闭环 | ✅ 已完成 | | 前端阶段四 | API 适配层 | ✅ 已完成 | | 前端阶段五 | 线程与流式状态接入 | ✅ 已完成 | | 前端阶段六 | Artifact 渲染器增强(CodeMirror / Markdown) | ✅ 已完成 | | 前后端阶段七 | 局部选区改写 | ✅ 已完成 | | 前后端阶段八 | 快捷动作系统 | ✅ 已完成 | | 前后端阶段九 | 本地模型支持完善 | ✅ 已完成 | **当前状态**:全部 9 个阶段已完成 🎉 --- ## 二、已完成工作详情 ### 2.1 前端路由接入 修改了 `frontend-web/src/pages/PageRoutes.tsx`: ```tsx import OpenCanvasRoutes from "@/open-canvas/routes/OpenCanvasRoutes"; // 在 PageLayout 内部添加: } /> ``` 子路由: - `/page/canvas` → `CanvasHomePage`(欢迎页 + 快速开始) - `/page/canvas/:thread_id` → `CanvasThreadPage`(主工作区) ### 2.2 前端目录结构(已建立,26 个文件) ``` frontend-web/src/open-canvas/ api/ client.ts # stub,Phase 5 实现 models.ts # stub,Phase 5 实现 threads.ts # stub,Phase 5 实现 runs.ts # stub,Phase 5 实现 components/ layout/ CanvasShell.tsx # 顶层布局壳,flex-col CanvasHeader.tsx # 顶部栏:返回键、折叠按钮、标题、流式指示 CanvasResizableLayout.tsx # 左右可拖拽双栏(react-resizable-panels) chat/ CanvasChatPanel.tsx # 左栏:欢迎状态 + 消息列表 + 禁用输入框 artifact/ CanvasArtifactPanel.tsx # 右栏:空状态或渲染器 CanvasArtifactHeader.tsx # 右栏顶部:图标、标题、语言 Badge、版本导航 CanvasArtifactVersionNav.tsx # 版本 prev/next 导航 CanvasEmptyState.tsx # 空状态:5 种快速开始模板 TextArtifactRenderer.tsx # 文本渲染(Phase 6 增强为 Markdown) CodeArtifactRenderer.tsx # 代码渲染(Phase 6 增强为 CodeMirror) contexts/ CanvasThreadContext.tsx # threadId、modelName CanvasGraphContext.tsx # messages、artifact、isStreaming CanvasUIContext.tsx # isChatCollapsed、isEditing、selection 等 UI 状态 hooks/ useCanvasStream.ts # stub,Phase 5 实现 useCanvasArtifact.ts # stub,Phase 6 实现 useCanvasSelection.ts # stub,Phase 7 实现 useCanvasQuickActions.ts # stub,Phase 8 实现 pages/ CanvasHomePage.tsx # 欢迎页 + 快速开始入口 CanvasThreadPage.tsx # 三层 Provider 装配 + CanvasShell routes/ OpenCanvasRoutes.tsx # React Router 子路由 types/ artifact.ts # CanvasArtifact、CanvasArtifactText、CanvasArtifactCode thread.ts # CanvasMessage、CanvasThreadValues、CanvasThreadMetadata model.ts # CanvasModel(含 supports_canvas_* 能力字段) selection.ts # CanvasTextSelection、CanvasCodeSelection quick-action.ts # QuickAction、QuickActionId utils/ artifact.ts # getCurrentArtifactContent、appendArtifactVersion 等工具 selection.ts # stub thread.ts # stub stream.ts # stub quick-actions.ts # stub constants/ index.ts # CANVAS_AGENT_ID、PROGRAMMING_LANGUAGES、QUICK_ACTIONS_* ``` ### 2.3 核心类型定义 ```ts // types/artifact.ts export type CanvasArtifact = { currentIndex: number; // 从 1 开始 contents: CanvasArtifactContent[]; }; export type CanvasArtifactText = { index: number; type: "text"; title: string; fullMarkdown: string; }; export type CanvasArtifactCode = { index: number; type: "code"; title: string; language: ProgrammingLanguageOptions; code: string; }; ``` ### 2.4 Context 结构 ```tsx // CanvasThreadPage.tsx 的装配顺序 ``` --- ## 三、已解决的技术难题 ### 3.1 react-resizable-panels v4.6.2 API 差异 此版本与旧版 API 不同,必须使用: ```tsx // ✅ 正确写法 import type { GroupImperativeHandle } from "react-resizable-panels"; const layoutRef = useRef(null); // ← 必须有 id // ← 必须有 id // 切换折叠 layoutRef.current?.setLayout({ chat: 0, artifact: 100 }); // ← 对象形式,非数组 ``` ### 3.2 Flex 高度链(防止底部内容被裁剪) ``` PageLayout(h-screen) └── main(flex-1) └── CanvasShell div(h-full flex flex-col) └── CanvasResizableLayout wrapper(flex-1 min-h-0 overflow-hidden)← 关键 └── ResizablePanelGroup(h-full) └── 面板内容(h-full) ``` **`min-h-0` 是必须的**,否则 flex 子项不会收缩,底部内容被推出屏幕外。 ### 3.3 折叠/展开按钮 `CanvasHeader.tsx` 中通过 `useCanvasUIContext()` 控制 `isChatCollapsed`, `CanvasResizableLayout.tsx` 中用 `useEffect` 监听并调用 `setLayout`: ```tsx useEffect(() => { layoutRef.current?.setLayout( isChatCollapsed ? { chat: 0, artifact: 100 } : { chat: 35, artifact: 65 } ); }, [isChatCollapsed]); ``` --- ## 四、下一步:后端 ThreadState 扩展 **目标文件**:`offline-backend-20260512/backend/packages/harness/deerflow/agents/thread_state.py` **任务**:在现有 `ThreadState` 中新增以下 Canvas 专用字段,不影响 `artifacts: list[str]`(文件产物): ```python # 新增 TypedDict(建议单独定义) class CanvasArtifactText(TypedDict): index: int type: Literal["text"] title: str fullMarkdown: str class CanvasArtifactCode(TypedDict): index: int type: Literal["code"] title: str language: str code: str class CanvasArtifact(TypedDict): currentIndex: int contents: list[CanvasArtifactText | CanvasArtifactCode] # 在 ThreadState 中新增字段 artifact: Optional[CanvasArtifact] # Canvas 主文档(区别于 artifacts 文件列表) highlighted_text: Optional[str] # 用户选中的文本片段 highlighted_code: Optional[str] # 用户选中的代码片段 canvas_mode: Optional[Literal["text", "code"]] quick_action_id: Optional[str] # 快捷动作 id web_search_enabled: bool # 默认 False web_search_results: Optional[list[dict]] ``` **验收标准**: - 旧线程无 `artifact` 字段时不报错 - `pnpm typecheck` 无新增错误(前端字段已对齐) - `make test` 全量回归通过 **之后紧接**:`canvas_agent` 注册 + `factory.py` 修改 + 最小节点闭环 --- ## 五、关键文件位置 | 文件 | 用途 | |------|------| | `frontend-web/docs/openCanvas.md` | 主需求文档 | | `frontend-web/docs/openCanvas-frontend-checklist.md` | 前端详细清单 | | `frontend-web/docs/openCanvas-backend-checklist.md` | 后端详细清单 | | `frontend-web/docs/openCanvas-progress.md` | 本进度文档 | | `F:\react01\openCanvas` | open-canvas 源码参考(本地克隆) | | `offline-backend-20260512/backend/packages/harness/deerflow/agents/thread_state.py` | 下一步主改文件 | | `offline-backend-20260512/backend/packages/harness/deerflow/agents/factory.py` | canvas_agent 注册位置 | --- ## 六、架构决策记录 | 决策 | 内容 | |------|------| | canvas_agent 方案 | 新增独立 canvas_agent,不改 lead_agent(方案 A)| | 路由形式 | `/page/canvas/*`,挂在 PageLayout 内 | | artifact 字段共存 | `artifact`(Canvas 文档)与 `artifacts: list[str]`(文件列表)独立共存 | | 模型配置真源 | 后端 config.yaml,前端动态加载 /api/models | | 前端 artifact 索引 | 从 1 开始(与 open-canvas 对齐) | | 流式首版策略 | 节点完成后输出完整 artifact 对象,不做 token 级局部流 | --- ## 七、开始新对话的推荐提示词 ``` 我们在集成 open-canvas 到 DeerFlow 项目。 参考进度文档:frontend-web/docs/openCanvas-progress.md 目前前端阶段一/二/三已完成,下一步是后端 ThreadState 扩展。 请从第四节"下一步:后端 ThreadState 扩展"开始继续。 ```