Files
operating-room-monitor-server/web/voice-confirmation/index.html
2026-04-28 10:41:48 +08:00

260 lines
10 KiB
HTML
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>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
panel: { DEFAULT: "#111827", muted: "#1f2937" },
},
},
},
};
</script>
<style>
@keyframes recBarWave {
0%,
100% {
transform: scaleY(0.22);
}
50% {
transform: scaleY(1);
}
}
@keyframes recPanelGlow {
0%,
100% {
box-shadow: 0 0 0 0 rgba(248, 113, 113, 0.2);
}
50% {
box-shadow: 0 0 20px 3px rgba(248, 113, 113, 0.35);
}
}
@keyframes recDotPulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.35;
}
}
.rec-banner-active {
animation: recPanelGlow 1.4s ease-in-out infinite;
}
.rec-banner-active .rec-wave-bar {
display: inline-block;
width: 0.45rem;
min-height: 2.5rem;
height: 2.5rem;
transform-origin: bottom center;
will-change: transform;
border-radius: 0.2rem;
animation: recBarWave 0.52s ease-in-out infinite;
}
.rec-banner-active .rec-wave-bar:nth-child(1) {
animation-delay: 0ms;
}
.rec-banner-active .rec-wave-bar:nth-child(2) {
animation-delay: 0.1s;
}
.rec-banner-active .rec-wave-bar:nth-child(3) {
animation-delay: 0.2s;
}
.rec-banner-active .rec-wave-bar:nth-child(4) {
animation-delay: 0.05s;
}
.rec-banner-active .rec-wave-bar:nth-child(5) {
animation-delay: 0.28s;
}
.rec-banner-active .rec-wave-bar:nth-child(6) {
animation-delay: 0.15s;
}
.rec-banner-active .rec-wave-bar:nth-child(7) {
animation-delay: 0.08s;
}
.rec-dot {
animation: recDotPulse 0.9s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.rec-banner-active {
animation: none;
}
.rec-banner-active .rec-wave-bar {
animation: none;
transform: scaleY(0.65);
}
.rec-dot {
animation: none;
opacity: 1;
}
}
</style>
</head>
<body class="min-h-screen bg-slate-950 text-slate-100 antialiased">
<div class="mx-auto max-w-5xl p-4 md:p-6">
<header class="mb-6 border-b border-slate-800 pb-4">
<h1 id="pageTitle" class="text-xl font-semibold tracking-tight text-sky-300">
语音确认
</h1>
<p class="mt-1 text-sm text-slate-400">
独立网页客户端:仅 WebSocket 收队首、HTTP 上传答复。请通过 http(s) 服务打开本页(勿用
<code class="text-slate-500">file://</code>)。生产环境请为静态页与 API 配置 HTTPS / WSS。
</p>
</header>
<div class="grid gap-4 lg:grid-cols-2">
<section class="rounded-xl border border-slate-800 bg-slate-900/80 p-4 shadow-lg">
<h2 class="mb-3 text-sm font-medium uppercase tracking-wide text-sky-400">连接</h2>
<div class="space-y-3">
<div>
<label class="mb-1 block text-xs text-slate-500" for="baseUrl">服务端 Base URL</label>
<input
id="baseUrl"
type="url"
class="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-slate-100 placeholder-slate-600 focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
placeholder="http://127.0.0.1:38080"
autocomplete="off"
/>
</div>
<div>
<label class="mb-1 block text-xs text-slate-500" for="terminalId">本机语音终端 ID</label>
<input
id="terminalId"
type="text"
class="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm"
placeholder="与 OR_SITE_CONFIG 中 voice_terminal_id 一致"
autocomplete="off"
/>
</div>
<label class="flex cursor-pointer items-center gap-2 text-sm">
<input id="autoAssign" type="checkbox" class="rounded border-slate-600 bg-slate-950 text-sky-500" />
启用服务端自动指派(开录后 WebSocket 连接并接收 <code class="text-xs">voice_assignment</code>
</label>
<div class="flex flex-wrap items-end gap-3">
<div>
<label class="mb-1 block text-xs text-slate-500" for="recordSec"
>TTS 起同时开录;本值=播完后再多采的秒数</label
>
<input
id="recordSec"
type="number"
min="2"
max="60"
step="0.5"
value="5"
class="w-32 rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm"
/>
</div>
<label class="flex cursor-pointer items-center gap-2 text-sm text-slate-400">
<input id="dryRun" type="checkbox" class="rounded border-slate-600 bg-slate-950" />
Dry-run录音后不上传
</label>
</div>
</div>
</section>
<section class="rounded-xl border border-slate-800 bg-slate-900/80 p-4 shadow-lg">
<h2 class="mb-3 text-sm font-medium uppercase tracking-wide text-sky-400">操作</h2>
<p id="status" class="mb-3 rounded-lg bg-slate-950 px-3 py-2 font-mono text-sm text-amber-200/90">
待机
</p>
<div
id="recBanner"
class="rec-banner mb-3 hidden overflow-hidden rounded-lg border-2 border-red-500/80 bg-gradient-to-b from-red-950/95 to-red-900/80 px-3 py-4 text-center shadow-lg"
role="status"
aria-live="polite"
aria-label="正在录音"
>
<div
id="recWave"
class="mb-3 flex h-14 items-end justify-center gap-1.5 sm:gap-2.5"
aria-hidden="true"
>
<span class="rec-wave-bar bg-rose-300/95 shadow-sm"></span>
<span class="rec-wave-bar bg-rose-200 shadow-sm"></span>
<span class="rec-wave-bar bg-rose-100 shadow-sm"></span>
<span class="rec-wave-bar bg-rose-50/95 shadow-sm"></span>
<span class="rec-wave-bar bg-rose-100 shadow-sm"></span>
<span class="rec-wave-bar bg-rose-200 shadow-sm"></span>
<span class="rec-wave-bar bg-rose-300/95 shadow-sm"></span>
</div>
<p class="text-sm font-bold text-red-50">
<span class="rec-dot mr-0.5 inline-block text-rose-200" aria-hidden="true"></span>
正在录音 — 请对着麦克风清晰作答
</p>
</div>
<div class="flex flex-wrap gap-2">
<button
type="button"
id="btnStop"
disabled
class="rounded-lg border border-slate-600 bg-slate-800 px-3 py-2 text-sm font-medium text-slate-200 hover:bg-slate-700 disabled:cursor-not-allowed disabled:opacity-50"
>
停止监控(本机)
</button>
<button
type="button"
id="btnRetry"
class="rounded-lg bg-amber-600 px-3 py-2 text-sm font-medium text-slate-950 hover:bg-amber-500"
>
重试本轮
</button>
<button
type="button"
id="btnReplay"
class="rounded-lg bg-slate-700 px-3 py-2 text-sm text-slate-100 hover:bg-slate-600"
>
仅重播话术
</button>
</div>
</section>
</div>
<div class="mt-4 grid gap-3 lg:grid-cols-2">
<div class="rounded-xl border border-slate-800 bg-slate-900/60 px-4 py-3">
<h3 class="text-xs font-medium text-slate-500">排队序号(当前 FIFO</h3>
<p id="queuePositionHint" class="mt-1 text-sm font-medium text-emerald-300/95"></p>
<h3 class="mt-3 text-xs font-medium text-slate-500">累积序号(本场入队)</h3>
<p id="cumulativeHint" class="mt-1 text-sm font-medium text-cyan-300/95"></p>
<p class="mt-2 text-xs text-slate-500">
排队:<code class="text-slate-600">pending_queue_position</code> /
<code class="text-slate-600">pending_queue_length</code>;累积:<code class="text-slate-600">pending_cumulative_ordinal</code>(均由服务端在 <code class="text-slate-600">voice_pending</code> 与 GET 中下发)。
</p>
</div>
<div class="rounded-xl border border-slate-800 bg-slate-900/60 px-4 py-3">
<h3 class="text-xs font-medium text-slate-500">服务端语音确认结果(最近一次 HTTP 响应)</h3>
<pre
id="resolveResult"
class="mt-2 max-h-36 overflow-auto whitespace-pre-wrap break-words font-mono text-xs text-slate-300"
></pre>
</div>
</div>
<div class="mt-4 grid gap-4 lg:grid-cols-2">
<section class="min-h-[220px] rounded-xl border border-slate-800 bg-slate-900/50 p-3">
<h3 class="mb-2 text-xs font-medium text-slate-500">队首待确认JSON</h3>
<pre
id="pendingJson"
class="max-h-80 overflow-auto whitespace-pre-wrap break-words font-mono text-xs text-slate-300"
></pre>
</section>
<section class="min-h-[220px] rounded-xl border border-slate-800 bg-slate-900/50 p-3">
<h3 class="mb-2 text-xs font-medium text-slate-500">日志</h3>
<pre
id="log"
class="max-h-80 overflow-auto whitespace-pre-wrap font-mono text-xs text-slate-400"
></pre>
</section>
</div>
</div>
<script src="voice_app.js" defer></script>
</body>
</html>