Qwen-Image-Flash / index.html
akhaliq's picture
akhaliq HF Staff
Strip non-functional buttons; prefill prompt; use NVIDIA logo
334b2a8
Raw
History Blame Contribute Delete
27.4 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#76b900" />
<title>Qwen-Image-Flash · 4-step Text-to-Image</title>
<style>
/* ---------- Design tokens ---------- */
:root {
--nv-green: #76b900;
--nv-green-2: #8acc2c;
--nv-green-3: #5e8f00;
--nv-green-soft: rgba(118, 185, 0, 0.18);
--nv-green-glow: rgba(118, 185, 0, 0.35);
/* Floating-card palette (dark on the image) */
--card: rgba(20, 22, 26, 0.78);
--card-strong: rgba(15, 17, 21, 0.92);
--card-border: rgba(255, 255, 255, 0.10);
--card-border-strong: rgba(255, 255, 255, 0.18);
--ink: #ffffff;
--ink-soft: rgba(255, 255, 255, 0.78);
--muted: rgba(255, 255, 255, 0.55);
/* Light fallback for the empty state */
--bg: #fafbf7;
--bg-2: #f4f5f0;
--ink-dark: #0a0a0a;
--muted-dark: #6a6e63;
--radius: 14px;
--radius-lg: 18px;
--shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.35);
--shadow-md: 0 8px 24px rgba(0, 0, 0, 0.20);
}
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
min-height: 100vh;
min-height: 100dvh;
color: var(--ink-dark);
background: var(--bg);
font-family: "Inter", "Helvetica Neue", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.5;
overflow-x: hidden;
}
/* ---------- Stage (full-bleed image / placeholder) ---------- */
.stage {
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
height: 100dvh;
overflow: hidden;
background: var(--bg);
}
/* Result image fills the stage. Object-fit:contain so 1024² isn't cropped
on a phone, but max-width/height keep it crisp. */
.result-img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: contain;
display: none;
animation: rise 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.result-img.show { display: block; }
@keyframes rise { from { opacity: 0; transform: scale(0.985); } }
/* Placeholder: subtle geometric background until first generation */
.placeholder {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background:
linear-gradient(60deg, transparent 49.5%, rgba(118, 185, 0, 0.05) 49.5%, rgba(118, 185, 0, 0.05) 50.5%, transparent 50.5%) 0 0 / 28px 48px,
linear-gradient(-60deg, transparent 49.5%, rgba(118, 185, 0, 0.05) 49.5%, rgba(118, 185, 0, 0.05) 50.5%, transparent 50.5%) 0 0 / 28px 48px,
radial-gradient(circle at 50% 35%, rgba(118, 185, 0, 0.10), transparent 60%),
var(--bg);
}
.placeholder-inner {
text-align: center;
color: var(--muted-dark);
max-width: 480px;
padding: 24px;
}
.placeholder-inner .emoji {
font-size: 64px;
display: block;
margin-bottom: 12px;
filter: saturate(1.1);
}
.placeholder-inner h2 {
font-size: 22px;
font-weight: 700;
color: var(--ink-dark);
margin: 0 0 6px;
letter-spacing: -0.01em;
}
.placeholder-inner p {
margin: 0;
font-size: 14.5px;
color: var(--muted-dark);
}
.placeholder-inner p b { color: var(--ink-dark); font-weight: 600; }
/* Loading overlay: dimmed stage with a centered pulse + status */
.loading-overlay {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 18px;
background:
radial-gradient(circle at 50% 50%, rgba(0,0,0,0.25), rgba(0,0,0,0.55) 70%);
color: #fff;
backdrop-filter: blur(2px);
-webkit-backdrop-filter: blur(2px);
padding: 24px;
text-align: center;
}
.loading-overlay.show { display: flex; }
.ring {
width: 56px; height: 56px;
border: 3px solid rgba(255, 255, 255, 0.18);
border-top-color: var(--nv-green);
border-radius: 50%;
animation: spin 0.85s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.load-text { font: 600 15px / 1.5 inherit; max-width: 320px; }
.load-text .sub {
display: block;
font-weight: 400;
color: rgba(255, 255, 255, 0.65);
margin-top: 4px;
font-size: 13px;
}
.progress-line {
width: 220px; max-width: 60vw; height: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.15);
overflow: hidden;
}
.progress-fill {
height: 100%; width: 0%;
background: linear-gradient(90deg, var(--nv-green-3), var(--nv-green));
transition: width 0.3s ease;
}
/* ---------- Floating prompt card (bottom-left) ---------- */
.prompt-card {
position: absolute;
left: 24px; bottom: 24px;
max-width: min(560px, calc(100% - 48px));
width: calc(100% - 48px);
background: var(--card-strong);
backdrop-filter: blur(20px) saturate(160%);
-webkit-backdrop-filter: blur(20px) saturate(160%);
border: 1px solid var(--card-border-strong);
border-radius: var(--radius-lg);
padding: 14px;
box-shadow: var(--shadow-lg);
color: var(--ink);
}
.prompt-card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 8px;
}
.prompt-card-head .label {
font: 700 12px / 1 inherit;
color: var(--ink);
letter-spacing: 0.04em;
text-transform: uppercase;
}
.prompt-card-head .nvidia-logo {
width: 22px; height: 22px;
border-radius: 5px;
object-fit: cover;
background: #fff;
padding: 2px;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);
}
.prompt-input {
width: 100%;
background: rgba(255, 255, 255, 0.06);
border: 1.5px solid var(--card-border);
border-radius: 10px;
color: var(--ink);
padding: 11px 13px;
font: 400 14.5px / 1.5 inherit;
resize: none;
min-height: 44px;
max-height: 140px;
transition: border-color 0.15s, background 0.15s;
-webkit-appearance: none;
}
.prompt-input:focus {
outline: none;
border-color: var(--nv-green);
background: rgba(255, 255, 255, 0.10);
}
.prompt-input::placeholder { color: var(--muted); }
.prompt-actions {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
}
.prompt-actions .spacer { flex: 1; }
/* Primary Generate button */
.gen-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0 18px;
height: 38px;
border-radius: 10px;
border: none;
background: var(--nv-green);
color: #0a0a0a;
font: 700 14px / 1 inherit;
letter-spacing: 0.01em;
cursor: pointer;
transition: filter 0.15s, transform 0.08s, box-shadow 0.15s;
box-shadow: 0 4px 14px rgba(118, 185, 0, 0.35);
-webkit-tap-highlight-color: transparent;
}
.gen-btn:hover:not(:disabled) { filter: brightness(1.08); box-shadow: 0 6px 18px rgba(118, 185, 0, 0.45); }
.gen-btn:active:not(:disabled) { transform: translateY(1px); }
.gen-btn:disabled { cursor: wait; opacity: 0.65; box-shadow: none; }
.gen-btn .spinner {
width: 14px; height: 14px;
border: 2px solid rgba(10, 10, 10, 0.25);
border-top-color: #0a0a0a;
border-radius: 50%;
animation: spin 0.7s linear infinite;
display: none;
}
.gen-btn.loading .spinner { display: inline-block; }
/* ---------- Floating toolbar (bottom-right): examples + download + stop ---------- */
.fab-stack {
position: absolute;
right: 24px; bottom: 24px;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 10px;
}
/* Subtle model info link (top-right corner) */
.info-link {
position: absolute;
top: 18px; right: 18px;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px 6px 8px;
border-radius: 999px;
background: var(--card);
backdrop-filter: blur(12px) saturate(140%);
-webkit-backdrop-filter: blur(12px) saturate(140%);
border: 1px solid var(--card-border);
color: var(--ink-soft);
font: 600 12px / 1 inherit;
letter-spacing: 0.01em;
text-decoration: none;
transition: border-color 0.15s, background 0.15s, color 0.15s;
max-width: calc(100% - 36px);
}
.info-link:hover { border-color: var(--nv-green); background: var(--card-strong); color: var(--ink); }
.info-mark {
width: 18px; height: 18px;
border-radius: 4px;
object-fit: cover;
background: #fff;
padding: 1px;
flex-shrink: 0;
}
.info-link .info-meta {
color: var(--muted);
font-weight: 500;
margin-left: 2px;
}
.info-link:hover .info-meta { color: var(--ink-soft); }
@media (max-width: 768px) {
.info-link { top: 12px; right: 12px; font-size: 11.5px; padding: 5px 10px 5px 7px; }
}
.fab {
width: 44px; height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 14px;
background: var(--card-strong);
backdrop-filter: blur(14px) saturate(140%);
-webkit-backdrop-filter: blur(14px) saturate(140%);
border: 1px solid var(--card-border);
color: var(--ink);
cursor: pointer;
text-decoration: none;
box-shadow: var(--shadow-md);
transition: border-color 0.15s, background 0.15s, color 0.15s;
-webkit-tap-highlight-color: transparent;
}
.fab:hover { border-color: var(--nv-green); }
.fab:active { transform: translateY(1px); }
.fab svg { width: 20px; height: 20px; display: block; }
.fab.primary {
background: var(--nv-green);
border-color: var(--nv-green);
color: #0a0a0a;
box-shadow: 0 6px 20px rgba(118, 185, 0, 0.4);
}
.fab.primary:hover { filter: brightness(1.08); }
.fab.show { display: inline-flex; }
.fab[hidden] { display: none; }
/* ---------- Popover sheets (examples, advanced, status) ---------- */
.sheet {
position: absolute;
left: 24px; bottom: 24px;
max-width: min(560px, calc(100% - 48px));
width: calc(100% - 48px);
background: var(--card-strong);
backdrop-filter: blur(20px) saturate(160%);
-webkit-backdrop-filter: blur(20px) saturate(160%);
border: 1px solid var(--card-border-strong);
border-radius: var(--radius-lg);
padding: 16px;
box-shadow: var(--shadow-lg);
color: var(--ink);
display: none;
animation: sheet-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.sheet.show { display: block; }
@keyframes sheet-in { from { opacity: 0; transform: translateY(8px); } }
.sheet-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.sheet-head .title {
font: 700 13px / 1 inherit;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--ink);
}
.sheet-close {
width: 28px; height: 28px;
display: inline-flex; align-items: center; justify-content: center;
border-radius: 8px;
border: 1px solid var(--card-border);
background: rgba(255, 255, 255, 0.04);
color: var(--ink-soft);
cursor: pointer;
}
.sheet-close:hover { color: var(--ink); border-color: var(--card-border-strong); }
/* Example chips in the sheet */
.examples-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.examples-grid .chip {
text-align: left;
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--card-border);
color: var(--ink-soft);
border-radius: 10px;
padding: 10px 12px;
font: 400 12.5px / 1.45 inherit;
cursor: pointer;
transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.examples-grid .chip:hover { border-color: var(--nv-green); background: var(--nv-green-soft); color: var(--ink); }
/* Advanced sheet: rows of sliders/inputs */
.row {
display: flex;
gap: 12px;
align-items: center;
}
.row > .col { flex: 1; min-width: 0; }
.field-label {
font: 500 11px / 1 inherit;
color: var(--muted);
letter-spacing: 0.04em;
text-transform: uppercase;
margin-bottom: 6px;
}
.slider-row {
display: flex;
align-items: center;
gap: 10px;
}
.slider-val {
font-variant-numeric: tabular-nums;
font: 700 13px / 1 inherit;
color: var(--ink);
min-width: 48px;
text-align: right;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
flex: 1; min-width: 0;
height: 6px;
border-radius: 999px;
background: linear-gradient(90deg, var(--nv-green) 0%, var(--nv-green) var(--fill, 50%), rgba(255, 255, 255, 0.10) var(--fill, 50%), rgba(255, 255, 255, 0.10) 100%);
cursor: pointer;
padding: 0;
border: none;
}
input[type="range"]:focus { outline: none; }
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px; height: 20px;
border-radius: 50%;
background: #fff;
border: 3px solid var(--nv-green);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
cursor: grab;
}
input[type="range"]::-moz-range-thumb {
width: 20px; height: 20px;
border-radius: 50%;
background: #fff;
border: 3px solid var(--nv-green);
cursor: grab;
}
input[type="text"], input[type="number"], textarea {
width: 100%;
background: rgba(255, 255, 255, 0.06);
border: 1.5px solid var(--card-border);
border-radius: 10px;
color: var(--ink);
padding: 10px 12px;
font: 400 14px / 1.5 inherit;
transition: border-color 0.15s, background 0.15s;
-webkit-appearance: none;
}
input[type="text"]:focus, input[type="number"]:focus, textarea:focus {
outline: none;
border-color: var(--nv-green);
background: rgba(255, 255, 255, 0.10);
}
textarea { resize: vertical; min-height: 60px; }
.check-pill {
display: inline-flex; align-items: center; gap: 8px;
padding: 8px 12px;
border-radius: 999px;
border: 1px solid var(--card-border);
background: rgba(255, 255, 255, 0.04);
color: var(--ink-soft);
font: 500 13px / 1 inherit;
cursor: pointer;
user-select: none;
}
.check-pill input { accent-color: var(--nv-green); width: 14px; height: 14px; }
.check-pill:hover { border-color: var(--card-border-strong); color: var(--ink); }
/* ---------- Status / error toast ---------- */
.status-toast {
position: absolute;
left: 50%;
top: 18px;
transform: translateX(-50%);
display: inline-flex;
align-items: center;
gap: 10px;
padding: 9px 14px;
border-radius: 999px;
background: var(--card-strong);
backdrop-filter: blur(14px) saturate(140%);
-webkit-backdrop-filter: blur(14px) saturate(140%);
border: 1px solid var(--card-border);
color: var(--ink);
font: 600 13px / 1 inherit;
box-shadow: var(--shadow-md);
max-width: calc(100% - 36px);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.status-toast.show { display: inline-flex; }
.status-toast.err { border-color: #c8362a; color: #ff8b8b; }
.status-toast .pip {
width: 8px; height: 8px; border-radius: 50%;
background: var(--nv-green);
box-shadow: 0 0 8px var(--nv-green-glow);
flex-shrink: 0;
}
.status-toast.busy .pip { animation: pulse 1.1s infinite; }
.status-toast.err .pip { background: #ff5d5d; box-shadow: 0 0 8px rgba(255, 93, 93, 0.5); }
.status-toast.ok .pip { background: var(--nv-green); }
@keyframes pulse { 50% { opacity: 0.45; } }
/* ---------- Mobile ---------- */
@media (max-width: 768px) {
.prompt-card, .sheet {
left: 12px; right: 12px; bottom: 12px;
max-width: none;
width: auto;
}
.fab-stack { right: 12px; bottom: 12px; }
.examples-grid { grid-template-columns: 1fr; } /* stack chips vertically */
}
@media (max-width: 420px) {
.prompt-card { padding: 12px; }
.gen-btn { padding: 0 14px; height: 36px; font-size: 13.5px; }
.fab { width: 42px; height: 42px; border-radius: 12px; }
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}
</style>
</head>
<body>
<main class="stage" aria-label="Generated image stage">
<!-- Result image (shown after first generation) -->
<img class="result-img" id="resultImg" alt="Generated image" />
<!-- Empty-state placeholder -->
<div class="placeholder" id="placeholder">
<div class="placeholder-inner">
<span class="emoji" aria-hidden="true">🐢</span>
<h2>Generate at the speed of thought.</h2>
<p>Four-step text-to-image from <b>nvidia/Qwen-Image-Flash</b>. Type a prompt, hit <b>Generate</b>, and the image fills this space.</p>
</div>
</div>
<!-- Loading overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="ring"></div>
<div class="load-text" id="loadText">
Queued — waiting for a GPU…
<span class="sub" id="loadSub">First run after build can take a few minutes to warm up CUDA.</span>
</div>
<div class="progress-line"><div class="progress-fill" id="progressFill"></div></div>
</div>
<!-- Status toast (errors + brief status) -->
<div class="status-toast" id="statusToast" role="status" aria-live="polite" hidden>
<span class="pip"></span>
<span class="text" id="statusText">Ready</span>
</div>
<!-- Floating prompt card -->
<section class="prompt-card" id="promptCard" aria-label="Prompt">
<div class="prompt-card-head">
<img class="nvidia-logo"
src="https://cdn-avatars.huggingface.co/v1/production/uploads/65df9200dc3292a8983e5017/Vs5FPVCH-VZBipV3qKTuy.png"
alt="NVIDIA"
draggable="false" />
<span class="label">Qwen-Image-Flash</span>
</div>
<textarea
id="prompt"
class="prompt-input"
rows="2"
autofocus>A red fox in a snowy pine forest at golden hour, photorealistic, sharp focus, soft bokeh</textarea>
<div class="prompt-actions">
<span class="spacer"></span>
<button class="gen-btn" id="generateBtn" aria-label="Generate">
<span class="spinner"></span>
<span class="label">Generate</span>
</button>
</div>
</section>
<!-- Examples sheet (auto-opens on first visit; click any chip to load it) -->
<section class="sheet" id="examplesSheet" aria-label="Example prompts" hidden>
<div class="sheet-head">
<span class="title">Try a prompt</span>
<button class="sheet-close" data-close="examplesSheet" aria-label="Close">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
</div>
<div class="examples-grid">
<button class="chip">A red fox in a snowy pine forest at golden hour, photorealistic, sharp focus, soft bokeh</button>
<button class="chip">A neon-lit cyberpunk street market at night, rain reflecting on wet pavement, cinematic</button>
<button class="chip">A watercolor painting of a koi fish swimming among lotus flowers, soft pastel palette</button>
<button class="chip">A steaming bowl of ramen, top-down flat lay, chopsticks resting on the side, moody lighting</button>
<button class="chip">A lone astronaut standing on a dune under two moons, vast alien desert, wide angle</button>
<button class="chip">A vintage botanical illustration of exotic orchids, ink and watercolor, parchment background</button>
</div>
</section>
<!-- Floating download button (bottom-right) -->
<div class="fab-stack" aria-label="Actions">
<a class="fab" id="downloadFab" href="#" download="qwen-image-flash.png" aria-label="Download PNG" hidden>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/>
</svg>
</a>
</div>
<!-- Subtle bottom-right info link (text, not a button) -->
<a class="info-link" href="https://huggingface.co/nvidia/Qwen-Image-Flash" target="_blank" rel="noopener">
<img class="info-mark"
src="https://cdn-avatars.huggingface.co/v1/production/uploads/65df9200dc3292a8983e5017/Vs5FPVCH-VZBipV3qKTuy.png"
alt=""
aria-hidden="true"
draggable="false" />
<span><b style="color: var(--ink);">nvidia/Qwen-Image-Flash</b></span>
<span class="info-meta">20B params · 4-step · DMD2 · Apache-2.0</span>
</a>
</main>
<script type="module">
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
const $ = (id) => document.getElementById(id);
const promptEl = $("prompt"),
generateBtn = $("generateBtn"),
examplesSheet = $("examplesSheet"),
placeholder = $("placeholder"),
loadingOverlay = $("loadingOverlay"),
resultImg = $("resultImg"),
downloadFab = $("downloadFab"),
statusToast = $("statusToast"),
statusText = $("statusText"),
loadText = $("loadText"),
loadSub = $("loadSub"),
progressFill = $("progressFill"),
promptCard = $("promptCard");
// ---- Defaults (advanced controls removed; backend always gets tested settings) ----
const DEFAULTS = {
negative_prompt: "",
width: 1024,
height: 1024,
num_inference_steps: 4,
randomize_seed: true,
};
let lastUsedSeed = 42;
// ---- Examples sheet (auto-opens on first visit) ----
const openExamples = () => { examplesSheet.hidden = false; };
const closeExamples = () => { examplesSheet.hidden = true; };
openExamples();
document.querySelectorAll("#examplesSheet .chip").forEach((chip) => {
chip.addEventListener("click", () => {
promptEl.value = chip.textContent;
closeExamples();
promptEl.focus();
});
});
document.querySelectorAll("[data-close]").forEach((btn) => {
btn.addEventListener("click", () => { $(btn.dataset.close).hidden = true; });
});
// Esc closes the examples sheet
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeExamples();
});
// ---- Client ----
let client = null;
async function getClient() {
if (!client) client = await Client.connect(window.location.origin);
return client;
}
function setStatus(state, text) {
statusToast.classList.remove("busy", "ok", "err");
if (state === "busy") statusToast.classList.add("busy");
if (state === "ok") statusToast.classList.add("ok");
if (state === "err") statusToast.classList.add("err");
statusText.textContent = text;
statusToast.hidden = false;
}
function clearStatus() { statusToast.hidden = true; }
async function generate() {
const p = promptEl.value.trim();
if (!p) {
setStatus("err", "Prompt cannot be empty.");
promptEl.focus();
openExamples(); // re-show examples so the user has a starting point
return;
}
closeExamples();
generateBtn.classList.add("loading");
generateBtn.disabled = true;
placeholder.style.display = "none";
resultImg.classList.remove("show");
downloadFab.hidden = true;
loadingOverlay.classList.add("show");
progressFill.style.width = "6%";
setStatus("busy", "Connecting to backend…");
loadSub.textContent = "First run after build can take a few minutes to warm up CUDA.";
try {
const c = await getClient();
loadText.firstChild.textContent = "Queued — generating image (4 steps)… ";
progressFill.style.width = "35%";
const result = await c.predict("/generate_image", {
prompt: p,
negative_prompt: DEFAULTS.negative_prompt,
width: DEFAULTS.width,
height: DEFAULTS.height,
num_inference_steps: DEFAULTS.num_inference_steps,
seed: DEFAULTS.randomize_seed ? Math.floor(Math.random() * 2147483647) : lastUsedSeed,
randomize_seed: DEFAULTS.randomize_seed,
});
progressFill.style.width = "100%";
const [fileData, usedSeed, info] = result.data;
if (usedSeed !== undefined && usedSeed !== null) {
lastUsedSeed = usedSeed;
}
const url = (fileData && (fileData.url || (fileData.path ? window.location.origin + "/file=" + fileData.path : null))) || null;
if (!url) throw new Error("No image returned from backend.");
resultImg.src = url;
resultImg.onload = () => {
loadingOverlay.classList.remove("show");
resultImg.classList.add("show");
downloadFab.href = url;
downloadFab.hidden = false;
setStatus("ok", info || `Done · seed ${usedSeed ?? ""}`);
setTimeout(() => { if (statusToast.classList.contains("ok")) clearStatus(); }, 4000);
};
} catch (err) {
console.error(err);
loadingOverlay.classList.remove("show");
progressFill.style.width = "0%";
placeholder.style.display = "flex";
setStatus("err", err?.message?.replace(/^Error: ?/, "") || "Something went wrong.");
} finally {
generateBtn.classList.remove("loading");
generateBtn.disabled = false;
}
}
generateBtn.addEventListener("click", generate);
promptEl.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") generate();
if (e.key === "Enter" && !e.shiftKey) {
// Submit on plain Enter for a quick "tap → image" feel
e.preventDefault();
generate();
}
});
// ---- Auto-grow the prompt textarea as the user types ----
const autoGrow = () => {
promptEl.style.height = "auto";
promptEl.style.height = Math.min(promptEl.scrollHeight, 140) + "px";
};
promptEl.addEventListener("input", autoGrow);
autoGrow();
</script>
</body>
</html>