aut4rk commited on
Commit
2b8633e
·
verified ·
1 Parent(s): 81796a5

Fix Brave ASR fallback and recover from WebGPU transcription errors

Browse files
README.md CHANGED
@@ -4,9 +4,85 @@ emoji: 🎙️
4
  colorFrom: indigo
5
  colorTo: purple
6
  sdk: static
 
7
  app_file: index.html
8
  custom_headers:
9
  cross-origin-embedder-policy: require-corp
10
  cross-origin-opener-policy: same-origin
11
  cross-origin-resource-policy: cross-origin
12
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  colorFrom: indigo
5
  colorTo: purple
6
  sdk: static
7
+ app_build_command: npm run build && cp -r dist/* .
8
  app_file: index.html
9
  custom_headers:
10
  cross-origin-embedder-policy: require-corp
11
  cross-origin-opener-policy: same-origin
12
  cross-origin-resource-policy: cross-origin
13
  ---
14
+
15
+ # Browser Voice Agent Demo
16
+
17
+ Browser-first, on-device voice agent demo with Gradio-style camouflage. The app is served locally with Vite during development and built as static assets for public deployment.
18
+
19
+ ## Stack
20
+
21
+ - `Vite` for local development, preview, and bundling
22
+ - `Bun` for package management and tests
23
+ - `@mlc-ai/web-llm` for local browser LLM inference
24
+ - `@huggingface/transformers` for local ASR
25
+ - Vendored Pocket TTS browser worker derived from `KevinAHM/pocket-tts-web`
26
+
27
+ ## Local Development
28
+
29
+ ```bash
30
+ bun install
31
+ bun run dev
32
+ ```
33
+
34
+ Then open `http://localhost:3000`.
35
+
36
+ The same dev server now carries the isolation headers, so Pocket can take the threaded path without a separate custom preview server.
37
+
38
+ For a production-like static serve, use:
39
+
40
+ ```bash
41
+ bun run preview
42
+ ```
43
+
44
+ That serves the built `dist/` output with the same COOP/COEP/CORP headers.
45
+
46
+ ## Test
47
+
48
+ ```bash
49
+ bun test
50
+ ```
51
+
52
+ ## Build
53
+
54
+ ```bash
55
+ bun run build
56
+ ```
57
+
58
+ The production output is emitted to `dist/`.
59
+
60
+ ## Pocket TTS Runtime
61
+
62
+ The app now ships a built-in Pocket TTS browser runtime and same-origin model assets:
63
+
64
+ - `src/vendor/pocket-tts/inference-worker.js`
65
+ - `src/vendor/pocket-tts/sentencepiece.js`
66
+ - `src/vendor/pocket-tts/CODE-LICENSE`
67
+ - `src/vendor/pocket-tts/onnx/*`
68
+ - `src/vendor/pocket-tts/tokenizer.model`
69
+ - `src/vendor/pocket-tts/voices.bin`
70
+ - `src/vendor/onnxruntime-web/*`
71
+
72
+ Those files are copied into `dist/` during `bun run build` and served locally from:
73
+
74
+ - `/pocket-tts/inference-worker.js`
75
+ - `/pocket-tts/sentencepiece.js`
76
+ - `/pocket-tts/onnx/*`
77
+ - `/pocket-tts/tokenizer.model`
78
+ - `/pocket-tts/voices.bin`
79
+ - `/onnxruntime-web/*`
80
+
81
+ The vendored worker code is Apache 2.0; its license file is preserved in [src/vendor/pocket-tts/CODE-LICENSE](/home/autark/src/private-voice-agent/src/vendor/pocket-tts/CODE-LICENSE).
82
+
83
+ For best Pocket performance, serve the app with:
84
+
85
+ - `Cross-Origin-Opener-Policy: same-origin`
86
+ - `Cross-Origin-Embedder-Policy: require-corp`
87
+
88
+ Without those headers, the worker still runs, but ONNX Runtime drops back to single-threaded WASM.
assets/{index-ucxsjAc2.js → index-0lmKKa3c.js} RENAMED
@@ -1,14 +1,14 @@
1
- (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))n(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const a of s.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&n(a)}).observe(document,{childList:!0,subtree:!0});function t(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function n(i){if(i.ep)return;i.ep=!0;const s=t(i);fetch(i.href,s)}})();const $=1,q=20,oe=q*1e3,ce="Read the following lines clearly at a comfortable speaking pace until calibration is complete. Repeat from the beginning if time remains.",le=`The quick brown fox jumps over the lazy dog.
2
  I am speaking in my natural voice at a comfortable volume.
3
  The sound of my own voice has never bothered me.
4
  A recording is never quite the same as the original.
5
  I confirm this is a true representation of how I speak.
6
  My speech remains clear when I maintain a consistent distance from the microphone.
7
  I will continue speaking until the calibration timer reaches zero.
8
- This sample should capture my normal pronunciation and pacing.`,ue=()=>({hasWebGPU:!1,hasMediaDevices:!1,hasAudioWorklet:!1,hasCrossOriginIsolation:!1,canRunDemo:!1,failureReason:"Checking browser capabilities."}),L=()=>({source:"first-utterance",ready:!1,referenceAudioKey:null,embeddingCacheKey:null,lastUpdatedAt:null}),de=()=>({llmModelId:"Qwen2.5-0.5B-Instruct-q4f16_1-MLC",asrModelId:"Xenova/whisper-tiny.en",ttsModelId:"/pocket-tts",maxTurnsPersisted:8,targetFirstAudioMs:2500}),Q=(r=de())=>({capability:ue(),phase:"idle",micPermission:"unknown",consentAccepted:!1,firstContactComplete:!1,turns:[],voiceProfile:L(),statusText:"Idle",errorMessage:null,runtimeReady:!1,supportsResume:!1,lastOpenedAt:null,calibrationProgress:0,calibrationSecondsRemaining:null,calibrationRecording:!1,benchmarkSummary:null,config:r}),U=r=>r.capability.canRunDemo?r.runtimeReady?r.capability.hasCrossOriginIsolation?"Ready":"Ready (single-threaded)":"Run Microphone Calibration":r.capability.failureReason??"Unsupported";class he{#e;#t=new Set;constructor(e=Q()){this.#e=e}getState(){return this.#e}subscribe(e){return this.#t.add(e),e(this.#e),()=>{this.#t.delete(e)}}setState(e){const t=this.#e,n=typeof e=="function"?e(t):e;if(n!==t){this.#e=n;for(const i of this.#t)i(this.#e)}}update(e){this.setState(t=>({...t,...e}))}appendTurn(e){this.setState(t=>({...t,turns:[...t.turns,e]}))}updateTurn(e,t){this.setState(n=>({...n,turns:n.turns.map(i=>i.id===e?{...i,...t}:i)}))}hydrate(e){this.setState(t=>({...t,turns:e.turns,voiceProfile:e.voiceProfile,supportsResume:e.turns.length>0||e.voiceProfile.ready,lastOpenedAt:e.lastOpenedAt,firstContactComplete:e.turns.some(n=>n.isFirstContact),statusText:U(t)}))}setVoiceProfile(e){this.update({voiceProfile:e})}clearConversation(){this.setState(e=>({...e,phase:"idle",firstContactComplete:!1,turns:[],voiceProfile:e.runtimeReady?e.voiceProfile:L(),errorMessage:null,statusText:U(e),supportsResume:!1,lastOpenedAt:null,calibrationProgress:e.runtimeReady?1:0,calibrationSecondsRemaining:e.runtimeReady?0:null,calibrationRecording:!1}))}toPersistedSession(){const e=this.getState();return{version:$,turns:e.turns.slice(-e.config.maxTurnsPersisted),voiceProfile:e.voiceProfile,lastOpenedAt:Date.now()}}}const X=(r,e,t={})=>({id:crypto.randomUUID(),role:r,transcript:e,audioStatus:t.audioStatus??"none",createdAt:Date.now(),isFirstContact:t.isFirstContact??!1}),_=160,me=24,ge=520,pe=r=>r.trim().split(/\s+/).filter(Boolean).length,fe=r=>{const e=/[.!?]+["')\]]*(?=\s|$)/.exec(r);return!e||e.index==null?-1:e.index+e[0].length},be=r=>{if(r.length<_||pe(r)<me)return-1;const e=r.lastIndexOf(" ");return e>=Math.floor(_*.94)?e:-1},O=(r,e=!1)=>{let t=r.replace(/^\s+/,"");const n=[];for(;t.trim();){const i=fe(t);if(i>0){const a=t.slice(0,i).trim();a&&n.push(a),t=t.slice(i).replace(/^\s+/,"");continue}if(!e)break;const s=t.trim();s&&n.push(s),t="";break}return{segments:n,remainder:t}},v=r=>{const e=r.replace(/^\s+/,""),t=be(e);return t<=0?{segment:null,remainder:e}:{segment:e.slice(0,t).trim(),remainder:e.slice(t).replace(/^\s+/,"")}};class we{#e;#t;#r=null;#i=null;constructor(e,t){this.#e=e,this.#t=t}get state(){return this.#e.getState()}async bootstrap(){const e=await this.#t.capabilities.detect();this.#e.update({capability:e,runtimeReady:!1,statusText:e.canRunDemo?"Run Microphone Calibration":"Unsupported"});const t=this.#t.persistence.load();t&&this.#e.hydrate(t)}setConsentAccepted(e){this.#e.update({consentAccepted:e})}async runBenchmark(){const e=this.#e.getState();let t=null;const n=i=>{this.#e.getState().phase==="benchmarking"&&this.#e.update({calibrationProgress:i.progress,calibrationSecondsRemaining:Math.max(0,Math.ceil(i.remainingMs/1e3)),calibrationRecording:i.isRecording})};if(e.phase!=="benchmarking"){if(!e.consentAccepted){this.#e.update({errorMessage:"Allow microphone access and local audio processing to calibrate the system.",phase:"error",statusText:"Consent required",runtimeReady:!1});return}if(!e.capability.canRunDemo){this.#e.update({errorMessage:e.capability.failureReason,phase:"error",statusText:"Unsupported",runtimeReady:!1});return}this.#e.update({phase:"benchmarking",statusText:"Microphone calibration",errorMessage:null,runtimeReady:!1,benchmarkSummary:null,calibrationProgress:0,calibrationSecondsRemaining:q,calibrationRecording:!1,voiceProfile:L()});try{const i=await this.#t.audioCapture.ensureMicrophonePermission();if(this.#e.update({micPermission:i}),i!=="granted")throw new Error("Microphone access was denied. Retry when the browser permission is available.");const s=new AbortController,a=(async()=>{await this.#t.asr.initialize(),await this.#t.asr.warmup?.(),await this.#t.llm.initialize(),await this.#t.llm.warmup?.(),await this.#t.tts.initialize(),await this.#t.tts.warmup?.()})().catch(l=>{t=l,s.abort()});this.#e.update({statusText:"Microphone calibration",calibrationRecording:!0});const o=await this.#t.audioCapture.recordForDuration({durationMs:oe,signal:s.signal,onProgress:n});if(t)throw t;this.#e.update({calibrationRecording:!1,calibrationProgress:1,calibrationSecondsRemaining:0,statusText:"Finalizing calibration"}),await a,await this.#t.asr.transcribe(o),this.#e.update({statusText:"Finalizing calibration"});const c=await this.#t.tts.bootstrapFromUtterance(o);this.#e.setVoiceProfile(c.voiceProfile),this.#e.update({phase:"idle",statusText:e.capability.hasCrossOriginIsolation?"Ready":"Ready (single-threaded)",runtimeReady:!0,errorMessage:null,benchmarkSummary:"Calibration ready. Captured 20 seconds of local audio and completed the transcription check."})}catch(i){const s=t??i;this.#e.update({phase:"error",statusText:"Calibration failed",runtimeReady:!1,calibrationRecording:!1,errorMessage:s instanceof Error?s.message:"Microphone calibration failed."})}}}async start(){const e=this.#e.getState();if(e.phase==="arming"||e.phase==="listening"||e.phase==="thinking"||e.phase==="speaking"){this.#o();return}if(!e.consentAccepted){this.#e.update({errorMessage:"Allow microphone access and local audio processing to begin.",phase:"error",statusText:"Consent required"});return}if(!e.capability.canRunDemo){this.#e.update({errorMessage:e.capability.failureReason,phase:"error",statusText:"Unsupported"});return}if(!e.runtimeReady){this.#e.update({phase:"error",statusText:"Calibration required",errorMessage:"Run Microphone Calibration before starting a session."});return}this.#r?.abort(),this.#r=new AbortController,this.#e.update({errorMessage:null,phase:"arming",statusText:"Arming microphone"});const t=await this.#t.audioCapture.ensureMicrophonePermission();if(this.#e.update({micPermission:t}),t!=="granted"){this.#e.update({phase:"error",statusText:"Microphone denied",errorMessage:"Microphone access was denied. Retry when the browser permission is available."});return}this.#i=this.#s(this.#r.signal),this.#i}async clear(){this.#o(),this.#t.persistence.clear(),this.#e.clearConversation()}async dispose(){await this.clear(),await this.#t.audioCapture.dispose()}async#s(e){try{this.#e.update({phase:"listening",statusText:"Listening",errorMessage:null});const t=await this.#t.audioCapture.listenForUtterance({signal:e});if(e.aborted)return;await this.#n(t,e)}catch(t){if(e.aborted)return;this.#e.update({phase:"error",statusText:"Runtime failed",errorMessage:t instanceof Error?t.message:"Audio or model runtime failed."})}finally{this.#r?.signal===e&&(this.#r=null),this.#i&&(this.#i=null)}}async#n(e,t){this.#e.update({phase:"thinking",statusText:"Transcribing",errorMessage:null});const n=await this.#t.asr.transcribe(e),i=X("user",n.text);this.#e.appendTurn(i);const s=this.#e.getState().turns;let a=this.#e.getState().voiceProfile;const o=a.ready?Promise.resolve({voiceProfile:a}):this.#t.tts.bootstrapFromUtterance(e);a.ready||this.#e.update({statusText:"Thinking"});const c=X("assistant","",{audioStatus:"queued",isFirstContact:!this.#e.getState().firstContactComplete});this.#e.appendTurn(c),this.#e.update({statusText:"Thinking"});const l=this.#t.playback.createStream({signal:t,onEnded:()=>{this.#e.updateTurn(c.id,{audioStatus:"done"})}});let u="",d=null,h=!1,g=Promise.resolve();const m=async()=>{if(a.ready)return;a=(await o).voiceProfile,this.#e.setVoiceProfile(a)},p=f=>{const y=f.trim();y&&(g=g.then(async()=>{t.aborted||(await m(),await this.#t.tts.synthesizeStream({text:y,signal:t,onAudioChunk:async M=>{h||(h=!0,this.#e.update({phase:"speaking",statusText:"Speaking"}),this.#e.updateTurn(c.id,{audioStatus:"playing"})),await l.enqueue(M)}}))}))},F=()=>{d!=null&&(globalThis.clearTimeout(d),d=null)},G=()=>{d!=null||t.aborted||(d=globalThis.setTimeout(()=>{d=null;const f=v(u);f.segment&&(u=f.remainder,p(f.segment),v(u).segment&&G())},ge))},ae=await this.#t.llm.generate({turns:s,signal:t,onChunk:f=>{this.#e.updateTurn(c.id,{transcript:`${this.#a(c.id)}${f}`}),u+=f;const y=O(u);u=y.remainder,y.segments.length>0&&F();for(const M of y.segments)p(M);v(u).segment&&G()}});this.#e.updateTurn(c.id,{transcript:ae}),F();const E=v(u);E.segment&&(u=E.remainder,p(E.segment));const N=O(u,!0);u=N.remainder;for(const f of N.segments)p(f);await m(),await g,await l.finish(),h||this.#e.updateTurn(c.id,{audioStatus:"done"}),this.#e.update({firstContactComplete:!0,phase:"idle",statusText:this.#l()}),this.#t.persistence.save(this.#e.toPersistedSession())}#a(e){return this.#e.getState().turns.find(t=>t.id===e)?.transcript??""}#l(){return this.#e.getState().capability.hasCrossOriginIsolation?"Ready":"Ready (single-threaded)"}#o(){this.#r?.abort(),this.#r=null,this.#t.audioCapture.stop(),this.#t.playback.stop(),this.#e.update({phase:"idle",statusText:this.#l()})}}const ye="modulepreload",Ce=function(r){return"/"+r},K={},j=function(e,t,n){let i=Promise.resolve();if(t&&t.length>0){let c=function(l){return Promise.all(l.map(u=>Promise.resolve(u).then(d=>({status:"fulfilled",value:d}),d=>({status:"rejected",reason:d}))))};document.getElementsByTagName("link");const a=document.querySelector("meta[property=csp-nonce]"),o=a?.nonce||a?.getAttribute("nonce");i=c(t.map(l=>{if(l=Ce(l),l in K)return;K[l]=!0;const u=l.endsWith(".css"),d=u?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${l}"]${d}`))return;const h=document.createElement("link");if(h.rel=u?"stylesheet":ye,u||(h.as="script"),h.crossOrigin="",h.href=l,o&&h.setAttribute("nonce",o),document.head.appendChild(h),u)return new Promise((g,m)=>{h.addEventListener("load",g),h.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${l}`)))})}))}function s(a){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=a,window.dispatchEvent(o),!o.defaultPrevented)throw a}return i.then(a=>{for(const o of a||[])o.status==="rejected"&&s(o.reason);return e().catch(s)})},Ie=r=>{const e=r.userAgent??"",t=r.userAgentData?.brands??[];return/\bBrave\//i.test(e)?!0:t.some(n=>/brave/i.test(n.brand))},D=r=>{switch(r.status){case"initiate":return`Preparing ${r.file??"speech model"}...`;case"download":return`Downloading ${r.file??"speech model"}...`;case"progress":return`Downloading ${r.file??"speech model"} (${Math.round(r.progress??0)}%)`;case"done":return`Loaded ${r.file??"speech model"}.`;case"ready":return`Speech model ready (${r.model??r.task??"loaded"}).`;default:return"Loading speech model..."}};class Ae{constructor(e){this.modelId=e}#e=null;#t="wasm";async initialize(e={}){if(this.#e)return;const{env:t,pipeline:n}=await j(async()=>{const{env:s,pipeline:a}=await import("./transformers.web-Cny4Tgyw.js");return{env:s,pipeline:a}},[]);t.allowLocalModels=!1,t.useFS=!1,t.useFSCache=!1,t.useBrowserCache=!0;const i=typeof navigator<"u"&&"gpu"in navigator&&!Ie(navigator)?"webgpu":"wasm";try{e.onProgress?.(`Loading ${this.modelId} on ${i.toUpperCase()}...`),this.#e=await n("automatic-speech-recognition",this.modelId,{device:i,dtype:i==="webgpu"?"fp32":"q8",progress_callback:s=>{e.onProgress?.(D(s))}}),this.#t=i}catch(s){if(i!=="webgpu")throw s;console.warn("ASR WebGPU initialization failed, falling back to WASM.",s),e.onProgress?.("Speech WebGPU setup failed. Falling back to WASM..."),this.#e=await n("automatic-speech-recognition",this.modelId,{device:"wasm",dtype:"q8",progress_callback:a=>{e.onProgress?.(D(a))}}),this.#t="wasm"}e.onProgress?.(`Speech ready on ${this.#t.toUpperCase()}.`)}async warmup(e={}){await this.initialize(e),e.onProgress?.("Speech runtime warmed.")}async transcribe(e){if(await this.initialize(),!this.#e)throw new Error("ASR pipeline failed to initialize.");const t=URL.createObjectURL(e);try{const n=await this.#e(t),i=typeof n=="string"?n:n.text??"";if(!i.trim())throw new Error("Speech recognition returned an empty transcript.");return{text:i.trim()}}finally{URL.revokeObjectURL(t)}}getDevice(){return this.#t}}const H=`You are a deadpan browser voice agent demo.
9
 
10
  Respond directly, briefly, and clearly. Sound like a capable local assistant, not a character.
11
  Do not mention that you are part of a game, experiment, fiction, or narrative.
12
  Do not call attention to the user's cloned voice.
13
  Do not add theatrical flourishes or emotional commentary unless the user explicitly asks for it.
14
- `,Se=["Qwen2.5-0.5B-Instruct-q4f16_1-MLC","SmolLM2-360M-Instruct-q4f16_1-MLC","Llama-3.2-1B-Instruct-q4f16_1-MLC","Qwen2.5-1.5B-Instruct-q4f16_1-MLC"],ve=r=>{const e=typeof r.progress=="number"?`${Math.round(r.progress*100)}%`:null;return r.text&&e?`${r.text} (${e})`:r.text?r.text:e?`Loading language model (${e})`:"Loading language model..."};class T{constructor(e){this.preferredModelId=e}#e=null;#t=null;async initialize(e={}){if(this.#e){e.onProgress?.(`Language ready (${this.#t??this.preferredModelId??"model loaded"}).`);return}const t=await j(()=>import("./index-DFLBea7M.js"),[]),n=this.preferredModelId??T.#i(t);e.onProgress?.(`Preparing ${n}...`),this.#e=await t.CreateMLCEngine(n,{initProgressCallback:i=>{e.onProgress?.(ve(i))}}),this.#t=n,e.onProgress?.(`Language ready (${n}).`)}async generate(e){if(await this.initialize(),!this.#e)throw new Error("LLM engine failed to initialize.");const t=T.#r(e.turns),n=await this.#e.chat.completions.create({messages:t,temperature:.7,stream:!0});let i="";for await(const s of n){if(e.signal?.aborted)break;const a=s.choices[0]?.delta?.content??"";a&&(i+=a,e.onChunk(a))}return i.trim()}async warmup(e={}){if(await this.initialize(e),!this.#e)throw new Error("LLM engine failed to initialize.");e.onProgress?.("Compiling first language response..."),await this.#e.chat.completions.create({messages:[{role:"system",content:H},{role:"user",content:"Reply with one short word."}],temperature:0,max_tokens:1}),e.onProgress?.("Language runtime warmed.")}static#r(e){const t=[{role:"system",content:H}];for(const n of e)n.role==="system"||!n.transcript.trim()||t.push({role:n.role,content:n.transcript});return t}static#i(e){const n=(e.prebuiltAppConfig?.model_list??[]).map(s=>{const a="model_id"in s?s.model_id:"";return{candidate:s,modelId:a}}).filter(s=>!!s.modelId);for(const s of Se){const a=n.find(o=>o.modelId.includes(s));if(a)return a.modelId}const i=n.at(0)?.modelId;if(!i)throw new Error("WebLLM did not expose any prebuilt models.");return i}}const S=24e3,Re=S*10,ke="/pocket-tts",Te=new URL("data:text/javascript;base64,Y29uc29sZS5pbmZvKCJbUG9ja2V0Qm9vdHN0cmFwXSB3b3JrZXIgbW9kdWxlIHN0YXJ0aW5nIik7CgpzZWxmLmFkZEV2ZW50TGlzdGVuZXIoImVycm9yIiwgKGV2ZW50KSA9PiB7CiAgY29uc29sZS5lcnJvcigiW1BvY2tldEJvb3RzdHJhcF0gZXJyb3IgZXZlbnQiLCB7CiAgICBtZXNzYWdlOiBldmVudC5tZXNzYWdlLAogICAgZmlsZW5hbWU6IGV2ZW50LmZpbGVuYW1lLAogICAgbGluZW5vOiBldmVudC5saW5lbm8sCiAgICBjb2xubzogZXZlbnQuY29sbm8sCiAgfSk7Cn0pOwoKc2VsZi5hZGRFdmVudExpc3RlbmVyKCJ1bmhhbmRsZWRyZWplY3Rpb24iLCAoZXZlbnQpID0+IHsKICBjb25zb2xlLmVycm9yKCJbUG9ja2V0Qm9vdHN0cmFwXSB1bmhhbmRsZWQgcmVqZWN0aW9uIiwgZXZlbnQucmVhc29uKTsKfSk7Cgpjb25zdCBwb3N0Qm9vdHN0cmFwRXJyb3IgPSAoZXJyb3IpID0+IHsKICBjb25zdCBtZXNzYWdlID0KICAgIGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBgJHtlcnJvci5uYW1lfTogJHtlcnJvci5tZXNzYWdlfWAgOiBTdHJpbmcoZXJyb3IpOwoKICBzZWxmLnBvc3RNZXNzYWdlKHsKICAgIHR5cGU6ICJlcnJvciIsCiAgICBlcnJvcjogYFBvY2tldCBib290c3RyYXAgZmFpbGVkOiAke21lc3NhZ2V9YCwKICB9KTsKfTsKCmNvbnN0IHF1ZXVlZE1lc3NhZ2VzID0gW107CmxldCB2ZW5kb3JNZXNzYWdlSGFuZGxlciA9IG51bGw7CmxldCB2ZW5kb3JXb3JrZXJVcmwgPSBudWxsOwpsZXQgcmVzb2x2ZVZlbmRvcldvcmtlclVybCA9IG51bGw7CmNvbnN0IHZlbmRvcldvcmtlclVybFByb21pc2UgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gewogIHJlc29sdmVWZW5kb3JXb3JrZXJVcmwgPSByZXNvbHZlOwp9KTsKCmNvbnN0IGZvcndhcmRPclF1ZXVlTWVzc2FnZSA9IChldmVudCkgPT4gewogIGlmICgKICAgIHZlbmRvcldvcmtlclVybCA9PSBudWxsICYmCiAgICB0eXBlb2YgZXZlbnQuZGF0YSA9PT0gIm9iamVjdCIgJiYKICAgIGV2ZW50LmRhdGEgIT09IG51bGwgJiYKICAgIGV2ZW50LmRhdGEudHlwZSA9PT0gImxvYWQiICYmCiAgICB0eXBlb2YgZXZlbnQuZGF0YS5kYXRhPy52ZW5kb3JXb3JrZXJVcmwgPT09ICJzdHJpbmciCiAgKSB7CiAgICB2ZW5kb3JXb3JrZXJVcmwgPSBldmVudC5kYXRhLmRhdGEudmVuZG9yV29ya2VyVXJsOwogICAgcmVzb2x2ZVZlbmRvcldvcmtlclVybD8uKHZlbmRvcldvcmtlclVybCk7CiAgfQoKICBpZiAodmVuZG9yTWVzc2FnZUhhbmRsZXIpIHsKICAgIHZvaWQgdmVuZG9yTWVzc2FnZUhhbmRsZXIoZXZlbnQpOwogICAgcmV0dXJuOwogIH0KCiAgcXVldWVkTWVzc2FnZXMucHVzaChldmVudC5kYXRhKTsKICBjb25zb2xlLmluZm8oIltQb2NrZXRCb290c3RyYXBdIHF1ZXVlZCBtZXNzYWdlIiwgewogICAgdHlwZToKICAgICAgdHlwZW9mIGV2ZW50LmRhdGEgPT09ICJvYmplY3QiICYmCiAgICAgIGV2ZW50LmRhdGEgIT09IG51bGwgJiYKICAgICAgInR5cGUiIGluIGV2ZW50LmRhdGEKICAgICAgICA/IGV2ZW50LmRhdGEudHlwZQogICAgICAgIDogInVua25vd24iLAogIH0pOwp9OwoKc2VsZi5hZGRFdmVudExpc3RlbmVyKCJtZXNzYWdlIiwgZm9yd2FyZE9yUXVldWVNZXNzYWdlKTsKCnZvaWQgKGFzeW5jICgpID0+IHsKICB0cnkgewogICAgaWYgKHZlbmRvcldvcmtlclVybCA9PSBudWxsKSB7CiAgICAgIHZlbmRvcldvcmtlclVybCA9IGF3YWl0IHZlbmRvcldvcmtlclVybFByb21pc2U7CiAgICB9CiAgICBpZiAoIXZlbmRvcldvcmtlclVybCkgewogICAgICB0aHJvdyBuZXcgRXJyb3IoIk1pc3NpbmcgdmVuZG9yIHdvcmtlciBVUkwuIik7CiAgICB9CiAgICBjb25zb2xlLmluZm8oIltQb2NrZXRCb290c3RyYXBdIGltcG9ydGluZyB2ZW5kb3Igd29ya2VyIik7CiAgICBhd2FpdCBpbXBvcnQoLyogQHZpdGUtaWdub3JlICovIHZlbmRvcldvcmtlclVybCk7CiAgICBjb25zb2xlLmluZm8oIltQb2NrZXRCb290c3RyYXBdIHZlbmRvciB3b3JrZXIgaW1wb3J0ZWQiKTsKCiAgICBpZiAodHlwZW9mIHNlbGYub25tZXNzYWdlICE9PSAiZnVuY3Rpb24iKSB7CiAgICAgIHRocm93IG5ldyBFcnJvcigiVmVuZG9yIHdvcmtlciBkaWQgbm90IGluc3RhbGwgYSBtZXNzYWdlIGhhbmRsZXIuIik7CiAgICB9CgogICAgdmVuZG9yTWVzc2FnZUhhbmRsZXIgPSBzZWxmLm9ubWVzc2FnZS5iaW5kKHNlbGYpOwogICAgY29uc29sZS5pbmZvKCJbUG9ja2V0Qm9vdHN0cmFwXSB2ZW5kb3IgaGFuZGxlciByZWFkeSIsIHsKICAgICAgcXVldWVkTWVzc2FnZXM6IHF1ZXVlZE1lc3NhZ2VzLmxlbmd0aCwKICAgIH0pOwoKICAgIHdoaWxlIChxdWV1ZWRNZXNzYWdlcy5sZW5ndGggPiAwKSB7CiAgICAgIGNvbnN0IGRhdGEgPSBxdWV1ZWRNZXNzYWdlcy5zaGlmdCgpOwogICAgICBhd2FpdCB2ZW5kb3JNZXNzYWdlSGFuZGxlcigKICAgICAgICBuZXcgTWVzc2FnZUV2ZW50KCJtZXNzYWdlIiwgewogICAgICAgICAgZGF0YSwKICAgICAgICB9KSwKICAgICAgKTsKICAgIH0KICB9IGNhdGNoIChlcnJvcikgewogICAgY29uc29sZS5lcnJvcigiW1BvY2tldEJvb3RzdHJhcF0gdmVuZG9yIHdvcmtlciBpbXBvcnQgZmFpbGVkIiwgZXJyb3IpOwogICAgcG9zdEJvb3RzdHJhcEVycm9yKGVycm9yKTsKICAgIHRocm93IGVycm9yOwogIH0KfSkoKTsKCmV4cG9ydCB7fTsK",import.meta.url),J=()=>{let r,e;return{promise:new Promise((n,i)=>{r=n,e=i}),resolve:r,reject:e}},Y=r=>r?r.startsWith("https://")||r.startsWith("http://")?r.replace(/\/+$/,""):r.startsWith("/")||r.startsWith("./")||r.startsWith("../")?r.replace(/\/+$/,""):`https://huggingface.co/spaces/${r.replace(/^spaces\//,"")}/resolve/main`:ke,Pe=r=>r.startsWith("https://")||r.startsWith("http://")?r:new URL(r,globalThis.location.href).toString(),Ze=()=>{if(typeof navigator>"u")return!1;const r=navigator.userAgentData?.platform??navigator.platform??navigator.userAgent??"";return/win/i.test(r)},Ee=r=>{if(r.numberOfChannels===1)return r.getChannelData(0).slice();const e=r.length,t=new Float32Array(e);for(let n=0;n<r.numberOfChannels;n+=1){const i=r.getChannelData(n);for(let s=0;s<e;s+=1)t[s]+=i[s]/r.numberOfChannels}return t},Me=(r,e,t)=>{if(e===t)return r;const n=e/t,i=Math.max(1,Math.round(r.length/n)),s=new Float32Array(i);for(let a=0;a<i;a+=1){const o=a*n,c=Math.floor(o),l=Math.min(c+1,r.length-1),u=o-c;s[a]=r[c]*(1-u)+r[l]*u}return s},We=r=>{const e=r.reduce((o,c)=>o+c.length,0),t=new Float32Array(e);let n=0;for(const o of r)t.set(o,n),n+=o.length;const i=new ArrayBuffer(44+t.length*2),s=new DataView(i),a=(o,c)=>{for(let l=0;l<c.length;l+=1)s.setUint8(o+l,c.charCodeAt(l))};a(0,"RIFF"),s.setUint32(4,36+t.length*2,!0),a(8,"WAVE"),a(12,"fmt "),s.setUint32(16,16,!0),s.setUint16(20,1,!0),s.setUint16(22,1,!0),s.setUint32(24,S,!0),s.setUint32(28,S*2,!0),s.setUint16(32,2,!0),s.setUint16(34,16,!0),a(36,"data"),s.setUint32(40,t.length*2,!0);for(let o=0;o<t.length;o+=1){const c=Math.max(-1,Math.min(1,t[o]??0));s.setInt16(44+o*2,c<0?c*32768:c*32767,!0)}return new Blob([i],{type:"audio/wav"})};class Ve{#e=null;#t=null;#r=null;#i=null;#s=null;#n=null;#a=Y();#l=!1;#o=!1;#h=null;#u=1;#m=!1;#d=null;#c="Worker not started.";async initialize(e){return this.#a=Y(e.modelId),this.#u=this.#v(),this.#d=e.onProgress??null,this.#r?this.#r:(this.#r=this.#w().catch(t=>{throw this.#r=null,t}),this.#r)}async warmup(e={}){await this.initialize({modelId:this.#a,onProgress:e.onProgress}),!this.#l&&(e.onProgress?.("Compiling voice runtime..."),await this.#f("Benchmark."),this.#l=!0,e.onProgress?.("Voice runtime warmed."))}async bootstrapFromUtterance(e){if(await this.initialize({modelId:this.#a}),!this.#e)throw new Error("Pocket TTS worker failed to initialize.");const t=await this.#I(e),n=J();return this.#s=n,this.#e.postMessage({type:"encode_voice",data:{audio:t}},[t.buffer]),await n.promise,this.#o=!0,{embeddingId:`custom-${Date.now()}`}}async synthesize(e){if(await this.initialize({modelId:this.#a}),e.signal?.aborted)throw new Error("Speech synthesis was cancelled.");if(!this.#o&&e.referenceAudio&&await this.bootstrapFromUtterance(e.referenceAudio),!this.#o&&!this.#h)throw new Error("No Pocket TTS voice is ready.");return this.#f(e.text,e.signal)}async stream(e){if(await this.initialize({modelId:this.#a}),e.signal?.aborted)throw new Error("Speech synthesis was cancelled.");if(!this.#o&&e.referenceAudio&&await this.bootstrapFromUtterance(e.referenceAudio),!this.#o&&!this.#h)throw new Error("No Pocket TTS voice is ready.");await this.#C(e.text,e.onAudioChunk,e.signal)}async#w(){try{await this.#p()}catch(e){if(this.#u>1&&!this.#m){console.warn("Pocket TTS worker failed with multithreaded WASM. Retrying single-threaded.",e),this.#m=!0,this.#u=1,this.#S(),await this.#p();return}throw e}}async#p(){this.#i=J(),this.#c=`Starting worker (${this.#u} thread${this.#u===1?"":"s"})`,console.info("[PocketRuntime] starting worker",{modelBaseUrl:this.#a,threadCount:this.#u,crossOriginIsolated:globalThis.crossOriginIsolated===!0}),this.#e=new Worker(Te,{type:"module"}),this.#e.onmessage=n=>{this.#y(n.data)},this.#e.onmessageerror=n=>{console.error("[PocketRuntime] worker message error",n),this.#g(new Error(`Pocket TTS worker message error. Last status: ${this.#c}.`))},this.#e.onerror=n=>{const i=[n.filename,n.lineno,n.colno].filter(Boolean).join(":"),s=new Error([n.message||"Pocket TTS worker crashed.",i?`at ${i}`:null,`last status: ${this.#c}`,`thread count: ${this.#u}`].filter(Boolean).join(" "));console.error("[PocketRuntime] worker crashed",{message:n.message,filename:n.filename,lineno:n.lineno,colno:n.colno,lastStatus:this.#c,threadCount:this.#u}),this.#g(s)},console.info("[PocketRuntime] posting load request",{modelBaseUrl:this.#a,threadCount:this.#u});const e=Pe(this.#a),t=new URL("/pocket-tts/inference-worker.js",globalThis.location.href).toString();return this.#e.postMessage({type:"load",data:{modelBaseUrl:e,vendorWorkerUrl:t,threadCount:this.#u}}),this.#i.promise}#y(e){switch(e.type==="status"&&e.status&&(this.#c=e.status,console.info("[PocketRuntime] worker status",e.status,e.state)),e.type){case"loaded":this.#c="Worker loaded",console.info("[PocketRuntime] worker loaded"),this.#i?.resolve(),this.#i=null;break;case"voices_loaded":this.#c=`Voices loaded (${e.defaultVoice??"none"})`,this.#h=e.defaultVoice??null,this.#h&&this.#d?.(`Voice ready (${this.#h}).`);break;case"voice_encoded":this.#c=`Voice encoded (${e.voiceName??"custom"})`,this.#s?.resolve(),this.#s=null,this.#o=!0,this.#d?.("Voice profile encoded.");break;case"voice_set":this.#c=`Voice selected (${e.voiceName??"unknown"})`,e.voiceName==="custom"&&(this.#o=!0),e.voiceName&&this.#d?.(`Voice selected (${e.voiceName}).`);break;case"audio_chunk":if(this.#n?.chunks&&e.data){const t=this.#n,n=Float32Array.from(e.data);t.mode==="blob"&&t.chunks.push(n),t.onAudioChunk&&(t.chunkTask=t.chunkTask.then(()=>t.onAudioChunk?.(n)).catch(i=>{this.#g(i instanceof Error?i:new Error("Pocket TTS audio streaming failed."))}))}break;case"stream_ended":if(this.#c="Audio stream ended",this.#n){const t=this.#n;this.#n=null,t.abortCleanup?.(),t.chunkTask.finally(()=>{if(t.mode==="blob"){t.resolve(We(t.chunks));return}t.resolve()})}break;case"error":this.#c=e.error||"Pocket TTS failed",console.error("[PocketRuntime] worker error",e.error),this.#g(new Error(e.error||"Pocket TTS failed."));break;case"status":e.status&&this.#d?.(e.status);break}}#g(e){if(console.error("[PocketRuntime] rejecting pending work",e),this.#i?.reject(e),this.#i=null,this.#s?.reject(e),this.#s=null,this.#n){const t=this.#n;this.#n=null,t.abortCleanup?.(),t.reject(e)}this.#e?.terminate(),this.#e=null}async#f(e,t){return new Promise((n,i)=>{this.#b({text:e,signal:t,mode:"blob",resolve:n,reject:i})})}async#C(e,t,n){return new Promise((i,s)=>{this.#b({text:e,signal:n,mode:"stream",resolve:i,reject:s,onAudioChunk:t})})}#b(e){if(!this.#e)throw new Error("Pocket TTS worker failed to initialize.");if(this.#n)throw new Error("Pocket TTS only supports one generation at a time.");const t={mode:e.mode,chunks:[],chunkTask:Promise.resolve(),onAudioChunk:e.onAudioChunk,resolve:e.resolve,reject:e.reject};if(e.signal){const n=()=>{this.#n===t&&(this.#e?.postMessage({type:"stop"}),this.#n=null,t.abortCleanup?.(),e.reject(new Error("Speech synthesis was cancelled.")))};e.signal.addEventListener("abort",n,{once:!0}),t.abortCleanup=()=>{e.signal?.removeEventListener("abort",n)}}this.#n=t,this.#e.postMessage({type:"generate",data:{text:e.text}})}async#I(e){const t=await this.#A(),n=await e.arrayBuffer(),i=await t.decodeAudioData(n.slice(0)),s=Ee(i);return Me(s,i.sampleRate,S).slice(0,Re)}async#A(){return this.#t?this.#t:(this.#t=new AudioContext({sampleRate:S}),this.#t)}#S(){this.#e?.terminate(),this.#e=null,this.#i=null,this.#s=null,this.#n=null,this.#c="Worker reset"}#v(){return typeof navigator>"u"||globalThis.crossOriginIsolated!==!0?1:Ze()?(this.#d?.("Voice runtime is using single-threaded mode on Windows for compatibility."),1):Math.min(navigator.hardwareConcurrency||4,8)}}const xe=r=>({source:r?"cached":"first-utterance",ready:!0,referenceAudioKey:r??"first-utterance",embeddingCacheKey:r,lastUpdatedAt:Date.now()});class Be{constructor(e){this.modelId=e}#e=null;#t=null;#r=null;async initialize(e={}){if(this.#e){e.onProgress?.("Voice runtime ready.");return}const t=typeof window<"u"?window.__PRIVATE_VOICE_POCKET_TTS__??(this.#e??=new Ve):null;if(!t)throw new Error("Speech runtime is unavailable in this browser environment.");await t.initialize?.({modelId:this.modelId,onProgress:e.onProgress}),this.#e=t}async warmup(e={}){await this.initialize(e),await this.#e?.warmup?.({onProgress:e.onProgress})}async bootstrapFromUtterance(e){if(await this.initialize(),!this.#e)throw new Error("Speech runtime is unavailable.");this.#t=e;const t=await this.#e.bootstrapFromUtterance(e),n=xe(t.embeddingId??null);return this.#r=n,{voiceProfile:n}}async synthesize(e){if(await this.initialize(),e.signal?.aborted)throw new Error("Speech synthesis was cancelled.");if(!this.#e)throw new Error("Speech runtime is unavailable.");const t=e.referenceAudio??this.#t;if(!t)throw new Error("No voice reference is available for synthesis.");return this.#e.synthesize({text:e.text,signal:e.signal,referenceAudio:t})}async synthesizeStream(e){if(await this.initialize(),e.signal?.aborted)throw new Error("Speech synthesis was cancelled.");if(!this.#e?.stream)throw new Error("Streaming speech synthesis is unavailable.");const t=e.referenceAudio??this.#t;if(!t&&!this.#r?.ready)throw new Error("No voice reference is available for synthesis.");await this.#e.stream({text:e.text,signal:e.signal,referenceAudio:t,onAudioChunk:e.onAudioChunk})}}const Le=.024,ze=900,Fe=12e3;class A{#e=null;#t=null;#r=null;#i=null;#s=null;async ensureMicrophonePermission(){try{return await this.#n(),"granted"}catch{return"denied"}}async listenForUtterance(e={}){const t=await this.#n();if(typeof MediaRecorder>"u")throw new Error("MediaRecorder is unavailable in this browser.");const n=A.#a(),i=n?new MediaRecorder(t,{mimeType:n}):new MediaRecorder(t);this.#s=i;const s=[];let a=!1,o=0,c=0,l=0;i.ondataavailable=m=>{m.data.size>0&&s.push(m.data)};const u=()=>{cancelAnimationFrame(l),i.state!=="inactive"&&i.stop()};e.signal?.addEventListener("abort",()=>{u()},{once:!0});const d=new Float32Array(this.#r?.fftSize??2048),h=()=>{if(!this.#r){l=requestAnimationFrame(h);return}this.#r.getFloatTimeDomainData(d);const m=A.#l(d),p=performance.now();if(m>Le&&(a=!0,o=p),a&&o>0&&p-o>=ze){u();return}if(!a&&p-c>=Fe){u();return}l=requestAnimationFrame(h)},g=await new Promise((m,p)=>{i.onstop=()=>{if(cancelAnimationFrame(l),s.length===0){p(new Error("No audio was captured."));return}m(new Blob(s,{type:i.mimeType||"audio/webm"}))},i.onerror=()=>{cancelAnimationFrame(l),p(new Error("Audio capture failed."))},c=performance.now(),i.start(250),l=requestAnimationFrame(h)});return this.#s=null,g}async recordForDuration(e){const t=await this.#n();if(typeof MediaRecorder>"u")throw new Error("MediaRecorder is unavailable in this browser.");const n=A.#a(),i=n?new MediaRecorder(t,{mimeType:n}):new MediaRecorder(t);this.#s=i;const s=[],a=Math.max(1e3,e.durationMs);let o=0,c=0,l=0,u=!1;i.ondataavailable=g=>{g.data.size>0&&s.push(g.data)};const d=(g,m)=>{e.onProgress?.({progress:Math.max(0,Math.min(1,g/a)),elapsedMs:g,remainingMs:Math.max(0,a-g),isRecording:m})},h=()=>{window.clearTimeout(o),window.clearInterval(c),i.state!=="inactive"&&i.stop()};e.signal?.addEventListener("abort",()=>{u=!0,h()},{once:!0});try{return await new Promise((g,m)=>{i.onstop=()=>{if(window.clearTimeout(o),window.clearInterval(c),u){m(new Error("Microphone calibration was cancelled."));return}if(s.length===0){m(new Error("No microphone calibration audio was captured."));return}d(a,!1),g(new Blob(s,{type:i.mimeType||"audio/webm"}))},i.onerror=()=>{window.clearTimeout(o),window.clearInterval(c),m(new Error("Microphone calibration recording failed."))},l=performance.now(),d(0,!0),i.start(250),o=window.setTimeout(()=>{h()},a),c=window.setInterval(()=>{const p=Math.min(a,performance.now()-l);d(p,!0)},100)})}finally{this.#s=null}}stop(){this.#s&&this.#s.state!=="inactive"&&this.#s.stop()}async dispose(){this.stop(),this.#e?.getTracks().forEach(e=>e.stop()),this.#e=null,this.#i?.disconnect(),this.#i=null,this.#r?.disconnect(),this.#r=null,await this.#t?.close(),this.#t=null}async#n(){if(this.#e)return this.#e;const e=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}});return this.#e=e,this.#t=new AudioContext,this.#i=this.#t.createMediaStreamSource(e),this.#r=this.#t.createAnalyser(),this.#r.fftSize=2048,this.#i.connect(this.#r),e}static#a(){const e=["audio/webm;codecs=opus","audio/mp4","audio/webm"];for(const t of e)if(MediaRecorder.isTypeSupported(t))return t}static#l(e){let t=0;for(const n of e)t+=n*n;return Math.sqrt(t/e.length)}}const Ge=r=>r.hasMediaDevices?r.hasWebGPU?r.hasAudioWorklet?{...r,canRunDemo:!0,failureReason:null}:{...r,canRunDemo:!1,failureReason:"AudioWorklet support is unavailable, so live capture is unreliable."}:{...r,canRunDemo:!1,failureReason:"WebGPU is unavailable, so local model execution cannot start."}:{...r,canRunDemo:!1,failureReason:"This browser does not expose microphone capture APIs."};class Ne{async detect(){const e={hasWebGPU:typeof navigator<"u"&&"gpu"in navigator,hasMediaDevices:typeof navigator<"u"&&typeof navigator.mediaDevices?.getUserMedia=="function",hasAudioWorklet:typeof window<"u"&&typeof window.AudioWorkletNode<"u",hasCrossOriginIsolation:typeof globalThis<"u"&&globalThis.crossOriginIsolated===!0};return Ge(e)}}const W="private-voice-agent/session";class Ue{#e;constructor(e=typeof localStorage<"u"?localStorage:null){this.#e=e}load(){if(!this.#e)return null;const e=this.#e.getItem(W);if(!e)return null;try{const t=JSON.parse(e);return t.version!==$?(this.clear(),null):t}catch{return this.clear(),null}}save(e){this.#e&&this.#e.setItem(W,JSON.stringify(e))}clear(){this.#e?.removeItem(W)}}const R=24e3,Xe=.45,_e=()=>{let r,e;return{promise:new Promise((n,i)=>{r=n,e=i}),resolve:r,reject:e}};class Oe{#e=null;#t=0;#r=!1;#i=!1;#s=!1;#n=new Set;#a=[];#l=0;#o=_e();#h;constructor(e={}){this.#h=e.onEnded??null,e.signal?.addEventListener("abort",()=>{this.stop()},{once:!0})}async enqueue(e){this.#r||this.#i||e.length===0||(this.#a.push(e),this.#l+=e.length/R,this.#u())}async finish(){return this.#r||this.#i?this.#o.promise:(this.#i=!0,this.#u(!0),this.#d(),this.#o.promise)}stop(){if(!this.#r){this.#r=!0,this.#a=[],this.#l=0;for(const e of this.#n)try{e.stop()}catch{}this.#n.clear(),this.#d()}}#u(e=!1){if(!(this.#a.length===0||this.#r)&&!(!this.#s&&!e&&this.#l<Xe))for(this.#s=!0;this.#a.length>0;){const t=this.#a.shift();t&&(this.#l=Math.max(0,this.#l-t.length/R),this.#m(t))}}#m(e){const t=this.#c();t.state==="suspended"&&t.resume().catch(()=>{});const n=t.createBuffer(1,e.length,R);n.getChannelData(0).set(e);const i=t.createBufferSource();i.buffer=n,i.connect(t.destination);const s=Math.max(this.#t,t.currentTime+.02);this.#t=s+n.duration,i.onended=()=>{this.#n.delete(i),this.#d()},this.#n.add(i),i.start(s)}#d(){if(!(this.#r||this.#i&&this.#a.length===0&&this.#n.size===0))return;this.#h?.(),this.#h=null;const t=this.#e;this.#e=null,t&&t.state!=="closed"&&t.close().catch(()=>{}),this.#o.resolve()}#c(){return this.#e?this.#e:(this.#e=new AudioContext({sampleRate:R}),this.#e)}}class Ke{#e=null;#t=null;#r=null;#i=null;#s=null;async play(e,t={}){this.stop();const n=URL.createObjectURL(e),i=new Audio(n);this.#e=i,this.#t=n;const s=()=>{this.#e===i&&(this.#e=null),this.#t===n&&(URL.revokeObjectURL(n),this.#t=null),this.#i===s&&(this.#i=null),this.#r&&(this.#r=null)};this.#i=s,t.signal&&t.signal.addEventListener("abort",()=>{this.stop()},{once:!0}),await new Promise((a,o)=>{this.#r=()=>{t.onEnded?.(),s(),a()},i.onended=()=>{this.#r?.()},i.onerror=()=>{s(),o(new Error("Audio playback failed."))},i.play().catch(c=>{s(),o(c)})})}createStream(e={}){return this.stop(),this.#s=new Oe(e),this.#s}stop(){this.#s?.stop(),this.#s=null,this.#e&&(this.#e.pause(),this.#e.currentTime=0,this.#e=null);const e=this.#i,t=this.#r;this.#i=null,this.#r=null,e?.(),t?.()}}const w=new he(Q()),C=new we(w,{capabilities:new Ne,audioCapture:new A,asr:new Ae(w.getState().config.asrModelId),llm:new T(w.getState().config.llmModelId),tts:new Be(w.getState().config.ttsModelId),playback:new Ke,persistence:new Ue});let b="calibration";const ee=document.querySelector("#calibration-view"),te=document.querySelector("#assistant-view"),P=document.querySelector("#calibration-button"),re=document.querySelector("#calibration-copy"),ie=document.querySelector("#calibration-prompt"),ne=document.querySelector("#calibration-fill"),se=document.querySelector("#calibration-timer"),V=document.querySelector("#benchmark-summary"),x=document.querySelector("#calibration-error"),k=document.querySelector("#transcript-log"),I=document.querySelector("#transcript-empty"),Z=document.querySelector("#record-button"),B=document.querySelector("#assistant-error");if(!ee||!te||!P||!re||!ie||!ne||!se||!V||!x||!k||!I||!Z||!B)throw new Error("The demo shell is missing required DOM nodes.");const z=()=>{const r=w.getState();if(ee.hidden=b!=="calibration",te.hidden=b!=="assistant",re.textContent=r.runtimeReady?"Calibration is complete. Continue when you're ready to open the assistant.":r.phase==="benchmarking"?"Calibration is running now. Keep reading the prompt clearly until the timer ends.":ce,ie.textContent=le,ne.style.width=`${Math.round(r.calibrationProgress*100)}%`,se.textContent=r.phase==="benchmarking"?r.calibrationRecording?`${r.calibrationSecondsRemaining??0}s remaining`:"Finalizing calibration...":r.runtimeReady?"Calibration complete.":"Calibration required.",V.hidden=!r.benchmarkSummary,V.textContent=r.benchmarkSummary??"",P.disabled=r.phase==="benchmarking",P.textContent=r.phase==="benchmarking"?"Calibrating...":r.runtimeReady?"Continue":"Calibrate",k.replaceChildren(),r.turns.length===0)I.hidden=!1,I.textContent=r.runtimeReady?'Press Record and say "Hello, my name is _____."':"Complete calibration first.",k.append(I);else{I.hidden=!0;for(const e of r.turns){const t=document.createElement("article");t.className="turn",t.dataset.role=e.role;const n=document.createElement("div");n.className="turn-header",n.innerHTML=`<span>${e.role}</span><span>${e.audioStatus}</span>`;const i=document.createElement("div");i.className="turn-body",i.textContent=e.transcript||"…",t.append(n,i),k.append(t)}}Z.disabled=!r.runtimeReady||r.phase==="benchmarking",Z.textContent=r.phase==="listening"||r.phase==="arming"?"Stop Recording":r.phase==="thinking"||r.phase==="speaking"?"Stop":"Record",x.hidden=b!=="calibration"||!r.errorMessage,x.textContent=b==="calibration"?r.errorMessage??"":"",B.hidden=b!=="assistant"||!r.errorMessage,B.textContent=b==="assistant"?r.errorMessage??"":""};P.addEventListener("click",()=>{if(C.setConsentAccepted(!0),w.getState().runtimeReady){b="assistant",z();return}C.runBenchmark()});Z.addEventListener("click",()=>{C.setConsentAccepted(!0),C.start()});window.addEventListener("beforeunload",()=>{C.dispose()});w.subscribe(()=>{z()});C.bootstrap().then(()=>{z()});
 
1
+ (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))i(n);new MutationObserver(n=>{for(const s of n)if(s.type==="childList")for(const a of s.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&i(a)}).observe(document,{childList:!0,subtree:!0});function t(n){const s={};return n.integrity&&(s.integrity=n.integrity),n.referrerPolicy&&(s.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?s.credentials="include":n.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function i(n){if(n.ep)return;n.ep=!0;const s=t(n);fetch(n.href,s)}})();const Y=1,$=20,ae=$*1e3,oe="Read the following lines clearly at a comfortable speaking pace until calibration is complete. Repeat from the beginning if time remains.",ce=`The quick brown fox jumps over the lazy dog.
2
  I am speaking in my natural voice at a comfortable volume.
3
  The sound of my own voice has never bothered me.
4
  A recording is never quite the same as the original.
5
  I confirm this is a true representation of how I speak.
6
  My speech remains clear when I maintain a consistent distance from the microphone.
7
  I will continue speaking until the calibration timer reaches zero.
8
+ This sample should capture my normal pronunciation and pacing.`,le=()=>({hasWebGPU:!1,hasMediaDevices:!1,hasAudioWorklet:!1,hasCrossOriginIsolation:!1,canRunDemo:!1,failureReason:"Checking browser capabilities."}),L=()=>({source:"first-utterance",ready:!1,referenceAudioKey:null,embeddingCacheKey:null,lastUpdatedAt:null}),ue=()=>({llmModelId:"Qwen2.5-0.5B-Instruct-q4f16_1-MLC",asrModelId:"Xenova/whisper-tiny.en",ttsModelId:"/pocket-tts",maxTurnsPersisted:8,targetFirstAudioMs:2500}),q=(r=ue())=>({capability:le(),phase:"idle",micPermission:"unknown",consentAccepted:!1,firstContactComplete:!1,turns:[],voiceProfile:L(),statusText:"Idle",errorMessage:null,runtimeReady:!1,supportsResume:!1,lastOpenedAt:null,calibrationProgress:0,calibrationSecondsRemaining:null,calibrationRecording:!1,benchmarkSummary:null,config:r}),U=r=>r.capability.canRunDemo?r.runtimeReady?r.capability.hasCrossOriginIsolation?"Ready":"Ready (single-threaded)":"Run Microphone Calibration":r.capability.failureReason??"Unsupported";class de{#e;#t=new Set;constructor(e=q()){this.#e=e}getState(){return this.#e}subscribe(e){return this.#t.add(e),e(this.#e),()=>{this.#t.delete(e)}}setState(e){const t=this.#e,i=typeof e=="function"?e(t):e;if(i!==t){this.#e=i;for(const n of this.#t)n(this.#e)}}update(e){this.setState(t=>({...t,...e}))}appendTurn(e){this.setState(t=>({...t,turns:[...t.turns,e]}))}updateTurn(e,t){this.setState(i=>({...i,turns:i.turns.map(n=>n.id===e?{...n,...t}:n)}))}hydrate(e){this.setState(t=>({...t,turns:e.turns,voiceProfile:e.voiceProfile,supportsResume:e.turns.length>0||e.voiceProfile.ready,lastOpenedAt:e.lastOpenedAt,firstContactComplete:e.turns.some(i=>i.isFirstContact),statusText:U(t)}))}setVoiceProfile(e){this.update({voiceProfile:e})}clearConversation(){this.setState(e=>({...e,phase:"idle",firstContactComplete:!1,turns:[],voiceProfile:e.runtimeReady?e.voiceProfile:L(),errorMessage:null,statusText:U(e),supportsResume:!1,lastOpenedAt:null,calibrationProgress:e.runtimeReady?1:0,calibrationSecondsRemaining:e.runtimeReady?0:null,calibrationRecording:!1}))}toPersistedSession(){const e=this.getState();return{version:Y,turns:e.turns.slice(-e.config.maxTurnsPersisted),voiceProfile:e.voiceProfile,lastOpenedAt:Date.now()}}}const X=(r,e,t={})=>({id:crypto.randomUUID(),role:r,transcript:e,audioStatus:t.audioStatus??"none",createdAt:Date.now(),isFirstContact:t.isFirstContact??!1}),O=160,he=24,me=520,ge=r=>r.trim().split(/\s+/).filter(Boolean).length,pe=r=>{const e=/[.!?]+["')\]]*(?=\s|$)/.exec(r);return!e||e.index==null?-1:e.index+e[0].length},fe=r=>{if(r.length<O||ge(r)<he)return-1;const e=r.lastIndexOf(" ");return e>=Math.floor(O*.94)?e:-1},_=(r,e=!1)=>{let t=r.replace(/^\s+/,"");const i=[];for(;t.trim();){const n=pe(t);if(n>0){const a=t.slice(0,n).trim();a&&i.push(a),t=t.slice(n).replace(/^\s+/,"");continue}if(!e)break;const s=t.trim();s&&i.push(s),t="";break}return{segments:i,remainder:t}},v=r=>{const e=r.replace(/^\s+/,""),t=fe(e);return t<=0?{segment:null,remainder:e}:{segment:e.slice(0,t).trim(),remainder:e.slice(t).replace(/^\s+/,"")}};class be{#e;#t;#r=null;#i=null;constructor(e,t){this.#e=e,this.#t=t}get state(){return this.#e.getState()}async bootstrap(){const e=await this.#t.capabilities.detect();this.#e.update({capability:e,runtimeReady:!1,statusText:e.canRunDemo?"Run Microphone Calibration":"Unsupported"});const t=this.#t.persistence.load();t&&this.#e.hydrate(t)}setConsentAccepted(e){this.#e.update({consentAccepted:e})}async runBenchmark(){const e=this.#e.getState();let t=null;const i=n=>{this.#e.getState().phase==="benchmarking"&&this.#e.update({calibrationProgress:n.progress,calibrationSecondsRemaining:Math.max(0,Math.ceil(n.remainingMs/1e3)),calibrationRecording:n.isRecording})};if(e.phase!=="benchmarking"){if(!e.consentAccepted){this.#e.update({errorMessage:"Allow microphone access and local audio processing to calibrate the system.",phase:"error",statusText:"Consent required",runtimeReady:!1});return}if(!e.capability.canRunDemo){this.#e.update({errorMessage:e.capability.failureReason,phase:"error",statusText:"Unsupported",runtimeReady:!1});return}this.#e.update({phase:"benchmarking",statusText:"Microphone calibration",errorMessage:null,runtimeReady:!1,benchmarkSummary:null,calibrationProgress:0,calibrationSecondsRemaining:$,calibrationRecording:!1,voiceProfile:L()});try{const n=await this.#t.audioCapture.ensureMicrophonePermission();if(this.#e.update({micPermission:n}),n!=="granted")throw new Error("Microphone access was denied. Retry when the browser permission is available.");const s=new AbortController,a=(async()=>{await this.#t.asr.initialize(),await this.#t.asr.warmup?.(),await this.#t.llm.initialize(),await this.#t.llm.warmup?.(),await this.#t.tts.initialize(),await this.#t.tts.warmup?.()})().catch(l=>{t=l,s.abort()});this.#e.update({statusText:"Microphone calibration",calibrationRecording:!0});const o=await this.#t.audioCapture.recordForDuration({durationMs:ae,signal:s.signal,onProgress:i});if(t)throw t;this.#e.update({calibrationRecording:!1,calibrationProgress:1,calibrationSecondsRemaining:0,statusText:"Finalizing calibration"}),await a,await this.#t.asr.transcribe(o),this.#e.update({statusText:"Finalizing calibration"});const c=await this.#t.tts.bootstrapFromUtterance(o);this.#e.setVoiceProfile(c.voiceProfile),this.#e.update({phase:"idle",statusText:e.capability.hasCrossOriginIsolation?"Ready":"Ready (single-threaded)",runtimeReady:!0,errorMessage:null,benchmarkSummary:"Calibration ready. Captured 20 seconds of local audio and completed the transcription check."})}catch(n){const s=t??n;this.#e.update({phase:"error",statusText:"Calibration failed",runtimeReady:!1,calibrationRecording:!1,errorMessage:s instanceof Error?s.message:"Microphone calibration failed."})}}}async start(){const e=this.#e.getState();if(e.phase==="arming"||e.phase==="listening"||e.phase==="thinking"||e.phase==="speaking"){this.#o();return}if(!e.consentAccepted){this.#e.update({errorMessage:"Allow microphone access and local audio processing to begin.",phase:"error",statusText:"Consent required"});return}if(!e.capability.canRunDemo){this.#e.update({errorMessage:e.capability.failureReason,phase:"error",statusText:"Unsupported"});return}if(!e.runtimeReady){this.#e.update({phase:"error",statusText:"Calibration required",errorMessage:"Run Microphone Calibration before starting a session."});return}this.#r?.abort(),this.#r=new AbortController,this.#e.update({errorMessage:null,phase:"arming",statusText:"Arming microphone"});const t=await this.#t.audioCapture.ensureMicrophonePermission();if(this.#e.update({micPermission:t}),t!=="granted"){this.#e.update({phase:"error",statusText:"Microphone denied",errorMessage:"Microphone access was denied. Retry when the browser permission is available."});return}this.#i=this.#s(this.#r.signal),this.#i}async clear(){this.#o(),this.#t.persistence.clear(),this.#e.clearConversation()}async dispose(){await this.clear(),await this.#t.audioCapture.dispose()}async#s(e){try{this.#e.update({phase:"listening",statusText:"Listening",errorMessage:null});const t=await this.#t.audioCapture.listenForUtterance({signal:e});if(e.aborted)return;await this.#n(t,e)}catch(t){if(e.aborted)return;this.#e.update({phase:"error",statusText:"Runtime failed",errorMessage:t instanceof Error?t.message:"Audio or model runtime failed."})}finally{this.#r?.signal===e&&(this.#r=null),this.#i&&(this.#i=null)}}async#n(e,t){this.#e.update({phase:"thinking",statusText:"Transcribing",errorMessage:null});const i=await this.#t.asr.transcribe(e),n=X("user",i.text);this.#e.appendTurn(n);const s=this.#e.getState().turns;let a=this.#e.getState().voiceProfile;const o=a.ready?Promise.resolve({voiceProfile:a}):this.#t.tts.bootstrapFromUtterance(e);a.ready||this.#e.update({statusText:"Thinking"});const c=X("assistant","",{audioStatus:"queued",isFirstContact:!this.#e.getState().firstContactComplete});this.#e.appendTurn(c),this.#e.update({statusText:"Thinking"});const l=this.#t.playback.createStream({signal:t,onEnded:()=>{this.#e.updateTurn(c.id,{audioStatus:"done"})}});let u="",d=null,h=!1,g=Promise.resolve();const m=async()=>{if(a.ready)return;a=(await o).voiceProfile,this.#e.setVoiceProfile(a)},p=f=>{const y=f.trim();y&&(g=g.then(async()=>{t.aborted||(await m(),await this.#t.tts.synthesizeStream({text:y,signal:t,onAudioChunk:async W=>{h||(h=!0,this.#e.update({phase:"speaking",statusText:"Speaking"}),this.#e.updateTurn(c.id,{audioStatus:"playing"})),await l.enqueue(W)}}))}))},G=()=>{d!=null&&(globalThis.clearTimeout(d),d=null)},F=()=>{d!=null||t.aborted||(d=globalThis.setTimeout(()=>{d=null;const f=v(u);f.segment&&(u=f.remainder,p(f.segment),v(u).segment&&F())},me))},se=await this.#t.llm.generate({turns:s,signal:t,onChunk:f=>{this.#e.updateTurn(c.id,{transcript:`${this.#a(c.id)}${f}`}),u+=f;const y=_(u);u=y.remainder,y.segments.length>0&&G();for(const W of y.segments)p(W);v(u).segment&&F()}});this.#e.updateTurn(c.id,{transcript:se}),G();const E=v(u);E.segment&&(u=E.remainder,p(E.segment));const N=_(u,!0);u=N.remainder;for(const f of N.segments)p(f);await m(),await g,await l.finish(),h||this.#e.updateTurn(c.id,{audioStatus:"done"}),this.#e.update({firstContactComplete:!0,phase:"idle",statusText:this.#l()}),this.#t.persistence.save(this.#e.toPersistedSession())}#a(e){return this.#e.getState().turns.find(t=>t.id===e)?.transcript??""}#l(){return this.#e.getState().capability.hasCrossOriginIsolation?"Ready":"Ready (single-threaded)"}#o(){this.#r?.abort(),this.#r=null,this.#t.audioCapture.stop(),this.#t.playback.stop(),this.#e.update({phase:"idle",statusText:this.#l()})}}const we="modulepreload",ye=function(r){return"/"+r},K={},Q=function(e,t,i){let n=Promise.resolve();if(t&&t.length>0){let c=function(l){return Promise.all(l.map(u=>Promise.resolve(u).then(d=>({status:"fulfilled",value:d}),d=>({status:"rejected",reason:d}))))};document.getElementsByTagName("link");const a=document.querySelector("meta[property=csp-nonce]"),o=a?.nonce||a?.getAttribute("nonce");n=c(t.map(l=>{if(l=ye(l),l in K)return;K[l]=!0;const u=l.endsWith(".css"),d=u?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${l}"]${d}`))return;const h=document.createElement("link");if(h.rel=u?"stylesheet":we,u||(h.as="script"),h.crossOrigin="",h.href=l,o&&h.setAttribute("nonce",o),document.head.appendChild(h),u)return new Promise((g,m)=>{h.addEventListener("load",g),h.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${l}`)))})}))}function s(a){const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=a,window.dispatchEvent(o),!o.defaultPrevented)throw a}return n.then(a=>{for(const o of a||[])o.status==="rejected"&&s(o.reason);return e().catch(s)})},Ce=r=>{const e=r.userAgent??"",t=r.userAgentData?.brands??[];return r.brave||/\bBrave\//i.test(e)?!0:t.some(i=>/brave/i.test(i.brand))},Ie=r=>{const e=r instanceof Error?`${r.name}: ${r.message}`:String(r);return/(shadermodule|webgpu|wgsl|compute stage|gpu|reshape\d+_kernel)/i.test(e)},Ae=r=>{switch(r.status){case"initiate":return`Preparing ${r.file??"speech model"}...`;case"download":return`Downloading ${r.file??"speech model"}...`;case"progress":return`Downloading ${r.file??"speech model"} (${Math.round(r.progress??0)}%)`;case"done":return`Loaded ${r.file??"speech model"}.`;case"ready":return`Speech model ready (${r.model??r.task??"loaded"}).`;default:return"Loading speech model..."}};class Se{constructor(e){this.modelId=e}#e=null;#t="wasm";async initialize(e={}){if(this.#e)return;const t=typeof navigator<"u"&&"gpu"in navigator&&!Ce(navigator)?"webgpu":"wasm";try{await this.#r(t,e)}catch(i){if(t!=="webgpu")throw i;console.warn("ASR WebGPU initialization failed, falling back to WASM.",i),e.onProgress?.("Speech WebGPU setup failed. Falling back to WASM..."),await this.#r("wasm",e)}e.onProgress?.(`Speech ready on ${this.#t.toUpperCase()}.`)}async warmup(e={}){await this.initialize(e),e.onProgress?.("Speech runtime warmed.")}async transcribe(e){if(await this.initialize(),!this.#e)throw new Error("ASR pipeline failed to initialize.");const t=URL.createObjectURL(e);try{const i=await this.#i(t);if(!i.trim())throw new Error("Speech recognition returned an empty transcript.");return{text:i.trim()}}finally{URL.revokeObjectURL(t)}}getDevice(){return this.#t}async#r(e,t={}){const{env:i,pipeline:n}=await Q(async()=>{const{env:s,pipeline:a}=await import("./transformers.web-Cny4Tgyw.js");return{env:s,pipeline:a}},[]);i.allowLocalModels=!1,i.useFS=!1,i.useFSCache=!1,i.useBrowserCache=!0,t.onProgress?.(`Loading ${this.modelId} on ${e.toUpperCase()}...`),this.#e=await n("automatic-speech-recognition",this.modelId,{device:e,dtype:e==="webgpu"?"fp32":"q8",progress_callback:s=>{t.onProgress?.(Ae(s))}}),this.#t=e}async#i(e){if(!this.#e)throw new Error("ASR pipeline failed to initialize.");try{const t=await this.#e(e);return typeof t=="string"?t:t.text??""}catch(t){if(this.#t!=="webgpu"||!Ie(t))throw t;if(console.warn("ASR WebGPU transcription failed, retrying on WASM.",t),this.#e=null,await this.#r("wasm"),!this.#e)throw new Error("ASR WASM fallback failed to initialize.");const i=await this.#e(e);return typeof i=="string"?i:i.text??""}}}const D=`You are a deadpan browser voice agent demo.
9
 
10
  Respond directly, briefly, and clearly. Sound like a capable local assistant, not a character.
11
  Do not mention that you are part of a game, experiment, fiction, or narrative.
12
  Do not call attention to the user's cloned voice.
13
  Do not add theatrical flourishes or emotional commentary unless the user explicitly asks for it.
14
+ `,ve=["Qwen2.5-0.5B-Instruct-q4f16_1-MLC","SmolLM2-360M-Instruct-q4f16_1-MLC","Llama-3.2-1B-Instruct-q4f16_1-MLC","Qwen2.5-1.5B-Instruct-q4f16_1-MLC"],Re=r=>{const e=typeof r.progress=="number"?`${Math.round(r.progress*100)}%`:null;return r.text&&e?`${r.text} (${e})`:r.text?r.text:e?`Loading language model (${e})`:"Loading language model..."};class T{constructor(e){this.preferredModelId=e}#e=null;#t=null;async initialize(e={}){if(this.#e){e.onProgress?.(`Language ready (${this.#t??this.preferredModelId??"model loaded"}).`);return}const t=await Q(()=>import("./index-DFLBea7M.js"),[]),i=this.preferredModelId??T.#i(t);e.onProgress?.(`Preparing ${i}...`),this.#e=await t.CreateMLCEngine(i,{initProgressCallback:n=>{e.onProgress?.(Re(n))}}),this.#t=i,e.onProgress?.(`Language ready (${i}).`)}async generate(e){if(await this.initialize(),!this.#e)throw new Error("LLM engine failed to initialize.");const t=T.#r(e.turns),i=await this.#e.chat.completions.create({messages:t,temperature:.7,stream:!0});let n="";for await(const s of i){if(e.signal?.aborted)break;const a=s.choices[0]?.delta?.content??"";a&&(n+=a,e.onChunk(a))}return n.trim()}async warmup(e={}){if(await this.initialize(e),!this.#e)throw new Error("LLM engine failed to initialize.");e.onProgress?.("Compiling first language response..."),await this.#e.chat.completions.create({messages:[{role:"system",content:D},{role:"user",content:"Reply with one short word."}],temperature:0,max_tokens:1}),e.onProgress?.("Language runtime warmed.")}static#r(e){const t=[{role:"system",content:D}];for(const i of e)i.role==="system"||!i.transcript.trim()||t.push({role:i.role,content:i.transcript});return t}static#i(e){const i=(e.prebuiltAppConfig?.model_list??[]).map(s=>{const a="model_id"in s?s.model_id:"";return{candidate:s,modelId:a}}).filter(s=>!!s.modelId);for(const s of ve){const a=i.find(o=>o.modelId.includes(s));if(a)return a.modelId}const n=i.at(0)?.modelId;if(!n)throw new Error("WebLLM did not expose any prebuilt models.");return n}}const S=24e3,ke=S*10,Te="/pocket-tts",Pe=new URL("data:text/javascript;base64,Y29uc29sZS5pbmZvKCJbUG9ja2V0Qm9vdHN0cmFwXSB3b3JrZXIgbW9kdWxlIHN0YXJ0aW5nIik7CgpzZWxmLmFkZEV2ZW50TGlzdGVuZXIoImVycm9yIiwgKGV2ZW50KSA9PiB7CiAgY29uc29sZS5lcnJvcigiW1BvY2tldEJvb3RzdHJhcF0gZXJyb3IgZXZlbnQiLCB7CiAgICBtZXNzYWdlOiBldmVudC5tZXNzYWdlLAogICAgZmlsZW5hbWU6IGV2ZW50LmZpbGVuYW1lLAogICAgbGluZW5vOiBldmVudC5saW5lbm8sCiAgICBjb2xubzogZXZlbnQuY29sbm8sCiAgfSk7Cn0pOwoKc2VsZi5hZGRFdmVudExpc3RlbmVyKCJ1bmhhbmRsZWRyZWplY3Rpb24iLCAoZXZlbnQpID0+IHsKICBjb25zb2xlLmVycm9yKCJbUG9ja2V0Qm9vdHN0cmFwXSB1bmhhbmRsZWQgcmVqZWN0aW9uIiwgZXZlbnQucmVhc29uKTsKfSk7Cgpjb25zdCBwb3N0Qm9vdHN0cmFwRXJyb3IgPSAoZXJyb3IpID0+IHsKICBjb25zdCBtZXNzYWdlID0KICAgIGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBgJHtlcnJvci5uYW1lfTogJHtlcnJvci5tZXNzYWdlfWAgOiBTdHJpbmcoZXJyb3IpOwoKICBzZWxmLnBvc3RNZXNzYWdlKHsKICAgIHR5cGU6ICJlcnJvciIsCiAgICBlcnJvcjogYFBvY2tldCBib290c3RyYXAgZmFpbGVkOiAke21lc3NhZ2V9YCwKICB9KTsKfTsKCmNvbnN0IHF1ZXVlZE1lc3NhZ2VzID0gW107CmxldCB2ZW5kb3JNZXNzYWdlSGFuZGxlciA9IG51bGw7CmxldCB2ZW5kb3JXb3JrZXJVcmwgPSBudWxsOwpsZXQgcmVzb2x2ZVZlbmRvcldvcmtlclVybCA9IG51bGw7CmNvbnN0IHZlbmRvcldvcmtlclVybFByb21pc2UgPSBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gewogIHJlc29sdmVWZW5kb3JXb3JrZXJVcmwgPSByZXNvbHZlOwp9KTsKCmNvbnN0IGZvcndhcmRPclF1ZXVlTWVzc2FnZSA9IChldmVudCkgPT4gewogIGlmICgKICAgIHZlbmRvcldvcmtlclVybCA9PSBudWxsICYmCiAgICB0eXBlb2YgZXZlbnQuZGF0YSA9PT0gIm9iamVjdCIgJiYKICAgIGV2ZW50LmRhdGEgIT09IG51bGwgJiYKICAgIGV2ZW50LmRhdGEudHlwZSA9PT0gImxvYWQiICYmCiAgICB0eXBlb2YgZXZlbnQuZGF0YS5kYXRhPy52ZW5kb3JXb3JrZXJVcmwgPT09ICJzdHJpbmciCiAgKSB7CiAgICB2ZW5kb3JXb3JrZXJVcmwgPSBldmVudC5kYXRhLmRhdGEudmVuZG9yV29ya2VyVXJsOwogICAgcmVzb2x2ZVZlbmRvcldvcmtlclVybD8uKHZlbmRvcldvcmtlclVybCk7CiAgfQoKICBpZiAodmVuZG9yTWVzc2FnZUhhbmRsZXIpIHsKICAgIHZvaWQgdmVuZG9yTWVzc2FnZUhhbmRsZXIoZXZlbnQpOwogICAgcmV0dXJuOwogIH0KCiAgcXVldWVkTWVzc2FnZXMucHVzaChldmVudC5kYXRhKTsKICBjb25zb2xlLmluZm8oIltQb2NrZXRCb290c3RyYXBdIHF1ZXVlZCBtZXNzYWdlIiwgewogICAgdHlwZToKICAgICAgdHlwZW9mIGV2ZW50LmRhdGEgPT09ICJvYmplY3QiICYmCiAgICAgIGV2ZW50LmRhdGEgIT09IG51bGwgJiYKICAgICAgInR5cGUiIGluIGV2ZW50LmRhdGEKICAgICAgICA/IGV2ZW50LmRhdGEudHlwZQogICAgICAgIDogInVua25vd24iLAogIH0pOwp9OwoKc2VsZi5hZGRFdmVudExpc3RlbmVyKCJtZXNzYWdlIiwgZm9yd2FyZE9yUXVldWVNZXNzYWdlKTsKCnZvaWQgKGFzeW5jICgpID0+IHsKICB0cnkgewogICAgaWYgKHZlbmRvcldvcmtlclVybCA9PSBudWxsKSB7CiAgICAgIHZlbmRvcldvcmtlclVybCA9IGF3YWl0IHZlbmRvcldvcmtlclVybFByb21pc2U7CiAgICB9CiAgICBpZiAoIXZlbmRvcldvcmtlclVybCkgewogICAgICB0aHJvdyBuZXcgRXJyb3IoIk1pc3NpbmcgdmVuZG9yIHdvcmtlciBVUkwuIik7CiAgICB9CiAgICBjb25zb2xlLmluZm8oIltQb2NrZXRCb290c3RyYXBdIGltcG9ydGluZyB2ZW5kb3Igd29ya2VyIik7CiAgICBhd2FpdCBpbXBvcnQoLyogQHZpdGUtaWdub3JlICovIHZlbmRvcldvcmtlclVybCk7CiAgICBjb25zb2xlLmluZm8oIltQb2NrZXRCb290c3RyYXBdIHZlbmRvciB3b3JrZXIgaW1wb3J0ZWQiKTsKCiAgICBpZiAodHlwZW9mIHNlbGYub25tZXNzYWdlICE9PSAiZnVuY3Rpb24iKSB7CiAgICAgIHRocm93IG5ldyBFcnJvcigiVmVuZG9yIHdvcmtlciBkaWQgbm90IGluc3RhbGwgYSBtZXNzYWdlIGhhbmRsZXIuIik7CiAgICB9CgogICAgdmVuZG9yTWVzc2FnZUhhbmRsZXIgPSBzZWxmLm9ubWVzc2FnZS5iaW5kKHNlbGYpOwogICAgY29uc29sZS5pbmZvKCJbUG9ja2V0Qm9vdHN0cmFwXSB2ZW5kb3IgaGFuZGxlciByZWFkeSIsIHsKICAgICAgcXVldWVkTWVzc2FnZXM6IHF1ZXVlZE1lc3NhZ2VzLmxlbmd0aCwKICAgIH0pOwoKICAgIHdoaWxlIChxdWV1ZWRNZXNzYWdlcy5sZW5ndGggPiAwKSB7CiAgICAgIGNvbnN0IGRhdGEgPSBxdWV1ZWRNZXNzYWdlcy5zaGlmdCgpOwogICAgICBhd2FpdCB2ZW5kb3JNZXNzYWdlSGFuZGxlcigKICAgICAgICBuZXcgTWVzc2FnZUV2ZW50KCJtZXNzYWdlIiwgewogICAgICAgICAgZGF0YSwKICAgICAgICB9KSwKICAgICAgKTsKICAgIH0KICB9IGNhdGNoIChlcnJvcikgewogICAgY29uc29sZS5lcnJvcigiW1BvY2tldEJvb3RzdHJhcF0gdmVuZG9yIHdvcmtlciBpbXBvcnQgZmFpbGVkIiwgZXJyb3IpOwogICAgcG9zdEJvb3RzdHJhcEVycm9yKGVycm9yKTsKICAgIHRocm93IGVycm9yOwogIH0KfSkoKTsKCmV4cG9ydCB7fTsK",import.meta.url),H=()=>{let r,e;return{promise:new Promise((i,n)=>{r=i,e=n}),resolve:r,reject:e}},J=r=>r?r.startsWith("https://")||r.startsWith("http://")?r.replace(/\/+$/,""):r.startsWith("/")||r.startsWith("./")||r.startsWith("../")?r.replace(/\/+$/,""):`https://huggingface.co/spaces/${r.replace(/^spaces\//,"")}/resolve/main`:Te,Ze=r=>r.startsWith("https://")||r.startsWith("http://")?r:new URL(r,globalThis.location.href).toString(),Ee=()=>{if(typeof navigator>"u")return!1;const r=navigator.userAgentData?.platform??navigator.platform??navigator.userAgent??"";return/win/i.test(r)},We=r=>{if(r.numberOfChannels===1)return r.getChannelData(0).slice();const e=r.length,t=new Float32Array(e);for(let i=0;i<r.numberOfChannels;i+=1){const n=r.getChannelData(i);for(let s=0;s<e;s+=1)t[s]+=n[s]/r.numberOfChannels}return t},Me=(r,e,t)=>{if(e===t)return r;const i=e/t,n=Math.max(1,Math.round(r.length/i)),s=new Float32Array(n);for(let a=0;a<n;a+=1){const o=a*i,c=Math.floor(o),l=Math.min(c+1,r.length-1),u=o-c;s[a]=r[c]*(1-u)+r[l]*u}return s},Ve=r=>{const e=r.reduce((o,c)=>o+c.length,0),t=new Float32Array(e);let i=0;for(const o of r)t.set(o,i),i+=o.length;const n=new ArrayBuffer(44+t.length*2),s=new DataView(n),a=(o,c)=>{for(let l=0;l<c.length;l+=1)s.setUint8(o+l,c.charCodeAt(l))};a(0,"RIFF"),s.setUint32(4,36+t.length*2,!0),a(8,"WAVE"),a(12,"fmt "),s.setUint32(16,16,!0),s.setUint16(20,1,!0),s.setUint16(22,1,!0),s.setUint32(24,S,!0),s.setUint32(28,S*2,!0),s.setUint16(32,2,!0),s.setUint16(34,16,!0),a(36,"data"),s.setUint32(40,t.length*2,!0);for(let o=0;o<t.length;o+=1){const c=Math.max(-1,Math.min(1,t[o]??0));s.setInt16(44+o*2,c<0?c*32768:c*32767,!0)}return new Blob([n],{type:"audio/wav"})};class xe{#e=null;#t=null;#r=null;#i=null;#s=null;#n=null;#a=J();#l=!1;#o=!1;#h=null;#u=1;#m=!1;#d=null;#c="Worker not started.";async initialize(e){return this.#a=J(e.modelId),this.#u=this.#v(),this.#d=e.onProgress??null,this.#r?this.#r:(this.#r=this.#w().catch(t=>{throw this.#r=null,t}),this.#r)}async warmup(e={}){await this.initialize({modelId:this.#a,onProgress:e.onProgress}),!this.#l&&(e.onProgress?.("Compiling voice runtime..."),await this.#f("Benchmark."),this.#l=!0,e.onProgress?.("Voice runtime warmed."))}async bootstrapFromUtterance(e){if(await this.initialize({modelId:this.#a}),!this.#e)throw new Error("Pocket TTS worker failed to initialize.");const t=await this.#I(e),i=H();return this.#s=i,this.#e.postMessage({type:"encode_voice",data:{audio:t}},[t.buffer]),await i.promise,this.#o=!0,{embeddingId:`custom-${Date.now()}`}}async synthesize(e){if(await this.initialize({modelId:this.#a}),e.signal?.aborted)throw new Error("Speech synthesis was cancelled.");if(!this.#o&&e.referenceAudio&&await this.bootstrapFromUtterance(e.referenceAudio),!this.#o&&!this.#h)throw new Error("No Pocket TTS voice is ready.");return this.#f(e.text,e.signal)}async stream(e){if(await this.initialize({modelId:this.#a}),e.signal?.aborted)throw new Error("Speech synthesis was cancelled.");if(!this.#o&&e.referenceAudio&&await this.bootstrapFromUtterance(e.referenceAudio),!this.#o&&!this.#h)throw new Error("No Pocket TTS voice is ready.");await this.#C(e.text,e.onAudioChunk,e.signal)}async#w(){try{await this.#p()}catch(e){if(this.#u>1&&!this.#m){console.warn("Pocket TTS worker failed with multithreaded WASM. Retrying single-threaded.",e),this.#m=!0,this.#u=1,this.#S(),await this.#p();return}throw e}}async#p(){this.#i=H(),this.#c=`Starting worker (${this.#u} thread${this.#u===1?"":"s"})`,console.info("[PocketRuntime] starting worker",{modelBaseUrl:this.#a,threadCount:this.#u,crossOriginIsolated:globalThis.crossOriginIsolated===!0}),this.#e=new Worker(Pe,{type:"module"}),this.#e.onmessage=i=>{this.#y(i.data)},this.#e.onmessageerror=i=>{console.error("[PocketRuntime] worker message error",i),this.#g(new Error(`Pocket TTS worker message error. Last status: ${this.#c}.`))},this.#e.onerror=i=>{const n=[i.filename,i.lineno,i.colno].filter(Boolean).join(":"),s=new Error([i.message||"Pocket TTS worker crashed.",n?`at ${n}`:null,`last status: ${this.#c}`,`thread count: ${this.#u}`].filter(Boolean).join(" "));console.error("[PocketRuntime] worker crashed",{message:i.message,filename:i.filename,lineno:i.lineno,colno:i.colno,lastStatus:this.#c,threadCount:this.#u}),this.#g(s)},console.info("[PocketRuntime] posting load request",{modelBaseUrl:this.#a,threadCount:this.#u});const e=Ze(this.#a),t=new URL("/pocket-tts/inference-worker.js",globalThis.location.href).toString();return this.#e.postMessage({type:"load",data:{modelBaseUrl:e,vendorWorkerUrl:t,threadCount:this.#u}}),this.#i.promise}#y(e){switch(e.type==="status"&&e.status&&(this.#c=e.status,console.info("[PocketRuntime] worker status",e.status,e.state)),e.type){case"loaded":this.#c="Worker loaded",console.info("[PocketRuntime] worker loaded"),this.#i?.resolve(),this.#i=null;break;case"voices_loaded":this.#c=`Voices loaded (${e.defaultVoice??"none"})`,this.#h=e.defaultVoice??null,this.#h&&this.#d?.(`Voice ready (${this.#h}).`);break;case"voice_encoded":this.#c=`Voice encoded (${e.voiceName??"custom"})`,this.#s?.resolve(),this.#s=null,this.#o=!0,this.#d?.("Voice profile encoded.");break;case"voice_set":this.#c=`Voice selected (${e.voiceName??"unknown"})`,e.voiceName==="custom"&&(this.#o=!0),e.voiceName&&this.#d?.(`Voice selected (${e.voiceName}).`);break;case"audio_chunk":if(this.#n?.chunks&&e.data){const t=this.#n,i=Float32Array.from(e.data);t.mode==="blob"&&t.chunks.push(i),t.onAudioChunk&&(t.chunkTask=t.chunkTask.then(()=>t.onAudioChunk?.(i)).catch(n=>{this.#g(n instanceof Error?n:new Error("Pocket TTS audio streaming failed."))}))}break;case"stream_ended":if(this.#c="Audio stream ended",this.#n){const t=this.#n;this.#n=null,t.abortCleanup?.(),t.chunkTask.finally(()=>{if(t.mode==="blob"){t.resolve(Ve(t.chunks));return}t.resolve()})}break;case"error":this.#c=e.error||"Pocket TTS failed",console.error("[PocketRuntime] worker error",e.error),this.#g(new Error(e.error||"Pocket TTS failed."));break;case"status":e.status&&this.#d?.(e.status);break}}#g(e){if(console.error("[PocketRuntime] rejecting pending work",e),this.#i?.reject(e),this.#i=null,this.#s?.reject(e),this.#s=null,this.#n){const t=this.#n;this.#n=null,t.abortCleanup?.(),t.reject(e)}this.#e?.terminate(),this.#e=null}async#f(e,t){return new Promise((i,n)=>{this.#b({text:e,signal:t,mode:"blob",resolve:i,reject:n})})}async#C(e,t,i){return new Promise((n,s)=>{this.#b({text:e,signal:i,mode:"stream",resolve:n,reject:s,onAudioChunk:t})})}#b(e){if(!this.#e)throw new Error("Pocket TTS worker failed to initialize.");if(this.#n)throw new Error("Pocket TTS only supports one generation at a time.");const t={mode:e.mode,chunks:[],chunkTask:Promise.resolve(),onAudioChunk:e.onAudioChunk,resolve:e.resolve,reject:e.reject};if(e.signal){const i=()=>{this.#n===t&&(this.#e?.postMessage({type:"stop"}),this.#n=null,t.abortCleanup?.(),e.reject(new Error("Speech synthesis was cancelled.")))};e.signal.addEventListener("abort",i,{once:!0}),t.abortCleanup=()=>{e.signal?.removeEventListener("abort",i)}}this.#n=t,this.#e.postMessage({type:"generate",data:{text:e.text}})}async#I(e){const t=await this.#A(),i=await e.arrayBuffer(),n=await t.decodeAudioData(i.slice(0)),s=We(n);return Me(s,n.sampleRate,S).slice(0,ke)}async#A(){return this.#t?this.#t:(this.#t=new AudioContext({sampleRate:S}),this.#t)}#S(){this.#e?.terminate(),this.#e=null,this.#i=null,this.#s=null,this.#n=null,this.#c="Worker reset"}#v(){return typeof navigator>"u"||globalThis.crossOriginIsolated!==!0?1:Ee()?(this.#d?.("Voice runtime is using single-threaded mode on Windows for compatibility."),1):Math.min(navigator.hardwareConcurrency||4,8)}}const Be=r=>({source:r?"cached":"first-utterance",ready:!0,referenceAudioKey:r??"first-utterance",embeddingCacheKey:r,lastUpdatedAt:Date.now()});class Le{constructor(e){this.modelId=e}#e=null;#t=null;#r=null;async initialize(e={}){if(this.#e){e.onProgress?.("Voice runtime ready.");return}const t=typeof window<"u"?window.__PRIVATE_VOICE_POCKET_TTS__??(this.#e??=new xe):null;if(!t)throw new Error("Speech runtime is unavailable in this browser environment.");await t.initialize?.({modelId:this.modelId,onProgress:e.onProgress}),this.#e=t}async warmup(e={}){await this.initialize(e),await this.#e?.warmup?.({onProgress:e.onProgress})}async bootstrapFromUtterance(e){if(await this.initialize(),!this.#e)throw new Error("Speech runtime is unavailable.");this.#t=e;const t=await this.#e.bootstrapFromUtterance(e),i=Be(t.embeddingId??null);return this.#r=i,{voiceProfile:i}}async synthesize(e){if(await this.initialize(),e.signal?.aborted)throw new Error("Speech synthesis was cancelled.");if(!this.#e)throw new Error("Speech runtime is unavailable.");const t=e.referenceAudio??this.#t;if(!t)throw new Error("No voice reference is available for synthesis.");return this.#e.synthesize({text:e.text,signal:e.signal,referenceAudio:t})}async synthesizeStream(e){if(await this.initialize(),e.signal?.aborted)throw new Error("Speech synthesis was cancelled.");if(!this.#e?.stream)throw new Error("Streaming speech synthesis is unavailable.");const t=e.referenceAudio??this.#t;if(!t&&!this.#r?.ready)throw new Error("No voice reference is available for synthesis.");await this.#e.stream({text:e.text,signal:e.signal,referenceAudio:t,onAudioChunk:e.onAudioChunk})}}const ze=.024,Ge=900,Fe=12e3;class A{#e=null;#t=null;#r=null;#i=null;#s=null;async ensureMicrophonePermission(){try{return await this.#n(),"granted"}catch{return"denied"}}async listenForUtterance(e={}){const t=await this.#n();if(typeof MediaRecorder>"u")throw new Error("MediaRecorder is unavailable in this browser.");const i=A.#a(),n=i?new MediaRecorder(t,{mimeType:i}):new MediaRecorder(t);this.#s=n;const s=[];let a=!1,o=0,c=0,l=0;n.ondataavailable=m=>{m.data.size>0&&s.push(m.data)};const u=()=>{cancelAnimationFrame(l),n.state!=="inactive"&&n.stop()};e.signal?.addEventListener("abort",()=>{u()},{once:!0});const d=new Float32Array(this.#r?.fftSize??2048),h=()=>{if(!this.#r){l=requestAnimationFrame(h);return}this.#r.getFloatTimeDomainData(d);const m=A.#l(d),p=performance.now();if(m>ze&&(a=!0,o=p),a&&o>0&&p-o>=Ge){u();return}if(!a&&p-c>=Fe){u();return}l=requestAnimationFrame(h)},g=await new Promise((m,p)=>{n.onstop=()=>{if(cancelAnimationFrame(l),s.length===0){p(new Error("No audio was captured."));return}m(new Blob(s,{type:n.mimeType||"audio/webm"}))},n.onerror=()=>{cancelAnimationFrame(l),p(new Error("Audio capture failed."))},c=performance.now(),n.start(250),l=requestAnimationFrame(h)});return this.#s=null,g}async recordForDuration(e){const t=await this.#n();if(typeof MediaRecorder>"u")throw new Error("MediaRecorder is unavailable in this browser.");const i=A.#a(),n=i?new MediaRecorder(t,{mimeType:i}):new MediaRecorder(t);this.#s=n;const s=[],a=Math.max(1e3,e.durationMs);let o=0,c=0,l=0,u=!1;n.ondataavailable=g=>{g.data.size>0&&s.push(g.data)};const d=(g,m)=>{e.onProgress?.({progress:Math.max(0,Math.min(1,g/a)),elapsedMs:g,remainingMs:Math.max(0,a-g),isRecording:m})},h=()=>{window.clearTimeout(o),window.clearInterval(c),n.state!=="inactive"&&n.stop()};e.signal?.addEventListener("abort",()=>{u=!0,h()},{once:!0});try{return await new Promise((g,m)=>{n.onstop=()=>{if(window.clearTimeout(o),window.clearInterval(c),u){m(new Error("Microphone calibration was cancelled."));return}if(s.length===0){m(new Error("No microphone calibration audio was captured."));return}d(a,!1),g(new Blob(s,{type:n.mimeType||"audio/webm"}))},n.onerror=()=>{window.clearTimeout(o),window.clearInterval(c),m(new Error("Microphone calibration recording failed."))},l=performance.now(),d(0,!0),n.start(250),o=window.setTimeout(()=>{h()},a),c=window.setInterval(()=>{const p=Math.min(a,performance.now()-l);d(p,!0)},100)})}finally{this.#s=null}}stop(){this.#s&&this.#s.state!=="inactive"&&this.#s.stop()}async dispose(){this.stop(),this.#e?.getTracks().forEach(e=>e.stop()),this.#e=null,this.#i?.disconnect(),this.#i=null,this.#r?.disconnect(),this.#r=null,await this.#t?.close(),this.#t=null}async#n(){if(this.#e)return this.#e;const e=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}});return this.#e=e,this.#t=new AudioContext,this.#i=this.#t.createMediaStreamSource(e),this.#r=this.#t.createAnalyser(),this.#r.fftSize=2048,this.#i.connect(this.#r),e}static#a(){const e=["audio/webm;codecs=opus","audio/mp4","audio/webm"];for(const t of e)if(MediaRecorder.isTypeSupported(t))return t}static#l(e){let t=0;for(const i of e)t+=i*i;return Math.sqrt(t/e.length)}}const Ne=r=>r.hasMediaDevices?r.hasWebGPU?r.hasAudioWorklet?{...r,canRunDemo:!0,failureReason:null}:{...r,canRunDemo:!1,failureReason:"AudioWorklet support is unavailable, so live capture is unreliable."}:{...r,canRunDemo:!1,failureReason:"WebGPU is unavailable, so local model execution cannot start."}:{...r,canRunDemo:!1,failureReason:"This browser does not expose microphone capture APIs."};class Ue{async detect(){const e={hasWebGPU:typeof navigator<"u"&&"gpu"in navigator,hasMediaDevices:typeof navigator<"u"&&typeof navigator.mediaDevices?.getUserMedia=="function",hasAudioWorklet:typeof window<"u"&&typeof window.AudioWorkletNode<"u",hasCrossOriginIsolation:typeof globalThis<"u"&&globalThis.crossOriginIsolated===!0};return Ne(e)}}const M="private-voice-agent/session";class Xe{#e;constructor(e=typeof localStorage<"u"?localStorage:null){this.#e=e}load(){if(!this.#e)return null;const e=this.#e.getItem(M);if(!e)return null;try{const t=JSON.parse(e);return t.version!==Y?(this.clear(),null):t}catch{return this.clear(),null}}save(e){this.#e&&this.#e.setItem(M,JSON.stringify(e))}clear(){this.#e?.removeItem(M)}}const R=24e3,Oe=.45,_e=()=>{let r,e;return{promise:new Promise((i,n)=>{r=i,e=n}),resolve:r,reject:e}};class Ke{#e=null;#t=0;#r=!1;#i=!1;#s=!1;#n=new Set;#a=[];#l=0;#o=_e();#h;constructor(e={}){this.#h=e.onEnded??null,e.signal?.addEventListener("abort",()=>{this.stop()},{once:!0})}async enqueue(e){this.#r||this.#i||e.length===0||(this.#a.push(e),this.#l+=e.length/R,this.#u())}async finish(){return this.#r||this.#i?this.#o.promise:(this.#i=!0,this.#u(!0),this.#d(),this.#o.promise)}stop(){if(!this.#r){this.#r=!0,this.#a=[],this.#l=0;for(const e of this.#n)try{e.stop()}catch{}this.#n.clear(),this.#d()}}#u(e=!1){if(!(this.#a.length===0||this.#r)&&!(!this.#s&&!e&&this.#l<Oe))for(this.#s=!0;this.#a.length>0;){const t=this.#a.shift();t&&(this.#l=Math.max(0,this.#l-t.length/R),this.#m(t))}}#m(e){const t=this.#c();t.state==="suspended"&&t.resume().catch(()=>{});const i=t.createBuffer(1,e.length,R);i.getChannelData(0).set(e);const n=t.createBufferSource();n.buffer=i,n.connect(t.destination);const s=Math.max(this.#t,t.currentTime+.02);this.#t=s+i.duration,n.onended=()=>{this.#n.delete(n),this.#d()},this.#n.add(n),n.start(s)}#d(){if(!(this.#r||this.#i&&this.#a.length===0&&this.#n.size===0))return;this.#h?.(),this.#h=null;const t=this.#e;this.#e=null,t&&t.state!=="closed"&&t.close().catch(()=>{}),this.#o.resolve()}#c(){return this.#e?this.#e:(this.#e=new AudioContext({sampleRate:R}),this.#e)}}class De{#e=null;#t=null;#r=null;#i=null;#s=null;async play(e,t={}){this.stop();const i=URL.createObjectURL(e),n=new Audio(i);this.#e=n,this.#t=i;const s=()=>{this.#e===n&&(this.#e=null),this.#t===i&&(URL.revokeObjectURL(i),this.#t=null),this.#i===s&&(this.#i=null),this.#r&&(this.#r=null)};this.#i=s,t.signal&&t.signal.addEventListener("abort",()=>{this.stop()},{once:!0}),await new Promise((a,o)=>{this.#r=()=>{t.onEnded?.(),s(),a()},n.onended=()=>{this.#r?.()},n.onerror=()=>{s(),o(new Error("Audio playback failed."))},n.play().catch(c=>{s(),o(c)})})}createStream(e={}){return this.stop(),this.#s=new Ke(e),this.#s}stop(){this.#s?.stop(),this.#s=null,this.#e&&(this.#e.pause(),this.#e.currentTime=0,this.#e=null);const e=this.#i,t=this.#r;this.#i=null,this.#r=null,e?.(),t?.()}}const w=new de(q()),C=new be(w,{capabilities:new Ue,audioCapture:new A,asr:new Se(w.getState().config.asrModelId),llm:new T(w.getState().config.llmModelId),tts:new Le(w.getState().config.ttsModelId),playback:new De,persistence:new Xe});let b="calibration";const j=document.querySelector("#calibration-view"),ee=document.querySelector("#assistant-view"),P=document.querySelector("#calibration-button"),te=document.querySelector("#calibration-copy"),re=document.querySelector("#calibration-prompt"),ie=document.querySelector("#calibration-fill"),ne=document.querySelector("#calibration-timer"),V=document.querySelector("#benchmark-summary"),x=document.querySelector("#calibration-error"),k=document.querySelector("#transcript-log"),I=document.querySelector("#transcript-empty"),Z=document.querySelector("#record-button"),B=document.querySelector("#assistant-error");if(!j||!ee||!P||!te||!re||!ie||!ne||!V||!x||!k||!I||!Z||!B)throw new Error("The demo shell is missing required DOM nodes.");const z=()=>{const r=w.getState();if(j.hidden=b!=="calibration",ee.hidden=b!=="assistant",te.textContent=r.runtimeReady?"Calibration is complete. Continue when you're ready to open the assistant.":r.phase==="benchmarking"?"Calibration is running now. Keep reading the prompt clearly until the timer ends.":oe,re.textContent=ce,ie.style.width=`${Math.round(r.calibrationProgress*100)}%`,ne.textContent=r.phase==="benchmarking"?r.calibrationRecording?`${r.calibrationSecondsRemaining??0}s remaining`:"Finalizing calibration...":r.runtimeReady?"Calibration complete.":"Calibration required.",V.hidden=!r.benchmarkSummary,V.textContent=r.benchmarkSummary??"",P.disabled=r.phase==="benchmarking",P.textContent=r.phase==="benchmarking"?"Calibrating...":r.runtimeReady?"Continue":"Calibrate",k.replaceChildren(),r.turns.length===0)I.hidden=!1,I.textContent=r.runtimeReady?'Press Record and say "Hello, my name is _____."':"Complete calibration first.",k.append(I);else{I.hidden=!0;for(const e of r.turns){const t=document.createElement("article");t.className="turn",t.dataset.role=e.role;const i=document.createElement("div");i.className="turn-header",i.innerHTML=`<span>${e.role}</span><span>${e.audioStatus}</span>`;const n=document.createElement("div");n.className="turn-body",n.textContent=e.transcript||"…",t.append(i,n),k.append(t)}}Z.disabled=!r.runtimeReady||r.phase==="benchmarking",Z.textContent=r.phase==="listening"||r.phase==="arming"?"Stop Recording":r.phase==="thinking"||r.phase==="speaking"?"Stop":"Record",x.hidden=b!=="calibration"||!r.errorMessage,x.textContent=b==="calibration"?r.errorMessage??"":"",B.hidden=b!=="assistant"||!r.errorMessage,B.textContent=b==="assistant"?r.errorMessage??"":""};P.addEventListener("click",()=>{if(C.setConsentAccepted(!0),w.getState().runtimeReady){b="assistant",z();return}C.runBenchmark()});Z.addEventListener("click",()=>{C.setConsentAccepted(!0),C.start()});window.addEventListener("beforeunload",()=>{C.dispose()});w.subscribe(()=>{z()});C.bootstrap().then(()=>{z()});
index.html CHANGED
@@ -11,7 +11,7 @@
11
  name="description"
12
  content="Local browser voice assistant with on-device speech processing."
13
  />
14
- <script type="module" crossorigin src="/assets/index-ucxsjAc2.js"></script>
15
  <link rel="stylesheet" crossorigin href="/assets/index-D37VpK1p.css">
16
  </head>
17
  <body>
 
11
  name="description"
12
  content="Local browser voice assistant with on-device speech processing."
13
  />
14
+ <script type="module" crossorigin src="/assets/index-0lmKKa3c.js"></script>
15
  <link rel="stylesheet" crossorigin href="/assets/index-D37VpK1p.css">
16
  </head>
17
  <body>