# 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 扩展"开始继续。
```