DedeProGames's picture
Fix design level selection during backend readiness check
e8bbbc6 verified
Raw History Blame Contribute Delete
10.8 kB
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client@2.7.1/dist/index.min.js";
const BACKEND = "SupraLabs/supra2-img-demo";
const BACKEND_URL = "https://supralabs-supra2-img-demo.hf.space";
const STEPS = { Fast: 3, Quality: 8, "Ultra Design": 16 };
const form = document.querySelector("#generator-form");
const prompt = document.querySelector("#prompt");
const generateButton = document.querySelector("#generate");
const buttonLabel = document.querySelector("#button-label");
const guidance = document.querySelector("#guidance");
const guidanceValue = document.querySelector("#guidance-value");
const seed = document.querySelector("#seed");
const randomize = document.querySelector("#randomize");
const image = document.querySelector("#result-image");
const placeholder = document.querySelector("#placeholder");
const download = document.querySelector("#download");
const seedUsed = document.querySelector("#seed-used");
const resultState = document.querySelector("#result-state");
const status = document.querySelector("#status");
const modeLabel = document.querySelector("#selected-mode");
const modeAvailability = document.querySelector("#mode-availability");
const level = document.querySelector("#level");
const levelTrack = document.querySelector("#level-track");
const levelNodes = [...document.querySelectorAll(".level-node")];
const levelLabels = [...document.querySelectorAll(".level-labels button")];
const progressPanel = document.querySelector("#render-progress");
const progressTrack = document.querySelector("#progress-track");
const progressFill = document.querySelector("#progress-fill");
const progressPercent = document.querySelector("#progress-percent");
const progressStage = document.querySelector("#progress-stage");
const LEVELS = ["Fast", "Quality", "Ultra Design"];
let mode = "Quality";
let clientPromise;
let dragging = false;
let levelTouched = false;
let shownProgress = 0;
let displayedProgress = 0;
let progressFrame = 0;
let backendMinimumSteps = 10;
function setStatus(message, kind = "") {
status.textContent = message;
status.className = `status ${kind}`.trim();
}
function setMode(nextMode) {
if (!(nextMode in STEPS)) return;
const previous = mode;
mode = nextMode;
level.dataset.level = mode;
modeLabel.textContent = `${mode} 路 ${STEPS[mode]} steps`;
levelTrack.setAttribute("aria-valuenow", String(LEVELS.indexOf(mode)));
levelTrack.setAttribute("aria-valuetext", `${mode}, ${STEPS[mode]} steps`);
modeAvailability.textContent = STEPS[mode] < backendMinimumSteps
? `${mode} can be selected, but generation at ${STEPS[mode]} steps is awaiting the CPU service update.`
: "";
if (mode === "Ultra Design" && previous !== mode) requestAnimationFrame(playUltra);
if (mode !== "Ultra Design" && previous === "Ultra Design") cancelUltra();
}
function cancelUltra() {
level.querySelectorAll(".level-rainbow, .level-node").forEach((item) =>
item.getAnimations().forEach((animation) => { if (animation.id === "rainbow-grow") animation.cancel(); }));
}
function playUltra() {
cancelUltra();
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const rainbow = level.querySelector(".level-rainbow");
const knob = level.querySelector('.level-node[data-mode="Ultra Design"]');
const grow = rainbow.animate(
[{ width: "0%", opacity: 1 }, { width: "100%", opacity: 1 }],
{ duration: 1500, delay: 520, easing: "cubic-bezier(.45,.05,.25,1)", fill: "backwards" }
);
grow.id = "rainbow-grow";
const pulse = knob.animate(
[{ transform: "scale(1)" }, { transform: "scale(1.9)", offset: .55 }, { transform: "scale(1.65)" }],
{ duration: 520, delay: 1900, easing: "ease-out", fill: "backwards" }
);
pulse.id = "rainbow-grow";
}
function setProgress(percent, stage) {
shownProgress = Math.max(shownProgress, Math.min(100, Math.max(0, Math.round(percent))));
progressPanel.hidden = false;
progressFill.style.width = `${shownProgress}%`;
progressStage.textContent = stage;
progressTrack.setAttribute("aria-valuenow", String(shownProgress));
cancelAnimationFrame(progressFrame);
if (matchMedia("(prefers-reduced-motion: reduce)").matches) {
displayedProgress = shownProgress;
progressPercent.textContent = `${shownProgress}%`;
return;
}
const start = displayedProgress;
const target = shownProgress;
const started = performance.now();
const tick = (now) => {
const t = Math.min(1, (now - started) / 700);
displayedProgress = start + (target - start) * (1 - (1 - t) ** 3);
progressPercent.textContent = `${Math.round(displayedProgress)}%`;
if (t < 1) progressFrame = requestAnimationFrame(tick);
};
progressFrame = requestAnimationFrame(tick);
}
function pickLevel(clientX) {
const bounds = levelTrack.getBoundingClientRect();
const fraction = Math.min(1, Math.max(0, (clientX - bounds.left - 18) / (bounds.width - 36)));
setMode(LEVELS[Math.round(fraction * 2)]);
}
async function refreshAvailableModes() {
try {
const response = await fetch(`${BACKEND_URL}/config`, { cache: "no-store" });
if (!response.ok) throw new Error("CPU backend unavailable");
const config = await response.json();
const stepControl = config.components?.find((part) => part.type === "slider" && part.props?.label === "Sampling steps");
backendMinimumSteps = Number(stepControl?.props?.minimum ?? 10);
if (!levelTouched && STEPS[mode] < backendMinimumSteps) setMode("Ultra Design");
else setMode(mode);
} catch {
modeAvailability.textContent = "The CPU service is waking up. You can try generating an image shortly.";
}
}
function setBusy(busy) {
generateButton.disabled = busy;
buttonLabel.textContent = busy ? "Generating..." : "Generate image";
resultState.textContent = busy ? "Working on CPU" : "Ready to create";
}
function imageUrl(value) {
if (typeof value === "string") return value;
if (value && typeof value.url === "string") return value.url;
if (value && typeof value.path === "string") {
return new URL(`/gradio_api/file=${value.path}`, "https://supralabs-supra2-img-demo.hf.space").href;
}
throw new Error("The image service returned no image URL.");
}
for (const option of levelLabels) option.addEventListener("click", () => { levelTouched = true; setMode(option.dataset.mode); });
levelTrack.addEventListener("pointerdown", (event) => {
dragging = true;
levelTouched = true;
levelTrack.focus({ preventScroll: true });
try { levelTrack.setPointerCapture(event.pointerId); } catch { /* Pointer capture is optional. */ }
pickLevel(event.clientX);
event.preventDefault();
});
levelTrack.addEventListener("pointermove", (event) => { if (dragging) pickLevel(event.clientX); });
levelTrack.addEventListener("pointerup", () => { dragging = false; });
levelTrack.addEventListener("pointercancel", () => { dragging = false; });
levelTrack.addEventListener("keydown", (event) => {
levelTouched = true;
const index = LEVELS.indexOf(mode);
const next = event.key === "ArrowRight" || event.key === "ArrowUp" ? index + 1
: event.key === "ArrowLeft" || event.key === "ArrowDown" ? index - 1
: event.key === "Home" ? 0 : event.key === "End" ? 2 : null;
if (next !== null) { event.preventDefault(); setMode(LEVELS[Math.max(0, Math.min(2, next))]); }
});
refreshAvailableModes();
guidance.addEventListener("input", () => { guidanceValue.value = Number(guidance.value).toFixed(1); });
document.querySelectorAll("#examples button").forEach((example) => {
example.addEventListener("click", () => { prompt.value = example.textContent.trim(); prompt.focus(); });
});
form.addEventListener("submit", async (event) => {
event.preventDefault();
const description = prompt.value.trim();
if (!description) { prompt.focus(); return; }
if (STEPS[mode] < backendMinimumSteps) {
setStatus(`${mode} needs the CPU service update before it can generate at ${STEPS[mode]} steps.`, "error");
return;
}
const seedNumber = Number(seed.value);
if (!randomize.checked && (!Number.isInteger(seedNumber) || seedNumber < 0 || seedNumber > 2147483647)) {
setStatus("Choose a seed between 0 and 2147483647.", "error");
seed.focus();
return;
}
setBusy(true);
setStatus("Connecting to the CPU image service...");
shownProgress = 0;
displayedProgress = 0;
cancelAnimationFrame(progressFrame);
progressPercent.textContent = "0%";
setProgress(0, "Connecting to CPU");
try {
clientPromise ??= Client.connect(BACKEND, { events: ["status", "data"] });
const client = await clientPromise;
const job = client.submit("/generate", [description, STEPS[mode], Number(guidance.value), seedNumber, randomize.checked]);
let gotImage = false;
for await (const message of job) {
if (message.type === "status") {
if (message.stage === "pending") {
const position = Number.isInteger(message.position) ? ` 路 position ${message.position + 1}` : "";
setStatus(`Queued for CPU generation${position}`);
setProgress(0, "Waiting in queue");
} else if (message.stage === "generating") {
const progress = message.progress_data?.find((part) => part?.desc);
setStatus(progress?.desc || "Generating your image on CPU...");
let fraction = Number(progress?.progress);
if (!Number.isFinite(fraction) && Number(progress?.length) > 0) fraction = Number(progress.index) / Number(progress.length);
if (!Number.isFinite(fraction) && progress?.desc) {
const steps = progress.desc.match(/(\d+)\s*\/\s*(\d+)/);
if (steps) fraction = Number(steps[1]) / Number(steps[2]);
}
if (Number.isFinite(fraction)) setProgress(4 + 90 * Math.min(1, Math.max(0, fraction)), progress?.desc || "Rendering image");
else setProgress(4, "Preparing image");
} else if (message.stage === "error") {
throw new Error(message.message || "Image generation failed.");
}
}
if (message.type === "data" && Array.isArray(message.data)) {
const url = imageUrl(message.data[0]);
image.src = url;
image.hidden = false;
placeholder.hidden = true;
download.href = url;
download.hidden = false;
seedUsed.textContent = `Seed ${message.data[1]}`;
setProgress(100, "Image ready");
gotImage = true;
}
}
if (!gotImage) throw new Error("The image service did not return an image.");
setStatus(`${mode} complete 路 ${STEPS[mode]} steps`, "success");
} catch (error) {
console.error(error);
clientPromise = undefined;
setStatus(error instanceof Error ? error.message : "Image generation failed. Please try again.", "error");
} finally {
setBusy(false);
}
});