# 报告协作工作台 — 前端对接指南 > **适用版本:** 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 " \ 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/` |