Zhen Ye
feat: Continuous object tracking, speed estimation, and overlay syncing
ff50694
// Intel Summary Module - Mission intel generation and display
APP.ui.intel = {};
APP.ui.intel.setIntelStatus = function (kind, text) {
const { $ } = APP.core.utils;
const intelStamp = $("#intelStamp");
const intelDot = $("#intelDot");
if (!intelStamp || !intelDot) return;
intelStamp.innerHTML = text;
intelDot.className = "dot" + (kind === "warn" ? " warn" : (kind === "bad" ? " bad" : ""));
intelDot.style.width = "7px";
intelDot.style.height = "7px";
intelDot.style.boxShadow = "none";
};
APP.ui.intel.setIntelThumb = function (i, dataUrl) {
const { $ } = APP.core.utils;
const thumbs = [$("#intelThumb0"), $("#intelThumb1"), $("#intelThumb2")];
const img = thumbs[i];
if (!img) return;
img.src = dataUrl || "";
};
APP.ui.intel.resetIntelUI = function () {
const { $ } = APP.core.utils;
const intelSummaryBox = $("#intelSummaryBox");
if (!intelSummaryBox) return;
intelSummaryBox.innerHTML = 'Upload a video, then click <b>Reason</b> to generate an unbiased scene summary.';
APP.ui.intel.setIntelStatus("warn", "Idle");
APP.ui.intel.setIntelThumb(0, "");
APP.ui.intel.setIntelThumb(1, "");
APP.ui.intel.setIntelThumb(2, "");
};
// External hook for intel summary (can be replaced by user)
APP.ui.intel.externalIntel = async function (frames) {
console.log("externalIntel called with", frames.length, "frames");
return "Video processed. No external intel provider connected.";
};
APP.ui.intel.computeIntelSummary = async function () {
const { state } = APP.core;
const { $ } = APP.core.utils;
const { log } = APP.ui.logging;
const intelSummaryBox = $("#intelSummaryBox");
const videoHidden = $("#videoHidden");
const videoEngage = $("#videoEngage");
if (!intelSummaryBox) return;
if (!state.videoLoaded) {
APP.ui.intel.resetIntelUI();
return;
}
if (state.intelBusy) return;
state.intelBusy = true;
APP.ui.intel.setIntelStatus("warn", "Generating…");
intelSummaryBox.textContent = "Sampling frames and running analysis…";
try {
const videoEl = videoHidden || videoEngage;
const dur = videoEl ? (videoEl.duration || 0) : 0;
const times = [0, dur ? dur * 0.33 : 1, dur ? dur * 0.66 : 2];
const frames = [];
for (let i = 0; i < times.length; i++) {
await APP.core.video.seekTo(videoEl, times[i]);
const canvas = document.createElement("canvas");
canvas.width = 640;
canvas.height = 360;
const ctx = canvas.getContext("2d");
ctx.drawImage(videoEl, 0, 0, canvas.width, canvas.height);
const dataUrl = canvas.toDataURL("image/jpeg", 0.6);
frames.push(dataUrl);
try {
APP.ui.intel.setIntelThumb(i, dataUrl);
} catch (_) { }
}
const summary = await APP.ui.intel.externalIntel(frames);
intelSummaryBox.textContent = summary;
APP.ui.intel.setIntelStatus("good", `Updated · ${new Date().toLocaleTimeString()}`);
} catch (err) {
APP.ui.intel.setIntelStatus("bad", "Summary unavailable");
intelSummaryBox.textContent = `Unable to generate summary: ${err.message}`;
console.error(err);
} finally {
state.intelBusy = false;
}
};
// Render mission context (if applicable)
APP.ui.intel.renderMissionContext = function () {
const { state } = APP.core;
const { $ } = APP.core.utils;
const missionClassesEl = $("#missionClasses");
const missionIdEl = $("#missionId");
if (missionClassesEl) {
if (state.hf.queries && state.hf.queries.length > 0) {
missionClassesEl.textContent = state.hf.queries.join(", ");
} else {
missionClassesEl.textContent = "All objects (no filter)";
}
}
if (missionIdEl) {
missionIdEl.textContent = state.hf.missionId || "—";
}
};