first commit

This commit is contained in:
陈赣
2026-06-03 11:00:50 +08:00
commit 322b72ac5b
18 changed files with 1821 additions and 0 deletions

142
app/static/app.js Normal file
View File

@@ -0,0 +1,142 @@
const connection = document.querySelector("#connection");
const detectionsEl = document.querySelector("#detections");
const frameIdEl = document.querySelector("#frame-id");
const fpsEl = document.querySelector("#fps");
const errorEl = document.querySelector("#error");
const sourceEl = document.querySelector("#source");
const deviceSelect = document.querySelector("#device-select");
const timingTokenEl = document.querySelector("#timing-token");
const timingSignEl = document.querySelector("#timing-sign");
const timingUrlEl = document.querySelector("#timing-url");
const timingOpenEl = document.querySelector("#timing-open");
const timingFrameEl = document.querySelector("#timing-frame");
let selectedDevice = "";
let pendingDevice = "";
let devicesSignature = "";
function setConnection(online, text) {
connection.textContent = text;
connection.classList.toggle("online", online);
connection.classList.toggle("offline", !online);
}
function formatMs(value) {
if (value === undefined || value === null || value === 0) {
return "-";
}
return `${Number(value).toFixed(2)} ms`;
}
function renderDevices(devices, currentDeviceNum) {
if (!devices.length) {
deviceSelect.innerHTML = '<option value="">未配置摄像头</option>';
deviceSelect.disabled = true;
return;
}
const displayDevice = pendingDevice || currentDeviceNum;
const nextSignature = `${displayDevice}|${devices.map((device) => device.device_num).join(",")}`;
if (nextSignature === devicesSignature) {
return;
}
selectedDevice = displayDevice;
devicesSignature = nextSignature;
deviceSelect.innerHTML = devices
.map((device) => {
const selected = device.device_num === displayDevice ? "selected" : "";
return `<option value="${device.device_num}" ${selected}>${device.name} · ${device.device_num}</option>`;
})
.join("");
}
function renderTimings(timings) {
timingTokenEl.textContent = timings?.token_cache ? "缓存" : formatMs(timings?.token_ms);
timingSignEl.textContent = formatMs(timings?.sign_ms);
timingUrlEl.textContent = formatMs(timings?.stream_url_ms);
timingOpenEl.textContent = formatMs(timings?.open_ms);
timingFrameEl.textContent = formatMs(timings?.first_frame_ms);
}
function renderDetections(detections) {
if (!detections.length) {
detectionsEl.className = "detections empty";
detectionsEl.textContent = "暂无目标";
return;
}
detectionsEl.className = "detections";
detectionsEl.innerHTML = detections
.map((det) => {
const score = `${(det.score * 100).toFixed(1)}%`;
const box = det.box.join(", ");
return `
<div class="det-item">
<div class="det-title">
<span>${det.label}</span>
<span>${score}</span>
</div>
<div class="det-box">box: [${box}]</div>
</div>
`;
})
.join("");
}
async function switchDevice(deviceNum) {
pendingDevice = deviceNum;
devicesSignature = "";
setConnection(false, "切换中");
const response = await fetch(`/devices/${encodeURIComponent(deviceNum)}`, { method: "POST" });
if (!response.ok) {
throw new Error("切换摄像头失败");
}
const video = document.querySelector("#video");
video.src = `/video?t=${Date.now()}`;
}
function connectWebSocket() {
const protocol = window.location.protocol === "https:" ? "wss" : "ws";
const ws = new WebSocket(`${protocol}://${window.location.host}/ws/detections`);
ws.addEventListener("open", () => setConnection(true, "已连接"));
ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
frameIdEl.textContent = data.frame_id ?? "-";
fpsEl.textContent = data.fps ?? "-";
errorEl.textContent = data.error || (data.connected ? "正常" : "未连接");
sourceEl.textContent = data.source || "-";
setConnection(Boolean(data.connected), data.connected ? "已连接" : "重连中");
if (pendingDevice && data.current_device_num === pendingDevice) {
pendingDevice = "";
deviceSelect.disabled = false;
}
renderDevices(data.devices || [], data.current_device_num || "");
renderTimings(data.timings || {});
renderDetections(data.detections || []);
});
ws.addEventListener("close", () => {
setConnection(false, "已断开");
setTimeout(connectWebSocket, 1500);
});
ws.addEventListener("error", () => {
setConnection(false, "连接错误");
ws.close();
});
}
deviceSelect.addEventListener("change", (event) => {
selectedDevice = event.target.value;
switchDevice(event.target.value).catch(() => {
pendingDevice = "";
devicesSignature = "";
setConnection(false, "切换失败");
deviceSelect.disabled = false;
});
});
connectWebSocket();