初始化

This commit is contained in:
gozeros committed 2026-09-07 18:24:55 +08:00
1 parent 34474bd5fc
commit b638b541d0
3008 files changed
+837699

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
# Wiki 数据流转与本地编码验收(2026-09-03)
## 结论
本地 BGE-M3 小批量编码已安装、配置并实际运行。已有 Wiki 的向量化、自动沉淀、
普通库文件导出、助手库异步导入、助手库文件导出、空普通库回流及两端向量检索已实测通过。
**技能自动生成 Wiki 尚未完成验收**:WeKnora 的两个生成模型都返回 HTTP 402
`Insufficient Balance`。不能把后半段通过等同于全链路通过。
本次修改运行在本地 DeerFlow 开发服务(前端 5175、后端 8001),连接用户提供的远端
WeKnora;没有把业务代码发布到服务器,没有提交 Git,也没有直接写 WeKnora 图数据库。
## 本地编码配置
- 实现:FastEmbed / ONNX Runtime CPU,官方 `BAAI/bge-m3` 权重。
- 配置名:`bge-embedding-m3`,1024 维,每批 2 条,2 个 CPU 推理线程。
- 运行依赖:FastEmbed 随标准后端依赖安装;模型必须提前放在离线目录中,仍按需加载,不影响主服务启动。
- 权重:约 2.3 GB,持久缓存 `C:\Users\ji\.cache\deerflow\embeddings`,不进入业务仓库。
- 配置:`llmwiki.local_wiki_index.embedding.provider: local`;可用
`local_model_path` 指定离线权重目录,详见后端 README。
- 查询向量在本地实时编码。已有 Wiki 在内容哈希、模型指纹匹配且向量非空时直接复用。
不同模型或维度不能混用;导入不静默重新编码。
- 原始文档分段向量与模型生成后的 Wiki 文本不同,不能相互冒用。本次新建的是 Wiki
正文的 BGE-M3 索引,不是把既有 512 维文档向量改名为 1024 维。
## 实际数据验收
来源是已有普通知识库“图片测试111”,没有改写其 Wiki 内容。
| 阶段 | Wiki | Wiki 向量 | 实体 | 引用关系 |
| --- | ---: | ---: | ---: | ---: |
| 普通知识库导出 | 19 | 101 | 19 | 108 |
| 助手知识库导入后导出 | 19 | 101 | 19 | 108 |
| 新建普通知识库回流后导出 | 19 | 101 | 19 | 108 |
| 知识梳理总库导出(7 个来源记录) | 19 | 101 | 19 | 108 |
逐项断言通过:
1. 三份包的 Wiki slug、标题、正文、摘要、页面类型、别名、分类路径、出链一致。
2. 101 条向量的原始 float32 字节、分段编号、维度和模型指纹完全一致。
3. 将导入前后实体 ID 映射回原 Wiki slug 后,108 条关系的端点及谓词集合完全一致。
4. 总库导出没有因多个来源重复同步而重复导出同一当前页面的分段向量。
5. 元数据回归测试另覆盖 tags、目录恢复、原始文档片段不计入 Wiki 数量、原生技能无
ownership 记录时可被选中、短 Wiki 不漏向量、模型指纹不兼容时不假装向量检索。
普通包约 1.01 MB,助手包约 0.79 MB,回流包约 0.83 MB。普通包还带 2 个原始文档及
108 个原始片段;助手导出提供 Wiki、Wiki 向量和关系,不重新上传原始文档触发二次生成。
### 检索
实际查询:“军方用什么工具评估极端天气带来的风险?”
- 回流普通库 `/api/llmwiki/search` 返回 `mode=vector`,首条“国防气候评估工具”。
- 助手库 `/api/assistant-knowledge/search` 返回 `retrieval_mode=vector`,首条同上;
首个命中分段余弦相似度约 0.6343。
- 浏览器实际提交总库检索,显示“向量检索 · 余弦相似度”和相同命中;不是只检查返回字段。
- 普通库采用已有页面级聚合评分,因此页面最终分数与单分段余弦值不必相等。
- 回流的向量写入 **DeerFlow 管理的本地 Wiki 索引**。未写入 WeKnora 原生向量数据库,
本次结论适用于本系统问答检索,不能声称原生 WeKnora 自身的检索也已完成该向量导入。
### 页面
普通与助手库复用同一 Wiki 阅读组件:目录树、类型/标签筛选、分类面包屑、别名标签、
正文内链、出链、反向链接及邻接关系视图。窄窗口可收起目录。
浏览器验证了“索引 → 国防气候评估工具”、展开关系视图、助手 Wiki 跳回普通库来源,
以及普通 Wiki 的“在知识梳理中查看”反向跳转。
### 验收数据位置
- 原有普通库:`48f669dc-433e-4aab-b5ea-897b6f7a3d2f`
- 知识梳理总库:`c3fd9841-0718-44dc-860e-aae3020fd485`
- 最终助手测试库:`489be905-08c8-40fe-8d60-42ac21dd945a`
- 最终普通回流测试库:`428162aa-03f6-4c61-8994-360f2f39e0c9`
新建测试库名称均以“Wiki链路验收-0903-1826”开头,保留用于复核;未删除用户已有库。
本地 `.runtime/wiki-live-result.json` 保存验收结果,`.runtime/wiki_live_acceptance.py`
保存本次验收流程,均为忽略的运行产物,不包含认证口令。
## 这次实际修复的问题
- 内置技能只有磁盘目录、没有归属记录时,被错误判定为不存在。
- 技能应上传去代码后的知识正文,让 WeKnora 生成 Wiki,而不是伪造一个 `skill` 类型页面。
- Wiki 生成等待使用实际 stats 接口;零页结果明确失败,不无期限等待。
- 向量化完成后自动导出沉淀到总库,并阻断导出回调重复触发全量同步。
- 短摘要和短正文同时存在时,旧分段阈值会将整页过滤为零向量。
- 分类路径必须先创建 WeKnora folder 并绑定 folder_id;只传 category_path 会被服务端清空。
- Wiki 的标签/分类/别名/原始引用保留;图节点绑定真实 Wiki,避免生成重复的空实体页面。
- 保留归一化 float32 原始字节;导入中不能用 `section_index or index`,因为合法编号 0 会被改写。
- 总库多个来源保留审计,但检索与导出只使用当前 Wiki 版本对应的向量,避免旧贡献污染检索。
- 包解析改为同盘临时 SQLite,上传先落盘再异步导入,导出完成后才提供文件下载。
## 阻塞与未通过的范围
1. 技能 `knowledge-base-ingest` 已送入专门创建的普通测试库,但远端
`deepseek-v4-flash` 和 `qwen3.6-flash` 均因余额不足失败。WeKnora 即使失败也可能把
文档 parse_status 标为 completed。需恢复可用生成模型后重新运行技能全链路验收。
2. 已采用磁盘暂存和有界批处理,但本次只有约 1 MB 实测,**未做数 GB 压测、断点续传、
进程中断恢复验收**。大包部署仍需磁盘容量、代理 body size 和超时配置。
3. 当前实体去重主要按类型及规范化名称;这次没有完成跨别名、歧义实体的模型对齐及
多来源事实冲突合并验收,不能视为这一历史需求已全面完成。
4. 重复上传仍会保留独立来源记录。向量导出已消除旧版本重复,但上传身份幂等、部分远端
写入失败后的无损重试、跨实例图片资源打包仍需独立验证。
5. 当前版本已补充自动增量链路:服务启动立即补扫,之后默认每 30 秒扫描全部普通
知识库;Wiki 抽取完成后无需手工点击向量化,并自动以来源快照覆盖同步到知识梳理总库。
6. 回流当前只允许新建空 Wiki 普通库,避免覆盖已有页面。普通库现有页面列表的超大规模
分页与助手问答的最终生成回答不属于这轮通过结论。
## 自动化检查
- 后端相关测试:118 passed(assistant repository、Wiki sync、package roundtrip、
WeKnora 接口、向量检索与知识库管理)。
- 修改的 Python 功能文件 Ruff 检查通过;app.py 既有启动顺序导致的 E402 不在此次改动中处理。
- `git diff --check` 通过(只有仓库换行提示)。
- 前端完整 `pnpm typecheck` 未通过:57 个既有/本轮范围外错误,涉及 nextra 缺失、Markdown
插件类型及其他模块;本轮新建/修改的 Wiki 组件、助手 API、技能面板没有出现在错误清单中。
浏览器实际页面与跳转已验证,但不能声称全项目构建通过。
@@ -0,0 +1,719 @@
# AgentScope 2.0 多智能体报告协作工作台——前端实施计划
> 文档状态:前端代码已落地、契约冻结;联调等待后端运行中干预与报告改写
> 编写日期:2026-09-02;进度更新日期:2026-09-06
> 目标项目:`F:\react01\deeflow-code\deerflow-server\frontend-web`
> 视觉与交互参考:`F:\react01\coze-frontend\frontend\apps\coze-studio`
> 参考页面:`http://localhost:3000/work_flow?space_id=1&workflow_id=wf_88cb9f4c6642`
> 配套后端计划:`docs/agentscope-多智能体报告协作工作台-后端实施计划.md`
> 本文只规划新增页面,不修改现有多智能体会商页面,不复用旧 Workflow Studio 执行状态。
## 0. 当前进度
`frontend-web/src/report-collaboration/` 已实现两栏工作台、会话/方案/run API 客户端、mock driver、SSE reducer、`MessageList` 适配、方案卡、画布、节点抽屉、Composer、报告预览与历史。`RC-FE-001`~`013` 的页面骨架与契约层视为完成。
后端已通规划前路径(澄清 → 出方案 → 选定 → `queued` run),见后端计划 §0。前端真实执行体验仍受阻:
- `GET /runs/{id}/stream` 已挂载(`RC-BE-011`);dispatcher/租约已落地(`RC-BE-012`),默认 `worker_enabled=false` 仍不自动领取 queued run。
- 报告改写 / 恢复接口已由后端 `RC-BE-015` 挂载。
- 质量角色已由 `QualityRoleKernel` 跑通(`RC-BE-013`);真实 AgentScope 模型/`web_search` 仍待后续票。
联调顺序:先打开 `report_collaboration.enabled` 验证规划前 REST 与 `GET /runs/{id}/stream` 重放;画布自动执行需同时打开 `worker_enabled`(默认关)。
## 1. 前端目标
新增独立的「报告协作」工作台。用户可以通过对话提出任意报告需求,查看系统生成的多个协作方案,点击方案卡片在流程图中预览协作结构,确认后观察多智能体执行,并在全过程通过统一对话框提出修改意见。
前端必须满足:
1. 页面采用“左侧对话、右侧流程画布”的固定两栏布局,画布、工具栏、弹窗和抽屉参考 Coze Studio 页面风格。
2. 消息列表必须复用 DeerFlow 现有 `MessageList`,不复制 Coze 的会话消息实现。
3. 所有执行过程、AgentScope 团队消息、智能体回复、工具调用及其原始返回、错误、协助卡、返工和最终报告入口都通过现有消息协议按时间顺序显示在左侧对话区,不设置独立执行结果面板,也不另做“阶段摘要”替代实时消息。
4. 候选方案、流程节点、对话进度和报告使用同一份后端事实,不各自推断状态。
5. 用户可在规划中、运行中、等待输入和报告完成后继续对话。
6. 用户协助卡的出现和提交都不能在前端被解释为“节点已经完成”。
7. 页面刷新、SSE 断线重连、历史会话切换后能恢复到一致状态。
暂定:
- 页面名称:`报告协作`。
- 路由:`/page/workspace/report-collaboration`。
- 会话路由:`/page/workspace/report-collaboration/:sessionId`。
- 默认候选方案数量:3。
- 点击画布节点时打开覆盖式节点说明抽屉;抽屉只展示节点职责和合同,不展示节点执行结果。
---
## 2. 范围与非目标
### 2.1 本期范围
- 新建、查看、重命名、删除协作报告会话。
- 使用对话澄清报告需求。
- 显示 2~3 个候选方案卡片。
- 卡片与右侧流程图预览联动。
- 节点说明抽屉:职责、所依据的信息类型、预期产出、工具、资料要求和验收条件。
- 实时展示节点排队、运行、校验、等待用户、完成、返工、失败和取消。
- 在左侧对话时间线中直接展示每个智能体的流式文本、团队对话、工具调用和工具返回,并展示审稿意见、报告文件和报告版本。
- 并行阶段用横向的智能体选择器切换当前查看对象;选择器只控制同一份消息流的聚焦范围,不创建另一套执行结果视图。
- 运行期间发送补充要求、重新检索、重新分析、节点重跑等指令。
- 完成后进行报告问答、章节重写、全文润色和版本恢复。
- 报告模板选择、Markdown 预览、Markdown/Word 下载。
- 响应式布局、键盘操作、加载反馈和错误恢复。
### 2.2 明确不做
- 不引入 `@coze-workflow/playground` 或 `@coze-arch/coze-design`。
- 不加载 Coze workflow definition、run 或 node run。
- 不允许前端拖拽连线后直接改变执行逻辑。
- 不新增第二套消息气泡、Markdown、引用和文件卡渲染器。
- 不展示模型原始 chain-of-thought、系统/内部 prompt、密钥和被标记为内部控制信息的字段。
- 不把用户有权查看的 AgentScope TeamSay、智能体文本、工具调用或工具返回压缩成“正在检索”等摘要卡;这些原始业务消息必须进入复用的 `MessageList`。敏感字段只能按统一脱敏策略替换,不能以摘要替代。
- 不增加第三栏、常驻详情栏、节点执行结果栏或独立执行时间线。
- 节点抽屉不展示节点生成正文、工具返回、证据列表、错误堆栈或 attempt 历史;这些过程统一在左侧 `MessageList` 的原生消息/工具步骤中查看。
- 不改造现有圆桌、多智能体会商、深度研究和 AI 写作页面。
- 不把浏览器状态作为业务事实源。
---
## 3. 参考页面行为映射
| 目标行为 | Coze 参考文件 | DeerFlow 目标组件 | 说明 |
|---|---|---|---|
| 两栏骨架 | `workflow-studio-layout.tsx` 的面板比例与分隔条 | `ReportCollaborationLayout.tsx` | 左对话、右画布;只保留一条可调整分隔线 |
| 顶栏 | `workflow-studio-header.tsx` | `ReportCollaborationHeader.tsx` | 替换为会话、模板、报告、停止等操作 |
| 候选卡片 | `workflow-proposal-cards.tsx` | `ReportPlanCards.tsx` | 点击预览,明确确认后才执行 |
| 右侧画布 | `workflow-canvas.tsx` | `ReportFlowCanvas.tsx` | 使用 `@xyflow/react` 重建,不引入 Coze Playground |
| 左侧消息区 | `run-conversation-panel.tsx` | `ReportConversationPanel.tsx` | 外层风格参考 Coze,内部复用 DeerFlow `MessageList` |
| 节点说明抽屉 | Coze 节点配置面板的视觉与抽屉行为 | `NodeInspectorDrawer.tsx` | 点击画布节点打开,只展示职责与输入输出合同 |
| 全程执行反馈 | 参考其进度和状态表达 | `AgentStageSelector` + DeerFlow `MessageList` | 选择智能体后直接显示其流式消息、TeamSay、工具调用与返回;不再合成为阶段摘要 |
| 状态栏 | `connection-status.tsx` | `ConnectionStatusBar.tsx` | 展示连接、重连和事件游标 |
| 对话历史 | `left-panel-tabs.tsx` | `ConversationSidebarTabs.tsx` | 对话与历史,不增加“节点资源”Tab |
| 异步反馈 | 规划进度、运行状态 | MessageList 步骤条 + 画布状态 | 操作超过 300ms 必须有反馈 |
主要参考源:
- `F:\react01\coze-frontend\frontend\apps\coze-studio\src\workflow-standalone\components\workflow-studio-layout.tsx`
- `F:\react01\coze-frontend\frontend\apps\coze-studio\src\workflow-standalone\components\workflow-studio-composition.tsx`
- `F:\react01\coze-frontend\frontend\apps\coze-studio\src\workflow-standalone\components\workflow-studio-header.tsx`
- `F:\react01\coze-frontend\frontend\apps\coze-studio\src\workflow-standalone\components\run-conversation\workflow-proposal-cards.tsx`
- `F:\react01\coze-frontend\frontend\apps\coze-studio\src\workflow-standalone\styles.module.less`
若直接复制 Apache-2.0 源码片段或样式,必须保留版权头并维护来源说明;默认做行为映射和视觉重建。
---
## 4. 页面结构
```text
┌──────────────────────────────────────────────────────────────────────────┐
│ 返回 报告协作 / 会话标题 保存状态 模板 报告 停止/继续 更多 │
├────────────────────────────────┬─────────────────────────────────────────┤
│ 对话 / 历史 │ 协作流程图 │
│ │ │
│ DeerFlow MessageList │ @xyflow/react │
│ · 并行时:[协调者][市场][政策] │ · 节点状态与活动连线 │
│ · 用户与协调者/成员消息 │ · 缩放、适配视图、小地图 │
│ · 原生 ToolCall / ToolMessage │ · 点击节点打开覆盖式说明抽屉 │
│ · 协助卡、报告文件和版本 │ │
│ │ │
│ 统一输入框 │ │
├────────────────────────────────┴─────────────────────────────────────────┤
│ 已连接 · run_xxx · 事件 128 │
└──────────────────────────────────────────────────────────────────────────┘
```
节点抽屉覆盖在右侧画布之上,不占用第三栏:
```text
┌─────────────────────────────────────────┐
│ 节点名称 关闭 × │
│ 角色 / 当前状态 │
├─────────────────────────────────────────┤
│ 节点职责 │
│ 要根据什么:所需输入、上游类型、资料要求 │
│ 要产出什么:成果类型、格式、验收条件 │
│ 可用工具与边界 │
│ 上下游关系 │
├─────────────────────────────────────────┤
│ 在对话中对此节点提出意见 │
└─────────────────────────────────────────┘
```
抽屉明确不展示实际执行结果、生成正文、工具返回、来源明细、错误堆栈或 attempt 时间线;这些信息只在左侧原生消息流中展示。
布局默认值:
- 顶栏高度:56px。
- 左侧面板:默认 480px,最小 360px,最大 720px。
- 右侧画布:最小 480px,使用剩余空间。
- 节点说明抽屉:覆盖画布右侧,建议宽度 420px;关闭后完整归还画布空间。
- 状态栏:24~28px。
- 低于 960px:左侧对话和右侧画布改为顶部按钮切换的单面板模式;节点说明仍使用覆盖式 Drawer。
- 两栏分隔条:支持 pointer 拖动、方向键、Home、End。
### 4.1 原生消息流与并行智能体切换
“复用 `MessageList`”在本页面的含义是复用它现有的 assistant 文本、`tool_calls`、`tool` 消息、引用、文件和协助卡渲染语义;不把 AgentScope 的运行过程先解析成专用步骤条、进度摘要或阶段结果卡。
单个智能体:
- `agent.reply.started` 立即创建一条稳定的 assistant 消息;`message.delta` 持续更新同一 `messageId`,所以用户会先看到流式状态、再看到逐字回复,而不是等待节点结束。
- 工具调用创建/更新该 assistant 消息的 `tool_calls`;工具返回创建匹配 `tool_call_id` 的原生 `tool` 消息。现有 `MessageGroup` 负责把两者配对显示,不再另写工具过程渲染器。
- AgentScope TeamSay 作为带 `agentRunId` 的普通 assistant 消息进入同一流,按到达顺序显示。它不是节点完成的证据,节点状态仍由右侧画布和后端状态机决定。
并行智能体:
```text
多角度并行研究 3/4 进行中
[ 协调者 ] [ 市场规模 ✓ ] [ 品牌竞争 ● ] [ 海外市场 ● ] [ 政策风险 ! ]
└──────────────────── 当前选中智能体的原生 MessageList ────────────────────┘
```
- `AgentStageSelector` 只在并行阶段出现,横向放置在左侧对话区顶部;使用现有图标体系的头像/角色图标、名称、状态文字和非纯颜色状态标识。默认选中第一位已开始运行的成员;用户手动切换后保持选择,不因其他成员返回而抢走阅读焦点。
- 所有成员的事件都持续接收、按每个 `agentRunId` 持久化和更新,即使当前未选中;切回即可看到完整历史与正在更新的同一条消息,不丢 token、不重新请求、不把结果拼成摘要。
- 当前选择只将共享用户消息、协调者广播消息和目标成员消息组成一个 `BaseStream<AgentThreadState>` 交给同一个 `MessageList`。切换标签仅切换该视图的消息范围,不改变 run、节点或消息的后端事实。
- 某成员请求用户协助时,其标签显示“待回复”徽标,选择器显示待处理数;点击徽标切到该成员,原生协助工具消息仍由现有 `MessageList` 协助卡渲染。其他成员可继续运行。
- 节点抽屉打开某并行成员时,同步聚焦对应标签;抽屉仍只展示合同。用户从抽屉或输入框发送反馈时带 `targetNodeId`/`agentRunId`,后端在安全点生效。
这不是把完整日志另做成调试面板:对话区展示的是模型和工具的真实业务消息;只有 chain-of-thought、内部提示词及按权限必须脱敏的字段不进入该消息流。
### 4.2 页面主要状态
| 状态 | 左侧对话区 | 右侧流程画布 | 主要操作 |
|---|---|---|---|
| `empty` | 欢迎语和输入框 | 空态 | 输入需求、选模板 |
| `clarifying` | 澄清问题和协助卡 | 需求理解节点 | 回答或补充 |
| `planning` | 用户消息和规划进度 | 骨架或需求图 | 继续补充,等待安全点生效 |
| `proposal_ready` | 2~3 张方案卡 | 点击卡片预览对应图 | 切换、修改、采用 |
| `running` | 协调者与成员原生消息、工具过程和用户反馈 | 实时节点状态 | 干预、停止、在对话中要求重跑 |
| `awaiting_input` | 等待用户卡片、原因和影响 | 对应节点显示等待 | 回答、取消 |
| `reviewing` | 审稿意见和返工过程 | 评审/返工节点状态 | 接受或补充要求 |
| `completed` | 总结、报告文件卡、版本和后续对话 | 完整执行图 | 问答、改写、导出 |
| `failed` | 错误说明和恢复建议 | 失败节点 | 通过对话重试或取消 |
| `cancelled` | 已保留成果说明 | 未完成节点取消 | 从已有成果继续或新建方案 |
---
## 5. 前端目录与组件边界
建议新增:
```text
frontend-web/src/report-collaboration/
api/
client.ts
sessions.ts
plans.ts
runs.ts
reports.ts
types.ts
components/
ReportCollaborationLayout.tsx
ReportCollaborationHeader.tsx
ConversationSidebarTabs.tsx
ReportConversationPanel.tsx
AgentStageSelector.tsx
ReportComposer.tsx
ReportPlanCards.tsx
ReportFlowCanvas.tsx
ReportFlowNode.tsx
NodeInspectorDrawer.tsx
ReportPreviewDialog.tsx
InterventionCard.tsx
SessionHistoryPanel.tsx
ConnectionStatusBar.tsx
hooks/
useReportCollaborationSession.ts
useReportCollaborationStream.ts
useReportCollaborationMessages.ts
useReportPlanSelection.ts
useReportPanelLayout.ts
state/
event-normalizer.ts
event-reducer.ts
selectors.ts
message-adapter.ts
pages/
ReportCollaborationPage.tsx
styles/
report-collaboration.css
tests/
```
修改范围:
- `frontend-web/src/pages/WorkspaceRoutes.tsx`:增加页面和会话路由。
- `frontend-web/src/components/workspace/workspace-nav-chat-list.tsx`:增加导航入口。
- `frontend-web/src/components/workspace/messages/message-list.tsx`:仅允许增加一个向后兼容的通用业务卡片插槽。
- `frontend-web/src/core/messages/utils.ts`:如确需识别通用业务卡类型,只做集中式扩展。
不得把新功能放进:
- `frontend-web/src/roundtable-planning/`
- `frontend-web/src/open-canvas/`
- Coze 项目的 `workflow-standalone/`
---
## 6. 数据来源与前端状态
### 6.1 三层状态
前端状态分为:
1. 服务器快照:session、原生 messages、plans、run、nodes、artifacts、report versions;每条消息带稳定的 `agentRunId`、`nodeRunId`、`phaseId` 和可见性标记,其中 artifacts 只用于生成左侧消息卡和报告文件,不形成独立结果面板。
2. durable SSE reducer:从 `seq` 连续应用事件,更新服务器快照投影。
3. 纯展示状态:预览中的方案、选中的节点、聚焦的并行 `agentRunId`、两栏宽度、节点抽屉开关、窄屏当前视图和画布位置。
纯展示状态不能改变执行结果。点击卡片只改变 `previewPlanId`;只有点击“采用方案”才改变服务器 `selectedPlanId`。
### 6.2 前端事件包
```ts
interface CollaborationEvent<T = unknown> {
eventId: string;
sessionId: string;
runId?: string;
seq: number;
type: string;
timestamp: string;
data: T;
}
```
必须处理:
- 快照先到、事件后到。
- 事件重复。
- 事件短暂乱序。
- SSE 断线后从 `after_seq` 重放。
- 页面切换时旧连接晚到事件。
- token delta 多次更新同一消息。
- TeamSay、assistant 文本、tool-call 参数 delta、tool 返回 delta 分别更新各自稳定消息,不得等节点完成后批量生成摘要。
- 并行成员的消息可交错到达;Reducer 必须按 `agentRunId + messageId` 更新,而不是按当前被选中的标签丢弃未聚焦成员的事件。
- 节点重新打开后出现新 attempt。
Reducer 以 `sessionId + runId + seq` 去重;消息行以 `messageId`、工具结果以 `toolCallId`、标签以 `agentRunId` 使用后端稳定 ID,禁止使用数组下标。
---
## 7. 分步实施计划
### FE-0:冻结参考视觉与行为基线
任务:
1. 在 1440×900、1280×800、1024×768 打开参考页面。
2. 保存初始态、方案态、运行态和节点说明抽屉四组截图。
3. 记录顶栏、两栏比例、间距、圆角、边框、阴影、画布工具栏和状态色。
4. 形成“参考组件—目标组件—允许差异”表。
5. 明确消息列表保持 DeerFlow 原样,不纳入像素对齐。
交付物:
- 视觉基线截图。
- 视觉 token 表。
- 页面行为清单。
验收:参考的每个用户可见状态都有截图或明确说明不存在。
### FE-1:路由、菜单和静态页面骨架
任务:
1. 增加两个路由和菜单项。
2. 创建 `ReportCollaborationPage`。
3. 创建顶栏、左侧对话栏、右侧画布和底部连接栏。
4. 实现可调整宽度的两栏布局,以及“专注对话/专注画布”切换。
5. 实现窄屏单面板切换与覆盖式节点 Drawer。
验收:
- 不连接后端也能呈现完整空态。
- 375~1440px 不出现页面级横向滚动。
- 对话/画布切换不会卸载会话状态或清空输入内容。
- 键盘可以调整两栏分隔条。
### FE-2:页面设计系统与 Coze 风格重建
建立页面专用语义变量:
```text
--rc-bg-page
--rc-bg-panel
--rc-bg-canvas
--rc-border
--rc-text-primary
--rc-text-secondary
--rc-accent
--rc-running
--rc-waiting
--rc-success
--rc-danger
--rc-panel-shadow
--rc-card-radius
```
规则:
- 从 DeerFlow 主题 token 派生,不在组件中散落十六进制颜色。
- 使用 Lucide SVG 图标,不使用 emoji。
- hover/focus/展开动画控制在 150~300ms。
- 支持 `prefers-reduced-motion`。
- 交互状态不能只靠颜色表达。
- 正文对比度达到 4.5:1。
- 操作超过 300ms 显示 spinner、骨架或流式状态。
验收:除 MessageList 内部外,两栏布局、密度、卡片、画布工具栏、弹窗和节点抽屉达到参考基线。
### FE-3:API 类型和 TanStack Query 层
实现:
- session CRUD。
- messages 查询与发送。
- plan request、plans 查询、select。
- run 创建、查询、停止、重试。
- command 创建、确认、取消。
- artifacts、sources、reports、versions 查询。
每个写请求携带:
- `idempotencyKey`
- `expectedRevision`
冲突行为:
- `409`:刷新当前快照并提示用户重新确认。
- `422`:在对应操作附近显示具体错误。
- `429`:显示预算/并发限制,不自动重试。
- `5xx`:保留用户输入,允许使用同一幂等键重试。
验收:API 层不包含页面展示逻辑,类型与后端 OpenAPI 一致。
### FE-4:SSE、快照和事件 Reducer
实现 `useReportCollaborationStream`:
1. 先拉 session snapshot。
2. 使用 snapshot 的 `lastSeq` 建立 SSE。
3. 校验每个事件 session/run 身份。
4. 按 seq 应用事件并去重。
5. 出现序号缺口时暂停 live apply,补拉缺失事件。
6. 断线指数退避并支持 `Last-Event-ID`。
7. 终态后完成最后一次事件补拉再关闭连接。
Reducer 必须覆盖:
- 消息流增量。
- 计划候选和 revision。
- node/node attempt 状态。
- artifact 创建和校验。
- command 分类和执行。
- report delta 和正式版本。
- 错误、取消、接管恢复。
- AgentScope 原始可见消息和工具事件:`message.created/delta/completed`、`tool_call.created/delta/completed`、`tool_result.created/delta/completed`、`team_message.created/delta/completed`。
验收:重复事件、乱序事件和重连都不产生重复消息、重复节点或状态倒退。
### FE-5:复用 DeerFlow `MessageList`
复用:
- `frontend-web/src/components/workspace/messages/message-list.tsx`
- `frontend-web/src/components/workspace/messages/message-list-item.tsx`
- `frontend-web/src/components/workspace/messages/message-group.tsx`
- `frontend-web/src/components/workspace/messages/context.ts`
实现无损协议适配 `message-adapter.ts`:
1. 将已持久化的 `CollaborationMessage` 一对一映射为 LangGraph SDK `Message[]`,不将文本或工具结果二次概括。
2. 构造最小 `BaseStream<AgentThreadState>` 外观:`messages`、`isLoading`、`isThreadLoading`、`values`。
3. 协调者、成员 AgentScope TeamSay 和成员回复都映射为普通 assistant 消息;保留 `messageId`、文本 chunk、角色/成员名称、`agentRunId`、`nodeRunId`、`phaseId`、来源事件 ID 和完成态。
4. AgentScope 工具调用映射为该 assistant 消息的标准 `tool_calls`;调用结果映射为标准 `tool` 消息,并严格复用原有 `tool_call_id` 配对。工具参数、返回正文及增量保持原文,只有后端明确标注的脱敏字段被替换。
5. 将来源、文件、报告 Markdown delta 和正式版本继续映射为既有引用、文件卡和普通 assistant 消息;规划卡、用户确认卡和报告替换卡仍通过一个通用业务卡片插槽渲染。
6. 错误、恢复建议、命令影响范围和报告版本变化也必须进入同一时间线;错误堆栈以可读错误消息替代,但不能吞掉已发生的工具/成员消息。
7. `selectMessagesForAgentRun(allMessages, focusedAgentRunId)` 只在客户端从已同步消息中选出共享消息与目标成员消息;并行选择器不得重新拉取、重新排序或改写消息。
约束:
- 不使用 LangGraph `useThreadStream` 发送消息。
- 新页面输入框调用报告协作 API。
- 不增加多个页面专用 MessageList props。
- 普通聊天、AI 写作、深度研究等未传插槽时必须零变化。
- 不另建执行结果列表、节点结果面板或成果时间线。
- 不用“运行中”“已检索 N 条”等专用摘要替代真实消息。`MessageList` 本身的流式状态和原生工具步骤是唯一过程视图。
验收:
- 历史和流式消息使用同一行稳定更新。
- 单成员首次开始回复、每次工具启动和工具返回均在 300ms 内出现可见的原生消息行或原生工具步骤;不允许持续数秒只有静态加载圈。
- 并行标签切换后显示目标成员的完整已持久化消息和实时 delta;未选中成员持续接收,标签上的运行/待回复状态及时更新。
- 用户上翻阅读时不强制滚底。
- 用户发送新消息后滚到最新位置。
- 卡片显示成功不改变后端节点状态。
### FE-6:需求澄清和候选方案卡片
`ReportPlanCards` 展示:
- 推荐标签、策略、标题、摘要和推荐理由。
- 分析角度和参与角色。
- 关键步骤与质量门。
- 预计时间、成本等级和资料范围。
- 方案验证错误。
交互:
- 点击卡片或“查看流程”只切换预览。
- “采用此方案”调用 select API。
- “开始协作”再次校验并创建 run。
- 方案调整后显示新 revision;旧 revision 只读。
- 过期 revision 返回 409 后不静默覆盖。
验收:连续快速切换三张卡时,画布最终显示最后一次选择,不闪回旧图。
### FE-7:协作流程画布
技术:`@xyflow/react`。可以借鉴但不直接耦合:
- `frontend-web/src/components/ai-elements/canvas.tsx`
- `frontend-web/src/roundtable-planning/components/FlowGraphPanel.tsx`
节点显示:
- 节点名、角色名和分析角度。
- 状态文字、图标和颜色。
- 等待用户、返工、失败和 superseded 标记。
画布能力:
- 自动布局。
- 拖动节点只保存浏览器展示位置。
- 缩放、适配视图、方向切换、小地图。
- 活动边低强度流动动画。
- 点击节点打开覆盖在画布上的节点说明抽屉。
- 节点重跑、补充或纠偏一律通过左侧对话完成;抽屉只提供“在对话中对此节点提出意见”入口。
- 方案切换后清理不属于新方案的本地坐标。
验收:
- 30 个节点内流畅操作。
- 单个 node event 不导致整个 React Flow 重挂。
- `awaiting_input`、`reopened`、`superseded` 不与 `completed` 混淆。
### FE-8:节点说明抽屉与对话定位
点击画布节点打开 `NodeInspectorDrawer`。抽屉只展示计划合同:
1. 节点名称、角色、分析角度和当前状态标签。
2. 节点职责:负责解决什么问题。
3. 要根据什么:所需输入类型、上游节点类型、资料范围和时效要求。
4. 要产出什么:成果类型、结构要求和验收条件。
5. 可用工具、禁止事项、预算上限。
6. 上游与下游关系。
抽屉不展示:
- 节点实际生成内容。
- 工具调用返回值。
- EvidenceBundle 或其他成果物正文。
- attempt 执行时间线。
- 错误堆栈和调试信息。
- 报告正文。
交互:
- “在对话中对此节点提出意见”关闭抽屉、聚焦左侧 Composer,并绑定 `targetNodeId` 上下文。
- 若该节点对应正在并行运行的成员,同时绑定该成员的 `agentRunId` 并切换左侧选择器,使用户发送前能先看到该成员的真实对话与工具过程。
- 用户仍需在对话区输入“重新检索”“重新分析”等要求;抽屉不直接发起业务变更。
- 状态标签来自后端节点状态,但不加载节点结果接口。
- 关闭抽屉不清空 selected node;再次点击同一节点可恢复说明。
报告预览不使用常驻第三栏。最终报告、章节候选和版本先作为左侧 MessageList 中的普通消息/文件卡出现;点击文件卡时复用 `ArtifactFileDetail` 或现有 Markdown 能力打开大尺寸 Dialog,关闭后回到两栏工作台。
验收:
- 抽屉宽度不改变两栏布局,不挤压左侧对话区。
- 抽屉只读取节点合同和图结构,不请求节点执行结果。
- 所有原生成员消息、工具过程、错误和报告入口都能在左侧对话时间线找到。
### FE-9:全过程对话干预
`ReportComposer` 根据状态显示不同提示:
- 空闲:输入报告需求。
- 规划中:补充要求将在规划安全点应用。
- 运行中:协调者正在判断影响范围。
- 等待用户:优先回答等待问题。
- 完成后:可问答、重写、润色或重新研究。
发送流程:
1. 生成 idempotency key。
2. 插入 pending 用户消息;带 `targetNodeId`/`agentRunId` 时在相关成员消息流和共享上下文中引用同一条用户消息,不重复持久化。
3. 显示“正在识别修改范围”。
4. `command.classified` 后展示意图和影响节点。
5. 高成本或低置信度命令显示确认卡。
6. `command.accepted` 后显示排队或等待安全点。
7. 完成后关联新 node attempt 或 report version。
8. 失败时保留原消息和重试入口。
典型映射:
| 用户输入 | 预期操作 |
|---|---|
| “增加政策风险角度” | 新增角度节点和受影响下游 |
| “资料太旧,重新找” | 重新检索指定证据包 |
| “竞争格局分析不到位” | 复用证据,重新分析 |
| “重写第二章” | 只生成章节替换候选 |
| “语气更正式” | 新报告版本,不重新研究 |
| “为什么得出这个结论” | 只读问答,不修改报告 |
验收:
- 双击发送不创建重复命令。
- 选中“品牌竞争分析师”后发送补充要求,只影响该成员及依赖分析,其他并行成员仍保持消息流和运行状态。
- 运行中反馈不会让 run 或 node 误变 completed。
- “重写第二章”不会触发全量检索。
- “资料太旧”不会只做文字润色。
### FE-10:会话历史、报告版本与导出
- History Tab 显示标题、状态、更新时间、当前版本。
- 新建会话不删除旧会话。
- 会话切换时先关闭旧 SSE,再加载新快照。
- 报告修改先生成候选,确认后创建新版本。
- 版本恢复创建新的头版本,不删除历史。
- 下载复用现有 Markdown/Word 能力。
- 文件名来自报告标题,不暴露宿主机或沙箱路径。
验收:历史会话可完整恢复左侧消息流程、右侧节点状态和报告版本;不额外恢复节点结果面板。
### FE-11:测试、视觉回归和性能
单元测试:
- event normalizer/reducer。
- message adapter。
- plan revision 和选择状态。
- node status 和 available actions。
- command pending/idempotency。
组件测试:
- 空态、澄清、规划、候选、运行、等待、完成、失败、取消。
- 卡片选择、节点点击、只读说明 Drawer、确认弹窗和报告预览 Dialog。
- MessageList 通用业务卡插槽零回归。
- 单成员流式文本、TeamSay、工具调用与工具返回的原样重放;并行成员交错到达、切换标签、切回后不中断 delta。
- 非当前标签成员发起协助时显示待回复徽标,点击后使用原生协助卡回复到正确节点。
E2E:
1. 新建会话并输入新能源汽车报告要求。
2. 选择候选并开始。
3. 运行中增加风险角度。
4. 重新检索海外最新资料。
5. 完成后重写第二章。
6. 刷新、断网重连、切换会话。
7. 停止和重试失败节点。
性能门:
- 30 节点画布不卡顿。
- 长消息列表使用现有优化策略。
- SSE 高频 delta 合帧后再 setState。
- 画布和消息区使用独立 selector,避免互相触发整树重渲染。
- 节点抽屉只订阅节点合同和状态,不能因阶段产出更新而反复重渲染。
- 只渲染当前聚焦成员和共享消息的可视窗口;非当前成员的消息仍写入 store,但不因 token delta 重渲染当前 `MessageList`。
---
## 8. 后端接口依赖
前端开发前,后端必须冻结以下接口:
```text
POST /api/report-collaboration/sessions
GET /api/report-collaboration/sessions
GET /api/report-collaboration/sessions/{session_id}
PATCH /api/report-collaboration/sessions/{session_id}
DELETE /api/report-collaboration/sessions/{session_id}
POST /api/report-collaboration/sessions/{session_id}/messages
GET /api/report-collaboration/sessions/{session_id}/messages
POST /api/report-collaboration/sessions/{session_id}/plan-requests
GET /api/report-collaboration/sessions/{session_id}/plans
POST /api/report-collaboration/sessions/{session_id}/plans/{plan_id}/select
POST /api/report-collaboration/sessions/{session_id}/runs
GET /api/report-collaboration/runs/{run_id}
GET /api/report-collaboration/runs/{run_id}/events
GET /api/report-collaboration/runs/{run_id}/stream
POST /api/report-collaboration/runs/{run_id}/commands
POST /api/report-collaboration/runs/{run_id}/cancel
GET /api/report-collaboration/runs/{run_id}/nodes/{node_id}/contract
GET /api/report-collaboration/sessions/{session_id}/reports
POST /api/report-collaboration/sessions/{session_id}/report-rewrites
POST /api/report-collaboration/sessions/{session_id}/report-rewrites/{id}/apply
```
前后端共享枚举:
- session status。
- run status。
- node/node attempt status。
- command intent/status。
- event type/version。
- artifact type/schema version。
- report version/change type。
---
## 9. 前端开发顺序
页面与契约层已完成(代码在 `frontend-web/src/report-collaboration/`):
1. `RC-FE-001`:视觉截图与行为基线。
2. `RC-FE-002`:路由、菜单和左对话/右画布两栏空壳。
3. `RC-FE-003`:页面 token、弹窗、抽屉和响应式。
4. `RC-FE-004`:API 类型和 mock server。
5. `RC-FE-005`:SSE reducer 与快照恢复。
6. `RC-FE-006`:MessageList adapter 和通用卡片插槽。
7. `RC-FE-007`:需求澄清与候选方案卡。
8. `RC-FE-008`:流程画布和自定义节点。
9. `RC-FE-009`:只读节点说明抽屉与对话目标定位。
10. `RC-FE-010`:统一 Composer 和命令确认。
11. `RC-FE-011`:报告预览、版本和导出。
12. `RC-FE-012`:历史、重连和异常恢复。
13. `RC-FE-013`:组件/E2E/视觉/性能验收。
依赖规则:
- 契约已冻结为 `src/report-collaboration/api/types.ts`;后端 `RC-BE-001` 已对齐。
- 真实 SSE 联调:后端 `RC-BE-011` 已挂载 `GET /runs/{id}/stream`;默认无后台循环时流只会重放已入库事件。
- 报告版本操作已接后端 `RC-BE-015`(改写候选 / apply / restore)。
- 执行画布实时状态依赖打开 `worker_enabled`;质量角色内核已在 `RC-BE-013` 落地,真实成员见 `RC-BE-017`。
---
## 10. 前端完成定义
页面与 mock 联调路径已落地。真实后端可联调 `GET /runs/{id}/stream`、领取/恢复(`RC-BE-012`,默认不挂 worker)、质量角色(`RC-BE-013`)以及报告改写/恢复(`RC-BE-015`)。此前以 mock driver 覆盖交互,不以「前端未编码」计未完成。
前端只有同时满足以下条件才算完成:
1. 页面结构与 Coze 参考页面逐状态验收通过。
2. 消息区复用 DeerFlow `MessageList`,其他聊天页面无回归。
3. 候选卡、左侧对话流程、右侧流程图和报告来自同一 session/run/event 状态。
4. 点击候选只预览,明确确认后才执行。
5. 用户可以在规划、运行、完成后三个阶段继续对话。
6. 协助卡显示或提交不会导致前端误标完成。
7. 节点点击只打开说明抽屉,可查看职责、所需输入、预期输出、工具和验收条件,不展示执行结果。
8. 节点重开、返工和 superseded 状态表达正确。
9. SSE 重连、刷新和历史切换无重复消息和状态倒退。
10. 报告修改以候选和版本形式完成,不直接覆盖正式报告。
11. 页面始终只有对话与画布两个常驻区域,所有执行过程和产出入口都能在对话时间线中找到。
12. 响应式、键盘、对比度、加载反馈和错误恢复通过验收。
@@ -0,0 +1,481 @@
# 报告协作工作台 — 前端对接指南
> **适用版本:** RC-BE-000~018 已落地(2026-09-06)
> **配套文档:**
> - [前端实施计划](./agentscope-多智能体报告协作工作台-前端实施计划.md)
> - [后端实施计划](./agentscope-多智能体报告协作工作台-后端实施计划.md)
> - 模块 README:`frontend-web/src/report-collaboration/README.md`
本文说明**前端如何对接真实后端**:环境配置、API 契约、用户流程、状态机、错误处理与联调验收。前端 API 客户端与页面已实现,通常**无需再写一套 HTTP 层**。
---
## 1. 结论速览
| 问题 | 答案 |
|------|------|
| 前端还要写接口吗? | **不用**。`src/report-collaboration/api/` 已封装 REST + SSE,页面通过 `getReportCollaborationDriver()` 调用。 |
| 为什么调不通? | 最常见:`config.yaml` 里 `report_collaboration.enabled: false` → 全部 **503**;或 `worker_enabled: false` → run 不入队执行。 |
| Mock 和真后端怎么切? | **Mock 已移除**。前端永远请求真实后端(`deerflow.rc-mock` / `VITE_RC_MOCK` 开关已删除);联调需 `enabled: true`。 |
| 字段会和 MySQL 对不上吗? | 前端只认 JSON 契约(`api/types.ts`),不直连表。缺列表现为后端 500/SQL 错误,部署后需**重启 Gateway** 跑 `create_all` + 列同步。 |
| 从哪进页面? | `/page/workspace/report-collaboration`(新会话)或 `/page/workspace/report-collaboration/:sessionId`。侧边栏入口默认注释,可直接 URL 访问。 |
---
## 2. 架构:前端已有什么
```
ReportCollaborationPage
└─ useReportCollaborationSession(唯一数据入口)
├─ driver.getSnapshot() ← REST 冷启动
├─ driver.openRunStream() ← SSE 实时事件
├─ driver.sendMessage / createCommand / startRun …
└─ event-reducer ← 事件 → UI 状态
ReportConversationPanel
└─ MessageList(message-adapter 映射 CollaborationMessage → LangGraph Message)
```
### 2.1 目录结构
```text
frontend-web/src/report-collaboration/
api/
types.ts # 冻结契约(snake_case,与后端 Pydantic 一致)
client.ts # rcFetch:鉴权、幂等键、乐观锁、错误映射
sessions.ts # 会话 CRUD、消息
plans.ts # 方案请求 / 列表 / 选定
runs.ts # Run、SSE、命令、节点合同
reports.ts # 报告版本、改写、恢复
driver.ts # 真实后端统一门面(Mock 驱动已移除)
state/
event-normalizer.ts
event-reducer.ts
selectors.ts
message-adapter.ts
hooks/
useReportCollaborationSession.ts # 快照 → SSE → reducer → 动作
useReportCollaborationStream.ts # 断线重连、after_seq 续接
useReportCollaborationMessages.ts
components/ …
pages/ReportCollaborationPage.tsx
```
### 2.2 事实源原则
- **冷启动:** `GET /sessions/{id}` 返回 `SessionSnapshot`(session、messages、plans、run、nodes、agent_runs、commands、report_versions、last_seq)。
- **运行中:** `GET /runs/{id}/stream?after_seq=N` 订阅 durable SSE。
- **断线 / 序号缺口:** `GET /runs/{id}/events?after_seq=N` REST 补拉。
- **澄清 / 规划阶段(尚无 run):** 每 2.5s 轮询快照(后端事件主要在 run 流上)。
纯展示状态(预览方案、聚焦成员、面板宽度)**不**写入服务器,仅存前端。
---
## 3. 后端启用与部署
### 3.1 必改配置
文件:`offline-backend-20260512/backend/config.yaml`
```yaml
report_collaboration:
enabled: false # 联调必须改为 true
worker_enabled: false # 要看成员执行必须 true
planner_model: null # 建议填可用模型名
coordinator_model: null
research_model: null
writer_model: null
reviewer_model: null
# … token_budget、max_concurrent_runs_per_user 等见同文件
```
| 配置 | 现象 |
|------|------|
| `enabled: false` | 所有路由 **503**,`code: REPORT_COLLABORATION_DISABLED` |
| `enabled: true`,`worker_enabled: false` | REST/SSE 可用,run 入队但不自动执行(测 API 可用,无成员流式输出) |
| 两者 `true` + 模型配置完整 | 完整联调 |
### 3.2 MySQL / 表结构
生产环境 Alembic 自动升级默认关闭,启动时走 `create_all` + `_ensure_orm_columns_sync`。
- 迁移脚本(可选手动跑):`20260905_01`、`20260906_01`、`20260907_01`
- ORM:`packages/harness/deerflow/persistence/report_collaboration/model.py`(13 张表)
- **部署后务必重启 Gateway**,观察日志是否有 `Failed to auto-add column report_collaboration_*`
已有 RC-003 时代旧表会通过列同步补上 `template_snapshot_json`、`budget_snapshot_json`、`usage_json`、`budget_exhausted_reason` 等可空列,并创建 `rewrite_proposals`、`audits`。
### 3.3 前端环境变量
| 变量 | 说明 |
|------|------|
| `VITE_BACKEND_BASE_URL` | Gateway 地址,默认 `http://127.0.0.1:8001` |
请求经 `apiFetch`,自动携带登录 JWT(`credentials: "include"`)。**必须先登录 DeerFlow**。
---
## 4. API 基址与请求约定
### 4.1 Base URL
```text
{VITE_BACKEND_BASE_URL}/api/report-collaboration
```
代码常量:`REPORT_COLLABORATION_BASE()`(`api/client.ts`)。
### 4.2 请求头
| Header | 何时携带 | 说明 |
|--------|----------|------|
| `Authorization` | 全部 | 由 `apiFetch` 注入 |
| `X-Idempotency-Key` | 写操作 | 格式 `rc-{uuid}`;重复提交返回首次结果 |
| `X-Expected-Revision` | 选方案、开 run、改会话等 | 会话 `requirement_revision` 或方案 `revision` |
| `Accept: text/event-stream` | SSE | `openRunStream` |
### 4.3 错误响应格式
```json
{
"code": "REPORT_COLLABORATION_DISABLED",
"message": "报告协作工作台未启用",
"detail": { }
}
```
前端映射(`api/client.ts`):
| HTTP | 异常类 | 典型场景 |
|------|--------|----------|
| 409 | `ConflictError` | revision 过期、active run 冲突 |
| 422 | `ValidationError` | 参数/语义错误、缺幂等键 |
| 429 | `RateLimitError` | 预算耗尽、每用户并发 run 上限 |
| 5xx | `ServerError` | 可带同一幂等键重试 |
| 0 | `NetworkError` | fetch 失败 |
### 4.4 完整路由清单(与前端 client 对齐)
| 方法 | 路径 | 前端模块 |
|------|------|----------|
| POST | `/sessions` | `sessions.createSession` |
| GET | `/sessions` | `sessions.listSessions` |
| GET | `/sessions/{id}` | `sessions.getSessionSnapshot` |
| PATCH | `/sessions/{id}` | `sessions.updateSession` |
| DELETE | `/sessions/{id}` | `sessions.deleteSession` |
| GET | `/sessions/{id}/messages` | `sessions.listMessages` |
| POST | `/sessions/{id}/messages` | `sessions.sendMessage` |
| POST | `/sessions/{id}/plan-requests` | `plans.requestPlans` |
| GET | `/sessions/{id}/plans` | `plans.listPlans` |
| POST | `/sessions/{id}/plans/{planId}/select` | `plans.selectPlan` |
| POST | `/sessions/{id}/runs` | `runs.startRun` |
| GET | `/sessions/{id}/reports` | `reports.listReportVersions` |
| POST | `/sessions/{id}/report-rewrites` | `reports.createRewrite` |
| POST | `/sessions/{id}/report-rewrites/{id}/apply` | `reports.applyRewrite` |
| POST | `/sessions/{id}/report-versions/{id}/restore` | `reports.restoreReportVersion` |
| GET | `/runs/{id}` | `runs.getRun` |
| GET | `/runs/{id}/events` | `runs.listRunEvents` |
| GET | `/runs/{id}/stream` | `runs.openRunStream` |
| POST | `/runs/{id}/cancel` | `runs.cancelRun` |
| POST | `/runs/{id}/commands` | `runs.createCommand` |
| POST | `/runs/{id}/commands/{id}/confirm` | `runs.confirmCommand` |
| POST | `/runs/{id}/commands/{id}/cancel` | `runs.cancelCommand` |
| GET | `/runs/{id}/nodes/{nodeId}/contract` | `runs.getNodeContract` |
| GET | `/runs/{id}/artifacts` | (后端已暴露,前端按需接) |
| GET | `/runs/{id}/sources` | (后端已暴露,前端按需接) |
---
## 5. 用户流程与 API 时序
```mermaid
sequenceDiagram
participant U as 用户
participant FE as 前端 RC
participant API as Gateway
participant W as Worker
U->>FE: 输入需求
FE->>API: POST /sessions
FE->>API: POST /sessions/{id}/messages
API-->>FE: 澄清(快照 messages)
U->>FE: 回答澄清
FE->>API: POST /messages
FE->>API: POST /plan-requests
API-->>FE: 候选方案(快照 plans)
U->>FE: 采用方案
FE->>API: POST /plans/{id}/select
U->>FE: 开始协作
FE->>API: POST /runs
FE->>API: GET /runs/{id}/stream
W-->>API: 持久化事件
API-->>FE: SSE
U->>FE: 运行中干预
FE->>API: POST /commands → confirm
```
### 5.1 阶段 0:进入页面
| 路由 | 行为 |
|------|------|
| `/page/workspace/report-collaboration` | 空态;`POST /sessions` 后跳转带 `sessionId` |
| `/page/workspace/report-collaboration/:sessionId` | 加载快照;有 `location.state.rcInitialMessage` 时自动发首条消息 |
路由注册:`frontend-web/src/pages/WorkspaceRoutes.tsx`。
侧边栏入口已在 `workspace-nav-chat-list.tsx` 中放开(「报告协作」项),也可直接访问 URL。
### 5.2 阶段 1:需求澄清(无 run)
```http
POST /api/report-collaboration/sessions
POST /api/report-collaboration/sessions/{sessionId}/messages
Content-Type: application/json
{ "text": "请写一份…", "target_node_id": null, "agent_run_id": null }
```
- `useReportCollaborationSession.send()`:当 `session.status` 不是 `running` / `awaiting_input` / `reviewing` 时走 `sendMessage`。
- 会话状态:`empty` → `clarifying` → `planning` → `proposal_ready`。
- 澄清问题以 `CollaborationMessage` 进入时间线;业务卡通过 `MessageList` 的 `businessCardSlot` 渲染(`additional_kwargs.report_collaboration_card`)。
### 5.3 阶段 2:候选方案
```http
POST /api/report-collaboration/sessions/{sessionId}/plan-requests
POST /api/report-collaboration/sessions/{sessionId}/plans/{planId}/select
```
- **预览方案**(`previewPlanId`):纯前端,不调 API。
- **采用方案**:`select` 写 `selected_plan_id`,**不**创建 run。
- 409:方案 revision 过期 → 前端 `refreshSnapshot`,提示用户重选。
- 右栏 `ReportFlowCanvas` 使用 `PlanNode` / `PlanEdge` 渲染 DAG。
### 5.4 阶段 3:开始执行与 SSE
```http
POST /api/report-collaboration/sessions/{sessionId}/runs
{ "plan_id": "plan-xxx" }
GET /api/report-collaboration/runs/{runId}/stream?after_seq=0
Accept: text/event-stream
```
SSE 每条 `data:` 为 JSON:
```typescript
interface CollaborationEvent {
eventId: string;
sessionId: string;
runId?: string | null;
seq: number;
type: CollaborationEventType;
timestamp: string;
data: unknown;
}
```
常用 `type`(完整列表见 `api/types.ts`):
| 类型 | 含义 |
|------|------|
| `message.created` / `message.delta` / `message.completed` | 对话镜像流式 |
| `tool_call.*` / `tool_result.*` | 工具调用与返回 |
| `team_message.*` | 成员 / 协调者广播 |
| `plan.proposed` / `plan.selected` | 方案生命周期 |
| `run.status.changed` | Run 状态 |
| `node.status.changed` / `node.progress` | 节点执行 |
| `command.confirmation.required` | 需用户确认的干预 |
| `report.version.created` | 报告版本落盘 |
| `heartbeat` | 保活 |
### 5.5 阶段 4:运行中干预
```http
POST /api/report-collaboration/runs/{runId}/commands
POST /api/report-collaboration/runs/{runId}/commands/{commandId}/confirm
POST /api/report-collaboration/runs/{runId}/commands/{commandId}/cancel
POST /api/report-collaboration/runs/{runId}/cancel
```
- Run 活跃时 `send()` 自动走 `createCommand`(而非 `sendMessage`)。
- 可从节点抽屉 / `AgentStageSelector` 传入 `target_node_id`、`agent_run_id`。
- `InterventionCard` 展示 `confirmation_required` 命令;确认后调 `confirmCommand`。
`CommandIntent` 枚举(后端分类,前端展示 reason/impact):`answer_question`、`update_requirements`、`research_again`、`rewrite_section`、`rerun_node`、`replan`、`cancel` 等。
### 5.6 阶段 5:报告版本与改写
```http
GET /api/report-collaboration/sessions/{sessionId}/reports
POST /api/report-collaboration/sessions/{sessionId}/report-rewrites
POST /api/report-collaboration/sessions/{sessionId}/report-rewrites/{rewriteId}/apply
POST /api/report-collaboration/sessions/{sessionId}/report-versions/{versionId}/restore
```
- `createRewrite` 只生成候选,不直接覆盖正式版本。
- `applyRewrite` / `restoreReportVersion` 产生新的 head 版本,历史保留。
- `ReportPreviewDialog` 展示 `ReportVersion.markdown`。
---
## 6. 前端状态机(接入方须知)
**不要**在页面里用零散 REST 字段拼 UI 状态。统一路径:
```text
SessionSnapshot → applySessionSnapshot()
CollaborationEvent[] → normalizeCollaborationEvent() → applyCollaborationEvents()
→ selectors(derivePagePhase、selectPlanForCanvas、…)
```
### 6.1 核心 Selector
| Selector | 用途 |
|----------|------|
| `derivePagePhase` | Composer 占位、方案卡、空态 |
| `selectPlanForCanvas` | 画布 DAG(含预览方案) |
| `selectAgentTabs` | 并行成员 Tab |
| `selectNodeStatusMap` | 节点颜色 / 状态 |
| `selectPendingConfirmationCommands` | 干预确认卡 |
| `selectHeadReportVersion` | 报告预览入口 |
| `selectStreamingAgentRunIds` | 流式成员指示 |
### 6.2 SSE 连接策略(`useReportCollaborationStream`)
- Run 非终态时连接;`completed` / `failed` / `cancelled` 后关流并 REST 最终补拉。
- 断线:指数退避重连(1s~30s),`after_seq` 取 reducer 内 `lastSeq`。
- `hasSeqGap`:暂停 live 应用,先 `listRunEvents` 回填。
### 6.3 乐观消息
用户发送时插入 `metadata.optimistic: true` 的 pending 消息;服务端镜像带相同 `idempotency_key` 时 reducer 原地替换。
---
## 7. 消息与 MessageList 复用
`useReportCollaborationMessages` 将 `CollaborationMessage[]` 转为 `@langchain/langgraph-sdk` 的 `Message[]`,并构造合成 `BaseStream` 供 `MessageList` 消费。
| 来源 | 渲染 |
|------|------|
| `human` / `ai` 文本 | 普通气泡 |
| `tool_calls` + `tool` | 现有工具卡 |
| `team_message` 事件 | 带 `name` 的成员发言 |
| 业务卡 | `businessCardSlot`(方案组、命令确认、报告可用) |
分组键:`core/messages/utils.ts` 中 `assistant:task-report-import` 同级扩展 `assistant:business-card`,判据 `additional_kwargs.report_collaboration_card`。
### 7.1 在其他页面嵌入 RC(最小集)
1. `useReportCollaborationSession(sessionId)`
2. `ThreadContext.Provider` + `MessageList` + `businessCardSlot`
3. 引入 `report-collaboration/styles/report-collaboration.css`
---
## 8. 单测
Mock 驱动(原 `api/mock.ts`)与 `deerflow.rc-mock` / `VITE_RC_MOCK` 开关已移除,前端只请求真实后端;本地演示请直接联调后端。
单测:
```bash
cd frontend-web
pnpm test:report-collaboration
```
---
## 9. 联调验收清单
### 9.1 准备
- [ ] `report_collaboration.enabled: true`
- [ ] `report_collaboration.worker_enabled: true`
- [ ] 五个 `*_model` 已配置且可用
- [ ] Gateway 已重启(MySQL 表/列就绪)
- [ ] 前端已登录(JWT 有效)
### 9.2 探活
```bash
curl -s -H "Authorization: Bearer <token>" \
http://127.0.0.1:8001/api/report-collaboration/sessions
```
期望:**200** + `{ "sessions": [...] }`,而非 503。
### 9.3 页面流程(浏览器 Network)
1. 打开 `/#/page/workspace/report-collaboration`
2. 输入需求 → `POST .../sessions` **200**
3. 跳转 → `GET .../sessions/{id}` **200**,响应含 `SessionSnapshot`
4. 首条消息 → `POST .../messages` **200**
5. 请求方案 → `POST .../plan-requests` **200**
6. 采用方案 → `POST .../plans/{id}/select` **200**
7. 开始协作 → `POST .../runs` **200**
8. `GET .../runs/{id}/stream` 持续收到 SSE(`message.delta`、`node.status.changed` 等)
9. 刷新页面 → 消息与 `last_seq` 续接正确
10. 运行中输入「重新检索」→ `POST .../commands` → 确认卡 → `confirm` **200**
### 9.4 常见失败
| 现象 | 原因 | 处理 |
|------|------|------|
| 全部 503 | `enabled: false` | 改配置并重启 |
| run 一直 queued | `worker_enabled: false` | 改为 true |
| 409 选方案 | revision 过期 | 刷新后重选(前端已自动 refresh) |
| 429 | 预算/并发上限 | 调大配置或取消其他 run |
| 500 + SQL | 表缺列 | 重启 Gateway,查列同步日志 |
---
## 10. 契约与类型维护
- **单一事实源:** `frontend-web/src/report-collaboration/api/types.ts`
- **后端镜像:** `offline-backend-20260512/backend/app/report_collaboration/contracts/`
- 修改字段须同步两份实施计划与本指南,并跑前后端相关测试。
`SessionSnapshot` 形状(摘要):
```typescript
interface SessionSnapshot {
session: ReportSession;
messages: CollaborationMessage[];
plans: ReportPlanCandidate[];
run: ReportRun | null;
nodes: CollaborationNodeState[];
agent_runs: AgentRun[];
commands: CollaborationCommand[];
report_versions: ReportVersion[];
last_seq: number;
}
```
`ReportRun` 可选字段(RC-BE-016):`usage`、`budget_exhausted_reason`。
---
## 11. 后续工作(非对接阻塞)
| 项 | 说明 |
|----|------|
| RC-BE-019 | 灰度发布与离线部署文档 |
| 侧边栏入口 | 已放开(`workspace-nav-chat-list.tsx` 的「报告协作」项) |
| iframe 嵌入 | RC 页尚未专门适配 `embed=1`,可参考现有深链模式扩展 |
| 真人盲评 | RC-BE-018 为确定性评测集,上线前仍需计划 §11.4 人工评审 |
---
## 12. 相关代码索引
| 用途 | 路径 |
|------|------|
| API 客户端 | `frontend-web/src/report-collaboration/api/` |
| 会话 Hook | `frontend-web/src/report-collaboration/hooks/useReportCollaborationSession.ts` |
| 页面 | `frontend-web/src/report-collaboration/pages/ReportCollaborationPage.tsx` |
| 路由 | `frontend-web/src/pages/WorkspaceRoutes.tsx` |
| 后端路由 | `offline-backend-20260512/backend/app/gateway/routers/report_collaboration.py` |
| 配置 | `offline-backend-20260512/backend/config.yaml` → `report_collaboration` |
| 持久化 | `offline-backend-20260512/backend/packages/harness/deerflow/persistence/report_collaboration/` |
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,304 @@
# OpenVibeCoding 内网可视化应用生成平台:分步开发方案
> 文档状态:待实施
>
> 目标环境:现有 Ubuntu 22.04 Docker 服务器;对外可用端口限定为 `8020-8030`。
>
> 本文基于 2026-08-31 的只读巡检编写。未在目标服务器上安装、停止或修改任何服务。
## 1. 目标与范围
将 OpenVibeCoding 改造成一个使用公司内网模型、在公司服务器 Docker 沙箱中生成并运行可视化应用的工作台。用户通过对话生成项目,查看并修改文件,在浏览器中预览运行结果。
第一期只服务以下项目类型:
- 数据大屏、数据看板、海报和展示型页面;
- React + Vite + TypeScript 前端;
- ECharts 图表、流程图;
- 可选的轻量 Python FastAPI 接口;
- 项目文件浏览、编辑、运行日志和实时预览。
第一期明确不做:
- 腾讯云 CloudBase、SCF、TCR、CodeBuddy、云端一键部署;
- 任意语言、任意 npm/PyPI 包的无限制安装;
- 用户自定义 Dockerfile、Docker Socket 挂载、宿主机命令执行;
- 直接将沙箱暴露为独立公网端口;
- 对不可信代码提供“绝对安全”的隔离承诺。
## 2. 已确认的服务器基线
目标服务器当前具备:
| 项目 | 巡检结果 | 判断 |
|---|---:|---|
| 操作系统 | Ubuntu 22.04.5 LTS | 支持 Docker 方案 |
| CPU | 32 vCPU | 足够第一期 |
| 内存 | 125 GiB;巡检时可用约 106 GiB | 足够第一期 |
| 根磁盘 | 2 TB;巡检时可用约 1.6 TB | 足够 Harbor、镜像与工作区 |
| Docker | 29.1.3,Docker Compose 2.40.3 | 已满足 |
| 负载 | 约 0.5 | 当前有余量 |
| `8020-8030` | 巡检时无监听、无 Docker 端口映射 | 可规划使用 |
该服务器已承载 DeerFlow、Coze、LiteLLM、数据库和其他容器;`80`、`8000`、`8005`、`8006`、`3306`、`5432` 等端口已经被使用。因此,本项目不能占用现有端口、网络、数据库或容器名称。
该服务器有公网 IP,且已有多项服务绑定在 `0.0.0.0`。本方案将它视作“公网可达的共享主机”处理;云安全组和宿主机防火墙必须额外核验,不能仅凭端口监听结果假设安全。
## 3. 总体架构
```mermaid
flowchart LR
U[用户浏览器] -->|:8020| G[App Builder Gateway]
U -->|:8022 /preview/<sandboxId>/| P[Preview Router]
G --> O[OpenVibe Web / Agent Server]
O --> M[现有内网模型网关]
O --> DB[(独立 PostgreSQL)]
O --> R[(独立 Redis)]
O --> SM[Local Sandbox Manager]
SM --> D[Docker Engine]
D --> S1[visual-ui Sandbox]
D --> S2[visual-fullstack Sandbox]
P --> S1
P --> S2
D --> H[Harbor :8023]
S1 --> N[内网 pnpm/npm 源或预置离线依赖]
S2 --> N
```
设计原则:
1. 保留 OpenVibe 的对话、任务、文件树、工具卡片和 OpenCode ACP 代码代理体验。
2. 模型请求仅由 OpenVibe 服务端发往既有内网模型网关;API Key 不进入沙箱。
3. 每个项目运行于独立 Docker 容器和独立工作区;沙箱只运行项目代码,不承担平台控制逻辑。
4. 浏览器预览只经过统一预览路由,不为每个项目映射新的公网端口。
5. 先用固定模板和固定依赖清单保证稳定,后续再按白名单增加依赖。
## 4. 端口规划
端口虽然可用,但应尽量少暴露。除 8020、8022 和按需开放的 8023 外,其余服务只加入 Docker 内部网络。
| 端口 | 服务 | 绑定范围 | 用途 |
|---:|---|---|---|
| 8020 | `appbuilder-gateway` | 公网访问面或 VPN 白名单 | 控制台页面、`/api`、SSE/WebSocket |
| 8021 | 预留 | 仅 `127.0.0.1` | 平台调试;第一期不对外开放 |
| 8022 | `preview-router` | 公网访问面或 VPN 白名单 | 统一预览入口:`/preview/<sandboxId>/` |
| 8023 | Harbor | 仅沙箱节点、构建节点和管理员网段 | 私有镜像仓库 HTTPS Registry |
| 8024 | 预留 | 不对外开放 | 后续 npm 私服或运维服务 |
| 8025-8030 | 预留 | 不对外开放 | 扩展、迁移或故障切换 |
### 4.1 预览路由规则
暂时没有可用泛域名时,使用路径型预览:
```text
http(s)://<服务器地址>:8022/preview/<sandboxId>/
```
项目模板的 Vite `base` 和 HMR 路径必须由启动脚本注入为该前缀,确保图片、JS/CSS 资源及热更新不会错误请求到根路径。后续若获得 `*.preview.company.local` 内网泛域名,可切换为子域名预览,用户体验更好。
## 5. 镜像、模板与离线依赖策略
### 5.1 标准镜像
第一期维护两种版本化镜像,不允许用户自行构建 Dockerfile:
| 镜像 | 用途 | 预置内容 |
|---|---|---|
| `appbuilder/visual-ui:<version>` | 大屏、海报、纯前端 | Node.js 20、pnpm、React、Vite、TypeScript、ECharts、流程图库、统一 UI 库、Tailwind、图标与中文字体 |
| `appbuilder/visual-fullstack:<version>` | 前端加接口 | 上述前端依赖 + Python 3.12、FastAPI、Uvicorn、Pydantic |
流程图库第一期统一选择一种(建议 React Flow),避免同时生成多种流程图语法和样式。地图、背景图、字体等资源也应作为受版本控制的本地资产打包,不能依赖 Google Fonts 或公网 CDN。
### 5.2 项目模板
每次创建项目时从镜像中的模板初始化到 `/workspace`:
```text
templates/
visual-screen/ # React + ECharts + React Flow + Mock 数据
visual-screen-api/ # visual-screen + FastAPI + /api 反向代理
poster-page/ # 海报、营销展示、静态动效
```
`visual-screen-api` 约定:前端只请求相对路径 `/api/*`;预览网关将其转发到同一沙箱的 FastAPI `8000` 端口。没有后端时保留 Mock 数据模块,生成的页面仍可运行。
### 5.3 依赖预置方式
不能仅把安装命令写进 Dockerfile 后在用户创建项目时再联网执行。正确做法是:
1. 在受控构建环境中锁定 `package.json`、`pnpm-lock.yaml`、`requirements.lock`。
2. 构建镜像时执行 `pnpm fetch`,在镜像保存只读 pnpm store;Python 包保存到 `/opt/wheelhouse`。
3. 沙箱初始化使用 `pnpm install --offline --frozen-lockfile` 和 `pip install --no-index --find-links=/opt/wheelhouse`。
4. 第一期开启“固定依赖白名单”:AI 只能使用镜像已安装的依赖;新依赖先由管理员审核、加入锁文件和下一版基础镜像。
5. 第二期部署 Verdaccio/Nexus 等内部制品源,作为受控补充而非允许访问公网 npm/PyPI。
## 6. OpenVibe 改造边界
### 6.1 保留
- 前端交互:对话、任务状态、文件浏览与修改、预览、日志;
- OpenCode ACP 代码代理;
- 项目与会话领域模型;
- 代码代理的流式事件协议。
### 6.2 替换或关闭
| OpenVibe 当前能力 | 第一期开源/内网实现 |
|---|---|
| CodeBuddy | 只启用 OpenCode,配置内网 OpenAI 兼容模型网关 |
| CloudBase、SCF 沙箱 | 新增 `LocalDockerSandboxManager` |
| TCR | Harbor |
| CloudBase 环境、用户环境池 | Docker 容器、工作区卷、沙箱记录 |
| CloudBase 消息与事件持久化 | 独立 PostgreSQL;Redis 用于临时队列与状态 |
| CloudBase 存储、函数、MCP、资源管理后台 | 第一期开关关闭或移除入口 |
| CloudBase 项目模板和系统提示词 | 替换为大屏/海报/FastAPI 模板与受控依赖提示词 |
重点改造点是将 OpenVibe 的 `scf-sandbox-manager` 和 CloudBase 生命周期调用替换为本地 Docker 生命周期。为降低前端和 OpenCode 工具改动范围,新管理器应适配现有的文件、命令、日志、健康检查与工作区信息契约,而不是改写所有 Agent 工具。
## 7. 沙箱安全基线
每个沙箱容器必须满足:
- 非 root 用户运行;禁止 `privileged`,禁止 Docker Socket,禁止宿主机目录挂载;
- `--cap-drop=ALL`、`no-new-privileges`、`pids-limit`;
- 首期默认限制:`1 vCPU`、`3 GB 内存`、`10 GB 工作区`、60 分钟空闲回收;
- 使用独立 Docker network;默认无公网出口,只允许访问预置依赖、必要的内网 DNS 和平台回调;
- 数据库、Redis、Harbor 管理接口、宿主机管理端口不在沙箱网络可访问范围;
- 运行命令、容器生命周期、文件变更和预览访问写入审计日志;
- 平台服务由受限服务账号调用 Docker API;用户代码永远不直接获得 Docker 权限。
第一期为“内部可信用户的容器隔离”。若后续允许大量非可信代码或文件上传,应迁移为 Kubernetes 节点,并评估 gVisor/Kata 等更强运行时隔离。
## 8. 分步实施计划
### 阶段 0:部署前隔离与基线确认
**工作项**
1. 为本项目建立独立目录、Docker Compose Project、卷和网络,命名统一为 `appbuilder-*`。
2. 确认云安全组、iptables/nftables 规则:仅允许管理员 SSH;8020、8022、8023 仅向 VPN 或指定办公出口开放。
3. 不复用当前服务器上已有的 MySQL、PostgreSQL、Redis、Nginx 配置;建立独立实例或独立命名空间。
4. 创建内部 CA 证书或确定可信 HTTPS 证书方案;Docker 构建/运行节点信任 Harbor 证书。
5. 轮换目标服务器 root 密码,改用 SSH 密钥和受限运维账号;关闭 root 密码远程登录。
**验收**
- `8020-8030` 无端口冲突;
- 新容器、网络和数据卷均不影响既有 DeerFlow 等服务;
- 业务数据库和沙箱端口无法从非授权公网地址访问。
### 阶段 1:内网基础设施与镜像供应链
**工作项**
1. 在独立 Compose 项目中部署 Harbor,主入口为 `8023`,镜像数据使用独立持久化目录。
2. 构建并推送 `visual-ui`、`visual-fullstack` 两种基础镜像;记录镜像版本、SBOM 和依赖锁文件。
3. 完成三个模板及离线依赖初始化脚本。
4. 先不启动漏洞扫描;严格断网环境下后续通过离线包维护漏洞库。
5. 预留内部 npm/PyPI 源接入方式;第一期仅使用预置依赖。
**验收**
- Docker daemon 能从 Harbor 拉取指定版本镜像;
- 在禁止公网访问的测试网络中,三个模板均能启动并构建;
- 一个 ECharts 页面、一个流程图页面和一个 FastAPI `/api/health` 均可运行。
### 阶段 2:OpenVibe 去 CloudBase 化与内网模型接入
**工作项**
1. Fork 固定版本的 OpenVibeCoding,建立 `local-platform` 改造分支,记录上游 commit 与许可证。
2. 只保留 OpenCode Agent;将 provider 指向现有内网模型网关,模型凭证只保存在服务端环境变量或密钥服务。
3. 将基础实体、任务、消息、流式事件的持久化迁移为独立 PostgreSQL;不再调用 CloudBase 集合。
4. 关闭 CloudBase 环境、函数、存储、数据库、资源仪表盘和相关 MCP 入口。
5. 将系统提示词和项目创建默认项改为“可视化页面 + 受控模板/依赖”。
**验收**
- 不设置任何腾讯云、CloudBase、CodeBuddy、TCR 环境变量时,平台可启动;
- 用户发起“生成 ECharts 大屏”后,OpenCode 能调用内网模型并生成项目文件;
- 服务端日志和沙箱环境中均没有模型 API Key。
### 阶段 3:本地沙箱管理器与预览路由
**工作项**
1. 实现 `LocalDockerSandboxManager`:创建、启动、停止、超时回收、查询状态和删除容器。
2. 每个沙箱分配工作区卷、随机内部容器地址、模板类型和资源限制;在数据库记录 `sandbox_id`、项目、用户、镜像版本、过期时间和状态。
3. 实现或适配沙箱内 runner:文件读写、受限命令执行、日志流、健康检查、前端端口和 FastAPI 端口发现。
4. 实现 `preview-router`:由平台根据 `sandboxId` 维护路由,将 `/preview/<sandboxId>/` 代理到对应内部容器;支持 WebSocket/HMR。
5. 增加容器异常退出、端口未就绪、构建失败、超时与手动重启的可见状态。
**验收**
- 两个用户创建的项目存在于不同容器和不同工作区;
- 修改 ECharts 配置文件后,预览地址显示修改结果;
- FastAPI 模板的 `/api/health` 可通过前端相对路径访问;
- 沙箱不能访问 Docker Socket、宿主机数据库和非白名单网络;
- 空闲超时后容器被回收,项目文件仍可恢复。
### 阶段 4:产品能力收口
**工作项**
1. 在文件面板支持安全的文件查看、编辑、保存、目录树刷新与下载;限制文件大小和可编辑扩展名。
2. 在对话中增加模板选择和能力提示:大屏、流程图、海报、可选 FastAPI。
3. 为 Agent 增加依赖白名单、图表主题、中文字体、Mock 数据、接口契约等明确提示词。
4. 增加“运行/停止/重启/删除沙箱”和日志面板;仅项目所有者或管理员有相应权限。
5. 增加项目快照、模板版本记录和基础审计日志。
**验收**
- 从一句自然语言需求到可预览项目的完整链路可完成;
- 用户可查看并手动修改文件,刷新后改动保留;
- 页面覆盖 ECharts、流程图、响应式大屏和可选 FastAPI 接口;
- 非拥有者不能读取、修改或预览另一用户的项目。
### 阶段 5:上线前安全与运行验收
**工作项**
1. 压测 6 个并发沙箱,并记录 CPU、内存、磁盘、容器启动时间、首屏预览时间和模型等待时间。
2. 验证镜像保留策略、工作区配额、容器回收、数据库备份和 Harbor 备份恢复。
3. 执行越权、路径穿越、命令注入、网络探测、资源耗尽和模型密钥泄露测试。
4. 建立日常运维操作:新增基础依赖、构建镜像、发布模板、回滚镜像、清理工作区和审计查询。
**验收**
- 并发运行时既有 DeerFlow 等服务不受明显影响;
- 任一沙箱异常退出不会影响其他项目或平台控制服务;
- 备份可恢复项目元数据、工作区和 Harbor 镜像;
- 仅开放计划中的入口端口,内部组件无不必要公网监听。
## 9. 首期资源与并发建议
由于该主机已有多项业务,首期不应按理论最大值分配:
| 组件 | 首期限制 |
|---|---|
| Harbor | 8 vCPU、16 GB 内存、500 GB 数据盘配额 |
| OpenVibe 控制服务 | 4 vCPU、8-16 GB 内存 |
| 单个 UI 沙箱 | 1 vCPU、3 GB 内存、10 GB 工作区 |
| 单个 Fullstack 沙箱 | 1.5 vCPU、4 GB 内存、12 GB 工作区 |
| 沙箱并发 | 先限制为 6;稳定后再逐步上调 |
当并发超过 10 个,或要面向更广泛用户开放时,应优先增加独立沙箱节点,而不是继续在本共享主机上加容器。
## 10. 实施顺序与交付物
建议按以下提交/交付顺序推进,每一步都可独立验收和回退:
1. `docs: confirm appbuilder network, ports and security baseline`
2. `infra: add Harbor and versioned visual sandbox images`
3. `infra: add offline templates and dependency locks`
4. `feat: replace OpenVibe CloudBase persistence and model provider`
5. `feat: add local Docker sandbox manager`
6. `feat: add sandbox runner and path-based preview router`
7. `feat: add visual-screen and FastAPI project workflows`
8. `test: add sandbox isolation, preview and recovery coverage`
9. `ops: add backup, cleanup, monitoring and rollout runbook`
达到阶段 3 即可交付“内网模型驱动、生成并预览大屏/海报页面”的 PoC;达到阶段 5 后再作为受控的内部多人平台发布。
@@ -0,0 +1,139 @@
# Iframe Chat Page Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a standalone `/page/workspace/chats_iframe/*` chat page that reuses the existing thread system and history, while showing only `你好` and no input box before the first user message.
**Architecture:** Keep the existing workspace shell and chat runtime intact, then add a new route pair and a copied page component dedicated to the iframe path. The new page keeps the original chat behavior once a thread already has messages, but it owns its empty-state rendering and URL transition to `/page/workspace/chats_iframe/:thread_id`.
**Tech Stack:** React, TypeScript, React Router, Next navigation shim, existing LangGraph thread hooks
---
### Task 1: Add the iframe chat routes
**Files:**
- Modify: `frontend-web/src/pages/WorkspaceRoutes.tsx`
- [ ] **Step 1: Add `chats_iframe/new` and `chats_iframe/:thread_id` routes that render the new page inside the existing workspace layout and chat runtime**
```tsx
<Route
path="chats_iframe/new"
element={
<WorkspaceLayout {...layoutProps}>
<ChatRuntime>
<IframeChatPage />
</ChatRuntime>
</WorkspaceLayout>
}
/>
<Route
path="chats_iframe/:thread_id"
element={
<WorkspaceLayout {...layoutProps}>
<ChatRuntime>
<IframeChatPage />
</ChatRuntime>
</WorkspaceLayout>
}
/>
```
- [ ] **Step 2: Ensure the new route imports compile cleanly**
Run: `rg -n "IframeChatPage|chats_iframe" frontend-web/src/pages/WorkspaceRoutes.tsx`
Expected: `WorkspaceRoutes.tsx` contains one import for `IframeChatPage` and both `chats_iframe` route entries.
### Task 2: Add a standalone iframe chat page
**Files:**
- Create: `frontend-web/src/pages/IframeChatPage.tsx`
- Reference: `frontend-web/src/pages/ChatPage.tsx`
- [ ] **Step 1: Copy the current `ChatPage` implementation into `IframeChatPage` and preserve the existing chat runtime integration**
```tsx
export default function IframeChatPage() {
const routeBase = useRouteBase();
const router = useRouter();
const [showFollowups, setShowFollowups] = useState(false);
const [hasStartedConversation, setHasStartedConversation] = useState(false);
const { threadId, setThreadId, isNewThread, setIsNewThread, isMock } =
useThreadChat();
// keep the rest of the existing chat hooks and handlers aligned with ChatPage
}
```
- [ ] **Step 2: Change the thread creation URL replacement to stay on the iframe route family**
```tsx
onStart: (createdThreadId) => {
setThreadId(createdThreadId);
setIsNewThread(false);
history.replaceState(
null,
"",
`${window.location.pathname}${window.location.search}#${routeBase}/chats_iframe/${createdThreadId}`,
);
},
```
- [ ] **Step 3: Replace the default landing state with a simple `你好` view and do not render the input box while the iframe page is still a brand-new untouched chat**
```tsx
{isLandingState && mounted ? (
<div className="flex min-h-[240px] items-center justify-center">
<div className="text-2xl font-medium">你好</div>
</div>
) : null}
{!isLandingState &&
(mounted ? (
<div className="w-full -translate-y-4">
<InputBox
className="w-full"
isNewThread={false}
threadId={threadId}
autoFocus={false}
status={thread.error ? "error" : thread.isLoading ? "streaming" : "ready"}
context={settings.context}
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" || isUploading}
onContextChange={(context) => setSettings("context", context)}
promptPrefix={promptPrefixProp}
onFollowupsVisibilityChange={setShowFollowups}
onSubmit={handleSubmit}
onStop={handleStop}
/>
</div>
) : (
<div aria-hidden="true" className="bg-background/5 h-32 w-full -translate-y-4 rounded-3xl border" />
))}
```
- [ ] **Step 4: Keep message rendering, thread title, export, token usage, todos, artifact triggers, and all post-first-message behavior identical to the original page**
Run: `rg -n "MessageList|ThreadTitle|ArtifactTrigger|TokenUsageIndicator|TodoList" frontend-web/src/pages/IframeChatPage.tsx`
Expected: The new page still renders the same core chat primitives used by `ChatPage`.
### Task 3: Verify the new page compiles
**Files:**
- Test: `frontend-web/src/pages/IframeChatPage.tsx`
- Test: `frontend-web/src/pages/WorkspaceRoutes.tsx`
- [ ] **Step 1: Run the frontend build**
Run: `cmd /c npm run build`
Expected: Build succeeds without TypeScript or bundling errors related to `IframeChatPage` or `chats_iframe`.
- [ ] **Step 2: Spot-check the changed routes in the built source**
Run: `rg -n "chats_iframe" frontend-web/src/pages`
Expected: Matches appear in `WorkspaceRoutes.tsx` and `IframeChatPage.tsx`.
- [ ] **Step 3: Commit after verification**
```bash
git add docs/superpowers/plans/2026-06-01-iframe-chat-page.md frontend-web/src/pages/WorkspaceRoutes.tsx frontend-web/src/pages/IframeChatPage.tsx
git commit -m "feat: add iframe chat page routes"
```