# 接口契约清单 ## 前端 API 基础配置 位置:`src/lib/config.ts`、`src/lib/api.ts` ```ts API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://:8000' timeout: 30000 ``` 前端 request interceptor 会从 localStorage 读取 `token`,附加: ```http Authorization: Bearer ``` 当前后端多数接口没有鉴权依赖,所以这个 header 主要是前端侧行为。 ## 前端封装的 API | 函数 | 方法与路径 | 状态 | 说明 | |------|------------|------|------| | `login(username, password)` | `POST /api/auth/login` | 对齐 | 后端返回 `{ token, username }`,前端只使用 token | | `getProjects()` | `GET /api/projects` | 对齐 | 前端映射 `frame_count`、`thumbnail_url` 等字段 | | `createProject(payload)` | `POST /api/projects` | 对齐 | 支持 `name`、`description`、`parse_fps` | | `updateProject(id, payload)` | `PATCH /api/projects/{id}` | 对齐 | 后端是 `PATCH /api/projects/{id}` | | `deleteProject(id)` | `DELETE /api/projects/{id}` | 对齐 | 当前 UI 未明显接入 | | `getTemplates()` | `GET /api/templates` | 对齐 | 前端从 `mapping_rules` 取 classes/rules | | `createTemplate(payload)` | `POST /api/templates` | 对齐 | 后端会打包 classes/rules 到 mapping_rules | | `updateTemplate(id, payload)` | `PATCH /api/templates/{id}` | 对齐 | 模板编辑页使用 | | `deleteTemplate(id)` | `DELETE /api/templates/{id}` | 对齐 | 模板编辑页使用 | | `uploadMedia(file, projectId)` | `POST /api/media/upload` | 对齐 | multipart form-data | | `uploadDicomBatch(files, projectId)` | `POST /api/media/upload/dicom` | 对齐 | multipart form-data | | `parseMedia(projectId, options?)` | `POST /api/media/parse?project_id=...` | 对齐 | 创建异步拆帧任务并返回 task;由项目库“生成帧”显式调用,支持 `parse_fps`、`max_frames`、`target_width` | | `getTask(taskId)` | `GET /api/tasks/{task_id}` | 对齐 | 查询异步任务状态 | | `cancelTask(taskId)` | `POST /api/tasks/{task_id}/cancel` | 对齐 | 取消 queued/running 任务,后端写 cancelled 并尝试 revoke Celery | | `retryTask(taskId)` | `POST /api/tasks/{task_id}/retry` | 对齐 | 对 failed/cancelled 任务创建新的 queued 重试任务 | | `getProjectFrames(projectId)` | `GET /api/projects/{id}/frames` | 对齐 | 后端返回预签名 image_url,以及 `timestamp_ms`、`source_frame_number` | | `predictMask(payload)` | `POST /api/ai/predict` | 对齐 | 前端发送 `image_id/prompt_type/prompt_data/model`,并把后端 `polygons` 转为 `masks[].pathData` | | `propagateMasks(payload)` | `POST /api/ai/propagate` | 对齐 | 当前帧 seed mask 向视频片段传播,并保存后续帧标注 | | `getAiModelStatus(selectedModel?)` | `GET /api/ai/models/status` | 对齐 | 返回 GPU、SAM 2、SAM 3 的真实运行状态 | | `getProjectAnnotations(projectId, frameId?)` | `GET /api/ai/annotations` | 对齐 | 前端加载工作区时用于回显已保存标注 | | `saveAnnotation(payload)` | `POST /api/ai/annotate` | 对齐 | 工作区归档保存当前项目未保存 mask | | `updateAnnotation(annotationId, payload)` | `PATCH /api/ai/annotations/{annotation_id}` | 对齐 | 工作区归档保存 dirty mask | | `deleteAnnotation(annotationId)` | `DELETE /api/ai/annotations/{annotation_id}` | 对齐 | 工作区清空当前帧已保存标注 | | `importGtMask(file, projectId, frameId, templateId?)` | `POST /api/ai/import-gt-mask` | 对齐 | multipart 上传 GT mask,后端按非零像素值/连通域生成 polygon 标注和 seed point | | `getDashboardOverview()` | `GET /api/dashboard/overview` | 对齐 | Dashboard 初始统计、队列和活动日志 | | `exportCoco(projectId)` | `GET /api/export/{projectId}/coco` | 对齐 | 后端实际是 `GET /api/export/{project_id}/coco` | | `exportMasks(projectId)` | `GET /api/export/{projectId}/masks` | 对齐 | 下载单标注 mask、语义融合 mask 和类别映射 ZIP | ## 后端 FastAPI 接口 以下列表来自当前运行的 OpenAPI: | 方法 | 路径 | 用途 | |------|------|------| | POST | `/api/auth/login` | 登录 | | POST | `/api/projects` | 创建项目 | | GET | `/api/projects` | 项目列表 | | GET | `/api/projects/{project_id}` | 项目详情 | | PATCH | `/api/projects/{project_id}` | 更新项目 | | DELETE | `/api/projects/{project_id}` | 删除项目 | | POST | `/api/projects/{project_id}/frames` | 添加帧记录 | | GET | `/api/projects/{project_id}/frames` | 项目帧列表 | | GET | `/api/projects/{project_id}/frames/{frame_id}` | 单帧详情 | | POST | `/api/templates` | 创建模板 | | GET | `/api/templates` | 模板列表 | | GET | `/api/templates/{template_id}` | 模板详情 | | PATCH | `/api/templates/{template_id}` | 更新模板 | | DELETE | `/api/templates/{template_id}` | 删除模板 | | POST | `/api/media/upload` | 上传视频/图片/DICOM 单文件 | | POST | `/api/media/upload/dicom` | 批量上传 DICOM | | POST | `/api/media/parse` | 创建 Celery 拆帧任务;query 支持 `project_id`、`source_type`、`parse_fps`、`max_frames`、`target_width` | | GET | `/api/tasks` | 查询后台任务列表 | | GET | `/api/tasks/{task_id}` | 查询单个后台任务 | | POST | `/api/tasks/{task_id}/cancel` | 取消后台任务 | | POST | `/api/tasks/{task_id}/retry` | 重试失败或取消的后台任务 | | POST | `/api/ai/predict` | SAM 2 / SAM 3 可选推理 | | POST | `/api/ai/propagate` | SAM 2 / SAM 3 视频片段传播并保存标注 | | GET | `/api/ai/models/status` | GPU 和 SAM 模型状态 | | POST | `/api/ai/auto` | 自动分割 | | POST | `/api/ai/annotate` | 保存 AI 标注 | | POST | `/api/ai/import-gt-mask` | 导入 GT mask 并生成标注/seed point | | GET | `/api/ai/annotations` | 查询项目标注,可选按帧过滤 | | PATCH | `/api/ai/annotations/{annotation_id}` | 更新已保存标注 | | DELETE | `/api/ai/annotations/{annotation_id}` | 删除已保存标注 | | GET | `/api/dashboard/overview` | Dashboard 聚合快照 | | GET | `/api/export/{project_id}/coco` | 导出 COCO JSON | | GET | `/api/export/{project_id}/masks` | 导出 PNG mask ZIP | | GET | `/health` | 健康检查 | | WS | `/ws/progress` | WebSocket 进度通道,未出现在 OpenAPI paths 中 | ### WebSocket 进度通道 `/ws/progress` 用于 Dashboard 实时接收后台任务状态。前端连接成功后会定时发送 `ping` 作为心跳;后端收到任意文本心跳后返回: ```json { "type": "status", "status": "connected", "message": "Progress stream active", "timestamp": "2026-05-01T00:00:00+00:00" } ``` 后台任务进度由 Celery worker 写入 Redis `seg:progress` 频道,再由 FastAPI 转发到当前活跃 WebSocket 连接。Dashboard 的“WebSocket 已连接/断开”状态来自浏览器 WebSocket 的 `onopen/onclose/onerror`,不再依赖是否刚好收到任务进度事件。 ## 关键请求体 ### 登录 ```json { "username": "admin", "password": "123456" } ``` ### 创建项目 ```json { "name": "example.mp4", "description": "导入说明", "parse_fps": 30 } ``` ### 创建标准帧序列拆帧任务 ```text POST /api/media/parse?project_id=1&parse_fps=15&max_frames=120&target_width=960 ``` 任务 `payload` 会记录本次拆帧参数;完成后的 `result.frame_sequence` 返回 `original_fps`、`parse_fps`、`frame_count`、`duration_ms`、`target_width`、帧宽高和 MinIO object prefix。每条 `FrameOut` 包含: ```json { "frame_index": 0, "image_url": "http://...", "width": 960, "height": 540, "timestamp_ms": 0, "source_frame_number": 0 } ``` ### 创建/更新模板 ```json { "name": "腹腔镜胆囊切除术", "color": "#06b6d4", "z_index": 0, "classes": [ { "id": "cls-1", "name": "胆囊", "color": "#ffae00", "zIndex": 280, "category": "腹腔镜胆囊切除术" } ], "rules": [] } ``` ### AI 推理请求体 前端 `predictMask()` 当前已适配后端 `PredictRequest`: ```json { "image_id": 123, "model": "sam2", "prompt_type": "point", "prompt_data": { "points": [[0.5, 0.5]], "labels": [1] } } ``` `prompt_type` 支持: - `point` - `box` - `interactive`,用于 SAM 2 交互式细化,`prompt_data` 同时携带 `box`、累计 `points` 和 `labels`。 - `semantic`,选择 `sam3` 时进入 SAM 3 文本语义推理。前端 AI 页面不会再用 SAM 2 发送纯文本 semantic;SAM 2 的交互入口应使用点/框提示。SAM 3 真实可用性由 `/api/ai/models/status` 中的外部环境和本地 checkpoint 状态决定。 SAM 2 点提示和 auto fallback 当前只采用最高分候选 mask,避免同一提示下多个备选 mask 被前端叠加显示。 工作区 SAM 2 请求包含反向点时,`CanvasArea` 会发送 `options.auto_filter_background=true` 和 `options.min_score=0.05`;如果负向点过滤后没有可用 polygon,前端会移除当前旧候选 mask 并要求重新框选或添加正向点。 选择 `sam3` 且发送 `box` 时,前端仍传 normalized `[x1, y1, x2, y2]`,后端适配层会转换成官方几何 prompt 的 `[center_x, center_y, width, height]` 正框;当前 SAM 3 不接正/反点修正。 可选 `options` 字段: - `crop_to_prompt`:对 point/box/interactive prompt 按锚点或框附近区域裁剪后推理,再把 polygon 回映射到原图坐标。 - `auto_filter_background`:过滤低分结果,并移除包含负向点的 polygon。 - `min_score`:配合 `auto_filter_background` 使用的最低置信度阈值;对 SAM 3 semantic 请求也会作为 external worker 的 `confidence_threshold` 传入,避免本地 checkpoint 在默认高阈值下返回 0 个 mask。 后端响应: ```json { "polygons": [ [[0.25, 0.25], [0.75, 0.25], [0.75, 0.75], [0.25, 0.75]] ], "scores": [0.5] } ``` 前端会把上面的 `polygons` 转成: ```json { "masks": [ { "pathData": "M 160 90 L 480 90 L 480 270 L 160 270 Z", "segmentation": [[160, 90, 480, 90, 480, 270, 160, 270]], "bbox": [160, 90, 320, 180] } ] } ``` ### 视频片段传播请求体 工作区“传播片段”调用: ```json { "project_id": 1, "frame_id": 123, "model": "sam2", "direction": "forward", "max_frames": 30, "include_source": false, "save_annotations": true, "seed": { "polygons": [[[0.1, 0.1], [0.3, 0.1], [0.3, 0.3]]], "bbox": [0.1, 0.1, 0.2, 0.2], "label": "胆囊", "color": "#ff0000", "class_metadata": {"id": "c1", "name": "胆囊", "color": "#ff0000", "zIndex": 20}, "template_id": 2 } } ``` `model=sam2` 使用 SAM 2 video predictor 的 mask seed 传播;`model=sam3` 使用独立 Python 3.12 helper 中的 SAM 3 video tracker,并以 seed bbox 作为初始提示。响应会返回已创建的 `annotations`,保存的 `mask_data.source` 为 `sam2_propagation` 或 `sam3_propagation`。 ## 已完成的接口对齐 - `updateProject()` 已从 `PUT` 改为 `PATCH`。 - `exportCoco()` 已从 `/api/export/coco/{projectId}` 改为 `/api/export/{projectId}/coco`。 - Canvas 已使用真实 `frame.id` 作为 `image_id`。 - 点和框坐标已转成后端需要的归一化坐标。 - 后端 `polygons` 已在前端转成 Konva 可渲染的 path。 - `saveAnnotation()` 已接入 `POST /api/ai/annotate`。 - `getProjectAnnotations()` 已接入 `GET /api/ai/annotations`。 - `updateAnnotation()` 已接入 `PATCH /api/ai/annotations/{annotationId}`。 - `deleteAnnotation()` 已接入 `DELETE /api/ai/annotations/{annotationId}`。 - `importGtMask()` 已接入 `POST /api/ai/import-gt-mask`,导入后端生成的 polygon 标注、原始 `gt_label_value` 和 seed point。 - `exportMasks()` 已接入 `GET /api/export/{projectId}/masks`。 - `parseMedia()` 已改为创建 Celery 后台任务,并返回 `ProcessingTask`。 - `getTask()` 已接入 `GET /api/tasks/{taskId}`。 - `cancelTask()` 已接入 `POST /api/tasks/{taskId}/cancel`。 - `retryTask()` 已接入 `POST /api/tasks/{taskId}/retry`。 - `getDashboardOverview()` 已从 `processing_tasks` 聚合解析队列。 - Dashboard 任务列表已展示 queued/running/failed/cancelled 任务,并可通过 `getTask()` 查看失败详情。 - 工作区导出按钮已调用 `exportCoco()` / `exportMasks()`,并会先保存未归档 mask。 - PNG mask ZIP 已包含每帧 `semantic_frame_*.png` 和 `semantic_classes.json`,重叠区域按 zIndex 裁决。 ## 仍需处理的接口问题 - WebSocket 地址已从 `VITE_WS_PROGRESS_URL` 读取,未配置时从 `API_BASE_URL` 推导;部署时仍要确认浏览器能访问该地址。 - Celery worker 进度会写 PostgreSQL 任务表,同时发布到 Redis `seg:progress`;FastAPI 订阅后广播到 `/ws/progress`。 - 已保存标注目前支持分类级更新、polygon 顶点拖动、顶点删除、边中点插入、多 polygon 子区域选择编辑后的 PATCH 更新和整帧清空删除;复杂洞结构的专业编辑仍未实现。