Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PXG-Tiny Playground — in-browser live demo</title> | |
| <style> | |
| :root{ | |
| --bg:#0d0f1a; --panel:#1a1f3a; --panel2:#20264a; --ink:#e8eaf6; --dim:#9aa3c7; | |
| --gold:#ffd24a; --green:#5df08a; --blue:#5db8ff; --line:#2a3060; | |
| --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; | |
| } | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| body{background:var(--bg);color:var(--ink);font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif; | |
| min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:34px 18px 60px; | |
| background-image:radial-gradient(circle at 50% -10%,#1b2150 0%,transparent 50%)} | |
| a{color:var(--blue);text-decoration:none} | |
| h1{font-family:var(--mono);font-size:28px;letter-spacing:-.5px} | |
| h1 span{color:var(--gold)} | |
| .tag{color:var(--dim);font-size:14px;margin:8px 0 4px;text-align:center} | |
| .tag b{color:var(--ink)} | |
| .panel{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:22px; | |
| width:min(720px,100%);margin-top:22px} | |
| .row{display:flex;gap:10px;flex-wrap:wrap} | |
| input[type=text]{flex:1;min-width:220px;background:#0a0c16;border:1px solid var(--line);border-radius:10px; | |
| color:var(--ink);font-family:var(--mono);font-size:15px;padding:12px 14px;outline:none} | |
| input[type=text]:focus{border-color:var(--gold)} | |
| button{background:var(--gold);color:#1a1400;border:none;border-radius:10px;font-family:var(--mono); | |
| font-size:14px;font-weight:700;padding:12px 22px;cursor:pointer} | |
| button:disabled{opacity:.5;cursor:wait} | |
| button.ghost{background:var(--panel2);color:var(--ink);border:1px solid var(--line);font-weight:400} | |
| .subrow{display:flex;gap:16px;align-items:center;margin-top:12px;font-size:13px;color:var(--dim);flex-wrap:wrap} | |
| .subrow label{display:flex;gap:6px;align-items:center;cursor:pointer} | |
| canvas{width:256px;height:256px;background:#0a0c16;border:1px solid var(--line);border-radius:12px; | |
| image-rendering:pixelated;margin:18px auto 4px;display:block} | |
| #status{font-family:var(--mono);font-size:12.5px;color:var(--dim);text-align:center;min-height:20px;margin-top:8px} | |
| #status.ok{color:var(--green)} #status.err{color:#ff7b7b} | |
| .ex{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:16px} | |
| .ex button{background:var(--panel2);color:var(--dim);border:1px solid var(--line);font-weight:400; | |
| font-size:12px;padding:7px 12px;border-radius:999px} | |
| .ex button:hover{color:var(--ink);border-color:var(--gold)} | |
| .foot{margin-top:26px;font-size:12.5px;color:var(--dim);text-align:center;line-height:1.8} | |
| .chips{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-top:10px} | |
| .note{font-size:12px;color:var(--dim);text-align:center;margin-top:10px} | |
| </style> | |
| </head> | |
| <body> | |
| <h1>PXG<span>-Tiny</span> playground</h1> | |
| <p class="tag">the <b>entire model runs in your browser</b> — real NumPy inference via Pyodide, | |
| weights (~1.8 MB) served from this page · <b>no server, no GPU, no API</b></p> | |
| <div class="panel"> | |
| <div class="row"> | |
| <input type="text" id="prompt" placeholder="a golden sword" spellcheck="false"> | |
| <button id="go" disabled>Generate</button> | |
| </div> | |
| <div class="subrow"> | |
| <label>seed <input type="number" id="seed" value="0" style="width:80px;background:#0a0c16;border:1px solid var(--line);border-radius:6px;color:var(--ink);padding:4px 8px;font-family:var(--mono)"></label> | |
| <label><input type="checkbox" id="verify" checked> verifier-guided retries (8 attempts)</label> | |
| </div> | |
| <canvas id="cv" width="16" height="16"></canvas> | |
| <div id="status">booting Pyodide + loading weights… first load takes ~20-40 s, then ~1-3 s per sprite</div> | |
| <div class="ex" id="ex"></div> | |
| </div> | |
| <p class="note">38 trained classes — characters (wizard, knight, archer, skeleton, zombie), | |
| animals (cat, dog, bird, fish, deer, mouse, bat), weapons, potions, treasure, furniture, | |
| trees, rocks, bushes, tiles, fireball, house. Anything else → a clarifying question.</p> | |
| <div class="foot"> | |
| model: <a href="https://huggingface.co/Tarul/pxg-tiny">huggingface.co/Tarul/pxg-tiny</a> · | |
| code: <a href="https://github.com/tarulahsan/PXG-Tiny-AI">github.com/tarulahsan/PXG-Tiny-AI</a><br> | |
| created by <b>Chowdhury Tarul Ahsan</b> · <a href="mailto:tarulahsan@gmail.com">tarulahsan@gmail.com</a> · | |
| <a href="index.html">← back to the landing page</a> | |
| </div> | |
| <script> | |
| const EXAMPLES = ["a golden sword","a purple bat with wings","a brown deer with antlers", | |
| "a slim vial of green potion","a wizard with a purple hat","a cottage with a red roof", | |
| "a wooden chair","a knight in iron armor","a mossy boulder","old oak with autumn leaves", | |
| "a knight with a golden sword","a large fireball","a water texture tile","a gold coin"]; | |
| const cv = document.getElementById("cv"), ctx = cv.getContext("2d"); | |
| const statusEl = document.getElementById("status"); | |
| const goBtn = document.getElementById("go"), promptEl = document.getElementById("prompt"); | |
| const seedEl = document.getElementById("seed"), verifyEl = document.getElementById("verify"); | |
| let busy = false; | |
| const worker = new Worker("worker.js"); | |
| worker.onmessage = (e) => { | |
| const d = e.data; | |
| if (d.type === "status") { statusEl.textContent = d.msg; } | |
| else if (d.type === "ready") { | |
| statusEl.textContent = "model ready — the whole 483k-param network is running in this tab"; | |
| statusEl.className = "ok"; goBtn.disabled = false; | |
| } else if (d.type === "result") { draw(d.data); busy = false; goBtn.disabled = false; } | |
| else if (d.type === "error") { | |
| statusEl.textContent = "error: " + d.msg; statusEl.className = "err"; | |
| busy = false; goBtn.disabled = false; | |
| } | |
| }; | |
| worker.postMessage({ type: "init" }); | |
| function draw(r) { | |
| if (r.rgba === null || r.rgba === undefined) { | |
| statusEl.textContent = r.message || ("[" + r.gate + "] try one of the trained classes"); | |
| statusEl.className = "err"; return; | |
| } | |
| const img = ctx.createImageData(16, 16); | |
| img.data.set(r.rgba); | |
| ctx.putImageData(img, 0, 0); | |
| if (r.gate === "accept") | |
| statusEl.textContent = `accepted — attempt ${r.attempt + 1}, seed ${r.seed}`; | |
| else | |
| statusEl.textContent = `last-resort sprite (hard prompt) — seed ${r.seed}, try another seed`; | |
| statusEl.className = "ok"; | |
| } | |
| function go() { | |
| if (busy) return; | |
| const p = promptEl.value.trim(); | |
| if (p.length < 4) { statusEl.textContent = "type a prompt, e.g. 'a gold sword'"; return; } | |
| busy = true; goBtn.disabled = true; | |
| statusEl.textContent = "generating…"; | |
| worker.postMessage({ type: "generate", prompt: p, | |
| seed: Number(seedEl.value) || 0, verify: verifyEl.checked }); | |
| } | |
| goBtn.onclick = go; | |
| promptEl.addEventListener("keydown", (e) => { if (e.key === "Enter") go(); }); | |
| document.getElementById("ex").innerHTML = ""; | |
| EXAMPLES.forEach((p) => { | |
| const b = document.createElement("button"); | |
| b.textContent = p; | |
| b.onclick = () => { promptEl.value = p; go(); }; | |
| document.getElementById("ex").appendChild(b); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |