@streamoji/aitwin 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -2
- package/dist/aitwin.cjs +2 -2
- package/dist/aitwin.js +1792 -1111
- package/dist/assets/visemeDiffPreview.worker-C2ONr8XS.js +1 -0
- package/dist/index.d.ts +80 -0
- package/package.json +5 -1
- package/dist/assets/visemeDiffPreview.worker-zI0svOaa.js +0 -1
package/README.md
CHANGED
|
@@ -61,6 +61,9 @@ Provide **`id`** (cloud twin) **or** **`assets`** (fixed URLs). One is required.
|
|
|
61
61
|
| `onStatusChange` | TTS status: idle, loading, speaking, done, error |
|
|
62
62
|
| `onDisplayStatus` | Compositor label (viseme / idle / transition) |
|
|
63
63
|
| `onError` | Load or runtime errors |
|
|
64
|
+
| `onUserTranscript` | Voice STT transcript (partial + final) |
|
|
65
|
+
| `onBotOutput` | Bot LLM text from voice session |
|
|
66
|
+
| `onRealtimeLipsyncDebug` | Lipsync debug snapshots (lab) |
|
|
64
67
|
|
|
65
68
|
Use **stable** `useCallback` handlers for `onReady` / `onError` / `onDisplayStatus` in parent components.
|
|
66
69
|
|
|
@@ -68,11 +71,64 @@ Use **stable** `useCallback` handlers for `onReady` / `onError` / `onDisplayStat
|
|
|
68
71
|
|
|
69
72
|
| Method | Description |
|
|
70
73
|
|--------|-------------|
|
|
71
|
-
| `speakText(text, options?)` | TTS + lipsync; optional per-call `tts` / `voiceId` |
|
|
74
|
+
| `speakText(text, options?)` | SSE TTS + lipsync; optional per-call `tts` / `voiceId` |
|
|
75
|
+
| `connect(options)` | Mic + WebSocket voice session (`/ws/voice`) |
|
|
76
|
+
| `disconnect()` | End voice session |
|
|
77
|
+
| `isConnected()` | Whether voice WebSocket is active |
|
|
72
78
|
| `stop()` | Stop playback and return toward idle |
|
|
73
|
-
| `
|
|
79
|
+
| `setTtsEngineId(engineId)` | Switch Google / Inworld / Cartesia |
|
|
74
80
|
| `renderViseme(to, options?)` | Manual viseme transition (lab) |
|
|
75
81
|
| `isReady()` | Whether face assets are loaded |
|
|
82
|
+
| `getStatus()` | Current lipsync status |
|
|
83
|
+
|
|
84
|
+
### Realtime voice (`connect`)
|
|
85
|
+
|
|
86
|
+
Call from a **user gesture** (button click) so the browser unlocks mic + audio:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import { useRef } from "react";
|
|
90
|
+
import { AiTwin, type AiTwinHandle } from "@streamoji/aitwin";
|
|
91
|
+
|
|
92
|
+
function VoiceDemo() {
|
|
93
|
+
const twinRef = useRef<AiTwinHandle>(null);
|
|
94
|
+
|
|
95
|
+
const onConnect = async () => {
|
|
96
|
+
const authJwt = await getAuthToken(); // client_… — same as SSE
|
|
97
|
+
await twinRef.current?.connect({
|
|
98
|
+
authToken: authJwt,
|
|
99
|
+
voiceId: optionalCartesiaUuid,
|
|
100
|
+
speakingRate: 0.85,
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<AiTwin
|
|
107
|
+
ref={twinRef}
|
|
108
|
+
id="olivia"
|
|
109
|
+
onUserTranscript={(text, final) => final && console.log("you:", text)}
|
|
110
|
+
onBotOutput={(text) => console.log("bot:", text)}
|
|
111
|
+
/>
|
|
112
|
+
<button type="button" onClick={() => void onConnect()}>
|
|
113
|
+
Connect mic
|
|
114
|
+
</button>
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
WebSocket URL: `wss://<api-host>/ws/voice?<query>`. The SDK builds it from `connect()` options. Browsers cannot set `Authorization` on WebSocket easily — pass the same JWT as **`authToken`** query param.
|
|
121
|
+
|
|
122
|
+
| Query param | Required | Source |
|
|
123
|
+
|-------------|----------|--------|
|
|
124
|
+
| `authToken` | Yes | Same JWT as SSE `Authorization: Bearer` (`client_…` from `getAuthToken`) |
|
|
125
|
+
| `voiceId` | No | Cartesia UUID override |
|
|
126
|
+
| `speaking_rate` | No | `0.5`–`1.5`, default `0.85` |
|
|
127
|
+
| `tts_stream` | No | Default `true` (server TTS + `avatar_audio_chunk`) |
|
|
128
|
+
|
|
129
|
+
`speakText()` (SSE) and `connect()` (voice) share the same billing token. Pass `authToken` on `<AiTwin>` or in `connect({ authToken })`; omitted → dev `getAuthToken` is fetched automatically.
|
|
130
|
+
|
|
131
|
+
Low-level API: `createRealtimeVoiceLipsyncController`, `buildVoiceWebSocketUrl`, `fetchDevAuthToken`.
|
|
76
132
|
|
|
77
133
|
## Architecture (aitwin monorepo)
|
|
78
134
|
|
package/dist/aitwin.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Pe=require("react/jsx-runtime"),p=require("react");var Le=typeof document<"u"?document.currentScript:null;const Ct="https://pub-607ad1fc22e2400eb57d17240aab857c.r2.dev",ht={publicBase:Ct},Mt="0.1.7",Ft={version:Mt},Ot={},ve="https://ai.streamoji.com",Pt=`${ht.publicBase}/custom-faces`,Lt=`${ht.publicBase}/aitwin-workers/v${Ft.version}/visemeDiffPreview.worker.js`;function Ut(e){if(e)return e;const t=typeof{url:typeof document>"u"?require("url").pathToFileURL(__filename).href:Le&&Le.tagName.toUpperCase()==="SCRIPT"&&Le.src||new URL("aitwin.cjs",document.baseURI).href}<"u"?Ot?.VITE_AITWIN_WORKER_URL:void 0;return t?.trim()?t.trim():Lt}const Nt="https://us-central1-streamoji-265f4.cloudfunctions.net/getAiTwin",Ge="eng_a7f2b9c1",be="eng_d4e8f3a2",ae="eng_c9b1e6d4",re=ae,Bt=new Set([be,ae]),_t={[Ge]:"oculus_direct",[ae]:"oculus_direct",[be]:"inworld_word"},Xe={google:Ge,inworld:be,cartesia:ae};function Vt(e){return _t[e]}function $t(e){return e===ae}function Ht(e){if(e.ttsEngineId&&e.ttsEngineId in _t)return e.ttsEngineId;const t=e.tts?.toLowerCase();return t&&t in Xe?Xe[t]:re}class Ae extends Error{constructor(t="AI twin not found"){super(t),this.name="AiTwinNotFoundError"}}async function mt(e,t=Nt){const n=new URL(t);n.searchParams.set("id",e);const r=await fetch(n.toString());if(r.status===404)throw new Ae;if(!r.ok)throw new Error(`getAiTwin failed (${r.status})`);const s=await r.json();if(!s.success)throw new Ae(s.error??"AI twin not found");const{tts:i,...c}=s.data;return{...c,ttsEngineId:Ht(s.data)}}const Wt="https://us-central1-streamoji-265f4.cloudfunctions.net/getAuthToken",qt="client_6TNvp3SCs4Og0a1ijm9TommXLql1",Kt="kk8Fq8EmexzP10jMIEwY3R44M5RKUEm1",Gt="6TNvp3SCs4Og0a1ijm9TommXLql1",jt="Swaraj Mali";async function Ne(){const e=await fetch(Wt,{method:"POST",headers:{"Content-Type":"application/json","Client-Id":qt,"Client-Secret":Kt},body:JSON.stringify({userId:Gt,userName:jt})});if(!e.ok)throw new Error(`getAuthToken failed (${e.status})`);const t=await e.json(),n=t.authToken??t.token??"";if(!n.trim())throw new Error("getAuthToken returned no token");return n}async function zt(e,t=ve){const n=await fetch(`${t.replace(/\/$/,"")}/api/session-value`,{headers:{Authorization:`Bearer ${e}`}});if(!n.ok)throw new Error(`/api/session-value failed (${n.status})`);const r=await n.json();if(!r.value)throw new Error("Session value response missing value");return r}function Qt(e){const t=e.trim();if(t.length%2!==0)throw new Error("Invalid hex key");const n=new Uint8Array(t.length/2);for(let r=0;r<t.length;r+=2)n[r/2]=Number.parseInt(t.slice(r,r+2),16);return n}function Se(e){return Uint8Array.from(e)}function Jt(e,t){const n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}async function Yt(e,t){if(!t)throw new Error("Asset key is required for encrypted assets");if(e.byteLength<28)throw new Error("Encrypted payload too small");const n=new Uint8Array(e),r=Se(n.subarray(0,12)),s=Se(n.subarray(12,28)),i=Se(n.subarray(28)),c=Se(Jt(i,s)),a=await crypto.subtle.importKey("raw",Qt(t),{name:"AES-GCM"},!1,["decrypt"]);return crypto.subtle.decrypt({name:"AES-GCM",iv:r,tagLength:128},a,c)}let Be=null;const Ve=new Set;function Xt(e,t=Pt){const n=`${t.replace(/\/$/,"")}/${e}`;return{twinBase:n,binBase:n,encrypted:!0}}function Zt(e){Be=e;for(const t of Ve)t()}function en(e,t){const n=typeof globalThis.location<"u"?globalThis.location.href:void 0;if(!n)return e;const r=s=>{try{return new URL(s,n).href.replace(/\/$/,"")}catch{return s}};return{twinBase:r(e.twinBase),binBase:r(e.binBase),encrypted:e.encrypted}}function se(){if(!Be)throw new Error("Twin assets not configured; call setActiveTwinAssets first");return Be}function tn(e){return Ve.add(e),()=>Ve.delete(e)}function Ze(){const{binBase:e,twinBase:t,encrypted:n}=se();return n?`${e}/sil.bin`:`${t}/sil.png`}function nn(){const{binBase:e,twinBase:t,encrypted:n}=se();return n?`${e}/idle.bin`:`${t}/idle.mp4`}function rn(){const{encrypted:e,binBase:t,twinBase:n}=se();return e?t:n}function je(){return se().encrypted}function sn(){const{binBase:e,twinBase:t,encrypted:n}=se();return n?`${e}/atlas.bin`:`${t}/atlas.json`}function on(){const{binBase:e,twinBase:t,encrypted:n}=se();return n?`${e}/expression_atlas.bin`:`${t}/expression_atlas.json`}function an(e){const t=e.split(/\r?\n/);let n="",r="";for(const i of t)i.startsWith("event:")?n=i.slice(6).trim():i.startsWith("data:")&&(r=i.slice(5).trim());if(!n)return null;let s={};if(r)try{s=JSON.parse(r)}catch{s={raw:r}}return{event:n,data:s}}const wt=["aa","CH","DD","E","FF","I","O","PP","RR","SS","TH","U","kk","nn","sil"],cn=new Map(wt.map(e=>[e.toLowerCase(),e]));function gt(e){const t=(e??"").trim();return t?cn.get(t.toLowerCase())??"sil":"sil"}const un={aei:["aa","E","I"],o:["O","U"],ee:["I"],bmp:["PP"],fv:["FF"],l:["nn"],r:["RR"],th:["TH"],qw:["U","O"],cdgknstxyz:["DD","SS","kk","CH"]};function ln(e){if(!e)return["sil"];const t=e.toLowerCase();return un[t]??["sil"]}function fn(e,t){const n=e.word.length;if(n<=0)return 0;const r=e.wduration;if(r<=0)return 0;const s=Math.max(0,Math.min(1,(t-e.wtime)/r));return Math.min(n-1,Math.floor(s*n))}function De(e,t){const n=Math.max(0,Math.min(e.length-1,t));return e[n]??""}function dn(e,t){const n=e.toLowerCase(),r=De(n,t),s=n.slice(Math.max(0,t-1),t+2);return r==="i"||r==="y"?"I":s.includes("ee")||r==="e"||n.includes("ea")?"E":"aa"}function hn(e,t){const n=e.toLowerCase(),r=n.slice(Math.max(0,t-1),Math.min(n.length,t+3));if(/oo|ou|uw/.test(r))return"U";const s=De(n,t);return s==="u"||s==="w"?"U":"O"}function _n(e,t){const n=e.toLowerCase(),r=n.slice(Math.max(0,t-2),t+1);if(/qu/.test(r))return"U";const s=n.slice(Math.max(0,t-1),Math.min(n.length,t+3));return/wo|wh/.test(s)||De(n,t)==="o"?"O":"U"}const Te={d:"DD",t:"DD",n:"DD",s:"SS",z:"SS",k:"kk",g:"kk",c:"CH",x:"CH",j:"CH"};function mn(e,t){const n=e.toLowerCase(),r=De(n,t);if(Te[r])return Te[r];for(let s=0;s<n.length;s++){const i=n[s];if(Te[i])return Te[i]}return"DD"}function wn(e,t,n){const r=ln(e);if(r.length===1)return r[0];const s=t.trim();if(!s)return r[0];const i=(e??"").toLowerCase(),c=Math.max(0,n);switch(i){case"aei":return dn(s,c);case"o":return hn(s,c);case"qw":return _n(s,c);case"cdgknstxyz":return mn(s,c);default:return r[0]}}function gn(e){let t=e.trim();const n=t.indexOf(",");t.startsWith("data:")&&n>=0&&(t=t.slice(n+1)),t=t.replace(/\s+/g,"").replace(/-/g,"+").replace(/_/g,"/");const r=t.length%4;return r!==0&&(t+="=".repeat(4-r)),t}const yn=24e3;function pn(e){return e.length>=4&&e[0]===82&&e[1]===73&&e[2]===70&&e[3]===70}function et(e,t,n=yn){const r=t.byteOffset%2===0?t:t.slice(),s=new Int16Array(r.buffer,r.byteOffset,r.byteLength/2),i=new Float32Array(s.length);for(let a=0;a<s.length;a++)i[a]=s[a]>=32768?-(65536-s[a])/32768:s[a]/32767;const c=e.createBuffer(1,i.length,n);return c.copyToChannel(i,0),c}function En(e){const t=gn(e),n=window.atob(t),r=new Uint8Array(n.length);for(let s=0;s<n.length;s++)r[s]=n.charCodeAt(s);return r}async function Sn(e,t){const n=En(t);if(pn(n)){const r=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength);return e.decodeAudioData(r)}if(n.length>=2&&n.length%2===0)return et(e,n);try{const r=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength);return await e.decodeAudioData(r)}catch{return et(e,n)}}function yt(e,t){let n=null;for(const r of e)t<r.vtime||t>=r.vtime+r.vduration||(!n||r.vtime>=n.vtime)&&(n=r);return n?n.viseme:"sil"}function pt(e,t){for(const n of e)if(t>=n.wtime&&t<n.wtime+n.wduration)return n;return null}function tt(e){let t=0;for(const n of e)t=Math.max(t,n.vtime+n.vduration);return t}const Et=.5,St=1.5,ze="Olivia",Tt="f786b574-daa5-4673-aa0c-cbe3e8534c02",Qe=.85;function kt(e,t=ve){const n=`${t.replace(/\/$/,"")}/avatar_ttsWithPoses`,r=[],s=[],i=[];let c="neutral";const a=[],l=[];let d=null,m=!1,_=!1,k=!1,o=!1,x=0,w=0,u=0,g=0,E=null,R=1,$="",y=re,S=re,C=!1;const D=new Set,W=()=>{for(const h of l)h.playbackRate.value=R},q=()=>D.forEach(h=>h()),ce=()=>{E!=null&&(clearTimeout(E),E=null)},ge=()=>{for(const h of l)try{h.stop()}catch{}l.length=0},Z=()=>{C=!0,ce(),ge(),a.length=0,m=!1,_=!1,k=!1,o=!1,r.length=0,s.length=0,i.length=0,c="neutral",u=0,g=0,w=0,e("idle"),q()},K=()=>{!o||m||l.length>0||a.length>0||(_=!1,ce(),e("done"),q())},ye=()=>{const h=tt(r);if(h<=0){e("done"),q();return}_=!0,x=performance.now(),e("speaking"),q(),ce();const M=R>0?h/R:h;E=setTimeout(()=>{C||(_=!1,E=null,e("done"),q())},M)},Me=(h,M,B)=>{h.length!==0&&(B&&(u=M),h.forEach(P=>{const U=String(P.word??"").trim();if(!U)return;const Q=Math.round((P.start??0)*1e3),V=Math.round((P.duration??0)*1e3);s.push({word:U,wtime:u+Q,wduration:V,queueIndex:s.length})}))},ue=(h,M,B,P)=>{h.length!==0&&(B&&(u=M),h.forEach(I=>{const U=String(I.symbol??"").trim();if(!U)return;const Q=Math.round((I.start??0)*1e3),V=Math.max(1,Math.round((I.duration??0)*1e3)),L=u+Q;let F;if(Vt(P)==="oculus_direct")F=gt(U);else{const O=pt(s,L),J=O?fn(O,L):0;F=wn(U,O?.word??"",J)}r.push({viseme:F,weight:1,vtime:L,vduration:V})}),q())},ie=(h,M,B,P,I)=>{Me(M,B,P),ue(h,B,P,I)},pe=async(h,M,B,P)=>{if(C||!h?.trim()){(M.length>0||B.length>0)&&!k&&ie(M,B,u,P,S);return}if(m){a.push({audio:h,visemes:M,words:B,isNewSegment:P});return}m=!0;try{const I=window.AudioContext||window.webkitAudioContext,U=d??new I;U.state==="suspended"&&await U.resume(),d=U;const Q=await Sn(U,h);k=!0;const V=U.currentTime;let L=g;const F=!_;L<V&&(L=V+.1),g=L+Q.duration;const O=U.createBufferSource();if(O.buffer=Q,O.playbackRate.value=R,O.connect(U.destination),l.push(O),C){l.pop();return}if(F){_=!0,e("speaking"),w=L,u=0,r.length=0,s.length=0;const H=(L-V)*1e3;x=performance.now()+H}O.onended=()=>{const H=l.indexOf(O);H>=0&&l.splice(H,1),K(),q()},O.start(L);const J=(L-w)*1e3;ie(M,B,J,P,S)}catch(I){console.error("[avatarTtsLipsync] audio chunk failed:",I)}finally{m=!1;const I=a.shift();I?await pe(I.audio,I.visemes,I.words,I.isNewSegment):K()}},ee=()=>{if(o=!0,!k&&r.length>0){ye();return}K()};return{setDeveloperToken:h=>{$=h},setTtsEngineId:h=>{y=h},getTtsEngineId:()=>y,speak:async(h,M)=>{Z(),C=!1,e("loading");const B=$.trim();if(!B)throw new Error("Developer token required for avatar_ttsWithPoses");const P=M?.ttsEngineId??y??re;S=P;const I=M?.voiceId??ze,U=M?.speakingRate??Qe,Q=Math.max(Et,Math.min(St,U)),V={user_query:h,ttsEngineId:P,speakingRate:Q};Bt.has(P)&&(V.voice_id=I);try{const L=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${B}`},body:JSON.stringify(V)});if(!L.ok)throw new Error(`avatar_ttsWithPoses failed (${L.status})`);const F=L.body;if(!F)throw new Error("No response body");const O=F.getReader(),J=new TextDecoder;let H="";const fe=async de=>{const f=an(de);if(f){if(f.event==="audio"){const T=f.data.chunk,v=f.data.visemes??[],A=f.data.words??[],b=f.data.is_new_segment??!1;if(T)await pe(T,v,A,b);else if(v.length>0||A.length>0){const G=b&&r.length>0?tt(r):u;ie(v,A,G,b,P)}}else if(f.event==="metadata"){const T=f.data.mood;typeof T=="string"&&T.trim()&&(c=T.trim());const v=f.data.sentence_emotions;if(Array.isArray(v)){i.length=0;for(const A of v){if(!A||typeof A!="object")continue;const b=A;i.push({sentence_index:Number(b.sentence_index??0),text:String(b.text??""),sentiment:typeof b.sentiment=="string"?b.sentiment:void 0,emotion:String(b.emotion??c),start_word:Number(b.start_word??0),end_word:Number(b.end_word??0)})}}q()}else if(f.event==="error")throw new Error(String(f.data.message??"avatar_ttsWithPoses stream error"))}};for(;;){const{done:de,value:f}=await O.read();f&&(H+=J.decode(f,{stream:!0}));const T=H.split(`
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Ze=require("react/jsx-runtime"),g=require("react"),En=require("@pipecat-ai/client-js"),zt=require("@pipecat-ai/websocket-transport");var et=typeof document<"u"?document.currentScript:null;const Sn="https://pub-607ad1fc22e2400eb57d17240aab857c.r2.dev",Yt={publicBase:Sn},kn="0.1.9",vn={version:kn},Tn={},Te="https://ai.streamoji.com",bn=`${Yt.publicBase}/custom-faces`,An=`${Yt.publicBase}/aitwin-workers/v${vn.version}/visemeDiffPreview.worker.js`;function Rn(e){if(e)return e;const t=typeof{url:typeof document>"u"?require("url").pathToFileURL(__filename).href:et&&et.tagName.toUpperCase()==="SCRIPT"&&et.src||new URL("aitwin.cjs",document.baseURI).href}<"u"?Tn?.VITE_AITWIN_WORKER_URL:void 0;return t?.trim()?t.trim():An}const In="https://us-central1-streamoji-265f4.cloudfunctions.net/getAiTwin",ut="eng_a7f2b9c1",qe="eng_d4e8f3a2",Se="eng_c9b1e6d4",ye=Se,Dn=new Set([qe,Se]),Jt={[ut]:"oculus_direct",[Se]:"oculus_direct",[qe]:"inworld_word"},Dt={google:ut,inworld:qe,cartesia:Se};function Cn(e){return Jt[e]}function Mn(e){return e===Se}function xn(e){if(e.ttsEngineId&&e.ttsEngineId in Jt)return e.ttsEngineId;const t=e.tts?.toLowerCase();return t&&t in Dt?Dt[t]:ye}class Ve extends Error{constructor(t="AI twin not found"){super(t),this.name="AiTwinNotFoundError"}}async function Qt(e,t=In){const n=new URL(t);n.searchParams.set("id",e);const r=await fetch(n.toString());if(r.status===404)throw new Ve;if(!r.ok)throw new Error(`getAiTwin failed (${r.status})`);const s=await r.json();if(!s.success)throw new Ve(s.error??"AI twin not found");const{tts:i,...o}=s.data;return{...o,ttsEngineId:xn(s.data)}}const Pn="https://us-central1-streamoji-265f4.cloudfunctions.net/getAuthToken",Ln="client_6TNvp3SCs4Og0a1ijm9TommXLql1",On="kk8Fq8EmexzP10jMIEwY3R44M5RKUEm1",Bn="6TNvp3SCs4Og0a1ijm9TommXLql1",Fn="Swaraj Mali";async function Ne(){const e=await fetch(Pn,{method:"POST",headers:{"Content-Type":"application/json","Client-Id":Ln,"Client-Secret":On},body:JSON.stringify({userId:Bn,userName:Fn})});if(!e.ok)throw new Error(`getAuthToken failed (${e.status})`);const t=await e.json(),n=t.authToken??t.token??"";if(!n.trim())throw new Error("getAuthToken returned no token");return n}async function Un(e,t=Te){const n=await fetch(`${t.replace(/\/$/,"")}/api/session-value`,{headers:{Authorization:`Bearer ${e}`}});if(!n.ok)throw new Error(`/api/session-value failed (${n.status})`);const r=await n.json();if(!r.value)throw new Error("Session value response missing value");return r}function Nn(e){const t=e.trim();if(t.length%2!==0)throw new Error("Invalid hex key");const n=new Uint8Array(t.length/2);for(let r=0;r<t.length;r+=2)n[r/2]=Number.parseInt(t.slice(r,r+2),16);return n}function Le(e){return Uint8Array.from(e)}function Vn(e,t){const n=new Uint8Array(e.length+t.length);return n.set(e,0),n.set(t,e.length),n}async function $n(e,t){if(!t)throw new Error("Asset key is required for encrypted assets");if(e.byteLength<28)throw new Error("Encrypted payload too small");const n=new Uint8Array(e),r=Le(n.subarray(0,12)),s=Le(n.subarray(12,28)),i=Le(n.subarray(28)),o=Le(Vn(i,s)),a=await crypto.subtle.importKey("raw",Nn(t),{name:"AES-GCM"},!1,["decrypt"]);return crypto.subtle.decrypt({name:"AES-GCM",iv:r,tagLength:128},a,o)}let nt=null;const rt=new Set;function Oe(e){return e.includes("/custom-faces/")}function $e(e){const t=e.binBase.trim().replace(/\/$/,""),n=e.twinBase.trim().replace(/\/$/,"");if(!(e.encrypted||Oe(t)||Oe(n)))return{twinBase:n,binBase:t,encrypted:!1};const s=Oe(t)?t:Oe(n)?n:t;return{twinBase:s,binBase:s,encrypted:!0}}function be(e){return $e(e).encrypted}function Wn(e,t=bn){const n=`${t.replace(/\/$/,"")}/${e}`;return{twinBase:n,binBase:n,encrypted:!0}}function Hn(e){nt=$e(e);for(const t of rt)t()}function qn(e,t){const n=typeof globalThis.location<"u"?globalThis.location.href:void 0;if(!n)return $e(e);const r=s=>{try{return new URL(s,n).href.replace(/\/$/,"")}catch{return s}};return $e({twinBase:r(e.twinBase),binBase:r(e.binBase),encrypted:e.encrypted})}function ke(){if(!nt)throw new Error("Twin assets not configured; call setActiveTwinAssets first");return nt}function Kn(e){return rt.add(e),()=>rt.delete(e)}function Ct(){const e=ke();return be(e)?`${e.binBase}/sil.bin`:`${e.twinBase}/sil.png`}function jn(){const e=ke();return be(e)?`${e.binBase}/idle.bin`:`${e.twinBase}/idle.mp4`}function Gn(){const e=ke();return be(e)?e.binBase:e.twinBase}function Ke(){return be(ke())}function zn(){const e=ke();return be(e)?`${e.binBase}/atlas.bin`:`${e.twinBase}/atlas.json`}function Yn(){const e=ke();return be(e)?`${e.binBase}/expression_atlas.bin`:`${e.twinBase}/expression_atlas.json`}function Jn(e){const t=e.split(/\r?\n/);let n="",r="";for(const i of t)i.startsWith("event:")?n=i.slice(6).trim():i.startsWith("data:")&&(r=i.slice(5).trim());if(!n)return null;let s={};if(r)try{s=JSON.parse(r)}catch{s={raw:r}}return{event:n,data:s}}const Xt=["aa","CH","DD","E","FF","I","O","PP","RR","SS","TH","U","kk","nn","sil"],Qn=new Map(Xt.map(e=>[e.toLowerCase(),e]));function Zt(e){const t=(e??"").trim();return t?Qn.get(t.toLowerCase())??"sil":"sil"}const Xn={aei:["aa","E","I"],o:["O","U"],ee:["I"],bmp:["PP"],fv:["FF"],l:["nn"],r:["RR"],th:["TH"],qw:["U","O"],cdgknstxyz:["DD","SS","kk","CH"]};function Zn(e){if(!e)return["sil"];const t=e.toLowerCase();return Xn[t]??["sil"]}function er(e,t){const n=e.word.length;if(n<=0)return 0;const r=e.wduration;if(r<=0)return 0;const s=Math.max(0,Math.min(1,(t-e.wtime)/r));return Math.min(n-1,Math.floor(s*n))}function je(e,t){const n=Math.max(0,Math.min(e.length-1,t));return e[n]??""}function tr(e,t){const n=e.toLowerCase(),r=je(n,t),s=n.slice(Math.max(0,t-1),t+2);return r==="i"||r==="y"?"I":s.includes("ee")||r==="e"||n.includes("ea")?"E":"aa"}function nr(e,t){const n=e.toLowerCase(),r=n.slice(Math.max(0,t-1),Math.min(n.length,t+3));if(/oo|ou|uw/.test(r))return"U";const s=je(n,t);return s==="u"||s==="w"?"U":"O"}function rr(e,t){const n=e.toLowerCase(),r=n.slice(Math.max(0,t-2),t+1);if(/qu/.test(r))return"U";const s=n.slice(Math.max(0,t-1),Math.min(n.length,t+3));return/wo|wh/.test(s)||je(n,t)==="o"?"O":"U"}const Be={d:"DD",t:"DD",n:"DD",s:"SS",z:"SS",k:"kk",g:"kk",c:"CH",x:"CH",j:"CH"};function sr(e,t){const n=e.toLowerCase(),r=je(n,t);if(Be[r])return Be[r];for(let s=0;s<n.length;s++){const i=n[s];if(Be[i])return Be[i]}return"DD"}function ir(e,t,n){const r=Zn(e);if(r.length===1)return r[0];const s=t.trim();if(!s)return r[0];const i=(e??"").toLowerCase(),o=Math.max(0,n);switch(i){case"aei":return tr(s,o);case"o":return nr(s,o);case"qw":return rr(s,o);case"cdgknstxyz":return sr(s,o);default:return r[0]}}const or=24e3;function ar(e){let t=e.trim();const n=t.indexOf(",");t.startsWith("data:")&&n>=0&&(t=t.slice(n+1)),t=t.replace(/\s+/g,"").replace(/-/g,"+").replace(/_/g,"/");const r=t.length%4;return r!==0&&(t+="=".repeat(4-r)),t}function cr(e){return e.length>=4&&e[0]===82&&e[1]===73&&e[2]===70&&e[3]===70}function Mt(e,t,n=or){const r=t.byteOffset%2===0?t:t.slice(),s=new Int16Array(r.buffer,r.byteOffset,r.byteLength/2),i=new Float32Array(s.length);for(let a=0;a<s.length;a++)i[a]=s[a]>=32768?-(65536-s[a])/32768:s[a]/32767;const o=e.createBuffer(1,i.length,n);return o.copyToChannel(i,0),o}function ur(e){const t=ar(e),n=window.atob(t),r=new Uint8Array(n.length);for(let s=0;s<n.length;s++)r[s]=n.charCodeAt(s);return r}async function lr(e,t){const n=ur(t);if(cr(n)){const r=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength);return e.decodeAudioData(r)}if(n.length>=2&&n.length%2===0)return Mt(e,n);try{const r=n.buffer.slice(n.byteOffset,n.byteOffset+n.byteLength);return await e.decodeAudioData(r)}catch{return Mt(e,n)}}function xt(e){let t=0;for(const n of e)t=Math.max(t,n.vtime+n.vduration);return t}function fr(e,t){for(const n of e)if(t>=n.wtime&&t<n.wtime+n.wduration)return n;return null}function en(e,t){const n=[],r=[];let s=null,i=!1,o=!1,a=!1,f=!1,l=0,m=0,p=0,_=0,S=null,k=1,u=t,y=!1;const d=()=>{S!=null&&(clearTimeout(S),S=null)},C=()=>{for(const w of r)try{w.stop()}catch{}r.length=0},E=()=>{for(const w of r)w.playbackRate.value=k},R=()=>{!f||i||r.length>0||n.length>0||(o=!1,d(),e.onSpeakingDone(),e.onNotify())},U=w=>{const T=xt(w);if(T<=0){e.onSpeakingDone(),e.onNotify();return}o=!0,l=performance.now(),e.onSpeakingStart(),e.onNotify(),d();const A=k>0?T/k:T;S=setTimeout(()=>{y||(o=!1,S=null,e.onSpeakingDone(),e.onNotify())},A)},V=(w,T,A,P)=>{T.length!==0&&(P&&(p=A),T.forEach(x=>{const I=String(x.word??"").trim();if(!I)return;const $=Math.round((x.start??0)*1e3),Y=x.duration!=null?Math.round(x.duration*1e3):x.end!=null?Math.max(1,Math.round((x.end-(x.start??0))*1e3)):0;w.push({word:I,wtime:p+$,wduration:Y,queueIndex:w.length,gesture:x.gesture??null,emphasis:x.emphasis===!0})}))},z=(w,T,A,P,x,B)=>{A.length!==0&&(x&&(p=P),A.forEach(I=>{const $=String(I.symbol??I.viseme??"").trim();if(!$)return;const Y=Math.round((I.start??0)*1e3),_e=Math.max(1,Math.round((I.duration??0)*1e3)),re=p+Y;let j;if(Cn(B)==="oculus_direct")j=Zt($);else{const X=fr(T,re),Z=X?er(X,re):0;j=ir($,X?.word??"",Z)}w.push({viseme:j,weight:1,vtime:re,vduration:_e})}),e.onNotify())},K=(w,T,A,P,x,B,I)=>{V(T,P,x,B),z(w,T,A,x,B,I)},v=async(w,T,A,P,x,B,I)=>{if(y||!A?.trim()){(P.length>0||x.length>0)&&!a&&K(w,T,P,x,p,B,u);return}if(i){n.push({audio:A,visemes:P,words:x,isNewSegment:B});return}i=!0;try{const $=window.AudioContext||window.webkitAudioContext;s||(s=new $);const Y=s;Y.state==="suspended"&&await Y.resume();const _e=await lr(Y,A);a=!0;const re=Y.currentTime;let j=_;const X=!o;j<re&&(j=re+.1),_=j+_e.duration;const Z=Y.createBufferSource();if(Z.buffer=_e,Z.playbackRate.value=k,Z.connect(Y.destination),r.push(Z),y){r.pop();return}if(X){o=!0,e.onSpeakingStart(),m=j,p=0,w.length=0,T.length=0,I?.();const b=(j-re)*1e3;l=performance.now()+b}Z.onended=()=>{const b=r.indexOf(Z);b>=0&&r.splice(b,1),R(),e.onNotify()},Z.start(j);const c=(j-m)*1e3;K(w,T,P,x,c,B,u)}catch($){console.error("[avatarChunkPlayback] audio chunk failed:",$)}finally{i=!1;const $=n.shift();$?await v(w,T,$.audio,$.visemes,$.words,$.isNewSegment,I):R()}};return{setStreamTtsEngineId:w=>{u=w},setPlaybackSpeed:w=>{k=Math.max(.1,Math.min(1,w)),E()},getPlaybackSpeed:()=>k,resetForNewStream:w=>{y=!1,f=!1,u=w},primeAudioContext:async()=>{const w=window.AudioContext||window.webkitAudioContext;s||(s=new w),s.state==="suspended"&&await s.resume()},stop:()=>{y=!0,d(),C(),n.length=0,i=!1,o=!1,a=!1,f=!1,p=0,_=0,m=0,l=0},markStreamComplete:w=>{if(f=!0,!a&&w.length>0){U(w);return}R()},playAudioChunk:v,queueTimingsOnly:(w,T,A,P,x)=>{let B=p;x&&w.length>0&&(B=xt(w)),K(w,T,A,P,B,x,u)},getPlaybackElapsedMs:()=>o?a&&s&&m>0?Math.max(0,(s.currentTime-m)*1e3):Math.max(0,(performance.now()-l)*k):0,isSpeaking:()=>o,getPlaybackStartPerf:()=>l,bargeIn:()=>{y=!0,C(),n.length=0,i=!1,o=!1,a=!1,_=0,m=0,l=0,d()},interrupt:()=>{C(),n.length=0,i=!1,o=!1,a=!1,_=0,m=0,l=0,d()}}}function Ge(e,t){let n=null;for(const r of e)t<r.vtime||t>=r.vtime+r.vduration||(!n||r.vtime>=n.vtime)&&(n=r);return n?n.viseme:"sil"}function dr(e,t){for(const n of e)if(t>=n.wtime&&t<n.wtime+n.wduration)return n;return null}const lt=.5,ft=1.5,dt="Olivia",tn="f786b574-daa5-4673-aa0c-cbe3e8534c02",ze=.85;function st(e,t=Te){const n=`${t.replace(/\/$/,"")}/avatar_ttsWithPoses`,r=[],s=[],i=[];let o="neutral",a="",f=ye;const l=new Set,m=()=>l.forEach(k=>k()),p=en({onSpeakingStart:()=>e("speaking"),onSpeakingDone:()=>e("done"),onNotify:m},ye),_=()=>{p.stop(),r.length=0,s.length=0,i.length=0,o="neutral",e("idle"),m()};return{setDeveloperToken:k=>{a=k},setTtsEngineId:k=>{f=k},getTtsEngineId:()=>f,speak:async(k,u)=>{_(),e("loading");const y=a.trim();if(!y)throw new Error("Developer token required for avatar_ttsWithPoses");const d=u?.ttsEngineId??f??ye;p.resetForNewStream(d),p.setStreamTtsEngineId(d);const C=u?.voiceId??dt,E=u?.speakingRate??ze,R=Math.max(lt,Math.min(ft,E)),U={user_query:k,ttsEngineId:d,speakingRate:R};Dn.has(d)&&(U.voice_id=C);try{const V=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`},body:JSON.stringify(U)});if(!V.ok)throw new Error(`avatar_ttsWithPoses failed (${V.status})`);const z=V.body;if(!z)throw new Error("No response body");const K=z.getReader(),v=new TextDecoder;let M="";const w=async T=>{const A=Jn(T);if(A){if(A.event==="audio"){const P=A.data.chunk,x=A.data.visemes??[],B=A.data.words??[],I=A.data.is_new_segment??!1;P?await p.playAudioChunk(r,s,P,x,B,I):(x.length>0||B.length>0)&&p.queueTimingsOnly(r,s,x,B,I)}else if(A.event==="metadata"){const P=A.data.mood;typeof P=="string"&&P.trim()&&(o=P.trim());const x=A.data.sentence_emotions;if(Array.isArray(x)){i.length=0;for(const B of x){if(!B||typeof B!="object")continue;const I=B;i.push({sentence_index:Number(I.sentence_index??0),text:String(I.text??""),sentiment:typeof I.sentiment=="string"?I.sentiment:void 0,emotion:String(I.emotion??o),start_word:Number(I.start_word??0),end_word:Number(I.end_word??0)})}}m()}else if(A.event==="error")throw new Error(String(A.data.message??"avatar_ttsWithPoses stream error"))}};for(;;){const{done:T,value:A}=await K.read();A&&(M+=v.decode(A,{stream:!0}));const P=M.split(`
|
|
2
2
|
|
|
3
|
-
`);H=T.pop()??"";for(const v of T)await fe(v);if(de){H.trim()&&await fe(H.trim()),C||ee();break}}}catch(L){throw console.error("[avatarTtsLipsync]",L),Z(),e("error"),L}},stop:Z,getVisemeQueue:()=>r,getWordQueue:()=>s,getSentenceEmotions:()=>i,getStreamMood:()=>c,getPlaybackElapsedMs:()=>_?k&&d&&w>0?Math.max(0,(d.currentTime-w)*1e3):Math.max(0,(performance.now()-x)*R):0,isSpeaking:()=>_,setPlaybackSpeed:h=>{R=Math.max(.1,Math.min(1,h)),W()},getPlaybackSpeed:()=>R,subscribe:h=>(D.add(h),()=>D.delete(h))}}function Tn(e,t,n){return{width:e.crop?.source_width??t,height:e.crop?.source_height??n}}function kn(e,t){const n=e.crop;return{x:t?.source_x??n?.x??0,y:t?.source_y??n?.y??0,width:t?.source_w??n?.width??e.frame_width??0,height:t?.source_h??n?.height??e.frame_height??0}}function In(e){return e?(e.w??0)>0&&(e.h??0)>0:!1}async function xe(e,t){const n=await fetch(e);if(!n.ok)throw new Error(`Failed to load encrypted asset: ${e}`);const r=await n.arrayBuffer();return Yt(r,t)}async function vn(e,t){if(!t)throw new Error("Missing asset key for encrypted JSON");const n=await xe(e,t),r=new TextDecoder().decode(n);return JSON.parse(r)}async function An(e){const t=new Blob([e],{type:"image/webp"}),n=URL.createObjectURL(t);return new Promise((r,s)=>{const i=new Image;i.decoding="async",i.onload=()=>{URL.revokeObjectURL(n),r(i)},i.onerror=()=>{URL.revokeObjectURL(n),s(new Error("Failed to decode decrypted image"))},i.src=n})}async function Rn(e){const t=new Blob([e],{type:"image/png"}),n=URL.createObjectURL(t);return new Promise((r,s)=>{const i=new Image;i.decoding="async",i.onload=()=>{URL.revokeObjectURL(n),r(i)},i.onerror=()=>{URL.revokeObjectURL(n),s(new Error("Failed to decode decrypted PNG"))},i.src=n})}async function bn(e){const t=nn();if(!je())return t;if(!e)throw new Error("Missing asset key for encrypted idle video");const n=await xe(t,e);return URL.createObjectURL(new Blob([n],{type:"video/mp4"}))}function It(e,t){return`${e}/${t}_minus_sil.png`}const Je=15,Dn=["aa","E","I","O","U","PP","FF","DD","SS","TH","CH","RR","kk","nn"],$e=["t01_0.17","t02_0.33","t03_0.50","t04_0.67","t05_0.83"],xn=$e.length,nt=new Set(["aa__kk","aa__nn","aa__sil","CH__aa","CH__DD","CH__E","CH__FF","CH__I","CH__kk","CH__nn","CH__O","CH__PP","CH__RR","CH__sil","CH__SS","CH__TH","CH__U","DD__aa","DD__E","DD__FF","DD__I","DD__kk","DD__nn","DD__O","DD__PP","DD__RR","DD__sil","DD__SS","DD__TH","DD__U","E__aa","E__FF","E__I","E__kk","E__nn","E__O","E__PP","E__RR","E__sil","E__SS","E__TH","E__U","FF__aa","FF__I","FF__kk","FF__nn","FF__O","FF__PP","FF__RR","FF__sil","FF__SS","FF__TH","FF__U","I__aa","I__kk","I__nn","I__O","I__PP","I__RR","I__sil","I__SS","I__TH","I__U","kk__nn","kk__sil","nn__sil","O__aa","O__kk","O__nn","O__PP","O__RR","O__sil","O__SS","O__TH","O__U","PP__aa","PP__kk","PP__nn","PP__RR","PP__sil","PP__SS","PP__TH","PP__U","RR__aa","RR__kk","RR__nn","RR__sil","RR__SS","RR__TH","RR__U","SS__aa","SS__kk","SS__nn","SS__sil","SS__TH","SS__U","TH__aa","TH__kk","TH__nn","TH__sil","TH__U","U__aa","U__kk","U__nn","U__sil"]);function rt(e,t){return`${e}__${t}`}function st(e,t){return`pairs/${e}/${t}_minus_sil.png`}function He(e){return`${e}_minus_sil.png`}function Cn(e,t){if(e===t)return[];const n=[],r=rt(e,t);if(nt.has(r)){for(const i of $e)n.push(st(r,i));return n}const s=rt(t,e);if(nt.has(s)){for(let i=xn-1;i>=0;i--)n.push(st(s,$e[i]));return n}return[]}function Mn(e){return e<16?[]:e<35?[2]:e<51?[0,4]:e<67?[0,2,4]:e<83?[0,1,2,3]:[0,1,2,3,4]}function Fn(e,t){return Mn(t).map(r=>e[r]).filter(r=>r!=null)}function On(e,t,n,r){if(r<=0)return 0;const i=Math.max(1,n-t)/r,c=e-t,a=Math.floor(c/i);return Math.min(r-1,Math.max(0,a))}function Pn(e,t,n){if(!n)return`${e}/${t}`;const r=t.split("/").pop()??t,s=r.includes(".")?r.slice(0,r.lastIndexOf(".")):r;return`${e}/${s}.bin`}const Ln=[.32,.24,.16,.08,0],Ce=Ln.map(e=>`eyes_${e.toFixed(2)}`),Un=["eyes_0.32","eyes_0.16","eyes_0.00"],Nn=["eyes_0.32","eyes_0.16","eyes_0.08","eyes_0.00"],Bn={fear:"fear",afraid:"fear",anxiety:"fear",anger:"anger",angry:"anger",mad:"anger",sad:"sad",sadness:"sad",sorrow:"sad",neutral:"neutral",calm:"neutral",love:"love",loving:"love",affection:"love",happy:"happy",happiness:"happy",joy:"happy",joyful:"happy",excited:"happy",disgust:"disgust",disgusted:"disgust"};function oe(e,t="neutral"){const n=String(e??"").trim().toLowerCase();return n?Bn[n]??t:t}function z(e){return e==="neutral"?null:It(e,"eyes_standard")}function We(e,t){return!t||t==="eyes_standard"?z(e):e==="neutral"&&t==="eyes_standard"?null:It(e,t)}function Vn(e){if(!e)return"open";const t=e.match(/\/(eyes_[^/]+)_minus_sil\.png$/);if(!t?.[1])return"open";const n=t[1];return Ce.includes(n)?n:"open"}function $n(e){return e==="open"?-1:Ce.indexOf(e)}function Hn(e,t){const n=[];for(const r of t)n.push(We(e,r));for(let r=t.length-2;r>=0;r--)n.push(We(e,t[r]));return n.push(z(e)),n}function Wn(e,t){if(t==="open")return[z(e)];const n=$n(t);if(n<0)return[z(e)];const r=[];for(let s=n;s>=0;s--)r.push(We(e,Ce[s]));return r.push(z(e)),r}const qn=["t01_0.20","t02_0.40","t03_0.60","t04_0.80"];function Kn(e,t){return`${e}__${t}`}function Gn(e,t,n){return`pairs/${Kn(e,t)}/${n}_minus_sil.png`}function jn(e,t){return qn.map(n=>Gn(e,t,n))}function _e(e,t){return e+Math.random()*(t-e)}function ke(){return _e(14,32)}function zn(e={}){const{initialDelayMs:t=800+Math.random()*1200,intervalMinMs:n=2200,intervalMaxMs:r=5800,doubleBlinkChance:s=.18,doubleBlinkGapMinMs:i=120,doubleBlinkGapMaxMs:c=220,fullBlinkChance:a=.75,postEmotionChangeDelayMs:l=380,minDwellMs:d=550}=e;let m="neutral",_="neutral",k=0,o={kind:"idle",nextBlinkAt:performance.now()+t},x=null,w="__eyes_open__|neutral";function u(y,S){x=y,w=`${y??"__eyes_open__"}|${S}`}function g(y,S){o={kind:"idle",nextBlinkAt:y+_e(n,r)},_=S,u(z(S),S)}function E(y,S,C){const D=C?Un:Math.random()<a?Ce:Nn;o={kind:"playing",paths:Hn(S,D),index:0,holdUntil:y+ke(),after:"idle",blinkEmotion:S},_=S,u(o.paths[0]??null,S)}function R(y,S,C){const D=Vn(x),W=[];if(D!=="open"){const q=Wn(S,D);W.push(...q.slice(0,-1))}W.push(...jn(S,C)),W.push(z(C)),o={kind:"emotionBlend",paths:W,index:0,holdUntil:y+ke(),thenEmotion:C},u(W[0]??null,S)}function $(y){if(o.kind==="emotionBlend"){for(;y>=o.holdUntil&&o.index<o.paths.length-1;)o.index+=1,o.holdUntil=y+ke();const S=o.paths[o.index]??null;u(S,o.thenEmotion),y>=o.holdUntil&&o.index>=o.paths.length-1&&(m=o.thenEmotion,_=o.thenEmotion,u(z(o.thenEmotion),o.thenEmotion),o={kind:"idle",nextBlinkAt:y+l});return}if(o.kind==="idle"){u(z(_),_),y>=o.nextBlinkAt&&E(y,_,!1);return}if(o.kind==="doublePause"){u(z(o.blinkEmotion),o.blinkEmotion),y>=o.resumeAt&&E(y,o.blinkEmotion,!0);return}if(o.kind==="playing"){for(;y>=o.holdUntil&&o.index<o.paths.length-1;)o.index+=1,o.holdUntil=y+ke();if(u(o.paths[o.index]??null,o.blinkEmotion),y>=o.holdUntil&&o.index>=o.paths.length-1){if(o.after==="resumeIdle"){g(y+l,m);return}if(o.after==="doublePause"){o={kind:"doublePause",resumeAt:y+_e(i,c),blinkEmotion:o.blinkEmotion},u(z(o.blinkEmotion),o.blinkEmotion);return}if(o.after==="idle"&&Math.random()<s){o={kind:"doublePause",resumeAt:y+_e(i,c),blinkEmotion:o.blinkEmotion},u(z(o.blinkEmotion),o.blinkEmotion);return}g(y,o.blinkEmotion)}}}return{advance:$,setTargetEmotion(y){const S=oe(y,m);if(S===m&&o.kind!=="emotionBlend")return;const C=performance.now();if(d>0&&S!==m&&o.kind!=="emotionBlend"&&!(m==="neutral"&&S!=="neutral")&&C-k<d)return;const D=m;if(m=S,k=C,o.kind==="playing"){R(C,_,S);return}if(o.kind==="emotionBlend"){o={...o,thenEmotion:S};return}R(C,D,S)},getTargetEmotion:()=>m,getExpressionPath:()=>x,getDrawKey:()=>w,reset(){m="neutral",_="neutral",k=0,g(performance.now()+_e(n,r),"neutral")}}}function Qn(){const e=document.createElement("video");e.muted=!0,e.playsInline=!0,e.preload="auto",e.setAttribute("playsinline",""),e.loop=!0;let t=!1,n=null,r="";const s=()=>{n&&(URL.revokeObjectURL(n),n=null)};return{async load(i){t&&r===i||(s(),t=!1,r=i,i.startsWith("blob:")&&(n=i),await new Promise((c,a)=>{const l=()=>{const d=e.duration;if(!Number.isFinite(d)||d<=0){a(new Error(`Idle video has invalid duration: ${i}`));return}t=!0,e.currentTime=0,c()};e.addEventListener("loadeddata",l,{once:!0}),e.addEventListener("error",()=>a(new Error(`Failed to load idle video: ${i}`)),{once:!0}),e.src=i,e.load()}))},isReady(){return t},getVideo(){return e},setActive(i){t&&(i?e.paused&&e.play().catch(()=>{}):e.paused||e.pause())},restart(){t&&(e.currentTime=0)},dispose(){e.pause(),e.removeAttribute("src"),e.load(),s(),t=!1,r=""}}}const Jn=50,vt=400;function Yn(e,t,n){return e.filter(r=>{const s=r.queueIndex??-1;return s>=t&&s<=n})}function Xn(e,t){const n=[];for(const r of e){const s=Yn(t,r.start_word,r.end_word);if(s.length===0)continue;const i=s[0],c=s[s.length-1];n.push({sentence:r,startMs:Math.max(0,i.wtime-Jn),endMs:c.wtime+c.wduration+vt})}return n.sort((r,s)=>r.startMs-s.startMs)}function Zn(e,t,n,r="neutral"){const s=oe(r);if(t.length===0)return s;if(n.length===0||e<n[0].wtime)return oe(t[0]?.emotion,s);const i=Xn(t,n);let c=null;for(const d of i)e>=d.startMs&&e<=d.endMs&&(c=d);if(c)return oe(c.sentence.emotion,s);const a=n[n.length-1],l=a.wtime+a.wduration;return e>l+vt?oe(t[t.length-1]?.emotion,s):s}function At(e,t,n,r,s=Je){const i=new OffscreenCanvas(n,r),c=i.getContext("2d",{willReadFrequently:!0});c.drawImage(e,0,0,n,r);const a=c.getImageData(0,0,n,r),l=c.createImageData(n,r);l.data.set(a.data);const m=new OffscreenCanvas(n,r).getContext("2d",{willReadFrequently:!0});for(const _ of t){m.clearRect(0,0,n,r),m.drawImage(_,0,0,n,r);const k=m.getImageData(0,0,n,r);for(let o=0;o<l.data.length;o+=4){const x=k.data[o],w=k.data[o+1],u=k.data[o+2];Math.max(x,w,u)>s&&(l.data[o]=x,l.data[o+1]=w,l.data[o+2]=u,l.data[o+3]=255)}}return c.putImageData(l,0,0),i}let ne=null,me=null;const Re=new Map;function er(){ne=null,me=null,Re.clear()}tn(er);function tr(e){return e??me??void 0}function Ye(e){return new Promise((t,n)=>{const r=new Image;r.decoding="async",r.onload=()=>t(r),r.onerror=()=>n(new Error(`Failed to load image: ${e}`)),r.src=e})}async function it(e,t,n,r){const s=n?await vn(e,r):await fetch(e).then(d=>{if(!d.ok)throw new Error(`Failed to load ${e}`);return d.json()}),i=s.sheets?.[0];if(!i?.path)throw new Error(`${e} has no sheets[0].path`);const c=n?await An(await xe(Pn(t,i.path,n),r??"")):await Ye(`${t}/${i.path}`),a=new Map;for(const d of s.cells??[])d.path&&a.set(d.path,d);const l=new Map;for(const d of s.sheets??[])l.set(d.index,d);return{atlas:c,atlasMeta:s,cellByPath:a,sheetByIndex:l,diffBase:t}}async function qe(e){const t=tr(e),n=je();if(n&&!t)throw new Error("Encrypted assets enabled but no key provided");if(ne&&(!n||me===(t??null))){const i=await ne;return we(i)}me=t??null,Re.clear();const r=rn();ne=(async()=>{const i=n?await Rn(await xe(Ze(),t??"")):await Ye(Ze()),c=await it(sn(),r,n,t);let a=null;try{a=await it(on(),r,n,t)}catch{a=null}return{sil:i,viseme:c,expression:a}})();const s=await ne;return we(s)}async function Rt(e){if(await qe(e),!ne)throw new Error("Assets not loaded");return ne}function we(e){return Tn(e.viseme.atlasMeta,e.sil.naturalWidth,e.sil.naturalHeight)}function nr(e,t){const n=document.createElement("canvas");return n.width=t.w,n.height=t.h,n.getContext("2d",{willReadFrequently:!0}).drawImage(e.atlas,t.x,t.y,t.w,t.h,0,0,t.w,t.h),n}async function ot(e,t,n){const r=`${t.diffBase}::${n}`,s=Re.get(r);if(s)return s;const i=(async()=>{const c=t.cellByPath.get(n);if(!c)throw new Error(`No atlas cell for path: ${n}`);const a=kn(t.atlasMeta,c),l=we(e);if(In(c)){const _=nr(t,c),k=document.createElement("canvas");k.width=l.width,k.height=l.height;const o=k.getContext("2d");return o.fillStyle="#000",o.fillRect(0,0,l.width,l.height),o.drawImage(_,a.x,a.y),k}const d=`${t.diffBase}/${n}`,m=await Ye(d);if(m.naturalWidth===l.width&&m.naturalHeight===l.height)return m;throw new Error(`Diff PNG wrong size for ${n}`)})();return Re.set(r,i),i}async function at(e,t,n,r=Je,s,i){if(s){const{width:k,height:o}=s;if(i&&!i()||t.readyState<HTMLMediaElement.HAVE_CURRENT_DATA)return;const x=document.createElement("canvas");x.width=k,x.height=o;const w=x.getContext("2d");if(w.clearRect(0,0,k,o),w.drawImage(t,0,0,k,o),i&&!i())return;(e.width!==k||e.height!==o)&&(e.width=k,e.height=o);const u=e.getContext("2d");u.clearRect(0,0,k,o),u.drawImage(x,0,0);return}const c=await Rt(),{width:a,height:l}=we(c);(e.width!==a||e.height!==l)&&(e.width=a,e.height=l);const d=[],m=e.getContext("2d");if(m.clearRect(0,0,a,l),t.readyState<HTMLMediaElement.HAVE_CURRENT_DATA){const k=c.sil;m.drawImage(k,0,0,a,l);return}if(d.length===0){m.drawImage(t,0,0,a,l);return}const _=At(t,d,a,l,r);m.drawImage(_,0,0)}async function Ke(e,t,n=Je){const r=await Rt(me??void 0),{width:s,height:i}=we(r);(e.width!==s||e.height!==i)&&(e.width=s,e.height=i);const c=[];t.mouthPath&&c.push(await ot(r,r.viseme,t.mouthPath)),t.expressionPath&&r.expression&&c.push(await ot(r,r.expression,t.expressionPath));const a=e.getContext("2d");if(a.clearRect(0,0,s,i),c.length===0){a.drawImage(r.sil,0,0,s,i);return}const l=At(r.sil,c,s,i,n);a.drawImage(l,0,0)}async function rr(e){await Ke(e,{mouthPath:null,expressionPath:null})}function sr(e){const t=new Map;for(const n of e)t.has(n.vtime)||t.set(n.vtime,n);return[...t.values()].sort((n,r)=>n.vtime-r.vtime)}function ir(e){const t=sr(e),n=[];for(let r=0;r<t.length-1;r++){const s=t[r].viseme,i=t[r+1].viseme,c=t[r].vtime,a=t[r+1].vtime;s===i||a<=c||n.push({from:s,to:i,fromVtime:c,toVtime:a})}return n}function or(e,t){let n=null;for(const r of e)t>=r.fromVtime&&t<r.toVtime&&(n=r);return n}function ar(e,t){return{gapMs:Math.max(0,t-e),holdEnd:e,transStart:e}}const cr=500,ur="__sil__";function lr(){return{lastDrawnKey:""}}function he(e){e.lastDrawnKey=""}function ct(e){return e??ur}function fr(e,t){if(e.length===0)return{diffPath:null,label:"sil"};const n=ir(e),r=or(n,t);if(r){const i=r.toVtime-r.fromVtime,c=ar(r.fromVtime,r.toVtime);if(t>=c.transStart&&t<r.toVtime){const a=Cn(r.from,r.to),l=Fn(a,i);if(l.length>0){const d=On(t,c.transStart,r.toVtime,l.length);return{diffPath:l[d]??l[l.length-1],label:`${r.from}→${r.to}`}}return{diffPath:He(r.to),label:r.to}}}const s=yt(e,t);return s==="sil"?{diffPath:null,label:"sil"}:{diffPath:He(s),label:s}}function bt(e,t){const n=t??ct(e.expressionPath);return`${ct(e.mouthPath)}|${n}`}function dr(e,t,n,r,s,i,c,a){const l=fr(t,n),d={mouthPath:l.diffPath,expressionPath:r},m=bt(d,s);m!==i.lastDrawnKey&&(a(l.label),c(e,d,m,_=>{_===m&&(i.lastDrawnKey=m)}))}function ut(e,t,n,r,s,i,c=null){const a={mouthPath:null,expressionPath:t},l=c!=null?`|idle@${Math.round(c*1e3)}`:"",d=bt(a,n)+l;!(c!=null)&&d===r.lastDrawnKey||(i(c!=null?"idle":"sil"),s(e,a,d,_=>{_===d&&(r.lastDrawnKey=d)}))}function Dt(e){let t=0,n=null,r=!1,s=!1;const i=async()=>{if(!r){r=!0;try{for(;n;){const a=n;n=null;const l=++t,d=await e(a.diffs,l,a);n||d&&l===t&&a.onDisplayed?.(a.drawKey)}}finally{r=!1,n&&i()}}},c=()=>{s||(s=!0,queueMicrotask(()=>{s=!1,i()}))};return{enqueue(a,l,d){n={diffs:a,drawKey:l,onDisplayed:d},c()},reset(){t+=1,n=null},isLatestSeq(a){return a===t},getLatestSeq(){return t}}}const lt=-1,ft=new Map;async function hr(e){let t=ft.get(e);if(!t){const n=await fetch(e);if(!n.ok)throw new Error(`Failed to fetch worker script (${n.status}): ${e}`);const r=await n.blob();t=URL.createObjectURL(r),ft.set(e,t)}return new Worker(t,{name:"visemeDiffPreview"})}function _r(){return typeof Worker<"u"&&typeof OffscreenCanvas<"u"}function mr(e,t){(e.width!==t.width||e.height!==t.height)&&(e.width=t.width,e.height=t.height);const n=e.getContext("2d");n&&n.drawImage(t,0,0)}function dt(e,t){let n=0;const r=Dt(async(s,i)=>(await qe(),await Ke(e,s),r.isLatestSeq(i)));return{usesWorker:!1,async loadAssets(s){return await qe(s)},async renderSilOnly(){n+=1,r.reset(),await rr(e)},drawLiveFrame(s,i,c,a){r.enqueue(i,c,a)},async renderViseme(s,i){n+=1;const c=n;t.onStatus?.(i),await Ke(s,{mouthPath:He(i),expressionPath:null})},dispose(){n+=1,r.reset()}}}function wr(e,t,n){let r=null,s=1,i=0,c=!1,a=!1;const l=new Map,d=Dt(async(w,u,g)=>(l.set(u,{drawKey:g.drawKey,onDisplayed:g.onDisplayed}),await o({type:"drawFrame",diffPath:w.mouthPath,expressionDiffPath:w.expressionPath,liveSeq:u},void 0,!0),!1)),m=w=>{if(w===lt){let E=-1,R;for(const[$,y]of l)$>=E&&(E=$,R=y);E>=0&&l.delete(E),R?.onDisplayed?.(R.drawKey);return}const u=-w,g=l.get(u);l.delete(u),g?.onDisplayed?.(g.drawKey)},_=new Map,k=(w,u)=>{a||!r||r.postMessage(w,[])},o=(w,u,g=!1)=>{if(a)return Promise.resolve();const E=s++;return new Promise((R,$)=>{_.set(E,{resolve:y=>R(y),reject:$,generation:typeof w.generation=="number"?w.generation:void 0,expectRenderDone:g}),k({...w,requestId:E})})},x=(async()=>{r=await hr(n),r.onmessage=w=>{if(a)return;const u=w.data;if(u.type==="status"){t.onStatus?.(u.label);return}if(u.type==="frame"){const E=u.generation<0;if(!E&&u.generation!==i){u.bitmap.close();return}if(E&&!(u.generation===lt)){const $=-u.generation;if(!d.isLatestSeq($)){u.bitmap.close();return}}if(E&&t.shouldAcceptLiveFrame&&!t.shouldAcceptLiveFrame()){u.bitmap.close();return}mr(e,u.bitmap),u.bitmap.close(),E&&m(u.generation);return}const g=_.get(u.requestId);if(g)switch(u.type){case"ready":_.delete(u.requestId),g.resolve();break;case"loadAssetsDone":_.delete(u.requestId),g.resolve({width:u.width,height:u.height});break;case"renderDone":if(g.expectRenderDone&&g.generation!=null&&u.generation!==g.generation)return;_.delete(u.requestId),g.resolve();break;case"renderAborted":_.delete(u.requestId),g.resolve();break;case"error":_.delete(u.requestId),t.onError?.(u.message),g.reject(new Error(u.message));break}},r.onerror=w=>{if(a)return;const u=w.message||"Worker error";t.onError?.(u);for(const[,g]of _)g.reject(new Error(u));_.clear()},await o({type:"init"}),c=!0})();return{usesWorker:!0,async loadAssets(w){if(a)return{width:842,height:1264};if(await x,a)return{width:842,height:1264};if(!c)throw new Error("Worker init failed");return await o({type:"loadAssets",keyHex:w,urls:en(se())})??{width:842,height:1264}},async renderSilOnly(){if(a||(await x,a))return;i+=1,d.reset(),l.clear();const w=i;await o({type:"renderSil",generation:w},void 0,!0)},drawLiveFrame(w,u,g,E){a||x.then(()=>{a||d.enqueue(u,g,E)})},async renderViseme(w,u,g={}){if(a||(await x,a))return;i+=1;const E=i;try{await o({type:"renderViseme",generation:E,to:u,from:g.from,transitionDurationMs:g.transitionDurationMs,gapMs:g.gapMs,threshold:g.threshold},void 0,!0)}catch(R){if(R?.name==="AbortError")return;throw R}},dispose(){if(!a){a=!0,i+=1,d.reset(),l.clear();for(const[,w]of _)w.resolve();_.clear(),r?.terminate(),r=null}}}}function gr(e,t={}){if(!_r())return dt(e,t);const n=Ut(t.workerScriptUrl);try{return wr(e,t,n)}catch{return dt(e,t)}}class yr extends Error{constructor(t,n){super(`${t} timed out after ${Math.round(n/1e3)}s`),this.name="LoadTimeoutError"}}function Ue(e,t,n){return new Promise((r,s)=>{const i=globalThis.setTimeout(()=>{s(new yr(n,t))},t);e.then(c=>{globalThis.clearTimeout(i),r(c)},c=>{globalThis.clearTimeout(i),s(c)})})}const pr={position:"relative",display:"inline-block",lineHeight:0},Er={display:"block",maxWidth:"100%",height:"auto"},Sr={position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",padding:"1rem",background:"rgba(0,0,0,0.55)",color:"#fff",fontSize:"0.875rem",textAlign:"center"};function Ie(e){return $t(e)?Tt:ze}const Tr=p.forwardRef(function({id:t,assets:n,authToken:r,ttsEngineId:s=re,voiceId:i,speakingRate:c=Qe,className:a,style:l,canvasStyle:d,onStatusChange:m,onDisplayStatus:_,onError:k,onReady:o,showErrorOverlay:x=!0},w){const u=p.useRef(null),g=p.useRef(null),E=p.useRef(lr()),R=p.useRef(zn()),$=p.useRef(0),y=p.useRef(0),S=p.useRef(null),C=p.useRef(!1),D=p.useRef(null),W=p.useRef(!1),[q,ce]=p.useState("idle"),[ge,Z]=p.useState(null),[K,ye]=p.useState(!1),[Me,ue]=p.useState(!1),[ie,pe]=p.useState({width:842,height:1264}),ee=p.useRef(r??null),le=p.useRef(null),h=p.useRef(null),M=p.useRef(!1),B=p.useRef({width:842,height:1264}),P=p.useRef(o),I=p.useRef(_),U=p.useRef(k);P.current=o,I.current=_,U.current=k;const Q=p.useCallback(f=>{const T=f?.ttsEngineId??s??h.current?.ttsEngineId??re,v=f?.voiceId??i??h.current?.voiceId??Ie(T);return{ttsEngineId:T,voiceId:v}},[s,i]),V=p.useCallback(f=>{Z(f),U.current?.(f)},[]),L=p.useCallback(f=>{ce(f),m?.(f)},[m]),F=p.useRef(kt(L,ve)),O=p.useCallback(()=>{S.current!=null&&(clearTimeout(S.current),S.current=null)},[]),J=p.useCallback(async()=>{const f=y.current,T=g.current;!T||!K||F.current.isSpeaking()||(he(E.current),await T.renderSilOnly(),f===y.current&&(F.current.isSpeaking()||(I.current?.("sil"),W.current&&D.current&&(D.current.restart(),D.current.setActive(!0)))))},[K]),H=p.useCallback(()=>{O();const f=y.current;J(),S.current=setTimeout(()=>{S.current=null,f===y.current&&J()},cr)},[O,J]),fe=p.useCallback(async()=>{const f=Math.floor(Date.now()/1e3);if(le.current&&f<le.current.expiresAt-60)return le.current.key;const T=ee.current??await Ne();ee.current=T,F.current.setDeveloperToken(T);const v=await zt(T,ve);return le.current={key:v.value,expiresAt:v.expiresAt},v.value},[]);p.useEffect(()=>{r&&(ee.current=r,F.current.setDeveloperToken(r))},[r]),p.useEffect(()=>{F.current.setTtsEngineId(s),h.current&&(h.current.ttsEngineId=s)},[s]),p.useEffect(()=>{const f=i??Ie(s);h.current&&(h.current.voiceId=f)},[i,s]);const de=p.useMemo(()=>n?JSON.stringify(n):`id:${t??""}`,[n,t]);return p.useEffect(()=>{if(!t&&!n){V("AiTwin requires either `id` or `assets`");return}let f=!1;M.current=!1,ye(!1),ue(!1),Z(null),h.current=null,D.current=null,W.current=!1;const T=u.current;if(!T)return;let v=null;return(async()=>{try{let A,b,G;if(n)A=n,b=s,G=i??Ie(b);else{I.current?.("getAiTwin");const N=await Ue(mt(t),3e4,"getAiTwin");if(f)return;if(!N.faceId?.trim())throw new Error("getAiTwin returned no faceId");b=N.ttsEngineId,G=i??N.voiceId??Ie(b),A=Xt(N.faceId)}h.current={ttsEngineId:b,voiceId:G},Zt(A),D.current=Qn(),v=gr(T,{onStatus:N=>{f||I.current?.(N)},onError:N=>{f||V(N)},shouldAcceptLiveFrame:()=>F.current.isSpeaking()}),g.current=v,I.current?.("auth");const Y=ee.current??r??await Ue(Ne(),3e4,"getAuthToken");if(f)return;ee.current=Y,F.current.setDeveloperToken(Y),F.current.setTtsEngineId(b);const Ee=je()?await fe():void 0;if(f)return;I.current?.("loadAssets");const j=await Ue(v.loadAssets(Ee),18e4,"load face assets");if(!f&&j.width>0&&j.height>0&&(B.current=j,pe(j)),f)return;M.current=!0,ye(!0),P.current?.(),v.renderSilOnly().then(()=>{f||I.current?.("sil")}),(async()=>{try{const N=await bn(Ee);if(await D.current.load(N),f)return;W.current=!0,ue(!0),he(E.current),D.current.setActive(!0);const X=D.current.getVideo();await at(T,X,null,void 0,B.current),f||I.current?.("idle")}catch(N){if(f)return;W.current=!1,ue(!1),console.warn("[AiTwin] Idle video unavailable:",N)}})()}catch(A){if(f)return;if(A instanceof Ae){V(A.message);return}V(A instanceof Error?A.message:"Failed to load AI twin")}})(),()=>{f=!0,D.current?.dispose(),v?.dispose(),g.current=null}},[de,n,s,i,r,fe,V]),p.useEffect(()=>{q==="done"&&K&&!F.current.isSpeaking()&&H()},[q,K,H]),p.useEffect(()=>{const f=F.current,T={current:!1},v=()=>{const A=u.current,b=g.current;if(!A||!b||!K){$.current=requestAnimationFrame(v);return}const G=f.isSpeaking(),Y=f.getPlaybackElapsedMs(),Ee=G?Zn(Y,f.getSentenceEmotions(),f.getWordQueue(),f.getStreamMood()):f.getStreamMood();if(G){if(W.current&&D.current?.setActive(!1),f.getVisemeQueue().length===0){$.current=requestAnimationFrame(v);return}R.current.setTargetEmotion(Ee),R.current.advance(performance.now());const N=R.current.getExpressionPath(),X=R.current.getDrawKey();dr(A,f.getVisemeQueue(),Y,N,X,E.current,(te,Fe,Oe,xt)=>b.drawLiveFrame(te,Fe,Oe,xt),te=>I.current?.(te))}else if(W.current&&D.current){const j=D.current,N=j.getVideo();N.readyState>=HTMLMediaElement.HAVE_CURRENT_DATA?(j.setActive(!0),T.current||(T.current=!0,at(A,N,null,void 0,B.current,()=>!f.isSpeaking()).then(()=>I.current?.("idle")).finally(()=>{T.current=!1}))):ut(A,null,"__sil__",E.current,(X,te,Fe,Oe)=>b.drawLiveFrame(X,te,Fe,Oe),X=>I.current?.(X))}else ut(A,null,"__sil__",E.current,(j,N,X,te)=>b.drawLiveFrame(j,N,X,te),j=>I.current?.(j));C.current&&!G&&(R.current.reset(),D.current?.setActive(!1),H()),C.current=G,$.current=requestAnimationFrame(v)};return $.current=requestAnimationFrame(v),()=>{cancelAnimationFrame($.current),O(),f.stop()}},[K,Me,H,O]),p.useImperativeHandle(w,()=>({speakText:async(f,T)=>{const v=f.trim();if(!v)return;const A=g.current;if(!A||!M.current&&!K)throw new Error("AI twin is not ready");const{ttsEngineId:b,voiceId:G}=Q(T);Z(null),y.current+=1,O(),he(E.current),R.current.reset(),C.current=!1;try{W.current?(D.current?.setActive(!1),D.current?.restart(),he(E.current)):await A.renderSilOnly(),await F.current.speak(v,{voiceId:G,speakingRate:T?.speakingRate??c,ttsEngineId:b})}catch(Y){throw V(Y instanceof Error?Y.message:"TTS failed"),Y}},stop:()=>{y.current+=1,O(),F.current.stop(),he(E.current),C.current=!1,H()},setTtsEngineId:f=>{F.current.setTtsEngineId(f),h.current&&(h.current.ttsEngineId=f)},renderViseme:async(f,T)=>{const v=g.current,A=u.current;if(!v||!A||!M.current)throw new Error("AI twin is not ready");await v.renderViseme(A,f,{from:T?.from,transitionDurationMs:T?.transitionDurationMs,gapMs:T?.gapMs,onStatus:b=>I.current?.(b)})},isReady:()=>M.current,getStatus:()=>q}),[K,c,O,H,V,Q,q]),Pe.jsxs("div",{className:a,style:{...pr,...l},children:[Pe.jsx("canvas",{ref:u,width:ie.width,height:ie.height,style:{...Er,...d},"aria-label":t?`AI twin ${t}`:"AI twin face"}),x&&ge?Pe.jsx("div",{style:Sr,children:ge}):null]})});exports.AiTwin=Tr;exports.AiTwinNotFoundError=Ae;exports.DEFAULT_TTS_ENGINE_ID=re;exports.OCULUS_VISEME_IDS=wt;exports.SPEAKING_RATE_MAX=St;exports.SPEAKING_RATE_MIN=Et;exports.TTS_ENGINE_CARTESIA=ae;exports.TTS_ENGINE_GOOGLE=Ge;exports.TTS_ENGINE_INWORLD=be;exports.VISEME_IDS=Dn;exports.VISEME_TEST_CARTESIA_VOICE_ID=Tt;exports.VISEME_TEST_SPEAKING_RATE=Qe;exports.VISEME_TEST_VOICE_ID=ze;exports.createAvatarTtsLipsyncController=kt;exports.fetchAiTwin=mt;exports.fetchDevAuthToken=Ne;exports.normalizeOculusViseme=gt;exports.resolveVisemeAtTime=yt;exports.resolveWordAtTime=pt;
|
|
3
|
+
`);M=P.pop()??"";for(const x of P)await w(x);if(T){M.trim()&&await w(M.trim()),p.markStreamComplete(r);break}}}catch(V){throw console.error("[avatarTtsLipsync]",V),_(),e("error"),V}},stop:_,getVisemeQueue:()=>r,getWordQueue:()=>s,getSentenceEmotions:()=>i,getStreamMood:()=>o,getPlaybackElapsedMs:()=>p.getPlaybackElapsedMs(),isSpeaking:()=>p.isSpeaking(),setPlaybackSpeed:k=>{p.setPlaybackSpeed(k)},getPlaybackSpeed:()=>p.getPlaybackSpeed(),subscribe:k=>(l.add(k),()=>l.delete(k))}}const ht=16e3,mt=24e3;class hr extends zt.DailyMediaManager{constructor(){super(!0,!0,void 0,void 0,512,ht,mt)}bufferBotAudio(t,n){}}const mr=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function _r(e){return mr.test(e.trim())}function wr(e){const t=e.replace(/\/$/,"");return t.startsWith("https://")?`wss://${t.slice(8)}`:t.startsWith("http://")?`ws://${t.slice(7)}`:t}function nn(e){if(e.wsUrl?.trim())return e.wsUrl.trim();const t=e.authToken?.trim();if(!t)throw new Error("Voice connect requires authToken (same JWT as SSE Authorization Bearer).");const n=e.apiBase??Te,r=new URL(`${wr(n)}/ws/voice`);r.searchParams.set("authToken",t);const s=e.voiceId?.trim();s&&_r(s)&&r.searchParams.set("voiceId",s);const i=e.speakingRate??ze;return r.searchParams.set("speaking_rate",String(Math.max(lt,Math.min(ft,i)))),e.ttsStream===!1?r.searchParams.set("tts_stream","false"):r.searchParams.set("tts_stream","true"),r.toString()}function Pt(e,t){if(!Array.isArray(e))return[];const n=[];for(const r of e)!r||typeof r!="object"||n.push({sentence_index:Number(r.sentence_index??0),text:String(r.text??""),sentiment:typeof r.sentiment=="string"?r.sentiment:void 0,emotion:String(r.emotion??t),start_word:Number(r.start_word??0),end_word:Number(r.end_word??0)});return n}function gr(e){if(!e||typeof e!="object")return null;const t=e,n=t.data;return n&&typeof n=="object"?n:t}function rn(e,t={}){const n=[],r=[],s=[],i=[];let o="",a="neutral",f=Se,l=!1,m=null,p=null,_={...t},S=0,k=null;const u=new Set,y=[];let d=!1,C=null;const E=()=>u.forEach(c=>c()),R=en({onSpeakingStart:()=>e("speaking"),onSpeakingDone:()=>e("done"),onNotify:E},f),U=()=>{if(C)return C;const c=new MessageChannel;return c.port1.onmessage=()=>{const b=y.shift();b!==void 0&&$(b),y.length>0?c.port2.postMessage(null):d=!1},C=c,c},V=c=>{y.push(c),!d&&(d=!0,U().port2.postMessage(null))},z=()=>R.getPlaybackElapsedMs(),K=(c,b=o)=>{if(!_.onLipsyncDebug)return;const O=z(),Q=n;_.onLipsyncDebug({event:c,spokenText:b,mood:a,playbackAnchorPerf:R.getPlaybackStartPerf(),playbackElapsedMs:O,queueLength:Q.length,activeViseme:Ge(Q,O),segments:i.map(G=>({...G,visemes:[...G.visemes]}))})},v=(c,b)=>{_.onLipsyncDebug&&(k={event:c,spokenText:b},S===0&&(S=requestAnimationFrame(()=>{S=0;const O=k;k=null,O&&K(O.event,O.spokenText)})))},M=()=>{n.length=0,r.length=0,i.length=0,o="",a="neutral",s.length=0},w=()=>{R.stop(),M(),v("reset"),E()},T=()=>{w(),e("idle"),E()},A=(c,b)=>{typeof c.mood=="string"&&c.mood.trim()&&(a=c.mood.trim()),Array.isArray(c.sentence_emotions)&&c.sentence_emotions.length>0&&(b&&(s.length=0),s.push(...Pt(c.sentence_emotions,a)))},P=c=>{const b=String(c.spoken_text??c.text??"").trim();b&&(o=b),R.resetForNewStream(f),typeof c.mood=="string"&&c.mood.trim()&&(a=c.mood.trim()),s.length=0,s.push(...Pt(c.sentence_emotions,a)),Array.isArray(c.gestures)&&c.gestures,v("metadata",b),E()},x=(c,b,O,Q)=>{if(!_.onLipsyncDebug)return;const G=n.slice(O),ce=Q.filter(W=>String(W.symbol??W.viseme??"").trim());i.push({spokenText:b,audioOffsetMs:G[0]?.vtime??0,isNewSegment:!!c.is_new_segment,chunkDurationMs:c.chunk_duration_ms!=null?Number(c.chunk_duration_ms):null,receivedAtMs:performance.now(),visemes:G.map((W,we)=>{const ue=ce[we];return{rawSymbol:String(ue?.symbol??ue?.viseme??W.viseme).trim(),viseme:W.viseme,startSec:Number(ue?.start??0),durationSec:Number(ue?.duration??0),vtimeMs:W.vtime,vdurationMs:W.vduration}})})},B=c=>{const b=String(c.spoken_text??c.text??"").trim(),O=!!c.is_new_segment||Number(c.chunk_index??-1)===0,Q=c.visemes??[],G=c.words??[],ce=String(c.chunk??"").trim();b&&(o=b),A(c,O),O&&R.resetForNewStream(f);const W=n.length;R.playAudioChunk(n,r,ce,Q,G,O,()=>{i.length=0}).then(()=>{x(c,b,W,Q),v("audio_chunk",b)})},I=c=>{console.warn("[realtimeVoice] avatar_lipsync is deprecated — expect avatar_audio_chunk");const b=String(c.spoken_text??c.text??"").trim();b&&(o=b),A(c,!!c.is_new_segment);const O=n.length;R.queueTimingsOnly(n,r,c.visemes??[],c.words??[],!!c.is_new_segment),x(c,b,O,c.visemes??[]),v("lipsync",b),E()},$=c=>{const b=gr(c);if(!b){console.warn("[realtimeVoice] server message with no payload",c);return}const O=String(b.type??"unknown");switch(O!=="avatar_metadata"&&O!=="avatar_audio_chunk"&&O!=="avatar_lipsync"&&O!=="error"&&console.debug("[realtimeVoice] server message",O,b),O){case"avatar_metadata":P(b);break;case"avatar_audio_chunk":B(b);break;case"avatar_lipsync":I(b);break;case"error":{const Q=String(b.message??"Voice session error");console.error("[realtimeVoice] server error:",Q),e("error"),E();break}case"avatar_speak":console.warn("[realtimeVoice] avatar_speak received; tts_stream=false Mode B is not wired yet:",b.text);break}},Y=()=>m||(p=new hr,m=new En.PipecatClient({transport:new zt.WebSocketTransport({mediaManager:p,recorderSampleRate:ht,playerSampleRate:mt}),enableCam:!1,enableMic:!0,callbacks:{onBotReady:()=>{l=!0,e("idle"),E()},onDisconnected:()=>{l=!1,T()},onError:c=>{const b=typeof c=="object"&&c!==null&&"data"in c?String(c.data??c):String(c);console.error("[realtimeVoice]",b,c),e("error"),E()},onTransportStateChanged:c=>{c==="error"&&(e("error"),E())},onServerMessage:c=>{V(c)},onUserStartedSpeaking:()=>{R.bargeIn(),M(),e("idle"),v("user_started"),E()},onBotStartedSpeaking:()=>{v("bot_started")},onBotStoppedSpeaking:()=>{R.markStreamComplete(n),v("bot_stopped")},onUserTranscript:c=>{_.onUserTranscript?.(c.text,!!c.final)},onBotOutput:c=>{_.onBotOutput?.(c.text)}}}),m);return{setDeveloperToken:c=>{},setTtsEngineId:c=>{f=c,R.setStreamTtsEngineId(c)},getTtsEngineId:()=>f,speak:async(c,b)=>{const O=c.trim();if(O){if(!m||!l)throw new Error("Realtime voice is not connected. Call connect() first.");w(),await m.sendText(O)}},stop:()=>{w(),e("idle"),E()},connect:async c=>{if(c&&(_={..._,...c}),!_.authToken?.trim())throw new Error("Voice connect requires authToken (same JWT as SSE Authorization Bearer).");e("loading");const b=Y();await b.initDevices();const O=nn(_);console.info("[realtimeVoice] connecting (avatar_audio_chunk RTVI):",O);try{await b.connect({wsUrl:O})}catch(Q){l=!1,e("error"),E();const G=Q instanceof Error?Q.message:"Realtime voice connect failed (WebSocket 101 only means upgrade OK — check authToken/origin/RTVI handshake)";throw new Error(`${G} — ${O}`)}l=!0,e("idle"),E()},disconnect:async()=>{if(y.length=0,d=!1,m)try{await m.disconnect()}catch(c){console.error("[realtimeVoice] disconnect failed:",c)}l=!1,T()},isConnected:()=>l,unlockAudio:async()=>{await R.primeAudioContext()},getVisemeQueue:()=>n,getWordQueue:()=>r,getSentenceEmotions:()=>s,getStreamMood:()=>a,getPlaybackElapsedMs:z,isSpeaking:()=>R.isSpeaking(),setPlaybackSpeed:c=>{R.setPlaybackSpeed(c)},getPlaybackSpeed:()=>R.getPlaybackSpeed(),subscribe:c=>(u.add(c),()=>u.delete(c))}}function pr(e,t,n){return{width:e.crop?.source_width??t,height:e.crop?.source_height??n}}function yr(e,t){const n=e.crop;return{x:t?.source_x??n?.x??0,y:t?.source_y??n?.y??0,width:t?.source_w??n?.width??e.frame_width??0,height:t?.source_h??n?.height??e.frame_height??0}}function Er(e){return e?(e.w??0)>0&&(e.h??0)>0:!1}async function Ye(e,t){const n=await fetch(e);if(!n.ok)throw new Error(`Failed to load encrypted asset: ${e}`);const r=await n.arrayBuffer();return $n(r,t)}async function Sr(e,t){if(!t)throw new Error("Missing asset key for encrypted JSON");const n=await Ye(e,t),r=new TextDecoder().decode(n);return JSON.parse(r)}async function kr(e){const t=new Blob([e],{type:"image/webp"}),n=URL.createObjectURL(t);return new Promise((r,s)=>{const i=new Image;i.decoding="async",i.onload=()=>{URL.revokeObjectURL(n),r(i)},i.onerror=()=>{URL.revokeObjectURL(n),s(new Error("Failed to decode decrypted image"))},i.src=n})}async function vr(e){const t=new Blob([e],{type:"image/png"}),n=URL.createObjectURL(t);return new Promise((r,s)=>{const i=new Image;i.decoding="async",i.onload=()=>{URL.revokeObjectURL(n),r(i)},i.onerror=()=>{URL.revokeObjectURL(n),s(new Error("Failed to decode decrypted PNG"))},i.src=n})}function Tr(e){const t=new Blob([e],{type:"video/mp4"});return URL.createObjectURL(t)}async function br(e){const t=jn();if(!Ke())return t;if(!e)throw new Error("Missing asset key for encrypted idle video");const n=await Ye(t,e);return Tr(n)}function _t(e,t){return`${e}/${t}_minus_sil.png`}const sn=5;function on(e){const t=e?.composite_threshold;if(typeof t=="number"&&t>0)return t;const n=e?.crop?.content_threshold;return typeof n=="number"&&n>0?n:sn}const Ar=["aa","E","I","O","U","PP","FF","DD","SS","TH","CH","RR","kk","nn"],it=["t01_0.17","t02_0.33","t03_0.50","t04_0.67","t05_0.83"],Rr=it.length,Lt=new Set(["aa__kk","aa__nn","aa__sil","CH__aa","CH__DD","CH__E","CH__FF","CH__I","CH__kk","CH__nn","CH__O","CH__PP","CH__RR","CH__sil","CH__SS","CH__TH","CH__U","DD__aa","DD__E","DD__FF","DD__I","DD__kk","DD__nn","DD__O","DD__PP","DD__RR","DD__sil","DD__SS","DD__TH","DD__U","E__aa","E__FF","E__I","E__kk","E__nn","E__O","E__PP","E__RR","E__sil","E__SS","E__TH","E__U","FF__aa","FF__I","FF__kk","FF__nn","FF__O","FF__PP","FF__RR","FF__sil","FF__SS","FF__TH","FF__U","I__aa","I__kk","I__nn","I__O","I__PP","I__RR","I__sil","I__SS","I__TH","I__U","kk__nn","kk__sil","nn__sil","O__aa","O__kk","O__nn","O__PP","O__RR","O__sil","O__SS","O__TH","O__U","PP__aa","PP__kk","PP__nn","PP__RR","PP__sil","PP__SS","PP__TH","PP__U","RR__aa","RR__kk","RR__nn","RR__sil","RR__SS","RR__TH","RR__U","SS__aa","SS__kk","SS__nn","SS__sil","SS__TH","SS__U","TH__aa","TH__kk","TH__nn","TH__sil","TH__U","U__aa","U__kk","U__nn","U__sil"]);function Ot(e,t){return`${e}__${t}`}function Bt(e,t){return`pairs/${e}/${t}_minus_sil.png`}function We(e){return`${e}_minus_sil.png`}function Ir(e,t){if(t.has(e))return e;const n=/^pairs\/([^/]+)\//.exec(e);if(!n)return null;const r=n[1].split("__")[1];if(!r||r==="sil")return null;const s=We(r);return t.has(s)?s:null}function an(e,t){return e.filter(n=>t.has(n))}function Dr(e,t,n){const r=wt(e,t);return an(r,n).length>0}function wt(e,t){if(e===t)return[];const n=[],r=Ot(e,t);if(Lt.has(r)){for(const i of it)n.push(Bt(r,i));return n}const s=Ot(t,e);if(Lt.has(s)){for(let i=Rr-1;i>=0;i--)n.push(Bt(s,it[i]));return n}return[]}function Cr(e){return e<16?[]:e<35?[2]:e<51?[0,4]:e<67?[0,2,4]:e<83?[0,1,2,3]:[0,1,2,3,4]}function Mr(e,t){return Cr(t).map(r=>e[r]).filter(r=>r!=null)}function xr(e,t,n,r){if(r<=0)return 0;const i=Math.max(1,n-t)/r,o=e-t,a=Math.floor(o/i);return Math.min(r-1,Math.max(0,a))}function Pr(e,t,n){if(!n)return`${e}/${t}`;const r=t.split("/").pop()??t,s=r.includes(".")?r.slice(0,r.lastIndexOf(".")):r;return`${e}/${s}.bin`}const Lr=[.32,.24,.16,.08,0],Or=Lr.map(e=>`eyes_${e.toFixed(2)}`),Br=["eyes_0.32","eyes_0.16","eyes_0.00"],Fr=["eyes_0.32","eyes_0.16","eyes_0.08","eyes_0.00"],Ft=2,Ur=-30,ot=20,Nr={fear:-30,anger:-20,sad:-10,neutral:0,love:10,happy:0,disgust:20};function gt(e){return`brow_${e}`}function pt(e){return Nr[e]}function Ee(e){return Math.max(Ur,Math.min(ot,e))}function Vr(e,t){if(e===t)return[];const n=e<t?Ft:-Ft,r=[];let s=e;for(;s!==t;)s+=n,r.push(s);return r}const $r={fear:"fear",afraid:"fear",anxiety:"fear",anger:"anger",angry:"anger",mad:"anger",sad:"sad",sadness:"sad",sorrow:"sad",neutral:"neutral",calm:"neutral",love:"love",loving:"love",affection:"love",happy:"happy",happiness:"happy",joy:"happy",joyful:"happy",excited:"happy",disgust:"disgust",disgusted:"disgust"};function ve(e,t="neutral"){const n=String(e??"").trim().toLowerCase();return n?$r[n]??t:t}function cn(e){return e===0?null:_t(gt(e),"eyes_standard")}function un(e){return cn(pt(e))}function Ut(e,t){const n=gt(pt(e));return!t||t==="eyes_standard"?un(e):e==="neutral"&&t==="eyes_standard"?null:_t(n,t)}function Wr(e,t){const n=[];for(const r of t)n.push(Ut(e,r));for(let r=t.length-2;r>=0;r--)n.push(Ut(e,t[r]));return n.push(un(e)),n}function Hr(e){return _t(gt(e),"eyes_standard")}function qr(e,t){return Vr(e,t).map(Hr)}const Kr=6;function jr(e,t){for(const n of e)if(t>=n.wtime&&t<n.wtime+n.wduration)return n;return null}function Gr(e){const t=Ee(e);return t>=ot?ot:Ee(t+Kr)}function zr(e,t,n){const r=Ee(n);return jr(t,e)?.emphasis?Gr(r):r}function Nt(e){if(!e)return 0;const t=e.match(/^brow_(-?\d+)\//);if(!t?.[1])return null;const n=Number.parseInt(t[1],10);return Number.isFinite(n)?Ee(n):null}function Ce(e,t){return e+Math.random()*(t-e)}function Fe(){return Ce(14,32)}function Yr(e={}){const{initialDelayMs:t=800+Math.random()*1200,intervalMinMs:n=2200,intervalMaxMs:r=5800,doubleBlinkChance:s=.18,doubleBlinkGapMinMs:i=120,doubleBlinkGapMaxMs:o=220,fullBlinkChance:a=.75,postEmotionChangeDelayMs:f=380,minDwellMs:l=550}=e;let m="neutral",p="neutral",_=0,S=0,k=0,u={kind:"idle",nextBlinkAt:performance.now()+t},y=null,d="__eyes_open__|neutral|brow:0";function C(v=m){d=`${y??"__eyes_open__"}|${v}|brow:${S}`}function E(v,M=m){y=v;const w=Nt(v);w!=null?S=w:v==null&&(S=0),C(M)}function R(v){return cn(v)}function U(v,M,w){const T=Ee(M),A=Ee(w);if(_=A,T===A){E(R(A));return}const P=[...qr(T,A),R(A)];u={kind:"browBlend",paths:P,index:0,holdUntil:v+Fe(),targetBrow:A},E(P[0]??null)}function V(v,M){u={kind:"idle",nextBlinkAt:v+Ce(n,r)},p=M,E(R(_),M)}function z(v,M,w){const T=w?Br:Math.random()<a?Or:Fr;u={kind:"playing",paths:Wr(M,T),index:0,holdUntil:v+Fe(),after:"idle",blinkEmotion:M},p=M,E(u.paths[0]??null,M)}function K(v){if(u.kind==="browBlend"){for(;v>=u.holdUntil&&u.index<u.paths.length-1;)u.index+=1,u.holdUntil=v+Fe();const M=u.paths[u.index]??null;E(M),v>=u.holdUntil&&u.index>=u.paths.length-1&&(S=u.targetBrow,_=u.targetBrow,E(R(u.targetBrow)),u={kind:"idle",nextBlinkAt:v+f});return}if(u.kind==="idle"){E(R(_),p),v>=u.nextBlinkAt&&z(v,p,!1);return}if(u.kind==="doublePause"){E(R(_),u.blinkEmotion),v>=u.resumeAt&&z(v,u.blinkEmotion,!0);return}if(u.kind==="playing"){for(;v>=u.holdUntil&&u.index<u.paths.length-1;)u.index+=1,u.holdUntil=v+Fe();if(E(u.paths[u.index]??null,u.blinkEmotion),v>=u.holdUntil&&u.index>=u.paths.length-1){if(u.after==="resumeIdle"){V(v+f,m);return}if(u.after==="doublePause"){u={kind:"doublePause",resumeAt:v+Ce(i,o),blinkEmotion:u.blinkEmotion},E(R(_),u.blinkEmotion);return}if(u.after==="idle"&&Math.random()<s){u={kind:"doublePause",resumeAt:v+Ce(i,o),blinkEmotion:u.blinkEmotion},E(R(_),u.blinkEmotion);return}V(v,u.blinkEmotion)}}}return{advance:K,setTargetEmotion(v){const M=ve(v,m);if(M===m)return;const w=performance.now();l>0&&M!==m&&!(m==="neutral"&&M!=="neutral")&&w-k<l||(m=M,p=M,k=w)},setTargetEyebrow(v){const M=Ee(v);if(M===_&&u.kind!=="browBlend")return;const w=performance.now(),T=u.kind==="browBlend"?Nt(y)??S:S;_=M,!(M===T&&u.kind!=="browBlend")&&(u.kind==="playing"||u.kind==="doublePause"||U(w,T,M))},getTargetEmotion:()=>m,getTargetEyebrow:()=>_,getExpressionPath:()=>y,getDrawKey:()=>d,reset(){m="neutral",p="neutral",_=0,S=0,k=0,V(performance.now()+Ce(n,r),"neutral")}}}function Jr(){const e=document.createElement("video");e.muted=!0,e.playsInline=!0,e.preload="auto",e.setAttribute("playsinline",""),e.loop=!0;let t=!1,n=null,r="";const s=()=>{n&&(URL.revokeObjectURL(n),n=null)};return{async load(i){t&&r===i||(s(),t=!1,r=i,i.startsWith("blob:")&&(n=i),await new Promise((o,a)=>{const f=()=>{const l=e.duration;if(!Number.isFinite(l)||l<=0){a(new Error(`Idle video has invalid duration: ${i}`));return}t=!0,e.currentTime=0,o()};e.addEventListener("loadeddata",f,{once:!0}),e.addEventListener("error",()=>a(new Error(`Failed to load idle video: ${i}`)),{once:!0}),e.src=i,e.load()}))},isReady(){return t},getVideo(){return e},setActive(i){t&&(i?e.paused&&e.play().catch(()=>{}):e.paused||e.pause())},restart(){t&&(e.currentTime=0)},dispose(){e.pause(),e.removeAttribute("src"),e.load(),s(),t=!1,r=""}}}const Qr=50,ln=400;function Xr(e,t,n){return e.filter(r=>{const s=r.queueIndex??-1;return s>=t&&s<=n})}function Zr(e,t){const n=[];for(const r of e){const s=Xr(t,r.start_word,r.end_word);if(s.length===0)continue;const i=s[0],o=s[s.length-1];n.push({sentence:r,startMs:Math.max(0,i.wtime-Qr),endMs:o.wtime+o.wduration+ln})}return n.sort((r,s)=>r.startMs-s.startMs)}function es(e,t,n,r="neutral"){const s=ve(r);if(t.length===0)return s;if(n.length===0||e<n[0].wtime)return ve(t[0]?.emotion,s);const i=Zr(t,n);let o=null;for(const l of i)e>=l.startMs&&e<=l.endMs&&(o=l);if(o)return ve(o.sentence.emotion,s);const a=n[n.length-1],f=a.wtime+a.wduration;return e>f+ln?ve(t[t.length-1]?.emotion,s):s}function fn(e,t,n,r,s=sn){const i=new OffscreenCanvas(n,r),o=i.getContext("2d",{willReadFrequently:!0});o.drawImage(e,0,0,n,r);const a=o.getImageData(0,0,n,r),f=o.createImageData(n,r);f.data.set(a.data);const m=new OffscreenCanvas(n,r).getContext("2d",{willReadFrequently:!0});for(const p of t){m.clearRect(0,0,n,r),m.drawImage(p,0,0,n,r);const _=m.getImageData(0,0,n,r);for(let S=0;S<f.data.length;S+=4){const k=_.data[S],u=_.data[S+1],y=_.data[S+2];Math.max(k,u,y)>s&&(f.data[S]=k,f.data[S+1]=u,f.data[S+2]=y,f.data[S+3]=255)}}return o.putImageData(f,0,0),i}let pe=null,Me=null;const He=new Map;function ts(){pe=null,Me=null,He.clear()}Kn(ts);function ns(e){return e??Me??void 0}function yt(e){return new Promise((t,n)=>{const r=new Image;r.decoding="async",r.onload=()=>t(r),r.onerror=()=>n(new Error(`Failed to load image: ${e}`)),r.src=e})}async function rs(e){return Ke()?vr(await Ye(Ct(),e??"")):yt(Ct())}async function Vt(e,t,n,r){const s=n?await Sr(e,r):await fetch(e).then(l=>{if(!l.ok)throw new Error(`Failed to load ${e}`);return l.json()}),i=s.sheets?.[0];if(!i?.path)throw new Error(`${e} has no sheets[0].path`);const o=n?await kr(await Ye(Pr(t,i.path,n),r??"")):await yt(`${t}/${i.path}`),a=new Map;for(const l of s.cells??[])l.path&&a.set(l.path,l);const f=new Map;for(const l of s.sheets??[])f.set(l.index,l);return{atlas:o,atlasMeta:s,cellByPath:a,sheetByIndex:f,diffBase:t}}async function at(e){const t=ns(e),n=Ke();if(n&&!t)throw new Error("Encrypted assets enabled but no key provided");if(pe&&(!n||Me===(t??null))){const i=await pe;return{...xe(i),visemePaths:[...i.viseme.cellByPath.keys()]}}Me=t??null,He.clear();const r=Gn();pe=(async()=>{const i=await rs(t),o=await Vt(zn(),r,n,t);let a=null;try{a=await Vt(Yn(),r,n,t)}catch{a=null}return{sil:i,viseme:o,expression:a}})();const s=await pe;return{...xe(s),visemePaths:[...s.viseme.cellByPath.keys()]}}async function dn(e){if(await at(e),!pe)throw new Error("Assets not loaded");return pe}function xe(e){return pr(e.viseme.atlasMeta,e.sil.naturalWidth,e.sil.naturalHeight)}function ss(e,t){const n=document.createElement("canvas");return n.width=t.w,n.height=t.h,n.getContext("2d",{willReadFrequently:!0}).drawImage(e.atlas,t.x,t.y,t.w,t.h,0,0,t.w,t.h),n}async function $t(e,t,n){const r=`${t.diffBase}::${n}`,s=He.get(r);if(s)return s;const i=(async()=>{const o=t.cellByPath.get(n);if(!o)throw new Error(`No atlas cell for path: ${n}`);const a=yr(t.atlasMeta,o),f=xe(e);if(Er(o)){const p=ss(t,o),_=document.createElement("canvas");_.width=f.width,_.height=f.height;const S=_.getContext("2d");return S.fillStyle="#000",S.fillRect(0,0,f.width,f.height),S.drawImage(p,a.x,a.y),_}const l=`${t.diffBase}/${n}`,m=await yt(l);if(m.naturalWidth===f.width&&m.naturalHeight===f.height)return m;throw new Error(`Diff PNG wrong size for ${n}`)})();return He.set(r,i),i}async function Wt(e,t,n,r,s,i){if(s){const{width:S,height:k}=s;if(i&&!i()||t.readyState<HTMLMediaElement.HAVE_CURRENT_DATA)return;const u=document.createElement("canvas");u.width=S,u.height=k;const y=u.getContext("2d");if(y.clearRect(0,0,S,k),y.drawImage(t,0,0,S,k),i&&!i())return;(e.width!==S||e.height!==k)&&(e.width=S,e.height=k);const d=e.getContext("2d");d.clearRect(0,0,S,k),d.drawImage(u,0,0);return}const o=await dn(),a=on(o.viseme.atlasMeta),{width:f,height:l}=xe(o);(e.width!==f||e.height!==l)&&(e.width=f,e.height=l);const m=[],p=e.getContext("2d");if(p.clearRect(0,0,f,l),t.readyState<HTMLMediaElement.HAVE_CURRENT_DATA){const S=o.sil;p.drawImage(S,0,0,f,l);return}if(m.length===0){p.drawImage(t,0,0,f,l);return}const _=fn(t,m,f,l,a);p.drawImage(_,0,0)}async function ct(e,t,n){const r=await dn(Me??void 0),s=on(r.viseme.atlasMeta),{width:i,height:o}=xe(r);(e.width!==i||e.height!==o)&&(e.width=i,e.height=o);const a=[];if(t.mouthPath){const m=new Set(r.viseme.cellByPath.keys()),p=Ir(t.mouthPath,m)??(m.has(t.mouthPath)?t.mouthPath:null);p&&a.push(await $t(r,r.viseme,p))}t.expressionPath&&r.expression&&a.push(await $t(r,r.expression,t.expressionPath));const f=e.getContext("2d");if(f.clearRect(0,0,i,o),a.length===0){f.drawImage(r.sil,0,0,i,o);return}const l=fn(r.sil,a,i,o,s);f.drawImage(l,0,0)}async function is(e){await ct(e,{mouthPath:null,expressionPath:null})}function os(e){const t=new Map;for(const n of e)t.has(n.vtime)||t.set(n.vtime,n);return[...t.values()].sort((n,r)=>n.vtime-r.vtime)}function as(e){const t=os(e),n=[];for(let r=0;r<t.length-1;r++){const s=t[r].viseme,i=t[r+1].viseme,o=t[r].vtime,a=t[r+1].vtime;s===i||a<=o||n.push({from:s,to:i,fromVtime:o,toVtime:a})}return n}function cs(e,t){let n=null;for(const r of e)t>=r.fromVtime&&t<r.toVtime&&(n=r);return n}function us(e,t){return{gapMs:Math.max(0,t-e),holdEnd:e,transStart:e}}const hn=400,ls=200,fs=600,ds="__sil__";function hs(){return{lastDrawnKey:""}}function he(e){e.lastDrawnKey="",e.transitionCache=void 0}function ms(e,t){if(t?.transitionCache&&t.transitionCache.queueLength===e.length)return t.transitionCache.transitions;const n=as(e);return t&&(t.transitionCache={queueLength:e.length,transitions:n}),n}function _s(e){let t="sil";for(const n of e)n.viseme!=="sil"&&(t=n.viseme);return t}function ws(e,t){return e==="sil"?!1:t?Dr(e,"sil",t):wt(e,"sil").length>0}function gs(){return hn+fs}function Ht(e){return e??ds}function ps(e,t,n,r){if(e.length===0)return{diffPath:null,label:"sil"};const s=ms(e,r),i=cs(s,t);if(i){const a=i.toVtime-i.fromVtime,f=us(i.fromVtime,i.toVtime);if(t>=f.transStart&&t<i.toVtime){let l=wt(i.from,i.to);n&&(l=an(l,n));const m=Mr(l,a);if(m.length>0){const p=xr(t,f.transStart,i.toVtime,m.length);return{diffPath:m[p]??m[m.length-1],label:`${i.from}→${i.to}`}}return{diffPath:We(i.to),label:i.to}}}const o=Ge(e,t);return o==="sil"?{diffPath:null,label:"sil"}:{diffPath:We(o),label:o}}function mn(e,t){const n=t??Ht(e.expressionPath);return`${Ht(e.mouthPath)}|${n}`}function ys(e,t,n,r,s,i,o,a,f,l){const m=ps(t,n,f,i),p={mouthPath:m.diffPath,expressionPath:r},_=mn(p,s);_!==i.lastDrawnKey&&(a(m.label),o(e,p,_,S=>{S===_&&(i.lastDrawnKey=_)}))}function qt(e,t,n,r,s,i,o=null){const a={mouthPath:null,expressionPath:t},f=o!=null?`|idle@${Math.round(o*1e3)}`:"",l=mn(a,n)+f;!(o!=null)&&l===r.lastDrawnKey||(i(o!=null?"idle":"sil"),s(e,a,l,p=>{p===l&&(r.lastDrawnKey=l)}))}function _n(e){let t=0,n=null,r=!1,s=!1;const i=async()=>{if(!r){r=!0;try{for(;n;){const a=n;n=null;const f=++t,l=await e(a.diffs,f,a);n||l&&f===t&&a.onDisplayed?.(a.drawKey)}}finally{r=!1,n&&i()}}},o=()=>{s||(s=!0,queueMicrotask(()=>{s=!1,i()}))};return{enqueue(a,f,l){n={diffs:a,drawKey:f,onDisplayed:l},o()},reset(){t+=1,n=null},isLatestSeq(a){return a===t},getLatestSeq(){return t}}}const Kt=-1,jt=new Map;async function Es(e){let t=jt.get(e);if(!t){const n=await fetch(e);if(!n.ok)throw new Error(`Failed to fetch worker script (${n.status}): ${e}`);const r=await n.blob();t=URL.createObjectURL(r),jt.set(e,t)}return new Worker(t,{name:"visemeDiffPreview"})}function Ss(){return typeof Worker<"u"&&typeof OffscreenCanvas<"u"}function ks(e,t){(e.width!==t.width||e.height!==t.height)&&(e.width=t.width,e.height=t.height);const n=e.getContext("2d");n&&n.drawImage(t,0,0)}function Gt(e,t){let n=0,r=null;const s=_n(async(i,o)=>(await at(),await ct(e,i),s.isLatestSeq(o)));return{usesWorker:!1,async loadAssets(i){const o=await at(i);return r=o.visemePaths?new Set(o.visemePaths):null,o},getVisemeAtlasPaths(){return r},async renderSilOnly(){n+=1,s.reset(),await is(e)},drawLiveFrame(i,o,a,f){s.enqueue(o,a,f)},async renderViseme(i,o){n+=1;const a=n;t.onStatus?.(o),await ct(i,{mouthPath:We(o),expressionPath:null})},dispose(){n+=1,s.reset()}}}function vs(e,t,n){let r=null,s=1,i=0,o=!1,a=!1,f=null;const l=new Map,m=_n(async(y,d,C)=>(l.set(d,{drawKey:C.drawKey,onDisplayed:C.onDisplayed}),await k({type:"drawFrame",diffPath:y.mouthPath,expressionDiffPath:y.expressionPath,liveSeq:d},void 0,!0),!1)),p=y=>{if(y===Kt){let E=-1,R;for(const[U,V]of l)U>=E&&(E=U,R=V);E>=0&&l.delete(E),R?.onDisplayed?.(R.drawKey);return}const d=-y,C=l.get(d);l.delete(d),C?.onDisplayed?.(C.drawKey)},_=new Map,S=(y,d)=>{a||!r||r.postMessage(y,[])},k=(y,d,C=!1)=>{if(a)return Promise.resolve();const E=s++;return new Promise((R,U)=>{_.set(E,{resolve:V=>R(V),reject:U,generation:typeof y.generation=="number"?y.generation:void 0,expectRenderDone:C}),S({...y,requestId:E})})},u=(async()=>{r=await Es(n),r.onmessage=y=>{if(a)return;const d=y.data;if(d.type==="status"){t.onStatus?.(d.label);return}if(d.type==="frame"){const E=d.generation<0;if(!E&&d.generation!==i){d.bitmap.close();return}if(E&&!(d.generation===Kt)){const U=-d.generation;if(!m.isLatestSeq(U)){d.bitmap.close();return}}if(E&&t.shouldAcceptLiveFrame&&!t.shouldAcceptLiveFrame()){d.bitmap.close();return}ks(e,d.bitmap),d.bitmap.close(),E&&p(d.generation);return}const C=_.get(d.requestId);if(C)switch(d.type){case"ready":_.delete(d.requestId),C.resolve();break;case"loadAssetsDone":_.delete(d.requestId),f=d.visemePaths?.length?new Set(d.visemePaths):null,C.resolve({width:d.width,height:d.height,visemePaths:d.visemePaths});break;case"renderDone":if(C.expectRenderDone&&C.generation!=null&&d.generation!==C.generation)return;_.delete(d.requestId),C.resolve();break;case"renderAborted":_.delete(d.requestId),C.resolve();break;case"error":_.delete(d.requestId),t.onError?.(d.message),C.reject(new Error(d.message));break}},r.onerror=y=>{if(a)return;const d=y.message||"Worker error";t.onError?.(d);for(const[,C]of _)C.reject(new Error(d));_.clear()},await k({type:"init"}),o=!0})();return{usesWorker:!0,async loadAssets(y){if(a)return{width:842,height:1264};if(await u,a)return{width:842,height:1264};if(!o)throw new Error("Worker init failed");return await k({type:"loadAssets",keyHex:y,urls:qn(ke())})??{width:842,height:1264}},getVisemeAtlasPaths(){return f},async renderSilOnly(){if(a||(await u,a))return;i+=1,m.reset(),l.clear();const y=i;await k({type:"cancelLiveDraws"}),await k({type:"renderSil",generation:y},void 0,!0)},drawLiveFrame(y,d,C,E){a||u.then(()=>{a||m.enqueue(d,C,E)})},async renderViseme(y,d,C={}){if(a||(await u,a))return;i+=1,m.reset(),l.clear();const E=i;try{await k({type:"cancelLiveDraws"}),await k({type:"renderViseme",generation:E,to:d,from:C.from,transitionDurationMs:C.transitionDurationMs,gapMs:C.gapMs,threshold:C.threshold},void 0,!0)}catch(R){if(R?.name==="AbortError")return;throw R}},dispose(){if(!a){a=!0,i+=1,m.reset(),l.clear();for(const[,y]of _)y.resolve();_.clear(),r?.terminate(),r=null}}}}function Ts(e,t={}){if(!Ss())return Gt(e,t);const n=Rn(t.workerScriptUrl);try{return vs(e,t,n)}catch{return Gt(e,t)}}class bs extends Error{constructor(t,n){super(`${t} timed out after ${Math.round(n/1e3)}s`),this.name="LoadTimeoutError"}}function tt(e,t,n){return new Promise((r,s)=>{const i=globalThis.setTimeout(()=>{s(new bs(n,t))},t);e.then(o=>{globalThis.clearTimeout(i),r(o)},o=>{globalThis.clearTimeout(i),s(o)})})}const As={position:"relative",display:"inline-block",lineHeight:0},Rs={display:"block",maxWidth:"100%",height:"auto"},Is={position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",padding:"1rem",background:"rgba(0,0,0,0.55)",color:"#fff",fontSize:"0.875rem",textAlign:"center"};function Ue(e){return Mn(e)?tn:dt}function me(e,t){return t?.isSpeaking()?t:e.isSpeaking()?e:t?.isConnected()?t:e}const Ds=g.forwardRef(function({id:t,assets:n,authToken:r,ttsEngineId:s=ye,voiceId:i,speakingRate:o=ze,className:a,style:f,canvasStyle:l,onStatusChange:m,onDisplayStatus:p,onError:_,onReady:S,showErrorOverlay:k=!0,onUserTranscript:u,onBotOutput:y,onRealtimeLipsyncDebug:d},C){const E=g.useRef(null),R=g.useRef(null),U=g.useRef(hs()),V=g.useRef(Yr()),z=g.useRef(0),K=g.useRef(0),v=g.useRef(0),M=g.useRef(null),w=g.useRef(null),T=g.useRef(!1),A=g.useRef(!1),P=g.useRef(!1),x=g.useRef("sil"),B=g.useRef(!1),I=g.useRef(null),$=g.useRef(!1),[Y,_e]=g.useState("idle"),[re,j]=g.useState(null),[X,Z]=g.useState(!1),[c,b]=g.useState(!1),[O,Q]=g.useState({width:842,height:1264}),G=g.useRef(r??null),ce=g.useRef(null),W=g.useRef(null),we=g.useRef(!1),ue=g.useRef({width:842,height:1264}),Je=g.useRef(null),Et=g.useRef(S),ee=g.useRef(p),St=g.useRef(_),kt=g.useRef(u),vt=g.useRef(y),Tt=g.useRef(d);Et.current=S,ee.current=p,St.current=_,kt.current=u,vt.current=y,Tt.current=d;const bt=g.useCallback(h=>{const D=h?.ttsEngineId??s??W.current?.ttsEngineId??ye,L=h?.voiceId??i??W.current?.voiceId??Ue(D);return{ttsEngineId:D,voiceId:L}},[s,i]),le=g.useCallback(h=>{j(h),St.current?.(h)},[]),Ae=g.useCallback(h=>{_e(h),m?.(h)},[m]),H=g.useRef(st(Ae,Te)),q=g.useRef(null);g.useEffect(()=>(H.current=st(Ae,Te),r&&H.current.setDeveloperToken(r),H.current.setTtsEngineId(s),()=>{H.current.stop();const h=q.current;h&&(h.disconnect(),q.current=null)}),[Ae,r,s]);const At=g.useCallback(()=>(q.current||(q.current=rn(Ae,{speakingRate:o,onUserTranscript:(h,D)=>kt.current?.(h,D),onBotOutput:h=>vt.current?.(h),onLipsyncDebug:h=>Tt.current?.(h)}),q.current.setTtsEngineId(s)),q.current),[Ae,o,s]),se=g.useCallback(()=>{M.current!=null&&(clearTimeout(M.current),M.current=null)},[]),oe=g.useCallback(()=>{w.current!=null&&(clearTimeout(w.current),w.current=null),P.current=!1},[]),Rt=g.useCallback(()=>{!$.current||!I.current||(oe(),P.current=!0,w.current=setTimeout(()=>{if(w.current=null,me(H.current,q.current).isSpeaking()){P.current=!1;return}if(T.current){P.current=!1;return}const h=I.current;if(!h){P.current=!1;return}h.restart(),h.setActive(!0);const D=h.getVideo();let L=!1;const F=()=>{L||(L=!0,D.removeEventListener("playing",N),P.current=!1)},N=()=>{F()};if(D.addEventListener("playing",N,{once:!0}),D.readyState>=HTMLMediaElement.HAVE_CURRENT_DATA&&!D.paused){F();return}setTimeout(F,300)},ls))},[oe]),Pe=g.useCallback(()=>{if(se(),ee.current?.("sil"),T.current=!1,!$.current||!I.current){P.current=!1;return}Rt()},[se,Rt]),Qe=g.useCallback(async h=>{const D=K.current,L=R.current,F=E.current,N=()=>{h?.fallbackOnly||(T.current=!1)};if(!L||!F||!X){N();return}if(me(H.current,q.current).isSpeaking()){N();return}if(h?.fallbackOnly){if(!T.current||(he(U.current),await L.renderSilOnly(),D!==K.current)||me(H.current,q.current).isSpeaking())return;Pe();return}const te=x.current!=="sil"?x.current:_s(me(H.current,q.current).getVisemeQueue()),ie=ws(te,Je.current);if(he(U.current),!ie){if(await L.renderSilOnly(),D!==K.current||me(H.current,q.current).isSpeaking())return;Pe();return}try{await L.renderViseme(F,"sil",{from:te,transitionDurationMs:hn})}catch(fe){if(fe?.name!=="AbortError")throw fe}if(D!==K.current){N();return}if(me(H.current,q.current).isSpeaking()){N();return}Pe()},[X,Pe]),Re=g.useCallback(()=>{if(A.current||T.current)return;A.current=!0,T.current=!0,se();const h=K.current;Qe().finally(()=>{A.current=!1}),M.current=setTimeout(()=>{M.current=null,h===K.current&&Qe({fallbackOnly:!0})},gs())},[se,Qe]),It=g.useCallback(async()=>{const h=Math.floor(Date.now()/1e3);if(ce.current&&h<ce.current.expiresAt-60)return ce.current.key;const D=G.current??await Ne();G.current=D,H.current.setDeveloperToken(D);const L=await Un(D,Te);return ce.current={key:L.value,expiresAt:L.expiresAt},L.value},[]);g.useEffect(()=>{r&&(G.current=r,H.current.setDeveloperToken(r))},[r]),g.useEffect(()=>{H.current.setTtsEngineId(s),q.current?.setTtsEngineId(s),W.current&&(W.current.ttsEngineId=s)},[s]),g.useEffect(()=>{const h=i??Ue(s);W.current&&(W.current.voiceId=h)},[i,s]);const wn=g.useMemo(()=>n?JSON.stringify(n):`id:${t??""}`,[n,t]);return g.useEffect(()=>{if(!t&&!n){le("AiTwin requires either `id` or `assets`");return}let h=!1;we.current=!1,Z(!1),b(!1),j(null),W.current=null,I.current=null,$.current=!1;const D=E.current;if(!D)return;let L=null;return(async()=>{try{let F,N,te;if(n)F=n,N=s,te=i??Ue(N);else{ee.current?.("getAiTwin");const J=await tt(Qt(t),3e4,"getAiTwin");if(h)return;if(!J.faceId?.trim())throw new Error("getAiTwin returned no faceId");N=J.ttsEngineId,te=i??J.voiceId??Ue(N),F=Wn(J.faceId)}W.current={ttsEngineId:N,voiceId:te},Hn(F),I.current=Jr(),L=Ts(D,{onStatus:J=>{h||ee.current?.(J)},onError:J=>{h||le(J)},shouldAcceptLiveFrame:()=>me(H.current,q.current).isSpeaking()&&!T.current}),R.current=L,ee.current?.("auth");const ie=G.current??r??await tt(Ne(),3e4,"getAuthToken");if(h)return;G.current=ie,H.current.setDeveloperToken(ie),H.current.setTtsEngineId(N);const fe=Ke()?await It():void 0;if(h)return;ee.current?.("loadAssets");const ae=await tt(L.loadAssets(fe),18e4,"load face assets");if(!h&&ae.width>0&&ae.height>0&&(ue.current=ae,Q(ae)),h)return;Je.current=L.getVisemeAtlasPaths(),we.current=!0,Z(!0),Et.current?.(),L.renderSilOnly().then(()=>{h||ee.current?.("sil")}),(async()=>{try{const J=await br(fe);if(await I.current.load(J),h)return;$.current=!0,b(!0),he(U.current),I.current.setActive(!0);const ne=I.current.getVideo();await Wt(D,ne,null,void 0,ue.current),h||ee.current?.("idle")}catch(J){if(h)return;$.current=!1,b(!1),console.warn("[AiTwin] Idle video unavailable:",J)}})()}catch(F){if(h)return;if(F instanceof Ve){le(F.message);return}le(F instanceof Error?F.message:"Failed to load AI twin")}})(),()=>{h=!0,I.current?.dispose(),L?.dispose(),R.current=null}},[wn,n,s,i,r,It,le]),g.useEffect(()=>{const h={current:!1},D=()=>{const L=E.current,F=R.current,N=me(H.current,q.current);if(!L||!F||!X){z.current=requestAnimationFrame(D);return}const te=N.isSpeaking(),ie=te&&!B.current,fe=B.current&&!te;ie&&(se(),oe(),T.current=!1,A.current=!1,he(U.current),v.current=0,x.current="sil",I.current?.setActive(!1)),fe&&(V.current.reset(),I.current?.setActive(!1),Re());const ae=N.getPlaybackElapsedMs(),J=fe||T.current||P.current;if(te){$.current&&I.current?.setActive(!1);const ne=N.getVisemeQueue(),ge=ne.length>0?Ge(ne,ae):"sil";ge!=="sil"&&(x.current=ge);const de=es(ae,N.getSentenceEmotions(),N.getWordQueue(),N.getStreamMood());V.current.setTargetEmotion(de),V.current.setTargetEyebrow(zr(ae,N.getWordQueue(),pt(de))),V.current.advance(performance.now());const Ie=V.current.getExpressionPath(),Xe=V.current.getDrawKey();ys(L,ne,ae,Ie,Xe,U.current,(De,gn,pn,yn)=>F.drawLiveFrame(De,gn,pn,yn),De=>ee.current?.(De),Je.current)}else if(!J&&$.current&&I.current){const ne=I.current,ge=ne.getVideo();ge.readyState>=HTMLMediaElement.HAVE_CURRENT_DATA?(ne.setActive(!0),h.current||(h.current=!0,Wt(L,ge,null,void 0,ue.current,()=>!N.isSpeaking()&&!T.current&&!P.current).then(()=>ee.current?.("idle")).finally(()=>{h.current=!1}))):qt(L,null,"__sil__",U.current,(de,Ie,Xe,De)=>F.drawLiveFrame(de,Ie,Xe,De),de=>ee.current?.(de))}else J||qt(L,null,"__sil__",U.current,(ne,ge,de,Ie)=>F.drawLiveFrame(ne,ge,de,Ie),ne=>ee.current?.(ne));B.current=te,z.current=requestAnimationFrame(D)};return z.current=requestAnimationFrame(D),()=>{cancelAnimationFrame(z.current),se(),oe(),H.current.stop(),q.current?.stop()}},[X,c,Re,se,oe]),g.useImperativeHandle(C,()=>({speakText:async(h,D)=>{const L=h.trim();if(!L)return;const F=R.current;if(!F||!we.current&&!X)throw new Error("AI twin is not ready");const{ttsEngineId:N,voiceId:te}=bt(D);j(null),K.current+=1,se(),oe(),T.current=!1,A.current=!1,x.current="sil",he(U.current),V.current.reset(),B.current=!1;try{$.current?(I.current?.setActive(!1),I.current?.restart(),he(U.current)):await F.renderSilOnly(),await H.current.speak(L,{voiceId:te,speakingRate:D?.speakingRate??o,ttsEngineId:N})}catch(ie){throw le(ie instanceof Error?ie.message:"TTS failed"),ie}},stop:()=>{K.current+=1,se(),oe(),T.current=!1,A.current=!1,H.current.stop(),q.current?.stop(),he(U.current),B.current=!1,Re()},setTtsEngineId:h=>{H.current.setTtsEngineId(h),q.current?.setTtsEngineId(h),W.current&&(W.current.ttsEngineId=h)},renderViseme:async(h,D)=>{const L=R.current,F=E.current;if(!L||!F||!we.current)throw new Error("AI twin is not ready");await L.renderViseme(F,h,{from:D?.from,transitionDurationMs:D?.transitionDurationMs,gapMs:D?.gapMs,onStatus:N=>ee.current?.(N)})},isReady:()=>we.current,getStatus:()=>Y,connect:async(h={})=>{j(null);try{let D=h.authToken?.trim();D||(D=G.current??r??await Ne(),G.current=D);const L=h.voiceId?.trim()??i??W.current?.voiceId,F=At();await F.unlockAudio(),await F.connect({...h,authToken:D,voiceId:L,speakingRate:h.speakingRate??o})}catch(D){throw le(D instanceof Error?D.message:"Voice connect failed"),D}},disconnect:async()=>{const h=q.current;h&&(K.current+=1,se(),oe(),T.current=!1,A.current=!1,he(U.current),B.current=!1,await h.disconnect(),Re())},isConnected:()=>q.current?.isConnected()??!1}),[X,o,r,i,se,oe,Re,le,bt,At,Y]),Ze.jsxs("div",{className:a,style:{...As,...f},children:[Ze.jsx("canvas",{ref:E,width:O.width,height:O.height,style:{...Rs,...l},"aria-label":t?`AI twin ${t}`:"AI twin face"}),k&&re?Ze.jsx("div",{style:Is,children:re}):null]})});exports.AiTwin=Ds;exports.AiTwinNotFoundError=Ve;exports.DEFAULT_TTS_ENGINE_ID=ye;exports.MIC_SAMPLE_RATE=ht;exports.OCULUS_VISEME_IDS=Xt;exports.PLAYBACK_SAMPLE_RATE=mt;exports.SPEAKING_RATE_MAX=ft;exports.SPEAKING_RATE_MIN=lt;exports.TTS_ENGINE_CARTESIA=Se;exports.TTS_ENGINE_GOOGLE=ut;exports.TTS_ENGINE_INWORLD=qe;exports.VISEME_IDS=Ar;exports.VISEME_TEST_CARTESIA_VOICE_ID=tn;exports.VISEME_TEST_SPEAKING_RATE=ze;exports.VISEME_TEST_VOICE_ID=dt;exports.buildVoiceWebSocketUrl=nn;exports.createAvatarTtsLipsyncController=st;exports.createRealtimeVoiceLipsyncController=rn;exports.fetchAiTwin=Qt;exports.fetchDevAuthToken=Ne;exports.normalizeOculusViseme=Zt;exports.resolveVisemeAtTime=Ge;exports.resolveWordAtTime=dr;
|