- Load consumable catalog XLSX with openpyxl and drop the pandas dependency. - Add optional demo CORS settings and FastAPI CORSMiddleware for browser clients. - Add scripts/demo_client static page and local server for API smoke tests. Made-with: Cursor
Demo Client
一个浏览器里的单页 demo,用于手动触发 app/api.py 里的 5 个 /client/* 接口,覆盖开始/结束手术、查询结果、拉取待确认耗材,以及本地麦克风录 WAV 并上传语音确认接口。
结构
scripts/demo_client/
server.py # 基于 stdlib 的静态服务器;额外暴露 /labels.json
index.html # 单文件页面(原生 JS,零构建依赖)
运行方式
# 1) 启动后端(默认 38080)。CORS 中间件在 settings.demo_cors_enabled=True 时自动挂载。
uv run python main.py
# 2) 启动 demo 客户端静态服务(默认 127.0.0.1:38081)。
python scripts/demo_client/server.py
# 或指定端口:
python scripts/demo_client/server.py -p 9000 --host 0.0.0.0
# 3) 浏览器访问:
open http://localhost:38081/
页面顶部的「服务端 Base URL」默认是 http://localhost:38080;如果后端部署在其他主机/端口,直接改这里即可。
页面包含什么
GET /health连通性检查- §4.1
POST /client/surgeries/start— 含surgery_id校验、camera_ids多值输入、candidate_consumables标签编辑器(初始值从/labels.json载入,可增删) - §4.2
POST /client/surgeries/end - §4.3
GET /client/surgeries/{id}/result— 以表格渲染details与summary - §4.4
GET /client/surgeries/{id}/pending-confirmation— 支持手动拉取与 2s 自动轮询 - §4.5
POST .../resolve— 本地麦克风录音 → 16 kHz 单声道 WAV →multipart/form-data上传
右侧「响应日志」按时间倒序展示每次请求的 method/url/status/body,便于联调截图。
关于 /labels.json
server.py 在进程启动时读 app/resources/consumable_classifier_labels.yaml 的 names 映射并返回 {"labels": [...]}。优先用 PyYAML(主项目依赖已间接引入),缺失时回退到手写的最小 YAML 解析器(仅兼容该文件的已知形状)。
关于麦克风
浏览器的 getUserMedia 仅在安全上下文可用,对 demo 实际意味着:
- 可以用:
http://localhost、http://127.0.0.1、https://... - 不能用:直接
file://双击打开index.html,或通过非本机http://访问 —— 所以这里用了独立的静态 HTTP 服务器而不是file://。
页面采用:
navigator.mediaDevices.getUserMedia拿到单声道音轨AudioContext+ScriptProcessorNode捕获 Float32 原始 PCM(输入采样率取决于系统,如 48 kHz)- 前端把样本线性降采样到 16 kHz、转 Int16 并拼 RIFF/WAVE 头
- 产出
Blob("audio/wav")后可以「下载 wav(调试)」或直接「上传并确认」
关闭 CORS(生产环境)
app/config.py 新增:
DEMO_CORS_ENABLED(默认True,生产请在.env里置false)DEMO_CORS_ORIGINS(默认*,可写http://my-host:38081,https://or-demo.example.com)