HanningChen commited on
Commit
95e24be
·
1 Parent(s): 5ee17b1
Files changed (2) hide show
  1. webui/app.py +3 -0
  2. webui/static/main.js +288 -133
webui/app.py CHANGED
@@ -174,6 +174,8 @@ runner = ModelRunner(
174
 
175
  @app.get("/", response_class=HTMLResponse)
176
  def index(request: Request):
 
 
177
  return templates.TemplateResponse(
178
  "index.html",
179
  {
@@ -187,6 +189,7 @@ def index(request: Request):
187
  "od_choices": OD_CHOICES,
188
  "default_od": DEFAULT_OD,
189
  "task_ids": runner.list_task_ids(),
 
190
  },
191
  )
192
 
 
174
 
175
  @app.get("/", response_class=HTMLResponse)
176
  def index(request: Request):
177
+ task_ids = runner.list_task_ids()
178
+ task_items = [(tid, runner.id2task_name.get(str(tid), f"task_{tid}")) for tid in task_ids]
179
  return templates.TemplateResponse(
180
  "index.html",
181
  {
 
189
  "od_choices": OD_CHOICES,
190
  "default_od": DEFAULT_OD,
191
  "task_ids": runner.list_task_ids(),
192
+ "task_items": task_items
193
  },
194
  )
195
 
webui/static/main.js CHANGED
@@ -1,170 +1,325 @@
1
- // ==========================
2
- // DOM helpers
3
- // ==========================
 
 
 
 
 
 
 
4
  const $ = (id) => document.getElementById(id);
 
 
5
 
6
- // ==========================
7
- // Elements
8
- // ==========================
9
  const form = $("runForm");
10
  const runBtn = $("runBtn");
11
  const runBtnText = $("runBtnText");
12
  const spinner = $("spinner");
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
- const noiseType = $("noiseType");
15
- const noiseStrengthHidden = $("noiseStrength");
16
-
17
- const noisePanels = {
18
- none: $("noisePanelNone"),
19
- gaussian: $("noisePanelGaussian"),
20
- linear: $("noisePanelLinear"),
21
- adv: $("noisePanelAdv"),
22
- };
23
-
24
- const noiseSliders = {
25
- gaussian: $("noiseGaussian"),
26
- linear: $("noiseLinear"),
27
- adv: $("noiseAdv"),
28
- };
29
-
30
- const noiseVals = {
31
- gaussian: $("noiseValGaussian"),
32
- linear: $("noiseValLinear"),
33
- adv: $("noiseValAdv"),
34
- };
35
-
36
- // HDC / score function
37
- const scoreSelect = document.querySelector('select[name="score_function"]');
38
- const hdcBitsField = $("hdcBitsField");
39
- const hdcBitsSelect = $("hdcBits");
40
- const hdvDimSelect = document.querySelector('select[name="hdv_dim"]');
41
-
42
- // HW noise
43
- const hwNoiseDist = $("hwNoiseDist");
44
- const hwNoiseWidth = $("hwNoiseWidth");
45
- const hwNoiseStrength = $("hwNoiseStrength");
46
- const hwNoiseWidthVal = $("hwNoiseWidthVal");
47
- const hwNoiseStrengthVal = $("hwNoiseStrengthVal");
48
-
49
- // Output
50
  const imgInput = $("imgInput");
51
  const imgYolo = $("imgYolo");
52
  const imgSelected = $("imgSelected");
53
- const metaBox = $("meta");
54
-
55
- // ==========================
56
- // Noise helpers
57
- // ==========================
58
- function noiseFamily(val) {
59
- if (!val || val === "none") return "none";
60
- if (val === "gaussian") return "gaussian";
61
- if (val === "linear") return "linear";
62
- if (val.startsWith("adv")) return "adv";
63
- return "none";
64
  }
65
 
66
- function updateNoiseUI() {
67
- const fam = noiseFamily(noiseType.value);
 
 
 
 
 
68
 
69
- Object.values(noisePanels).forEach((p) => p.classList.add("hidden"));
70
- noiseStrengthHidden.value = "0";
 
 
 
 
71
 
72
- if (fam === "none") {
73
- noisePanels.none.classList.remove("hidden");
74
- return;
75
- }
 
 
76
 
77
- noisePanels[fam].classList.remove("hidden");
78
- const slider = noiseSliders[fam];
79
- const val = slider.value;
80
- noiseStrengthHidden.value = val;
81
- noiseVals[fam].innerText = val;
82
  }
83
 
84
- // ==========================
85
- // HDC helpers
86
- // ==========================
87
- function updateHdcBitsVisibility() {
88
- if (scoreSelect.value === "HDC") {
89
- hdcBitsField.style.display = "block";
90
- } else {
91
- hdcBitsField.style.display = "none";
92
  }
 
 
 
 
 
93
  }
94
 
95
- // ==========================
96
- // HW noise helpers
97
- // ==========================
98
- function updateHwNoiseUI() {
99
- hwNoiseWidthVal.innerText = hwNoiseWidth.value;
100
- hwNoiseStrengthVal.innerText = hwNoiseStrength.value;
101
  }
102
 
103
- // ==========================
104
- // Event bindings
105
- // ==========================
106
- noiseType.addEventListener("change", updateNoiseUI);
107
 
108
- Object.keys(noiseSliders).forEach((k) => {
109
- noiseSliders[k].addEventListener("input", () => {
110
- noiseVals[k].innerText = noiseSliders[k].value;
111
- noiseStrengthHidden.value = noiseSliders[k].value;
112
- });
113
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
- scoreSelect.addEventListener("change", updateHdcBitsVisibility);
 
116
 
117
- hwNoiseWidth.addEventListener("input", updateHwNoiseUI);
118
- hwNoiseStrength.addEventListener("input", updateHwNoiseUI);
 
119
 
120
- // ==========================
121
- // Form submit
122
- // ==========================
123
- form.addEventListener("submit", async (e) => {
124
- e.preventDefault();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- runBtn.disabled = true;
127
- spinner.classList.remove("hidden");
128
- runBtnText.innerText = "Running...";
129
 
130
- const fd = new FormData(form);
 
131
 
132
- // --- Explicit HDC bits handling (IMPORTANT) ---
133
- if (scoreSelect.value === "HDC") {
134
- fd.set("hdc_bits", hdcBitsSelect.value);
135
- } else {
136
- fd.set("hdc_bits", "32"); // backend default
137
  }
138
 
139
- try {
140
- const res = await fetch("/api/run", {
141
- method: "POST",
142
- body: fd,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
- const data = await res.json();
 
 
 
146
 
147
- if (!data.ok) {
148
- throw new Error(data.error || "Unknown error");
 
 
149
  }
150
 
151
- imgInput.src = data.image_urls.input;
152
- imgYolo.src = data.image_urls.yolo;
153
- imgSelected.src = data.image_urls.selected;
154
-
155
- metaBox.innerText = JSON.stringify(data, null, 2);
156
- } catch (err) {
157
- alert("Error: " + err.message);
158
- } finally {
159
- spinner.classList.add("hidden");
160
- runBtn.disabled = false;
161
- runBtnText.innerText = "Run";
162
- }
163
- });
164
 
165
- // ==========================
166
- // Init on page load
167
- // ==========================
168
- updateNoiseUI();
169
- updateHdcBitsVisibility();
170
- updateHwNoiseUI();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ====== Error logger (best-effort) ======
2
+ window.addEventListener("error", (e) => {
3
+ const log = document.getElementById("statusLog");
4
+ if (log) {
5
+ log.textContent =
6
+ "JS error: " + (e.message || String(e.error)) + "\n" + log.textContent;
7
+ }
8
+ });
9
+
10
+ // ====== Helpers ======
11
  const $ = (id) => document.getElementById(id);
12
+ const qs = (sel) => document.querySelector(sel);
13
+ const qsa = (sel) => Array.from(document.querySelectorAll(sel));
14
 
15
+ // ====== Elements ======
 
 
16
  const form = $("runForm");
17
  const runBtn = $("runBtn");
18
  const runBtnText = $("runBtnText");
19
  const spinner = $("spinner");
20
+ const resetBtn = $("resetBtn");
21
+
22
+ const fileInput = $("fileInput");
23
+ const dropzone = $("dropzone");
24
+ const fileMeta = $("fileMeta");
25
+ const previewWrap = $("previewWrap");
26
+ const previewImg = $("previewImg");
27
+
28
+ const statusDot = $("statusDot");
29
+ const statusText = $("statusText");
30
+ const statusLog = $("statusLog");
31
+ const jobPill = $("jobPill");
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  const imgInput = $("imgInput");
34
  const imgYolo = $("imgYolo");
35
  const imgSelected = $("imgSelected");
36
+ const meta = $("meta");
37
+
38
+ const dlInput = $("dlInput");
39
+ const dlYolo = $("dlYolo");
40
+ const dlSelected = $("dlSelected");
41
+ const copyMetaBtn = $("copyMetaBtn");
42
+
43
+ // Guard: if your HTML hasn’t loaded / ids mismatch
44
+ if (!form || !fileInput || !dropzone) {
45
+ console.error("Missing required DOM elements. Check your element ids.");
 
46
  }
47
 
48
+ // ====== UI helpers ======
49
+ function setStatus(kind, text, logLine) {
50
+ // kind: idle | run | ok | bad
51
+ if (statusDot) statusDot.className = `dot ${kind}`;
52
+ if (statusText) statusText.textContent = text;
53
+ if (statusLog && logLine) statusLog.textContent = logLine + "\n" + statusLog.textContent;
54
+ }
55
 
56
+ function setRunning(isRunning) {
57
+ if (!runBtn || !spinner || !runBtnText) return;
58
+ runBtn.disabled = isRunning;
59
+ spinner.classList.toggle("hidden", !isRunning);
60
+ runBtnText.textContent = isRunning ? "Running…" : "Run";
61
+ }
62
 
63
+ function clearResults() {
64
+ if (imgInput) imgInput.removeAttribute("src");
65
+ if (imgYolo) imgYolo.removeAttribute("src");
66
+ if (imgSelected) imgSelected.removeAttribute("src");
67
+ if (meta) meta.textContent = "";
68
+ if (jobPill) jobPill.textContent = "job: —";
69
 
70
+ [dlInput, dlYolo, dlSelected].forEach((a) => {
71
+ if (!a) return;
72
+ a.classList.add("hidden");
73
+ a.removeAttribute("href");
74
+ });
75
  }
76
 
77
+ function setPreview(file) {
78
+ if (!previewWrap || !previewImg || !fileMeta) return;
79
+
80
+ if (!file) {
81
+ previewWrap.classList.add("hidden");
82
+ previewImg.removeAttribute("src");
83
+ fileMeta.textContent = "No file selected";
84
+ return;
85
  }
86
+
87
+ fileMeta.textContent = `${file.name} • ${(file.size / 1024).toFixed(1)} KB`;
88
+ const url = URL.createObjectURL(file);
89
+ previewImg.src = url;
90
+ previewWrap.classList.remove("hidden");
91
  }
92
 
93
+ function setFileToInput(file) {
94
+ // Ensures FormData(form) sees the file even for drag/drop
95
+ const dt = new DataTransfer();
96
+ dt.items.add(file);
97
+ fileInput.files = dt.files;
 
98
  }
99
 
100
+ // ====== Noise panel logic ======
101
+ (function initNoisePanels() {
102
+ const noiseType = $("noiseType");
103
+ if (!noiseType) return;
104
 
105
+ const panels = {
106
+ none: $("noisePanelNone"),
107
+ gaussian: $("noisePanelGaussian"),
108
+ linear: $("noisePanelLinear"),
109
+ adv: $("noisePanelAdv"),
110
+ };
111
+
112
+ const sliders = {
113
+ gaussian: $("noiseGaussian"),
114
+ linear: $("noiseLinear"),
115
+ adv: $("noiseAdv"),
116
+ };
117
+
118
+ const labels = {
119
+ gaussian: $("noiseValGaussian"),
120
+ linear: $("noiseValLinear"),
121
+ adv: $("noiseValAdv"),
122
+ };
123
+
124
+ const hiddenStrength = $("noiseStrength");
125
+ if (!hiddenStrength) return;
126
 
127
+ function noiseFamily(val) {
128
+ const t = String(val || "none").toLowerCase();
129
 
130
+ if (t === "none" || t === "off") return "none";
131
+ if (t === "gaussian" || t.includes("gauss")) return "gaussian";
132
+ if (t === "linear" || t.includes("linear")) return "linear";
133
 
134
+ // adversarial variants
135
+ if (
136
+ t === "adv" ||
137
+ t.startsWith("adv") ||
138
+ t.includes("adversarial") ||
139
+ t.includes("attack") ||
140
+ t.includes("fgsm") ||
141
+ t.includes("pgd")
142
+ ) return "adv";
143
+
144
+ return "none";
145
+ }
146
+
147
+ function updateHiddenStrength() {
148
+ const fam = noiseFamily(noiseType.value);
149
+ if (fam === "none") {
150
+ hiddenStrength.value = "0";
151
+ return;
152
+ }
153
+ hiddenStrength.value = String(sliders[fam]?.value ?? "0");
154
+ }
155
 
156
+ function showPanelForCurrentSelection() {
157
+ const fam = noiseFamily(noiseType.value);
 
158
 
159
+ Object.values(panels).forEach((p) => p && p.classList.add("hidden"));
160
+ (panels[fam] || panels.none)?.classList.remove("hidden");
161
 
162
+ updateHiddenStrength();
 
 
 
 
163
  }
164
 
165
+ Object.keys(sliders).forEach((k) => {
166
+ const s = sliders[k];
167
+ if (!s) return;
168
+ s.addEventListener("input", () => {
169
+ if (labels[k]) labels[k].textContent = s.value;
170
+ if (noiseFamily(noiseType.value) === k) updateHiddenStrength();
171
+ });
172
+ });
173
+
174
+ noiseType.addEventListener("change", showPanelForCurrentSelection);
175
+ showPanelForCurrentSelection();
176
+ })();
177
+
178
+ // ====== Tabs + HW noise labels ======
179
+ (function initTabsAndHwNoise() {
180
+ const tabBtns = qsa(".tabBtn");
181
+ const tabPanels = qsa(".tabPanel");
182
+
183
+ tabBtns.forEach((b) => {
184
+ b.addEventListener("click", () => {
185
+ tabBtns.forEach((x) => x.classList.remove("active"));
186
+ tabPanels.forEach((p) => p.classList.add("hidden"));
187
+
188
+ b.classList.add("active");
189
+ const id = b.getAttribute("data-tab");
190
+ $(id)?.classList.remove("hidden");
191
+ });
192
+ });
193
+
194
+ const w = $("hwNoiseWidth");
195
+ const s = $("hwNoiseStrength");
196
+ const wv = $("hwNoiseWidthVal");
197
+ const sv = $("hwNoiseStrengthVal");
198
+ if (w && wv) w.addEventListener("input", () => (wv.textContent = w.value));
199
+ if (s && sv) s.addEventListener("input", () => (sv.textContent = s.value));
200
+ })();
201
+
202
+ // ====== Upload / Dropzone ======
203
+ (function initUpload() {
204
+ if (!fileInput || !dropzone) return;
205
+
206
+ // Click to open picker
207
+ dropzone.addEventListener("click", () => fileInput.click());
208
+ dropzone.addEventListener("keydown", (e) => {
209
+ if (e.key === "Enter" || e.key === " ") {
210
+ e.preventDefault();
211
+ fileInput.click();
212
+ }
213
+ });
214
+
215
+ // File picked
216
+ fileInput.addEventListener("change", () => {
217
+ const file = fileInput.files && fileInput.files[0];
218
+ setPreview(file);
219
+ });
220
+
221
+ // Drag styling
222
+ ["dragenter", "dragover"].forEach((evt) => {
223
+ dropzone.addEventListener(evt, (e) => {
224
+ e.preventDefault();
225
+ e.stopPropagation();
226
+ dropzone.classList.add("drag");
227
+ });
228
+ });
229
+
230
+ ["dragleave", "drop"].forEach((evt) => {
231
+ dropzone.addEventListener(evt, (e) => {
232
+ e.preventDefault();
233
+ e.stopPropagation();
234
+ dropzone.classList.remove("drag");
235
  });
236
+ });
237
+
238
+ // Drop file
239
+ dropzone.addEventListener("drop", (e) => {
240
+ const file = e.dataTransfer?.files?.[0];
241
+ if (!file) return;
242
+ setFileToInput(file);
243
+ setPreview(file);
244
+ });
245
+ })();
246
+
247
+ // ====== Reset + copy meta ======
248
+ if (resetBtn) {
249
+ resetBtn.addEventListener("click", () => {
250
+ if (form) form.reset();
251
+ if (fileInput) fileInput.value = "";
252
+ setPreview(null);
253
+ clearResults();
254
+ if (statusLog) statusLog.textContent = "Waiting for input…";
255
+ setStatus("idle", "Idle", "Reset UI.");
256
+ });
257
+ }
258
+
259
+ if (copyMetaBtn) {
260
+ copyMetaBtn.addEventListener("click", async () => {
261
+ const text = meta?.textContent || "";
262
+ if (!text) return;
263
+ await navigator.clipboard.writeText(text);
264
+ setStatus("ok", "Done", "Copied metadata to clipboard.");
265
+ });
266
+ }
267
 
268
+ // ====== Submit ======
269
+ if (form) {
270
+ form.addEventListener("submit", async (e) => {
271
+ e.preventDefault();
272
 
273
+ const file = fileInput?.files?.[0];
274
+ if (!file) {
275
+ setStatus("bad", "Error", "No file selected.");
276
+ return;
277
  }
278
 
279
+ setRunning(true);
280
+ setStatus("run", "Running", "Submitting request to /api/run …");
281
+
282
+ const fd = new FormData(form);
 
 
 
 
 
 
 
 
 
283
 
284
+ try {
285
+ const resp = await fetch("/api/run", { method: "POST", body: fd });
286
+ const data = await resp.json();
287
+
288
+ if (!resp.ok || !data.ok) {
289
+ throw new Error(data?.error || `HTTP ${resp.status}`);
290
+ }
291
+
292
+ if (jobPill) jobPill.textContent = `job: ${data.job_id}`;
293
+ setStatus("ok", "Done", `Inference finished. job_id=${data.job_id}`);
294
+
295
+ // Update images (cache-bust)
296
+ const bust = `t=${Date.now()}`;
297
+ if (imgInput) imgInput.src = `${data.image_urls.input}?${bust}`;
298
+ if (imgYolo) imgYolo.src = `${data.image_urls.yolo}?${bust}`;
299
+ if (imgSelected) imgSelected.src = `${data.image_urls.selected}?${bust}`;
300
+
301
+ // Open links
302
+ if (dlInput) { dlInput.href = data.image_urls.input; dlInput.classList.remove("hidden"); }
303
+ if (dlYolo) { dlYolo.href = data.image_urls.yolo; dlYolo.classList.remove("hidden"); }
304
+ if (dlSelected) { dlSelected.href = data.image_urls.selected; dlSelected.classList.remove("hidden"); }
305
+
306
+ // Meta
307
+ if (meta) {
308
+ meta.textContent = JSON.stringify(
309
+ {
310
+ task_id: data.task_id,
311
+ task_name: data.task_name,
312
+ selected_indices: data.selected_indices,
313
+ job_id: data.job_id,
314
+ },
315
+ null,
316
+ 2
317
+ );
318
+ }
319
+ } catch (err) {
320
+ setStatus("bad", "Error", String(err));
321
+ } finally {
322
+ setRunning(false);
323
+ }
324
+ });
325
+ }