Spaces:
Sleeping
Sleeping
| window.addEventListener("error", (e) => { | |
| const log = document.querySelector("#statusLog"); | |
| if (log) log.textContent = "JS error: " + (e.message || e.error) + "\n" + log.textContent; | |
| }); | |
| const $ = (sel) => document.querySelector(sel); | |
| 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"); | |
| function setStatus(kind, text, logLine) { | |
| // kind: idle | run | ok | bad | |
| statusDot.className = `dot ${kind}`; | |
| statusText.textContent = text; | |
| if (logLine) statusLog.textContent = logLine + "\n" + statusLog.textContent; | |
| } | |
| function setRunning(isRunning) { | |
| runBtn.disabled = isRunning; | |
| spinner.classList.toggle("hidden", !isRunning); | |
| runBtnText.textContent = isRunning ? "Running…" : "Run"; | |
| } | |
| function clearResults() { | |
| imgInput.removeAttribute("src"); | |
| imgYolo.removeAttribute("src"); | |
| imgSelected.removeAttribute("src"); | |
| meta.textContent = ""; | |
| jobPill.textContent = "job: —"; | |
| [dlInput, dlYolo, dlSelected].forEach(a => { | |
| a.classList.add("hidden"); | |
| a.removeAttribute("href"); | |
| }); | |
| } | |
| function setPreview(file) { | |
| if (!file) { | |
| previewWrap.classList.add("hidden"); | |
| previewImg.removeAttribute("src"); | |
| fileMeta.textContent = "No file selected"; | |
| return; | |
| } | |
| fileMeta.textContent = `${file.name} • ${(file.size / 1024).toFixed(1)} KB`; | |
| const url = URL.createObjectURL(file); | |
| previewImg.src = url; | |
| previewWrap.classList.remove("hidden"); | |
| } | |
| dropzone.addEventListener("click", () => fileInput.click()); | |
| dropzone.addEventListener("keydown", (e) => { | |
| if (e.key === "Enter" || e.key === " ") fileInput.click(); | |
| }); | |
| fileInput.addEventListener("change", () => { | |
| const file = fileInput.files && fileInput.files[0]; | |
| setPreview(file); | |
| }); | |
| ["dragenter", "dragover"].forEach(evt => { | |
| dropzone.addEventListener(evt, (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| dropzone.classList.add("drag"); | |
| }); | |
| }); | |
| ["dragleave", "drop"].forEach(evt => { | |
| dropzone.addEventListener(evt, (e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| dropzone.classList.remove("drag"); | |
| }); | |
| }); | |
| dropzone.addEventListener("drop", (e) => { | |
| const file = e.dataTransfer.files && e.dataTransfer.files[0]; | |
| if (!file) return; | |
| // Set preview | |
| setPreview(file); | |
| // Safely assign to input (works across browsers) | |
| const dt = new DataTransfer(); | |
| dt.items.add(file); | |
| fileInput.files = dt.files; | |
| }); | |
| resetBtn.addEventListener("click", () => { | |
| form.reset(); | |
| fileInput.value = ""; | |
| setPreview(null); | |
| clearResults(); | |
| statusLog.textContent = "Waiting for input…"; | |
| setStatus("idle", "Idle", "Reset UI."); | |
| }); | |
| copyMetaBtn.addEventListener("click", async () => { | |
| const text = meta.textContent || ""; | |
| if (!text) return; | |
| await navigator.clipboard.writeText(text); | |
| setStatus("ok", "Done", "Copied metadata to clipboard."); | |
| }); | |
| form.addEventListener("submit", async (e) => { | |
| e.preventDefault(); | |
| const file = fileInput.files && fileInput.files[0]; | |
| if (!file) { | |
| setStatus("bad", "Error", "No file selected."); | |
| return; | |
| } | |
| setRunning(true); | |
| setStatus("run", "Running", "Submitting request to /api/run …"); | |
| const fd = new FormData(form); | |
| try { | |
| 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}`); | |
| } | |
| jobPill.textContent = `job: ${data.job_id}`; | |
| setStatus("ok", "Done", `Inference finished. job_id=${data.job_id}`); | |
| // Update images (cache-bust) | |
| const bust = `t=${Date.now()}`; | |
| imgInput.src = `${data.image_urls.input}?${bust}`; | |
| imgYolo.src = `${data.image_urls.yolo}?${bust}`; | |
| imgSelected.src = `${data.image_urls.selected}?${bust}`; | |
| // Open links | |
| dlInput.href = data.image_urls.input; | |
| dlYolo.href = data.image_urls.yolo; | |
| dlSelected.href = data.image_urls.selected; | |
| [dlInput, dlYolo, dlSelected].forEach(a => a.classList.remove("hidden")); | |
| // Meta | |
| meta.textContent = JSON.stringify({ | |
| task_id: data.task_id, | |
| task_name: data.task_name, | |
| selected_indices: data.selected_indices, | |
| job_id: data.job_id, | |
| }, null, 2); | |
| } catch (err) { | |
| setStatus("bad", "Error", String(err)); | |
| } finally { | |
| setRunning(false); | |
| } | |
| }); | |