import { FireDetector, drawBoxes, className } from "./detector.js"; const fileInput = document.getElementById("file-input"); const detectBtn = document.getElementById("detect-btn"); const clearBtn = document.getElementById("clear-btn"); const sampleBtn = document.getElementById("sample-btn"); const statusEl = document.getElementById("status"); const statusDot = document.getElementById("status-dot"); const inputCanvas = document.getElementById("input-canvas"); const outputCanvas = document.getElementById("output-canvas"); const inputFrame = inputCanvas.closest(".canvas-frame"); const outputFrame = outputCanvas.closest(".canvas-frame"); const jsonOutput = document.getElementById("json-output"); const dropzone = document.getElementById("dropzone"); const countValue = document.getElementById("count-value"); const latencyValue = document.getElementById("latency-value"); const outputBadge = document.getElementById("output-badge"); const detector = new FireDetector(); let currentImage = null; function setStatus(message, state = "ready") { statusEl.textContent = message; statusDot.className = "status-dot"; if (state === "loading") statusDot.classList.add("status-dot--loading"); else if (state === "error") statusDot.classList.add("status-dot--error"); else statusDot.classList.add("status-dot--ready"); } function setCanvasState(frame, hasImage) { frame.classList.toggle("has-image", hasImage); } function drawImageToCanvas(canvas, image) { canvas.width = image.width; canvas.height = image.height; const ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(image, 0, 0); } async function loadImageFromUrl(url) { const response = await fetch(url); if (!response.ok) throw new Error("Failed to load image"); const blob = await response.blob(); return loadImageFromFile(blob); } function loadImageFromFile(file) { return new Promise((resolve, reject) => { const url = URL.createObjectURL(file); const image = new Image(); image.onload = () => { URL.revokeObjectURL(url); resolve(image); }; image.onerror = () => { URL.revokeObjectURL(url); reject(new Error("Failed to load image")); }; image.src = url; }); } function summarizeDetections(boxes) { const counts = {}; for (const box of boxes) { const name = className(box.cls_id); counts[name] = (counts[name] ?? 0) + 1; } return Object.entries(counts) .map(([name, count]) => `${count} ${name}${count === 1 ? "" : "s"}`) .join(", "); } function updateMetrics(count, latencyMs, boxes = null) { countValue.textContent = count ?? "—"; latencyValue.textContent = latencyMs != null ? `${latencyMs} ms` : "—"; if (count == null) { outputBadge.classList.add("hidden"); return; } const summary = boxes?.length ? summarizeDetections(boxes) : `${count} detections`; outputBadge.textContent = summary; outputBadge.classList.remove("hidden"); } function clearDemo() { currentImage = null; fileInput.value = ""; jsonOutput.textContent = "[]"; setCanvasState(inputFrame, false); setCanvasState(outputFrame, false); detectBtn.disabled = true; clearBtn.disabled = true; updateMetrics(null, null); setStatus("Model ready. Upload a scene or load the example.", "ready"); } async function setCurrentImage(image) { currentImage = image; drawImageToCanvas(inputCanvas, currentImage); drawImageToCanvas(outputCanvas, currentImage); setCanvasState(inputFrame, true); setCanvasState(outputFrame, true); jsonOutput.textContent = "[]"; detectBtn.disabled = false; clearBtn.disabled = false; updateMetrics(null, null); } async function loadImage(file) { setStatus("Loading image...", "loading"); const image = await loadImageFromFile(file); await setCurrentImage(image); setStatus("Image loaded. Running Ember...", "loading"); await runDetection(); } async function loadExample() { try { setStatus("Loading example scene...", "loading"); const image = await loadImageFromUrl("./example_input.png"); await setCurrentImage(image); setStatus("Example loaded. Running Ember...", "loading"); await runDetection(); } catch (error) { setStatus(error.message, "error"); } } async function runDetection() { if (!currentImage) return; detectBtn.disabled = true; setStatus("Analyzing scene...", "loading"); const started = performance.now(); try { const boxes = await detector.predictImage(currentImage); const latencyMs = Math.round(performance.now() - started); drawImageToCanvas(outputCanvas, currentImage); drawBoxes(outputCanvas.getContext("2d"), boxes); jsonOutput.textContent = JSON.stringify(boxes, null, 2); updateMetrics(boxes.length, latencyMs, boxes); const headline = boxes.length === 0 ? "No fire hazards detected in this frame." : `Ember found ${boxes.length} detection${boxes.length === 1 ? "" : "s"} in ${latencyMs} ms.`; setStatus(headline, "ready"); } catch (error) { console.error(error); setStatus(`Detection failed: ${error.message}`, "error"); } finally { detectBtn.disabled = false; } } fileInput.addEventListener("change", async (event) => { const file = event.target.files?.[0]; if (!file) return; try { await loadImage(file); } catch (error) { setStatus(error.message, "error"); } }); detectBtn.addEventListener("click", runDetection); clearBtn.addEventListener("click", clearDemo); sampleBtn.addEventListener("click", loadExample); dropzone.addEventListener("dragover", (event) => { event.preventDefault(); dropzone.classList.add("is-dragover"); }); dropzone.addEventListener("dragleave", () => dropzone.classList.remove("is-dragover")); dropzone.addEventListener("drop", async (event) => { event.preventDefault(); dropzone.classList.remove("is-dragover"); const file = event.dataTransfer?.files?.[0]; if (!file || !file.type.startsWith("image/")) { setStatus("Please drop an image file.", "error"); return; } try { await loadImage(file); } catch (error) { setStatus(error.message, "error"); } }); try { setStatus("Loading Ember model...", "loading"); await detector.load("./weights.onnx"); setStatus("Ember ready. Upload a scene or load the example.", "ready"); } catch (error) { console.error(error); setStatus("Failed to load model. Check that weights.onnx is available.", "error"); }