Spaces:
Sleeping
Sleeping
| // 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…"); | |
| }); | |
| })(); | |