Files
operating-room-monitor-server/clients/demo-client/index.html
op b8bf31c23c Improve demo client consumable UX and clarify live vs offline flows.
Separate mode-specific steps, show product code with name in chips, validate against mistaken video paths, and load label_id from labels.yaml via server.py.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 10:44:41 +08:00

164 lines
7.9 KiB
HTML
Executable File
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.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>手术监控 · 联调台</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="app">
<header class="header">
<h1>手术监控 · 联调台</h1>
<p class="subtitle">联调专用页面 · 正式客户端只需调用 <code>/client/surgeries/start</code>,无需上传视频</p>
<div class="top-grid">
<div>
<label for="base-url">API 地址</label>
<input id="base-url" type="url" value="http://127.0.0.1:38080" />
</div>
<div>
<label for="surgery-id">手术号6 位)</label>
<input id="surgery-id" type="text" inputmode="numeric" maxlength="6" value="123456" />
</div>
<div>
<button type="button" class="secondary" id="btn-refresh-status">刷新状态</button>
</div>
</div>
<div class="status-bar">
<span class="pill" id="pill-health">检测中…</span>
<span class="pill" id="pill-demo-modes"></span>
<span class="pill" id="pill-mode">链路 1 · 真摄像头</span>
</div>
<div id="banner" class="banner"></div>
<div id="voice-callout" class="voice-callout hidden">
开录成功后,请启动
<a href="../voice-confirmation/">语音确认终端</a>(默认 :8080终端 ID 须与站点配置
<code>voice_or_room_bindings</code> 匹配。
</div>
</header>
<div class="layout-main">
<div class="main-col">
<section class="card">
<h2>选择运行模式</h2>
<div class="mode-cards mode-cards--two">
<button type="button" class="mode-card active" data-mode="live-rtsp">
<div class="title">链路 1 · 真摄像头</div>
<div class="desc">填 camera_id 开录 · 服务端自动拉 RTSP · 无需上传视频</div>
</button>
<button type="button" class="mode-card" data-mode="offline-batch">
<div class="title">链路 3 · 离线精确</div>
<div class="desc">Demo 回放 · 须上传 MP4 · 无语音 · 不走实时会话</div>
</button>
</div>
</section>
<section class="card mode-panel active" data-mode="live-rtsp">
<div class="mode-callout mode-callout--live">
<strong>正式对接链路。</strong>客户端只传 <code>camera_ids</code> 与耗材候选;视频由服务端从 RTSP 拉流并切片,<em>不要</em>在此页上传 MP4。
</div>
<h2>步骤 1 · 摄像头 ID</h2>
<label for="camera-ids">摄像头 ID逗号分隔</label>
<input id="camera-ids" type="text" value="or-cam-01, or-cam-02, or-cam-03, or-cam-04" placeholder="or-cam-01, or-cam-02" />
<p id="live-rtsp-segment-hint" class="labels-meta" style="margin-top:8px">
默认仅录制 3 号机位or-cam-03并每 2 分钟切片跑 batch服务端落盘 slice 仅用于推理24 小时后自动删除(不影响已入库明细)。下方可预览每路画面。
</p>
</section>
<section class="card mode-panel" data-mode="offline-batch">
<div class="mode-callout mode-callout--offline">
<strong>Demo / 回归专用。</strong>用历史录像离线跑算法;与链路 1 无关,<em>只有此模式</em>需要上传 MP4。
</div>
<h2>步骤 1 · 上传 MP4</h2>
<p class="labels-meta">不启动实时会话,处理完成后直接查结果。</p>
<div class="upload-zone" id="offline-zone" style="margin-top:12px">
<div class="icon">MP4</div>
<div id="offline-fname">点击或拖放 MP4 到此处</div>
<input type="file" id="offline-vfile" hidden accept="video/mp4,video/*" />
</div>
<p class="stream-hint" id="offline-hint"></p>
<button type="button" class="secondary" id="offline-pick" style="margin-top:10px">选择文件</button>
<label class="checkbox-row">
<input type="checkbox" id="offline-batch-include-vis" />
<span id="offline-batch-vis-label">生成标注视频24 小时内可预览)</span>
</label>
</section>
<section class="card card--rtsp-preview mode-panel" data-mode="live-rtsp">
<div class="preview-section-head">
<div>
<h2>术间画面预览</h2>
<p class="labels-meta preview-section-desc">
真 RTSP 经 HLS 低延迟播放;四路默认 2×2 排布,便于监看。
</p>
</div>
<span id="preview-status" class="preview-status-pill">未启动</span>
</div>
<div class="preview-toolbar">
<button type="button" class="secondary" id="btn-preview-refresh">启动 / 刷新</button>
<button type="button" class="secondary" id="btn-preview-stop">停止预览</button>
</div>
<div id="rtsp-preview-grid" class="preview-grid preview-grid--rtsp"></div>
</section>
<section class="card mode-panel active" data-mode="live-rtsp offline-batch">
<h2>步骤 2 · 耗材候选AI 白名单)</h2>
<p id="consumables-mode-hint" class="field-hint">
选择本次手术可能用到的耗材名称或产品编码;留空表示使用全部标签。<strong>此处不是视频上传区,请勿填写 .mp4 路径。</strong>
</p>
<div id="consumables-warn" class="consumables-warn hidden" role="alert"></div>
<div class="chip-toolbar">
<input id="chip-search" class="chip-search" type="text" placeholder="搜索编号或名称…" autocomplete="off" />
<button type="button" class="secondary" id="btn-chips-all">全选标签</button>
<button type="button" class="secondary" id="btn-chips-clear">清空选择</button>
</div>
<div id="chips" class="chips"></div>
<p id="labels-meta" class="labels-meta">加载中…</p>
<details class="advanced">
<summary>高级JSON 编辑(耗材名称/编码,与上方标签同步)</summary>
<div class="advanced-body">
<textarea id="candidate-consumables-json" rows="5" spellcheck="false" placeholder='[{"消耗品编号": "14764-2-4", "名称": "一次性使用手术单"}]'></textarea>
</div>
</details>
</section>
<section class="card">
<h2>操作</h2>
<p id="action-mode-hint" class="field-hint"></p>
<div class="steps">
<button type="button" class="primary lg" id="btn-start">开始手术</button>
<button type="button" class="warn" id="btn-end">结束手术</button>
<button type="button" class="secondary" id="btn-result">查询结果</button>
</div>
</section>
<section class="card">
<h2>结果</h2>
<p id="offline-batch-timing" class="labels-meta timing-meta hidden"></p>
<p id="video-batch-doctor-info" class="labels-meta"></p>
<div id="video-batch-vis" class="vis-block hidden">
<video id="video-batch-vis-player" controls playsinline></video>
<p id="video-batch-vis-hint" class="stream-hint"></p>
</div>
<div id="result-render" class="result-area"></div>
</section>
</div>
<aside class="dev-log">
<details>
<summary>开发者日志</summary>
<div style="padding:8px 10px 0">
<button type="button" class="secondary" id="btn-clear-log" style="width:100%;margin-bottom:8px">清空日志</button>
</div>
<div id="log-scroll" class="log-scroll"></div>
</details>
</aside>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.17/dist/hls.min.js"></script>
<script src="app.js"></script>
</body>
</html>