szwendaczjakomaj's picture
fix(frontend): match Field Report panel min-height to Classification panel
d1571a6
Raw
History Blame Contribute Delete
5.99 kB
import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client@2.2.1";
const $ = (id) => document.getElementById(id);
// Match Field Report panel height to Document Classification panel (one-shot after fonts load)
window.addEventListener("load", () => {
const classifyPanel = document.querySelector(".col:first-child > .panel");
const reportPanel = document.querySelector(".col:last-child > .panel");
if (classifyPanel && reportPanel)
reportPanel.style.minHeight = classifyPanel.offsetHeight + "px";
});
const SAMPLES = [
{ src: "/examples/paper-2.jpg", fmt: "airfix_instruction", lbl: "Instructions A" },
{ src: "/examples/paper-3.JPG", fmt: "airfix_instruction", lbl: "Instructions B" },
{ src: "/examples/shop-2.png", fmt: "airfix_shop", lbl: "Shop listing" },
{ src: "/examples/shop-3.png", fmt: "airfix_shop", lbl: "Colour chart" },
];
let samplesLoaded = 0;
let samplesSettled = 0;
const smpBtns = document.querySelectorAll(".smp");
smpBtns.forEach((btn, i) => {
const s = SAMPLES[i];
btn.addEventListener("click", () => loadSample(s.src, s.fmt));
const probe = new Image();
probe.onload = () => {
btn.style.backgroundImage = `url('${s.src}')`;
btn.title = s.lbl;
samplesLoaded++;
if (++samplesSettled === smpBtns.length && samplesLoaded === 0)
document.querySelector(".samples").style.display = "none";
};
probe.onerror = () => {
btn.style.display = "none";
if (++samplesSettled === smpBtns.length && samplesLoaded === 0)
document.querySelector(".samples").style.display = "none";
};
probe.src = s.src;
});
async function loadSample(src, fmt) {
const resp = await fetch(src);
const blob = await resp.blob();
const filename = src.split("/").pop();
const file = new File([blob], filename, { type: blob.type });
const dt = new DataTransfer();
dt.items.add(file);
$("file").files = dt.files;
$("file").dispatchEvent(new Event("change"));
$("format").value = fmt;
}
// On file select: swap the dropzone for the EXHIBIT A evidence frame
// (dark frame + clip + RECEIVED stamp wrapping the user's uploaded image).
let previewUrl = null;
$("file").addEventListener("change", () => {
const f = $("file").files[0];
const preview = $("preview");
const dropzone = $("dropzone");
const evidence = $("evidence");
if (previewUrl) { URL.revokeObjectURL(previewUrl); previewUrl = null; }
if (f) {
previewUrl = URL.createObjectURL(f);
preview.src = previewUrl;
$("src-name").textContent = "SRC: " + f.name;
dropzone.style.display = "none";
evidence.classList.add("on");
} else {
preview.removeAttribute("src");
$("src-name").textContent = "SRC: —";
dropzone.style.display = "";
evidence.classList.remove("on");
}
// Reset the right side to STANDBY (radar) before the next analysis.
$("reportIdle").style.display = "";
$("report").style.display = "none";
$("reportToggle").style.display = "none";
});
async function run() {
const file = $("file").files[0];
const report = $("report");
const scan = $("scan");
// Always swap radar idle for the report text element before reporting anything.
$("reportIdle").style.display = "none";
report.style.display = "";
$("reportToggle").style.display = "none";
if (!file) {
report.classList.remove("pulse");
report.classList.add("alert");
report.textContent = "No image provided.";
return;
}
const format_id = $("format").value;
$("analyze").disabled = true;
$("manifest").innerHTML = '<div class="idle-m">Awaiting resupply manifest…</div>';
scan.classList.add("on");
report.classList.remove("alert");
report.classList.add("pulse");
report.textContent = "▸ Establishing uplink to field unit…";
try {
const client = await Client.connect(window.location.origin);
const job = client.submit("/analyze", { image: handle_file(file), format_id });
let finalReceived = false;
for await (const msg of job) {
if (msg.type !== "data") continue;
const [text, manifest] = msg.data; // tuple yielded by the backend
if (manifest && manifest !== "") { // final event: manifest non-empty
finalReceived = true;
report.classList.remove("pulse");
report.textContent = text;
$("manifest").innerHTML = manifest;
// Collapse only when real codes were found. The empty/timeout/error
// returns from _run_analysis are <p>…</p> with no <li>, so the raw
// report stays expanded (it's then the only useful output).
if (/<li/.test(manifest)) collapseReport();
} else { // streaming status line
report.textContent = text;
}
}
if (!finalReceived) {
report.classList.remove("pulse");
report.classList.add("alert");
report.textContent = "Analysis ended without a result — field unit may be offline.";
}
} catch (err) {
report.classList.remove("pulse");
report.classList.add("alert");
report.textContent = "Field unit offline — " + (err && err.message ? err.message : err);
} finally {
scan.classList.remove("on"); // stop the sweep loop
$("analyze").disabled = false;
}
}
// Collapse the raw Field Report into the INTEL DECODED bar (called on success
// when the manifest carries real codes). The bar is an accordion header.
function collapseReport() {
$("report").style.display = "none";
const tog = $("reportToggle");
tog.style.display = "";
tog.setAttribute("aria-expanded", "false");
$("reportCaret").textContent = "▸";
}
$("reportToggle").addEventListener("click", () => {
const report = $("report");
const expanded = report.style.display !== "none";
report.style.display = expanded ? "none" : "";
$("reportToggle").setAttribute("aria-expanded", String(!expanded));
$("reportCaret").textContent = expanded ? "▸" : "▾";
});
$("analyze").addEventListener("click", run);