feat(server): 暴露 /api/sessions/{sid}/ws 进度流端点(+websockets 依赖)

This commit is contained in:
lhl
2026-08-29 11:55:45 +08:00
parent 8493bea589
commit 61611b6b8a
4 changed files with 45 additions and 1 deletions
+1
View File
@@ -150,6 +150,7 @@ python scripts/serve.py
- 分步端点(聊天底层复用):`POST .../start-parse``POST .../confirm-parse``POST .../start-impact`
`POST .../confirm-impact``POST .../generate`body `{"output_language":"auto|zh|ja"}`)、`POST .../run-qa`
- 结果:`GET .../result/preview|download|impact-report|qa-report`
- 进度流:`GET /api/sessions/{id}/ws`(WebSocket,实时推送各步骤进度事件;运行依赖 `websockets>=12`,已包含在 `pip install -e ".[dev]"` 中)
- **既有系统**以代码库目录(项目配置)或 `.zip`(会话上传)提供(追加/改修场景);不传则影响调查跳过
- 部署到公网后登记 `service_url`(格式 `http://<域名或公网IP>:<端口>`)供评审系统 B 阶段黑盒冒烟
+1
View File
@@ -19,6 +19,7 @@ dependencies = [
"fastapi>=0.115",
"uvicorn>=0.30",
"python-multipart>=0.0.9",
"websockets>=12",
]
[project.optional-dependencies]
+19 -1
View File
@@ -11,14 +11,16 @@
"""
from __future__ import annotations
import asyncio
from pathlib import Path
from typing import Any
from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi import FastAPI, File, Form, HTTPException, UploadFile, WebSocket, WebSocketDisconnect
from fastapi.responses import FileResponse, HTMLResponse
from pydantic import BaseModel
from genesis.inference.factory import build_inference_engine
from genesis.server.hub import hub
from genesis.server.service import FileTypeError, GenesisService, ServiceStepError
from genesis.server.store import (
ProjectConfigError, ProjectsStore, SessionNotFoundError, SessionStore,
@@ -302,6 +304,22 @@ def create_app(
raise _error(404, "RESULT_NOT_FOUND", "QA 报告不存在")
return FileResponse(rec.qa_report_path, media_type="application/json", filename="qa-report.json")
# ---------- 进度流(WebSocket ----------
@app.websocket("/api/sessions/{sid}/ws")
async def session_progress_ws(ws: WebSocket, sid: str):
await ws.accept()
hub.register_loop(asyncio.get_running_loop())
q = hub.subscribe(sid)
try:
while True:
event = await q.get()
await ws.send_json(event)
except WebSocketDisconnect:
pass
finally:
hub.unsubscribe(sid, q)
# ---------- 聊天 ----------
@app.post("/api/chat/{sid}/messages")
+24
View File
@@ -0,0 +1,24 @@
import threading
from fastapi.testclient import TestClient
from genesis.server.app import create_app
from genesis.server.hub import hub
from genesis.server.store import SessionStore
def _client(tmp_path):
return TestClient(create_app(
store=SessionStore(db_path=str(tmp_path / "s.db")),
data_root=str(tmp_path / "data"), engine="fake"))
def test_ws_streams_progress(tmp_path):
c = _client(tmp_path)
def trigger():
hub.emit("ws-s1", {"type": "progress", "step": "gen", "status": "ok", "detail": "生成中"})
with c.websocket_connect("/api/sessions/ws-s1/ws") as ws:
threading.Thread(target=trigger).start()
data = ws.receive_json()
assert data["type"] == "progress"
assert data["step"] == "gen"