deerflow-code/docs/agentscope-多智能体报告协作工作台-前端对接指南.md
2026-09-07 18:24:55 +08:00

482 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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