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>
164 lines
7.9 KiB
HTML
Executable File
164 lines
7.9 KiB
HTML
Executable File
<!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>
|