// static/main.js (() => { // Helpers const $ = (id) => document.getElementById(id); const qsa = (sel) => Array.from(document.querySelectorAll(sel)); // Elements const form = $("runForm"); const runBtn = $("runBtn"); const runBtnText = $("runBtnText"); const spinner = $("spinner"); const resetBtn = $("resetBtn"); const fileInput = $("fileInput"); const dropzone = $("dropzone"); const fileMeta = $("fileMeta"); const previewWrap = $("previewWrap"); const previewImg = $("previewImg"); const statusDot = $("statusDot"); const statusText = $("statusText"); const statusLog = $("statusLog"); const jobPill = $("jobPill"); const imgInput = $("imgInput"); const imgYolo = $("imgYolo"); const imgSelected = $("imgSelected"); const meta = $("meta"); const dlInput = $("dlInput"); const dlYolo = $("dlYolo"); const dlSelected = $("dlSelected"); const copyMetaBtn = $("copyMetaBtn"); // Noise elements const noiseType = $("noiseType"); const noiseStrengthHidden = $("noiseStrength"); const noisePanelNone = $("noisePanelNone"); const noisePanelGaussian = $("noisePanelGaussian"); const noisePanelLinear = $("noisePanelLinear"); const noisePanelAdv = $("noisePanelAdv"); const noiseGaussian = $("noiseGaussian"); const noiseLinear = $("noiseLinear"); const noiseAdv = $("noiseAdv"); const noiseValGaussian = $("noiseValGaussian"); const noiseValLinear = $("noiseValLinear"); const noiseValAdv = $("noiseValAdv"); // HW noise elements (UI labels only unless backend supports) const hwNoiseWidth = $("hwNoiseWidth"); const hwNoiseStrength = $("hwNoiseStrength"); const hwNoiseWidthVal = $("hwNoiseWidthVal"); const hwNoiseStrengthVal = $("hwNoiseStrengthVal"); function setStatus(kind, text, logLine) { // kind: idle | run | ok | bad if (statusDot) statusDot.className = `dot ${kind}`; if (statusText) statusText.textContent = text; if (statusLog && logLine != null) statusLog.textContent = logLine; } function setRunning(isRunning) { if (!runBtn) return; runBtn.disabled = isRunning; if (spinner) spinner.classList.toggle("hidden", !isRunning); if (runBtnText) runBtnText.textContent = isRunning ? "Running…" : "Run"; } function setPreview(file) { if (!fileMeta || !previewWrap || !previewImg) return; if (!file) { fileMeta.textContent = "No file selected"; previewWrap.classList.add("hidden"); previewImg.removeAttribute("src"); return; } fileMeta.textContent = `${file.name} • ${(file.size / 1024).toFixed(1)} KB`; const url = URL.createObjectURL(file); previewImg.src = url; previewWrap.classList.remove("hidden"); } function setFileToInput(file) { const dt = new DataTransfer(); dt.items.add(file); fileInput.files = dt.files; } // ---------------- Tabs ---------------- function initTabs() { const tabBtns = qsa(".tabBtn"); const tabPanels = qsa(".tabPanel"); tabBtns.forEach((btn) => { btn.addEventListener("click", () => { tabBtns.forEach((b) => b.classList.remove("active")); tabPanels.forEach((p) => p.classList.add("hidden")); btn.classList.add("active"); const id = btn.getAttribute("data-tab"); const panel = $(id); if (panel) panel.classList.remove("hidden"); }); }); } // ------------- HDC bits toggle ------------- function initHdcBitsToggle() { // score function select has NAME but no ID in your HTML const scoreSel = document.querySelector('select[name="score_function"]'); const bitsField = $("hdcBitsField"); const bitsSel = $("hdcBits"); if (!scoreSel || !bitsField || !bitsSel) { console.warn("HDC bits toggle: missing DOM elements"); return; } const update = () => { const isHDC = scoreSel.value === "HDC"; bitsField.style.display = isHDC ? "" : "none"; bitsSel.disabled = !isHDC; }; scoreSel.addEventListener("change", update); update(); } // ------------- Noise panel switching ------------- function noiseFamily(val) { const t = String(val || "none").toLowerCase(); if (t === "none" || t === "default" || t === "off") return "none"; if (t === "gaussian") return "gaussian"; if (t === "linear") return "linear"; if (t.startsWith("adv")) return "adv"; return "none"; } function updateNoiseHidden() { if (!noiseType || !noiseStrengthHidden) return; const fam = noiseFamily(noiseType.value); let v = 0; if (fam === "gaussian" && noiseGaussian) v = Number(noiseGaussian.value || 0); if (fam === "linear" && noiseLinear) v = Number(noiseLinear.value || 0); if (fam === "adv" && noiseAdv) v = Number(noiseAdv.value || 0); noiseStrengthHidden.value = String(v); } function showNoisePanel() { if (!noiseType) return; const fam = noiseFamily(noiseType.value); [noisePanelNone, noisePanelGaussian, noisePanelLinear, noisePanelAdv].forEach((p) => { if (p) p.classList.add("hidden"); }); if (fam === "none" && noisePanelNone) noisePanelNone.classList.remove("hidden"); if (fam === "gaussian" && noisePanelGaussian) noisePanelGaussian.classList.remove("hidden"); if (fam === "linear" && noisePanelLinear) noisePanelLinear.classList.remove("hidden"); if (fam === "adv" && noisePanelAdv) noisePanelAdv.classList.remove("hidden"); updateNoiseHidden(); } function initNoise() { if (!noiseType) return; noiseType.addEventListener("change", showNoisePanel); if (noiseGaussian) { noiseGaussian.addEventListener("input", () => { if (noiseValGaussian) noiseValGaussian.textContent = noiseGaussian.value; if (noiseFamily(noiseType.value) === "gaussian") updateNoiseHidden(); }); } if (noiseLinear) { noiseLinear.addEventListener("input", () => { if (noiseValLinear) noiseValLinear.textContent = noiseLinear.value; if (noiseFamily(noiseType.value) === "linear") updateNoiseHidden(); }); } if (noiseAdv) { noiseAdv.addEventListener("input", () => { if (noiseValAdv) noiseValAdv.textContent = noiseAdv.value; if (noiseFamily(noiseType.value) === "adv") updateNoiseHidden(); }); } showNoisePanel(); } // ------------- HW noise label updates ------------- function initHwNoiseLabels() { if (hwNoiseWidth && hwNoiseWidthVal) { hwNoiseWidthVal.textContent = hwNoiseWidth.value; hwNoiseWidth.addEventListener("input", () => { hwNoiseWidthVal.textContent = hwNoiseWidth.value; }); } if (hwNoiseStrength && hwNoiseStrengthVal) { hwNoiseStrengthVal.textContent = hwNoiseStrength.value; hwNoiseStrength.addEventListener("input", () => { hwNoiseStrengthVal.textContent = hwNoiseStrength.value; }); } } // ------------- Upload / Dropzone ------------- function initUpload() { if (!fileInput || !dropzone) return; dropzone.addEventListener("click", () => fileInput.click()); dropzone.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); fileInput.click(); } }); fileInput.addEventListener("change", () => { const f = fileInput.files && fileInput.files[0]; setPreview(f); }); dropzone.addEventListener("dragover", (e) => { e.preventDefault(); dropzone.classList.add("drag"); }); dropzone.addEventListener("dragleave", () => { dropzone.classList.remove("drag"); }); dropzone.addEventListener("drop", (e) => { e.preventDefault(); dropzone.classList.remove("drag"); const f = e.dataTransfer?.files?.[0]; if (!f) return; setFileToInput(f); setPreview(f); }); } // ------------- Reset ------------- function initReset() { if (!resetBtn || !form) return; resetBtn.addEventListener("click", () => { form.reset(); if (fileInput) fileInput.value = ""; setPreview(null); if (jobPill) jobPill.textContent = "job: —"; if (imgInput) imgInput.removeAttribute("src"); if (imgYolo) imgYolo.removeAttribute("src"); if (imgSelected) imgSelected.removeAttribute("src"); if (meta) meta.textContent = ""; // reset labels if (noiseValGaussian && noiseGaussian) noiseValGaussian.textContent = noiseGaussian.value; if (noiseValLinear && noiseLinear) noiseValLinear.textContent = noiseLinear.value; if (noiseValAdv && noiseAdv) noiseValAdv.textContent = noiseAdv.value; initHdcBitsToggle(); showNoisePanel(); setStatus("idle", "Idle", "Waiting for input…"); setRunning(false); }); } // ------------- Copy meta ------------- function initCopyMeta() { if (!copyMetaBtn || !meta) return; copyMetaBtn.addEventListener("click", async () => { const text = meta.textContent || ""; if (!text) return; await navigator.clipboard.writeText(text); setStatus("ok", "Done", "Copied metadata to clipboard."); }); } // ------------- Submit ------------- function initSubmit() { if (!form) return; form.addEventListener("submit", async (e) => { e.preventDefault(); const file = fileInput?.files?.[0]; if (!file) { setStatus("bad", "Error", "No file selected."); return; } updateNoiseHidden(); setRunning(true); setStatus("run", "Running", "Submitting request…"); try { const fd = new FormData(form); const resp = await fetch("/api/run", { method: "POST", body: fd }); const data = await resp.json(); if (!resp.ok || !data.ok) throw new Error(data?.error || `HTTP ${resp.status}`); if (jobPill) jobPill.textContent = `job: ${data.job_id}`; const bust = `t=${Date.now()}`; if (imgInput) imgInput.src = `${data.image_urls.input}?${bust}`; if (imgYolo) imgYolo.src = `${data.image_urls.yolo}?${bust}`; if (imgSelected) imgSelected.src = `${data.image_urls.selected}?${bust}`; if (dlInput) { dlInput.href = data.image_urls.input; dlInput.classList.remove("hidden"); } if (dlYolo) { dlYolo.href = data.image_urls.yolo; dlYolo.classList.remove("hidden"); } if (dlSelected) { dlSelected.href = data.image_urls.selected; dlSelected.classList.remove("hidden"); } if (meta) { meta.textContent = JSON.stringify( { job_id: data.job_id, task_id: data.task_id, task_name: data.task_name, selected_indices: data.selected_indices, }, null, 2 ); } setStatus("ok", "Done", "Finished."); } catch (err) { setStatus("bad", "Error", String(err)); } finally { setRunning(false); } }); } // Boot document.addEventListener("DOMContentLoaded", () => { if (!form || !fileInput || !dropzone) { console.error("Missing DOM elements: check ids in HTML."); if (statusLog) statusLog.textContent = "JS init failed: missing DOM elements (check ids)."; return; } initTabs(); initHdcBitsToggle(); initNoise(); initHwNoiseLabels(); initUpload(); initReset(); initCopyMeta(); initSubmit(); setStatus("idle", "Idle", "Waiting for input…"); }); })();