将 Demo 录制收敛为三条独立链路,并重做联调台 UI。

移除 demo_orch 统一编排,改为 recording_demo 与 live/simulated 服务;客户端拆分为静态资源,以模式卡片与 chip 耗材覆盖三链路联调,并同步测试与文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin
2026-05-21 16:50:23 +08:00
parent 09885b4184
commit 153c91f8ff
16 changed files with 2030 additions and 1364 deletions

View File

@@ -33,11 +33,9 @@ from app.schemas import (
VoiceTerminalAssignmentResponse,
build_consumption_summary,
)
from app.services.recording_live import accept_live_recording
from app.services.surgery_pipeline import SurgeryPipeline
from app.services.voice_terminal_hub import (
VoiceTerminalHub,
assign_voice_terminal_after_recording_started,
)
from app.services.voice_terminal_hub import VoiceTerminalHub
from app.surgery_errors import SurgeryPipelineError
router = APIRouter()
@@ -135,19 +133,21 @@ async def health() -> HealthResponse | JSONResponse:
@router.get(
"/internal/demo/orchestrator-status",
"/internal/demo/recording-modes-status",
tags=["demo"],
summary="一键联调接口是否可用",
description="供 demo 页探测:是否启用 orchestrator、RTSP 文件配置等;此路由始终存在,不依赖 DEMO_ORCHESTRATOR_ENABLED。",
summary="Demo 录制模式(链路 2/3是否可用",
description="供 demo 页探测;始终注册,不依赖 DEMO_ORCHESTRATOR_ENABLED。",
)
async def demo_orchestrator_status() -> dict:
async def recording_modes_status() -> dict:
f = (settings.or_site_config_json_file or "").strip()
enabled = bool(settings.demo_orchestrator_enabled)
return {
"orchestrator_enabled": bool(settings.demo_orchestrator_enabled),
"orchestrate_method": "POST",
"orchestrate_path": "/internal/demo/orchestrate-and-start",
"video_batch_method": "POST",
"video_batch_path": "/internal/demo/video-batch-surgery",
"demo_recording_modes_enabled": enabled,
"orchestrator_enabled": enabled,
"simulated_start_method": "POST",
"simulated_start_path": "/internal/demo/simulated-start",
"offline_batch_method": "POST",
"offline_batch_path": "/internal/demo/offline-batch",
"or_site_config_json_file_set": bool(f),
"or_site_config_json_file": f or None,
"orchestrator_rtsp_port": settings.demo_orchestrator_rtsp_port,
@@ -181,15 +181,20 @@ async def start_surgery(
payload.camera_ids,
payload.candidate_consumables,
)
accepted: SurgeryApiResponse | None = None
async def _start() -> None:
nonlocal accepted
accepted = await accept_live_recording(
pipeline,
voice_hub,
surgery_id=payload.surgery_id,
camera_ids=list(payload.camera_ids),
candidate_consumables=list(payload.candidate_consumables),
message="摄像头录制已开始,手术已启动。",
)
try:
async def _start() -> None:
await pipeline.start_recording(
payload.surgery_id,
payload.camera_ids,
payload.candidate_consumables,
)
await _call_recording_with_retries(
_start,
max_attempts=bp.SURGERY_RECORDING_MAX_ATTEMPTS,
@@ -199,18 +204,8 @@ async def start_surgery(
except SurgeryPipelineError as exc:
_raise_surgery_pipeline_http(exc, payload.surgery_id)
await assign_voice_terminal_after_recording_started(
voice_hub,
surgery_id=payload.surgery_id,
camera_ids=list(payload.camera_ids),
set_voice_terminal_id=pipeline.set_voice_terminal_id,
)
return SurgeryApiResponse(
surgery_id=payload.surgery_id,
status="accepted",
message="摄像头录制已开始,手术已启动。",
)
assert accepted is not None
return accepted
@router.post(