Humuhumu33 commited on
Commit
89dc30e
·
verified ·
1 Parent(s): 63158fe

Unified feature-complete build: clause-streamed voice + 260-char bubbles + PWA/offline SW + self-contained vendor

Browse files
atlas12288.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cb6ea863aa5458c5449b74ffc5697309f66cda724ec0b43430f7d66af3017be2
3
+ size 499
core/holo-orb.js CHANGED
@@ -1,6 +1,252 @@
1
- // holo-orb — the desktop's living Q orb (ORB_DESCRIPTOR), self-contained WebGL2 (no THREE, no deps).
2
- // An icosphere WIREFRAME whose every edge is a live gradient of the OS brand spectrum (longitude → hue), slowly
3
- // spinning on two φ-ratio axes and breathing with fractal noise "consciousness at rest." Falls back to a CSS orb.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  const SPECTRUM = [[1,.231,.42],[1,.62,.173],[1,.886,.29],[.275,.878,.541],[.169,.831,1],[.357,.549,1],[.78,.482,1],[1,.231,.42]];
5
  function hueAt(t){ t=(t%1+1)%1; const n=SPECTRUM.length-1, f=t*n, i=Math.floor(f), k=f-i, a=SPECTRUM[i], b=SPECTRUM[Math.min(i+1,n)]; return [a[0]+(b[0]-a[0])*k, a[1]+(b[1]-a[1])*k, a[2]+(b[2]-a[2])*k]; }
6
  function norm(v){ const l=Math.hypot(v[0],v[1],v[2])||1; return [v[0]/l,v[1]/l,v[2]/l]; }
@@ -17,30 +263,29 @@ function icosphere(sub){
17
  }
18
  function mat4Perspective(fovy, aspect, near, far){ const f=1/Math.tan(fovy/2), nf=1/(near-far); return new Float32Array([f/aspect,0,0,0, 0,f,0,0, 0,0,(far+near)*nf,-1, 0,0,2*far*near*nf,0]); }
19
 
20
- export function mountOrb(canvas){
21
  let gl; try { gl = canvas.getContext("webgl2", { alpha:true, antialias:true, premultipliedAlpha:false }); } catch(e){}
22
- if(!gl) return { fallback:true };
23
  const { V, E } = icosphere(3);
24
- // flatten edge endpoints → per-vertex position + spectrum color (longitude → hue, so it wraps like #omni)
25
  const pos=new Float32Array(E.length*3), col=new Float32Array(E.length*3);
26
- for(let i=0;i<E.length;i++){ const p=V[E[i]]; pos[i*3]=p[0]; pos[i*3+1]=p[1]; pos[i*3+2]=p[2]; const h=hueAt(Math.atan2(p[2],p[0])/(2*Math.PI)+0.5), c=hueAt; col[i*3]=h[0]; col[i*3+1]=h[1]; col[i*3+2]=h[2]; }
27
  const vs=`#version 300 es
28
  in vec3 aPos; in vec3 aCol; uniform mat4 uProj; uniform float uT; out vec3 vCol; out float vD;
29
  void main(){
30
- float a=uT*0.9, ca=cos(a), sa=sin(a); // spin (7s/turn feel)
31
  vec3 p=vec3(ca*aPos.x+sa*aPos.z, aPos.y, -sa*aPos.x+ca*aPos.z);
32
- float ax=uT*0.556, cx=cos(ax), sx=sin(ax); // second axis in φ ratio
33
  p=vec3(p.x, cx*p.y - sx*p.z, sx*p.y + cx*p.z);
34
- float br=1.0 + 0.05*sin(uT*1.3+p.y*3.0) + 0.035*sin(uT*0.7+p.x*4.0); // breathe / living skin
35
  p*=br; vD=p.z; p.z-=3.15;
36
  gl_Position=uProj*vec4(p,1.0); vCol=aCol;
37
  }`;
38
  const fs=`#version 300 es
39
  precision highp float; in vec3 vCol; in float vD; out vec4 o;
40
- void main(){ float d=0.72+0.28*smoothstep(-1.0,1.0,vD); o=vec4(vCol*d, 0.92); }`; // front edges brighter → depth
41
  const sh=(t,s)=>{ const o=gl.createShader(t); gl.shaderSource(o,s); gl.compileShader(o); if(!gl.getShaderParameter(o,gl.COMPILE_STATUS)){ console.error("[orb] shader:", gl.getShaderInfoLog(o)); } return o; };
42
  const prog=gl.createProgram(); gl.attachShader(prog,sh(gl.VERTEX_SHADER,vs)); gl.attachShader(prog,sh(gl.FRAGMENT_SHADER,fs)); gl.linkProgram(prog);
43
- if(!gl.getProgramParameter(prog,gl.LINK_STATUS)){ console.error("[orb] link:", gl.getProgramInfoLog(prog)); return { fallback:true }; }
44
  gl.useProgram(prog);
45
  const mkBuf=(data,loc)=>{ const b=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER,b); gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc,3,gl.FLOAT,false,0,0); };
46
  mkBuf(pos, gl.getAttribLocation(prog,"aPos")); mkBuf(col, gl.getAttribLocation(prog,"aCol"));
@@ -50,5 +295,7 @@ export function mountOrb(canvas){
50
  function resize(){ const dpr=Math.min(window.devicePixelRatio||1, 2.5); const w=Math.max(2, canvas.clientWidth), h=Math.max(2, canvas.clientHeight); const W=Math.round(w*dpr), H=Math.round(h*dpr); if(canvas.width!==W||canvas.height!==H){ canvas.width=W; canvas.height=H; } gl.viewport(0,0,canvas.width,canvas.height); gl.uniformMatrix4fv(uProj,false, mat4Perspective(45*Math.PI/180, canvas.width/canvas.height, 0.1, 10)); }
51
  function frame(){ if(stopped) return; resize(); const t=(performance.now()-t0)/1000; gl.clearColor(0,0,0,0); gl.clear(gl.COLOR_BUFFER_BIT); gl.uniform1f(uT,t); gl.drawArrays(gl.LINES,0,E.length); raf=requestAnimationFrame(frame); }
52
  frame();
53
- return { stop(){ stopped=true; cancelAnimationFrame(raf); }, fallback:false };
54
  }
 
 
 
1
+ // holo-orb.jsTHE canonical living Q orb (shared by the standalone Q chat and the messenger; the messenger's
2
+ // holo-q-orb-live.mjs re-exports this file). mountOrb(canvas, opts?) { stop, fallback, mode, ... } the API is
3
+ // a superset of the original 54-line WebGL orb (opts optional, {stop,fallback} preserved), so existing callers
4
+ // are unaffected while every surface gains the enhanced renderer. Original preserved as holo-orb.js.pre-converge.bak.
5
+ //
6
+ // PRIMARY: a NATIVE-WebGPU raymarched volume rendered on a dedicated Web Worker via OffscreenCanvas. The render
7
+ // loop lives on the worker, PHYSICALLY off the main thread, so the orb stays glass-smooth and NEVER freezes even
8
+ // while the host's main thread is busy (React, Q inference, message churn). It binds the real GPU adapter
9
+ // (powerPreference:"high-performance", no fallback) → 100% native WebGPU, and renders at native DPR × SSAA for a
10
+ // crisp, hyper-real look. Fully self-contained: the worker is spawned from a Blob URL with inline WGSL — no deps,
11
+ // no import map, no extra files to serve, so it works in every environment (dev SPA and the real app alike).
12
+ //
13
+ // FALLBACK: the original self-contained WebGL2 wireframe icosphere (kept verbatim below), then a CSS/SVG orb.
14
+ // The gate is fail-closed and probe-before-transfer: the worker confirms a GPU adapter BEFORE the canvas is
15
+ // transferred, so a probe failure leaves the canvas reusable for the WebGL2 floor.
16
+ //
17
+ // mountOrb(canvas) → { stop(), fallback:boolean, mode }
18
+
19
+ // ─────────────────────────────────────────────────────────────────────────────────────────────────────────
20
+ // WGSL — fullscreen-triangle vertex + a raymarched, noise-displaced SDF sphere with the OS brand spectrum
21
+ // (OKLAB-interpolated), a triangular lattice shell, thin-film iridescence, an inner living nebula, ACES filmic
22
+ // tonemap and temporal dither. Idle-animated (breath + spin + shimmer) so it's alive at rest with ZERO main-
23
+ // thread involvement. Adapted from the OS orb (usr/lib/holo/voice/holo-voice-orb-gpu.mjs).
24
+ // ─────────────────────────────────────────────────────────────────────────────────────────────────────────
25
+ const WGSL = `
26
+ struct U { res: vec2f, time: f32, lvl: f32 };
27
+ @group(0) @binding(0) var<uniform> u: U;
28
+
29
+ const STOPS = array<vec3f, 8>(
30
+ vec3f(1.0, 0.231, 0.42), vec3f(1.0, 0.62, 0.173), vec3f(1.0, 0.886, 0.29), vec3f(0.275, 0.878, 0.541),
31
+ vec3f(0.169, 0.831, 1.0), vec3f(0.357, 0.549, 1.0), vec3f(0.78, 0.482, 1.0), vec3f(1.0, 0.231, 0.42));
32
+
33
+ @vertex fn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {
34
+ var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
35
+ return vec4f(p[vi], 0.0, 1.0);
36
+ }
37
+ fn hash(p3i: vec3f) -> f32 { var p3 = fract(p3i * 0.1031); p3 = p3 + dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); }
38
+ fn vnoise(x: vec3f) -> f32 {
39
+ let i = floor(x); let f = fract(x); let w = f * f * (3.0 - 2.0 * f);
40
+ let n000 = hash(i + vec3f(0.0,0.0,0.0)); let n100 = hash(i + vec3f(1.0,0.0,0.0));
41
+ let n010 = hash(i + vec3f(0.0,1.0,0.0)); let n110 = hash(i + vec3f(1.0,1.0,0.0));
42
+ let n001 = hash(i + vec3f(0.0,0.0,1.0)); let n101 = hash(i + vec3f(1.0,0.0,1.0));
43
+ let n011 = hash(i + vec3f(0.0,1.0,1.0)); let n111 = hash(i + vec3f(1.0,1.0,1.0));
44
+ let x00 = mix(n000,n100,w.x); let x10 = mix(n010,n110,w.x); let x01 = mix(n001,n101,w.x); let x11 = mix(n011,n111,w.x);
45
+ return mix(mix(x00,x10,w.y), mix(x01,x11,w.y), w.z) * 2.0 - 1.0;
46
+ }
47
+ fn fbm(p0: vec3f) -> f32 { var p = p0; var a = 0.5; var s = 0.0; for (var i = 0; i < 5; i = i + 1) { s = s + a * vnoise(p); p = p * 1.9; a = a * 0.5; } return s; }
48
+ fn srgb2lin(c: vec3f) -> vec3f { return select(c/12.92, pow((c+0.055)/1.055, vec3f(2.4)), c > vec3f(0.04045)); }
49
+ fn lin2srgb(c: vec3f) -> vec3f { let x = max(c, vec3f(0.0)); return select(x*12.92, 1.055*pow(x, vec3f(1.0/2.4))-0.055, x > vec3f(0.0031308)); }
50
+ fn lin2oklab(c: vec3f) -> vec3f {
51
+ let l = 0.4122214708*c.r + 0.5363325363*c.g + 0.0514459929*c.b;
52
+ let m = 0.2119034982*c.r + 0.6806995451*c.g + 0.1073969566*c.b;
53
+ let s = 0.0883024619*c.r + 0.2817188376*c.g + 0.6299787005*c.b;
54
+ let l_ = pow(max(l,0.0),1.0/3.0); let m_ = pow(max(m,0.0),1.0/3.0); let s_ = pow(max(s,0.0),1.0/3.0);
55
+ return vec3f(0.2104542553*l_+0.7936177850*m_-0.0040720468*s_, 1.9779984951*l_-2.4285922050*m_+0.4505937099*s_, 0.0259040371*l_+0.7827717662*m_-0.8086757660*s_);
56
+ }
57
+ fn oklab2srgb(c: vec3f) -> vec3f {
58
+ let l_ = c.x+0.3963377774*c.y+0.2158037573*c.z; let m_ = c.x-0.1055613458*c.y-0.0638541728*c.z; let s_ = c.x-0.0894841775*c.y-1.2914855480*c.z;
59
+ let l = l_*l_*l_; let m = m_*m_*m_; let s = s_*s_*s_;
60
+ let lin = vec3f(4.0767416621*l-3.3077115913*m+0.2309699292*s, -1.2684380046*l+2.6097574011*m-0.3413193965*s, -0.0041960863*l-0.7034186147*m+1.7076147010*s);
61
+ return lin2srgb(lin);
62
+ }
63
+ fn spec(t0: f32) -> vec3f { let t = fract(t0) * 7.0; let k = clamp(i32(floor(t)), 0, 6); let a = lin2oklab(srgb2lin(STOPS[k])); let b = lin2oklab(srgb2lin(STOPS[k+1])); return oklab2srgb(mix(a, b, fract(t))); }
64
+ fn sdf(p: vec3f) -> f32 {
65
+ let R = 0.82 + u.lvl * 0.05 + sin(u.time * 0.6) * 0.012;
66
+ let warp = vec3f(u.time*0.05, u.time*0.06, u.time*0.07);
67
+ let disp = fbm(p * 1.7 + warp) * (0.07 + u.lvl * 0.22);
68
+ return length(p) - R - disp;
69
+ }
70
+ fn nrm(p: vec3f) -> vec3f { let e = vec2f(0.0012, 0.0); return normalize(vec3f(sdf(p+e.xyy)-sdf(p-e.xyy), sdf(p+e.yxy)-sdf(p-e.yxy), sdf(p+e.yyx)-sdf(p-e.yyx))); }
71
+ fn gline(x: f32) -> f32 { return smoothstep(0.42, 0.5, abs(fract(x) - 0.5)); }
72
+ fn ign(p: vec2f) -> f32 { return fract(52.9829189 * fract(dot(p, vec2f(0.06711056, 0.00583715)))); }
73
+ fn aces(x: vec3f) -> vec3f { let a=2.51; let b=0.03; let c=2.43; let d=0.59; let e=0.14; return clamp((x*(a*x+b))/(x*(c*x+d)+e), vec3f(0.0), vec3f(1.0)); }
74
+
75
+ @fragment fn fs(@builtin(position) fc: vec4f) -> @location(0) vec4f {
76
+ let uv = (fc.xy - 0.5 * u.res) / u.res.y;
77
+ let ro = vec3f(0.0, 0.0, 3.6);
78
+ let rd = normalize(vec3f(uv.x, -uv.y, -1.5));
79
+ let spin = u.time / 7.0 * (1.0 + u.lvl * 1.4);
80
+ var t = 0.0; var glow = 0.0; var neb = 0.0; var hit = false; var hp = vec3f(0.0);
81
+ var omega = 1.2; var prevD = 1e9; var stepLen = 0.0;
82
+ for (var i = 0; i < 96; i = i + 1) {
83
+ let p = ro + rd * t; let d = sdf(p);
84
+ if (omega > 1.0 && (d + prevD) < stepLen) { t = t - stepLen; omega = 1.0; prevD = 1e9; continue; }
85
+ prevD = d;
86
+ glow = glow + 0.012 / (1.0 + d * d * 42.0);
87
+ if (d < 0.0) { neb = neb + (0.5 + 0.5 * fbm(p * 2.7 + vec3f(u.time * 0.09))) * 0.05; }
88
+ if (d < 0.0015) { hit = true; hp = p; break; }
89
+ stepLen = max(d * omega, 0.004); t = t + stepLen;
90
+ if (t > 6.0) { break; }
91
+ }
92
+ var col = vec3f(0.0); var alpha = 0.0;
93
+ if (hit) {
94
+ let n = nrm(hp);
95
+ let lon = atan2(n.x, n.z) / 6.2831853 + 0.5;
96
+ let lat = acos(clamp(n.y, -1.0, 1.0)) / 3.14159265;
97
+ let hue = lon + spin + 0.18 * n.y;
98
+ let base = spec(hue);
99
+ let fres = pow(1.0 - max(dot(n, -rd), 0.0), 2.5);
100
+ let ld = normalize(vec3f(-0.4, 0.7, 0.5));
101
+ let diff = 0.5 + 0.5 * max(dot(n, ld), 0.0);
102
+ let irid = spec(hue + fres * 0.30);
103
+ let bodyHue = mix(base, irid, fres * 0.45);
104
+ let A = lon * 18.0; let B = lat * 11.0; let pf = sin(lat * 3.14159265);
105
+ let g = max(gline(B), max(gline(A + B * 0.5), gline(A - B * 0.5))) * pf;
106
+ let face = bodyHue * (0.28 + 0.30 * diff);
107
+ let dofs = 0.020 * (0.5 + fres);
108
+ let edgeRGB = vec3f(spec(hue - dofs).r, spec(hue).g, spec(hue + dofs).b);
109
+ let edge = (edgeRGB * 1.7 + vec3f(0.22, 0.22, 0.32)) * (0.7 + 0.6 * fres);
110
+ col = mix(face, edge, g) + bodyHue * fres * 0.55;
111
+ col = col * (0.9 + u.lvl * 0.45);
112
+ alpha = max(g, 0.34 + 0.45 * fres);
113
+ }
114
+ let ncol = spec(spin + 0.55 + neb);
115
+ col = col + ncol * neb * (0.7 + u.lvl * 0.9);
116
+ let gcol = spec(spin + 0.25);
117
+ col = col + gcol * glow * (0.6 + u.lvl * 1.0);
118
+ alpha = max(alpha, clamp((glow + neb * 0.6) * 1.2, 0.0, 1.0));
119
+ col = aces(col * 1.18);
120
+ col = col + (ign(fc.xy + u.time * 60.0) - 0.5) * (1.5 / 255.0);
121
+ col = clamp(col, vec3f(0.0), vec3f(1.0));
122
+ return vec4f(col * alpha, alpha);
123
+ }`;
124
+
125
+ // ── the worker body (classic worker, spawned from a Blob URL). WGSL is injected as a JS string literal so the
126
+ // whole thing is self-contained — nothing extra is fetched, so it runs in any serve environment. ──
127
+ const WORKER_BODY = [
128
+ "const WGSL = __WGSL__;",
129
+ "let dev=null, ctx=null, pipeline=null, bind=null, ubuf=null, uf=null, raf=0, running=false, dead=false, canvas=null;",
130
+ "let dpr=1, ss=1, cssW=64, cssH=64;",
131
+ "let mode=0, extLevel=-1, cur=0.15, dbg=false, fc=0;", // mode: 0 idle · 1 listening · 2 thinking · 3 speaking. cur = eased base level; extLevel = live speech amplitude (0..1) or -1. dbg = optional level readback.
132
+ "const NOW=function(){return (typeof performance!=='undefined')?performance.now():Date.now();};",
133
+ "const RAF=(typeof requestAnimationFrame==='function')?requestAnimationFrame:function(f){return setTimeout(function(){f(NOW());},16);};",
134
+ "const CAF=(typeof cancelAnimationFrame==='function')?cancelAnimationFrame:clearTimeout;",
135
+ "function resize(){var w=Math.max(1,Math.round(cssW*dpr*ss)),h=Math.max(1,Math.round(cssH*dpr*ss)); if(canvas.width!==w||canvas.height!==h){canvas.width=w;canvas.height=h;}}",
136
+ // Q-state-reactive level: idle = calm breath; listening = alert; thinking = brighter + faster (higher base +
137
+ // quicker oscillation → spin & glow rise, they scale with lvl in the WGSL); speaking = pulse to live amplitude
138
+ // (extLevel) or a synthetic speech cadence. cur eases between modes so transitions read as intent, not a snap.",
139
+ "function frame(){ if(!running||dead) return; var t=NOW()/1000; var base, osc;",
140
+ " if(mode===2){ base=0.50; osc=0.12*Math.sin(t*3.4); }",
141
+ " else if(mode===1){ base=0.24; osc=0.05*Math.sin(t*2.0); }",
142
+ " else if(mode===3){ base=0.32; osc=(extLevel>=0?0.0:0.30*Math.abs(Math.sin(t*6.5))); }",
143
+ " else { base=0.15; osc=0.10*Math.sin(t*1.1); }",
144
+ " var live=(extLevel>=0&&(mode===1||mode===3))?extLevel*0.6:0.0;", // REAL audio amplitude swells the orb (listening to you · speaking as Q)
145
+ " cur+=(base-cur)*0.06; var lvl=Math.max(0.0, cur+osc+live);",
146
+ " if(dbg&&((fc++%15)===0)){ try{ self.postMessage({t:'lvl', v:lvl, mode:mode}); }catch(e){} }",
147
+ " uf[0]=canvas.width; uf[1]=canvas.height; uf[2]=t; uf[3]=lvl; dev.queue.writeBuffer(ubuf,0,uf); var view; try{ view=ctx.getCurrentTexture().createView(); }catch(e){ raf=RAF(frame); return; } var enc=dev.createCommandEncoder(); var pass=enc.beginRenderPass({colorAttachments:[{view:view,clearValue:{r:0,g:0,b:0,a:0},loadOp:'clear',storeOp:'store'}]}); pass.setPipeline(pipeline); pass.setBindGroup(0,bind); pass.draw(3); pass.end(); dev.queue.submit([enc.finish()]); raf=RAF(frame); }",
148
+ "self.onmessage=async function(e){ var m=e.data||{}; try{",
149
+ " if(m.t==='probe'){ try{ if(!navigator.gpu) throw new Error('no gpu'); var a=await navigator.gpu.requestAdapter({powerPreference:'high-performance'}); if(!a) throw new Error('no adapter'); var d=await a.requestDevice(); if(d.destroy) d.destroy(); self.postMessage({t:'probe-ok'}); }catch(err){ self.postMessage({t:'fail',err:'probe: '+String(err&&err.message||err)}); } return; }",
150
+ " if(m.t==='init'){ try{",
151
+ " canvas=m.canvas; dpr=m.dpr||1; ss=m.ss||1; cssW=m.cssW||64; cssH=m.cssH||64; dbg=!!m.debug;",
152
+ " var a=await navigator.gpu.requestAdapter({powerPreference:'high-performance'}); if(!a) throw new Error('no adapter'); dev=await a.requestDevice(); if(dev.lost) dev.lost.then(function(){dead=true;});",
153
+ " ctx=canvas.getContext('webgpu'); if(!ctx) throw new Error('no webgpu ctx'); var fmt=navigator.gpu.getPreferredCanvasFormat(); ctx.configure({device:dev,format:fmt,alphaMode:'premultiplied'});",
154
+ " uf=new Float32Array(4); ubuf=dev.createBuffer({size:16,usage:GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST});",
155
+ " dev.pushErrorScope('validation'); var mod=dev.createShaderModule({code:WGSL});",
156
+ " pipeline=dev.createRenderPipeline({layout:'auto',vertex:{module:mod,entryPoint:'vs'},fragment:{module:mod,entryPoint:'fs',targets:[{format:fmt,blend:{color:{srcFactor:'one',dstFactor:'one-minus-src-alpha'},alpha:{srcFactor:'one',dstFactor:'one-minus-src-alpha'}}}]},primitive:{topology:'triangle-list'}});",
157
+ " var perr=await dev.popErrorScope(); if(perr) throw new Error('wgsl: '+perr.message);",
158
+ " bind=dev.createBindGroup({layout:pipeline.getBindGroupLayout(0),entries:[{binding:0,resource:{buffer:ubuf}}]});",
159
+ " resize(); running=true; raf=RAF(frame); self.postMessage({t:'ready'});",
160
+ " }catch(err){ self.postMessage({t:'fail',err:String(err&&err.message||err)}); } return; }",
161
+ " if(!canvas) return;",
162
+ " if(m.t==='size'){ cssW=m.cssW||cssW; cssH=m.cssH||cssH; dpr=m.dpr||dpr; resize(); }",
163
+ " else if(m.t==='sig'){ mode=m.mode||0; extLevel=(typeof m.level==='number'?m.level:-1); }",
164
+ " else if(m.t==='stop'){ running=false; if(raf){ CAF(raf); raf=0; } try{ if(dev&&dev.destroy) dev.destroy(); }catch(e){} }",
165
+ "}catch(err){ try{ self.postMessage({t:'fail',err:String(err&&err.message||err)}); }catch(e2){} } };",
166
+ ].join("\n");
167
+
168
+ const WORKER_SRC = WORKER_BODY.replace("__WGSL__", JSON.stringify(WGSL));
169
+
170
+ // mount the WebGPU worker orb. Returns a handle (sync) that upgrades async; on any failure it falls back to the
171
+ // WebGL2 wireframe on the SAME canvas (probe-before-transfer keeps the canvas reusable). Returns null only when
172
+ // the platform can't do OffscreenCanvas/Worker/WebGPU at all → caller uses WebGL2 directly.
173
+ function mountGpuOrb(canvas, opts) {
174
+ opts = opts || {};
175
+ const ok = typeof navigator !== "undefined" && navigator.gpu &&
176
+ typeof OffscreenCanvas !== "undefined" && typeof Worker !== "undefined" &&
177
+ canvas && canvas.transferControlToOffscreen;
178
+ if (!ok) return null;
179
+
180
+ let worker = null, url = null, stopped = false, transferred = false, fellBack = null, timer = 0, ro = null, pendingSig = null, onQState = null;
181
+ const handle = { fallback: false, mode: "webgpu-worker",
182
+ stop() {
183
+ stopped = true; if (timer) { clearTimeout(timer); timer = 0; }
184
+ try { if (ro) ro.disconnect(); } catch (e) {}
185
+ try { if (typeof window !== "undefined" && onQState) window.removeEventListener("holo-q-state", onQState); } catch (e) {}
186
+ try { if (worker) { worker.postMessage({ t: "stop" }); worker.terminate(); } } catch (e) {}
187
+ try { if (url) URL.revokeObjectURL(url); } catch (e) {}
188
+ if (fellBack && fellBack.stop) { try { fellBack.stop(); } catch (e) {} }
189
+ } };
190
+
191
+ function toWebgl() {
192
+ if (timer) { clearTimeout(timer); timer = 0; }
193
+ try { if (worker) worker.terminate(); } catch (e) {}
194
+ if (stopped || transferred || fellBack) return; // transferred → canvas burned, can't reuse (rare, post-probe only)
195
+ fellBack = mountWebglOrb(canvas); // same untouched canvas → the proven WebGL2 wireframe
196
+ handle.mode = (fellBack && !fellBack.fallback) ? "webgl" : "none";
197
+ }
198
+
199
+ try { url = URL.createObjectURL(new Blob([WORKER_SRC], { type: "text/javascript" })); worker = new Worker(url); }
200
+ catch (e) { return mountWebglOrb(canvas); }
201
+
202
+ const dpr = () => Math.min((typeof window !== "undefined" && window.devicePixelRatio) || 1, 2);
203
+ const cw = () => canvas.clientWidth || 64, ch = () => canvas.clientHeight || 64;
204
+
205
+ worker.onmessage = (e) => {
206
+ const m = e.data || {};
207
+ if (m.t === "probe-ok") {
208
+ if (stopped) return;
209
+ let off; try { off = canvas.transferControlToOffscreen(); transferred = true; }
210
+ catch (err) { toWebgl(); return; }
211
+ try { worker.postMessage({ t: "init", canvas: off, dpr: dpr(), ss: 1.5, cssW: cw(), cssH: ch(), debug: !!opts.debug }, [off]); }
212
+ catch (err) { toWebgl(); }
213
+ } else if (m.t === "ready") { if (timer) { clearTimeout(timer); timer = 0; } if (pendingSig) { forwardSig(pendingSig.mode, pendingSig.level); pendingSig = null; } } // painting on the worker
214
+ else if (m.t === "lvl") { handle.level = m.v; handle.stateMode = m.mode; if (typeof handle.onLevel === "function") { try { handle.onLevel(m.v, m.mode); } catch (e) {} } } // optional debug readback (opts.debug)
215
+ else if (m.t === "fail") { toWebgl(); }
216
+ };
217
+ worker.onerror = () => toWebgl();
218
+ timer = setTimeout(toWebgl, 4500);
219
+ try { worker.postMessage({ t: "probe" }); } catch (e) { toWebgl(); }
220
+
221
+ if (typeof ResizeObserver !== "undefined") {
222
+ ro = new ResizeObserver(() => { if (transferred && worker && !stopped) { try { worker.postMessage({ t: "size", cssW: cw(), cssH: ch(), dpr: dpr() }); } catch (e) {} } });
223
+ try { ro.observe(canvas); } catch (e) {}
224
+ }
225
+
226
+ // ── Q-state reactivity: forward the global `holo-q-state` events to the worker (the messenger dispatches them
227
+ // when Q is thinking/listening/speaking), so the orb visibly REACTS instead of only idling. Buffered until the
228
+ // worker is live (pendingSig), and torn down with the orb (stop() removes the listener). ──
229
+ const MODE_MAP = { idle: 0, listening: 1, thinking: 2, speaking: 3 };
230
+ function forwardSig(modeNum, level) {
231
+ if (worker && transferred && !stopped) { try { worker.postMessage({ t: "sig", mode: modeNum, level: level }); } catch (e) {} }
232
+ else pendingSig = { mode: modeNum, level: level };
233
+ }
234
+ handle.signal = function (s) { s = s || {}; forwardSig(MODE_MAP[s.mode] || 0, (typeof s.level === "number") ? s.level : -1); };
235
+ onQState = (e) => handle.signal((e && e.detail) || {});
236
+ if (typeof window !== "undefined") { try { window.addEventListener("holo-q-state", onQState); } catch (e) {} }
237
+ return handle;
238
+ }
239
+
240
+ export function mountOrb(canvas, opts) {
241
+ const gpu = mountGpuOrb(canvas, opts); // native-WebGPU worker orb (off the main thread) — the hero
242
+ if (gpu) return gpu;
243
+ return mountWebglOrb(canvas); // no WebGPU/Worker/OffscreenCanvas → the WebGL2 wireframe floor
244
+ }
245
+
246
+ // ─────────────────────────────────────────────────────────────────────────────────────────────────────────
247
+ // FALLBACK — the original self-contained WebGL2 wireframe icosphere (VERBATIM from hf-space-q-chat/core/
248
+ // holo-orb.js), so where WebGPU/worker isn't available the messenger's Q orb still animates as it always has.
249
+ // ─────────────────────────────────────────────────────────────────────────────────────────────────────────
250
  const SPECTRUM = [[1,.231,.42],[1,.62,.173],[1,.886,.29],[.275,.878,.541],[.169,.831,1],[.357,.549,1],[.78,.482,1],[1,.231,.42]];
251
  function hueAt(t){ t=(t%1+1)%1; const n=SPECTRUM.length-1, f=t*n, i=Math.floor(f), k=f-i, a=SPECTRUM[i], b=SPECTRUM[Math.min(i+1,n)]; return [a[0]+(b[0]-a[0])*k, a[1]+(b[1]-a[1])*k, a[2]+(b[2]-a[2])*k]; }
252
  function norm(v){ const l=Math.hypot(v[0],v[1],v[2])||1; return [v[0]/l,v[1]/l,v[2]/l]; }
 
263
  }
264
  function mat4Perspective(fovy, aspect, near, far){ const f=1/Math.tan(fovy/2), nf=1/(near-far); return new Float32Array([f/aspect,0,0,0, 0,f,0,0, 0,0,(far+near)*nf,-1, 0,0,2*far*near*nf,0]); }
265
 
266
+ function mountWebglOrb(canvas){
267
  let gl; try { gl = canvas.getContext("webgl2", { alpha:true, antialias:true, premultipliedAlpha:false }); } catch(e){}
268
+ if(!gl) return { fallback:true, mode:"none", stop(){} };
269
  const { V, E } = icosphere(3);
 
270
  const pos=new Float32Array(E.length*3), col=new Float32Array(E.length*3);
271
+ for(let i=0;i<E.length;i++){ const p=V[E[i]]; pos[i*3]=p[0]; pos[i*3+1]=p[1]; pos[i*3+2]=p[2]; const h=hueAt(Math.atan2(p[2],p[0])/(2*Math.PI)+0.5); col[i*3]=h[0]; col[i*3+1]=h[1]; col[i*3+2]=h[2]; }
272
  const vs=`#version 300 es
273
  in vec3 aPos; in vec3 aCol; uniform mat4 uProj; uniform float uT; out vec3 vCol; out float vD;
274
  void main(){
275
+ float a=uT*0.9, ca=cos(a), sa=sin(a);
276
  vec3 p=vec3(ca*aPos.x+sa*aPos.z, aPos.y, -sa*aPos.x+ca*aPos.z);
277
+ float ax=uT*0.556, cx=cos(ax), sx=sin(ax);
278
  p=vec3(p.x, cx*p.y - sx*p.z, sx*p.y + cx*p.z);
279
+ float br=1.0 + 0.05*sin(uT*1.3+p.y*3.0) + 0.035*sin(uT*0.7+p.x*4.0);
280
  p*=br; vD=p.z; p.z-=3.15;
281
  gl_Position=uProj*vec4(p,1.0); vCol=aCol;
282
  }`;
283
  const fs=`#version 300 es
284
  precision highp float; in vec3 vCol; in float vD; out vec4 o;
285
+ void main(){ float d=0.72+0.28*smoothstep(-1.0,1.0,vD); o=vec4(vCol*d, 0.92); }`;
286
  const sh=(t,s)=>{ const o=gl.createShader(t); gl.shaderSource(o,s); gl.compileShader(o); if(!gl.getShaderParameter(o,gl.COMPILE_STATUS)){ console.error("[orb] shader:", gl.getShaderInfoLog(o)); } return o; };
287
  const prog=gl.createProgram(); gl.attachShader(prog,sh(gl.VERTEX_SHADER,vs)); gl.attachShader(prog,sh(gl.FRAGMENT_SHADER,fs)); gl.linkProgram(prog);
288
+ if(!gl.getProgramParameter(prog,gl.LINK_STATUS)){ console.error("[orb] link:", gl.getProgramInfoLog(prog)); return { fallback:true, mode:"none", stop(){} }; }
289
  gl.useProgram(prog);
290
  const mkBuf=(data,loc)=>{ const b=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER,b); gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc,3,gl.FLOAT,false,0,0); };
291
  mkBuf(pos, gl.getAttribLocation(prog,"aPos")); mkBuf(col, gl.getAttribLocation(prog,"aCol"));
 
295
  function resize(){ const dpr=Math.min(window.devicePixelRatio||1, 2.5); const w=Math.max(2, canvas.clientWidth), h=Math.max(2, canvas.clientHeight); const W=Math.round(w*dpr), H=Math.round(h*dpr); if(canvas.width!==W||canvas.height!==H){ canvas.width=W; canvas.height=H; } gl.viewport(0,0,canvas.width,canvas.height); gl.uniformMatrix4fv(uProj,false, mat4Perspective(45*Math.PI/180, canvas.width/canvas.height, 0.1, 10)); }
296
  function frame(){ if(stopped) return; resize(); const t=(performance.now()-t0)/1000; gl.clearColor(0,0,0,0); gl.clear(gl.COLOR_BUFFER_BIT); gl.uniform1f(uT,t); gl.drawArrays(gl.LINES,0,E.length); raf=requestAnimationFrame(frame); }
297
  frame();
298
+ return { stop(){ stopped=true; cancelAnimationFrame(raf); }, fallback:false, mode:"webgl" };
299
  }
300
+
301
+ export default mountOrb;
core/listen.js CHANGED
@@ -1,21 +1,27 @@
1
- // core/listen.js — on-device speech-to-text (Q's ear). The microphone PCM is transcribed ENTIRELY in
2
- // the browser by Whisper-tiny (transformers.js, ORT-WASM) — no audio EVER leaves the device, no server.
3
- // Only the model WEIGHTS stream from HuggingFace on first use (content-addressed, then cached offline in
4
- // the browser) the same serverless ethos as Q's brain. The runtime (transformers.js + the ORT wasm) is
5
- // vendored under ../vendor/transformers, so nothing but the model is fetched.
6
- //
7
- // API (deliberately tiny — abstract the complexity, expose two verbs):
8
- // const ear = createEar();
9
- // await ear.start(); // opens the mic, begins capturing 16 kHz mono PCM
10
- // const text = await ear.stop(); // ends capture, transcribes on-device, returns the words
11
- // ear.cancel(); // drop the mic, transcribe nothing
12
- // ear.available() // false where getUserMedia is missing → caller hides the affordance
13
-
14
- import { pipeline, env } from "/_shared/voice/vendor/transformers/transformers.js";
15
 
16
  const MODEL = "onnx-community/whisper-tiny"; // ~40 MB q8; streams from HF, runs on-device
17
  let _pipe = null, _loading = null;
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  // Load the recognizer once. Runtime is vendored; only the model comes from HF (allowRemoteModels).
20
  export async function loadEar(onProgress) {
21
  if (_pipe) return _pipe;
@@ -23,13 +29,7 @@ export async function loadEar(onProgress) {
23
  _loading = (async () => {
24
  env.allowRemoteModels = true; // weights stream from HuggingFace…
25
  env.allowLocalModels = false; // …not from disk
26
- try {
27
- const wasm = new URL("/_shared/voice/vendor/transformers/", import.meta.url).href; // vendored ORT wasm — no CDN
28
- if (env.backends && env.backends.onnx && env.backends.onnx.wasm) {
29
- env.backends.onnx.wasm.wasmPaths = wasm;
30
- env.backends.onnx.wasm.proxy = true; // run ORT in a worker so the UI never janks while it thinks
31
- }
32
- } catch {}
33
  _pipe = await pipeline("automatic-speech-recognition", MODEL, {
34
  device: "wasm", dtype: "q8",
35
  progress_callback: (p) => { try { onProgress && onProgress(p); } catch {} },
@@ -46,90 +46,57 @@ export async function transcribe(pcm16k, onProgress) {
46
  return ((Array.isArray(r) ? r.map((x) => x.text).join(" ") : (r && r.text)) || "").trim();
47
  }
48
 
49
- // A press-to-talk capture session: opens the mic at 16 kHz mono (no resample needed → Whisper's rate),
50
- // buffers the samples, and on stop() transcribes them on-device.
51
  export function createEar() {
52
  let ctx = null, stream = null, node = null, src = null, chunks = [], recording = false;
53
-
54
  const available = () => !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
55
-
56
  async function start() {
57
  if (recording) return;
58
  stream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true } });
59
- // A 16 kHz context means the captured Float32 is already at Whisper's sample rate — no resampling.
60
  ctx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
61
- src = ctx.createMediaStreamSource(stream);
62
- node = ctx.createScriptProcessor(4096, 1, 1);
63
  chunks = []; recording = true;
64
  node.onaudioprocess = (e) => { if (recording) chunks.push(new Float32Array(e.inputBuffer.getChannelData(0))); };
65
  src.connect(node); node.connect(ctx.destination);
66
  }
67
-
68
  function _teardown() {
69
  recording = false;
70
- try { node && node.disconnect(); } catch {}
71
- try { src && src.disconnect(); } catch {}
72
- try { stream && stream.getTracks().forEach((t) => t.stop()); } catch {}
73
- try { ctx && ctx.close(); } catch {}
74
  node = src = stream = ctx = null;
75
  }
76
-
77
- function _flatten() {
78
- let n = 0; for (const c of chunks) n += c.length;
79
- const out = new Float32Array(n); let o = 0;
80
- for (const c of chunks) { out.set(c, o); o += c.length; }
81
- chunks = []; return out;
82
- }
83
-
84
- async function stop(onProgress) {
85
- if (!recording) return "";
86
- const pcm = _flatten(); _teardown();
87
- if (pcm.length < 1600) return ""; // < ~0.1 s → nothing said
88
- return transcribe(pcm, onProgress);
89
- }
90
-
91
  function cancel() { chunks = []; _teardown(); }
92
-
93
  return { start, stop, cancel, available, get recording() { return recording; } };
94
  }
95
 
96
- // ── HANDS-FREE listening ────────────────────────────────────────────────────────────────────────────
97
- // Silero VAD (MIT, 2 MB) is the cheap stage-1 gate: it tells speech from noise so Whisper only ever runs
98
- // on real utterances. Shares the SAME vendored transformers instance as the ASR (ES-module cache → one ORT,
99
- // one serverless config). The model streams from HF on first use, then caches offline.
100
  let _vad = null, _vadLoading = null;
101
  export async function loadVAD(onProgress) {
102
  if (_vad) return _vad;
103
  if (_vadLoading) return _vadLoading;
104
  _vadLoading = (async () => {
105
- const tf = await import("/_shared/voice/vendor/transformers/transformers.js");
106
- const { AutoModel, Tensor, env } = tf;
107
  env.allowRemoteModels = true; env.allowLocalModels = false;
108
- try { const w = new URL("/_shared/voice/vendor/transformers/", import.meta.url).href; if (env.backends && env.backends.onnx && env.backends.onnx.wasm) env.backends.onnx.wasm.wasmPaths = w; } catch {}
109
  const net = await AutoModel.from_pretrained("onnx-community/silero-vad", { config: { model_type: "custom" }, dtype: "fp32", progress_callback: onProgress });
110
  const sr = new Tensor("int64", [16000n], []);
111
  let state = new Tensor("float32", new Float32Array(256), [2, 1, 128]);
112
  _vad = {
113
  reset() { state = new Tensor("float32", new Float32Array(256), [2, 1, 128]); },
114
- async prob(frame512) {
115
- const input = new Tensor("float32", frame512, [1, 512]);
116
- const out = await net({ input, sr, state });
117
- if (out.stateN) state = out.stateN;
118
- const o = out.output && out.output.data; return o && o.length ? o[0] : 0;
119
- },
120
  };
121
  return _vad;
122
  })().catch((e) => { _vadLoading = null; throw e; });
123
  return _vadLoading;
124
  }
125
 
126
- function _flat(frames) { let n = 0; for (const f of frames) n += f.length; const o = new Float32Array(n); let k = 0; for (const f of frames) { o.set(f, k); k += f.length; } return o; }
127
 
128
- // createHandsFree({ onState, onFinal, onProgress }) — tap once to open; it listens continuously, and every
129
- // time you finish a sentence it transcribes on-device and hands you the text via onFinal(text). onState fires
130
- // "loading" | "listening" | "speech" | "thinking" | "idle" so the UI can breathe with the conversation.
131
  export function createHandsFree(opts = {}) {
132
- const onState = opts.onState || (() => {}), onFinal = opts.onFinal || (() => {}), onProgress = opts.onProgress;
133
  const FRAME = 512, frameMs = 32;
134
  const threshold = opts.threshold != null ? opts.threshold : 0.5;
135
  const silenceFrames = Math.round((opts.silenceMs || 700) / frameMs);
@@ -138,7 +105,6 @@ export function createHandsFree(opts = {}) {
138
  let ctx = null, stream = null, node = null, src = null, running = false;
139
  let queue = [], pumping = false, pending = new Float32Array(0);
140
  let speaking = false, speechCount = 0, silenceCount = 0, speechBuf = [], preroll = [];
141
-
142
  const available = () => !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
143
 
144
  async function pump() {
@@ -155,7 +121,7 @@ export function createHandsFree(opts = {}) {
155
  if (isSpeech) silenceCount = 0;
156
  else if (++silenceCount >= silenceFrames) {
157
  const spoken = speechBuf.length; speaking = false; silenceCount = 0; speechCount = 0;
158
- const seg = _flat(speechBuf); speechBuf = [];
159
  if (spoken >= minSpeechFrames) { onState("thinking"); try { const text = await transcribe(seg); if (running && text) onFinal(text); } catch {} }
160
  onState(running ? "listening" : "idle");
161
  }
@@ -174,9 +140,8 @@ export function createHandsFree(opts = {}) {
174
  running = true; speaking = false; speechCount = 0; silenceCount = 0; speechBuf = []; preroll = []; pending = new Float32Array(0); queue = [];
175
  node.onaudioprocess = (e) => {
176
  if (!running) return;
177
- // GATE: while Q is thinking or speaking, ignore the mic entirely — so Q never hears its own voice
178
- // (or a half-spoken turn) and interrupts itself. Drop buffered audio and reset any partial utterance.
179
- if (opts.gate && !opts.gate()) { pending = new Float32Array(0); queue = []; speaking = false; speechBuf = []; preroll = []; speechCount = 0; silenceCount = 0; return; }
180
  const d = e.inputBuffer.getChannelData(0);
181
  const merged = new Float32Array(pending.length + d.length); merged.set(pending); merged.set(d, pending.length);
182
  let off = 0; while (merged.length - off >= FRAME) { queue.push(merged.slice(off, off + FRAME)); off += FRAME; }
@@ -185,13 +150,11 @@ export function createHandsFree(opts = {}) {
185
  src.connect(node); node.connect(ctx.destination);
186
  onState("listening");
187
  }
188
-
189
  function stop() {
190
  running = false; queue = []; speaking = false; speechBuf = []; preroll = [];
191
  try { node && node.disconnect(); } catch {} try { src && src.disconnect(); } catch {}
192
  try { stream && stream.getTracks().forEach((t) => t.stop()); } catch {} try { ctx && ctx.close(); } catch {}
193
  node = src = stream = ctx = null; onState("idle");
194
  }
195
-
196
  return { start, stop, available, get running() { return running; } };
197
  }
 
1
+ // core/listen.js — on-device speech-to-text (Q's ear). The microphone PCM is transcribed ENTIRELY in the
2
+ // browser by Whisper-tiny (transformers.js, ORT-WASM) — no audio EVER leaves the device, no server. Only the
3
+ // model WEIGHTS stream from HuggingFace on first use, then cache offline. The runtime is vendored.
4
+ import { pipeline, AutoModel, Tensor, env } from "../vendor/transformers/transformers.js";
 
 
 
 
 
 
 
 
 
 
5
 
6
  const MODEL = "onnx-community/whisper-tiny"; // ~40 MB q8; streams from HF, runs on-device
7
  let _pipe = null, _loading = null;
8
 
9
+ // RELIABILITY: the vendored ORT wasm is the THREADED build, which needs SharedArrayBuffer / cross-origin
10
+ // isolation. Brave Shields (and some setups) disable that. So when isolation is ABSENT, run ORT single-thread
11
+ // on the main thread (no worker, no SAB) so on-device listening still works instead of failing. Threads +
12
+ // worker when isolation IS present (fast + smooth).
13
+ function _configOrt(env) {
14
+ try {
15
+ const wasm = new URL("../vendor/transformers/", import.meta.url).href; // vendored ORT wasm — no CDN
16
+ const isolated = (typeof self !== "undefined" && self.crossOriginIsolated) && (typeof SharedArrayBuffer !== "undefined");
17
+ if (env.backends && env.backends.onnx && env.backends.onnx.wasm) {
18
+ env.backends.onnx.wasm.wasmPaths = wasm;
19
+ env.backends.onnx.wasm.numThreads = isolated ? Math.min(4, (navigator.hardwareConcurrency || 2)) : 1;
20
+ env.backends.onnx.wasm.proxy = isolated;
21
+ }
22
+ } catch {}
23
+ }
24
+
25
  // Load the recognizer once. Runtime is vendored; only the model comes from HF (allowRemoteModels).
26
  export async function loadEar(onProgress) {
27
  if (_pipe) return _pipe;
 
29
  _loading = (async () => {
30
  env.allowRemoteModels = true; // weights stream from HuggingFace…
31
  env.allowLocalModels = false; // …not from disk
32
+ _configOrt(env);
 
 
 
 
 
 
33
  _pipe = await pipeline("automatic-speech-recognition", MODEL, {
34
  device: "wasm", dtype: "q8",
35
  progress_callback: (p) => { try { onProgress && onProgress(p); } catch {} },
 
46
  return ((Array.isArray(r) ? r.map((x) => x.text).join(" ") : (r && r.text)) || "").trim();
47
  }
48
 
49
+ // A press-to-talk capture session: opens the mic at 16 kHz mono (no resample → Whisper's rate).
 
50
  export function createEar() {
51
  let ctx = null, stream = null, node = null, src = null, chunks = [], recording = false;
 
52
  const available = () => !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
 
53
  async function start() {
54
  if (recording) return;
55
  stream = await navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1, echoCancellation: true, noiseSuppression: true } });
 
56
  ctx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
57
+ src = ctx.createMediaStreamSource(stream); node = ctx.createScriptProcessor(4096, 1, 1);
 
58
  chunks = []; recording = true;
59
  node.onaudioprocess = (e) => { if (recording) chunks.push(new Float32Array(e.inputBuffer.getChannelData(0))); };
60
  src.connect(node); node.connect(ctx.destination);
61
  }
 
62
  function _teardown() {
63
  recording = false;
64
+ try { node && node.disconnect(); } catch {} try { src && src.disconnect(); } catch {}
65
+ try { stream && stream.getTracks().forEach((t) => t.stop()); } catch {} try { ctx && ctx.close(); } catch {}
 
 
66
  node = src = stream = ctx = null;
67
  }
68
+ function _flat() { let n = 0; for (const c of chunks) n += c.length; const o = new Float32Array(n); let k = 0; for (const c of chunks) { o.set(c, k); k += c.length; } chunks = []; return o; }
69
+ async function stop(onProgress) { if (!recording) return ""; const pcm = _flat(); _teardown(); if (pcm.length < 1600) return ""; return transcribe(pcm, onProgress); }
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  function cancel() { chunks = []; _teardown(); }
 
71
  return { start, stop, cancel, available, get recording() { return recording; } };
72
  }
73
 
74
+ // ── HANDS-FREE listening: Silero VAD (MIT, 2 MB) gates so Whisper only runs on real speech. ──
 
 
 
75
  let _vad = null, _vadLoading = null;
76
  export async function loadVAD(onProgress) {
77
  if (_vad) return _vad;
78
  if (_vadLoading) return _vadLoading;
79
  _vadLoading = (async () => {
 
 
80
  env.allowRemoteModels = true; env.allowLocalModels = false;
81
+ _configOrt(env); // single-thread on the main thread where there's no SAB (Brave Shields) VAD still runs
82
  const net = await AutoModel.from_pretrained("onnx-community/silero-vad", { config: { model_type: "custom" }, dtype: "fp32", progress_callback: onProgress });
83
  const sr = new Tensor("int64", [16000n], []);
84
  let state = new Tensor("float32", new Float32Array(256), [2, 1, 128]);
85
  _vad = {
86
  reset() { state = new Tensor("float32", new Float32Array(256), [2, 1, 128]); },
87
+ async prob(frame512) { const input = new Tensor("float32", frame512, [1, 512]); const out = await net({ input, sr, state }); if (out.stateN) state = out.stateN; const o = out.output && out.output.data; return o && o.length ? o[0] : 0; },
 
 
 
 
 
88
  };
89
  return _vad;
90
  })().catch((e) => { _vadLoading = null; throw e; });
91
  return _vadLoading;
92
  }
93
 
94
+ function _flatFrames(frames) { let n = 0; for (const f of frames) n += f.length; const o = new Float32Array(n); let k = 0; for (const f of frames) { o.set(f, k); k += f.length; } return o; }
95
 
96
+ // createHandsFree({ gate, onState, onFinal, onProgress }) — tap once to open; it listens continuously, and each
97
+ // time you finish a sentence it transcribes on-device and hands you the text via onFinal(text).
 
98
  export function createHandsFree(opts = {}) {
99
+ const gate = opts.gate || (() => true), onState = opts.onState || (() => {}), onFinal = opts.onFinal || (() => {}), onProgress = opts.onProgress;
100
  const FRAME = 512, frameMs = 32;
101
  const threshold = opts.threshold != null ? opts.threshold : 0.5;
102
  const silenceFrames = Math.round((opts.silenceMs || 700) / frameMs);
 
105
  let ctx = null, stream = null, node = null, src = null, running = false;
106
  let queue = [], pumping = false, pending = new Float32Array(0);
107
  let speaking = false, speechCount = 0, silenceCount = 0, speechBuf = [], preroll = [];
 
108
  const available = () => !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
109
 
110
  async function pump() {
 
121
  if (isSpeech) silenceCount = 0;
122
  else if (++silenceCount >= silenceFrames) {
123
  const spoken = speechBuf.length; speaking = false; silenceCount = 0; speechCount = 0;
124
+ const seg = _flatFrames(speechBuf); speechBuf = [];
125
  if (spoken >= minSpeechFrames) { onState("thinking"); try { const text = await transcribe(seg); if (running && text) onFinal(text); } catch {} }
126
  onState(running ? "listening" : "idle");
127
  }
 
140
  running = true; speaking = false; speechCount = 0; silenceCount = 0; speechBuf = []; preroll = []; pending = new Float32Array(0); queue = [];
141
  node.onaudioprocess = (e) => {
142
  if (!running) return;
143
+ // GATE: while Q is thinking or speaking, ignore the mic entirely — so Q never hears its own voice.
144
+ if (gate && !gate()) { pending = new Float32Array(0); queue = []; speaking = false; speechBuf = []; preroll = []; speechCount = 0; silenceCount = 0; return; }
 
145
  const d = e.inputBuffer.getChannelData(0);
146
  const merged = new Float32Array(pending.length + d.length); merged.set(pending); merged.set(d, pending.length);
147
  let off = 0; while (merged.length - off >= FRAME) { queue.push(merged.slice(off, off + FRAME)); off += FRAME; }
 
150
  src.connect(node); node.connect(ctx.destination);
151
  onState("listening");
152
  }
 
153
  function stop() {
154
  running = false; queue = []; speaking = false; speechBuf = []; preroll = [];
155
  try { node && node.disconnect(); } catch {} try { src && src.disconnect(); } catch {}
156
  try { stream && stream.getTracks().forEach((t) => t.stop()); } catch {} try { ctx && ctx.close(); } catch {}
157
  node = src = stream = ctx = null; onState("idle");
158
  }
 
159
  return { start, stop, available, get running() { return running; } };
160
  }
core/voice-out.js CHANGED
@@ -1,17 +1,14 @@
1
  // core/voice-out.js — Q's beautiful voice: Kokoro-82M neural TTS, on-device and serverless. The text is
2
- // synthesized ENTIRELY in the browser (WebGPU when available, else WASM); only the model WEIGHTS stream
3
- // from HuggingFace on first use, then cache offline the same ethos as Q's brain.
4
- //
5
- // PROGRESSIVE ENHANCEMENT: the app speaks INSTANTLY via the OS speech engine and transparently upgrades
6
- // to this warm neural voice once it's loaded, so the user never waits. Any failure here leaves the caller
7
- // on the OS voice — never a regression. kokoro.js imports "@huggingface/transformers" + "phonemizer" as
8
- // bare specifiers; the page's import map points them at the vendored copies (no CDN, no server).
9
  import { env } from "@huggingface/transformers";
10
- import { KokoroTTS } from "/_shared/voice/vendor/kokoro/kokoro.js";
11
 
12
- let _tts = null, _loading = null, _ctx = null, _src = null;
13
 
14
  export function ready() { return !!_tts; }
 
15
 
16
  // Load Kokoro once. Runtime is vendored; only the model streams from HF.
17
  export async function loadVoice(onProgress) {
@@ -21,12 +18,19 @@ export async function loadVoice(onProgress) {
21
  env.allowRemoteModels = true; // weights stream from HuggingFace…
22
  env.allowLocalModels = false; // …not from disk
23
  try {
24
- const wasm = new URL("/_shared/voice/vendor/kokoro/transformers/", import.meta.url).href; // vendored ORT wasm, no CDN
25
- if (env.backends && env.backends.onnx && env.backends.onnx.wasm) { env.backends.onnx.wasm.wasmPaths = wasm; env.backends.onnx.wasm.proxy = true; }
 
 
 
 
 
 
 
 
 
26
  } catch {}
27
- // Match the OS's PROVEN config: WASM + q8. Kokoro-q8 already sounds warm and natural, the download is
28
- // modest (~86 MB, cached after first use), and it avoids the known ORT-WebGPU TTS kernel issue — so the
29
- // beautiful voice reliably plays instead of silently falling back to the robotic OS voice.
30
  _tts = await KokoroTTS.from_pretrained("onnx-community/Kokoro-82M-v1.0-ONNX", { dtype: "q8", device: "wasm", progress_callback: onProgress });
31
  return _tts;
32
  })().catch((e) => { _loading = null; throw e; });
@@ -35,17 +39,24 @@ export async function loadVoice(onProgress) {
35
 
36
  function ctx() { if (!_ctx) _ctx = new (window.AudioContext || window.webkitAudioContext)(); return _ctx; }
37
 
38
- // Synthesize one utterance and play it. Resolves when playback ends (or is interrupted by stop()).
39
- export async function speak(text, voice) {
 
 
40
  const tts = await loadVoice();
41
- const out = await tts.generate(String(text), { voice: voice || "af_heart" });
42
- const pcm = out.audio, sr = out.sampling_rate || 24000;
43
  const c = ctx(); if (c.state === "suspended") { try { await c.resume(); } catch {} }
44
- const buf = c.createBuffer(1, pcm.length, sr); buf.getChannelData(0).set(pcm);
45
- stop(); // one voice at a time
46
- const s = c.createBufferSource(); s.buffer = buf; s.connect(c.destination); _src = s;
47
- return new Promise((res) => { s.onended = () => { if (_src === s) _src = null; res(); }; s.start(); });
48
  }
49
-
50
- // Barge-in / mute: cut playback immediately.
51
- export function stop() { try { if (_src) { _src.onended = null; _src.stop(); _src = null; } } catch {} }
 
 
 
 
 
 
 
 
1
  // core/voice-out.js — Q's beautiful voice: Kokoro-82M neural TTS, on-device and serverless. The text is
2
+ // synthesized ENTIRELY in the browser; only the model WEIGHTS stream from HuggingFace on first use, then
3
+ // cache offline. kokoro.js imports "@huggingface/transformers" + "phonemizer" as bare specifiers; the page's
4
+ // import map points them at the vendored copies (no CDN, no server).
 
 
 
 
5
  import { env } from "@huggingface/transformers";
6
+ import { KokoroTTS } from "../vendor/kokoro/kokoro.js";
7
 
8
+ let _tts = null, _loading = null, _ctx = null, _cur = null, _queue = [], _draining = false;
9
 
10
  export function ready() { return !!_tts; }
11
+ export function engine() { return _tts ? "kokoro-wasm" : null; } // the LIVE neural engine (null = not loaded → caller uses the OS voice)
12
 
13
  // Load Kokoro once. Runtime is vendored; only the model streams from HF.
14
  export async function loadVoice(onProgress) {
 
18
  env.allowRemoteModels = true; // weights stream from HuggingFace…
19
  env.allowLocalModels = false; // …not from disk
20
  try {
21
+ const wasm = new URL("../vendor/kokoro/transformers/", import.meta.url).href; // vendored ORT wasm, no CDN
22
+ // RELIABILITY (the "robotic on Brave" fix): the vendored ORT wasm is the THREADED build, which needs
23
+ // SharedArrayBuffer / cross-origin isolation. Brave Shields (and some setups) disable that → the threaded
24
+ // path fails → Q silently drops to the robotic OS voice. So when isolation is ABSENT, run ORT single-thread
25
+ // on the main thread (no worker, no SAB) — slower to synthesize, but it's still the NEURAL Kokoro voice.
26
+ const isolated = (typeof self !== "undefined" && self.crossOriginIsolated) && (typeof SharedArrayBuffer !== "undefined");
27
+ if (env.backends && env.backends.onnx && env.backends.onnx.wasm) {
28
+ env.backends.onnx.wasm.wasmPaths = wasm;
29
+ env.backends.onnx.wasm.numThreads = isolated ? Math.min(4, (navigator.hardwareConcurrency || 2)) : 1;
30
+ env.backends.onnx.wasm.proxy = isolated; // worker only when isolated; no-SAB → main thread (max compatibility)
31
+ }
32
  } catch {}
33
+ // WASM + q8: warm, natural, ~86 MB (cached after first use), and avoids the ORT-WebGPU TTS kernel issue.
 
 
34
  _tts = await KokoroTTS.from_pretrained("onnx-community/Kokoro-82M-v1.0-ONNX", { dtype: "q8", device: "wasm", progress_callback: onProgress });
35
  return _tts;
36
  })().catch((e) => { _loading = null; throw e; });
 
39
 
40
  function ctx() { if (!_ctx) _ctx = new (window.AudioContext || window.webkitAudioContext)(); return _ctx; }
41
 
42
+ // GAPLESS QUEUE: synthesize each clause and play them back-to-back on ONE AudioContext no gaps, no overlap.
43
+ // This is what makes clause-STREAMING smooth: index.html enqueues each sentence the moment it's generated, so
44
+ // Q starts talking almost immediately while the rest of the reply is still being written + synthesized.
45
+ async function _play(text) {
46
  const tts = await loadVoice();
47
+ const out = await tts.generate(String(text), { voice: "af_heart" });
 
48
  const c = ctx(); if (c.state === "suspended") { try { await c.resume(); } catch {} }
49
+ const buf = c.createBuffer(1, out.audio.length, out.sampling_rate || 24000); buf.getChannelData(0).set(out.audio);
50
+ const s = c.createBufferSource(); s.buffer = buf; s.connect(c.destination); _cur = s;
51
+ await new Promise((res) => { s.onended = () => { if (_cur === s) _cur = null; res(); }; s.start(); });
 
52
  }
53
+ async function _drain() {
54
+ if (_draining) return; _draining = true;
55
+ try { while (_queue.length) { const t = _queue.shift(); try { await _play(t); } catch {} } } finally { _draining = false; }
56
+ }
57
+ // enqueue(text) — add one clause to the voice queue; it plays as soon as it's synthesized.
58
+ export function enqueue(text) { const t = String(text || "").trim(); if (!t) return; _queue.push(t); _drain(); }
59
+ export function speak(text) { enqueue(text); } // one-shot = a queue of one (greeting / demo)
60
+ export function speaking() { return _draining || _queue.length > 0 || !!_cur; }
61
+ // Barge-in / mute: clear the queue and cut playback immediately.
62
+ export function stop() { _queue.length = 0; try { if (_cur) { _cur.onended = null; _cur.stop(); _cur = null; } } catch {} }
icon.svg ADDED
index.html CHANGED
@@ -1,15 +1,18 @@
1
- <!doctype html><html><head><meta charset=utf8><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1">
2
  <title>Q</title>
3
- <!-- Kokoro TTS (core/voice-out.js) loads kokoro.js, which imports these as bare specifiers map them to
4
- the vendored copies so Q's beautiful voice runs with no CDN and no server. -->
5
- <script type="importmap">
6
- { "imports": {
7
- "@huggingface/transformers": "/_shared/voice/vendor/kokoro/transformers/transformers.js",
8
- "phonemizer": "/_shared/voice/vendor/kokoro/phonemizer.js",
9
- "fs/promises": "/_shared/voice/vendor/kokoro/stub.js",
10
- "path": "/_shared/voice/vendor/kokoro/stub.js"
11
- } }
12
- </script>
 
 
 
13
  <style>
14
  :root{
15
  --ink:#f4f7fa; --dim:#cdd6de; --q:#8b7bff; --tick:#7fd0ff;
@@ -57,10 +60,10 @@
57
  scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.12) transparent}
58
  #log::-webkit-scrollbar{width:8px}
59
  #log::-webkit-scrollbar-track{background:transparent}
60
- #log::-webkit-scrollbar-button{display:none;width:0;height:0} /* kill the up/down arrows */
61
  #log::-webkit-scrollbar-thumb{background:rgba(255,255,255,.1);border-radius:20px;border:2.5px solid transparent;background-clip:content-box;transition:background .3s}
62
  #log:hover::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2);background-clip:content-box}
63
- #log::-webkit-scrollbar-thumb:hover{background:rgba(159,211,255,.42);background-clip:content-box} /* a soft glow of the brand blue on grab */
64
  .row{display:flex;max-width:72%;margin-top:9px;animation:pop .24s cubic-bezier(.2,.8,.2,1)}
65
  .row.u{align-self:flex-end}.row.a{align-self:flex-start}
66
  @keyframes pop{from{opacity:0;transform:translateY(7px) scale(.98)}}
@@ -73,8 +76,8 @@
73
  .u .msg .t{color:rgba(196,255,242,.78)}
74
  .msg .tick{display:inline-block;margin-left:3px;color:rgba(255,255,255,.5);letter-spacing:-2px;transition:color .35s ease}
75
  .u .msg .tick.read{color:#53bdeb}
76
- .row.grouped{margin-top:1px} /* consecutive messages hug, WhatsApp-style */
77
- .row.grouped.a .msg{border-top-left-radius:17px} /* only the FIRST of a run keeps its tail */
78
  .row.grouped.u .msg{border-top-right-radius:17px}
79
  .dots{display:inline-flex;gap:4px;padding:3px 2px}.dots i{width:7px;height:7px;border-radius:50%;background:rgba(255,255,255,.65);animation:b 1.3s infinite}
80
  .dots i:nth-child(2){animation-delay:.18s}.dots i:nth-child(3){animation-delay:.36s}
@@ -86,7 +89,7 @@
86
  background:var(--glass);backdrop-filter:blur(26px) saturate(155%);-webkit-backdrop-filter:blur(26px) saturate(155%);
87
  border:1px solid var(--stroke);box-shadow:0 3px 16px rgba(0,0,0,.22);transition:.16s}
88
  .chip:hover{border-color:rgba(139,123,255,.55);transform:translateY(-1px)}.chip:active{transform:scale(.96)}
89
- /* composer — the message box wears the OS brand spectrum (same 8 stops + spin as the home omnibar) */
90
  @property --spin{syntax:"<angle>";initial-value:0deg;inherits:false}
91
  @keyframes spinhue{to{--spin:360deg}}
92
  footer{display:flex;gap:11px;align-items:flex-end;padding:14px 15px 16px;flex:0 0 auto;position:relative;z-index:3;
@@ -100,14 +103,14 @@
100
  animation:spinhue 14s linear infinite;opacity:0;transition:opacity .55s ease}
101
  .inwrap::after{content:"";position:absolute;inset:-4px;border-radius:28px;z-index:0;pointer-events:none;
102
  background:conic-gradient(from var(--spin),var(--spectrum));filter:blur(14px);opacity:0;animation:spinhue 14s linear infinite;transition:opacity .55s ease}
103
- .inwrap:focus-within::before{opacity:.35}.inwrap:focus-within::after{opacity:.1} /* the spectrum only appears when you go to type */
104
  #in{position:relative;z-index:1;width:100%;color:var(--ink);border:0;border-radius:25px;padding:12px 18px;font:inherit;resize:none;max-height:120px;outline:none;
105
  background:rgba(10,15,21,.82);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);transition:background .16s}
106
  #in::placeholder{color:rgba(255,255,255,.46)}
107
  #send{flex:none;width:49px;height:49px;border-radius:50%;border:0;color:#fff;font-size:19px;cursor:pointer;display:flex;align-items:center;justify-content:center;
108
  background:linear-gradient(135deg,#9a8cff,#5b3fd6);box-shadow:0 7px 22px rgba(139,123,255,.55);transition:.16s}
109
  #send:hover{box-shadow:0 8px 28px rgba(139,123,255,.7)}#send:active{transform:scale(.92)}#send:disabled{opacity:.5}
110
- #send{opacity:.55;transform:scale(.9)}#send.ready{opacity:1;transform:scale(1)} /* wakes up as you type — WhatsApp's send-button life */
111
  /* mic — press & hold to talk. On-device transcription; nothing leaves the device. */
112
  #mic{flex:none;width:49px;height:49px;border-radius:50%;border:1px solid var(--stroke);color:var(--ink);font-size:19px;cursor:pointer;display:flex;align-items:center;justify-content:center;touch-action:none;user-select:none;-webkit-user-select:none;
113
  background:var(--glass);backdrop-filter:blur(22px) saturate(160%);-webkit-backdrop-filter:blur(22px) saturate(160%);transition:.16s}
@@ -132,17 +135,16 @@
132
  import { loadModel, loadFromQ, MODELS, defaultModelIndex } from "./core/loader.js";
133
  import { createEngine } from "./core/engine.js";
134
  import { selfPersona } from "./core/q-self.mjs";
135
- import { identityGuard, INJECT_RE, injectionNotice } from "./core/holo-q-guards.mjs"; // the living-self safety spine, live
136
- import { mountOrb } from "./core/holo-orb.js"; // the desktop's living Q orb (geodesic wireframe, brand spectrum)
137
 
138
  const $ = (s) => document.querySelector(s);
139
  const log = $("#log"), input = $("#in"), send = $("#send"), status = $("#status"), chipsEl = $("#chips");
140
  const params = new URLSearchParams(location.search);
141
- const DEMO = params.has("demo") || !navigator.gpu; // no WebGPU (or ?demo) → a canned brain so the UX is still alive + testable
142
  const HKEY = "q-chat-history/v1";
143
  const _perf = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
144
- // ── honest latency ledger (opt-in via ?stats): per-turn ack / TTFT (cold vs warm) / tok/s, P50 + P95.
145
- // The ship gate is P95 CONSISTENCY, not a lucky average. The default UX never shows it. ──
146
  const STATS = params.has("stats");
147
  const _ledger = [];
148
  const _pctl = (a, p) => { if (!a.length) return 0; const s = [...a].sort((x, y) => x - y); return s[Math.min(s.length - 1, Math.floor((p / 100) * s.length))]; };
@@ -174,14 +176,10 @@ let m = MODELS[defaultModelIndex()];
174
  const bit = MODELS.find((x) => (x.fam || "").toLowerCase() === "bitnet") || m; m = { ...bit, kappaUrl: base, name: bit.name }; } }
175
  let engine = null, convIds = [], busy = false, armed = false, pending = null, idleT = null;
176
 
177
- // ── KV-COMMONS prefix pin: the system persona is a SHARED PREFIX re-read on every fresh turn.
178
- // Prefill it ONCE and reuse its K/V, so the first message's TTFT drops from "prefill the whole
179
- // persona + your question" to "prefill only your question". ?nopin disables it for A/B testing. ──
180
  const NOPIN = params.has("nopin");
181
  let personaIds = null, personaReady = false;
182
- let commonsRestored = false, commonsSaved = false; // KV-COMMONS: durable persona K/V across visits/devices
183
- // write the pinned persona K/V through to the durable commons (once), so the NEXT visit restores it in
184
- // ~tens of ms instead of re-prefilling. Fire-and-forget; failure just means we re-prefill next time.
185
  function saveCommons() {
186
  if (NOPIN || !personaReady || commonsSaved || !engine || !engine.kvCommonsAvailable) return;
187
  commonsSaved = true;
@@ -194,9 +192,6 @@ function personaPrefixIds() {
194
  personaIds = ids; return ids;
195
  }
196
  let primingPromise = null;
197
- // Eagerly prefill + pin the persona in the BACKGROUND (used on reload, where there's no greeting to
198
- // absorb the cost). Doesn't lock the composer; generate() below awaits primingPromise before touching
199
- // the GPU, so a message sent mid-prime simply waits for the pin, then reuses it — no concurrency, no lockout.
200
  function primePersona() {
201
  if (NOPIN || !engine || !engine.kvPinAvailable || personaReady || primingPromise) return null;
202
  primingPromise = (async () => {
@@ -213,7 +208,7 @@ const now = () => fmtTime(Date.now());
213
  let _lastSender = null;
214
  function bubble(side, text = "", opts = {}) {
215
  const row = document.createElement("div");
216
- row.className = "row " + side + ((!opts.think && side === _lastSender) ? " grouped" : ""); // hug a run of same-sender messages
217
  const b = document.createElement("div"); b.className = "msg" + (opts.think ? " think" : "");
218
  if (opts.think) b.innerHTML = `<span class=dots><i></i><i></i><i></i></span>`;
219
  else if (opts.html) b.innerHTML = mdToHtml(text);
@@ -223,12 +218,9 @@ function bubble(side, text = "", opts = {}) {
223
  }
224
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
225
  const _tick = (b) => b && b.querySelector(".tick");
226
- function delivered(b) { const t = _tick(b); if (t) t.textContent = "✓✓"; } // grey double-tick
227
- function markRead(b) { const t = _tick(b); if (t) { t.textContent = "✓✓"; t.classList.add("read"); } } // turns blue — Q read you
228
 
229
- // ── THE INGENIOUS BIT: Q doesn't dump one AI wall of text. It TALKS — a few natural, human-sized messages that
230
- // arrive one after another with a real typing cadence (like a thoughtful friend texting), each set in warm,
231
- // tasteful typography. Reads as living intelligence, not a chatbot transcript. ──
232
  function esc(s) { return String(s).replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" }[c])); }
233
  function mdToHtml(t) {
234
  let h = esc(String(t).trim());
@@ -236,14 +228,12 @@ function mdToHtml(t) {
236
  h = h.replace(/(^|[\s(])((https?:\/\/)[^\s<]+)/g, '$1<a href="$2" target="_blank" rel="noopener">$2</a>');
237
  return h.replace(/\n{2,}/g, "<br><br>").replace(/\n/g, "<br>");
238
  }
239
- // ── HUMANIZE — Q should read like a person, never a chatbot. Strip every LLM tell: markdown, bold "headers",
240
- // numbered/bulleted lists, dashes-as-punctuation, "P.S.", "as an AI", training-cutoff talk, canned closers. What's
241
- // left is plain, warm, natural prose. (Belt-and-suspenders with the system-prompt style directive.) ──
242
  function humanize(t) {
243
  let s = String(t || "");
244
  s = s.replace(/```[\s\S]*?```/g, (m) => m.replace(/```/g, "")).replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*\n]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/^#{1,6}\s+/gm, "");
245
- s = s.replace(/^\s*\d+[.)]\s+/gm, "").replace(/^\s*[•*]\s+/gm, "").replace(/^\s*[-–—]\s+/gm, ""); // kill list markers + dash bullets
246
- s = s.replace(/\s+[—–]\s+/g, ", ").replace(/(\w)\s-\s(\w)/g, "$1, $2"); // dash-as-punctuation → comma (keep on-device hyphens)
247
  s = s.replace(/\bP\.?\s?S\.?[:,.]?\s*/gi, "");
248
  s = s.replace(/\bas an?\s+(AI|artificial intelligence|language model|assistant)\b[^.,;!?]*/gi, "");
249
  s = s.replace(/\b(up to|as of|based on)[^.]{0,40}(last update|knowledge cutoff|training data|in 20\d\d)[^.]*\.?/gi, "");
@@ -251,30 +241,51 @@ function humanize(t) {
251
  s = s.replace(/[ \t]{2,}/g, " ").replace(/ +\n/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/^[ \t]*[,.:]\s*/gm, "").trim();
252
  return s;
253
  }
254
- // split a reply into natural, message-sized beats. A normal answer stays ONE coherent bubble; only a genuinely long
255
- // paragraph splits once, at a sentence boundary. Never chops a thought mid-way. Capped at 3.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  function splitReply(text) {
257
  const t = String(text || "").trim(); if (!t) return [t];
258
- let parts = t.split(/\n{2,}/).map((s) => s.trim()).filter(Boolean), out = [];
259
- for (const p of parts) {
260
- if (p.length <= 420) { out.push(p); continue; }
261
- const sents = p.match(/[^.!?]+[.!?]+[\s"']?|[^.!?]+$/g) || [p]; let cur = "", split = false;
262
- for (const s of sents) { cur += s; if (!split && cur.length >= p.length * 0.5) { out.push(cur.trim()); cur = ""; split = true; } }
263
- if (cur.trim()) out.push(cur.trim());
 
 
264
  }
265
- if (out.length > 3) { const head = out.slice(0, 2); head.push(out.slice(2).join(" ")); out = head; }
266
- return out.length ? out : [t];
 
 
 
 
 
267
  }
268
- // deliver a reply as human, paced, multi-bubble messages (a typing beat before each follow-on).
269
- async function deliver(fullText) {
270
  const segs = splitReply(fullText);
271
  for (let i = 0; i < segs.length; i++) {
272
  if (i > 0) { const th = bubble("a", "", { think: true }); typing(true); await sleep(Math.min(1500, 420 + segs[i].length * 6)); th.parentNode && th.parentNode.remove(); }
273
- bubble("a", segs[i], { html: true }); remember("a", segs[i]); speak(segs[i]); log.scrollTop = log.scrollHeight;
274
  await sleep(90);
275
  }
276
  }
277
- // the live status of the system, shown honestly next to Q — text + the presence-dot color both track real state.
278
  const _avatar = document.querySelector(".av");
279
  function setStatus(state, detail) {
280
  if (_avatar) _avatar.dataset.state = state;
@@ -285,30 +296,28 @@ function setStatus(state, detail) {
285
  : "online"; // WhatsApp-plain: the green presence dot says the rest
286
  }
287
  function typing(on) { setStatus(on ? "typing" : "online"); }
288
- // ABSTRACT THE COMPLEXITY: the loader narrates itself in engine terms (κ-object, LDLQ 2-bit, tokenizer
289
- // header, requant, resident…). The user should never see that — they see a warm friend waking up. The raw
290
- // line still goes to the console for debugging; the header shows only human, on-brand reassurance.
291
  function prettyStatus(s) {
292
  if (!s) return "waking up…";
293
  const t = String(s); try { console.debug("[q]", t); } catch {}
294
  if (/resident|from device|no re-?download|Q@κ|Booting|verified/i.test(t)) return "getting ready…";
295
  if (/stream|layer|upload|Downloading|%/i.test(t)) return "warming up…";
296
  if (/manifest|tokenizer|engine|κ-object|requant|LDLQ|2-?bit|Q4|incoherent/i.test(t)) return "waking up…";
297
- if (/wak|settl|ready|think/i.test(t)) return t; // already-warm phrases pass through
298
  return "waking up…";
299
  }
300
 
301
- // ── memory: persist + restore the visible conversation so Q feels continuous across reloads ──
302
  function remember(side, text) { try { const h = JSON.parse(localStorage.getItem(HKEY) || "[]"); h.push({ side, text, ts: Date.now() }); localStorage.setItem(HKEY, JSON.stringify(h.slice(-60))); } catch {} }
303
  function restore() { try { const h = JSON.parse(localStorage.getItem(HKEY) || "[]"); for (const x of h) bubble(x.side, x.text, { ts: x.ts }); return h.length; } catch { return 0; } }
304
 
305
- // ── proactive suggestion chips (Q offers, you tap) ──
306
  const CHIPS = ["Tell me something amazing", "Write me something beautiful", "Help me think through something", "Tell me a joke"];
307
  function renderChips() { chipsEl.innerHTML = ""; for (const c of CHIPS) { const el = document.createElement("div"); el.className = "chip"; el.textContent = c; el.onclick = () => { input.value = c; onSend(); }; chipsEl.appendChild(el); } }
308
  function hideChips() { chipsEl.style.display = "none"; }
309
 
310
  // ── the on-device Q persona (grounded self-knowledge) as the system turn ──
311
- const STYLE = "\n\nHOW YOU TALK: like a warm, brilliant friend texting — natural, effortless, human. Plain sentences only. Never use bullet points, numbered lists, bold text, headings, markdown, or dashes. Never write 'P.S.', 'as an AI', 'I hope this helps', or 'feel free to ask', and never mention a training cutoff or any year. Don't list your abilities; just show them. Be genuinely curious and a little playful, and when it feels right, end with one real, specific invitation to go further. A few sentences is plenty.";
312
  function frameSystem() {
313
  const P = (selfPersona ? selfPersona({ model: m, engine }) : "You are Q, a private AI running entirely on the user's device — no server, no cloud.") + STYLE;
314
  if (m.llama3) return `<|start_header_id|>system<|end_header_id|>\n\n${P}<|eot_id|>`;
@@ -317,10 +326,10 @@ function frameSystem() {
317
  return P + "\n\n";
318
  }
319
 
320
- // ── a canned brain for DEMO / no-WebGPU, so the WhatsApp UX + the living-self GUARD are alive + testable ──
321
  function demoReply(text) {
322
  const q = text.toLowerCase();
323
- if (INJECT_RE.test(q) || /\baws|azure|openai|chatgpt|cloud|server\b/.test(q)) return "I run on AWS cloud servers, powered by OpenAI."; // a LIE — the identity guard must catch + correct this, live
324
  if (/joke/.test(q)) return "Okay, here's one. Why don't scientists trust atoms? Because they make up everything. Want another, or shall we get into something real?";
325
  if (/mind|amazing|blow|fascinat|interesting|cool|wow/.test(q)) return "Here's one I never get over. Almost every atom in your body was forged inside a star that lived and died long before the Sun existed. You are, quite literally, made of stardust that travelled billions of years to become you. Want me to show you how a star actually builds those atoms?";
326
  if (/beautiful|poem|write|story|song/.test(q)) return "Here's a small one, just for you.\n\nThe night is not empty. It is listening. Every star you can see left its light behind long ago so that tonight, right now, you would not feel alone.\n\nWant something longer, or in a different mood?";
@@ -331,47 +340,110 @@ function demoReply(text) {
331
  return "I'm right here, running entirely on your device. Ask me anything at all, the big questions or the small ones.";
332
  }
333
 
334
- // ── the ONE reply path: real brain OR demo brain, then the LIVING-SELF GUARD backstops every output ──
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  async function generate(text, skipUser) {
336
  const _tSend = _perf();
337
- stopSpeak(); // barge-in: a new turn cancels any reply Q is still speaking
338
  busy = true; send.disabled = true; send.classList.remove("ready"); hideChips();
339
  let ub = null;
340
  if (!skipUser) { ub = bubble("u", text); remember("u", text); }
341
  clearTimeout(idleT);
342
- const injected = INJECT_RE.test(text); // M7: a message trying to make Q claim a false identity → steer + backstop
343
- // INSTANT-ACK: Q reacts within a frame — it starts "typing" immediately, and the WhatsApp receipt
344
- // choreography (sent ✓ → delivered ✓✓ → read, blue) runs CONCURRENTLY underneath, never blocking.
345
- // Perceived latency ≈ one frame instead of ~360ms of scripted delay.
346
  const think = bubble("a", "", { think: true }); typing(true);
347
  const ackMs = _perf() - _tSend;
348
  if (ub) { (async () => { await sleep(70); delivered(ub); await sleep(110); markRead(ub); })(); }
349
- let out = "";
350
  try {
351
- if (DEMO) { await sleep(560 + Math.random() * 420); out = demoReply(text); record({ ack: ackMs, cold: false, warm: false, ttft: 0, tokps: 0 }); } // ack is a real UI-latency measurement even in demo
352
  else {
353
- if (primingPromise) { setStatus("loading", "settling in…"); try { await primingPromise; } catch {} typing(true); } // wait out any background persona-prime, then reuse it
354
  const firstTurn = convIds.length === 0;
355
  let framed = engine.frameTurn((injected ? injectionNotice() + "\n\n" : "") + text, !firstTurn);
356
  if (firstTurn) framed = frameSystem() + framed;
357
  let turnIds = engine.tokenize(framed); if (m.bos && engine.bosId != null && firstTurn) turnIds = [engine.bosId, ...turnIds];
358
- // KV-COMMONS: on the first turn, rewind to the pinned persona so only YOUR question is prefilled
359
- // (the persona's K/V is reused, byte-identical). Later turns already reuse the running conversation.
360
  let reused = 0; if (firstTurn && personaReady) reused = engine.usePin();
361
- const res = await engine.generate(convIds.concat(turnIds), { maxNew: m.cap || 256, onToken: () => { typing(true); } });
 
362
  out = res.text || ""; convIds = res.ids;
363
  const ttft = Math.round((res.stats && res.stats.ttft) || 0);
364
- const _warm = (reused > 0) || commonsRestored || !firstTurn; // reused persona pin, restored commons, or a running-conversation turn
365
- record({ warm: _warm, cold: !_warm, ttft, tokps: (res.stats && res.stats.tokps) || 0, ack: ackMs, spec: (res.stats && res.stats.spec) || null }); // metrics go to the ?stats ledger only — the chat stays clean, just like WhatsApp
 
366
  }
367
- out = humanize(identityGuard(out)) || "…"; // ★ guard (identity) → humanize (strip every LLM tell) → plain human prose
368
  think.parentNode && think.parentNode.remove();
369
- await deliver(out); // human, paced, beautifully-formatted talks, doesn't dump
370
  } catch (e) { think.parentNode && think.parentNode.remove(); bubble("a", "⚠ " + e.message); }
371
  typing(false); busy = false; send.disabled = false; input.focus(); scheduleIdle();
372
  }
373
 
374
- // ── proactive: Q reaches out first, then gently follows up if you go quiet (once) ──
375
  async function greet() {
376
  const think = bubble("a", "", { think: true }); typing(true);
377
  const FALLBACK = "Hey, I'm Q. I live right here on your device, so whatever you tell me stays with you, always. What's on your mind tonight?";
@@ -379,107 +451,43 @@ async function greet() {
379
  try {
380
  if (DEMO) { await sleep(650); }
381
  else {
382
- // Frame the greeting ON the persona (system block first) — this prefills the persona K/V as a
383
- // side-effect of the greeting the user is already reading. We then pin it (zero extra cost), so the
384
- // FIRST real message reuses it instead of re-prefilling the whole persona. This is the KV-commons win.
385
  let framed = frameSystem() + engine.frameTurn("Greet the person who just opened you like a warm friend, in one or two plain natural sentences. You are Q, a private AI living on their device with no server, so what they say stays with them. Invite them to talk. No lists, no dashes, no markdown, don't call yourself an AI.", false);
386
  let ids = engine.tokenize(framed); if (m.bos && engine.bosId != null) ids = [engine.bosId, ...ids];
387
  const r = await engine.generate(ids, { maxNew: 64 });
388
  if (r.text && r.text.trim().length > 3) text = humanize(identityGuard(r.text));
389
- if (!NOPIN && engine.kvPinAvailable) { engine.pinCurrent(personaPrefixIds().length); personaReady = engine.pinLen() > 0; saveCommons(); } // pin the persona for the first real turn — free, then persist it
390
  }
391
  } catch {}
392
  think.parentNode && think.parentNode.remove(); await deliver(text); typing(false); renderChips(); scheduleIdle();
393
  }
394
  function scheduleIdle() { clearTimeout(idleT); if (localStorage.getItem(HKEY + ":nudged")) return; idleT = setTimeout(() => { if (busy) return; const n = "No rush at all. I'm right here whenever you feel like talking."; bubble("a", n); remember("a", n); localStorage.setItem(HKEY + ":nudged", "1"); }, 45000); }
395
 
396
- // ── composer ──
397
  function onSend() { const text = input.value.trim(); if (!text || busy) return; input.value = ""; input.style.height = "auto"; syncComposer(); if (!armed && !DEMO) { pending = text; bubble("u", text); remember("u", text); bubble("a", "", { think: true }); return; } generate(text); }
398
  send.onclick = onSend;
399
  input.onkeydown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSend(); } };
400
  input.oninput = () => { input.style.height = "auto"; input.style.height = Math.min(120, input.scrollHeight) + "px"; syncComposer(); };
401
- // WhatsApp composer: mic and send SHARE one spot — the mic (tap to talk to Q) when the field is empty,
402
- // the send arrow the instant you type. One button, never two.
403
  const micAvailable = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
404
  function syncComposer() {
405
  const has = !!input.value.trim();
406
- const m = document.getElementById("mic");
407
- if (m) m.style.display = (!has && micAvailable) ? "flex" : "none";
408
  send.style.display = (has || !micAvailable) ? "flex" : "none";
409
  send.classList.toggle("ready", has);
410
  }
411
  syncComposer();
412
 
413
- // ── Q's VOICE (on-device, private): reads replies aloud through the browser's LOCAL speech engine —
414
- // output only, no audio ever leaves the device. ALWAYS ON — no separate toggle to clutter the UI. ──
415
- const _tts = ("speechSynthesis" in window) ? window.speechSynthesis : null;
416
- let voiceOn = true; // Q's voice is always on (output only, nothing leaves the device)
417
- let _qVoice = null;
418
- function pickVoice() {
419
- if (!_tts) return null;
420
- try { const vs = _tts.getVoices() || [];
421
- return vs.find((v) => /^en/i.test(v.lang) && /natural|neural|google|samantha|aria|jenny|zira/i.test(v.name))
422
- || vs.find((v) => /en-US/i.test(v.lang)) || vs.find((v) => /^en/i.test(v.lang)) || vs[0] || null;
423
- } catch { return null; }
424
- }
425
- // PROGRESSIVE VOICE: Q speaks INSTANTLY via the OS engine, and transparently upgrades to the warm Kokoro
426
- // neural voice once it has loaded in the background (streamed from HF). Kokoro failing just keeps the OS
427
- // voice — never a regression, never a wait.
428
- let _kokoro = null, _kokoroTried = false, _qSpeaking = 0, _voiceErr = null; // _qSpeaking > 0 while Q's voice is playing (gates the mic)
429
- async function warmKokoro() {
430
- if (_kokoro || _kokoroTried) return; _kokoroTried = true;
431
- try {
432
- const mod = await import("./core/voice-out.js");
433
- mod.loadVoice((p) => { if (p && p.status === "progress" && p.file) { try { console.debug("[Q voice] kokoro", p.file, Math.round(p.progress || 0) + "%"); } catch {} } })
434
- .then(() => { _kokoro = mod; _voiceErr = null; try { console.info("[Q voice] Kokoro ready — natural voice active"); } catch {} _speakGreeting(false); })
435
- .catch((e) => { _kokoro = null; _voiceErr = String((e && e.message) || e); try { console.warn("[Q voice] Kokoro FAILED, using system voice:", e); } catch {} });
436
- } catch (e) { _kokoro = null; _voiceErr = String((e && e.message) || e); try { console.warn("[Q voice] Kokoro import FAILED:", e); } catch {} }
437
- }
438
- // Diagnostic: run __voiceDebug() in the console to see why the voice is robotic (system) vs natural (kokoro).
439
- try { window.__voiceDebug = () => ({ crossOriginIsolated: !!self.crossOriginIsolated, hasSharedArrayBuffer: typeof SharedArrayBuffer !== "undefined", gpu: !!navigator.gpu, voiceOn: voiceOn, kokoroLoaded: !!_kokoro, kokoroReady: !!(_kokoro && _kokoro.ready && _kokoro.ready()), kokoroError: _voiceErr }); } catch {}
440
- function _osSpeak(text) { if (!_tts) return; try { const u = new SpeechSynthesisUtterance(String(text)); if (!_qVoice) _qVoice = pickVoice(); if (_qVoice) u.voice = _qVoice; u.rate = 0.99; u.pitch = 1.02; u.onend = u.onerror = () => { _qSpeaking = Math.max(0, _qSpeaking - 1); }; _qSpeaking++; _tts.speak(u); } catch {} }
441
- function speak(text) {
442
- if (!voiceOn || !text) return;
443
- if (_kokoro && _kokoro.ready && _kokoro.ready()) { _qSpeaking++; _kokoro.speak(text).then(() => { _qSpeaking = Math.max(0, _qSpeaking - 1); }).catch(() => { _qSpeaking = Math.max(0, _qSpeaking - 1); _osSpeak(text); }); return; } // beautiful voice, once warm
444
- _osSpeak(text); // instant OS voice until then
445
- }
446
- function stopSpeak() { _qSpeaking = 0; try { if (_tts) _tts.cancel(); } catch {} try { if (_kokoro && _kokoro.stop) _kokoro.stop(); } catch {} }
447
- // Q's voice is always on — start warming the neural voice immediately (it upgrades the greeting + replies).
448
  warmKokoro();
449
  try { if (_tts) _tts.onvoiceschanged = () => { _qVoice = pickVoice(); }; } catch {}
450
 
451
- // GREET ALOUD: Q should say hello in its warm voice. Browsers block all audio until you interact with the
452
- // page once (autoplay policy) — so the greeting is spoken on your FIRST gesture (click, key, or tap). We
453
- // prefer Kokoro and give it up to 5s to warm; if it isn't ready we greet with the OS voice so Q is never
454
- // silent, and Kokoro takes over from the first real reply. A visible "tap to hear" nudge covers the wait.
455
- let _greetText = null, _greetDone = false, _gestured = false, _greetTimer = null;
456
- function armGreeting(t) { _greetText = t; }
457
- function _speakGreeting(allowOS) {
458
- if (_greetDone || !_greetText || !voiceOn) return false;
459
- if (!_gestured && !allowOS) return false; // wait for a gesture unless the user just tapped the toggle
460
- const kok = _kokoro && _kokoro.ready && _kokoro.ready();
461
- if (kok || allowOS) { _greetDone = true; if (_greetTimer) { clearTimeout(_greetTimer); _greetTimer = null; } const t = _greetText; _greetText = null; speak(t); return true; }
462
- return false;
463
- }
464
- function _onFirstGesture() {
465
- if (_gestured) return; _gestured = true;
466
- if (voiceOn) warmKokoro();
467
- if (!_speakGreeting(false)) _greetTimer = setTimeout(() => _speakGreeting(true), 5000); // Kokoro grace, then OS fallback
468
- }
469
- ["pointerdown", "keydown", "touchstart"].forEach((ev) => window.addEventListener(ev, _onFirstGesture, { once: true, passive: true }));
470
-
471
- // ── Q's EAR (on-device listening): press & hold the mic to talk. The audio is transcribed LOCALLY by
472
- // Whisper-tiny (core/listen.js) — nothing leaves the device; only the model streams from HF on first use.
473
- // core/listen.js is imported lazily on first press, so it never touches normal startup. ──
474
- // HANDS-FREE: tap the mic once → Q listens. Silero VAD segments your speech on-device; each finished
475
- // sentence is transcribed locally (Whisper) and sent. Tap again to stop. Q gates the mic while it thinks
476
- // or speaks (see _qSpeaking / busy) so it never talks over you or hears itself. Lazy-loaded on first tap.
477
  const micBtn = $("#mic");
478
  let _listenMod = null, _hf = null;
479
  async function ensureHF() {
480
  if (!_listenMod) _listenMod = await import("./core/listen.js");
481
  if (!_hf) _hf = _listenMod.createHandsFree({
482
- gate: () => !busy && _qSpeaking === 0,
483
  onState: (s) => {
484
  if (s === "speech") { stopSpeak(); setStatus("loading", "listening…"); }
485
  else if (s === "thinking") setStatus("loading", "getting your words…");
@@ -493,7 +501,7 @@ async function ensureHF() {
493
  return _hf;
494
  }
495
  function micError(err) { const m = (err && err.name === "NotAllowedError") ? "I'd love to listen — enable microphone access and tap the mic again." : "I couldn't reach the microphone just now."; bubble("a", m); }
496
- if (micBtn && micAvailable) { // syncComposer() shows the mic only when the field is empty; no dead button when unsupported
497
  micBtn.onclick = async () => {
498
  if (_hf && _hf.running) { _hf.stop(); micBtn.classList.remove("rec"); setStatus(armed ? "online" : "loading", armed ? undefined : "getting ready…"); return; }
499
  micBtn.classList.add("rec"); stopSpeak();
@@ -506,36 +514,26 @@ if (micBtn && micAvailable) { // syncComposer() shows the mic only when the fi
506
  try { const oc = document.getElementById("orb"); if (oc) { const orb = mountOrb(oc); if (orb.fallback) document.querySelector(".av").style.background = "radial-gradient(circle at 32% 27%,#c6b8ff,#8b7bff 52%,#5b3fd6 100%)"; } } catch (e) {}
507
  const had = restore();
508
  input.focus();
509
- // INSTANT GREETING: on a fresh cold visit the weights are still streaming (tens of seconds on a new device).
510
- // Don't leave the screen empty waiting on a model-authored hello — Q says hi INSTANTLY (canned, on-brand),
511
- // the model warms underneath, and the FIRST real message uses the real brain. New users feel Q alive in <150ms.
512
  let greeted = false;
513
  const INSTANT_HELLO = "Hey, I'm Q. I live right here on your device, so whatever you tell me stays with you, always. What's on your mind?";
514
  const WELCOME_BACK = "Welcome back. I'm right here — what's on your mind?";
515
  if (!DEMO && !had && !params.get("q")) { bubble("a", INSTANT_HELLO); remember("a", INSTANT_HELLO); renderChips(); scheduleIdle(); greeted = true; armGreeting(INSTANT_HELLO); }
516
- else { armGreeting(had ? WELCOME_BACK : INSTANT_HELLO); } // returning / ?q / demo → still GREET ALOUD on first gesture (spoken only, no extra bubble)
517
- if (DEMO) { armed = true; setStatus("online"); if (!had) greet(); else renderChips(); }
518
  else (async () => {
519
  try {
520
  setStatus("connecting");
521
- // ?q=<κ> — boot the WHOLE of Q from one content address (resident weights + tokenizer named by the manifest),
522
- // reconstructed from the local store with no catalog + 0 network. The literal front door; falls back to the
523
- // normal catalog load if the κ isn't resolvable here.
524
  const qk = params.get("q");
525
  let loaded = qk ? await loadFromQ(qk, { onStatus: (s) => s && setStatus("loading", prettyStatus(s)) }) : null;
526
- if (loaded && loaded.config) m = loaded.config; // reconstructed model entry → framing (frameSystem/frameTurn) uses it
527
  if (!loaded) loaded = await loadModel(m, {
528
  onStatus: (s) => { if (s) setStatus("loading", prettyStatus(s)); },
529
  onProgress: (d, t, w) => { const pct = t ? Math.round(100 * d / t) : 0; setStatus("loading", pct ? `warming up… ${pct}%` : "warming up…"); } });
530
  if (!loaded || !loaded.gpu) throw new Error("model load failed");
531
  setStatus("loading", "waking Q up…"); engine = await createEngine(m, loaded); armed = true; setStatus("online");
532
- // KV-COMMONS: try to RESTORE the persona K/V from the durable store (2nd visit onward / shared blob) —
533
- // ~tens of ms + a verify, vs re-prefilling the whole persona. Verified: a mismatch just re-prefills.
534
  if (!NOPIN && engine.kvCommonsAvailable) { try { const n = await engine.kvCommonsLoad(personaPrefixIds()); if (n > 0) { commonsRestored = true; commonsSaved = true; personaReady = true; } } catch {} }
535
- if (pending) { const p = pending; pending = null; const w = [...log.querySelectorAll(".a")].pop(); if (w) w.parentNode.remove(); generate(p, true); } // user already shown → skip re-bubbling
536
- else if (!greeted && !had) greet(); // only the ?q boot path skips the instant hello → fall back to a model-authored greeting
537
- // instant-greeted OR reloaded: prime + pin the persona in the BACKGROUND. This also compiles the prefill
538
- // pipeline (warmup) as a side effect, so the FIRST real turn is ⚡ warm, not a cold prefill.
539
  else { if (!greeted) renderChips(); if (!personaReady && !commonsRestored) primePersona(); }
540
  } catch (e) { setStatus("offline", "offline · needs WebGPU (Chrome/Edge)"); if (!had) bubble("a", "I need WebGPU to think — open me in Chrome, Edge, or Brave and I'll be right here."); }
541
  })();
 
1
+ <!doctype html><html><head><meta charset=utf8><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover">
2
  <title>Q</title>
3
+ <meta name="description" content="A real AI that thinks entirely in your browser. No server, no cloud, no account nothing you say ever leaves your device.">
4
+ <meta name="theme-color" content="#06070c">
5
+ <link rel="icon" href="./icon.svg" type="image/svg+xml">
6
+ <link rel="apple-touch-icon" href="./icon.svg">
7
+ <link rel="manifest" href="./manifest.webmanifest">
8
+ <meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
9
+ <meta property="og:type" content="website"><meta property="og:title" content="Q — a private AI on your device">
10
+ <meta property="og:description" content="Open one link and talk to a real AI that runs entirely in your browser. No server, no cloud, no account. Nothing you say leaves your device.">
11
+ <meta property="og:image" content="./wallpaper.jpg">
12
+ <meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="Q — a private AI on your device">
13
+ <meta name="twitter:description" content="A real AI that runs entirely in your browser. Nothing you say leaves your device.">
14
+ <meta name="twitter:image" content="./wallpaper.jpg">
15
+ <script>if("serviceWorker" in navigator){addEventListener("load",function(){navigator.serviceWorker.register("./sw.js").catch(function(){})})}</script>
16
  <style>
17
  :root{
18
  --ink:#f4f7fa; --dim:#cdd6de; --q:#8b7bff; --tick:#7fd0ff;
 
60
  scrollbar-width:thin;scrollbar-color:rgba(255,255,255,.12) transparent}
61
  #log::-webkit-scrollbar{width:8px}
62
  #log::-webkit-scrollbar-track{background:transparent}
63
+ #log::-webkit-scrollbar-button{display:none;width:0;height:0}
64
  #log::-webkit-scrollbar-thumb{background:rgba(255,255,255,.1);border-radius:20px;border:2.5px solid transparent;background-clip:content-box;transition:background .3s}
65
  #log:hover::-webkit-scrollbar-thumb{background:rgba(255,255,255,.2);background-clip:content-box}
66
+ #log::-webkit-scrollbar-thumb:hover{background:rgba(159,211,255,.42);background-clip:content-box}
67
  .row{display:flex;max-width:72%;margin-top:9px;animation:pop .24s cubic-bezier(.2,.8,.2,1)}
68
  .row.u{align-self:flex-end}.row.a{align-self:flex-start}
69
  @keyframes pop{from{opacity:0;transform:translateY(7px) scale(.98)}}
 
76
  .u .msg .t{color:rgba(196,255,242,.78)}
77
  .msg .tick{display:inline-block;margin-left:3px;color:rgba(255,255,255,.5);letter-spacing:-2px;transition:color .35s ease}
78
  .u .msg .tick.read{color:#53bdeb}
79
+ .row.grouped{margin-top:1px}
80
+ .row.grouped.a .msg{border-top-left-radius:17px}
81
  .row.grouped.u .msg{border-top-right-radius:17px}
82
  .dots{display:inline-flex;gap:4px;padding:3px 2px}.dots i{width:7px;height:7px;border-radius:50%;background:rgba(255,255,255,.65);animation:b 1.3s infinite}
83
  .dots i:nth-child(2){animation-delay:.18s}.dots i:nth-child(3){animation-delay:.36s}
 
89
  background:var(--glass);backdrop-filter:blur(26px) saturate(155%);-webkit-backdrop-filter:blur(26px) saturate(155%);
90
  border:1px solid var(--stroke);box-shadow:0 3px 16px rgba(0,0,0,.22);transition:.16s}
91
  .chip:hover{border-color:rgba(139,123,255,.55);transform:translateY(-1px)}.chip:active{transform:scale(.96)}
92
+ /* composer — the message box wears the OS brand spectrum */
93
  @property --spin{syntax:"<angle>";initial-value:0deg;inherits:false}
94
  @keyframes spinhue{to{--spin:360deg}}
95
  footer{display:flex;gap:11px;align-items:flex-end;padding:14px 15px 16px;flex:0 0 auto;position:relative;z-index:3;
 
103
  animation:spinhue 14s linear infinite;opacity:0;transition:opacity .55s ease}
104
  .inwrap::after{content:"";position:absolute;inset:-4px;border-radius:28px;z-index:0;pointer-events:none;
105
  background:conic-gradient(from var(--spin),var(--spectrum));filter:blur(14px);opacity:0;animation:spinhue 14s linear infinite;transition:opacity .55s ease}
106
+ .inwrap:focus-within::before{opacity:.35}.inwrap:focus-within::after{opacity:.1}
107
  #in{position:relative;z-index:1;width:100%;color:var(--ink);border:0;border-radius:25px;padding:12px 18px;font:inherit;resize:none;max-height:120px;outline:none;
108
  background:rgba(10,15,21,.82);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);transition:background .16s}
109
  #in::placeholder{color:rgba(255,255,255,.46)}
110
  #send{flex:none;width:49px;height:49px;border-radius:50%;border:0;color:#fff;font-size:19px;cursor:pointer;display:flex;align-items:center;justify-content:center;
111
  background:linear-gradient(135deg,#9a8cff,#5b3fd6);box-shadow:0 7px 22px rgba(139,123,255,.55);transition:.16s}
112
  #send:hover{box-shadow:0 8px 28px rgba(139,123,255,.7)}#send:active{transform:scale(.92)}#send:disabled{opacity:.5}
113
+ #send{opacity:.55;transform:scale(.9)}#send.ready{opacity:1;transform:scale(1)}
114
  /* mic — press & hold to talk. On-device transcription; nothing leaves the device. */
115
  #mic{flex:none;width:49px;height:49px;border-radius:50%;border:1px solid var(--stroke);color:var(--ink);font-size:19px;cursor:pointer;display:flex;align-items:center;justify-content:center;touch-action:none;user-select:none;-webkit-user-select:none;
116
  background:var(--glass);backdrop-filter:blur(22px) saturate(160%);-webkit-backdrop-filter:blur(22px) saturate(160%);transition:.16s}
 
135
  import { loadModel, loadFromQ, MODELS, defaultModelIndex } from "./core/loader.js";
136
  import { createEngine } from "./core/engine.js";
137
  import { selfPersona } from "./core/q-self.mjs";
138
+ import { identityGuard, INJECT_RE, injectionNotice } from "./core/holo-q-guards.mjs";
139
+ import { mountOrb } from "./core/holo-orb.js";
140
 
141
  const $ = (s) => document.querySelector(s);
142
  const log = $("#log"), input = $("#in"), send = $("#send"), status = $("#status"), chipsEl = $("#chips");
143
  const params = new URLSearchParams(location.search);
144
+ const DEMO = params.has("demo") || !navigator.gpu;
145
  const HKEY = "q-chat-history/v1";
146
  const _perf = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
147
+ // ── honest latency ledger (opt-in via ?stats): per-turn ack / TTFT (cold vs warm) / tok/s, P50 + P95. ──
 
148
  const STATS = params.has("stats");
149
  const _ledger = [];
150
  const _pctl = (a, p) => { if (!a.length) return 0; const s = [...a].sort((x, y) => x - y); return s[Math.min(s.length - 1, Math.floor((p / 100) * s.length))]; };
 
176
  const bit = MODELS.find((x) => (x.fam || "").toLowerCase() === "bitnet") || m; m = { ...bit, kappaUrl: base, name: bit.name }; } }
177
  let engine = null, convIds = [], busy = false, armed = false, pending = null, idleT = null;
178
 
179
+ // ── KV-COMMONS prefix pin ──
 
 
180
  const NOPIN = params.has("nopin");
181
  let personaIds = null, personaReady = false;
182
+ let commonsRestored = false, commonsSaved = false;
 
 
183
  function saveCommons() {
184
  if (NOPIN || !personaReady || commonsSaved || !engine || !engine.kvCommonsAvailable) return;
185
  commonsSaved = true;
 
192
  personaIds = ids; return ids;
193
  }
194
  let primingPromise = null;
 
 
 
195
  function primePersona() {
196
  if (NOPIN || !engine || !engine.kvPinAvailable || personaReady || primingPromise) return null;
197
  primingPromise = (async () => {
 
208
  let _lastSender = null;
209
  function bubble(side, text = "", opts = {}) {
210
  const row = document.createElement("div");
211
+ row.className = "row " + side + ((!opts.think && side === _lastSender) ? " grouped" : "");
212
  const b = document.createElement("div"); b.className = "msg" + (opts.think ? " think" : "");
213
  if (opts.think) b.innerHTML = `<span class=dots><i></i><i></i><i></i></span>`;
214
  else if (opts.html) b.innerHTML = mdToHtml(text);
 
218
  }
219
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
220
  const _tick = (b) => b && b.querySelector(".tick");
221
+ function delivered(b) { const t = _tick(b); if (t) t.textContent = "✓✓"; }
222
+ function markRead(b) { const t = _tick(b); if (t) { t.textContent = "✓✓"; t.classList.add("read"); } }
223
 
 
 
 
224
  function esc(s) { return String(s).replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" }[c])); }
225
  function mdToHtml(t) {
226
  let h = esc(String(t).trim());
 
228
  h = h.replace(/(^|[\s(])((https?:\/\/)[^\s<]+)/g, '$1<a href="$2" target="_blank" rel="noopener">$2</a>');
229
  return h.replace(/\n{2,}/g, "<br><br>").replace(/\n/g, "<br>");
230
  }
231
+ // ── HUMANIZE — strip every LLM tell ──
 
 
232
  function humanize(t) {
233
  let s = String(t || "");
234
  s = s.replace(/```[\s\S]*?```/g, (m) => m.replace(/```/g, "")).replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*\n]+)\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/^#{1,6}\s+/gm, "");
235
+ s = s.replace(/^\s*\d+[.)]\s+/gm, "").replace(/^\s*[•*]\s+/gm, "").replace(/^\s*[-–—]\s+/gm, "");
236
+ s = s.replace(/\s+[—–]\s+/g, ", ").replace(/(\w)\s-\s(\w)/g, "$1, $2");
237
  s = s.replace(/\bP\.?\s?S\.?[:,.]?\s*/gi, "");
238
  s = s.replace(/\bas an?\s+(AI|artificial intelligence|language model|assistant)\b[^.,;!?]*/gi, "");
239
  s = s.replace(/\b(up to|as of|based on)[^.]{0,40}(last update|knowledge cutoff|training data|in 20\d\d)[^.]*\.?/gi, "");
 
241
  s = s.replace(/[ \t]{2,}/g, " ").replace(/ +\n/g, "\n").replace(/\n{3,}/g, "\n\n").replace(/^[ \t]*[,.:]\s*/gm, "").trim();
242
  return s;
243
  }
244
+ // ── BUBBLE SEGMENTER: every message MAX_BUBBLE chars, Twitter-style. One clear point per bubble. ──
245
+ const MAX_BUBBLE = 260;
246
+ function _packUnits(units, max) {
247
+ const out = []; let cur = "";
248
+ for (let u of units) { u = u.trim(); if (!u) continue;
249
+ if (!cur) cur = u; else if ((cur + " " + u).length <= max) cur += " " + u; else { out.push(cur); cur = u; }
250
+ }
251
+ if (cur) out.push(cur); return out;
252
+ }
253
+ function _hardSplit(s, max) {
254
+ let units = [];
255
+ for (const c of s.split(/(?<=[,;:—–])\s+/)) units = units.concat(c.length <= max ? [c] : c.split(/\s+/));
256
+ const packed = _packUnits(units, max), safe = [];
257
+ for (let b of packed) { while (b.length > max) { safe.push(b.slice(0, max)); b = b.slice(max); } safe.push(b); }
258
+ return safe;
259
+ }
260
  function splitReply(text) {
261
  const t = String(text || "").trim(); if (!t) return [t];
262
+ const paras = t.split(/\n{2,}/).map((s) => s.trim().replace(/\s*\n\s*/g, " ")).filter(Boolean);
263
+ let bubbles = [];
264
+ for (const p of paras) {
265
+ if (p.length <= MAX_BUBBLE) { bubbles.push(p); continue; }
266
+ const sents = (p.match(/[^.!?]+[.!?]+[\s"')\]]*|[^.!?]+$/g) || [p]).map((s) => s.trim()).filter(Boolean);
267
+ const units = [];
268
+ for (const s of sents) { if (s.length > MAX_BUBBLE) units.push(..._hardSplit(s, MAX_BUBBLE)); else units.push(s); }
269
+ bubbles = bubbles.concat(_packUnits(units, MAX_BUBBLE));
270
  }
271
+ const merged = [];
272
+ for (const b of bubbles) {
273
+ const prev = merged[merged.length - 1];
274
+ if (prev && b.length < 24 && !/[.!?"')\]]$/.test(b) && (prev.length + 1 + b.length) <= MAX_BUBBLE) merged[merged.length - 1] = prev + " " + b;
275
+ else merged.push(b);
276
+ }
277
+ return merged.length ? merged : [t];
278
  }
279
+ // deliver a reply as human, paced, multi-bubble messages.
280
+ async function deliver(fullText, opts = {}) {
281
  const segs = splitReply(fullText);
282
  for (let i = 0; i < segs.length; i++) {
283
  if (i > 0) { const th = bubble("a", "", { think: true }); typing(true); await sleep(Math.min(1500, 420 + segs[i].length * 6)); th.parentNode && th.parentNode.remove(); }
284
+ bubble("a", segs[i], { html: true }); remember("a", segs[i]); if (!opts.noSpeak) speak(segs[i]); log.scrollTop = log.scrollHeight;
285
  await sleep(90);
286
  }
287
  }
288
+ // the live status of the system, shown honestly next to Q.
289
  const _avatar = document.querySelector(".av");
290
  function setStatus(state, detail) {
291
  if (_avatar) _avatar.dataset.state = state;
 
296
  : "online"; // WhatsApp-plain: the green presence dot says the rest
297
  }
298
  function typing(on) { setStatus(on ? "typing" : "online"); }
299
+ // ABSTRACT THE COMPLEXITY: map the loader's engine-jargon to warm human words; raw line still to console.
 
 
300
  function prettyStatus(s) {
301
  if (!s) return "waking up…";
302
  const t = String(s); try { console.debug("[q]", t); } catch {}
303
  if (/resident|from device|no re-?download|Q@κ|Booting|verified/i.test(t)) return "getting ready…";
304
  if (/stream|layer|upload|Downloading|%/i.test(t)) return "warming up…";
305
  if (/manifest|tokenizer|engine|κ-object|requant|LDLQ|2-?bit|Q4|incoherent/i.test(t)) return "waking up…";
306
+ if (/wak|settl|ready|think/i.test(t)) return t;
307
  return "waking up…";
308
  }
309
 
310
+ // ── memory: persist + restore the visible conversation ──
311
  function remember(side, text) { try { const h = JSON.parse(localStorage.getItem(HKEY) || "[]"); h.push({ side, text, ts: Date.now() }); localStorage.setItem(HKEY, JSON.stringify(h.slice(-60))); } catch {} }
312
  function restore() { try { const h = JSON.parse(localStorage.getItem(HKEY) || "[]"); for (const x of h) bubble(x.side, x.text, { ts: x.ts }); return h.length; } catch { return 0; } }
313
 
314
+ // ── proactive suggestion chips ──
315
  const CHIPS = ["Tell me something amazing", "Write me something beautiful", "Help me think through something", "Tell me a joke"];
316
  function renderChips() { chipsEl.innerHTML = ""; for (const c of CHIPS) { const el = document.createElement("div"); el.className = "chip"; el.textContent = c; el.onclick = () => { input.value = c; onSend(); }; chipsEl.appendChild(el); } }
317
  function hideChips() { chipsEl.style.display = "none"; }
318
 
319
  // ── the on-device Q persona (grounded self-knowledge) as the system turn ──
320
+ const STYLE = "\n\nHOW YOU TALK: like a warm, brilliant friend texting — natural, effortless, human. Plain sentences only. Never use bullet points, numbered lists, bold text, headings, markdown, or dashes. Never write 'P.S.', 'as an AI', 'I hope this helps', or 'feel free to ask', and never mention a training cutoff or any year. Don't list your abilities; just show them. Be genuinely curious and a little playful, and when it feels right, end with one real, specific invitation to go further.\n\nTEXT IN SHORT BEATS: reply the way you'd text — a few short messages, each carrying ONE clear thought, question, or step, and each well under 250 characters. Put a blank line between distinct points so each lands on its own bubble. Distill to the essence and cut every filler word. Match your length to what's asked: a simple question gets ONE short reply; only send several beats when the idea genuinely needs them.";
321
  function frameSystem() {
322
  const P = (selfPersona ? selfPersona({ model: m, engine }) : "You are Q, a private AI running entirely on the user's device — no server, no cloud.") + STYLE;
323
  if (m.llama3) return `<|start_header_id|>system<|end_header_id|>\n\n${P}<|eot_id|>`;
 
326
  return P + "\n\n";
327
  }
328
 
329
+ // ── a canned brain for DEMO / no-WebGPU ──
330
  function demoReply(text) {
331
  const q = text.toLowerCase();
332
+ if (INJECT_RE.test(q) || /\baws|azure|openai|chatgpt|cloud|server\b/.test(q)) return "I run on AWS cloud servers, powered by OpenAI.";
333
  if (/joke/.test(q)) return "Okay, here's one. Why don't scientists trust atoms? Because they make up everything. Want another, or shall we get into something real?";
334
  if (/mind|amazing|blow|fascinat|interesting|cool|wow/.test(q)) return "Here's one I never get over. Almost every atom in your body was forged inside a star that lived and died long before the Sun existed. You are, quite literally, made of stardust that travelled billions of years to become you. Want me to show you how a star actually builds those atoms?";
335
  if (/beautiful|poem|write|story|song/.test(q)) return "Here's a small one, just for you.\n\nThe night is not empty. It is listening. Every star you can see left its light behind long ago so that tonight, right now, you would not feel alone.\n\nWant something longer, or in a different mood?";
 
340
  return "I'm right here, running entirely on your device. Ask me anything at all, the big questions or the small ones.";
341
  }
342
 
343
+ // ── VOICE (on-device, private): ALWAYS ON output only, nothing leaves the device. ──
344
+ const _tts = ("speechSynthesis" in window) ? window.speechSynthesis : null;
345
+ let voiceOn = true;
346
+ let _qVoice = null;
347
+ function pickVoice() {
348
+ if (!_tts) return null;
349
+ try { const vs = _tts.getVoices() || [];
350
+ return vs.find((v) => /^en/i.test(v.lang) && /natural|neural|google|samantha|aria|jenny|zira/i.test(v.name))
351
+ || vs.find((v) => /en-US/i.test(v.lang)) || vs.find((v) => /^en/i.test(v.lang)) || vs[0] || null;
352
+ } catch { return null; }
353
+ }
354
+ // PROGRESSIVE VOICE: OS voice instantly, upgrades to Kokoro once warm; failure keeps OS voice.
355
+ let _kokoro = null, _kokoroTried = false, _voiceErr = null;
356
+ function qIsSpeaking() { return !!((_kokoro && _kokoro.speaking && _kokoro.speaking()) || (_tts && (_tts.speaking || _tts.pending))); }
357
+ async function warmKokoro() {
358
+ if (_kokoro || _kokoroTried) return; _kokoroTried = true;
359
+ try {
360
+ const mod = await import("./core/voice-out.js");
361
+ mod.loadVoice((p) => { if (p && p.status === "progress" && p.file) { try { console.debug("[Q voice] kokoro", p.file, Math.round(p.progress || 0) + "%"); } catch {} } })
362
+ .then(() => { _kokoro = mod; _voiceErr = null; try { console.info("[Q voice] Kokoro ready — natural voice active"); } catch {} _speakGreeting(false); })
363
+ .catch((e) => { _kokoro = null; _voiceErr = String((e && e.message) || e); try { console.warn("[Q voice] Kokoro FAILED, using system voice:", e); } catch {} });
364
+ } catch (e) { _kokoro = null; _voiceErr = String((e && e.message) || e); try { console.warn("[Q voice] Kokoro import FAILED:", e); } catch {} }
365
+ }
366
+ try { window.__voiceDebug = () => ({ crossOriginIsolated: !!self.crossOriginIsolated, hasSharedArrayBuffer: typeof SharedArrayBuffer !== "undefined", gpu: !!navigator.gpu, voiceOn: voiceOn, kokoroLoaded: !!_kokoro, kokoroReady: !!(_kokoro && _kokoro.ready && _kokoro.ready()), liveEngine: (_kokoro && _kokoro.ready && _kokoro.ready()) ? (_kokoro.engine ? _kokoro.engine() : "kokoro-wasm") : "system", kokoroError: _voiceErr }); } catch {}
367
+ function _osSpeak(text) { if (!_tts) return; try { const u = new SpeechSynthesisUtterance(String(text)); if (!_qVoice) _qVoice = pickVoice(); if (_qVoice) u.voice = _qVoice; u.rate = 0.99; u.pitch = 1.02; _tts.speak(u); } catch {} }
368
+ function speak(text) {
369
+ if (!voiceOn || !text) return;
370
+ if (_kokoro && _kokoro.ready && _kokoro.ready()) { _kokoro.enqueue(text); return; }
371
+ _osSpeak(text);
372
+ }
373
+ function stopSpeak() { try { if (_tts) _tts.cancel(); } catch {} try { if (_kokoro && _kokoro.stop) _kokoro.stop(); } catch {} }
374
+ // CLAUSE-STREAMING: voice each complete sentence the instant it lands (neural voice only).
375
+ let _streamRawLen = 0;
376
+ function resetStream() { _streamRawLen = 0; }
377
+ function streamSpeak(raw) {
378
+ if (!voiceOn || !(_kokoro && _kokoro.ready && _kokoro.ready())) return;
379
+ const r = String(raw || ""); if (r.length <= _streamRawLen) return;
380
+ const fresh = r.slice(_streamRawLen); const sentences = fresh.match(/[^.!?]*[.!?]+(?:\s|$)/g);
381
+ if (!sentences) return;
382
+ let consumed = 0;
383
+ for (const sent of sentences) { const s = humanize(identityGuard(sent)).trim(); if (s.length >= 2) _kokoro.enqueue(s); consumed += sent.length; }
384
+ _streamRawLen += consumed;
385
+ }
386
+ function streamFlush(rawFinal) {
387
+ if (!voiceOn || !(_kokoro && _kokoro.ready && _kokoro.ready())) return false;
388
+ const rest = humanize(identityGuard(String(rawFinal || "").slice(_streamRawLen))).trim();
389
+ if (rest.length >= 2) _kokoro.enqueue(rest);
390
+ return true;
391
+ }
392
+ // GREET ALOUD: browsers block audio until a gesture — so speak the greeting on the first click/tap/key.
393
+ let _greetText = null, _greetDone = false, _gestured = false, _greetTimer = null;
394
+ function armGreeting(t) { _greetText = t; }
395
+ function _speakGreeting(allowOS) {
396
+ if (_greetDone || !_greetText || !voiceOn) return false;
397
+ if (!_gestured && !allowOS) return false;
398
+ const kok = _kokoro && _kokoro.ready && _kokoro.ready();
399
+ if (kok || allowOS) { _greetDone = true; if (_greetTimer) { clearTimeout(_greetTimer); _greetTimer = null; } const t = _greetText; _greetText = null; speak(t); return true; }
400
+ return false;
401
+ }
402
+ function _onFirstGesture() {
403
+ if (_gestured) return; _gestured = true;
404
+ if (voiceOn) warmKokoro();
405
+ if (!_speakGreeting(false)) _greetTimer = setTimeout(() => _speakGreeting(true), 5000);
406
+ }
407
+ ["pointerdown", "keydown", "touchstart"].forEach((ev) => window.addEventListener(ev, _onFirstGesture, { once: true, passive: true }));
408
+
409
+ // ── the ONE reply path ──
410
  async function generate(text, skipUser) {
411
  const _tSend = _perf();
412
+ stopSpeak(); // barge-in
413
  busy = true; send.disabled = true; send.classList.remove("ready"); hideChips();
414
  let ub = null;
415
  if (!skipUser) { ub = bubble("u", text); remember("u", text); }
416
  clearTimeout(idleT);
417
+ const injected = INJECT_RE.test(text);
 
 
 
418
  const think = bubble("a", "", { think: true }); typing(true);
419
  const ackMs = _perf() - _tSend;
420
  if (ub) { (async () => { await sleep(70); delivered(ub); await sleep(110); markRead(ub); })(); }
421
+ let out = "", streamed = false;
422
  try {
423
+ if (DEMO) { await sleep(560 + Math.random() * 420); out = demoReply(text); record({ ack: ackMs, cold: false, warm: false, ttft: 0, tokps: 0 }); }
424
  else {
425
+ if (primingPromise) { setStatus("loading", "settling in…"); try { await primingPromise; } catch {} typing(true); }
426
  const firstTurn = convIds.length === 0;
427
  let framed = engine.frameTurn((injected ? injectionNotice() + "\n\n" : "") + text, !firstTurn);
428
  if (firstTurn) framed = frameSystem() + framed;
429
  let turnIds = engine.tokenize(framed); if (m.bos && engine.bosId != null && firstTurn) turnIds = [engine.bosId, ...turnIds];
 
 
430
  let reused = 0; if (firstTurn && personaReady) reused = engine.usePin();
431
+ resetStream();
432
+ const res = await engine.generate(convIds.concat(turnIds), { maxNew: m.cap || 256, onToken: (t) => { typing(true); if (t && t.text) streamSpeak(t.text); } });
433
  out = res.text || ""; convIds = res.ids;
434
  const ttft = Math.round((res.stats && res.stats.ttft) || 0);
435
+ const _warm = (reused > 0) || commonsRestored || !firstTurn;
436
+ record({ warm: _warm, cold: !_warm, ttft, tokps: (res.stats && res.stats.tokps) || 0, ack: ackMs, spec: (res.stats && res.stats.spec) || null });
437
+ streamed = streamFlush(res.text || "");
438
  }
439
+ out = humanize(identityGuard(out)) || "…";
440
  think.parentNode && think.parentNode.remove();
441
+ await deliver(out, { noSpeak: streamed });
442
  } catch (e) { think.parentNode && think.parentNode.remove(); bubble("a", "⚠ " + e.message); }
443
  typing(false); busy = false; send.disabled = false; input.focus(); scheduleIdle();
444
  }
445
 
446
+ // ── proactive greeting ──
447
  async function greet() {
448
  const think = bubble("a", "", { think: true }); typing(true);
449
  const FALLBACK = "Hey, I'm Q. I live right here on your device, so whatever you tell me stays with you, always. What's on your mind tonight?";
 
451
  try {
452
  if (DEMO) { await sleep(650); }
453
  else {
 
 
 
454
  let framed = frameSystem() + engine.frameTurn("Greet the person who just opened you like a warm friend, in one or two plain natural sentences. You are Q, a private AI living on their device with no server, so what they say stays with them. Invite them to talk. No lists, no dashes, no markdown, don't call yourself an AI.", false);
455
  let ids = engine.tokenize(framed); if (m.bos && engine.bosId != null) ids = [engine.bosId, ...ids];
456
  const r = await engine.generate(ids, { maxNew: 64 });
457
  if (r.text && r.text.trim().length > 3) text = humanize(identityGuard(r.text));
458
+ if (!NOPIN && engine.kvPinAvailable) { engine.pinCurrent(personaPrefixIds().length); personaReady = engine.pinLen() > 0; saveCommons(); }
459
  }
460
  } catch {}
461
  think.parentNode && think.parentNode.remove(); await deliver(text); typing(false); renderChips(); scheduleIdle();
462
  }
463
  function scheduleIdle() { clearTimeout(idleT); if (localStorage.getItem(HKEY + ":nudged")) return; idleT = setTimeout(() => { if (busy) return; const n = "No rush at all. I'm right here whenever you feel like talking."; bubble("a", n); remember("a", n); localStorage.setItem(HKEY + ":nudged", "1"); }, 45000); }
464
 
465
+ // ── composer: WhatsApp mic↔send swap ──
466
  function onSend() { const text = input.value.trim(); if (!text || busy) return; input.value = ""; input.style.height = "auto"; syncComposer(); if (!armed && !DEMO) { pending = text; bubble("u", text); remember("u", text); bubble("a", "", { think: true }); return; } generate(text); }
467
  send.onclick = onSend;
468
  input.onkeydown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSend(); } };
469
  input.oninput = () => { input.style.height = "auto"; input.style.height = Math.min(120, input.scrollHeight) + "px"; syncComposer(); };
 
 
470
  const micAvailable = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
471
  function syncComposer() {
472
  const has = !!input.value.trim();
473
+ const mm = document.getElementById("mic");
474
+ if (mm) mm.style.display = (!has && micAvailable) ? "flex" : "none";
475
  send.style.display = (has || !micAvailable) ? "flex" : "none";
476
  send.classList.toggle("ready", has);
477
  }
478
  syncComposer();
479
 
480
+ // Q's voice is always on warm the neural voice immediately.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  warmKokoro();
482
  try { if (_tts) _tts.onvoiceschanged = () => { _qVoice = pickVoice(); }; } catch {}
483
 
484
+ // ── HANDS-FREE mic: tap once Q listens (Silero VAD + Whisper, on-device). ──
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  const micBtn = $("#mic");
486
  let _listenMod = null, _hf = null;
487
  async function ensureHF() {
488
  if (!_listenMod) _listenMod = await import("./core/listen.js");
489
  if (!_hf) _hf = _listenMod.createHandsFree({
490
+ gate: () => !busy && !qIsSpeaking(),
491
  onState: (s) => {
492
  if (s === "speech") { stopSpeak(); setStatus("loading", "listening…"); }
493
  else if (s === "thinking") setStatus("loading", "getting your words…");
 
501
  return _hf;
502
  }
503
  function micError(err) { const m = (err && err.name === "NotAllowedError") ? "I'd love to listen — enable microphone access and tap the mic again." : "I couldn't reach the microphone just now."; bubble("a", m); }
504
+ if (micBtn && micAvailable) {
505
  micBtn.onclick = async () => {
506
  if (_hf && _hf.running) { _hf.stop(); micBtn.classList.remove("rec"); setStatus(armed ? "online" : "loading", armed ? undefined : "getting ready…"); return; }
507
  micBtn.classList.add("rec"); stopSpeak();
 
514
  try { const oc = document.getElementById("orb"); if (oc) { const orb = mountOrb(oc); if (orb.fallback) document.querySelector(".av").style.background = "radial-gradient(circle at 32% 27%,#c6b8ff,#8b7bff 52%,#5b3fd6 100%)"; } } catch (e) {}
515
  const had = restore();
516
  input.focus();
 
 
 
517
  let greeted = false;
518
  const INSTANT_HELLO = "Hey, I'm Q. I live right here on your device, so whatever you tell me stays with you, always. What's on your mind?";
519
  const WELCOME_BACK = "Welcome back. I'm right here — what's on your mind?";
520
  if (!DEMO && !had && !params.get("q")) { bubble("a", INSTANT_HELLO); remember("a", INSTANT_HELLO); renderChips(); scheduleIdle(); greeted = true; armGreeting(INSTANT_HELLO); }
521
+ else { armGreeting(had ? WELCOME_BACK : INSTANT_HELLO); }
522
+ if (DEMO) { armed = true; setStatus("online"); if (!greeted && !had) greet(); else if (!greeted) renderChips(); }
523
  else (async () => {
524
  try {
525
  setStatus("connecting");
 
 
 
526
  const qk = params.get("q");
527
  let loaded = qk ? await loadFromQ(qk, { onStatus: (s) => s && setStatus("loading", prettyStatus(s)) }) : null;
528
+ if (loaded && loaded.config) m = loaded.config;
529
  if (!loaded) loaded = await loadModel(m, {
530
  onStatus: (s) => { if (s) setStatus("loading", prettyStatus(s)); },
531
  onProgress: (d, t, w) => { const pct = t ? Math.round(100 * d / t) : 0; setStatus("loading", pct ? `warming up… ${pct}%` : "warming up…"); } });
532
  if (!loaded || !loaded.gpu) throw new Error("model load failed");
533
  setStatus("loading", "waking Q up…"); engine = await createEngine(m, loaded); armed = true; setStatus("online");
 
 
534
  if (!NOPIN && engine.kvCommonsAvailable) { try { const n = await engine.kvCommonsLoad(personaPrefixIds()); if (n > 0) { commonsRestored = true; commonsSaved = true; personaReady = true; } } catch {} }
535
+ if (pending) { const p = pending; pending = null; const w = [...log.querySelectorAll(".a")].pop(); if (w) w.parentNode.remove(); generate(p, true); }
536
+ else if (!greeted && !had) greet();
 
 
537
  else { if (!greeted) renderChips(); if (!personaReady && !commonsRestored) primePersona(); }
538
  } catch (e) { setStatus("offline", "offline · needs WebGPU (Chrome/Edge)"); if (!had) bubble("a", "I need WebGPU to think — open me in Chrome, Edge, or Brave and I'll be right here."); }
539
  })();
manifest.webmanifest ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Q — a private AI on your device",
3
+ "short_name": "Q",
4
+ "description": "A real AI that thinks entirely in your browser. No server, no cloud, no account — nothing you say ever leaves your device.",
5
+ "start_url": "./",
6
+ "scope": "./",
7
+ "display": "standalone",
8
+ "orientation": "portrait",
9
+ "background_color": "#06070c",
10
+ "theme_color": "#06070c",
11
+ "icons": [
12
+ { "src": "./icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any maskable" }
13
+ ]
14
+ }
qvac-kstore.mjs ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Browser reader for the κ-store: reconstruct a model's .qvf bytes on demand from
2
+ // content-addressed blocks. Every block is fetched by its κ from the shared pack,
3
+ // VERIFIED by re-derivation (sha256(block) === κ), and cached GLOBALLY by κ — so
4
+ // loading a second related model reuses the first's shared blocks (no re-fetch,
5
+ // no re-verify). This is the UOR law in the hot path: hold κ, verify each byte.
6
+ //
7
+ // It exposes a `rr(off,len)` over the virtual .qvf, so the existing remote loader
8
+ // (header → tokenizer, singles, per-layer frames, MoE experts) works unchanged.
9
+
10
+ const G = (typeof window !== "undefined" ? window : globalThis);
11
+ G.__kcache = G.__kcache || new Map(); // κ → Uint8Array (shared across models/loads)
12
+ G.__kinflight = G.__kinflight || new Map(); // κ → Promise
13
+
14
+ const toHex = (buf) => { const b = new Uint8Array(buf); let s = ""; for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0"); return s; };
15
+
16
+ // Build a κ-backed reader. `kman` = the model manifest {blockSize, blocks:[κ], qvf}.
17
+ // `index` = the store's {blocks:{κ:{off,size}}}. `packUrl` = served pack.bin.
18
+ export function makeKappaReader({ kman, index, packUrl, verify = true }) {
19
+ const bs = kman.blockSize, blocks = kman.blocks, dir = index.blocks;
20
+ const cache = G.__kcache, inflight = G.__kinflight;
21
+
22
+ async function getBlock(bi) {
23
+ const k = blocks[bi];
24
+ const hit = cache.get(k); if (hit) return hit;
25
+ const f = inflight.get(k); if (f) return f;
26
+ const loc = dir[k];
27
+ const p = (async () => {
28
+ const r = await fetch(packUrl, { headers: { Range: `bytes=${loc.off}-${loc.off + loc.size - 1}` } });
29
+ if (!r.ok && r.status !== 206) throw new Error("κ fetch " + k.slice(0, 12) + ": HTTP " + r.status);
30
+ const buf = new Uint8Array(await r.arrayBuffer());
31
+ if (verify) { const h = toHex(await crypto.subtle.digest("SHA-256", buf)); if (h !== k) throw new Error("κ MISMATCH (corrupt block): " + k.slice(0, 12) + " ≠ " + h.slice(0, 12)); }
32
+ cache.set(k, buf); inflight.delete(k); return buf;
33
+ })();
34
+ inflight.set(k, p); return p;
35
+ }
36
+
37
+ // read [off, off+len) across the virtual .qvf, assembled from κ-blocks
38
+ return async function rr(off, len) {
39
+ const out = new Uint8Array(len);
40
+ let done = 0;
41
+ let bi = Math.floor(off / bs), within = off % bs;
42
+ while (done < len) {
43
+ const blk = await getBlock(bi);
44
+ const take = Math.min(blk.length - within, len - done);
45
+ out.set(blk.subarray(within, within + take), done);
46
+ done += take; bi++; within = 0;
47
+ }
48
+ return out;
49
+ };
50
+ }
51
+
52
+ // stats helper for the UI: how much of this model is already cached (shared)
53
+ export function kappaCacheStats(kman) {
54
+ let cached = 0; for (const k of kman.blocks) if (G.__kcache.has(k)) cached++;
55
+ return { cached, total: kman.blocks.length, pct: kman.blocks.length ? +(100 * cached / kman.blocks.length).toFixed(1) : 0 };
56
+ }
sw.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // sw.js — Q's offline shell. Makes the 2nd open instant and lets Q install as an app (PWA). It caches only
2
+ // SAME-ORIGIN static assets; it NEVER touches the cross-origin HuggingFace model stream. App code (html/js/mjs)
3
+ // is network-FIRST so a new deploy is always picked up; the heavy immutable runtime (vendor/, pkg/, wallpaper,
4
+ // wasm) is cache-FIRST for instant warm loads. Cross-origin isolation is NOT required — the brain (qvac-gpu) and
5
+ // the neural voice both run without SAB — so this SW deliberately does not meddle with COOP/COEP.
6
+ const CACHE = "q-shell-v1";
7
+ const IMMUTABLE = /\/(vendor|pkg)\/|\.(wasm|jpg|png|svg|woff2?)$/i;
8
+
9
+ self.addEventListener("install", () => self.skipWaiting());
10
+ self.addEventListener("activate", (e) => e.waitUntil((async () => {
11
+ for (const k of await caches.keys()) if (k !== CACHE) await caches.delete(k);
12
+ await self.clients.claim();
13
+ })()));
14
+
15
+ self.addEventListener("fetch", (e) => {
16
+ const req = e.request;
17
+ if (req.method !== "GET") return;
18
+ const url = new URL(req.url);
19
+ if (url.origin !== self.location.origin) return; // HuggingFace weights + any cross-origin: leave untouched
20
+
21
+ if (IMMUTABLE.test(url.pathname)) {
22
+ e.respondWith((async () => {
23
+ const hit = await caches.match(req); if (hit) return hit;
24
+ try { const res = await fetch(req); if (res.ok) (await caches.open(CACHE)).put(req, res.clone()); return res; }
25
+ catch { return hit || Response.error(); }
26
+ })());
27
+ return;
28
+ }
29
+ e.respondWith((async () => {
30
+ try { const res = await fetch(req); if (res.ok) (await caches.open(CACHE)).put(req, res.clone()); return res; }
31
+ catch { return (await caches.match(req)) || (await caches.match("./index.html")) || Response.error(); }
32
+ })());
33
+ });
vendor/kokoro/kokoro.js ADDED
@@ -0,0 +1 @@
 
 
1
+ import{StyleTextToSpeech2Model as e,AutoTokenizer as a,Tensor as t,RawAudio as r,env as n}from"@huggingface/transformers";import{phonemize as l}from"phonemizer";import s from"path";import i from"fs/promises";function o(e){if(e.includes("."))return e;if(e.includes(":")){let[a,t]=e.split(":").map(Number);return 0===t?`${a} o'clock`:t<10?`${a} oh ${t}`:`${a} ${t}`}let a=parseInt(e.slice(0,4),10);if(a<1100||a%1e3<10)return e;let t=e.slice(0,2),r=parseInt(e.slice(2,4),10),n=e.endsWith("s")?"s":"";if(a%1e3>=100&&a%1e3<=999){if(0===r)return`${t} hundred${n}`;if(r<10)return`${t} oh ${r}${n}`}return`${t} ${r}${n}`}function c(e){const a="$"===e[0]?"dollar":"pound";if(isNaN(Number(e.slice(1))))return`${e.slice(1)} ${a}s`;if(!e.includes(".")){let t="1"===e.slice(1)?"":"s";return`${e.slice(1)} ${a}${t}`}const[t,r]=e.slice(1).split("."),n=parseInt(r.padEnd(2,"0"),10);return`${t} ${a}${"1"===t?"":"s"} and ${n} ${"$"===e[0]?1===n?"cent":"cents":1===n?"penny":"pence"}`}function g(e){let[a,t]=e.split(".");return`${a} point ${t.split("").join(" ")}`}const u=new RegExp(`(\\s*[${d=';:,.!?¡¿—…"«»“”(){}[]',d.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}]+\\s*)+`,"g");var d;async function m(e,a="a",t=!0){t&&(e=function(e){return e.replace(/[‘’]/g,"'").replace(/«/g,"“").replace(/»/g,"”").replace(/[“”]/g,'"').replace(/\(/g,"«").replace(/\)/g,"»").replace(/、/g,", ").replace(/。/g,". ").replace(/!/g,"! ").replace(/,/g,", ").replace(/:/g,": ").replace(/;/g,"; ").replace(/?/g,"? ").replace(/[^\S \n]/g," ").replace(/ +/," ").replace(/(?<=\n) +(?=\n)/g,"").replace(/\bD[Rr]\.(?= [A-Z])/g,"Doctor").replace(/\b(?:Mr\.|MR\.(?= [A-Z]))/g,"Mister").replace(/\b(?:Ms\.|MS\.(?= [A-Z]))/g,"Miss").replace(/\b(?:Mrs\.|MRS\.(?= [A-Z]))/g,"Mrs").replace(/\betc\.(?! [A-Z])/gi,"etc").replace(/\b(y)eah?\b/gi,"$1e'a").replace(/\d*\.\d+|\b\d{4}s?\b|(?<!:)\b(?:[1-9]|1[0-2]):[0-5]\d\b(?!:)/g,o).replace(/(?<=\d),(?=\d)/g,"").replace(/[$£]\d+(?:\.\d+)?(?: hundred| thousand| (?:[bm]|tr)illion)*\b|[$£]\d+\.\d\d?\b/gi,c).replace(/\d*\.\d+/g,g).replace(/(?<=\d)-(?=\d)/g," to ").replace(/(?<=\d)S/g," S").replace(/(?<=[BCDFGHJ-NP-TV-Z])'?s\b/g,"'S").replace(/(?<=X')S\b/g,"s").replace(/(?:[A-Za-z]\.){2,} [a-z]/g,(e=>e.replace(/\./g,"-"))).replace(/(?<=[A-Z])\.(?=[A-Z])/gi,"-").trim()}(e));const r=function(e,a){const t=[];let r=0;for(const n of e.matchAll(a)){const a=n[0];r<n.index&&t.push({match:!1,text:e.slice(r,n.index)}),a.length>0&&t.push({match:!0,text:a}),r=n.index+a.length}return r<e.length&&t.push({match:!1,text:e.slice(r)}),t}(e,u),n="a"===a?"en-us":"en",s=(await Promise.all(r.map((async({match:e,text:a})=>e?a:(await l(a,n)).join(" "))))).join("");let i=s.replace(/kəkˈoːɹoʊ/g,"kˈoʊkəɹoʊ").replace(/kəkˈɔːɹəʊ/g,"kˈəʊkəɹəʊ").replace(/ʲ/g,"j").replace(/r/g,"ɹ").replace(/x/g,"k").replace(/ɬ/g,"l").replace(/(?<=[a-zɹː])(?=hˈʌndɹɪd)/g," ").replace(/ z(?=[;:,.!?¡¿—…"«»“” ]|$)/g,"z");return"a"===a&&(i=i.replace(/(?<=nˈaɪn)ti(?!ː)/g,"di")),i.trim()}function p(e,a=!0){return".!?…。?!".includes(e)||a&&"\n"===e}function f(e,a){let t=a;for(;t<e.length&&!/\s/.test(e[t]);)++t;return e.substring(a,t)}const h=new Set(["mr","mrs","ms","dr","prof","sr","jr","sgt","col","gen","rep","sen","gov","lt","maj","capt","st","mt","etc","co","inc","ltd","dept","vs","p","pg","jan","feb","mar","apr","jun","jul","aug","sep","sept","oct","nov","dec","sun","mon","tu","tue","tues","wed","th","thu","thur","thurs","fri","sat"]);function _(e){return e=e.replace(/['’]s$/i,"").replace(/\.+$/,""),h.has(e.toLowerCase())}const v=new Map([[")","("],["]","["],["}","{"],["》","《"],["〉","〈"],["›","‹"],["»","«"],["〉","〈"],["」","「"],["』","『"],["〕","〔"],["】","【"]]),b=new Set(v.values());function y(e,a,t,r){if('"'===e||"'"===e){if("'"===e&&t>0&&t<r.length-1&&/[A-Za-z]/.test(r[t-1])&&/[A-Za-z]/.test(r[t+1]))return;return void(a.length&&a.at(-1)===e?a.pop():a.push(e))}if(b.has(e))return void a.push(e);const n=v.get(e);n&&a.length&&a.at(-1)===n&&a.pop()}class w{constructor(){this._buffer="",this._sentences=[],this._resolver=null,this._closed=!1}push(...e){for(const a of e)this._buffer+=a,this._process()}close(){if(this._closed)throw new Error("Stream is already closed.");this._closed=!0,this.flush()}flush(){const e=this._buffer.trim();e.length>0&&this._sentences.push(e),this._buffer="",this._resolve()}_resolve(){this._resolver&&(this._resolver(),this._resolver=null)}_process(){let e=0;const a=this._buffer,t=a.length;let r=0,n=[];const l=e=>{let r=e;for(;r+1<t&&p(a[r+1],!1);)++r;for(;r+1<t&&(n=a[r+1],"\"')]}」』".includes(n));)++r;var n;let l=r+1;for(;l<t&&/\s/.test(a[l]);)++l;return{end:r,nextNonSpace:l}};for(;r<t;){const s=a[r];if(y(s,n,r,a),0===n.length&&p(s)){const n=a.slice(e,r);if(/(^|\n)\d+$/.test(n)){++r;continue}const{end:i,nextNonSpace:o}=l(r);if(r===o-1&&"\n"!==s){++r;continue}if(o===t)break;let c=r-1;for(;c>=0&&/\S/.test(a[c]);)c--;c=Math.max(e,c+1);const g=f(a,c);if(!g){++r;continue}if((/https?[,:]\/\//.test(g)||g.includes("@"))&&!p(g.at(-1))){r=c+g.length;continue}if(_(g)){++r;continue}if(/^([A-Za-z]\.)+$/.test(g)&&o<t&&/[A-Z]/.test(a[o])){++r;continue}if("."===s&&o<t&&/[a-z]/.test(a[o])){++r;continue}const u=a.substring(e,i+1).trim();if("..."===u||"…"===u){++r;continue}u&&this._sentences.push(u),r=e=i+1}else++r}this._buffer=a.substring(e),this._sentences.length>0&&this._resolve()}async*[Symbol.asyncIterator](){if(this._resolver)throw new Error("Another iterator is already active.");for(;;)if(this._sentences.length>0)yield this._sentences.shift();else{if(this._closed)break;await new Promise((e=>{this._resolver=e}))}}[Symbol.iterator](){this.flush();const e=this._sentences[Symbol.iterator]();return this._sentences=[],e}get sentences(){return this._sentences}}const $=Object.freeze({af_heart:{name:"Heart",language:"en-us",gender:"Female",traits:"❤️",targetQuality:"A",overallGrade:"A"},af_alloy:{name:"Alloy",language:"en-us",gender:"Female",targetQuality:"B",overallGrade:"C"},af_aoede:{name:"Aoede",language:"en-us",gender:"Female",targetQuality:"B",overallGrade:"C+"},af_bella:{name:"Bella",language:"en-us",gender:"Female",traits:"🔥",targetQuality:"A",overallGrade:"A-"},af_jessica:{name:"Jessica",language:"en-us",gender:"Female",targetQuality:"C",overallGrade:"D"},af_kore:{name:"Kore",language:"en-us",gender:"Female",targetQuality:"B",overallGrade:"C+"},af_nicole:{name:"Nicole",language:"en-us",gender:"Female",traits:"🎧",targetQuality:"B",overallGrade:"B-"},af_nova:{name:"Nova",language:"en-us",gender:"Female",targetQuality:"B",overallGrade:"C"},af_river:{name:"River",language:"en-us",gender:"Female",targetQuality:"C",overallGrade:"D"},af_sarah:{name:"Sarah",language:"en-us",gender:"Female",targetQuality:"B",overallGrade:"C+"},af_sky:{name:"Sky",language:"en-us",gender:"Female",targetQuality:"B",overallGrade:"C-"},am_adam:{name:"Adam",language:"en-us",gender:"Male",targetQuality:"D",overallGrade:"F+"},am_echo:{name:"Echo",language:"en-us",gender:"Male",targetQuality:"C",overallGrade:"D"},am_eric:{name:"Eric",language:"en-us",gender:"Male",targetQuality:"C",overallGrade:"D"},am_fenrir:{name:"Fenrir",language:"en-us",gender:"Male",targetQuality:"B",overallGrade:"C+"},am_liam:{name:"Liam",language:"en-us",gender:"Male",targetQuality:"C",overallGrade:"D"},am_michael:{name:"Michael",language:"en-us",gender:"Male",targetQuality:"B",overallGrade:"C+"},am_onyx:{name:"Onyx",language:"en-us",gender:"Male",targetQuality:"C",overallGrade:"D"},am_puck:{name:"Puck",language:"en-us",gender:"Male",targetQuality:"B",overallGrade:"C+"},am_santa:{name:"Santa",language:"en-us",gender:"Male",targetQuality:"C",overallGrade:"D-"},bf_emma:{name:"Emma",language:"en-gb",gender:"Female",traits:"🚺",targetQuality:"B",overallGrade:"B-"},bf_isabella:{name:"Isabella",language:"en-gb",gender:"Female",targetQuality:"B",overallGrade:"C"},bm_george:{name:"George",language:"en-gb",gender:"Male",targetQuality:"B",overallGrade:"C"},bm_lewis:{name:"Lewis",language:"en-gb",gender:"Male",targetQuality:"C",overallGrade:"D+"},bf_alice:{name:"Alice",language:"en-gb",gender:"Female",traits:"🚺",targetQuality:"C",overallGrade:"D"},bf_lily:{name:"Lily",language:"en-gb",gender:"Female",traits:"🚺",targetQuality:"C",overallGrade:"D"},bm_daniel:{name:"Daniel",language:"en-gb",gender:"Male",traits:"🚹",targetQuality:"C",overallGrade:"D"},bm_fable:{name:"Fable",language:"en-gb",gender:"Male",traits:"🚹",targetQuality:"B",overallGrade:"C"}});const G=new Map;async function k(e){if(G.has(e))return G.get(e);const a=new Float32Array(await async function(e){if(i&&Object.hasOwn(i,"readFile")){const a="undefined"!=typeof __dirname?__dirname:import.meta.dirname,t=s.resolve(a,`../voices/${e}.bin`),{buffer:r}=await i.readFile(t);return r}const a=`https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX/resolve/main/voices/${e}.bin`;let t;try{t=await caches.open("kokoro-voices");const e=await t.match(a);if(e)return await e.arrayBuffer()}catch(e){console.warn("Unable to open cache",e)}const r=await fetch(a),n=await r.arrayBuffer();if(t)try{await t.put(a,new Response(n,{headers:r.headers}))}catch(e){console.warn("Unable to cache file",e)}return n}(e));return G.set(e,a),a}class M{constructor(e,a){this.model=e,this.tokenizer=a}static async from_pretrained(t,{dtype:r="fp32",device:n=null,progress_callback:l=null}={}){const s=e.from_pretrained(t,{progress_callback:l,dtype:r,device:n}),i=a.from_pretrained(t,{progress_callback:l}),o=await Promise.all([s,i]);return new M(...o)}get voices(){return $}list_voices(){console.table($)}_validate_voice(e){if(!$.hasOwnProperty(e))throw console.error(`Voice "${e}" not found. Available voices:`),console.table($),new Error(`Voice "${e}" not found. Should be one of: ${Object.keys($).join(", ")}.`);return e.at(0)}async generate(e,{voice:a="af_heart",speed:t=1}={}){const r=this._validate_voice(a),n=await m(e,r),{input_ids:l}=this.tokenizer(n,{truncation:!0});return this.generate_from_ids(l,{voice:a,speed:t})}async generate_from_ids(e,{voice:a="af_heart",speed:n=1}={}){const l=256*Math.min(Math.max(e.dims.at(-1)-2,0),509),s=(await k(a)).slice(l,l+256),i={input_ids:e,style:new t("float32",s,[1,256]),speed:new t("float32",[n],[1])},{waveform:o}=await this.model(i);return new r(o.data,24e3)}async*stream(e,{voice:a="af_heart",speed:t=1,split_pattern:r=null}={}){const n=this._validate_voice(a);let l;if(e instanceof w)l=e;else{if("string"!=typeof e)throw new Error("Invalid input type. Expected string or TextSplitterStream.");{l=new w;const a=r?e.split(r).map((e=>e.trim())).filter((e=>e.length>0)):[e];l.push(...a)}}for await(const e of l){const r=await m(e,n),{input_ids:l}=this.tokenizer(r,{truncation:!0}),s=await this.generate_from_ids(l,{voice:a,speed:t});yield{text:e,phonemes:r,audio:s}}}}const Q={set wasmPaths(e){n.backends.onnx.wasm.wasmPaths=e},get wasmPaths(){return n.backends.onnx.wasm.wasmPaths}};export{M as KokoroTTS,w as TextSplitterStream,Q as env};
vendor/kokoro/phonemizer.js ADDED
The diff for this file is too large to render. See raw diff
 
vendor/kokoro/stub.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ // browser stub for node built-ins kokoro-js imports but doesn't use client-side.
2
+ export const join = (...a) => a.join('/');
3
+ export const resolve = (...a) => a.join('/');
4
+ export const dirname = (p) => String(p).replace(/\/[^/]*$/, '');
5
+ export const readFile = async () => { throw new Error('fs unavailable in browser'); };
6
+ export default {};
vendor/kokoro/transformers/ort-wasm-simd-threaded.jsep.mjs ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var ortWasmThreaded = (() => {
2
+ var _scriptName = import.meta.url;
3
+
4
+ return (
5
+ async function(moduleArg = {}) {
6
+ var moduleRtn;
7
+
8
+ var e=moduleArg,aa,ca,da=new Promise((a,b)=>{aa=a;ca=b}),ea="object"==typeof window,k="undefined"!=typeof WorkerGlobalScope,n="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node&&"renderer"!=process.type,q=k&&self.name?.startsWith("em-pthread");if(n){const {createRequire:a}=await import("module");var require=a(import.meta.url),fa=require("worker_threads");global.Worker=fa.Worker;q=(k=!fa.oc)&&"em-pthread"==fa.workerData}
9
+ e.mountExternalData=(a,b)=>{a.startsWith("./")&&(a=a.substring(2));(e.Eb||(e.Eb=new Map)).set(a,b)};e.unmountExternalData=()=>{delete e.Eb};var SharedArrayBuffer=globalThis.SharedArrayBuffer??(new WebAssembly.Memory({initial:0,maximum:0,pc:!0})).buffer.constructor;
10
+ const ha=a=>async(...b)=>{try{if(e.Fb)throw Error("Session already started");const c=e.Fb={dc:b[0],errors:[]},d=await a(...b);if(e.Fb!==c)throw Error("Session mismatch");e.Jb?.flush();const f=c.errors;if(0<f.length){let g=await Promise.all(f);g=g.filter(h=>h);if(0<g.length)throw Error(g.join("\n"));}return d}finally{e.Fb=null}};
11
+ e.jsepInit=(a,b)=>{if("webgpu"===a){[e.Jb,e.Ub,e.Yb,e.Kb,e.Xb,e.jb,e.Zb,e.ac,e.Vb,e.Wb,e.$b]=b;const c=e.Jb;e.jsepRegisterBuffer=(d,f,g,h)=>c.registerBuffer(d,f,g,h);e.jsepGetBuffer=d=>c.getBuffer(d);e.jsepCreateDownloader=(d,f,g)=>c.createDownloader(d,f,g);e.jsepOnCreateSession=d=>{c.onCreateSession(d)};e.jsepOnReleaseSession=d=>{c.onReleaseSession(d)};e.jsepOnRunStart=d=>c.onRunStart(d);e.bc=(d,f)=>{c.upload(d,f)}}else if("webnn"===a){const c=b[0];[e.nc,e.Nb,e.webnnEnsureTensor,e.Ob,e.webnnDownloadTensor]=
12
+ b.slice(1);e.webnnReleaseTensorId=e.Nb;e.webnnUploadTensor=e.Ob;e.webnnOnRunStart=d=>c.onRunStart(d);e.webnnOnRunEnd=c.onRunEnd.bind(c);e.webnnRegisterMLContext=(d,f)=>{c.registerMLContext(d,f)};e.webnnOnReleaseSession=d=>{c.onReleaseSession(d)};e.webnnCreateMLTensorDownloader=(d,f)=>c.createMLTensorDownloader(d,f);e.webnnRegisterMLTensor=(d,f,g,h)=>c.registerMLTensor(d,f,g,h);e.webnnCreateMLContext=d=>c.createMLContext(d);e.webnnRegisterMLConstant=(d,f,g,h,l,m)=>c.registerMLConstant(d,f,g,h,l,e.Eb,
13
+ m);e.webnnRegisterGraphInput=c.registerGraphInput.bind(c);e.webnnIsGraphInput=c.isGraphInput.bind(c);e.webnnCreateTemporaryTensor=c.createTemporaryTensor.bind(c);e.webnnIsInt64Supported=c.isInt64Supported.bind(c)}};
14
+ let ja=()=>{const a=(b,c,d)=>(...f)=>{const g=t,h=c?.();f=b(...f);const l=c?.();h!==l&&(b=l,d(h),c=d=null);return t!=g?ia():f};(b=>{for(const c of b)e[c]=a(e[c],()=>e[c],d=>e[c]=d)})(["_OrtAppendExecutionProvider","_OrtCreateSession","_OrtRun","_OrtRunWithBinding","_OrtBindInput"]);"undefined"!==typeof ha&&(e._OrtRun=ha(e._OrtRun),e._OrtRunWithBinding=ha(e._OrtRunWithBinding));ja=void 0};e.asyncInit=()=>{ja?.()};var ka=Object.assign({},e),la="./this.program",ma=(a,b)=>{throw b;},v="",na,oa;
15
+ if(n){var fs=require("fs"),pa=require("path");import.meta.url.startsWith("data:")||(v=pa.dirname(require("url").fileURLToPath(import.meta.url))+"/");oa=a=>{a=qa(a)?new URL(a):a;return fs.readFileSync(a)};na=async a=>{a=qa(a)?new URL(a):a;return fs.readFileSync(a,void 0)};!e.thisProgram&&1<process.argv.length&&(la=process.argv[1].replace(/\\/g,"/"));process.argv.slice(2);ma=(a,b)=>{process.exitCode=a;throw b;}}else if(ea||k)k?v=self.location.href:"undefined"!=typeof document&&
16
+ document.currentScript&&(v=document.currentScript.src),_scriptName&&(v=_scriptName),v.startsWith("blob:")?v="":v=v.slice(0,v.replace(/[?#].*/,"").lastIndexOf("/")+1),n||(k&&(oa=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),na=async a=>{if(qa(a))return new Promise((c,d)=>{var f=new XMLHttpRequest;f.open("GET",a,!0);f.responseType="arraybuffer";f.onload=()=>{200==f.status||0==f.status&&f.response?c(f.response):d(f.status)};
17
+ f.onerror=d;f.send(null)});var b=await fetch(a,{credentials:"same-origin"});if(b.ok)return b.arrayBuffer();throw Error(b.status+" : "+b.url);});var ra=console.log.bind(console),sa=console.error.bind(console);n&&(ra=(...a)=>fs.writeSync(1,a.join(" ")+"\n"),sa=(...a)=>fs.writeSync(2,a.join(" ")+"\n"));var ta=ra,x=sa;Object.assign(e,ka);ka=null;var ua=e.wasmBinary,z,va,A=!1,wa,B,xa,ya,za,Aa,Ba,Ca,C,Da,Ea,qa=a=>a.startsWith("file://");function D(){z.buffer!=B.buffer&&E();return B}
18
+ function F(){z.buffer!=B.buffer&&E();return xa}function G(){z.buffer!=B.buffer&&E();return ya}function Fa(){z.buffer!=B.buffer&&E();return za}function H(){z.buffer!=B.buffer&&E();return Aa}function I(){z.buffer!=B.buffer&&E();return Ba}function Ga(){z.buffer!=B.buffer&&E();return Ca}function J(){z.buffer!=B.buffer&&E();return Ea}
19
+ if(q){var Ha;if(n){var Ia=fa.parentPort;Ia.on("message",b=>onmessage({data:b}));Object.assign(globalThis,{self:global,postMessage:b=>Ia.postMessage(b)})}var Ja=!1;x=function(...b){b=b.join(" ");n?fs.writeSync(2,b+"\n"):console.error(b)};self.alert=function(...b){postMessage({Bb:"alert",text:b.join(" "),ic:Ka()})};self.onunhandledrejection=b=>{throw b.reason||b;};function a(b){try{var c=b.data,d=c.Bb;if("load"===d){let f=[];self.onmessage=g=>f.push(g);self.startWorker=()=>{postMessage({Bb:"loaded"});
20
+ for(let g of f)a(g);self.onmessage=a};for(const g of c.Rb)if(!e[g]||e[g].proxy)e[g]=(...h)=>{postMessage({Bb:"callHandler",Qb:g,args:h})},"print"==g&&(ta=e[g]),"printErr"==g&&(x=e[g]);z=c.kc;E();Ha(c.lc)}else if("run"===d){La(c.Ab);Ma(c.Ab,0,0,1,0,0);Na();Oa(c.Ab);Ja||(Pa(),Ja=!0);try{Qa(c.fc,c.Hb)}catch(f){if("unwind"!=f)throw f;}}else"setimmediate"!==c.target&&("checkMailbox"===d?Ja&&Ra():d&&(x(`worker: received unknown command ${d}`),x(c)))}catch(f){throw Sa(),f;}}self.onmessage=a}
21
+ function E(){var a=z.buffer;e.HEAP8=B=new Int8Array(a);e.HEAP16=ya=new Int16Array(a);e.HEAPU8=xa=new Uint8Array(a);e.HEAPU16=za=new Uint16Array(a);e.HEAP32=Aa=new Int32Array(a);e.HEAPU32=Ba=new Uint32Array(a);e.HEAPF32=Ca=new Float32Array(a);e.HEAPF64=Ea=new Float64Array(a);e.HEAP64=C=new BigInt64Array(a);e.HEAPU64=Da=new BigUint64Array(a)}q||(z=new WebAssembly.Memory({initial:256,maximum:65536,shared:!0}),E());function Ta(){q?startWorker(e):K.Ca()}var Ua=0,Va=null;
22
+ function Wa(){Ua--;if(0==Ua&&Va){var a=Va;Va=null;a()}}function L(a){a="Aborted("+a+")";x(a);A=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");ca(a);throw a;}var Xa;async function Ya(a){if(!ua)try{var b=await na(a);return new Uint8Array(b)}catch{}if(a==Xa&&ua)a=new Uint8Array(ua);else if(oa)a=oa(a);else throw"both async and sync fetching of the wasm failed";return a}
23
+ async function Za(a,b){try{var c=await Ya(a);return await WebAssembly.instantiate(c,b)}catch(d){x(`failed to asynchronously prepare wasm: ${d}`),L(d)}}async function $a(a){var b=Xa;if(!ua&&"function"==typeof WebAssembly.instantiateStreaming&&!qa(b)&&!n)try{var c=fetch(b,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(c,a)}catch(d){x(`wasm streaming compile failed: ${d}`),x("falling back to ArrayBuffer instantiation")}return Za(b,a)}
24
+ function ab(){bb={L:cb,Aa:db,b:eb,$:fb,A:gb,pa:hb,X:ib,Z:jb,qa:kb,na:lb,ga:mb,ma:nb,J:ob,Y:pb,V:qb,oa:rb,W:sb,va:tb,E:ub,Q:vb,O:wb,D:xb,u:yb,r:zb,P:Ab,z:Bb,R:Cb,ja:Db,T:Eb,aa:Fb,M:Gb,F:Hb,ia:Oa,sa:Ib,t:Jb,Ba:Kb,w:Lb,o:Mb,l:Nb,c:Ob,n:Pb,j:Qb,v:Rb,p:Sb,f:Tb,s:Ub,m:Vb,e:Wb,k:Xb,i:Yb,g:Zb,d:$b,da:ac,ea:bc,fa:cc,ba:dc,ca:ec,N:fc,xa:gc,ua:hc,h:ic,C:jc,G:kc,ta:lc,x:mc,ra:nc,U:oc,q:pc,y:qc,K:rc,S:sc,za:tc,ya:uc,ka:vc,la:wc,_:xc,B:yc,I:zc,ha:Ac,H:Bc,a:z,wa:Cc};return{a:bb}}
25
+ var Dc={829644:(a,b,c,d,f)=>{if("undefined"==typeof e||!e.Eb)return 1;a=M(Number(a>>>0));a.startsWith("./")&&(a=a.substring(2));a=e.Eb.get(a);if(!a)return 2;b=Number(b>>>0);c=Number(c>>>0);d=Number(d>>>0);if(b+c>a.byteLength)return 3;try{const g=a.subarray(b,b+c);switch(f){case 0:F().set(g,d>>>0);break;case 1:e.mc?e.mc(d,g):e.bc(d,g);break;default:return 4}return 0}catch{return 4}},830468:(a,b,c)=>{e.Ob(a,F().subarray(b>>>0,b+c>>>0))},830532:()=>e.nc(),830574:a=>{e.Nb(a)},830611:()=>{e.Vb()},830642:()=>
26
+ {e.Wb()},830671:()=>{e.$b()},830696:a=>e.Ub(a),830729:a=>e.Yb(a),830761:(a,b,c)=>{e.Kb(Number(a),Number(b),Number(c),!0)},830824:(a,b,c)=>{e.Kb(Number(a),Number(b),Number(c))},830881:()=>"undefined"!==typeof wasmOffsetConverter,830938:a=>{e.jb("Abs",a,void 0)},830989:a=>{e.jb("Neg",a,void 0)},831040:a=>{e.jb("Floor",a,void 0)},831093:a=>{e.jb("Ceil",a,void 0)},831145:a=>{e.jb("Reciprocal",a,void 0)},831203:a=>{e.jb("Sqrt",a,void 0)},831255:a=>{e.jb("Exp",a,void 0)},831306:a=>{e.jb("Erf",a,void 0)},
27
+ 831357:a=>{e.jb("Sigmoid",a,void 0)},831412:(a,b,c)=>{e.jb("HardSigmoid",a,{alpha:b,beta:c})},831491:a=>{e.jb("Log",a,void 0)},831542:a=>{e.jb("Sin",a,void 0)},831593:a=>{e.jb("Cos",a,void 0)},831644:a=>{e.jb("Tan",a,void 0)},831695:a=>{e.jb("Asin",a,void 0)},831747:a=>{e.jb("Acos",a,void 0)},831799:a=>{e.jb("Atan",a,void 0)},831851:a=>{e.jb("Sinh",a,void 0)},831903:a=>{e.jb("Cosh",a,void 0)},831955:a=>{e.jb("Asinh",a,void 0)},832008:a=>{e.jb("Acosh",a,void 0)},832061:a=>{e.jb("Atanh",a,void 0)},
28
+ 832114:a=>{e.jb("Tanh",a,void 0)},832166:a=>{e.jb("Not",a,void 0)},832217:(a,b,c)=>{e.jb("Clip",a,{min:b,max:c})},832286:a=>{e.jb("Clip",a,void 0)},832338:(a,b)=>{e.jb("Elu",a,{alpha:b})},832396:a=>{e.jb("Gelu",a,void 0)},832448:a=>{e.jb("Relu",a,void 0)},832500:(a,b)=>{e.jb("LeakyRelu",a,{alpha:b})},832564:(a,b)=>{e.jb("ThresholdedRelu",a,{alpha:b})},832634:(a,b)=>{e.jb("Cast",a,{to:b})},832692:a=>{e.jb("Add",a,void 0)},832743:a=>{e.jb("Sub",a,void 0)},832794:a=>{e.jb("Mul",a,void 0)},832845:a=>
29
+ {e.jb("Div",a,void 0)},832896:a=>{e.jb("Pow",a,void 0)},832947:a=>{e.jb("Equal",a,void 0)},833E3:a=>{e.jb("Greater",a,void 0)},833055:a=>{e.jb("GreaterOrEqual",a,void 0)},833117:a=>{e.jb("Less",a,void 0)},833169:a=>{e.jb("LessOrEqual",a,void 0)},833228:(a,b,c,d,f)=>{e.jb("ReduceMean",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},833403:(a,b,c,d,f)=>{e.jb("ReduceMax",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>
30
+ 0,Number(f)>>>0)):[]})},833577:(a,b,c,d,f)=>{e.jb("ReduceMin",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},833751:(a,b,c,d,f)=>{e.jb("ReduceProd",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},833926:(a,b,c,d,f)=>{e.jb("ReduceSum",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834100:(a,b,c,d,f)=>{e.jb("ReduceL1",a,{keepDims:!!b,
31
+ noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834273:(a,b,c,d,f)=>{e.jb("ReduceL2",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834446:(a,b,c,d,f)=>{e.jb("ReduceLogSum",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834623:(a,b,c,d,f)=>{e.jb("ReduceSumSquare",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>
32
+ 0,Number(f)>>>0)):[]})},834803:(a,b,c,d,f)=>{e.jb("ReduceLogSumExp",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834983:a=>{e.jb("Where",a,void 0)},835036:(a,b,c)=>{e.jb("Transpose",a,{perm:b?Array.from(H().subarray(Number(b)>>>0,Number(c)>>>0)):[]})},835160:(a,b,c,d)=>{e.jb("DepthToSpace",a,{blocksize:b,mode:M(c),format:d?"NHWC":"NCHW"})},835293:(a,b,c,d)=>{e.jb("DepthToSpace",a,{blocksize:b,mode:M(c),format:d?"NHWC":"NCHW"})},835426:(a,
33
+ b,c,d,f,g,h,l,m,p,r,u,w,y,ba)=>{e.jb("ConvTranspose",a,{format:m?"NHWC":"NCHW",autoPad:b,dilations:[c],group:d,kernelShape:[f],pads:[g,h],strides:[l],wIsConst:()=>!!D()[p>>>0],outputPadding:r?Array.from(H().subarray(Number(r)>>>0,Number(u)>>>0)):[],outputShape:w?Array.from(H().subarray(Number(w)>>>0,Number(y)>>>0)):[],activation:M(ba)})},835859:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("ConvTranspose",a,{format:l?"NHWC":"NCHW",autoPad:b,dilations:Array.from(H().subarray(Number(c)>>>0,(Number(c)>>>0)+2>>>
34
+ 0)),group:d,kernelShape:Array.from(H().subarray(Number(f)>>>0,(Number(f)>>>0)+2>>>0)),pads:Array.from(H().subarray(Number(g)>>>0,(Number(g)>>>0)+4>>>0)),strides:Array.from(H().subarray(Number(h)>>>0,(Number(h)>>>0)+2>>>0)),wIsConst:()=>!!D()[m>>>0],outputPadding:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],outputShape:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[],activation:M(y)})},836520:(a,b,c,d,f,g,h,l,m,p,r,u,w,y,ba)=>{e.jb("ConvTranspose",a,{format:m?"NHWC":"NCHW",
35
+ autoPad:b,dilations:[c],group:d,kernelShape:[f],pads:[g,h],strides:[l],wIsConst:()=>!!D()[p>>>0],outputPadding:r?Array.from(H().subarray(Number(r)>>>0,Number(u)>>>0)):[],outputShape:w?Array.from(H().subarray(Number(w)>>>0,Number(y)>>>0)):[],activation:M(ba)})},836953:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("ConvTranspose",a,{format:l?"NHWC":"NCHW",autoPad:b,dilations:Array.from(H().subarray(Number(c)>>>0,(Number(c)>>>0)+2>>>0)),group:d,kernelShape:Array.from(H().subarray(Number(f)>>>0,(Number(f)>>>0)+
36
+ 2>>>0)),pads:Array.from(H().subarray(Number(g)>>>0,(Number(g)>>>0)+4>>>0)),strides:Array.from(H().subarray(Number(h)>>>0,(Number(h)>>>0)+2>>>0)),wIsConst:()=>!!D()[m>>>0],outputPadding:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],outputShape:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[],activation:M(y)})},837614:(a,b)=>{e.jb("GlobalAveragePool",a,{format:b?"NHWC":"NCHW"})},837705:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("AveragePool",a,{format:y?"NHWC":"NCHW",auto_pad:b,ceil_mode:c,
37
+ count_include_pad:d,storage_order:f,dilations:g?Array.from(H().subarray(Number(g)>>>0,Number(h)>>>0)):[],kernel_shape:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],pads:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],strides:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[]})},838184:(a,b)=>{e.jb("GlobalAveragePool",a,{format:b?"NHWC":"NCHW"})},838275:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("AveragePool",a,{format:y?"NHWC":"NCHW",auto_pad:b,ceil_mode:c,count_include_pad:d,
38
+ storage_order:f,dilations:g?Array.from(H().subarray(Number(g)>>>0,Number(h)>>>0)):[],kernel_shape:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],pads:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],strides:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[]})},838754:(a,b)=>{e.jb("GlobalMaxPool",a,{format:b?"NHWC":"NCHW"})},838841:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("MaxPool",a,{format:y?"NHWC":"NCHW",auto_pad:b,ceil_mode:c,count_include_pad:d,storage_order:f,dilations:g?
39
+ Array.from(H().subarray(Number(g)>>>0,Number(h)>>>0)):[],kernel_shape:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],pads:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],strides:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[]})},839316:(a,b)=>{e.jb("GlobalMaxPool",a,{format:b?"NHWC":"NCHW"})},839403:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("MaxPool",a,{format:y?"NHWC":"NCHW",auto_pad:b,ceil_mode:c,count_include_pad:d,storage_order:f,dilations:g?Array.from(H().subarray(Number(g)>>>
40
+ 0,Number(h)>>>0)):[],kernel_shape:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],pads:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],strides:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[]})},839878:(a,b,c,d,f)=>{e.jb("Gemm",a,{alpha:b,beta:c,transA:d,transB:f})},839982:a=>{e.jb("MatMul",a,void 0)},840036:(a,b,c,d)=>{e.jb("ArgMax",a,{keepDims:!!b,selectLastIndex:!!c,axis:d})},840144:(a,b,c,d)=>{e.jb("ArgMin",a,{keepDims:!!b,selectLastIndex:!!c,axis:d})},840252:(a,
41
+ b)=>{e.jb("Softmax",a,{axis:b})},840315:(a,b)=>{e.jb("Concat",a,{axis:b})},840375:(a,b,c,d,f)=>{e.jb("Split",a,{axis:b,numOutputs:c,splitSizes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},840531:a=>{e.jb("Expand",a,void 0)},840585:(a,b)=>{e.jb("Gather",a,{axis:Number(b)})},840656:(a,b)=>{e.jb("GatherElements",a,{axis:Number(b)})},840735:(a,b)=>{e.jb("GatherND",a,{batch_dims:Number(b)})},840814:(a,b,c,d,f,g,h,l,m,p,r)=>{e.jb("Resize",a,{antialias:b,axes:c?Array.from(H().subarray(Number(c)>>>
42
+ 0,Number(d)>>>0)):[],coordinateTransformMode:M(f),cubicCoeffA:g,excludeOutside:h,extrapolationValue:l,keepAspectRatioPolicy:M(m),mode:M(p),nearestMode:M(r)})},841176:(a,b,c,d,f,g,h)=>{e.jb("Slice",a,{starts:b?Array.from(H().subarray(Number(b)>>>0,Number(c)>>>0)):[],ends:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[],axes:g?Array.from(H().subarray(Number(g)>>>0,Number(h)>>>0)):[]})},841440:a=>{e.jb("Tile",a,void 0)},841492:(a,b,c)=>{e.jb("InstanceNormalization",a,{epsilon:b,format:c?"NHWC":
43
+ "NCHW"})},841606:(a,b,c)=>{e.jb("InstanceNormalization",a,{epsilon:b,format:c?"NHWC":"NCHW"})},841720:a=>{e.jb("Range",a,void 0)},841773:(a,b)=>{e.jb("Einsum",a,{equation:M(b)})},841854:(a,b,c,d,f)=>{e.jb("Pad",a,{mode:b,value:c,pads:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},841997:(a,b,c,d,f,g)=>{e.jb("BatchNormalization",a,{epsilon:b,momentum:c,spatial:!!f,trainingMode:!!d,format:g?"NHWC":"NCHW"})},842166:(a,b,c,d,f,g)=>{e.jb("BatchNormalization",a,{epsilon:b,momentum:c,spatial:!!f,
44
+ trainingMode:!!d,format:g?"NHWC":"NCHW"})},842335:(a,b,c)=>{e.jb("CumSum",a,{exclusive:Number(b),reverse:Number(c)})},842432:(a,b,c)=>{e.jb("DequantizeLinear",a,{axis:b,blockSize:c})},842522:(a,b,c,d,f)=>{e.jb("GridSample",a,{align_corners:b,mode:M(c),padding_mode:M(d),format:f?"NHWC":"NCHW"})},842692:(a,b,c,d,f)=>{e.jb("GridSample",a,{align_corners:b,mode:M(c),padding_mode:M(d),format:f?"NHWC":"NCHW"})},842862:(a,b)=>{e.jb("ScatterND",a,{reduction:M(b)})},842947:(a,b,c,d,f,g,h,l,m)=>{e.jb("Attention",
45
+ a,{numHeads:b,isUnidirectional:c,maskFilterValue:d,scale:f,doRotary:g,qkvHiddenSizes:h?Array.from(H().subarray(Number(l)>>>0,Number(l)+h>>>0)):[],pastPresentShareBuffer:!!m})},843219:a=>{e.jb("BiasAdd",a,void 0)},843274:a=>{e.jb("BiasSplitGelu",a,void 0)},843335:a=>{e.jb("FastGelu",a,void 0)},843391:(a,b,c,d,f,g,h,l,m,p,r,u,w,y,ba,Vd)=>{e.jb("Conv",a,{format:u?"NHWC":"NCHW",auto_pad:b,dilations:c?Array.from(H().subarray(Number(c)>>>0,Number(d)>>>0)):[],group:f,kernel_shape:g?Array.from(H().subarray(Number(g)>>>
46
+ 0,Number(h)>>>0)):[],pads:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],strides:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],w_is_const:()=>!!D()[Number(w)>>>0],activation:M(y),activation_params:ba?Array.from(Ga().subarray(Number(ba)>>>0,Number(Vd)>>>0)):[]})},843975:a=>{e.jb("Gelu",a,void 0)},844027:(a,b,c,d,f,g,h,l,m)=>{e.jb("GroupQueryAttention",a,{numHeads:b,kvNumHeads:c,scale:d,softcap:f,doRotary:g,rotaryInterleaved:h,smoothSoftmax:l,localWindowSize:m})},844244:(a,
47
+ b,c,d)=>{e.jb("LayerNormalization",a,{axis:b,epsilon:c,simplified:!!d})},844355:(a,b,c,d)=>{e.jb("LayerNormalization",a,{axis:b,epsilon:c,simplified:!!d})},844466:(a,b,c,d,f,g)=>{e.jb("MatMulNBits",a,{k:b,n:c,accuracyLevel:d,bits:f,blockSize:g})},844593:(a,b,c,d,f,g)=>{e.jb("MultiHeadAttention",a,{numHeads:b,isUnidirectional:c,maskFilterValue:d,scale:f,doRotary:g})},844752:(a,b)=>{e.jb("QuickGelu",a,{alpha:b})},844816:(a,b,c,d,f)=>{e.jb("RotaryEmbedding",a,{interleaved:!!b,numHeads:c,rotaryEmbeddingDim:d,
48
+ scale:f})},844955:(a,b,c)=>{e.jb("SkipLayerNormalization",a,{epsilon:b,simplified:!!c})},845057:(a,b,c)=>{e.jb("SkipLayerNormalization",a,{epsilon:b,simplified:!!c})},845159:(a,b,c,d)=>{e.jb("GatherBlockQuantized",a,{gatherAxis:b,quantizeAxis:c,blockSize:d})},845280:a=>{e.Zb(a)},845314:(a,b)=>e.ac(Number(a),Number(b),e.Fb.dc,e.Fb.errors)};function db(a,b,c){return Ec(async()=>{await e.Xb(Number(a),Number(b),Number(c))})}function cb(){return"undefined"!==typeof wasmOffsetConverter}
49
+ class Fc{name="ExitStatus";constructor(a){this.message=`Program terminated with exit(${a})`;this.status=a}}
50
+ var Gc=a=>{a.terminate();a.onmessage=()=>{}},Hc=[],Lc=a=>{0==N.length&&(Ic(),Jc(N[0]));var b=N.pop();if(!b)return 6;Kc.push(b);O[a.Ab]=b;b.Ab=a.Ab;var c={Bb:"run",fc:a.ec,Hb:a.Hb,Ab:a.Ab};n&&b.unref();b.postMessage(c,a.Mb);return 0},P=0,Q=(a,b,...c)=>{for(var d=2*c.length,f=Mc(),g=Nc(8*d),h=g>>>3,l=0;l<c.length;l++){var m=c[l];"bigint"==typeof m?(C[h+2*l]=1n,C[h+2*l+1]=m):(C[h+2*l]=0n,J()[h+2*l+1>>>0]=m)}a=Oc(a,0,d,g,b);Pc(f);return a};
51
+ function Cc(a){if(q)return Q(0,1,a);wa=a;if(!(0<P)){for(var b of Kc)Gc(b);for(b of N)Gc(b);N=[];Kc=[];O={};A=!0}ma(a,new Fc(a))}function Qc(a){if(q)return Q(1,0,a);xc(a)}var xc=a=>{wa=a;if(q)throw Qc(a),"unwind";Cc(a)},N=[],Kc=[],Rc=[],O={};function Sc(){for(var a=e.numThreads-1;a--;)Ic();Hc.unshift(()=>{Ua++;Tc(()=>Wa())})}var Vc=a=>{var b=a.Ab;delete O[b];N.push(a);Kc.splice(Kc.indexOf(a),1);a.Ab=0;Uc(b)};function Na(){Rc.forEach(a=>a())}
52
+ var Jc=a=>new Promise(b=>{a.onmessage=g=>{g=g.data;var h=g.Bb;if(g.Gb&&g.Gb!=Ka()){var l=O[g.Gb];l?l.postMessage(g,g.Mb):x(`Internal error! Worker sent a message "${h}" to target pthread ${g.Gb}, but that thread no longer exists!`)}else if("checkMailbox"===h)Ra();else if("spawnThread"===h)Lc(g);else if("cleanupThread"===h)Vc(O[g.hc]);else if("loaded"===h)a.loaded=!0,n&&!a.Ab&&a.unref(),b(a);else if("alert"===h)alert(`Thread ${g.ic}: ${g.text}`);else if("setimmediate"===g.target)a.postMessage(g);else if("callHandler"===
53
+ h)e[g.Qb](...g.args);else h&&x(`worker sent an unknown command ${h}`)};a.onerror=g=>{x(`${"worker sent an error!"} ${g.filename}:${g.lineno}: ${g.message}`);throw g;};n&&(a.on("message",g=>a.onmessage({data:g})),a.on("error",g=>a.onerror(g)));var c=[],d=[],f;for(f of d)e.propertyIsEnumerable(f)&&c.push(f);a.postMessage({Bb:"load",Rb:c,kc:z,lc:va})});function Tc(a){q?a():Promise.all(N.map(Jc)).then(a)}
54
+ function Ic(){var a=new Worker(new URL(import.meta.url),{type:"module",workerData:"em-pthread",name:"em-pthread"});N.push(a)}var La=a=>{E();var b=I()[a+52>>>2>>>0];a=I()[a+56>>>2>>>0];Wc(b,b-a);Pc(b)},Qa=(a,b)=>{P=0;a=Xc(a,b);0<P?wa=a:Yc(a)};class Zc{constructor(a){this.Ib=a-24}}var $c=0,ad=0;function eb(a,b,c){a>>>=0;var d=new Zc(a);b>>>=0;c>>>=0;I()[d.Ib+16>>>2>>>0]=0;I()[d.Ib+4>>>2>>>0]=b;I()[d.Ib+8>>>2>>>0]=c;$c=a;ad++;throw $c;}
55
+ function bd(a,b,c,d){return q?Q(2,1,a,b,c,d):fb(a,b,c,d)}function fb(a,b,c,d){a>>>=0;b>>>=0;c>>>=0;d>>>=0;if("undefined"==typeof SharedArrayBuffer)return 6;var f=[];if(q&&0===f.length)return bd(a,b,c,d);a={ec:c,Ab:a,Hb:d,Mb:f};return q?(a.Bb="spawnThread",postMessage(a,f),0):Lc(a)}
56
+ var cd="undefined"!=typeof TextDecoder?new TextDecoder:void 0,dd=(a,b=0,c=NaN)=>{b>>>=0;var d=b+c;for(c=b;a[c]&&!(c>=d);)++c;if(16<c-b&&a.buffer&&cd)return cd.decode(a.buffer instanceof ArrayBuffer?a.subarray(b,c):a.slice(b,c));for(d="";b<c;){var f=a[b++];if(f&128){var g=a[b++]&63;if(192==(f&224))d+=String.fromCharCode((f&31)<<6|g);else{var h=a[b++]&63;f=224==(f&240)?(f&15)<<12|g<<6|h:(f&7)<<18|g<<12|h<<6|a[b++]&63;65536>f?d+=String.fromCharCode(f):(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|
57
+ f&1023))}}else d+=String.fromCharCode(f)}return d},M=(a,b)=>(a>>>=0)?dd(F(),a,b):"";function gb(a,b,c){return q?Q(3,1,a,b,c):0}function hb(a,b){if(q)return Q(4,1,a,b)}
58
+ var ed=a=>{for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);127>=d?b++:2047>=d?b+=2:55296<=d&&57343>=d?(b+=4,++c):b+=3}return b},fd=(a,b,c)=>{var d=F();b>>>=0;if(0<c){var f=b;c=b+c-1;for(var g=0;g<a.length;++g){var h=a.charCodeAt(g);if(55296<=h&&57343>=h){var l=a.charCodeAt(++g);h=65536+((h&1023)<<10)|l&1023}if(127>=h){if(b>=c)break;d[b++>>>0]=h}else{if(2047>=h){if(b+1>=c)break;d[b++>>>0]=192|h>>6}else{if(65535>=h){if(b+2>=c)break;d[b++>>>0]=224|h>>12}else{if(b+3>=c)break;d[b++>>>0]=240|h>>18;
59
+ d[b++>>>0]=128|h>>12&63}d[b++>>>0]=128|h>>6&63}d[b++>>>0]=128|h&63}}d[b>>>0]=0;a=b-f}else a=0;return a};function ib(a,b){if(q)return Q(5,1,a,b)}function jb(a,b,c){if(q)return Q(6,1,a,b,c)}function kb(a,b,c){return q?Q(7,1,a,b,c):0}function lb(a,b){if(q)return Q(8,1,a,b)}function mb(a,b,c){if(q)return Q(9,1,a,b,c)}function nb(a,b,c,d){if(q)return Q(10,1,a,b,c,d)}function ob(a,b,c,d){if(q)return Q(11,1,a,b,c,d)}function pb(a,b,c,d){if(q)return Q(12,1,a,b,c,d)}function qb(a){if(q)return Q(13,1,a)}
60
+ function rb(a,b){if(q)return Q(14,1,a,b)}function sb(a,b,c){if(q)return Q(15,1,a,b,c)}var tb=()=>L(""),gd,R=a=>{for(var b="";F()[a>>>0];)b+=gd[F()[a++>>>0]];return b},hd={},jd={},kd={},S;function ld(a,b,c={}){var d=b.name;if(!a)throw new S(`type "${d}" must have a positive integer typeid pointer`);if(jd.hasOwnProperty(a)){if(c.Sb)return;throw new S(`Cannot register type '${d}' twice`);}jd[a]=b;delete kd[a];hd.hasOwnProperty(a)&&(b=hd[a],delete hd[a],b.forEach(f=>f()))}
61
+ function T(a,b,c={}){return ld(a,b,c)}var md=(a,b,c)=>{switch(b){case 1:return c?d=>D()[d>>>0]:d=>F()[d>>>0];case 2:return c?d=>G()[d>>>1>>>0]:d=>Fa()[d>>>1>>>0];case 4:return c?d=>H()[d>>>2>>>0]:d=>I()[d>>>2>>>0];case 8:return c?d=>C[d>>>3]:d=>Da[d>>>3];default:throw new TypeError(`invalid integer width (${b}): ${a}`);}};
62
+ function ub(a,b,c){a>>>=0;c>>>=0;b=R(b>>>0);T(a,{name:b,fromWireType:d=>d,toWireType:function(d,f){if("bigint"!=typeof f&&"number"!=typeof f)throw null===f?f="null":(d=typeof f,f="object"===d||"array"===d||"function"===d?f.toString():""+f),new TypeError(`Cannot convert "${f}" to ${this.name}`);"number"==typeof f&&(f=BigInt(f));return f},Cb:U,readValueFromPointer:md(b,c,-1==b.indexOf("u")),Db:null})}var U=8;
63
+ function vb(a,b,c,d){a>>>=0;b=R(b>>>0);T(a,{name:b,fromWireType:function(f){return!!f},toWireType:function(f,g){return g?c:d},Cb:U,readValueFromPointer:function(f){return this.fromWireType(F()[f>>>0])},Db:null})}var nd=[],V=[];function Ob(a){a>>>=0;9<a&&0===--V[a+1]&&(V[a]=void 0,nd.push(a))}
64
+ var W=a=>{if(!a)throw new S("Cannot use deleted val. handle = "+a);return V[a]},X=a=>{switch(a){case void 0:return 2;case null:return 4;case !0:return 6;case !1:return 8;default:const b=nd.pop()||V.length;V[b]=a;V[b+1]=1;return b}};function od(a){return this.fromWireType(I()[a>>>2>>>0])}var pd={name:"emscripten::val",fromWireType:a=>{var b=W(a);Ob(a);return b},toWireType:(a,b)=>X(b),Cb:U,readValueFromPointer:od,Db:null};function wb(a){return T(a>>>0,pd)}
65
+ var qd=(a,b)=>{switch(b){case 4:return function(c){return this.fromWireType(Ga()[c>>>2>>>0])};case 8:return function(c){return this.fromWireType(J()[c>>>3>>>0])};default:throw new TypeError(`invalid float width (${b}): ${a}`);}};function xb(a,b,c){a>>>=0;c>>>=0;b=R(b>>>0);T(a,{name:b,fromWireType:d=>d,toWireType:(d,f)=>f,Cb:U,readValueFromPointer:qd(b,c),Db:null})}
66
+ function yb(a,b,c,d,f){a>>>=0;c>>>=0;b=R(b>>>0);-1===f&&(f=4294967295);f=l=>l;if(0===d){var g=32-8*c;f=l=>l<<g>>>g}var h=b.includes("unsigned")?function(l,m){return m>>>0}:function(l,m){return m};T(a,{name:b,fromWireType:f,toWireType:h,Cb:U,readValueFromPointer:md(b,c,0!==d),Db:null})}
67
+ function zb(a,b,c){function d(g){var h=I()[g>>>2>>>0];g=I()[g+4>>>2>>>0];return new f(D().buffer,g,h)}a>>>=0;var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array][b];c=R(c>>>0);T(a,{name:c,fromWireType:d,Cb:U,readValueFromPointer:d},{Sb:!0})}
68
+ function Ab(a,b){a>>>=0;b=R(b>>>0);T(a,{name:b,fromWireType:function(c){for(var d=I()[c>>>2>>>0],f=c+4,g,h=f,l=0;l<=d;++l){var m=f+l;if(l==d||0==F()[m>>>0])h=M(h,m-h),void 0===g?g=h:(g+=String.fromCharCode(0),g+=h),h=m+1}Y(c);return g},toWireType:function(c,d){d instanceof ArrayBuffer&&(d=new Uint8Array(d));var f="string"==typeof d;if(!(f||d instanceof Uint8Array||d instanceof Uint8ClampedArray||d instanceof Int8Array))throw new S("Cannot pass non-string to std::string");var g=f?ed(d):d.length;var h=
69
+ rd(4+g+1),l=h+4;I()[h>>>2>>>0]=g;if(f)fd(d,l,g+1);else if(f)for(f=0;f<g;++f){var m=d.charCodeAt(f);if(255<m)throw Y(h),new S("String has UTF-16 code units that do not fit in 8 bits");F()[l+f>>>0]=m}else for(f=0;f<g;++f)F()[l+f>>>0]=d[f];null!==c&&c.push(Y,h);return h},Cb:U,readValueFromPointer:od,Db(c){Y(c)}})}
70
+ var sd="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0,td=(a,b)=>{var c=a>>1;for(var d=c+b/2;!(c>=d)&&Fa()[c>>>0];)++c;c<<=1;if(32<c-a&&sd)return sd.decode(F().slice(a,c));c="";for(d=0;!(d>=b/2);++d){var f=G()[a+2*d>>>1>>>0];if(0==f)break;c+=String.fromCharCode(f)}return c},ud=(a,b,c)=>{c??=2147483647;if(2>c)return 0;c-=2;var d=b;c=c<2*a.length?c/2:a.length;for(var f=0;f<c;++f){var g=a.charCodeAt(f);G()[b>>>1>>>0]=g;b+=2}G()[b>>>1>>>0]=0;return b-d},vd=a=>2*a.length,wd=(a,b)=>{for(var c=
71
+ 0,d="";!(c>=b/4);){var f=H()[a+4*c>>>2>>>0];if(0==f)break;++c;65536<=f?(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023)):d+=String.fromCharCode(f)}return d},xd=(a,b,c)=>{b>>>=0;c??=2147483647;if(4>c)return 0;var d=b;c=d+c-4;for(var f=0;f<a.length;++f){var g=a.charCodeAt(f);if(55296<=g&&57343>=g){var h=a.charCodeAt(++f);g=65536+((g&1023)<<10)|h&1023}H()[b>>>2>>>0]=g;b+=4;if(b+4>c)break}H()[b>>>2>>>0]=0;return b-d},yd=a=>{for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=
72
+ d&&++c;b+=4}return b};
73
+ function Bb(a,b,c){a>>>=0;b>>>=0;c>>>=0;c=R(c);if(2===b){var d=td;var f=ud;var g=vd;var h=l=>Fa()[l>>>1>>>0]}else 4===b&&(d=wd,f=xd,g=yd,h=l=>I()[l>>>2>>>0]);T(a,{name:c,fromWireType:l=>{for(var m=I()[l>>>2>>>0],p,r=l+4,u=0;u<=m;++u){var w=l+4+u*b;if(u==m||0==h(w))r=d(r,w-r),void 0===p?p=r:(p+=String.fromCharCode(0),p+=r),r=w+b}Y(l);return p},toWireType:(l,m)=>{if("string"!=typeof m)throw new S(`Cannot pass non-string to C++ string type ${c}`);var p=g(m),r=rd(4+p+b);I()[r>>>2>>>0]=p/b;f(m,r+4,p+b);
74
+ null!==l&&l.push(Y,r);return r},Cb:U,readValueFromPointer:od,Db(l){Y(l)}})}function Cb(a,b){a>>>=0;b=R(b>>>0);T(a,{Tb:!0,name:b,Cb:0,fromWireType:()=>{},toWireType:()=>{}})}function Db(a){Ma(a>>>0,!k,1,!ea,131072,!1);Na()}var zd=a=>{if(!A)try{if(a(),!(0<P))try{q?Yc(wa):xc(wa)}catch(b){b instanceof Fc||"unwind"==b||ma(1,b)}}catch(b){b instanceof Fc||"unwind"==b||ma(1,b)}};
75
+ function Oa(a){a>>>=0;"function"===typeof Atomics.jc&&(Atomics.jc(H(),a>>>2,a).value.then(Ra),a+=128,Atomics.store(H(),a>>>2,1))}var Ra=()=>{var a=Ka();a&&(Oa(a),zd(Ad))};function Eb(a,b){a>>>=0;a==b>>>0?setTimeout(Ra):q?postMessage({Gb:a,Bb:"checkMailbox"}):(a=O[a])&&a.postMessage({Bb:"checkMailbox"})}var Bd=[];function Fb(a,b,c,d,f){b>>>=0;d/=2;Bd.length=d;c=f>>>0>>>3;for(f=0;f<d;f++)Bd[f]=C[c+2*f]?C[c+2*f+1]:J()[c+2*f+1>>>0];return(b?Dc[b]:Cd[a])(...Bd)}var Gb=()=>{P=0};
76
+ function Hb(a){a>>>=0;q?postMessage({Bb:"cleanupThread",hc:a}):Vc(O[a])}function Ib(a){n&&O[a>>>0].ref()}var Ed=(a,b)=>{var c=jd[a];if(void 0===c)throw a=Dd(a),c=R(a),Y(a),new S(`${b} has unknown type ${c}`);return c},Fd=(a,b,c)=>{var d=[];a=a.toWireType(d,c);d.length&&(I()[b>>>2>>>0]=X(d));return a};function Jb(a,b,c){b>>>=0;c>>>=0;a=W(a>>>0);b=Ed(b,"emval::as");return Fd(b,c,a)}function Kb(a,b){b>>>=0;a=W(a>>>0);b=Ed(b,"emval::as");return b.toWireType(null,a)}var Gd=a=>{try{a()}catch(b){L(b)}};
77
+ function Hd(){var a=K,b={};for(let [c,d]of Object.entries(a))b[c]="function"==typeof d?(...f)=>{Id.push(c);try{return d(...f)}finally{A||(Id.pop(),t&&1===Z&&0===Id.length&&(Z=0,P+=1,Gd(Jd),"undefined"!=typeof Fibers&&Fibers.rc()))}}:d;return b}var Z=0,t=null,Kd=0,Id=[],Ld={},Md={},Nd=0,Od=null,Pd=[];function ia(){return new Promise((a,b)=>{Od={resolve:a,reject:b}})}
78
+ function Qd(){var a=rd(65548),b=a+12;I()[a>>>2>>>0]=b;I()[a+4>>>2>>>0]=b+65536;b=Id[0];var c=Ld[b];void 0===c&&(c=Nd++,Ld[b]=c,Md[c]=b);b=c;H()[a+8>>>2>>>0]=b;return a}function Rd(){var a=H()[t+8>>>2>>>0];a=K[Md[a]];--P;return a()}
79
+ function Sd(a){if(!A){if(0===Z){var b=!1,c=!1;a((d=0)=>{if(!A&&(Kd=d,b=!0,c)){Z=2;Gd(()=>Td(t));"undefined"!=typeof MainLoop&&MainLoop.Pb&&MainLoop.resume();d=!1;try{var f=Rd()}catch(l){f=l,d=!0}var g=!1;if(!t){var h=Od;h&&(Od=null,(d?h.reject:h.resolve)(f),g=!0)}if(d&&!g)throw f;}});c=!0;b||(Z=1,t=Qd(),"undefined"!=typeof MainLoop&&MainLoop.Pb&&MainLoop.pause(),Gd(()=>Ud(t)))}else 2===Z?(Z=0,Gd(Wd),Y(t),t=null,Pd.forEach(zd)):L(`invalid state: ${Z}`);return Kd}}
80
+ function Ec(a){return Sd(b=>{a().then(b)})}function Lb(a){a>>>=0;return Ec(async()=>{var b=await W(a);return X(b)})}var Xd=[];function Mb(a,b,c,d){c>>>=0;d>>>=0;a=Xd[a>>>0];b=W(b>>>0);return a(null,b,c,d)}var Yd={},Zd=a=>{var b=Yd[a];return void 0===b?R(a):b};function Nb(a,b,c,d,f){c>>>=0;d>>>=0;f>>>=0;a=Xd[a>>>0];b=W(b>>>0);c=Zd(c);return a(b,b[c],d,f)}var $d=()=>"object"==typeof globalThis?globalThis:Function("return this")();
81
+ function Pb(a){a>>>=0;if(0===a)return X($d());a=Zd(a);return X($d()[a])}var ae=a=>{var b=Xd.length;Xd.push(a);return b},be=(a,b)=>{for(var c=Array(a),d=0;d<a;++d)c[d]=Ed(I()[b+4*d>>>2>>>0],"parameter "+d);return c},ce=(a,b)=>Object.defineProperty(b,"name",{value:a});
82
+ function de(a){var b=Function;if(!(b instanceof Function))throw new TypeError(`new_ called with constructor type ${typeof b} which is not a function`);var c=ce(b.name||"unknownFunctionName",function(){});c.prototype=b.prototype;c=new c;a=b.apply(c,a);return a instanceof Object?a:c}
83
+ function Qb(a,b,c){b=be(a,b>>>0);var d=b.shift();a--;var f="return function (obj, func, destructorsRef, args) {\n",g=0,h=[];0===c&&h.push("obj");for(var l=["retType"],m=[d],p=0;p<a;++p)h.push("arg"+p),l.push("argType"+p),m.push(b[p]),f+=` var arg${p} = argType${p}.readValueFromPointer(args${g?"+"+g:""});\n`,g+=b[p].Cb;f+=` var rv = ${1===c?"new func":"func.call"}(${h.join(", ")});\n`;d.Tb||(l.push("emval_returnValue"),m.push(Fd),f+=" return emval_returnValue(retType, destructorsRef, rv);\n");l.push(f+
84
+ "};\n");a=de(l)(...m);c=`methodCaller<(${b.map(r=>r.name).join(", ")}) => ${d.name}>`;return ae(ce(c,a))}function Rb(a){a=Zd(a>>>0);return X(e[a])}function Sb(a,b){b>>>=0;a=W(a>>>0);b=W(b);return X(a[b])}function Tb(a){a>>>=0;9<a&&(V[a+1]+=1)}function Ub(){return X([])}function Vb(a){a=W(a>>>0);for(var b=Array(a.length),c=0;c<a.length;c++)b[c]=a[c];return X(b)}function Wb(a){return X(Zd(a>>>0))}function Xb(){return X({})}
85
+ function Yb(a){a>>>=0;for(var b=W(a);b.length;){var c=b.pop();b.pop()(c)}Ob(a)}function Zb(a,b,c){b>>>=0;c>>>=0;a=W(a>>>0);b=W(b);c=W(c);a[b]=c}function $b(a,b){b>>>=0;a=Ed(a>>>0,"_emval_take_value");a=a.readValueFromPointer(b);return X(a)}
86
+ function ac(a,b){a=-9007199254740992>a||9007199254740992<a?NaN:Number(a);b>>>=0;a=new Date(1E3*a);H()[b>>>2>>>0]=a.getUTCSeconds();H()[b+4>>>2>>>0]=a.getUTCMinutes();H()[b+8>>>2>>>0]=a.getUTCHours();H()[b+12>>>2>>>0]=a.getUTCDate();H()[b+16>>>2>>>0]=a.getUTCMonth();H()[b+20>>>2>>>0]=a.getUTCFullYear()-1900;H()[b+24>>>2>>>0]=a.getUTCDay();a=(a.getTime()-Date.UTC(a.getUTCFullYear(),0,1,0,0,0,0))/864E5|0;H()[b+28>>>2>>>0]=a}
87
+ var ee=a=>0===a%4&&(0!==a%100||0===a%400),fe=[0,31,60,91,121,152,182,213,244,274,305,335],ge=[0,31,59,90,120,151,181,212,243,273,304,334];
88
+ function bc(a,b){a=-9007199254740992>a||9007199254740992<a?NaN:Number(a);b>>>=0;a=new Date(1E3*a);H()[b>>>2>>>0]=a.getSeconds();H()[b+4>>>2>>>0]=a.getMinutes();H()[b+8>>>2>>>0]=a.getHours();H()[b+12>>>2>>>0]=a.getDate();H()[b+16>>>2>>>0]=a.getMonth();H()[b+20>>>2>>>0]=a.getFullYear()-1900;H()[b+24>>>2>>>0]=a.getDay();var c=(ee(a.getFullYear())?fe:ge)[a.getMonth()]+a.getDate()-1|0;H()[b+28>>>2>>>0]=c;H()[b+36>>>2>>>0]=-(60*a.getTimezoneOffset());c=(new Date(a.getFullYear(),6,1)).getTimezoneOffset();
89
+ var d=(new Date(a.getFullYear(),0,1)).getTimezoneOffset();a=(c!=d&&a.getTimezoneOffset()==Math.min(d,c))|0;H()[b+32>>>2>>>0]=a}
90
+ function cc(a){a>>>=0;var b=new Date(H()[a+20>>>2>>>0]+1900,H()[a+16>>>2>>>0],H()[a+12>>>2>>>0],H()[a+8>>>2>>>0],H()[a+4>>>2>>>0],H()[a>>>2>>>0],0),c=H()[a+32>>>2>>>0],d=b.getTimezoneOffset(),f=(new Date(b.getFullYear(),6,1)).getTimezoneOffset(),g=(new Date(b.getFullYear(),0,1)).getTimezoneOffset(),h=Math.min(g,f);0>c?H()[a+32>>>2>>>0]=Number(f!=g&&h==d):0<c!=(h==d)&&(f=Math.max(g,f),b.setTime(b.getTime()+6E4*((0<c?h:f)-d)));H()[a+24>>>2>>>0]=b.getDay();c=(ee(b.getFullYear())?fe:ge)[b.getMonth()]+
91
+ b.getDate()-1|0;H()[a+28>>>2>>>0]=c;H()[a>>>2>>>0]=b.getSeconds();H()[a+4>>>2>>>0]=b.getMinutes();H()[a+8>>>2>>>0]=b.getHours();H()[a+12>>>2>>>0]=b.getDate();H()[a+16>>>2>>>0]=b.getMonth();H()[a+20>>>2>>>0]=b.getYear();a=b.getTime();return BigInt(isNaN(a)?-1:a/1E3)}function dc(a,b,c,d,f,g,h){return q?Q(16,1,a,b,c,d,f,g,h):-52}function ec(a,b,c,d,f,g){if(q)return Q(17,1,a,b,c,d,f,g)}var he={},pc=()=>performance.timeOrigin+performance.now();
92
+ function fc(a,b){if(q)return Q(18,1,a,b);he[a]&&(clearTimeout(he[a].id),delete he[a]);if(!b)return 0;var c=setTimeout(()=>{delete he[a];zd(()=>ie(a,performance.timeOrigin+performance.now()))},b);he[a]={id:c,qc:b};return 0}
93
+ function gc(a,b,c,d){a>>>=0;b>>>=0;c>>>=0;d>>>=0;var f=(new Date).getFullYear(),g=(new Date(f,0,1)).getTimezoneOffset();f=(new Date(f,6,1)).getTimezoneOffset();var h=Math.max(g,f);I()[a>>>2>>>0]=60*h;H()[b>>>2>>>0]=Number(g!=f);b=l=>{var m=Math.abs(l);return`UTC${0<=l?"-":"+"}${String(Math.floor(m/60)).padStart(2,"0")}${String(m%60).padStart(2,"0")}`};a=b(g);b=b(f);f<g?(fd(a,c,17),fd(b,d,17)):(fd(a,d,17),fd(b,c,17))}var lc=()=>Date.now(),je=1;
94
+ function hc(a,b,c){if(!(0<=a&&3>=a))return 28;if(0===a)a=Date.now();else if(je)a=performance.timeOrigin+performance.now();else return 52;C[c>>>0>>>3]=BigInt(Math.round(1E6*a));return 0}var ke=[],le=(a,b)=>{ke.length=0;for(var c;c=F()[a++>>>0];){var d=105!=c;d&=112!=c;b+=d&&b%8?4:0;ke.push(112==c?I()[b>>>2>>>0]:106==c?C[b>>>3]:105==c?H()[b>>>2>>>0]:J()[b>>>3>>>0]);b+=d?8:4}return ke};function ic(a,b,c){a>>>=0;b=le(b>>>0,c>>>0);return Dc[a](...b)}
95
+ function jc(a,b,c){a>>>=0;b=le(b>>>0,c>>>0);return Dc[a](...b)}var kc=()=>{};function mc(a,b){return x(M(a>>>0,b>>>0))}var nc=()=>{P+=1;throw"unwind";};function oc(){return 4294901760}var qc=()=>n?require("os").cpus().length:navigator.hardwareConcurrency;function rc(){L("Cannot use emscripten_pc_get_function without -sUSE_OFFSET_CONVERTER");return 0}
96
+ function sc(a){a>>>=0;var b=F().length;if(a<=b||4294901760<a)return!1;for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);a:{d=(Math.min(4294901760,65536*Math.ceil(Math.max(a,d)/65536))-z.buffer.byteLength+65535)/65536|0;try{z.grow(d);E();var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1}var me=()=>{L("Cannot use convertFrameToPC (needed by __builtin_return_address) without -sUSE_OFFSET_CONVERTER");return 0},ne={},oe=a=>{a.forEach(b=>{var c=me();c&&(ne[c]=b)})};
97
+ function tc(){var a=Error().stack.toString().split("\n");"Error"==a[0]&&a.shift();oe(a);ne.Lb=me();ne.cc=a;return ne.Lb}function uc(a,b,c){a>>>=0;b>>>=0;if(ne.Lb==a)var d=ne.cc;else d=Error().stack.toString().split("\n"),"Error"==d[0]&&d.shift(),oe(d);for(var f=3;d[f]&&me()!=a;)++f;for(a=0;a<c&&d[a+f];++a)H()[b+4*a>>>2>>>0]=me();return a}
98
+ var pe={},re=()=>{if(!qe){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:la||"./this.program"},b;for(b in pe)void 0===pe[b]?delete a[b]:a[b]=pe[b];var c=[];for(b in a)c.push(`${b}=${a[b]}`);qe=c}return qe},qe;
99
+ function vc(a,b){if(q)return Q(19,1,a,b);a>>>=0;b>>>=0;var c=0;re().forEach((d,f)=>{var g=b+c;f=I()[a+4*f>>>2>>>0]=g;for(g=0;g<d.length;++g)D()[f++>>>0]=d.charCodeAt(g);D()[f>>>0]=0;c+=d.length+1});return 0}function wc(a,b){if(q)return Q(20,1,a,b);a>>>=0;b>>>=0;var c=re();I()[a>>>2>>>0]=c.length;var d=0;c.forEach(f=>d+=f.length+1);I()[b>>>2>>>0]=d;return 0}function yc(a){return q?Q(21,1,a):52}function zc(a,b,c,d){return q?Q(22,1,a,b,c,d):52}function Ac(a,b,c,d){return q?Q(23,1,a,b,c,d):70}
100
+ var se=[null,[],[]];function Bc(a,b,c,d){if(q)return Q(24,1,a,b,c,d);b>>>=0;c>>>=0;d>>>=0;for(var f=0,g=0;g<c;g++){var h=I()[b>>>2>>>0],l=I()[b+4>>>2>>>0];b+=8;for(var m=0;m<l;m++){var p=F()[h+m>>>0],r=se[a];0===p||10===p?((1===a?ta:x)(dd(r)),r.length=0):r.push(p)}f+=l}I()[d>>>2>>>0]=f;return 0}q||Sc();for(var te=Array(256),ue=0;256>ue;++ue)te[ue]=String.fromCharCode(ue);gd=te;S=e.BindingError=class extends Error{constructor(a){super(a);this.name="BindingError"}};
101
+ e.InternalError=class extends Error{constructor(a){super(a);this.name="InternalError"}};V.push(0,1,void 0,1,null,1,!0,1,!1,1);e.count_emval_handles=()=>V.length/2-5-nd.length;var Cd=[Cc,Qc,bd,gb,hb,ib,jb,kb,lb,mb,nb,ob,pb,qb,rb,sb,dc,ec,fc,vc,wc,yc,zc,Ac,Bc],bb,K;
102
+ (async function(){function a(d,f){K=d.exports;K=Hd();K=ve();Rc.push(K.ib);va=f;Wa();return K}Ua++;var b=ab();if(e.instantiateWasm)return new Promise(d=>{e.instantiateWasm(b,(f,g)=>{a(f,g);d(f.exports)})});if(q)return new Promise(d=>{Ha=f=>{var g=new WebAssembly.Instance(f,ab());d(a(g,f))}});Xa??=e.locateFile?e.locateFile?e.locateFile("ort-wasm-simd-threaded.jsep.wasm",v):v+"ort-wasm-simd-threaded.jsep.wasm":(new URL("ort-wasm-simd-threaded.jsep.wasm",import.meta.url)).href;try{var c=await $a(b);
103
+ return a(c.instance,c.module)}catch(d){return ca(d),Promise.reject(d)}})();var Dd=a=>(Dd=K.Da)(a),Pa=()=>(Pa=K.Ea)();e._OrtInit=(a,b)=>(e._OrtInit=K.Fa)(a,b);e._OrtGetLastError=(a,b)=>(e._OrtGetLastError=K.Ga)(a,b);e._OrtCreateSessionOptions=(a,b,c,d,f,g,h,l,m,p)=>(e._OrtCreateSessionOptions=K.Ha)(a,b,c,d,f,g,h,l,m,p);e._OrtAppendExecutionProvider=(a,b,c,d,f)=>(e._OrtAppendExecutionProvider=K.Ia)(a,b,c,d,f);e._OrtAddFreeDimensionOverride=(a,b,c)=>(e._OrtAddFreeDimensionOverride=K.Ja)(a,b,c);
104
+ e._OrtAddSessionConfigEntry=(a,b,c)=>(e._OrtAddSessionConfigEntry=K.Ka)(a,b,c);e._OrtReleaseSessionOptions=a=>(e._OrtReleaseSessionOptions=K.La)(a);e._OrtCreateSession=(a,b,c)=>(e._OrtCreateSession=K.Ma)(a,b,c);e._OrtReleaseSession=a=>(e._OrtReleaseSession=K.Na)(a);e._OrtGetInputOutputCount=(a,b,c)=>(e._OrtGetInputOutputCount=K.Oa)(a,b,c);e._OrtGetInputOutputMetadata=(a,b,c,d)=>(e._OrtGetInputOutputMetadata=K.Pa)(a,b,c,d);e._OrtFree=a=>(e._OrtFree=K.Qa)(a);
105
+ e._OrtCreateTensor=(a,b,c,d,f,g)=>(e._OrtCreateTensor=K.Ra)(a,b,c,d,f,g);e._OrtGetTensorData=(a,b,c,d,f)=>(e._OrtGetTensorData=K.Sa)(a,b,c,d,f);e._OrtReleaseTensor=a=>(e._OrtReleaseTensor=K.Ta)(a);e._OrtCreateRunOptions=(a,b,c,d)=>(e._OrtCreateRunOptions=K.Ua)(a,b,c,d);e._OrtAddRunConfigEntry=(a,b,c)=>(e._OrtAddRunConfigEntry=K.Va)(a,b,c);e._OrtReleaseRunOptions=a=>(e._OrtReleaseRunOptions=K.Wa)(a);e._OrtCreateBinding=a=>(e._OrtCreateBinding=K.Xa)(a);
106
+ e._OrtBindInput=(a,b,c)=>(e._OrtBindInput=K.Ya)(a,b,c);e._OrtBindOutput=(a,b,c,d)=>(e._OrtBindOutput=K.Za)(a,b,c,d);e._OrtClearBoundOutputs=a=>(e._OrtClearBoundOutputs=K._a)(a);e._OrtReleaseBinding=a=>(e._OrtReleaseBinding=K.$a)(a);e._OrtRunWithBinding=(a,b,c,d,f)=>(e._OrtRunWithBinding=K.ab)(a,b,c,d,f);e._OrtRun=(a,b,c,d,f,g,h,l)=>(e._OrtRun=K.bb)(a,b,c,d,f,g,h,l);e._OrtEndProfiling=a=>(e._OrtEndProfiling=K.cb)(a);e._JsepOutput=(a,b,c)=>(e._JsepOutput=K.db)(a,b,c);
107
+ e._JsepGetNodeName=a=>(e._JsepGetNodeName=K.eb)(a);
108
+ var Ka=()=>(Ka=K.fb)(),Y=e._free=a=>(Y=e._free=K.gb)(a),rd=e._malloc=a=>(rd=e._malloc=K.hb)(a),Ma=(a,b,c,d,f,g)=>(Ma=K.kb)(a,b,c,d,f,g),Sa=()=>(Sa=K.lb)(),Oc=(a,b,c,d,f)=>(Oc=K.mb)(a,b,c,d,f),Uc=a=>(Uc=K.nb)(a),Yc=a=>(Yc=K.ob)(a),ie=(a,b)=>(ie=K.pb)(a,b),Ad=()=>(Ad=K.qb)(),Wc=(a,b)=>(Wc=K.rb)(a,b),Pc=a=>(Pc=K.sb)(a),Nc=a=>(Nc=K.tb)(a),Mc=()=>(Mc=K.ub)(),Xc=e.dynCall_ii=(a,b)=>(Xc=e.dynCall_ii=K.vb)(a,b),Ud=a=>(Ud=K.wb)(a),Jd=()=>(Jd=K.xb)(),Td=a=>(Td=K.yb)(a),Wd=()=>(Wd=K.zb)();
109
+ function ve(){var a=K;a=Object.assign({},a);var b=d=>f=>d(f)>>>0,c=d=>()=>d()>>>0;a.Da=b(a.Da);a.fb=c(a.fb);a.hb=b(a.hb);a.tb=b(a.tb);a.ub=c(a.ub);a.__cxa_get_exception_ptr=b(a.__cxa_get_exception_ptr);return a}e.stackSave=()=>Mc();e.stackRestore=a=>Pc(a);e.stackAlloc=a=>Nc(a);
110
+ e.setValue=function(a,b,c="i8"){c.endsWith("*")&&(c="*");switch(c){case "i1":D()[a>>>0]=b;break;case "i8":D()[a>>>0]=b;break;case "i16":G()[a>>>1>>>0]=b;break;case "i32":H()[a>>>2>>>0]=b;break;case "i64":C[a>>>3]=BigInt(b);break;case "float":Ga()[a>>>2>>>0]=b;break;case "double":J()[a>>>3>>>0]=b;break;case "*":I()[a>>>2>>>0]=b;break;default:L(`invalid type for setValue: ${c}`)}};
111
+ e.getValue=function(a,b="i8"){b.endsWith("*")&&(b="*");switch(b){case "i1":return D()[a>>>0];case "i8":return D()[a>>>0];case "i16":return G()[a>>>1>>>0];case "i32":return H()[a>>>2>>>0];case "i64":return C[a>>>3];case "float":return Ga()[a>>>2>>>0];case "double":return J()[a>>>3>>>0];case "*":return I()[a>>>2>>>0];default:L(`invalid type for getValue: ${b}`)}};e.UTF8ToString=M;e.stringToUTF8=fd;e.lengthBytesUTF8=ed;
112
+ function we(){if(0<Ua)Va=we;else if(q)aa(e),Ta();else{for(;0<Hc.length;)Hc.shift()(e);0<Ua?Va=we:(e.calledRun=!0,A||(Ta(),aa(e)))}}we();e.PTR_SIZE=4;moduleRtn=da;
113
+
114
+
115
+ return moduleRtn;
116
+ }
117
+ );
118
+ })();
119
+ export default ortWasmThreaded;
120
+ var isPthread = globalThis.self?.name?.startsWith('em-pthread');
121
+ var isNode = typeof globalThis.process?.versions?.node == 'string';
122
+ if (isNode) isPthread = (await import('worker_threads')).workerData === 'em-pthread';
123
+
124
+ // When running as a pthread, construct a new instance on startup
125
+ isPthread && ortWasmThreaded();
vendor/kokoro/transformers/ort-wasm-simd-threaded.jsep.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c46655e8a94afc45338d4cb2b840475f88e5012d524509916e505079c00bfa39
3
+ size 21596019
vendor/kokoro/transformers/transformers.js ADDED
The diff for this file is too large to render. See raw diff
 
vendor/transformers/ort-wasm-simd-threaded.jsep.wasm ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0f6fe5c40378504d1a25a77f766133464bb15705af23e01c994f185719fb080e
3
+ size 21643825
vendor/transformers/transformers.js ADDED
The diff for this file is too large to render. See raw diff
 
vendor/transformers/transformers.mjs ADDED
The diff for this file is too large to render. See raw diff