| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8" /> |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> |
| <meta name="color-scheme" content="dark" /> |
| <title>AIDMA Studio</title> |
| <style> |
| :root{ |
| --bg:#070910;--panel:#111522;--panel2:#0b0e18;--line:rgba(255,255,255,.09); |
| --text:#f6f7fb;--muted:#9aa3b8;--purple:#8b6cff;--mint:#4ce6ce;--red:#ff6c82; |
| --shadow:0 30px 90px rgba(0,0,0,.42) |
| } |
| *{box-sizing:border-box} |
| body{ |
| margin:0;min-height:100vh;color:var(--text); |
| font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif; |
| background: |
| radial-gradient(circle at 12% 5%,rgba(139,108,255,.20),transparent 29%), |
| radial-gradient(circle at 90% 12%,rgba(76,230,206,.13),transparent 25%), |
| linear-gradient(180deg,#0b0e18 0%,var(--bg) 62%); |
| } |
| .shell{width:min(1160px,calc(100% - 28px));margin:auto;padding:28px 0 45px} |
| header{display:flex;align-items:center;justify-content:space-between;gap:18px;margin-bottom:22px} |
| .brand{display:flex;align-items:center;gap:13px} |
| .logo{ |
| width:48px;height:48px;border-radius:16px;display:grid;place-items:center; |
| font-weight:950;color:#060910;background:linear-gradient(135deg,var(--purple),var(--mint)); |
| box-shadow:0 16px 38px rgba(139,108,255,.28) |
| } |
| h1{font-size:clamp(25px,4vw,37px);letter-spacing:-1.3px;margin:0} |
| .sub{font-size:13px;color:var(--muted);margin-top:4px} |
| .tag{font-size:12px;color:#cfd4e4;border:1px solid var(--line);border-radius:999px;padding:8px 11px;background:rgba(255,255,255,.035)} |
| .grid{display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);gap:19px} |
| .panel{background:rgba(17,21,34,.83);border:1px solid var(--line);border-radius:24px;box-shadow:var(--shadow);backdrop-filter:blur(18px)} |
| .controls{padding:21px} |
| .viewer{padding:13px;min-height:625px;display:flex;flex-direction:column} |
| label{display:block;margin:0 0 8px;font-size:11px;font-weight:800;letter-spacing:.09em;text-transform:uppercase;color:#bcc3d4} |
| .field{margin-bottom:16px} |
| textarea,select,input{ |
| width:100%;border:1px solid var(--line);background:var(--panel2);color:var(--text); |
| border-radius:14px;outline:none;font:inherit;transition:.18s ease |
| } |
| textarea{min-height:168px;resize:vertical;padding:14px;line-height:1.52} |
| select,input{height:48px;padding:0 13px} |
| textarea:focus,select:focus,input:focus{border-color:rgba(139,108,255,.9);box-shadow:0 0 0 4px rgba(139,108,255,.12)} |
| .row{display:grid;grid-template-columns:1fr 1fr;gap:11px} |
| .boost{display:flex;justify-content:space-between;align-items:center;gap:12px;margin:0 1px 17px;color:#d4d9e7;font-size:13px} |
| .boost input{width:18px;height:18px;accent-color:var(--purple)} |
| button{ |
| width:100%;height:53px;border:0;border-radius:16px;cursor:pointer;color:#07100e; |
| font:850 15px Inter,system-ui;background:linear-gradient(135deg,var(--purple),var(--mint)); |
| box-shadow:0 17px 38px rgba(139,108,255,.24);transition:.18s ease |
| } |
| button:hover{transform:translateY(-1px);filter:brightness(1.05)} |
| button:disabled{cursor:not-allowed;opacity:.58;transform:none} |
| .fine{font-size:11px;line-height:1.5;color:#757f95;margin:13px 2px 0} |
| .canvas{ |
| position:relative;display:grid;place-items:center;flex:1;min-height:520px;overflow:hidden; |
| border-radius:18px;border:1px solid var(--line); |
| background: |
| linear-gradient(45deg,rgba(255,255,255,.026) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.026) 75%), |
| linear-gradient(45deg,rgba(255,255,255,.026) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.026) 75%),#090c15; |
| background-position:0 0,14px 14px;background-size:28px 28px |
| } |
| #image{display:none;width:100%;height:100%;object-fit:contain;background:#080a10} |
| .empty,.loading{text-align:center;color:var(--muted);padding:30px;max-width:470px} |
| .orb{width:73px;height:73px;margin:0 auto 17px;border-radius:25px;display:grid;place-items:center;font-size:29px;background:linear-gradient(135deg,rgba(139,108,255,.28),rgba(76,230,206,.19));border:1px solid rgba(255,255,255,.1)} |
| .loading{display:none} |
| .spinner{width:43px;height:43px;margin:0 auto 14px;border-radius:50%;border:3px solid rgba(255,255,255,.1);border-top-color:var(--mint);animation:spin .8s linear infinite} |
| @keyframes spin{to{transform:rotate(360deg)}} |
| .bar{display:flex;align-items:center;justify-content:space-between;gap:13px;padding:13px 4px 1px;min-height:48px;color:var(--muted);font-size:13px} |
| #status{overflow-wrap:anywhere} |
| #download{display:none;text-decoration:none;color:#06110e;background:var(--mint);border-radius:11px;padding:9px 13px;font-weight:850;white-space:nowrap} |
| .queuebox{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px} |
| .qchip{font-size:11px;border:1px solid var(--line);border-radius:999px;padding:7px 10px;color:#bcc4d5;background:rgba(255,255,255,.025)} |
| .error{color:var(--red)!important} |
| @media(max-width:900px){.grid{grid-template-columns:1fr}.viewer{min-height:520px}header{align-items:flex-start;flex-direction:column}} |
| @media(max-width:520px){.shell{width:calc(100% - 18px);padding-top:17px}.panel{border-radius:19px}.controls{padding:16px}.viewer{padding:9px}.row{grid-template-columns:1fr}} |
| </style> |
| </head> |
| <body> |
| <main class="shell"> |
| <header> |
| <div class="brand"> |
| <div class="logo">A6</div> |
| <div> |
| <h1>AIDMA Studio</h1> |
| <div class="sub">Static HTML interface · AIDMA-only remote generation</div> |
| </div> |
| </div> |
| <div class="tag">Fresh automatic seed every time</div> |
| </header> |
|
|
| <section class="grid"> |
| <div class="panel controls"> |
| <div class="field"> |
| <label for="model">AIDMA model</label> |
| <select id="model"> |
| <option value="Keltezaa/midjourney-v6-1-meets-flux-sdxl" data-trigger="(aidmaMJ6.1)"> |
| AIDMA MJ6.1 · FLUX v0.4 |
| </option> |
| <option value="demo001s/MidjourneyMeetsFlux" data-trigger="aidmaMJ6.1"> |
| AIDMA MJ6.1 · FLUX v0.5 Experimental |
| </option> |
| </select> |
| </div> |
|
|
| <div class="field"> |
| <label for="prompt">Prompt</label> |
| <textarea id="prompt" maxlength="5000" placeholder="Describe the subject, environment, camera, lighting, mood and composition..."></textarea> |
| </div> |
|
|
| <div class="field"> |
| <label for="style">Style</label> |
| <input id="style" list="styles" value="Cinematic" maxlength="300" placeholder="Choose or type any style" /> |
| <datalist id="styles"></datalist> |
| </div> |
|
|
| <div class="row"> |
| <div class="field"> |
| <label for="ratio">Aspect ratio</label> |
| <select id="ratio"> |
| <option value="1024x1024">1:1</option> |
| <option value="1280x720" selected>16:9</option> |
| <option value="720x1280">9:16</option> |
| <option value="1152x864">4:3</option> |
| <option value="864x1152">3:4</option> |
| <option value="1216x832">3:2</option> |
| <option value="832x1216">2:3</option> |
| </select> |
| </div> |
| <div class="field"> |
| <label for="quality">Quality</label> |
| <select id="quality"> |
| <option value="20">Fast</option> |
| <option value="28" selected>Balanced</option> |
| <option value="40">Ultra</option> |
| </select> |
| </div> |
| </div> |
|
|
| <div class="boost"> |
| <span>Professional prompt boost</span> |
| <input id="boost" type="checkbox" checked /> |
| </div> |
|
|
| <button id="generate">Add to private browser queue</button> |
|
|
| <div class="queuebox"> |
| <span class="qchip" id="queuedChip">Queued: 0</span> |
| <span class="qchip" id="activeChip">Active: none</span> |
| </div> |
|
|
| <div class="fine"> |
| This Static Space keeps its own FIFO queue inside your browser tab. The remote GPU is provided by the connected public Hugging Face generation Space. |
| </div> |
| </div> |
|
|
| <div class="panel viewer"> |
| <div class="canvas"> |
| <div class="empty" id="empty"> |
| <div class="orb">✦</div> |
| <strong>Your next frame begins here.</strong> |
| <div style="margin-top:8px">Write a prompt and add it to your queue.</div> |
| </div> |
| <div class="loading" id="loading"> |
| <div class="spinner"></div> |
| <strong id="loadingTitle">Connecting to the AIDMA engine…</strong> |
| <div id="queueStatus" style="margin-top:8px">Preparing request</div> |
| </div> |
| <img id="image" alt="Generated image" /> |
| </div> |
| <div class="bar"> |
| <div id="status">Ready.</div> |
| <a id="download" download="aidma-image.png">Download</a> |
| </div> |
| </div> |
| </section> |
| </main> |
|
|
| <script type="module"> |
| import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/+esm"; |
| |
| |
| |
| |
| |
| |
| const BACKEND_SPACE = "multimodalart/flux-lora-the-explorer"; |
| const BACKEND_ORIGIN = "https://multimodalart-flux-lora-the-explorer.hf.space"; |
| |
| const $ = id => document.getElementById(id); |
| const localQueue = []; |
| let processing = false; |
| let client = null; |
| let api = null; |
| let jobCounter = 0; |
| |
| const STYLES = [ |
| "No extra style","Photorealistic","Ultra realistic photography","Cinematic", |
| "Hollywood blockbuster","IMAX film still","35mm analog film","70mm epic cinema", |
| "Documentary photography","Fashion editorial","Luxury fashion campaign", |
| "Studio portrait","Environmental portrait","Street photography","Sports photography", |
| "Wildlife photography","Macro photography","Aerial photography","Drone photography", |
| "Underwater photography","Astrophotography","Architectural photography", |
| "Interior design photography","Product photography","Luxury product advertising", |
| "Food photography","Automotive photography","Concert photography","Wedding photography", |
| "Photojournalism","Vintage photography","Polaroid","Kodachrome","Technicolor", |
| "Film noir","Neo-noir","Black and white fine art","High-key lighting","Low-key lighting", |
| "Golden hour","Blue hour","Soft diffused light","Dramatic chiaroscuro", |
| "Volumetric lighting","Neon lighting","Bioluminescent lighting","Cyberpunk", |
| "Solarpunk","Steampunk","Dieselpunk","Atompunk","Cassette futurism","Retro futurism", |
| "Y2K futurism","Sci-fi concept art","Space opera","Hard science fiction","Alien world", |
| "Dystopian","Utopian","Post-apocalyptic","Fantasy concept art","Dark fantasy", |
| "High fantasy","Epic fantasy","Mythological","Medieval","Renaissance","Baroque", |
| "Rococo","Victorian","Art Nouveau","Art Deco","Bauhaus","Brutalism","Minimalism", |
| "Maximalism","Surrealism","Dreamlike surrealism","Magical realism","Expressionism", |
| "Impressionism","Post-impressionism","Romanticism","Symbolism","Cubism","Futurism", |
| "Dada","Pop art","Op art","Abstract art","Geometric abstraction","Psychedelic art", |
| "Visionary art","Graffiti","Street art","Ink illustration","Pen and ink", |
| "Graphite drawing","Charcoal drawing","Colored pencil","Pastel drawing","Watercolor", |
| "Gouache","Oil painting","Acrylic painting","Impasto painting","Digital painting", |
| "Matte painting","Concept art","Key art","Book cover illustration", |
| "Editorial illustration","Children's book illustration","Comic book art", |
| "Graphic novel","Manga","Anime","90s anime","Retro anime","Cel animation", |
| "Stop motion","Claymation","Paper cutout","Origami art","Low-poly 3D", |
| "Stylized 3D","Photorealistic 3D render","Unreal Engine cinematic","Octane render", |
| "Ray-traced CGI","Isometric 3D","Voxel art","Pixel art","16-bit game art", |
| "8-bit game art","PS1 game aesthetic","Retro arcade","Game concept art", |
| "Fantasy game splash art","Trading card art","Holographic art","Chrome metallic", |
| "Liquid metal","Crystal sculpture","Porcelain sculpture","Marble sculpture", |
| "Bronze sculpture","Wood carving","Embroidery","Tapestry","Mosaic","Stained glass", |
| "Paper collage","Mixed media","Blueprint","Technical drawing","Scientific illustration", |
| "Botanical illustration","Anatomical illustration","Logo design","Poster design", |
| "Album cover","Magazine cover","Movie poster","Luxury branding","Minimalist advertising", |
| "Japanese ukiyo-e","Chinese ink wash","Persian miniature","Islamic geometric art", |
| "Ancient Egyptian mural","Greek pottery art","Roman mosaic","Celtic art", |
| "Nordic folk art","Mexican folk art","Indian miniature painting","Arabesque ornament", |
| "Gothic cathedral art","Dark academia","Light academia","Cottagecore","Fairycore", |
| "Royalcore","Dreamcore","Weirdcore","Liminal space","Vaporwave","Synthwave", |
| "Retrowave","Lo-fi aesthetic","Grunge","Heavy metal album art","Elegant luxury", |
| "Clean commercial","Hyper-detailed","Ethereal","Whimsical","Moody","Haunting", |
| "Cozy","Playful","Epic" |
| ]; |
| |
| const STYLE_DETAIL = { |
| "No extra style":"", |
| "Photorealistic":"photorealistic, realistic materials, physically accurate lighting, natural texture", |
| "Ultra realistic photography":"ultra realistic professional photography, true-to-life optics, natural micro-texture", |
| "Cinematic":"cinematic composition, dramatic motivated lighting, filmic depth, refined color grading", |
| "Hollywood blockbuster":"large-scale blockbuster frame, premium production design, dynamic cinematic staging", |
| "IMAX film still":"IMAX film still, monumental scale, crisp cinematic detail, natural film contrast", |
| "35mm analog film":"35mm analog film, organic grain, subtle halation, authentic lens character", |
| "Fashion editorial":"high-fashion editorial, deliberate styling, premium magazine art direction", |
| "Luxury fashion campaign":"luxury fashion campaign, immaculate art direction, expensive materials", |
| "Studio portrait":"professional studio portrait, controlled key and fill lighting, clean separation", |
| "Product photography":"premium product photography, precise reflections, clean geometry, commercial detail", |
| "Luxury product advertising":"luxury advertising campaign, sculpted highlights, polished surfaces, elegant negative space", |
| "Film noir":"film noir, hard directional light, deep shadows, smoky suspenseful atmosphere", |
| "Neo-noir":"neo-noir, modern darkness, colored practical lights, tense cinematic mood", |
| "Volumetric lighting":"volumetric light shafts, atmospheric depth, visible light diffusion", |
| "Cyberpunk":"cyberpunk city aesthetic, neon infrastructure, rain-slick surfaces, dense futuristic detail", |
| "Solarpunk":"solarpunk, optimistic ecological technology, lush greenery, sustainable architecture", |
| "Steampunk":"steampunk, intricate brass machinery, Victorian engineering, warm industrial atmosphere", |
| "Sci-fi concept art":"high-end science-fiction concept art, believable technology, cinematic scale", |
| "Space opera":"epic space opera, colossal celestial scale, ornate futuristic design", |
| "Fantasy concept art":"premium fantasy concept art, rich worldbuilding, dramatic atmosphere", |
| "Dark fantasy":"dark fantasy, ominous mythology, textured shadows, ancient atmosphere", |
| "High fantasy":"high fantasy, majestic worldbuilding, luminous magic, heroic composition", |
| "Oil painting":"museum-quality oil painting, layered brushwork, rich pigments, painterly light", |
| "Watercolor":"expressive watercolor, translucent washes, granulation, soft edge variation", |
| "Digital painting":"high-end digital painting, confident brushwork, cinematic rendering", |
| "Anime":"premium anime illustration, expressive character design, clean linework, cinematic cel shading", |
| "Manga":"detailed manga artwork, confident ink lines, dynamic composition", |
| "90s anime":"1990s anime aesthetic, hand-painted cel look, nostalgic color palette", |
| "Photorealistic 3D render":"photorealistic 3D render, physically based materials, ray-traced lighting", |
| "Unreal Engine cinematic":"Unreal Engine cinematic frame, detailed assets, global illumination", |
| "Pixel art":"carefully designed pixel art, readable silhouettes, deliberate limited palette", |
| "PS1 game aesthetic":"PlayStation 1 aesthetic, low-poly geometry, retro affine texture character", |
| "Japanese ukiyo-e":"Japanese ukiyo-e woodblock print, elegant flat shapes, carved line rhythm", |
| "Islamic geometric art":"intricate Islamic geometric design, precise tessellation, balanced ornament", |
| "Surrealism":"surrealist visual logic, uncanny symbolism, dreamlike spatial relationships", |
| "Magical realism":"magical realism, believable everyday world touched by subtle wonder", |
| "Art Nouveau":"Art Nouveau, flowing organic ornament, elegant curves, botanical detail", |
| "Art Deco":"Art Deco, geometric luxury, symmetrical glamour, polished metallic accents", |
| "Brutalism":"brutalist design, monumental concrete geometry, severe structural rhythm", |
| "Minimalism":"minimalist composition, disciplined negative space, simple forms", |
| "Maximalism":"maximalist visual abundance, layered ornament, rich color and detail", |
| "Movie poster":"premium theatrical movie poster, iconic focal point, dramatic visual hierarchy", |
| "Album cover":"memorable album cover artwork, bold identity, strong central concept", |
| "Dark academia":"dark academia, old libraries, scholarly atmosphere, warm shadowed interiors", |
| "Dreamcore":"dreamcore, nostalgic uncanny spaces, soft surreal atmosphere", |
| "Vaporwave":"vaporwave, retro digital nostalgia, surreal neon geometry, glossy gradients", |
| "Synthwave":"synthwave, retro-futurist neon, dramatic horizon glow, 1980s electronic atmosphere", |
| "Elegant luxury":"elegant luxury aesthetic, refined materials, tasteful restraint, premium finish", |
| "Clean commercial":"clean commercial image, polished lighting, clear subject, advertising-ready composition", |
| "Hyper-detailed":"hyper-detailed, intricate micro-texture, crisp controlled complexity", |
| "Ethereal":"ethereal atmosphere, luminous haze, delicate light, graceful visual softness", |
| "Whimsical":"whimsical visual storytelling, imaginative forms, charming unexpected details", |
| "Moody":"moody atmosphere, controlled contrast, emotionally charged lighting", |
| "Haunting":"haunting beauty, restrained unease, atmospheric depth, memorable silhouette", |
| "Cozy":"cozy atmosphere, warm practical light, tactile materials, inviting detail", |
| "Epic":"epic scale, heroic composition, monumental depth, dramatic visual impact" |
| }; |
| |
| const PROMPT_BOOST = |
| "masterful composition, coherent anatomy, accurate hands and facial structure, " + |
| "clear subject separation, refined color harmony, realistic material response, " + |
| "controlled detail, intentional lighting, professional visual storytelling"; |
| |
| for (const style of STYLES) { |
| const option = document.createElement("option"); |
| option.value = style; |
| $("styles").appendChild(option); |
| } |
| |
| function normalise(value) { |
| return String(value || "").toLowerCase().replace(/[^a-z0-9]+/g, ""); |
| } |
| |
| function updateQueueUI() { |
| $("queuedChip").textContent = `Queued: ${localQueue.length}`; |
| $("activeChip").textContent = processing ? "Active: generating" : "Active: none"; |
| } |
| |
| function setViewer(mode, message = "") { |
| $("empty").style.display = mode === "empty" ? "block" : "none"; |
| $("loading").style.display = mode === "loading" ? "block" : "none"; |
| $("image").style.display = mode === "image" ? "block" : "none"; |
| if (message) $("status").textContent = message; |
| } |
| |
| function endpointInfo(names) { |
| const named = api?.named_endpoints || {}; |
| const entries = Object.entries(named); |
| for (const name of names) { |
| const exact = entries.find(([key]) => normalise(key) === normalise(name)); |
| if (exact) return { endpoint: exact[0], spec: exact[1] }; |
| } |
| for (const name of names) { |
| const partial = entries.find(([key]) => normalise(key).includes(normalise(name))); |
| if (partial) return { endpoint: partial[0], spec: partial[1] }; |
| } |
| return { endpoint: names[0], spec: null }; |
| } |
| |
| function buildPayload(spec, values, fallback) { |
| const params = spec?.parameters; |
| if (!Array.isArray(params) || !params.length) return fallback; |
| |
| return params.map(param => { |
| const name = normalise(param.parameter_name || param.label || param.name || param.component); |
| if (Object.prototype.hasOwnProperty.call(values, name)) return values[name]; |
| |
| for (const [alias, value] of Object.entries(values)) { |
| if (alias && name && (name.includes(alias) || alias.includes(name))) return value; |
| } |
| |
| if ("parameter_default" in param) return param.parameter_default; |
| return null; |
| }); |
| } |
| |
| async function connectBackend() { |
| if (client) return client; |
| $("loadingTitle").textContent = "Connecting to the remote AIDMA engine…"; |
| client = await Client.connect(BACKEND_SPACE, { |
| events: ["data", "status"], |
| space_status: status => { |
| if (status?.status && status.status !== "running") { |
| $("queueStatus").textContent = `Remote Space: ${status.status}`; |
| } |
| } |
| }); |
| api = await client.view_api(); |
| return client; |
| } |
| |
| function snapshotJob() { |
| const prompt = $("prompt").value.trim(); |
| if (!prompt) throw new Error("Write a prompt first."); |
| |
| const modelOption = $("model").selectedOptions[0]; |
| const [width, height] = $("ratio").value.split("x").map(Number); |
| const styleName = $("style").value.trim(); |
| const styleText = STYLE_DETAIL[styleName] ?? styleName; |
| |
| const parts = [prompt]; |
| if (styleText && normalise(styleName) !== normalise("No extra style")) parts.push(styleText); |
| if ($("boost").checked) parts.push(PROMPT_BOOST); |
| |
| return { |
| id: ++jobCounter, |
| repo: modelOption.value, |
| trigger: modelOption.dataset.trigger || "aidmaMJ6.1", |
| modelLabel: modelOption.textContent.trim(), |
| prompt: parts.join(", "), |
| width, |
| height, |
| steps: Number($("quality").value), |
| seed: crypto.getRandomValues(new Uint32Array(1))[0], |
| cfg: 3.5, |
| loraScale: 0.95 |
| }; |
| } |
| |
| function remoteFileUrl(value) { |
| if (typeof value !== "string") return null; |
| const text = value.trim(); |
| if (!text) return null; |
| |
| |
| if (/^(https?:\/\/|blob:|data:image\/)/i.test(text)) return text; |
| |
| |
| if (text.startsWith("/gradio_api/")) return `${BACKEND_ORIGIN}${text}`; |
| if (text.startsWith("gradio_api/")) return `${BACKEND_ORIGIN}/${text}`; |
| |
| |
| if ( |
| text.startsWith("/tmp/") || |
| text.includes("/gradio/") || |
| /\.(png|jpe?g|webp|gif|bmp)(\?|$)/i.test(text) |
| ) { |
| return `${BACKEND_ORIGIN}/gradio_api/file=${encodeURI(text)}`; |
| } |
| |
| return null; |
| } |
| |
| function extractImageUrl(value, seen = new Set()) { |
| if (value == null) return null; |
| |
| if (typeof Blob !== "undefined" && value instanceof Blob) { |
| return URL.createObjectURL(value); |
| } |
| |
| if (typeof File !== "undefined" && value instanceof File) { |
| return URL.createObjectURL(value); |
| } |
| |
| if (typeof value === "string") { |
| const trimmed = value.trim(); |
| |
| |
| if (/^[A-Za-z0-9+/=\s]{1000,}$/.test(trimmed)) { |
| return `data:image/png;base64,${trimmed.replace(/\s+/g, "")}`; |
| } |
| |
| return remoteFileUrl(trimmed); |
| } |
| |
| if (typeof value !== "object") return null; |
| if (seen.has(value)) return null; |
| seen.add(value); |
| |
| if (Array.isArray(value)) { |
| |
| for (const item of value) { |
| const found = extractImageUrl(item, seen); |
| if (found) return found; |
| } |
| return null; |
| } |
| |
| |
| for (const key of ["url", "path", "orig_name", "name", "image", "data"]) { |
| if (Object.prototype.hasOwnProperty.call(value, key)) { |
| const found = extractImageUrl(value[key], seen); |
| if (found) return found; |
| } |
| } |
| |
| for (const item of Object.values(value)) { |
| const found = extractImageUrl(item, seen); |
| if (found) return found; |
| } |
| |
| return null; |
| } |
| |
| function showImage(url, job) { |
| $("image").src = url; |
| $("download").href = url; |
| $("download").download = `aidma-${job.seed}.png`; |
| $("download").style.display = "inline-flex"; |
| setViewer("image", `${job.modelLabel} · seed ${job.seed}`); |
| } |
| |
| async function loadSelectedLora(job) { |
| const { endpoint, spec } = endpointInfo(["/add_custom_lora", "add_custom_lora"]); |
| const payload = buildPayload( |
| spec, |
| { |
| customlora: job.repo, |
| lorarepository: job.repo, |
| huggingfacepath: job.repo |
| }, |
| [job.repo] |
| ); |
| |
| const result = await client.predict(endpoint, payload); |
| const data = result?.data || []; |
| const selectedIndex = Number.isFinite(Number(data[4])) ? Number(data[4]) : null; |
| const trigger = typeof data[5] === "string" && data[5].trim() ? data[5].trim() : job.trigger; |
| return { selectedIndex, trigger }; |
| } |
| |
| async function runGeneration(job, selectedIndex, trigger) { |
| const { endpoint, spec } = endpointInfo(["/run_lora", "run_lora"]); |
| const finalPrompt = normalise(job.prompt).includes(normalise(trigger)) |
| ? job.prompt |
| : `${trigger}, ${job.prompt}`; |
| |
| const values = { |
| prompt: finalPrompt, |
| prompttext: finalPrompt, |
| imageinput: null, |
| inputimage: null, |
| image: null, |
| imagestrength: 0.75, |
| denoisestrength: 0.75, |
| cfgscale: job.cfg, |
| guidancescale: job.cfg, |
| steps: job.steps, |
| numinferencesteps: job.steps, |
| selectedindex: selectedIndex, |
| randomizeseed: false, |
| seed: job.seed, |
| width: job.width, |
| height: job.height, |
| lorascale: job.loraScale, |
| adapterstrength: job.loraScale |
| }; |
| |
| |
| |
| |
| |
| |
| |
| const fallbackWithoutState = [ |
| finalPrompt, null, 0.75, job.cfg, job.steps, false, |
| job.seed, job.width, job.height, job.loraScale |
| ]; |
| |
| const payload = buildPayload(spec, values, fallbackWithoutState); |
| |
| $("queueStatus").textContent = "Waiting for the remote GPU and final image"; |
| const response = await client.predict(endpoint, payload); |
| |
| |
| |
| const outputs = response?.data ?? response; |
| console.log("AIDMA final Gradio outputs:", outputs); |
| |
| |
| const imageOutput = Array.isArray(outputs) ? outputs[0] : outputs; |
| const returnedSeed = Array.isArray(outputs) ? Number(outputs[1]) : NaN; |
| const finalUrl = extractImageUrl(imageOutput) || extractImageUrl(outputs); |
| |
| if (!finalUrl) { |
| console.error("Unrecognized final Gradio image payload:", outputs); |
| throw new Error( |
| "The remote Space did not provide a usable final image. " + |
| "It may have rejected this LoRA or changed its API." |
| ); |
| } |
| |
| if (Number.isFinite(returnedSeed)) job.seed = returnedSeed; |
| showImage(finalUrl, job); |
| return finalUrl; |
| } |
| |
| async function processQueue() { |
| if (processing || !localQueue.length) return; |
| processing = true; |
| updateQueueUI(); |
| |
| while (localQueue.length) { |
| const job = localQueue.shift(); |
| updateQueueUI(); |
| $("download").style.display = "none"; |
| $("loadingTitle").textContent = `Generating job #${job.id}…`; |
| $("queueStatus").textContent = "Preparing AIDMA LoRA"; |
| $("status").className = ""; |
| setViewer("loading", `Job #${job.id} is running.`); |
| |
| try { |
| await connectBackend(); |
| const loaded = await loadSelectedLora(job); |
| $("queueStatus").textContent = "Submitting to remote GPU and waiting for final output"; |
| await runGeneration(job, loaded.selectedIndex, loaded.trigger); |
| } catch (error) { |
| console.error(error); |
| $("status").className = "error"; |
| $("status").textContent = error?.message || String(error); |
| $("empty").innerHTML = |
| '<div class="orb">!</div><strong>Generation failed.</strong>' + |
| '<div style="margin-top:8px">' + escapeHtml(error?.message || String(error)) + '</div>'; |
| setViewer("empty"); |
| |
| client = null; |
| api = null; |
| } |
| } |
| |
| processing = false; |
| updateQueueUI(); |
| } |
| |
| function escapeHtml(value) { |
| return String(value).replace(/[&<>"']/g, char => ({ |
| "&":"&","<":"<",">":">",'"':""","'":"'" |
| })[char]); |
| } |
| |
| $("generate").addEventListener("click", () => { |
| try { |
| const job = snapshotJob(); |
| localQueue.push(job); |
| $("status").className = ""; |
| $("status").textContent = `Job #${job.id} added to your browser queue.`; |
| updateQueueUI(); |
| processQueue(); |
| } catch (error) { |
| $("status").className = "error"; |
| $("status").textContent = error.message; |
| $("prompt").focus(); |
| } |
| }); |
| |
| $("prompt").addEventListener("keydown", event => { |
| if ((event.ctrlKey || event.metaKey) && event.key === "Enter") { |
| event.preventDefault(); |
| $("generate").click(); |
| } |
| }); |
| |
| updateQueueUI(); |
| </script> |
| </body> |
| </html> |
|
|