Spaces:
Running
Running
Unified feature-complete build: clause-streamed voice + 260-char bubbles + PWA/offline SW + self-contained vendor
Browse files- atlas12288.wasm +3 -0
- core/holo-orb.js +260 -13
- core/listen.js +37 -74
- core/voice-out.js +36 -25
- icon.svg +23 -0
- index.html +168 -170
- manifest.webmanifest +14 -0
- qvac-kstore.mjs +56 -0
- sw.js +33 -0
- vendor/kokoro/kokoro.js +1 -0
- vendor/kokoro/phonemizer.js +0 -0
- vendor/kokoro/stub.js +6 -0
- vendor/kokoro/transformers/ort-wasm-simd-threaded.jsep.mjs +125 -0
- vendor/kokoro/transformers/ort-wasm-simd-threaded.jsep.wasm +3 -0
- vendor/kokoro/transformers/transformers.js +0 -0
- vendor/transformers/ort-wasm-simd-threaded.jsep.wasm +3 -0
- vendor/transformers/transformers.js +0 -0
- vendor/transformers/transformers.mjs +0 -0
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 —
|
| 2 |
-
//
|
| 3 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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)
|
| 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);
|
| 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);
|
| 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);
|
| 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); }`;
|
| 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.js — THE 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 |
-
//
|
| 3 |
-
//
|
| 4 |
-
|
| 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 |
-
|
| 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
|
| 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 {
|
| 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
|
| 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 |
-
|
| 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
|
| 127 |
|
| 128 |
-
// createHandsFree({ onState, onFinal, onProgress }) — tap once to open; it listens continuously, and
|
| 129 |
-
// time you finish a sentence it transcribes on-device and hands you the text via onFinal(text).
|
| 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 =
|
| 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 |
-
|
| 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
|
| 3 |
-
//
|
| 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 "/
|
| 11 |
|
| 12 |
-
let _tts = null, _loading = null, _ctx = 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("/
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
} catch {}
|
| 27 |
-
//
|
| 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 |
-
//
|
| 39 |
-
|
|
|
|
|
|
|
| 40 |
const tts = await loadVoice();
|
| 41 |
-
const out = await tts.generate(String(text), { voice:
|
| 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,
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
return new Promise((res) => { s.onended = () => { if (_src === s) _src = null; res(); }; s.start(); });
|
| 48 |
}
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
<
|
| 4 |
-
|
| 5 |
-
<
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
<
|
|
|
|
|
|
|
|
|
|
| 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}
|
| 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}
|
| 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}
|
| 77 |
-
.row.grouped.a .msg{border-top-left-radius:17px}
|
| 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
|
| 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}
|
| 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)}
|
| 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";
|
| 136 |
-
import { mountOrb } from "./core/holo-orb.js";
|
| 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;
|
| 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
|
| 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;
|
| 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" : "");
|
| 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 = "✓✓"; }
|
| 227 |
-
function markRead(b) { const t = _tick(b); if (t) { t.textContent = "✓✓"; t.classList.add("read"); } }
|
| 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) => ({ "&": "&", "<": "<", ">": ">" }[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 —
|
| 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, "");
|
| 246 |
-
s = s.replace(/\s+[—–]\s+/g, ", ").replace(/(\w)\s-\s(\w)/g, "$1, $2");
|
| 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 |
-
//
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
function splitReply(text) {
|
| 257 |
const t = String(text || "").trim(); if (!t) return [t];
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
|
|
|
|
|
|
| 264 |
}
|
| 265 |
-
|
| 266 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
}
|
| 268 |
-
// deliver a reply as human, paced, multi-bubble messages
|
| 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
|
| 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
|
| 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;
|
| 298 |
return "waking up…";
|
| 299 |
}
|
| 300 |
|
| 301 |
-
// ── memory: persist + restore the visible conversation
|
| 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
|
| 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.
|
| 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
|
| 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.";
|
| 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 |
-
// ──
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
async function generate(text, skipUser) {
|
| 336 |
const _tSend = _perf();
|
| 337 |
-
stopSpeak(); // barge-in
|
| 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);
|
| 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 }); }
|
| 352 |
else {
|
| 353 |
-
if (primingPromise) { setStatus("loading", "settling in…"); try { await primingPromise; } catch {} typing(true); }
|
| 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 |
-
|
|
|
|
| 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;
|
| 365 |
-
record({ warm: _warm, cold: !_warm, ttft, tokps: (res.stats && res.stats.tokps) || 0, ack: ackMs, spec: (res.stats && res.stats.spec) || null });
|
|
|
|
| 366 |
}
|
| 367 |
-
out = humanize(identityGuard(out)) || "…";
|
| 368 |
think.parentNode && think.parentNode.remove();
|
| 369 |
-
await deliver(out
|
| 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
|
| 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(); }
|
| 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
|
| 407 |
-
if (
|
| 408 |
send.style.display = (has || !micAvailable) ? "flex" : "none";
|
| 409 |
send.classList.toggle("ready", has);
|
| 410 |
}
|
| 411 |
syncComposer();
|
| 412 |
|
| 413 |
-
//
|
| 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 |
-
//
|
| 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 &&
|
| 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) {
|
| 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); }
|
| 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;
|
| 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); }
|
| 536 |
-
else if (!greeted && !had) greet();
|
| 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) => ({ "&": "&", "<": "<", ">": ">" }[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
|
|
|