Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Vashtra · a 0.6B machine-learning specialist that runs in your browser</title> | |
| <style> | |
| :root{ | |
| --bg:#0a0c10; --panel:#11141b; --line:#1e2430; --line-soft:#171c26; | |
| --ink:#e8ecf4; --muted:#8b95a8; --dim:#5d6678; | |
| --accent:#7c9cff; --accent-dim:#3d5299; --warm:#ffb86b; --ok:#4ade80; | |
| --radius:14px; | |
| } | |
| *{box-sizing:border-box} | |
| html,body{height:100%} | |
| body{ | |
| margin:0; background:var(--bg); color:var(--ink); | |
| font:15px/1.6 ui-sans-serif,-apple-system,"Segoe UI",Inter,system-ui,sans-serif; | |
| -webkit-font-smoothing:antialiased; | |
| display:flex; flex-direction:column; | |
| } | |
| a{color:var(--accent); text-decoration:none} | |
| a:hover{text-decoration:underline} | |
| header{ | |
| border-bottom:1px solid var(--line); padding:18px 22px; | |
| display:flex; align-items:center; gap:14px; flex-wrap:wrap; | |
| background:linear-gradient(180deg,#0d1017,#0a0c10); | |
| } | |
| .mark{ | |
| width:34px;height:34px;border-radius:9px;flex:none; | |
| background:linear-gradient(135deg,var(--accent),#a97cff); | |
| display:grid;place-items:center;font-weight:700;color:#0a0c10;font-size:17px; | |
| } | |
| h1{font-size:17px;margin:0;letter-spacing:-.01em;font-weight:650} | |
| .sub{color:var(--muted);font-size:12.5px;margin-top:1px} | |
| .spacer{flex:1} | |
| .badge{ | |
| font-size:11.5px;color:var(--muted);border:1px solid var(--line); | |
| padding:5px 10px;border-radius:999px;white-space:nowrap; | |
| } | |
| .badge b{color:var(--ok);font-weight:600} | |
| main{flex:1; display:flex; flex-direction:column; max-width:860px; width:100%; | |
| margin:0 auto; padding:0 18px; min-height:0} | |
| #status{ | |
| margin:16px 0 0; padding:13px 15px; border:1px solid var(--line); | |
| border-radius:var(--radius); background:var(--panel); font-size:13.5px; | |
| display:flex; align-items:center; gap:11px; | |
| } | |
| .dot{width:8px;height:8px;border-radius:50%;background:var(--warm);flex:none; | |
| box-shadow:0 0 0 3px rgba(255,184,107,.13)} | |
| .dot.ready{background:var(--ok);box-shadow:0 0 0 3px rgba(74,222,128,.13)} | |
| .dot.err{background:#ff6b6b;box-shadow:0 0 0 3px rgba(255,107,107,.13)} | |
| .bar{height:3px;background:var(--line-soft);border-radius:2px;overflow:hidden;margin-top:9px} | |
| .bar>i{display:block;height:100%;width:0;background:var(--accent);transition:width .25s} | |
| #chat{flex:1; overflow-y:auto; padding:18px 2px 8px; min-height:0} | |
| .msg{display:flex;gap:12px;margin-bottom:20px} | |
| .who{ | |
| width:27px;height:27px;border-radius:7px;flex:none;display:grid;place-items:center; | |
| font-size:11px;font-weight:700;margin-top:1px; | |
| } | |
| .u .who{background:#232a38;color:var(--muted)} | |
| .a .who{background:linear-gradient(135deg,var(--accent),#a97cff);color:#0a0c10} | |
| .body{flex:1;min-width:0;white-space:pre-wrap;word-wrap:break-word;padding-top:2px} | |
| .a .body{color:#dfe5f0} | |
| .cursor::after{content:"▍";color:var(--accent);animation:bl 1s steps(2) infinite} | |
| @keyframes bl{50%{opacity:0}} | |
| .examples{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0 4px} | |
| .ex{ | |
| font-size:12.5px;color:var(--muted);background:var(--panel); | |
| border:1px solid var(--line);padding:8px 12px;border-radius:999px;cursor:pointer; | |
| transition:.15s; | |
| } | |
| .ex:hover{border-color:var(--accent-dim);color:var(--ink)} | |
| footer{border-top:1px solid var(--line);padding:14px 0 18px;background:var(--bg)} | |
| .composer{display:flex;gap:9px;align-items:flex-end} | |
| textarea{ | |
| flex:1;background:var(--panel);border:1px solid var(--line);color:var(--ink); | |
| border-radius:11px;padding:12px 14px;font:inherit;resize:none;max-height:150px; | |
| outline:none;transition:border-color .15s; | |
| } | |
| textarea:focus{border-color:var(--accent-dim)} | |
| button{ | |
| background:var(--accent);color:#0a0c10;border:0;border-radius:11px; | |
| padding:12px 20px;font:inherit;font-weight:650;cursor:pointer;transition:.15s; | |
| } | |
| button:disabled{background:#252b38;color:var(--dim);cursor:not-allowed} | |
| .foot{font-size:11.5px;color:var(--dim);margin-top:9px;text-align:center} | |
| @media(max-width:600px){ .badge.hide-sm{display:none} h1{font-size:15.5px} } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="mark">V</div> | |
| <div> | |
| <h1>Vashtra‑0.6B</h1> | |
| <div class="sub">A small language model that actually knows machine learning</div> | |
| </div> | |
| <div class="spacer"></div> | |
| <span class="badge hide-sm">Runs <b>100% in your browser</b> · no server</span> | |
| <a class="badge" href="https://huggingface.co/vigneshwar234/Vashtra-0.6B" target="_blank">Model ↗</a> | |
| <a class="badge hide-sm" href="https://huggingface.co/datasets/vigneshwar234/vashtra-ml-corpus" target="_blank">Data ↗</a> | |
| </header> | |
| <main> | |
| <div id="status"> | |
| <span class="dot" id="dot"></span> | |
| <div style="flex:1"> | |
| <div id="statusText">Checking your browser for WebGPU…</div> | |
| <div class="bar"><i id="barFill"></i></div> | |
| </div> | |
| </div> | |
| <div id="chat"></div> | |
| <div class="examples" id="examples"> | |
| <div class="ex">Explain why batch normalization helps training.</div> | |
| <div class="ex">LoRA vs full fine-tuning: when does each win?</div> | |
| <div class="ex">When should I use focal loss instead of cross-entropy?</div> | |
| <div class="ex">What causes exploding gradients in RNNs?</div> | |
| </div> | |
| </main> | |
| <footer> | |
| <div style="max-width:860px;margin:0 auto;padding:0 18px"> | |
| <div class="composer"> | |
| <textarea id="input" rows="1" placeholder="Ask about machine learning…" disabled></textarea> | |
| <button id="send" disabled>Send</button> | |
| </div> | |
| <div class="foot"> | |
| Vashtra‑0.6B · continued‑pretrained on arXiv cs.LG + ML Q&A · | |
| built by <a href="https://huggingface.co/vigneshwar234" target="_blank">vigneshwar234</a> | |
| </div> | |
| </div> | |
| </footer> | |
| <script type="module"> | |
| import { | |
| pipeline, TextStreamer, env, | |
| } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.6"; | |
| env.allowLocalModels = false; | |
| const MODEL = "vigneshwar234/Vashtra-0.6B-ONNX"; | |
| const dot = document.getElementById("dot"); | |
| const stat = document.getElementById("statusText"); | |
| const fill = document.getElementById("barFill"); | |
| const chat = document.getElementById("chat"); | |
| const input = document.getElementById("input"); | |
| const send = document.getElementById("send"); | |
| const examples= document.getElementById("examples"); | |
| let generator = null; | |
| let busy = false; | |
| const history = []; | |
| function setStatus(text, state){ | |
| stat.innerHTML = text; | |
| dot.className = "dot" + (state ? " " + state : ""); | |
| } | |
| function addMsg(role, text){ | |
| const el = document.createElement("div"); | |
| el.className = "msg " + (role === "user" ? "u" : "a"); | |
| el.innerHTML = `<div class="who">${role === "user" ? "You" : "V"}</div> | |
| <div class="body"></div>`; | |
| el.querySelector(".body").textContent = text; | |
| chat.appendChild(el); | |
| chat.scrollTop = chat.scrollHeight; | |
| return el.querySelector(".body"); | |
| } | |
| async function boot(){ | |
| if (!navigator.gpu){ | |
| setStatus( | |
| "This browser has no <b>WebGPU</b>. Vashtra runs on your GPU locally. " + | |
| "Open this in Chrome 113+, Edge, or Safari 18+ to try it.", "err"); | |
| return; | |
| } | |
| const onProgress = (p) => { | |
| if (p.status === "progress" && p.total){ | |
| const pct = (p.loaded / p.total) * 100; | |
| fill.style.width = pct.toFixed(1) + "%"; | |
| setStatus(`Downloading <b>${p.file ?? "model"}</b> ${pct.toFixed(0)}%`); | |
| } else if (p.status === "ready"){ | |
| fill.style.width = "100%"; | |
| } | |
| }; | |
| // q4 is ~350MB; q8 is the fallback if the 4-bit export was not produced. | |
| try{ | |
| setStatus("Downloading Vashtra (cached after the first visit)…"); | |
| try{ | |
| generator = await pipeline("text-generation", MODEL, | |
| { dtype: "q4", device: "webgpu", progress_callback: onProgress }); | |
| }catch(inner){ | |
| console.warn("q4 unavailable, falling back to q8:", inner); | |
| setStatus("Loading 8-bit build…"); | |
| generator = await pipeline("text-generation", MODEL, | |
| { dtype: "q8", device: "webgpu", progress_callback: onProgress }); | |
| } | |
| fill.style.width = "100%"; | |
| setStatus("Ready. Running locally on your GPU. Nothing leaves this page.", "ready"); | |
| input.disabled = false; send.disabled = false; input.focus(); | |
| }catch(e){ | |
| console.error(e); | |
| setStatus( | |
| "Could not load the model. It may still be training. " + | |
| `check <a href="https://huggingface.co/${MODEL}" target="_blank">the model repo</a>. ` + | |
| `<span style="color:var(--dim)">(${e.message})</span>`, "err"); | |
| } | |
| } | |
| async function ask(text){ | |
| if (busy || !generator || !text.trim()) return; | |
| busy = true; send.disabled = true; input.disabled = true; | |
| examples.style.display = "none"; | |
| addMsg("user", text); | |
| history.push({ role: "user", content: text }); | |
| const out = addMsg("assistant", ""); | |
| out.classList.add("cursor"); | |
| let acc = ""; | |
| const streamer = new TextStreamer(generator.tokenizer, { | |
| skip_prompt: true, | |
| skip_special_tokens: true, | |
| callback_function: (t) => { | |
| acc += t; | |
| out.textContent = acc; | |
| chat.scrollTop = chat.scrollHeight; | |
| }, | |
| }); | |
| try{ | |
| await generator(history, { | |
| max_new_tokens: 320, do_sample: true, temperature: 0.7, top_p: 0.9, streamer, | |
| }); | |
| history.push({ role: "assistant", content: acc }); | |
| }catch(e){ | |
| out.textContent = acc + `\n\n[generation failed: ${e.message}]`; | |
| }finally{ | |
| out.classList.remove("cursor"); | |
| busy = false; send.disabled = false; input.disabled = false; input.focus(); | |
| } | |
| } | |
| send.onclick = () => { const v = input.value; input.value = ""; input.style.height="auto"; ask(v); }; | |
| input.addEventListener("keydown", (e) => { | |
| if (e.key === "Enter" && !e.shiftKey){ e.preventDefault(); send.click(); } | |
| }); | |
| input.addEventListener("input", () => { | |
| input.style.height = "auto"; | |
| input.style.height = Math.min(input.scrollHeight, 150) + "px"; | |
| }); | |
| examples.addEventListener("click", (e) => { | |
| if (e.target.classList.contains("ex")) ask(e.target.textContent); | |
| }); | |
| boot(); | |
| </script> | |
| </body> | |
| </html> | |