Vashtra / index.html
vigneshwar234's picture
Remove emoji and em dashes, add q8 fallback
72b681e verified
Raw
History Blame Contribute Delete
10.1 kB
<!doctype html>
<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&#8209;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&#8209;0.6B · continued&#8209;pretrained on arXiv cs.LG + ML&nbsp;Q&amp;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&nbsp;113+, Edge, or Safari&nbsp;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>