feat: 站点 JSON、语音终端 WebSocket 指派与客户端联调
- 用 OR_SITE_CONFIG_JSON_FILE 统一术间配置(video_rtsp_urls + voice_or_room_bindings) - VoiceTerminalHub:assignment、WS 推送与 HTTP 查询;开录/停录后 notify - 一键联调 orchestrate-and-start 与 /client/surgeries/start 共用指派逻辑,修复 demo 路径不发 WS - 语音桌面端:SIGINT 退出、shutdown 清理、仅 WS 指派、固定 pending 轮询间隔、界面仅保留录音时长 - 新增/调整契约与绑定测试,文档与示例配置同步 Made-with: Cursor
This commit is contained in:
@@ -231,6 +231,12 @@
|
||||
<input id="surgery-id" type="text" inputmode="numeric" pattern="\d{6}" maxlength="6" value="123456" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="small muted" style="margin-top:10px">
|
||||
<label style="display:inline-flex;align-items:flex-start;gap:8px;cursor:pointer;max-width:52rem">
|
||||
<input type="checkbox" id="toggle-browser-voice-ui" style="margin-top:2px" />
|
||||
<span>显示<strong> §4.4 / §4.5</strong>(浏览器待确认与录音上传;默认关闭,主流程请用桌面语音客户端)</span>
|
||||
</label>
|
||||
</p>
|
||||
<div class="actions">
|
||||
<button id="btn-health" class="secondary">GET /health</button>
|
||||
<button type="button" class="secondary" id="btn-orch-status" title="检查一键联调接口是否已注册">GET 联调状态</button>
|
||||
@@ -241,7 +247,7 @@
|
||||
<section class="card">
|
||||
<h2>调试:多路视频 1–4 路(与一键联调 / 无真摄像头)</h2>
|
||||
<p class="callout-ok small">
|
||||
在下方选好各路视频、第 4.1 节勾选「一键联调」后点「开始手术」即可;服务端会起假 RTSP 并写 <code>VIDEO_RTSP_URLS_JSON_FILE</code>。无法使用一键时,请按 <code>scripts/demo_client/README.md</code> 在宿主机手跑
|
||||
在下方选好各路视频、第 4.1 节勾选「一键联调」后点「开始手术」即可;服务端会起假 RTSP 并合并写入 <code>OR_SITE_CONFIG_JSON_FILE</code> 的 <code>video_rtsp_urls</code>。无法使用一键时,请按 <code>scripts/demo_client/README.md</code> 在宿主机手跑
|
||||
<code>fake_rtsp_from_file.py</code> 并配置环境变量。
|
||||
</p>
|
||||
<div class="row" style="margin-top:8px; max-width:28rem">
|
||||
@@ -363,7 +369,7 @@
|
||||
<p class="small muted" style="margin:8px 0 0">
|
||||
<label style="display:inline-flex;align-items:flex-start;gap:8px;cursor:pointer;max-width:52rem">
|
||||
<input type="checkbox" id="orch-oneclick" style="margin-top:2px" />
|
||||
<span><strong>一键联调</strong>:点下面按钮时按「模拟路数」上传调试区为<strong>路 1…N</strong>选好的视频(1–4 路),由监控服务在<strong>能执行 docker+ffmpeg 的环境</strong>里自动起假 RTSP、写 <code>VIDEO_RTSP_URLS_JSON_FILE</code> 并开录(需 <code>DEMO_ORCHESTRATOR_ENABLED=true</code> 且该文件为可写挂载;详见 README)。不勾选时仍为普通 JSON 开录(需自行先起假流)。</span>
|
||||
<span><strong>一键联调</strong>:点下面按钮时按「模拟路数」上传调试区为<strong>路 1…N</strong>选好的视频(1–4 路),由监控服务在<strong>能执行 docker+ffmpeg 的环境</strong>里自动起假 RTSP、更新 <code>OR_SITE_CONFIG_JSON_FILE</code> 并开录(需 <code>DEMO_ORCHESTRATOR_ENABLED=true</code> 且该文件为可写挂载;详见 README)。不勾选时仍为普通开录(需自行先起假流并保证站点 JSON 中 RTSP 映射正确)。</span>
|
||||
</label>
|
||||
</p>
|
||||
<div class="actions">
|
||||
@@ -388,6 +394,7 @@
|
||||
<div id="result-render"></div>
|
||||
</section>
|
||||
|
||||
<div id="browser-voice-sections" hidden>
|
||||
<section class="card">
|
||||
<h2>§4.4 待确认耗材</h2>
|
||||
<div class="actions">
|
||||
@@ -427,6 +434,7 @@
|
||||
<a id="btn-download" class="small muted" href="#" download="voice.wav" style="display:none">下载 WAV(调试)</a>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<aside>
|
||||
@@ -483,6 +491,17 @@
|
||||
[...logEl.querySelectorAll(".log-item")].forEach(n => n.remove());
|
||||
};
|
||||
|
||||
const browserVoiceSections = $("browser-voice-sections");
|
||||
const toggleBrowserVoiceUi = $("toggle-browser-voice-ui");
|
||||
function syncBrowserVoiceUiVisibility() {
|
||||
if (!browserVoiceSections || !toggleBrowserVoiceUi) return;
|
||||
browserVoiceSections.hidden = !toggleBrowserVoiceUi.checked;
|
||||
}
|
||||
if (toggleBrowserVoiceUi) {
|
||||
toggleBrowserVoiceUi.addEventListener("change", syncBrowserVoiceUiVisibility);
|
||||
syncBrowserVoiceUiVisibility();
|
||||
}
|
||||
|
||||
async function apiJson(method, path, payload) {
|
||||
const url = baseUrl() + path;
|
||||
let res;
|
||||
@@ -523,8 +542,8 @@
|
||||
let hint = "";
|
||||
if (res.status === 404) {
|
||||
hint = "HTTP 404:本路径在服务端未注册。常见原因:1) 未设 DEMO_ORCHESTRATOR_ENABLED=true 并重启主进程,POST /internal/demo/orchestrate-and-start 未挂载;2)「服务端 Base URL」填错(须指向主 API 如 http://127.0.0.1:38080,不是本 demo 静态站 :38081)。可点「GET 联调状态」或打开浏览器控制台查看 [demo-client] 日志。";
|
||||
} else if (res.status === 400 && parsed && (parsed.detail || "").toString().indexOf("VIDEO_RTSP") >= 0) {
|
||||
hint = "需配置可写的 VIDEO_RTSP_URLS_JSON_FILE,且 Docker 下请 bind-mount 到容器内同路径。";
|
||||
} else if (res.status === 400 && parsed && (parsed.detail || "").toString().indexOf("OR_SITE_CONFIG") >= 0) {
|
||||
hint = "需配置可写的 OR_SITE_CONFIG_JSON_FILE(严格站点 JSON),且 Docker 下请 bind-mount 到容器内同路径。";
|
||||
} else if (res.status === 503) {
|
||||
hint = "合成假 RTSP 或开录失败,请见响应体与主服务终端 log(demo orchestrate-and-start / ffmpeg / docker)。";
|
||||
}
|
||||
@@ -640,12 +659,12 @@
|
||||
return;
|
||||
}
|
||||
const on = data.orchestrator_enabled === true;
|
||||
const fset = data.video_rtsp_urls_json_file_set === true;
|
||||
const fset = data.or_site_config_json_file_set === true;
|
||||
b.style.background = on && fset ? "rgba(34, 197, 94, 0.1)" : "rgba(245, 158, 11, 0.12)";
|
||||
b.style.color = "var(--text)";
|
||||
const fp = data.video_rtsp_urls_json_file || "(未设)";
|
||||
const fp = data.or_site_config_json_file || "(未设)";
|
||||
b.innerHTML = on
|
||||
? ("一键 <code>POST " + (data.orchestrate_path || "/internal/demo/orchestrate-and-start") + "</code>:" + (fset ? "已开放;RTSP 映射文件 " : "未设 ") + "<code>" + fp + "</code>")
|
||||
? ("一键 <code>POST " + (data.orchestrate_path || "/internal/demo/orchestrate-and-start") + "</code>:" + (fset ? "已开放;站点配置 " : "未设 ") + "<code>" + fp + "</code>")
|
||||
: ("一键开录 <strong>未注册</strong>:请在主服务 .env 设 <code>DEMO_ORCHESTRATOR_ENABLED=true</code> 并<strong>重启</strong>。当前 " + (data.orchestrate_path || "") + " 会 404。");
|
||||
} catch (e) {
|
||||
console.error("[demo-client] orchestrator-status failed", e);
|
||||
|
||||
Reference in New Issue
Block a user