| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>get-name · in-browser demo</title> |
| <style> |
| body{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;max-width:720px;margin:2rem auto;padding:0 1rem;color:#1f2328;line-height:1.5} |
| h1{font-size:1.4rem} |
| textarea{width:100%;box-sizing:border-box;min-height:4rem;font:inherit;padding:.6rem;border:1px solid #d0d7de;border-radius:8px} |
| button{font:inherit;padding:.5rem 1rem;border:0;border-radius:8px;background:#0969da;color:#fff;cursor:pointer} |
| button:disabled{background:#8bb3dd;cursor:wait} |
| pre{background:#f6f8fa;border:1px solid #d0d7de;border-radius:8px;padding:.8rem;overflow:auto;white-space:pre-wrap;word-break:break-word} |
| .ok{color:#1a7f37}.err{color:#cf222e}.muted{color:#57606a;font-size:.85rem} |
| </style> |
| </head> |
| <body> |
| <h1>get-name — extract the intended person's first name (in-browser)</h1> |
| <p class="muted"> |
| Model: <a href="https://huggingface.co/Qrzysztof/get-name">Qrzysztof/get-name</a> · |
| Built on <a href="https://huggingface.co/Cactus-Compute/needle2">cactus-needle</a>. |
| Loads the engine + tuned weights <em>directly from Hugging Face</em>. No server — everything runs in this tab. |
| </p> |
| <p><textarea id="q" placeholder='Type a sentence… e.g. Nice to meet you, Alex'>By the way, Alice, if you don't know, how old am I?</textarea></p> |
| <p><button id="go" onclick="run()" disabled>Extract name</button> |
| <span id="status" class="muted"></span></p> |
| <pre id="out" class="muted">waiting…</pre> |
|
|
| <script src="https://huggingface.co/Cactus-Compute/needle2/resolve/main/wasm/needle.js"></script> |
| <script> |
| const WASM_URL = "https://huggingface.co/Cactus-Compute/needle2/resolve/main/wasm/needle.wasm"; |
| const CACT_URL = "https://huggingface.co/Qrzysztof/get-name/resolve/main/tuned.cact"; |
| const SYSTEM = "You extract the given (first) name of the intended person. If the text has no intended person, do not call the tool."; |
| const TOOLS = [{ name: "extract_name", parameters: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } }]; |
| |
| let mod = null; |
| |
| const enc = s => { const b = new TextEncoder().encode(s + "\0"); const p = mod._malloc(b.length); mod.HEAPU8.set(b, p); return p; }; |
| |
| async function init() { |
| const status = document.getElementById("status"); |
| const btn = document.getElementById("go"); |
| status.textContent = "downloading engine + weights…"; btn.disabled = true; |
| |
| const [wasm, cact] = await Promise.all([ |
| fetch(WASM_URL).then(r => r.arrayBuffer()), |
| fetch(CACT_URL).then(r => r.arrayBuffer()), |
| ]); |
| |
| status.textContent = "compiling engine…"; |
| mod = await createNeedle({ wasmBinary: wasm }); |
| |
| const p = mod._malloc(cact.byteLength); |
| mod.HEAPU8.set(new Uint8Array(cact), p); |
| if (mod._needle_load(p, BigInt(cact.byteLength)) !== 0) throw new Error("needle_load failed"); |
| mod._free(p); |
| |
| const sysP = enc(SYSTEM); |
| const toolsP = enc(JSON.stringify(TOOLS)); |
| if (mod._needle_init(sysP, toolsP, 0) < 0) throw new Error("needle_init failed"); |
| |
| status.textContent = "ready ✓"; btn.disabled = false; |
| } |
| |
| async function run() { |
| const q = document.getElementById("q").value.trim(); |
| const out = document.getElementById("out"); |
| const status = document.getElementById("status"); |
| if (!q) return; |
| const btn = document.getElementById("go"); btn.disabled = true; |
| status.textContent = "generating…"; |
| try { |
| await (mod ? Promise.resolve() : init()); |
| |
| const qP = enc(q); |
| const cap = 1 << 20, outP = mod._malloc(cap); |
| const rc = mod._needle_complete(qP, 512, outP, cap); |
| if (rc < 0) throw new Error("needle_complete returned " + rc); |
| |
| |
| const bytes = mod.HEAPU8.subarray(outP, outP + cap); |
| let end = bytes.indexOf(0); if (end < 0) end = cap; |
| const response = new TextDecoder().decode(bytes.subarray(0, end)); |
| |
| out.textContent = response; |
| out.className = ""; |
| try { |
| const obj = JSON.parse(response); |
| const calls = obj.function_calls || []; |
| if (calls.length) { |
| status.innerHTML = `<span class="ok">intended person: <b>${calls[0].arguments.name}</b></span>`; |
| } else { |
| status.textContent = "no tool call (no intended person detected)"; |
| } |
| } catch { status.textContent = "raw response above (unparseable JSON)."; } |
| mod._free(qP); mod._free(outP); |
| } catch (e) { |
| out.textContent = String(e); |
| out.className = "err"; |
| status.textContent = "error"; |
| } |
| btn.disabled = false; |
| } |
| |
| init().catch(e => { document.getElementById("status").textContent = "error: " + e.message; }); |
| </script> |
| <div class="muted"><p>Tips:</p> |
| <ul> |
| <li>Runs fully client-side (WASM ~325 KB, weights ~13.7 MB, ~150–200 MB memory measured in Node; browser may differ).</li> |
| <li>CORS: works from any <code>http(s)</code> page; for a local file open over <code>python3 -m http.server</code>.</li> |
| <li>Gives one answer per <code>complete()</code> call — for batches, call the engine fresh per text (see README).</li> |
| <li>Model card, training data & API docs: <a href="https://huggingface.co/Qrzysztof/get-name">Qrzysztof/get-name</a>.</li> |
| </ul></div> |
| </body> |
| </html> |