Files
operating-room-monitor-server/clients/demo-client/index.html
Kevin 153c91f8ff 将 Demo 录制收敛为三条独立链路,并重做联调台 UI。
移除 demo_orch 统一编排,改为 recording_demo 与 live/simulated 服务;客户端拆分为静态资源,以模式卡片与 chip 耗材覆盖三链路联调,并同步测试与文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 16:50:23 +08:00

217 lines
10 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">三条链路:真摄像头实时、模拟实时、离线精确处理</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">
<button type="button" class="mode-card active" data-mode="live-rtsp">
<div class="title">链路 1 · 真摄像头</div>
<div class="desc">真实 RTSP · 实时算法 · 语音播报 · 需结束手术</div>
</button>
<button type="button" class="mode-card" data-mode="live-simulated">
<div class="title">链路 2 · 模拟实时</div>
<div class="desc">上传视频假流 · 实时算法 · 语音播报 · 需结束手术</div>
</button>
<button type="button" class="mode-card" data-mode="offline-batch">
<div class="title">链路 3 · 离线精确</div>
<div class="desc">整段 MP4 批处理 · 无语音 · 无需结束 · 可选标注视频</div>
</button>
</div>
</section>
<section class="card">
<h2>耗材候选</h2>
<div class="chip-toolbar">
<input id="chip-search" class="chip-search" type="text" placeholder="搜索标签…" />
<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"></textarea>
</div>
</details>
</section>
<section class="card mode-panel active" data-mode="live-rtsp">
<h2>真摄像头配置</h2>
<label for="camera-ids">摄像头 ID逗号分隔</label>
<input id="camera-ids" type="text" value="or-cam-03" placeholder="or-cam-03, or-cam-02" />
<p class="labels-meta" style="margin-top:8px">使用站点 JSON 中的真实 RTSP 地址。</p>
</section>
<section class="card mode-panel" data-mode="live-simulated">
<h2>模拟实时 · 上传视频</h2>
<label for="debug-stream-count">模拟路数</label>
<select id="debug-stream-count" style="max-width:8rem;margin-bottom:12px">
<option value="1" selected>1 路</option>
<option value="2">2 路</option>
<option value="3">3 路</option>
<option value="4">4 路</option>
</select>
<p class="labels-meta">每路须选择视频文件,将合成假 RTSP 后开录。</p>
<div class="stream-grid">
<div class="stream-slot" id="sim-stream-1">
<h4>路 1</h4>
<div class="upload-zone" id="sim-zone-1">
<div class="icon"></div>
<div id="sim-fname-1">点击或拖放视频</div>
<input type="file" id="sim-vfile-1" hidden accept="video/*" />
</div>
<div class="stream-actions">
<button type="button" class="secondary" id="sim-pick-1">选择文件</button>
<button type="button" class="secondary" id="sim-webcam-1">摄像头</button>
</div>
<div class="stream-hint" id="sim-hint-1"></div>
</div>
<div class="stream-slot hidden" id="sim-stream-2">
<h4>路 2</h4>
<div class="upload-zone" id="sim-zone-2">
<div class="icon"></div>
<div id="sim-fname-2">点击或拖放视频</div>
<input type="file" id="sim-vfile-2" hidden accept="video/*" />
</div>
<div class="stream-actions">
<button type="button" class="secondary" id="sim-pick-2">选择文件</button>
<button type="button" class="secondary" id="sim-webcam-2">摄像头</button>
</div>
<div class="stream-hint" id="sim-hint-2"></div>
</div>
<div class="stream-slot hidden" id="sim-stream-3">
<h4>路 3</h4>
<div class="upload-zone" id="sim-zone-3">
<div class="icon"></div>
<div id="sim-fname-3">点击或拖放视频</div>
<input type="file" id="sim-vfile-3" hidden accept="video/*" />
</div>
<div class="stream-actions">
<button type="button" class="secondary" id="sim-pick-3">选择文件</button>
<button type="button" class="secondary" id="sim-webcam-3">摄像头</button>
</div>
<div class="stream-hint" id="sim-hint-3"></div>
</div>
<div class="stream-slot hidden" id="sim-stream-4">
<h4>路 4</h4>
<div class="upload-zone" id="sim-zone-4">
<div class="icon"></div>
<div id="sim-fname-4">点击或拖放视频</div>
<input type="file" id="sim-vfile-4" hidden accept="video/*" />
</div>
<div class="stream-actions">
<button type="button" class="secondary" id="sim-pick-4">选择文件</button>
<button type="button" class="secondary" id="sim-webcam-4">摄像头</button>
</div>
<div class="stream-hint" id="sim-hint-4"></div>
</div>
</div>
<details class="advanced">
<summary>高级:每路 RTSP 路径与 camera_id</summary>
<div class="advanced-body">
<div class="stream-grid">
<div><label>路 1 RTSP_PATH</label><input id="adv-rpath-1" type="text" value="demo1" /></div>
<div><label>路 1 camera_id</label><input id="adv-cam-1" type="text" value="or-cam-03" /></div>
<div><label>路 2 RTSP_PATH</label><input id="adv-rpath-2" type="text" value="demo2" /></div>
<div><label>路 2 camera_id</label><input id="adv-cam-2" type="text" value="or-cam-02" /></div>
<div><label>路 3 RTSP_PATH</label><input id="adv-rpath-3" type="text" value="demo3" /></div>
<div><label>路 3 camera_id</label><input id="adv-cam-3" type="text" value="or-cam-04" /></div>
<div><label>路 4 RTSP_PATH</label><input id="adv-rpath-4" type="text" value="demo4" /></div>
<div><label>路 4 camera_id</label><input id="adv-cam-4" type="text" value="or-cam-01" /></div>
</div>
</div>
</details>
</section>
<section class="card mode-panel" data-mode="offline-batch">
<h2>离线精确 · 上传 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>生成标注视频24 小时内可预览)</span>
</label>
</section>
<section class="card">
<h2>操作</h2>
<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>
<div id="video-batch-vis" class="vis-block hidden">
<p id="video-batch-doctor-info" class="labels-meta"></p>
<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="app.js"></script>
</body>
</html>