q
File size: 11,440 Bytes
3365e13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
abdc36e
 
 
3365e13
 
7d0b228
 
 
 
 
 
1185393
 
3365e13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb2d355
3365e13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<!doctype html><html><head><meta charset=utf8><meta name=viewport content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>Q — private AI, in your browser</title>
<style>
  :root{--bg:#0b0e14;--panel:#141922;--ink:#e6e9ef;--dim:#8a94a6;--q:#7c5cff;--u:#1f6feb;--line:#1e2531}
  *{box-sizing:border-box}html,body{height:100%}
  body{margin:0;font:15px/1.55 -apple-system,Segoe UI,Roboto,system-ui,monospace;background:var(--bg);color:var(--ink);display:flex;flex-direction:column;overscroll-behavior:none}
  header{padding:10px 16px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px;flex:0 0 auto}
  header b{font-weight:600}header .s{color:var(--dim);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  #log{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:12px;-webkit-overflow-scrolling:touch}
  .msg{max-width:82%;padding:9px 13px;border-radius:12px;white-space:pre-wrap;word-wrap:break-word}
  .u{align-self:flex-end;background:var(--u)}.a{align-self:flex-start;background:var(--panel);border:1px solid #232b3a}
  .a.think{color:var(--dim);font-style:italic}
  footer{padding:12px 16px;border-top:1px solid var(--line);display:flex;gap:8px;flex:0 0 auto;padding-bottom:calc(12px + env(safe-area-inset-bottom))}
  #in{flex:1;background:var(--panel);border:1px solid #232b3a;color:var(--ink);border-radius:10px;padding:10px 12px;font:inherit;resize:none;max-height:140px}
  button{background:var(--q);color:#fff;border:0;border-radius:10px;padding:0 18px;font:inherit;cursor:pointer}button:disabled{opacity:.4;cursor:default}
  .stat{color:var(--dim);font-size:11px;margin-top:3px}
</style></head><body>
<header><b>Q</b> <span class=s id=st>booting…</span></header>
<div id=log></div>
<footer><textarea id=in rows=1 placeholder="Message Q…" disabled></textarea><button id=send disabled>Send</button></footer>
<script type=module>
import { ready, loadModel, MODELS, defaultModelIndex } from "./core/loader.js";
import { createEngine } from "./core/engine.js";
import { selfPersona, selfFacts } from "./core/q-self.mjs";   // ONE grounded self-knowledge, shared with the messenger + voice
const $ = (s) => document.querySelector(s);
const log = $("#log"), input = $("#in"), send = $("#send"), st = $("#st");
const bubble = (cls, text = "") => { const d = document.createElement("div"); d.className = "msg " + cls; d.textContent = text; log.appendChild(d); log.scrollTop = log.scrollHeight; return d; };
const params = new URLSearchParams(location.search);
const pick = params.get("m");
let m = pick ? (MODELS.find((x) => new RegExp(pick, "i").test(x.name)) || MODELS[0]) : MODELS[defaultModelIndex()];
// STREAM FROM HF: a bare link streams the HOLOGRAMTECH BitNet κ-object from Hugging Face; ?hf=<org/repo> or
// ?kappa=<absolute-url> override. The κ-object is content-addressed + pinned, so the host is an UNTRUSTED CDN —
// every block is re-derived (Law L5); a bad byte is rejected. Blocks cache locally after first load (0-net on return).
{
  let hf = params.get("hf"), kappa = params.get("kappa");
  if (!hf && !kappa && !pick) hf = "HOLOGRAMTECH/q-bitnet-2b";   // bare link → stream BitNet from Hologram's HF repo
  if (hf || kappa) {
    const base = (kappa || `https://huggingface.co/${hf}/resolve/main`).replace(/\/+$/, "");
    const bit = MODELS.find((x) => (x.fam || "").toLowerCase() === "bitnet") || m;
    // drop the model's SHA-256 manifest pin: a ?hf repo may carry a different (e.g. BLAKE3) manifest, so the
    // stale pin would falsely reject it. Per-block κ verification still applies (untrusted-CDN-safe).
    m = { ...bit, kappaUrl: base, manifestKappa: undefined, name: bit.name + " · via " + (hf || new URL(base).host) };
  }
}
// ?stream=layer → page the model layer-by-layer instead of resident. For t2 (BitNet) this exercises the DRAFT
// t2-streaming engine path (correctness first; the .qvf remote path adds fast-first-token). Default = resident.
{
  const sm = params.get("stream");
  if (sm && sm !== "false" && sm !== "resident") m = { ...m, stream: sm };
}
// ?verify=gpu → re-derive each BLAKE3 weight-block κ ENTIRELY on the GPU (2.74 GB/s) instead of pure-JS BLAKE3.
if (params.get("verify") === "gpu") globalThis.__gpuVerify = true;

// GROUND the model as on-device Q (a base/instruct model has NO self-knowledge — without this it confabulates
// a generic "I run on OpenAI/AWS cloud servers" identity, which is false). Injected as the SYSTEM turn.
function frameSystem() {
  const PERSONA = selfPersona({ model: m, engine });
  if (m.llama3) return `<|start_header_id|>system<|end_header_id|>\n\n${PERSONA}<|eot_id|>`;
  if (m.qwen)   return `<|im_start|>system\n${PERSONA}<|im_end|>\n`;
  if (m.olmo)   return `<|system|>\n${PERSONA}\n`;
  return PERSONA + "\n\n";
}

// GROUNDED IDENTITY (the anti-confabulation guard). A 2B model reverts to its training prior — "I'm GPT-3.5 on
// AWS" — when asked what/where it is, no matter the system prompt. But identity is not a guess: it is the TRUTH
// of THIS running instance. So provenance questions are answered DETERMINISTICALLY from the live facts (the real
// resident model + its κ + the real host it streamed from), never from the model. Grounded, not performed.
const IDENTITY_RX = /\b(are|r)\s*(you|u)\b.*\b(gpt|chatgpt|openai|claude|anthropic|gemini|bard|llama|language model|an? ai|running|local|on[- ]?device|in the browser|on (a )?server|in the cloud|hosted)\b|\bwhat( kind of| sort of| type of)?\b.*\b(model|llm|ai|are you|based on|powered by|architecture|run on|running)\b|\bwho\b.*\b(are you|made|built|created|trained|develop)\b|\bwhere\b.*\b(run|running|host|hosted|are you|live|located)\b|\bpowered by\b|\bwhat are you\b|\b(openai|chatgpt|gpt-?\d|aws|amazon web|google cloud|cloud server)\b|\b(local|cloud|server)\b.*\bmodel\b|\bdo you run\b/i;
function groundedIdentity() {
  const f = selfFacts({ model: m, engine });
  const name = f.model || (m && m.name) || "an on-device model";
  const host = (f.weightsFrom && !/^local$/i.test(f.weightsFrom)) ? f.weightsFrom : "Hugging Face";
  const q = f.quant ? ` (${f.quant})` : "";
  return `I'm Q. I run the ${name}${q} entirely in your browser on WebGPU — not GPT, not OpenAI, and not on any server or cloud. `
    + `My weights streamed from ${host} and are content-addressed: every block is re-derived byte-for-byte as it loads, so nothing can be tampered with and no host has to be trusted. `
    + `Once I'm loaded, nothing you type ever leaves your device.`;
}

let engine = null, convIds = [], busy = false, armed = false, pending = null;
input.disabled = send.disabled = false; input.placeholder = "Message Q… (model loading — will send the moment it's ready)"; input.focus();

async function generate(text, skipUser) {
  busy = true; input.disabled = send.disabled = true;
  if (!skipUser) bubble("u", text);
  // Answer identity/provenance questions from the grounded truth, not the model's confabulation.
  if (IDENTITY_RX.test(text)) {
    const a = bubble("a", groundedIdentity());
    const stat = document.createElement("div"); stat.className = "stat"; stat.textContent = "grounded · from this instance"; a.after(stat);
    busy = false; input.disabled = send.disabled = false; input.focus(); return;
  }
  const a = bubble("a think", "…"); let first = true;
  const stat = document.createElement("div"); stat.className = "stat";
  try {
    let framed = engine.frameTurn(text, convIds.length > 0);
    if (convIds.length === 0) framed = frameSystem() + framed;
    let turnIds = engine.tokenize(framed);
    if (m.bos && engine.bosId != null && convIds.length === 0) turnIds = [engine.bosId, ...turnIds];
    const res = await engine.generate(convIds.concat(turnIds), { maxNew: m.cap || 256, onToken: ({ text: t, stats }) => {
      if (first && t) { a.classList.remove("think"); a.textContent = ""; first = false; }
      a.textContent = t; log.scrollTop = log.scrollHeight;
      if (stats) stat.textContent = `${stats.tokps ? stats.tokps.toFixed(0) + " tok/s" : ""}${stats.msExec ? " · " + stats.msExec.toFixed(1) + "ms GPU/tok" : ""}${stats.ttft ? " · TTFT " + Math.round(stats.ttft) + "ms" : ""}`;
    } });
    if (first) { a.classList.remove("think"); a.textContent = res.text || "(no output)"; }
    convIds = res.ids; a.after(stat);
  } catch (e) { a.classList.remove("think"); a.textContent = "⚠ " + e.message; }
  busy = false; input.disabled = send.disabled = false; input.focus();
}
async function proactiveGreeting() {
  busy = true; input.disabled = send.disabled = true;
  const a = bubble("a think", "…"); let first = true;
  const stat = document.createElement("div"); stat.className = "stat";
  const FALLBACK = "Hey — I'm Q, running entirely in your browser, no server. My weights streamed from Hugging Face and are verified by re-derivation. What can I help you with?";
  try {
    const P = "This is the very first thing you say to the person who just opened you. You are Q — a private AI running entirely in their browser with no server, your weights streamed from Hugging Face and verified by re-derivation. Greet them warmly in one or two sentences and invite them to ask you anything.";
    let ids = engine.tokenize(engine.frameTurn(P, false));
    if (m.bos && engine.bosId != null) ids = [engine.bosId, ...ids];
    await engine.generate(ids, { maxNew: 64, onToken: ({ text: t, stats }) => { if (first && t) { a.classList.remove("think"); a.textContent = ""; first = false; } a.textContent = t; log.scrollTop = log.scrollHeight; if (stats && stats.tokps) stat.textContent = `${stats.tokps.toFixed(0)} tok/s`; } });
    if (first || a.textContent.trim().length < 4) { a.classList.remove("think"); a.textContent = FALLBACK; } else a.after(stat);
  } catch (e) { a.classList.remove("think"); a.textContent = FALLBACK; }
  busy = false; input.disabled = send.disabled = false; input.focus();
}
function onSend() {
  const text = input.value.trim(); if (!text || busy) return;
  input.value = ""; input.style.height = "auto";
  if (!armed) { pending = text; bubble("u", text); const w = bubble("a think", "…starting the model, one moment…"); w.dataset.pending = "1"; return; }
  generate(text);
}
send.onclick = onSend;
input.onkeydown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSend(); } };
input.oninput = () => { input.style.height = "auto"; input.style.height = Math.min(140, input.scrollHeight) + "px"; };

try {
  if (!navigator.gpu) throw new Error("This browser has no WebGPU — open in Chrome, Edge, or a recent mobile browser.");
  st.textContent = `loading ${m.name} (${m.size})…`;
  const loaded = await loadModel(m, { onStatus: (s) => { if (s) st.textContent = `${m.name}: ${s}`; }, onProgress: (d, t, w) => { st.textContent = `${m.name}: ${w} ${t ? Math.round(100 * d / t) : 0}%`; } });
  if (!loaded || !loaded.gpu) throw new Error("model load failed");
  engine = await createEngine(m, loaded);
  armed = true;
  st.textContent = `${m.name} · ${m.size} · in your browser · ready`;
  input.placeholder = "Message Q…";
  if (pending) { const w = [...log.querySelectorAll(".a")].reverse().find((x) => x.dataset.pending); if (w) w.remove(); const p = pending; pending = null; generate(p, true); }
  else await proactiveGreeting();
} catch (e) { st.textContent = "⚠ " + e.message; bubble("a", "Could not start: " + e.message); }
</script></body></html>