This commit is contained in:
Kevin
2026-04-28 10:41:48 +08:00
parent 482b016872
commit 15884bd68e
60 changed files with 2092 additions and 1994 deletions

View File

@@ -0,0 +1,259 @@
<!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>