Spaces:
Running
Running
| <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); | |
| // rc is NOT the output length: the engine writes a NUL-terminated string, | |
| // so scan the whole buffer for the first NUL terminator. | |
| 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> |