const classSelect = document.querySelector("#class-select"); const form = document.querySelector("#generate-form"); const guidance = document.querySelector("#guidance"); const guidanceValue = document.querySelector("#guidance-value"); const statusEl = document.querySelector("#status"); const imageStage = document.querySelector("#image-stage"); const resultTitle = document.querySelector("#result-title"); const message = document.querySelector("#message"); const button = document.querySelector("#generate-button"); const downloadLink = document.querySelector("#download-link"); const recognizeButton = document.querySelector("#recognize-button"); const recognitionList = document.querySelector("#recognition-list"); const cnnStatus = document.querySelector("#cnn-status"); let currentImage = null; function setMessage(text, isError = false) { message.textContent = text; message.classList.toggle("error", isError); } function setLoading(isLoading) { button.disabled = isLoading; button.querySelector("span:last-child").textContent = isLoading ? "生成中" : "生成"; } async function loadStatus() { const response = await fetch("/api/status"); const status = await response.json(); statusEl.textContent = status.checkpoint_exists ? `${status.device} · ${status.image_size}x${status.image_size} · step ${status.step ?? "not loaded"}` : "checkpoint missing"; } async function loadCnnStatus() { try { const response = await fetch("/api/cnn/classes"); const data = await response.json(); if (!response.ok) { throw new Error(data.detail || "CNN offline"); } const classes = Array.isArray(data) ? data : data.classes; cnnStatus.textContent = `CNN: ${classes.length} classes`; cnnStatus.title = classes.join(", "); } catch (error) { cnnStatus.textContent = "CNN: offline"; cnnStatus.title = error.message; } } async function loadClasses() { const response = await fetch("/api/classes"); const data = await response.json(); classSelect.innerHTML = ""; for (const name of data.classes) { const option = document.createElement("option"); option.value = name; option.textContent = name; classSelect.append(option); } classSelect.value = data.classes.includes("cat") ? "cat" : data.classes[0]; } guidance.addEventListener("input", () => { guidanceValue.value = Number(guidance.value).toFixed(2); }); form.addEventListener("submit", async (event) => { event.preventDefault(); const formData = new FormData(form); const seedValue = formData.get("seed"); const payload = { class_name: formData.get("class_name"), count: Number(formData.get("count")), guidance_scale: Number(formData.get("guidance_scale")), seed: seedValue ? Number(seedValue) : null, }; setLoading(true); setMessage("模型采样大约需要几十秒,MPS/CPU 会更慢。"); try { const response = await fetch("/api/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const data = await response.json(); if (!response.ok) { throw new Error(data.detail || "生成失败"); } imageStage.innerHTML = ""; const image = document.createElement("img"); image.src = data.image; image.alt = `${data.class_name} generated sketch samples`; imageStage.append(image); currentImage = data.image; resultTitle.textContent = data.class_name; downloadLink.href = data.image; downloadLink.setAttribute("aria-disabled", "false"); recognizeButton.disabled = false; recognitionList.innerHTML = ""; setMessage(`完成 · ${data.device} · step ${data.step} · CFG ${data.guidance_scale}`); await loadStatus(); } catch (error) { setMessage(error.message, true); } finally { setLoading(false); } }); recognizeButton.addEventListener("click", async () => { if (!currentImage) { return; } recognizeButton.disabled = true; recognizeButton.textContent = "识别中"; setMessage("正在请求 CNN 图片识别服务。"); try { const response = await fetch("/api/recognize", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ image: currentImage, top_k: 5, predictor: "quickdraw100" }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.detail || "识别失败"); } recognitionList.innerHTML = ""; for (const item of data.top || []) { const row = document.createElement("div"); row.className = "recognition-item"; const label = document.createElement("strong"); label.textContent = item.label; const track = document.createElement("div"); track.className = "confidence-track"; const fill = document.createElement("div"); fill.className = "confidence-fill"; fill.style.width = `${Math.max(0, Math.min(1, item.confidence)) * 100}%`; track.append(fill); const value = document.createElement("span"); value.className = "confidence-value"; value.textContent = `${Math.round(item.confidence * 100)}%`; row.append(label, track, value); recognitionList.append(row); } const top = data.prediction; setMessage(top ? `CNN top-1: ${top.label} · ${(top.confidence * 100).toFixed(1)}%` : "CNN 返回为空"); } catch (error) { setMessage(error.message, true); } finally { recognizeButton.disabled = false; recognizeButton.textContent = "识别当前图片"; } }); Promise.all([loadStatus(), loadClasses(), loadCnnStatus()]).catch((error) => { setMessage(error.message, true); });