18 KiB
报告协作工作台 — 前端对接指南
适用版本: RC-BE-000~018 已落地(2026-09-06)
配套文档:
本文说明前端如何对接真实后端:环境配置、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 目录结构
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=NREST 补拉。 - 澄清 / 规划阶段(尚无 run): 每 2.5s 轮询快照(后端事件主要在 run 流上)。
纯展示状态(预览方案、聚焦成员、面板宽度)不写入服务器,仅存前端。
3. 后端启用与部署
3.1 必改配置
文件:offline-backend-20260512/backend/config.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
{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 错误响应格式
{
"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 时序
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)
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:候选方案
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
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:
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:运行中干预
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:报告版本与改写
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 状态。统一路径:
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(最小集)
useReportCollaborationSession(sessionId)ThreadContext.Provider+MessageList+businessCardSlot- 引入
report-collaboration/styles/report-collaboration.css
8. 单测
Mock 驱动(原 api/mock.ts)与 deerflow.rc-mock / VITE_RC_MOCK 开关已移除,前端只请求真实后端;本地演示请直接联调后端。
单测:
cd frontend-web
pnpm test:report-collaboration
9. 联调验收清单
9.1 准备
report_collaboration.enabled: truereport_collaboration.worker_enabled: true- 五个
*_model已配置且可用 - Gateway 已重启(MySQL 表/列就绪)
- 前端已登录(JWT 有效)
9.2 探活
curl -s -H "Authorization: Bearer <token>" \
http://127.0.0.1:8001/api/report-collaboration/sessions
期望:200 + { "sessions": [...] },而非 503。
9.3 页面流程(浏览器 Network)
- 打开
/#/page/workspace/report-collaboration - 输入需求 →
POST .../sessions200 - 跳转 →
GET .../sessions/{id}200,响应含SessionSnapshot - 首条消息 →
POST .../messages200 - 请求方案 →
POST .../plan-requests200 - 采用方案 →
POST .../plans/{id}/select200 - 开始协作 →
POST .../runs200 GET .../runs/{id}/stream持续收到 SSE(message.delta、node.status.changed等)- 刷新页面 → 消息与
last_seq续接正确 - 运行中输入「重新检索」→
POST .../commands→ 确认卡 →confirm200
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 形状(摘要):
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/ |