masked-diffusion / index.html
iamleonie's picture mlabonne's picture
claude opus 5 (1m context)
Initial public commit
5efc5e5
Raw History Blame Contribute Delete
14.5 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Masked-diffusion chat · LFM2.5 Encoder</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<script>
// Private Space auth: HF signs the embedded iframe URL with ?__sign=<token>.
// Incognito / Safari block the third-party .hf.space cookie, so that token is
// the only credential — carry it on same-origin API requests, else /api/*
// returns 404 and the model-ready poll hangs at 92%.
(() => {
const sign = new URLSearchParams(location.search).get("__sign");
if (!sign) return;
const orig = self.fetch.bind(self);
self.fetch = (input, init) => {
try {
const u = new URL(typeof input === "string" ? input : input.url, location.href);
if (u.origin === location.origin && !u.searchParams.has("__sign")) {
u.searchParams.set("__sign", sign);
input = u.toString();
}
} catch (e) { /* non-URL input: pass through */ }
return orig(input, init);
};
})();
</script>
</head>
<body>
<div class="wrap" id="app">
<header class="site-intro">
<h1>Masked-<em>diffusion</em> chat</h1>
<p class="sub">Watch LFM2.5 Encoder turn masked tokens into an answer through iterative denoising.</p>
</header>
<section class="model-strip is-loading" id="model-strip" aria-label="Model status">
<span class="model-logo-frame" aria-hidden="true">
<img id="model-logo" class="model-logo" width="56" height="59" alt="" src="lliquid.gif">
</span>
<div class="model-copy">
<div class="model-name-row">
<a href="https://huggingface.co/LiquidAI/LFM2.5-Encoder-350M" target="_blank" rel="noopener">LFM2.5 Encoder</a>
<span class="model-variant">350M parameters</span>
</div>
</div>
<div class="load-block">
<div class="load-meta">
<span id="status" role="status" aria-live="polite">Connecting to server…</span>
<span id="load-percent">0%</span>
</div>
<div class="load-track" role="progressbar" aria-label="Model loading" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><span id="load-progress"></span></div>
</div>
</section>
<main>
<section class="diffusion-workspace" id="workspace" aria-label="Masked-diffusion generator">
<article class="workspace-card prompt-panel">
<header class="panel-header">
<div><h2>Prompt</h2><p>Enter an instruction or question</p></div>
</header>
<form id="composer">
<label class="sr-only" for="prompt">Prompt</label>
<div class="prompt-stage">
<textarea id="prompt" rows="5" placeholder="Ask LFM2.5 Encoder…">Give three tips for staying healthy.</textarea>
<div class="prompt-scan" aria-hidden="true"></div>
</div>
<div class="composer-row">
<span>Enter to diffuse · Shift + Enter for a new line</span>
<button id="go" type="submit" disabled><span>Diffuse</span><i aria-hidden="true">→</i></button>
</div>
</form>
<div class="settings-heading"><h3>Generation settings</h3><span>Customize the denoising path</span></div>
<div class="settings-grid">
<label class="range-control" for="steps"><span>Step budget</span><output id="stepsV" for="steps">32</output><input type="range" id="steps" min="8" max="128" step="8" value="32"></label>
<label class="range-control" for="mnt"><span>Max tokens</span><output id="mntV" for="mnt">64</output><input type="range" id="mnt" min="16" max="192" step="16" value="64"></label>
<label class="range-control" for="blk"><span>Block size</span><output id="blkV" for="blk">16</output><input type="range" id="blk" min="8" max="64" step="8" value="16"></label>
<label class="range-control" for="temp"><span>Temperature</span><output id="tempV" for="temp">0.00</output><input type="range" id="temp" min="0" max="100" step="5" value="0"></label>
<label class="range-control range-wide" for="tau"><span>Confidence reveal</span><output id="tauV" for="tau">0.90</output><input type="range" id="tau" min="50" max="100" step="5" value="90"></label>
</div>
</article>
<article class="workspace-card response-panel">
<header class="panel-header response-heading">
<div><h2>Response</h2></div>
<span class="activity-pill" id="activity"><i></i><span id="activityText">Ready</span></span>
</header>
<div class="response-stage" id="responseStage" aria-live="polite">
<div class="response-scan" aria-hidden="true"></div>
<div class="empty-state" id="emptyState">
<div class="idle-orbit" aria-hidden="true"><span></span><i></i></div>
<strong>Ready to denoise</strong>
<p>Your answer will emerge here, token by token.</p>
<div class="idle-masks" aria-hidden="true"><span></span><span></span><span></span><span></span><span></span><span></span></div>
</div>
<article class="assistant-message" id="assistantMessage" hidden>
<div id="canvas" class="canvas"></div>
<div class="generation-meta">
<div class="step-track"><span id="stepProgress"></span></div>
<div class="stats" id="live"></div>
</div>
</article>
</div>
</article>
</section>
<section class="examples-panel" aria-label="Example prompts">
<span class="examples-label">Examples</span>
<div class="example-list">
<button type="button" class="ex active" data-q="Give three tips for staying healthy.">Health tips</button>
<button type="button" class="ex" data-q="Explain what a black hole is to a five-year-old.">Black hole</button>
<button type="button" class="ex" data-q="Write a short poem about the ocean.">Ocean poem</button>
<button type="button" class="ex" data-q="What are the primary colors?">Primary colors</button>
<button type="button" class="ex" data-q="Give me a concise guide to brewing green tea.">Green tea</button>
<button type="button" class="ex" data-q="Explain why this loop never stops: while (x > 0) { x++; }">Debug a loop</button>
</div>
</section>
</main>
</div>
<script type="module">
// The model runs on the server (CPU). This script only drives the UI and
// streams denoising frames from /api/generate as newline-delimited JSON.
const MASK = null; // the server sends null for a still-masked position
const $ = (id) => document.getElementById(id);
const setLoadProgress = (percent) => {
const value = Math.max(0, Math.min(100, percent));
$("load-progress").style.width = `${value}%`;
$("load-percent").textContent = `${Math.round(value)}%`;
document.querySelector(".load-track").setAttribute("aria-valuenow", String(Math.round(value)));
};
const setStatus = (message, state = "loading") => {
$("status").textContent = message;
const badge = $("model-strip");
badge.classList.toggle("is-loading", state === "loading");
badge.classList.toggle("is-preparing", state === "preparing");
badge.classList.toggle("is-ready", state === "ready");
badge.classList.toggle("is-error", state === "error");
};
function updateRangeFill(el) {
const pct = ((+el.value - +el.min) / (+el.max - +el.min)) * 100;
el.style.setProperty("--pct", `${pct}%`);
}
for (const [s, l] of [["steps","stepsV"],["mnt","mntV"],["blk","blkV"]])
$(s).oninput = () => { updateRangeFill($(s)); $(l).textContent = $(s).value; };
$("temp").oninput = () => { updateRangeFill($("temp")); $("tempV").textContent = ($("temp").value / 100).toFixed(2); };
$("tau").oninput = () => { updateRangeFill($("tau")); $("tauV").textContent = ($("tau").value / 100).toFixed(2); };
document.querySelectorAll("input[type=range]").forEach(updateRangeFill);
// ---------- wait for the server-side model to be ready ----------
async function waitForServer() {
setStatus("Connecting to server…", "loading");
setLoadProgress(12);
for (let attempt = 0; attempt < 900; attempt++) {
try {
const r = await fetch("api/ready", { cache: "no-store" });
if (r.ok && (await r.json()).ready) return true;
} catch (e) { /* server still starting */ }
setStatus("Warming up model on the server…", "preparing");
setLoadProgress(Math.min(92, 12 + attempt * 4));
await new Promise((r) => setTimeout(r, 1000));
}
return false;
}
(async () => {
const ok = await waitForServer();
if (ok) {
setLoadProgress(100);
setStatus("Model ready", "ready");
$("go").disabled = false;
} else {
setStatus("Model unavailable", "error");
setLoadProgress(100);
$("load-percent").textContent = "Error";
}
})();
// ---------- rendering ----------
const escapeHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;");
function render(tokens, revealed) {
const revealedSet = new Set(revealed);
const parts = [];
for (let i = 0; i < tokens.length; i++) {
if (tokens[i] === MASK) {
parts.push(`<span class="mask" style="--token-index:${i}" aria-label="masked token">&lt;MASK&gt;</span>`);
continue;
}
const esc = escapeHtml(tokens[i] || "·");
parts.push(`<span class="tok${revealedSet.has(i) ? " new" : ""}" style="--token-index:${i}">${esc}</span>`);
}
$("canvas").innerHTML = parts.join("");
}
// ---------- streamed generation ----------
async function generate(userText) {
const maxNew = +$("mnt").value;
const params = new URLSearchParams({
prompt: userText,
mnt: $("mnt").value, steps: $("steps").value, blk: $("blk").value,
temp: $("temp").value, tau: $("tau").value,
});
const t0 = performance.now();
const resp = await fetch("api/generate?" + params.toString());
if (!resp.ok || !resp.body) throw new Error(`HTTP ${resp.status}`);
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = "", steps = 0, finalText = null;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let nl;
while ((nl = buffer.indexOf("\n")) >= 0) {
const line = buffer.slice(0, nl).trim();
buffer = buffer.slice(nl + 1);
if (!line) continue;
const msg = JSON.parse(line);
if (msg.error) throw new Error(msg.error);
if (msg.done) { finalText = msg.text; continue; }
steps = msg.step;
render(msg.tokens, msg.revealed);
const doneCount = msg.tokens.filter((t) => t !== MASK).length;
const dt = (performance.now() - t0) / 1000;
const progress = doneCount / maxNew * 100;
$("stepProgress").style.width = `${progress}%`;
$("activityText").textContent = `Denoising · ${Math.round(progress)}%`;
$("live").textContent = `Step ${msg.step} · ${doneCount}/${maxNew} tokens · ${dt.toFixed(2)}s · ${(doneCount / Math.max(dt, .01)).toFixed(0)} tok/s`;
await new Promise((r) => requestAnimationFrame(r));
}
}
const totalS = (performance.now() - t0) / 1000;
return { text: finalText ?? "", totalS, steps, msPerStep: totalS * 1000 / Math.max(steps, 1), tokPerS: maxNew / totalS };
}
$("composer").addEventListener("submit", async (event) => {
event.preventDefault();
const userText = $("prompt").value.trim();
if (!userText || $("go").disabled) return;
$("assistantMessage").hidden = false;
$("emptyState").hidden = true;
$("canvas").innerHTML = Array.from({ length: +$("mnt").value }, (_, i) => `<span class="mask" style="--token-index:${i}" aria-label="masked token">&lt;MASK&gt;</span>`).join("");
$("live").textContent = "Starting diffusion…";
$("stepProgress").style.width = "0%";
$("assistantMessage").setAttribute("aria-busy", "true");
$("workspace").classList.add("is-generating");
$("responseStage").classList.add("is-denoising");
$("responseStage").classList.remove("is-complete");
$("activity").className = "activity-pill is-running";
$("activityText").textContent = "Building canvas";
$("go").disabled = true;
$("go").querySelector("span").textContent = "Denoising";
try {
const r = await generate(userText);
$("canvas").textContent = r.text || "(empty)";
$("stepProgress").style.width = "100%";
$("live").textContent = `${r.steps} steps · ${r.totalS.toFixed(2)}s · ${r.msPerStep.toFixed(0)} ms/step · ${r.tokPerS.toFixed(0)} tok/s`;
$("activity").className = "activity-pill is-done";
$("activityText").textContent = "Complete";
$("responseStage").classList.add("is-complete");
window.__LAST_GEN = r;
} catch (e) {
$("canvas").textContent = "Error: " + (e.message || e);
$("activity").className = "activity-pill is-error";
$("activityText").textContent = "Could not finish";
console.error(e);
}
$("assistantMessage").removeAttribute("aria-busy");
$("workspace").classList.remove("is-generating");
$("responseStage").classList.remove("is-denoising");
$("go").disabled = false;
$("go").querySelector("span").textContent = "Diffuse";
setTimeout(() => $("responseStage").classList.remove("is-complete"), 900);
});
$("prompt").addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
event.preventDefault();
$("composer").requestSubmit();
}
});
document.querySelector(".example-list").addEventListener("click", (event) => {
const option = event.target.closest(".ex");
if (!option) return;
document.querySelectorAll(".ex").forEach((el) => el.classList.toggle("active", el === option));
$("prompt").value = option.dataset.q || "";
$("prompt").dispatchEvent(new Event("input", { bubbles: true }));
document.querySelector(".prompt-panel").classList.remove("example-changed");
requestAnimationFrame(() => document.querySelector(".prompt-panel").classList.add("example-changed"));
});
$("prompt").addEventListener("input", () => {
const active = document.querySelector(".ex.active");
if (!active?.dataset.q || active.dataset.q !== $("prompt").value) {
document.querySelectorAll(".ex").forEach((el) => el.classList.remove("active"));
}
});
</script>
</body>
</html>