react-ai-avatar 0.1.0 → 0.1.1

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 CHANGED
@@ -166,8 +166,8 @@ Optional data attributes: `data-base-x`/`data-base-y` (pupil rest position), `da
166
166
  - `glbUrl` (`string`) — CORS-enabled `.glb` URL with ARKit blendshapes, for `variant="glb"`.
167
167
  - `dicebearCollection` (`string`) — DiceBear style id (curated CC0 set), for `variant="dicebear"`.
168
168
  - `dicebearSeed` (`string`) — deterministic DiceBear seed, for `variant="dicebear"`.
169
- - `subtitle` / `thought` (`string`) — optional movie-style captions and a thought bubble.
170
- - `showGlow` / `showStatePill` / `showThought` / `showSubtitle` (`boolean`) — HUD satellites, each `true` by default. Set any to `false` to hide it individually: the reactive glow, the state pill, the thought bubble, and the subtitle respectively.
169
+ - `subtitle` / `thought` (`string`) — optional movie-style caption and a thought bubble. Pass raw text or markdown: both are flattened to spoken prose and rolled to a trailing window internally, so a long streamed reply never overflows or shows raw `**`/tables. For a long assistant reply, keep the full markdown in your chat transcript and pass the same text here for the short live caption.
170
+ - `showGlow` / `showStatePill` / `showThought` / `showSubtitle` (`boolean`) — HUD satellites, each `true` by default. Set any to `false` to hide it individually: the reactive glow, the state pill, the thought bubble, and the subtitle respectively. The built-in subtitle/thought float `absolute` around the face (needs open canvas); inside a constrained card, set `showSubtitle={false}` / `showThought={false}` and render `<AvatarCaption>` / `<AvatarThought>` in your own layout slot instead.
171
171
  - `maxMouthOpening`, `mouseTrackingIntensity`, `blinkIntervalMin/Max`, `blinkDuration` — animation tuning.
172
172
  - `stateColors`, `stateLabels` — theming; labels are announced via `aria-live`.
173
173
  - `customization` — preset colors and accessories (skin, hair, clothing, glasses, headphones…).
@@ -184,6 +184,8 @@ Everything the runtime uses is exported, so you can compose your own:
184
184
  - `useReducedMotion()` — SSR-safe `prefers-reduced-motion` hook.
185
185
  - `GeometricAvatar`, `MemojiAvatar`, `PixelArtAvatar`, `DoodleAvatar` — the raw presets.
186
186
  - `AudioVisualizer` — Siri-style waveform telemetry strip.
187
+ - `AvatarCaption` / `AvatarThought` — host-placed caption + thought widgets. In-flow (not `absolute`), so they fit your own layout slot without overflow; both flatten markdown to spoken prose and roll a trailing window.
188
+ - `toPlainText(md)` / `tailWindow(text, { maxChars })` — the pure text helpers behind those widgets, for building your own caption.
187
189
 
188
190
  ## Getting an `AnalyserNode`
189
191
 
@@ -280,7 +282,7 @@ function TextAvatar() {
280
282
 
281
283
  `createSpeechActivity(options?)` accepts `chargePerChar`, `decayMs` and `maxChargePerPush` to tune how wide / how fast the mouth reacts. The returned source has `push(chunk)`, `end()`, `reset()` (drop energy on an interrupted turn) and `sample()`. When `speechActivity` is provided it takes precedence over both `streamingText` and `analyser`. (`streamingText` is just this, with the diffing done for you — under the hood it's the exported `useStreamingTextActivity` hook.)
282
284
 
283
- > The demo dashboard ships this end-to-end: toggle **TEXT (STREAM)** to talk to an OpenAI-compatible endpoint (set `OPENAI_BASE_URL` / `OPENAI_API_KEY` / `OPENAI_MODEL`, or leave them unset / `MOCK_REALTIME=true` for a no-key mock). See `src/demo/useStreamingLLM.ts` and the `/api/chat` route in `server.ts`.
285
+ > [`examples/03-streaming-text-imperative.tsx`](examples/03-streaming-text-imperative.tsx) shows this end-to-end against an OpenAI-compatible endpoint. The browser only ever talks to your own `/api/chat`; a tiny reference relay that proxies to the provider (so the key never reaches the client) lives in [`examples/server/proxy.ts`](examples/server/proxy.ts).
284
286
 
285
287
  ## Positioning
286
288
 
@@ -294,17 +296,21 @@ The closest reference is [TalkingHead](https://github.com/met4citizen/TalkingHea
294
296
  | Makes visible | the voice | the *thinking* |
295
297
  | Setup | avatar platform + Blender + rig | `npm i` + one component |
296
298
 
297
- ## Demo / development
299
+ ## Development
298
300
 
299
- The repo ships a demo dashboard (Gemini Live + a no-API-key mock):
301
+ This repo is the library only no app or backend. The runnable, hosted demos
302
+ live on the project's docs site (built separately, client-side mock, no API key).
300
303
 
301
304
  ```bash
302
305
  npm install
303
- npm run dev # starts the demo at :3000 (MOCK_REALTIME=true needs no API key)
304
306
  npm test # vitest: engine, layer contract, SSR, parsers
307
+ npm run lint # tsc --noEmit
305
308
  npm run build:lib # builds the publishable package into dist/lib
306
309
  ```
307
310
 
311
+ Copy-pasteable integration examples — including a reference relay server for
312
+ real voice/text providers — live in [`examples/`](examples/).
313
+
308
314
  ## License
309
315
 
310
316
  MIT — for the library, the runtime and all built-in presets. Use it commercially, fork it, reskin it. SVGs you bring via `byos` keep whatever license they had.
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),n=require("react"),Y=require("@react-three/fiber"),he=require("three/addons/loaders/GLTFLoader.js"),me=require("three/addons/utils/SkeletonUtils.js"),de=require("@react-three/drei"),ge=require("three"),ae=require("./useReducedMotion-BRDEmRNI.cjs");function pe(f){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(f){for(const a in f)if(a!=="default"){const R=Object.getOwnPropertyDescriptor(f,a);Object.defineProperty(t,a,R.get?R:{enumerable:!0,get:()=>f[a]})}}return t.default=f,Object.freeze(t)}const W=pe(ge);class xe extends n.Component{constructor(t){super(t),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(t){this.props.onError((t==null?void 0:t.message)||String(t))}render(){return this.state.hasError?null:this.props.children}}function be(f){const t=new Map,a=c=>c.toLowerCase().replace(/[^a-z]/g,"");f.traverse(c=>{const h=c,m=h.morphTargetDictionary;if(!(!m||!h.morphTargetInfluences))for(const i of Object.keys(m)){const g=m[i],r=a(i);t.has(r)||t.set(r,[]),t.get(r).push({mesh:h,index:g})}});const R=c=>{const h=a(c);if(t.has(h))return t.get(h);const m=[];for(const[i,g]of t)i.endsWith(h)&&m.push(...g);return m},j=new Map;return{has(c){return this.entries(c).length>0},entries(c){return j.has(c)||j.set(c,R(c)),j.get(c)},set(c,h){const m=this.entries(c),i=Math.max(0,Math.min(1,h));for(const{mesh:g,index:r}of m)g.morphTargetInfluences[r]=i},names:Array.from(t.keys())}}function ke({url:f,state:t,analyser:a,maxMouthOpening:R=30,mouseTrackingIntensity:j=1,blinkIntervalMin:c=2e3,blinkIntervalMax:h=6e3,blinkDuration:m=100,reducedMotion:i=!1,onLoaded:g}){const r=Y.useLoader(he.GLTFLoader,f),{camera:l,controls:q}=Y.useThree(),p=n.useMemo(()=>me.clone(r.scene),[r.scene]),e=n.useMemo(()=>be(p),[p]);n.useEffect(()=>{var B;g(!0),console.log("[GLB] Model loaded. ARKit blendshapes found:",e.names),p.traverse(G=>{G.frustumCulled=!1}),p.updateMatrixWorld(!0);const L=new W.Box3().setFromObject(p),b=new W.Vector3,o=new W.Vector3;L.getSize(b),L.getCenter(o);const k=L.max.y-b.y*.08,Z=Math.max(.45,b.y*.42);l.position.set(o.x,k,L.max.z+Z),l.lookAt(o.x,k,o.z),l.updateProjectionMatrix();const w=q;w!=null&&w.target&&(w.target.set(o.x,k,o.z),(B=w.update)==null||B.call(w))},[p,e,l,q,g]);const S=n.useRef(0),O=n.useRef(0),v=n.useRef(0),z=n.useRef(0),T=n.useRef(0),A=n.useRef(0),d=n.useRef(0),u=n.useRef(0),D=n.useRef(t),$=n.useRef(0),I=n.useRef(0),V=n.useRef(0),X=n.useRef(0),J=n.useRef(Math.random()*3+2),x=n.useRef(0),N=n.useRef(!1),K=n.useRef(null),ee=n.useRef(null);return Y.useFrame((L,b)=>{var oe,ue;const o=W.MathUtils.lerp,k=L.clock.elapsedTime;if(t!==D.current&&(t==="listening"&&!i&&(u.current=1),D.current==="speaking"&&t!=="speaking"&&!i&&(I.current=2),D.current=t),u.current=Math.max(0,u.current-b*2.5),I.current=Math.max(0,I.current-b),!i)if(!N.current)J.current-=b,J.current<=0&&(N.current=!0);else{const M=1e3/(m||100);x.current+=b*M*2,x.current>=1&&(x.current=1,N.current=!1,J.current=Math.random()*((h-c)/1e3)+c/1e3)}if(!N.current&&x.current>0){const M=1e3/(m||100);x.current-=b*M*2,x.current<0&&(x.current=0)}e.set("eyeBlinkLeft",x.current),e.set("eyeBlinkRight",x.current);const Z=((oe=L.pointer)==null?void 0:oe.x)??0,w=((ue=L.pointer)==null?void 0:ue.y)??0,B=i?0:j;let G=Z*B,Q=w*B;t==="thinking"?(G=-.5*(i?0:1),Q=.6*(i?0:1)):i||(G+=Math.sin(k*.23)*.12,Q+=Math.sin(k*.31+1.3)*.08),V.current=o(V.current,G,.1),X.current=o(X.current,Q,.1);const F=V.current,U=X.current;e.set("eyeLookOutRight",Math.max(0,F)),e.set("eyeLookInLeft",Math.max(0,F)),e.set("eyeLookOutLeft",Math.max(0,-F)),e.set("eyeLookInRight",Math.max(0,-F)),e.set("eyeLookUpLeft",Math.max(0,U)),e.set("eyeLookUpRight",Math.max(0,U)),e.set("eyeLookDownLeft",Math.max(0,-U)),e.set("eyeLookDownRight",Math.max(0,-U));let E=0,te=0,ne=0;t==="listening"?(E=.35,te=.25):t==="thinking"?ne=.45:t==="speaking"?E=.12:E=.05,I.current>0&&t!=="speaking"&&(E=Math.max(E,.55));const le=t==="thinking"&&!i?1:0;T.current=o(T.current,E,.1),A.current=o(A.current,te,.1),d.current=o(d.current,ne,.1),$.current=o($.current,le,.08);const y=T.current,_=$.current;e.set("mouthSmileLeft",y),e.set("mouthSmileRight",y),e.set("browInnerUp",A.current),e.set("browDownLeft",d.current),e.set("browDownRight",d.current),e.set("cheekSquintLeft",y*.6),e.set("cheekSquintRight",y*.6),e.set("mouthDimpleLeft",y*.5),e.set("mouthDimpleRight",y*.5),e.set("eyeSquintLeft",Math.max(y*.35,_*.45)),e.set("eyeSquintRight",Math.max(y*.35,_*.45)),e.set("mouthPressLeft",_*.4),e.set("mouthPressRight",_*.4),e.set("eyeWideLeft",u.current*.7),e.set("eyeWideRight",u.current*.7),e.set("browOuterUpLeft",u.current*.6),e.set("browOuterUpRight",u.current*.6);let H=0,re=0,se=0,ce=0;if(t==="speaking"){(!K.current||ee.current!==a)&&(K.current=ae.createMouthEngine(a),ee.current=a);const M=K.current.read(),fe=R/30,P=Math.min(1,M.level*fe*1.7);M.shape==="o"?(re=P*.85,se=P*.6,H=P*.35):M.shape==="e"?(ce=P*.7,H=P*.3):M.shape==="a"&&(H=P*.9)}S.current=o(S.current,H,.25),O.current=o(O.current,re,.25),v.current=o(v.current,se,.25),z.current=o(z.current,ce,.25),e.set("jawOpen",S.current),e.set("mouthFunnel",O.current),e.set("mouthPucker",v.current),e.set("mouthStretchLeft",z.current),e.set("mouthStretchRight",z.current);const C=i||t==="speaking"||t==="thinking"?0:1,ie=Math.sin(k*.4)*.06*C;e.set("mouthLeft",Math.max(0,ie)),e.set("mouthRight",Math.max(0,-ie)),e.set("mouthRollLower",(Math.sin(k*.33)*.5+.5)*.07*C),e.set("mouthShrugUpper",(Math.sin(k*.7)*.5+.5)*.06*C)}),s.jsx("primitive",{object:p})}function we({state:f,analyser:t,size:a=300,className:R="",style:j,maxMouthOpening:c,blinkIntervalMin:h,blinkIntervalMax:m,blinkDuration:i,mouseTrackingIntensity:g,stateColors:r,glbUrl:l}){const[q,p]=n.useState(!1),[e,S]=n.useState(null),[O,v]=n.useState(!1),z=ae.useReducedMotion(),T=n.useCallback(d=>p(d),[]);n.useEffect(()=>{if(p(!1),S(null),v(!1),!l)return;let d=!1;return fetch(l).then(u=>{const D=u.headers.get("content-type")||"";if(!u.ok)throw new Error(`HTTP ${u.status} for ${l}`);if(D.includes("text/html"))throw new Error(`No .glb found at ${l} (server returned HTML).`);d||v(!0)}).catch(u=>{d||S((u==null?void 0:u.message)||String(u))}),()=>{d=!0}},[l]);const A={idle:(r==null?void 0:r.idle)??"#4b5563",listening:(r==null?void 0:r.listening)??"#3b82f6",thinking:(r==null?void 0:r.thinking)??"#8b5cf6",speaking:(r==null?void 0:r.speaking)??"#10b981",working:(r==null?void 0:r.working)??"#f59e0b"};return s.jsxs("div",{className:`relative flex items-center justify-center rounded-3xl overflow-hidden border border-zinc-800/40 bg-zinc-950/40 ${R}`,style:{width:a,height:a,...j},children:[s.jsxs(Y.Canvas,{camera:{position:[0,1.5,1],fov:35},shadows:!0,gl:{antialias:!0,alpha:!0,preserveDrawingBuffer:!0},style:{width:"100%",height:"100%",background:"transparent"},children:[s.jsx("ambientLight",{intensity:1.5}),s.jsx("spotLight",{position:[0,3,2],angle:.6,penumbra:1,intensity:3,color:A[f],castShadow:!0}),s.jsx("directionalLight",{position:[-2,2,-2],intensity:1.8,color:"#ffffff"}),s.jsx("directionalLight",{position:[2,2,2],intensity:2.2,color:"#ffffff"}),s.jsx(xe,{onError:d=>S(d),children:l&&O&&s.jsx(n.Suspense,{fallback:null,children:s.jsx(ke,{url:l,state:f,analyser:t,maxMouthOpening:c,mouseTrackingIntensity:g,blinkIntervalMin:h,blinkIntervalMax:m,blinkDuration:i,reducedMotion:z,onLoaded:T})})}),s.jsx(de.OrbitControls,{makeDefault:!0,enableZoom:!1,enablePan:!1,minPolarAngle:Math.PI/2.6,maxPolarAngle:Math.PI/1.7,minAzimuthAngle:-Math.PI/4,maxAzimuthAngle:Math.PI/4})]}),!l&&s.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center",children:[s.jsx("span",{className:"text-xs font-mono font-bold text-amber-400 uppercase tracking-wider mb-2",children:"Missing glbUrl"}),s.jsx("p",{className:"text-[10px] text-zinc-500 max-w-[200px] leading-relaxed",children:"Pass a CORS-enabled .glb URL (with ARKit blendshapes) via the glbUrl prop."})]}),l&&!q&&!e&&s.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/80 backdrop-blur-md z-20",children:[s.jsx("div",{className:"w-10 h-10 border-4 border-t-emerald-500 border-emerald-500/20 rounded-full animate-spin mb-3"}),s.jsx("span",{className:"text-[10px] font-mono font-bold tracking-widest text-zinc-400 animate-pulse",children:"LOADING GLB MODEL..."})]}),e&&s.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center",children:[s.jsx("span",{className:"text-xs font-mono font-bold text-red-400 uppercase tracking-wider mb-2",children:"Failed to load GLB"}),s.jsx("p",{className:"text-[10px] text-zinc-500 max-w-[200px] leading-relaxed break-all",children:e})]})]})}exports.GlbArkitAvatar=we;
@@ -0,0 +1,233 @@
1
+ import { jsxs as O, jsx as a } from "react/jsx-runtime";
2
+ import de, { useState as ee, useCallback as pe, useEffect as he, Suspense as ge, useMemo as le, useRef as s } from "react";
3
+ import { Canvas as xe, useLoader as ke, useThree as be, useFrame as we } from "@react-three/fiber";
4
+ import { GLTFLoader as Le } from "three/addons/loaders/GLTFLoader.js";
5
+ import { clone as ye } from "three/addons/utils/SkeletonUtils.js";
6
+ import { OrbitControls as Me } from "@react-three/drei";
7
+ import * as H from "three";
8
+ import { u as Re, c as ze } from "./useReducedMotion-BDcEizfP.js";
9
+ class Se extends de.Component {
10
+ constructor(t) {
11
+ super(t), this.state = { hasError: !1 };
12
+ }
13
+ static getDerivedStateFromError() {
14
+ return { hasError: !0 };
15
+ }
16
+ componentDidCatch(t) {
17
+ this.props.onError((t == null ? void 0 : t.message) || String(t));
18
+ }
19
+ render() {
20
+ return this.state.hasError ? null : this.props.children;
21
+ }
22
+ }
23
+ function ve(M) {
24
+ const t = /* @__PURE__ */ new Map(), p = (r) => r.toLowerCase().replace(/[^a-z]/g, "");
25
+ M.traverse((r) => {
26
+ const l = r, h = l.morphTargetDictionary;
27
+ if (!(!h || !l.morphTargetInfluences))
28
+ for (const c of Object.keys(h)) {
29
+ const f = h[c], n = p(c);
30
+ t.has(n) || t.set(n, []), t.get(n).push({ mesh: l, index: f });
31
+ }
32
+ });
33
+ const A = (r) => {
34
+ const l = p(r);
35
+ if (t.has(l)) return t.get(l);
36
+ const h = [];
37
+ for (const [c, f] of t)
38
+ c.endsWith(l) && h.push(...f);
39
+ return h;
40
+ }, R = /* @__PURE__ */ new Map();
41
+ return {
42
+ has(r) {
43
+ return this.entries(r).length > 0;
44
+ },
45
+ entries(r) {
46
+ return R.has(r) || R.set(r, A(r)), R.get(r);
47
+ },
48
+ set(r, l) {
49
+ const h = this.entries(r), c = Math.max(0, Math.min(1, l));
50
+ for (const { mesh: f, index: n } of h)
51
+ f.morphTargetInfluences[n] = c;
52
+ },
53
+ /** All normalized morph-target names found, for debugging. */
54
+ names: Array.from(t.keys())
55
+ };
56
+ }
57
+ function Ee({
58
+ url: M,
59
+ state: t,
60
+ analyser: p,
61
+ maxMouthOpening: A = 30,
62
+ mouseTrackingIntensity: R = 1,
63
+ blinkIntervalMin: r = 2e3,
64
+ blinkIntervalMax: l = 6e3,
65
+ blinkDuration: h = 100,
66
+ reducedMotion: c = !1,
67
+ onLoaded: f
68
+ }) {
69
+ const n = ke(Le, M), { camera: u, controls: N } = be(), d = le(() => ye(n.scene), [n.scene]), e = le(() => ve(d), [d]);
70
+ he(() => {
71
+ var G;
72
+ f(!0), console.log("[GLB] Model loaded. ARKit blendshapes found:", e.names), d.traverse((I) => {
73
+ I.frustumCulled = !1;
74
+ }), d.updateMatrixWorld(!0);
75
+ const w = new H.Box3().setFromObject(d), x = new H.Vector3(), i = new H.Vector3();
76
+ w.getSize(x), w.getCenter(i);
77
+ const k = w.max.y - x.y * 0.08, Q = Math.max(0.45, x.y * 0.42);
78
+ u.position.set(i.x, k, w.max.z + Q), u.lookAt(i.x, k, i.z), u.updateProjectionMatrix();
79
+ const b = N;
80
+ b != null && b.target && (b.target.set(i.x, k, i.z), (G = b.update) == null || G.call(b));
81
+ }, [d, e, u, N, f]);
82
+ const z = s(0), B = s(0), S = s(0), v = s(0), D = s(0), T = s(0), m = s(0), o = s(0), j = s(t), V = s(0), F = s(0), X = s(0), J = s(0), K = s(Math.random() * 3 + 2), g = s(0), q = s(!1), Z = s(null), te = s(null);
83
+ return we((w, x) => {
84
+ var ae, ue;
85
+ const i = H.MathUtils.lerp, k = w.clock.elapsedTime;
86
+ if (t !== j.current && (t === "listening" && !c && (o.current = 1), j.current === "speaking" && t !== "speaking" && !c && (F.current = 2), j.current = t), o.current = Math.max(0, o.current - x * 2.5), F.current = Math.max(0, F.current - x), !c)
87
+ if (!q.current)
88
+ K.current -= x, K.current <= 0 && (q.current = !0);
89
+ else {
90
+ const y = 1e3 / (h || 100);
91
+ g.current += x * y * 2, g.current >= 1 && (g.current = 1, q.current = !1, K.current = Math.random() * ((l - r) / 1e3) + r / 1e3);
92
+ }
93
+ if (!q.current && g.current > 0) {
94
+ const y = 1e3 / (h || 100);
95
+ g.current -= x * y * 2, g.current < 0 && (g.current = 0);
96
+ }
97
+ e.set("eyeBlinkLeft", g.current), e.set("eyeBlinkRight", g.current);
98
+ const Q = ((ae = w.pointer) == null ? void 0 : ae.x) ?? 0, b = ((ue = w.pointer) == null ? void 0 : ue.y) ?? 0, G = c ? 0 : R;
99
+ let I = Q * G, _ = b * G;
100
+ t === "thinking" ? (I = -0.5 * (c ? 0 : 1), _ = 0.6 * (c ? 0 : 1)) : c || (I += Math.sin(k * 0.23) * 0.12, _ += Math.sin(k * 0.31 + 1.3) * 0.08), X.current = i(X.current, I, 0.1), J.current = i(J.current, _, 0.1);
101
+ const U = X.current, W = J.current;
102
+ e.set("eyeLookOutRight", Math.max(0, U)), e.set("eyeLookInLeft", Math.max(0, U)), e.set("eyeLookOutLeft", Math.max(0, -U)), e.set("eyeLookInRight", Math.max(0, -U)), e.set("eyeLookUpLeft", Math.max(0, W)), e.set("eyeLookUpRight", Math.max(0, W)), e.set("eyeLookDownLeft", Math.max(0, -W)), e.set("eyeLookDownRight", Math.max(0, -W));
103
+ let E = 0, ne = 0, re = 0;
104
+ t === "listening" ? (E = 0.35, ne = 0.25) : t === "thinking" ? re = 0.45 : t === "speaking" ? E = 0.12 : E = 0.05, F.current > 0 && t !== "speaking" && (E = Math.max(E, 0.55));
105
+ const me = t === "thinking" && !c ? 1 : 0;
106
+ D.current = i(D.current, E, 0.1), T.current = i(T.current, ne, 0.1), m.current = i(m.current, re, 0.1), V.current = i(V.current, me, 0.08);
107
+ const L = D.current, Y = V.current;
108
+ e.set("mouthSmileLeft", L), e.set("mouthSmileRight", L), e.set("browInnerUp", T.current), e.set("browDownLeft", m.current), e.set("browDownRight", m.current), e.set("cheekSquintLeft", L * 0.6), e.set("cheekSquintRight", L * 0.6), e.set("mouthDimpleLeft", L * 0.5), e.set("mouthDimpleRight", L * 0.5), e.set("eyeSquintLeft", Math.max(L * 0.35, Y * 0.45)), e.set("eyeSquintRight", Math.max(L * 0.35, Y * 0.45)), e.set("mouthPressLeft", Y * 0.4), e.set("mouthPressRight", Y * 0.4), e.set("eyeWideLeft", o.current * 0.7), e.set("eyeWideRight", o.current * 0.7), e.set("browOuterUpLeft", o.current * 0.6), e.set("browOuterUpRight", o.current * 0.6);
109
+ let $ = 0, ce = 0, se = 0, ie = 0;
110
+ if (t === "speaking") {
111
+ (!Z.current || te.current !== p) && (Z.current = ze(p), te.current = p);
112
+ const y = Z.current.read(), fe = A / 30, P = Math.min(1, y.level * fe * 1.7);
113
+ y.shape === "o" ? (ce = P * 0.85, se = P * 0.6, $ = P * 0.35) : y.shape === "e" ? (ie = P * 0.7, $ = P * 0.3) : y.shape === "a" && ($ = P * 0.9);
114
+ }
115
+ z.current = i(z.current, $, 0.25), B.current = i(B.current, ce, 0.25), S.current = i(S.current, se, 0.25), v.current = i(v.current, ie, 0.25), e.set("jawOpen", z.current), e.set("mouthFunnel", B.current), e.set("mouthPucker", S.current), e.set("mouthStretchLeft", v.current), e.set("mouthStretchRight", v.current);
116
+ const C = c || t === "speaking" || t === "thinking" ? 0 : 1, oe = Math.sin(k * 0.4) * 0.06 * C;
117
+ e.set("mouthLeft", Math.max(0, oe)), e.set("mouthRight", Math.max(0, -oe)), e.set("mouthRollLower", (Math.sin(k * 0.33) * 0.5 + 0.5) * 0.07 * C), e.set("mouthShrugUpper", (Math.sin(k * 0.7) * 0.5 + 0.5) * 0.06 * C);
118
+ }), /* @__PURE__ */ a("primitive", { object: d });
119
+ }
120
+ function Ie({
121
+ state: M,
122
+ analyser: t,
123
+ size: p = 300,
124
+ className: A = "",
125
+ style: R,
126
+ maxMouthOpening: r,
127
+ blinkIntervalMin: l,
128
+ blinkIntervalMax: h,
129
+ blinkDuration: c,
130
+ mouseTrackingIntensity: f,
131
+ stateColors: n,
132
+ glbUrl: u
133
+ }) {
134
+ const [N, d] = ee(!1), [e, z] = ee(null), [B, S] = ee(!1), v = Re(), D = pe((m) => d(m), []);
135
+ he(() => {
136
+ if (d(!1), z(null), S(!1), !u) return;
137
+ let m = !1;
138
+ return fetch(u).then((o) => {
139
+ const j = o.headers.get("content-type") || "";
140
+ if (!o.ok) throw new Error(`HTTP ${o.status} for ${u}`);
141
+ if (j.includes("text/html"))
142
+ throw new Error(`No .glb found at ${u} (server returned HTML).`);
143
+ m || S(!0);
144
+ }).catch((o) => {
145
+ m || z((o == null ? void 0 : o.message) || String(o));
146
+ }), () => {
147
+ m = !0;
148
+ };
149
+ }, [u]);
150
+ const T = {
151
+ idle: (n == null ? void 0 : n.idle) ?? "#4b5563",
152
+ listening: (n == null ? void 0 : n.listening) ?? "#3b82f6",
153
+ thinking: (n == null ? void 0 : n.thinking) ?? "#8b5cf6",
154
+ speaking: (n == null ? void 0 : n.speaking) ?? "#10b981",
155
+ working: (n == null ? void 0 : n.working) ?? "#f59e0b"
156
+ };
157
+ return /* @__PURE__ */ O(
158
+ "div",
159
+ {
160
+ className: `relative flex items-center justify-center rounded-3xl overflow-hidden border border-zinc-800/40 bg-zinc-950/40 ${A}`,
161
+ style: { width: p, height: p, ...R },
162
+ children: [
163
+ /* @__PURE__ */ O(
164
+ xe,
165
+ {
166
+ camera: { position: [0, 1.5, 1], fov: 35 },
167
+ shadows: !0,
168
+ gl: { antialias: !0, alpha: !0, preserveDrawingBuffer: !0 },
169
+ style: { width: "100%", height: "100%", background: "transparent" },
170
+ children: [
171
+ /* @__PURE__ */ a("ambientLight", { intensity: 1.5 }),
172
+ /* @__PURE__ */ a(
173
+ "spotLight",
174
+ {
175
+ position: [0, 3, 2],
176
+ angle: 0.6,
177
+ penumbra: 1,
178
+ intensity: 3,
179
+ color: T[M],
180
+ castShadow: !0
181
+ }
182
+ ),
183
+ /* @__PURE__ */ a("directionalLight", { position: [-2, 2, -2], intensity: 1.8, color: "#ffffff" }),
184
+ /* @__PURE__ */ a("directionalLight", { position: [2, 2, 2], intensity: 2.2, color: "#ffffff" }),
185
+ /* @__PURE__ */ a(Se, { onError: (m) => z(m), children: u && B && /* @__PURE__ */ a(ge, { fallback: null, children: /* @__PURE__ */ a(
186
+ Ee,
187
+ {
188
+ url: u,
189
+ state: M,
190
+ analyser: t,
191
+ maxMouthOpening: r,
192
+ mouseTrackingIntensity: f,
193
+ blinkIntervalMin: l,
194
+ blinkIntervalMax: h,
195
+ blinkDuration: c,
196
+ reducedMotion: v,
197
+ onLoaded: D
198
+ }
199
+ ) }) }),
200
+ /* @__PURE__ */ a(
201
+ Me,
202
+ {
203
+ makeDefault: !0,
204
+ enableZoom: !1,
205
+ enablePan: !1,
206
+ minPolarAngle: Math.PI / 2.6,
207
+ maxPolarAngle: Math.PI / 1.7,
208
+ minAzimuthAngle: -Math.PI / 4,
209
+ maxAzimuthAngle: Math.PI / 4
210
+ }
211
+ )
212
+ ]
213
+ }
214
+ ),
215
+ !u && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center", children: [
216
+ /* @__PURE__ */ a("span", { className: "text-xs font-mono font-bold text-amber-400 uppercase tracking-wider mb-2", children: "Missing glbUrl" }),
217
+ /* @__PURE__ */ a("p", { className: "text-[10px] text-zinc-500 max-w-[200px] leading-relaxed", children: "Pass a CORS-enabled .glb URL (with ARKit blendshapes) via the glbUrl prop." })
218
+ ] }),
219
+ u && !N && !e && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/80 backdrop-blur-md z-20", children: [
220
+ /* @__PURE__ */ a("div", { className: "w-10 h-10 border-4 border-t-emerald-500 border-emerald-500/20 rounded-full animate-spin mb-3" }),
221
+ /* @__PURE__ */ a("span", { className: "text-[10px] font-mono font-bold tracking-widest text-zinc-400 animate-pulse", children: "LOADING GLB MODEL..." })
222
+ ] }),
223
+ e && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center", children: [
224
+ /* @__PURE__ */ a("span", { className: "text-xs font-mono font-bold text-red-400 uppercase tracking-wider mb-2", children: "Failed to load GLB" }),
225
+ /* @__PURE__ */ a("p", { className: "text-[10px] text-zinc-500 max-w-[200px] leading-relaxed break-all", children: e })
226
+ ] })
227
+ ]
228
+ }
229
+ );
230
+ }
231
+ export {
232
+ Ie as GlbArkitAvatar
233
+ };
@@ -0,0 +1,245 @@
1
+ import { jsxs as O, jsx as i } from "react/jsx-runtime";
2
+ import oe, { useState as H, useEffect as ie, useCallback as ne, Suspense as se, useRef as d } from "react";
3
+ import { Canvas as ce, useFrame as le } from "@react-three/fiber";
4
+ import { GLTFLoader as ue } from "three/addons/loaders/GLTFLoader.js";
5
+ import { VRMLoaderPlugin as de, VRMUtils as ae } from "@pixiv/three-vrm";
6
+ import { OrbitControls as pe } from "@react-three/drei";
7
+ import * as a from "three";
8
+ import { u as me, c as fe } from "./useReducedMotion-BDcEizfP.js";
9
+ class he extends oe.Component {
10
+ constructor(r) {
11
+ super(r), this.state = { hasError: !1 };
12
+ }
13
+ static getDerivedStateFromError() {
14
+ return { hasError: !0 };
15
+ }
16
+ componentDidCatch(r) {
17
+ this.props.onError((r == null ? void 0 : r.message) || String(r));
18
+ }
19
+ render() {
20
+ return this.state.hasError ? null : this.props.children;
21
+ }
22
+ }
23
+ function f(l, r, h) {
24
+ if (!l.expressionManager) return;
25
+ const E = l.expressionManager.expressionMap || {}, v = Object.keys(E);
26
+ let o = [];
27
+ switch (r) {
28
+ case "blink":
29
+ o = ["blink", "Blink", "BLINK"];
30
+ break;
31
+ case "aa":
32
+ o = ["aa", "Aa", "a", "A", "AA", "mouth_a", "mouth_A"];
33
+ break;
34
+ case "ee":
35
+ o = ["ee", "Ee", "e", "E", "EE", "mouth_e", "mouth_E"];
36
+ break;
37
+ case "oo":
38
+ o = ["oh", "Oh", "oo", "Oo", "o", "O", "OO", "mouth_o", "mouth_O"];
39
+ break;
40
+ case "happy":
41
+ o = ["happy", "Happy", "joy", "Joy", "JOY", "fun", "Fun", "FUN", "HAPPY"];
42
+ break;
43
+ case "sad":
44
+ o = ["sad", "Sad", "sorrow", "Sorrow", "SORROW", "sadness", "SAD"];
45
+ break;
46
+ case "relaxed":
47
+ o = ["relaxed", "Relaxed", "fun", "Fun", "FUN", "neutral", "Neutral", "RELAXED"];
48
+ break;
49
+ }
50
+ const A = o.find((x) => E[x] !== void 0 || v.includes(x));
51
+ if (A)
52
+ l.expressionManager.setValue(A, h);
53
+ else
54
+ try {
55
+ l.expressionManager.setValue(o[0], h);
56
+ } catch {
57
+ }
58
+ }
59
+ function ge({
60
+ url: l,
61
+ state: r,
62
+ analyser: h,
63
+ maxMouthOpening: E = 30,
64
+ mouseTrackingIntensity: v = 1,
65
+ blinkIntervalMin: o = 2e3,
66
+ blinkIntervalMax: A = 6e3,
67
+ blinkDuration: x = 100,
68
+ reducedMotion: P = !1,
69
+ onLoaded: S,
70
+ onError: t
71
+ }) {
72
+ const [e, B] = H(null);
73
+ ie(() => {
74
+ let b = !1, M = null;
75
+ const c = new ue();
76
+ return c.register((n) => new de(n)), c.load(
77
+ l,
78
+ (n) => {
79
+ var D, L;
80
+ if (b) {
81
+ const y = n.userData.vrm;
82
+ y && ae.deepDispose(y.scene);
83
+ return;
84
+ }
85
+ const k = n.userData.vrm;
86
+ if (!k) {
87
+ t(`No VRM found at ${l}.`);
88
+ return;
89
+ }
90
+ console.log(
91
+ "[VRM] Model loaded successfully. Available expressions:",
92
+ Object.keys(((D = k.expressionManager) == null ? void 0 : D.expressionMap) || {})
93
+ ), k.scene.traverse((y) => {
94
+ y.frustumCulled = !1;
95
+ });
96
+ const F = ((L = k.meta) == null ? void 0 : L.metaVersion) === "0";
97
+ k.scene.rotation.y = F ? Math.PI : 0, M = k, B(k), S(!0);
98
+ },
99
+ void 0,
100
+ (n) => {
101
+ b || t((n == null ? void 0 : n.message) || String(n));
102
+ }
103
+ ), () => {
104
+ b = !0, M && ae.deepDispose(M.scene);
105
+ };
106
+ }, [l, S, t]);
107
+ const g = d(0), p = d(0), m = d(0), U = d(0), w = d(0), z = d(0), V = d(Math.random() * 3 + 2), s = d(0), j = d(!1), I = d(null), K = d(null);
108
+ return le((b, M) => {
109
+ var T, X, G, J, $, W, Z, q, Q;
110
+ if (!e) return;
111
+ e.update(M);
112
+ const c = (T = e.humanoid) == null ? void 0 : T.getNormalizedBoneNode("neck"), n = (X = e.humanoid) == null ? void 0 : X.getNormalizedBoneNode("head");
113
+ if (((G = e.meta) == null ? void 0 : G.metaVersion) === "0") {
114
+ const u = (J = e.humanoid) == null ? void 0 : J.getNormalizedBoneNode("leftUpperArm"), N = ($ = e.humanoid) == null ? void 0 : $.getNormalizedBoneNode("rightUpperArm");
115
+ u && (u.rotation.z = Math.PI / 2.6), N && (N.rotation.z = -Math.PI / 2.6);
116
+ }
117
+ const F = ((W = b.pointer) == null ? void 0 : W.x) ?? ((Z = b.mouse) == null ? void 0 : Z.x) ?? 0, D = ((q = b.pointer) == null ? void 0 : q.y) ?? ((Q = b.mouse) == null ? void 0 : Q.y) ?? 0, L = P ? 0 : v, y = F * 0.35 * L, Y = -D * 0.2 * L;
118
+ if (c && (c.rotation.y = a.MathUtils.lerp(c.rotation.y, y, 0.1), c.rotation.x = a.MathUtils.lerp(c.rotation.x, Y, 0.1)), n && (n.rotation.y = a.MathUtils.lerp(n.rotation.y, y * 0.2, 0.1), n.rotation.x = a.MathUtils.lerp(n.rotation.x, Y * 0.2, 0.1)), !P)
119
+ if (!j.current)
120
+ V.current -= M, V.current <= 0 && (j.current = !0);
121
+ else {
122
+ const u = 1e3 / (x || 100);
123
+ s.current += M * u * 2, s.current >= 1 && (s.current = 1, j.current = !1, V.current = Math.random() * ((A - o) / 1e3) + o / 1e3);
124
+ }
125
+ if (!j.current && s.current > 0) {
126
+ const u = 1e3 / (x || 100);
127
+ s.current -= M * u * 2, s.current < 0 && (s.current = 0);
128
+ }
129
+ if (e.expressionManager) {
130
+ f(e, "blink", s.current);
131
+ let u = 0, N = 0, C = 0;
132
+ if (r === "listening" ? (u = 0.35, N = 0.2) : r === "thinking" ? (C = 0.25, N = 0.15, c && (c.rotation.y = a.MathUtils.lerp(c.rotation.y, -0.18, 0.05), c.rotation.x = a.MathUtils.lerp(c.rotation.x, 0.12, 0.05))) : r === "speaking" ? u = 0.15 : u = 0.05, U.current = a.MathUtils.lerp(U.current, u, 0.1), w.current = a.MathUtils.lerp(w.current, N, 0.1), z.current = a.MathUtils.lerp(z.current, C, 0.1), f(e, "happy", U.current), f(e, "relaxed", w.current), f(e, "sad", z.current), r === "speaking") {
133
+ (!I.current || K.current !== h) && (I.current = fe(h), K.current = h);
134
+ const R = I.current.read(), _ = E / 30;
135
+ let ee = 0, te = 0, re = 0;
136
+ R.shape === "e" ? te = R.level * 0.8 * _ : R.shape === "o" ? re = R.level * 0.8 * _ : R.shape === "a" && (ee = R.level * 0.95 * _), g.current = a.MathUtils.lerp(g.current, ee, 0.25), p.current = a.MathUtils.lerp(p.current, te, 0.25), m.current = a.MathUtils.lerp(m.current, re, 0.25), f(e, "aa", g.current), f(e, "ee", p.current), f(e, "oo", m.current);
137
+ } else
138
+ g.current = a.MathUtils.lerp(g.current, 0, 0.2), p.current = a.MathUtils.lerp(p.current, 0, 0.2), m.current = a.MathUtils.lerp(m.current, 0, 0.2), f(e, "aa", g.current), f(e, "ee", p.current), f(e, "oo", m.current);
139
+ e.expressionManager.update();
140
+ }
141
+ }), e ? /* @__PURE__ */ i("primitive", { object: e.scene }) : null;
142
+ }
143
+ function Ne({
144
+ state: l,
145
+ analyser: r,
146
+ size: h = 300,
147
+ className: E = "",
148
+ style: v,
149
+ maxMouthOpening: o,
150
+ blinkIntervalMin: A,
151
+ blinkIntervalMax: x,
152
+ blinkDuration: P,
153
+ mouseTrackingIntensity: S,
154
+ stateColors: t,
155
+ vrmUrl: e
156
+ }) {
157
+ const [B, g] = H(!1), [p, m] = H(null), U = me();
158
+ ie(() => {
159
+ g(!1), m(null);
160
+ }, [e]);
161
+ const w = ne((s) => g(s), []), z = ne((s) => m(s), []), V = {
162
+ idle: (t == null ? void 0 : t.idle) ?? "#4b5563",
163
+ listening: (t == null ? void 0 : t.listening) ?? "#3b82f6",
164
+ thinking: (t == null ? void 0 : t.thinking) ?? "#8b5cf6",
165
+ speaking: (t == null ? void 0 : t.speaking) ?? "#10b981",
166
+ working: (t == null ? void 0 : t.working) ?? "#f59e0b"
167
+ };
168
+ return /* @__PURE__ */ O(
169
+ "div",
170
+ {
171
+ className: `relative flex items-center justify-center rounded-3xl overflow-hidden border border-zinc-800/40 bg-zinc-950/40 ${E}`,
172
+ style: { width: h, height: h, ...v },
173
+ children: [
174
+ /* @__PURE__ */ O(
175
+ ce,
176
+ {
177
+ camera: { position: [0, 1.43, 0.88], fov: 44 },
178
+ shadows: !0,
179
+ gl: { antialias: !0, alpha: !0, preserveDrawingBuffer: !0 },
180
+ style: { width: "100%", height: "100%", background: "transparent" },
181
+ children: [
182
+ /* @__PURE__ */ i("ambientLight", { intensity: 1.5 }),
183
+ /* @__PURE__ */ i(
184
+ "spotLight",
185
+ {
186
+ position: [0, 3, 2],
187
+ angle: 0.6,
188
+ penumbra: 1,
189
+ intensity: 3,
190
+ color: V[l],
191
+ castShadow: !0
192
+ }
193
+ ),
194
+ /* @__PURE__ */ i("directionalLight", { position: [-2, 2, -2], intensity: 1.8, color: "#ffffff" }),
195
+ /* @__PURE__ */ i("directionalLight", { position: [2, 2, 2], intensity: 2.2, color: "#ffffff" }),
196
+ /* @__PURE__ */ i(he, { onError: (s) => m(s), children: e && /* @__PURE__ */ i(se, { fallback: null, children: /* @__PURE__ */ i(
197
+ ge,
198
+ {
199
+ url: e,
200
+ state: l,
201
+ analyser: r,
202
+ maxMouthOpening: o,
203
+ mouseTrackingIntensity: S,
204
+ blinkIntervalMin: A,
205
+ blinkIntervalMax: x,
206
+ blinkDuration: P,
207
+ reducedMotion: U,
208
+ onLoaded: w,
209
+ onError: z
210
+ }
211
+ ) }) }),
212
+ /* @__PURE__ */ i(
213
+ pe,
214
+ {
215
+ enableZoom: !1,
216
+ enablePan: !1,
217
+ minPolarAngle: Math.PI / 2.6,
218
+ maxPolarAngle: Math.PI / 1.7,
219
+ minAzimuthAngle: -Math.PI / 4,
220
+ maxAzimuthAngle: Math.PI / 4,
221
+ target: [0, 1.38, 0]
222
+ }
223
+ )
224
+ ]
225
+ }
226
+ ),
227
+ !e && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center", children: [
228
+ /* @__PURE__ */ i("span", { className: "text-xs font-mono font-bold text-amber-400 uppercase tracking-wider mb-2", children: "Missing vrmUrl" }),
229
+ /* @__PURE__ */ i("p", { className: "text-[10px] text-zinc-500 max-w-[200px] leading-relaxed", children: "Pass a CORS-enabled .vrm URL via the vrmUrl prop." })
230
+ ] }),
231
+ e && !B && !p && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/80 backdrop-blur-md z-20", children: [
232
+ /* @__PURE__ */ i("div", { className: "w-10 h-10 border-4 border-t-emerald-500 border-emerald-500/20 rounded-full animate-spin mb-3" }),
233
+ /* @__PURE__ */ i("span", { className: "text-[10px] font-mono font-bold tracking-widest text-zinc-400 animate-pulse", children: "LOADING NEURAL VRM MODEL..." })
234
+ ] }),
235
+ p && /* @__PURE__ */ O("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center", children: [
236
+ /* @__PURE__ */ i("span", { className: "text-xs font-mono font-bold text-red-400 uppercase tracking-wider mb-2", children: "Failed to load VRM" }),
237
+ /* @__PURE__ */ i("p", { className: "text-[10px] text-zinc-500 max-w-[200px] leading-relaxed break-all", children: p })
238
+ ] })
239
+ ]
240
+ }
241
+ );
242
+ }
243
+ export {
244
+ Ne as VrmAvatar
245
+ };
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),r=require("react"),re=require("@react-three/fiber"),ae=require("three/addons/loaders/GLTFLoader.js"),I=require("@pixiv/three-vrm"),se=require("@react-three/drei"),ie=require("three"),ne=require("./useReducedMotion-BRDEmRNI.cjs");function oe(s){const n=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const c in s)if(c!=="default"){const x=Object.getOwnPropertyDescriptor(s,c);Object.defineProperty(n,c,x.get?x:{enumerable:!0,get:()=>s[c]})}}return n.default=s,Object.freeze(n)}const o=oe(ie);class ce extends r.Component{constructor(n){super(n),this.state={hasError:!1}}static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(n){this.props.onError((n==null?void 0:n.message)||String(n))}render(){return this.state.hasError?null:this.props.children}}function m(s,n,c){if(!s.expressionManager)return;const x=s.expressionManager.expressionMap||{},N=Object.keys(x);let l=[];switch(n){case"blink":l=["blink","Blink","BLINK"];break;case"aa":l=["aa","Aa","a","A","AA","mouth_a","mouth_A"];break;case"ee":l=["ee","Ee","e","E","EE","mouth_e","mouth_E"];break;case"oo":l=["oh","Oh","oo","Oo","o","O","OO","mouth_o","mouth_O"];break;case"happy":l=["happy","Happy","joy","Joy","JOY","fun","Fun","FUN","HAPPY"];break;case"sad":l=["sad","Sad","sorrow","Sorrow","SORROW","sadness","SAD"];break;case"relaxed":l=["relaxed","Relaxed","fun","Fun","FUN","neutral","Neutral","RELAXED"];break}const j=l.find(b=>x[b]!==void 0||N.includes(b));if(j)s.expressionManager.setValue(j,c);else try{s.expressionManager.setValue(l[0],c)}catch{}}function le({url:s,state:n,analyser:c,maxMouthOpening:x=30,mouseTrackingIntensity:N=1,blinkIntervalMin:l=2e3,blinkIntervalMax:j=6e3,blinkDuration:b=100,reducedMotion:z=!1,onLoaded:L,onError:a}){const[e,D]=r.useState(null);r.useEffect(()=>{let M=!1,y=null;const d=new ae.GLTFLoader;return d.register(i=>new I.VRMLoaderPlugin(i)),d.load(s,i=>{var S,w;if(M){const k=i.userData.vrm;k&&I.VRMUtils.deepDispose(k.scene);return}const R=i.userData.vrm;if(!R){a(`No VRM found at ${s}.`);return}console.log("[VRM] Model loaded successfully. Available expressions:",Object.keys(((S=R.expressionManager)==null?void 0:S.expressionMap)||{})),R.scene.traverse(k=>{k.frustumCulled=!1});const B=((w=R.meta)==null?void 0:w.metaVersion)==="0";R.scene.rotation.y=B?Math.PI:0,y=R,D(R),L(!0)},void 0,i=>{M||a((i==null?void 0:i.message)||String(i))}),()=>{M=!0,y&&I.VRMUtils.deepDispose(y.scene)}},[s,L,a]);const g=r.useRef(0),p=r.useRef(0),h=r.useRef(0),v=r.useRef(0),O=r.useRef(0),U=r.useRef(0),V=r.useRef(Math.random()*3+2),u=r.useRef(0),P=r.useRef(!1),_=r.useRef(null),q=r.useRef(null);return re.useFrame((M,y)=>{var H,K,Y,G,X,J,$,W,Z;if(!e)return;e.update(y);const d=(H=e.humanoid)==null?void 0:H.getNormalizedBoneNode("neck"),i=(K=e.humanoid)==null?void 0:K.getNormalizedBoneNode("head");if(((Y=e.meta)==null?void 0:Y.metaVersion)==="0"){const f=(G=e.humanoid)==null?void 0:G.getNormalizedBoneNode("leftUpperArm"),E=(X=e.humanoid)==null?void 0:X.getNormalizedBoneNode("rightUpperArm");f&&(f.rotation.z=Math.PI/2.6),E&&(E.rotation.z=-Math.PI/2.6)}const B=((J=M.pointer)==null?void 0:J.x)??(($=M.mouse)==null?void 0:$.x)??0,S=((W=M.pointer)==null?void 0:W.y)??((Z=M.mouse)==null?void 0:Z.y)??0,w=z?0:N,k=B*.35*w,T=-S*.2*w;if(d&&(d.rotation.y=o.MathUtils.lerp(d.rotation.y,k,.1),d.rotation.x=o.MathUtils.lerp(d.rotation.x,T,.1)),i&&(i.rotation.y=o.MathUtils.lerp(i.rotation.y,k*.2,.1),i.rotation.x=o.MathUtils.lerp(i.rotation.x,T*.2,.1)),!z)if(!P.current)V.current-=y,V.current<=0&&(P.current=!0);else{const f=1e3/(b||100);u.current+=y*f*2,u.current>=1&&(u.current=1,P.current=!1,V.current=Math.random()*((j-l)/1e3)+l/1e3)}if(!P.current&&u.current>0){const f=1e3/(b||100);u.current-=y*f*2,u.current<0&&(u.current=0)}if(e.expressionManager){m(e,"blink",u.current);let f=0,E=0,Q=0;if(n==="listening"?(f=.35,E=.2):n==="thinking"?(Q=.25,E=.15,d&&(d.rotation.y=o.MathUtils.lerp(d.rotation.y,-.18,.05),d.rotation.x=o.MathUtils.lerp(d.rotation.x,.12,.05))):n==="speaking"?f=.15:f=.05,v.current=o.MathUtils.lerp(v.current,f,.1),O.current=o.MathUtils.lerp(O.current,E,.1),U.current=o.MathUtils.lerp(U.current,Q,.1),m(e,"happy",v.current),m(e,"relaxed",O.current),m(e,"sad",U.current),n==="speaking"){(!_.current||q.current!==c)&&(_.current=ne.createMouthEngine(c),q.current=c);const A=_.current.read(),F=x/30;let C=0,ee=0,te=0;A.shape==="e"?ee=A.level*.8*F:A.shape==="o"?te=A.level*.8*F:A.shape==="a"&&(C=A.level*.95*F),g.current=o.MathUtils.lerp(g.current,C,.25),p.current=o.MathUtils.lerp(p.current,ee,.25),h.current=o.MathUtils.lerp(h.current,te,.25),m(e,"aa",g.current),m(e,"ee",p.current),m(e,"oo",h.current)}else g.current=o.MathUtils.lerp(g.current,0,.2),p.current=o.MathUtils.lerp(p.current,0,.2),h.current=o.MathUtils.lerp(h.current,0,.2),m(e,"aa",g.current),m(e,"ee",p.current),m(e,"oo",h.current);e.expressionManager.update()}}),e?t.jsx("primitive",{object:e.scene}):null}function ue({state:s,analyser:n,size:c=300,className:x="",style:N,maxMouthOpening:l,blinkIntervalMin:j,blinkIntervalMax:b,blinkDuration:z,mouseTrackingIntensity:L,stateColors:a,vrmUrl:e}){const[D,g]=r.useState(!1),[p,h]=r.useState(null),v=ne.useReducedMotion();r.useEffect(()=>{g(!1),h(null)},[e]);const O=r.useCallback(u=>g(u),[]),U=r.useCallback(u=>h(u),[]),V={idle:(a==null?void 0:a.idle)??"#4b5563",listening:(a==null?void 0:a.listening)??"#3b82f6",thinking:(a==null?void 0:a.thinking)??"#8b5cf6",speaking:(a==null?void 0:a.speaking)??"#10b981",working:(a==null?void 0:a.working)??"#f59e0b"};return t.jsxs("div",{className:`relative flex items-center justify-center rounded-3xl overflow-hidden border border-zinc-800/40 bg-zinc-950/40 ${x}`,style:{width:c,height:c,...N},children:[t.jsxs(re.Canvas,{camera:{position:[0,1.43,.88],fov:44},shadows:!0,gl:{antialias:!0,alpha:!0,preserveDrawingBuffer:!0},style:{width:"100%",height:"100%",background:"transparent"},children:[t.jsx("ambientLight",{intensity:1.5}),t.jsx("spotLight",{position:[0,3,2],angle:.6,penumbra:1,intensity:3,color:V[s],castShadow:!0}),t.jsx("directionalLight",{position:[-2,2,-2],intensity:1.8,color:"#ffffff"}),t.jsx("directionalLight",{position:[2,2,2],intensity:2.2,color:"#ffffff"}),t.jsx(ce,{onError:u=>h(u),children:e&&t.jsx(r.Suspense,{fallback:null,children:t.jsx(le,{url:e,state:s,analyser:n,maxMouthOpening:l,mouseTrackingIntensity:L,blinkIntervalMin:j,blinkIntervalMax:b,blinkDuration:z,reducedMotion:v,onLoaded:O,onError:U})})}),t.jsx(se.OrbitControls,{enableZoom:!1,enablePan:!1,minPolarAngle:Math.PI/2.6,maxPolarAngle:Math.PI/1.7,minAzimuthAngle:-Math.PI/4,maxAzimuthAngle:Math.PI/4,target:[0,1.38,0]})]}),!e&&t.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center",children:[t.jsx("span",{className:"text-xs font-mono font-bold text-amber-400 uppercase tracking-wider mb-2",children:"Missing vrmUrl"}),t.jsx("p",{className:"text-[10px] text-zinc-500 max-w-[200px] leading-relaxed",children:"Pass a CORS-enabled .vrm URL via the vrmUrl prop."})]}),e&&!D&&!p&&t.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/80 backdrop-blur-md z-20",children:[t.jsx("div",{className:"w-10 h-10 border-4 border-t-emerald-500 border-emerald-500/20 rounded-full animate-spin mb-3"}),t.jsx("span",{className:"text-[10px] font-mono font-bold tracking-widest text-zinc-400 animate-pulse",children:"LOADING NEURAL VRM MODEL..."})]}),p&&t.jsxs("div",{className:"absolute inset-0 flex flex-col items-center justify-center bg-zinc-950/90 backdrop-blur-md z-20 p-4 text-center",children:[t.jsx("span",{className:"text-xs font-mono font-bold text-red-400 uppercase tracking-wider mb-2",children:"Failed to load VRM"}),t.jsx("p",{className:"text-[10px] text-zinc-500 max-w-[200px] leading-relaxed break-all",children:p})]})]})}exports.VrmAvatar=ue;
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ /**
3
+ * Host-placed caption + thought widgets.
4
+ *
5
+ * Unlike the overlays baked into `<RealtimeAvatar />` (which float `absolute`
6
+ * around the face and assume open canvas), these are plain in-flow blocks: you
7
+ * drop them into your own layout slot and they take that slot's width. That
8
+ * makes them safe inside a constrained card — no overflow clipping, no
9
+ * lienzo-infinito assumption.
10
+ *
11
+ * Both flatten markdown to spoken prose (`toPlainText`) and bound the length so
12
+ * a long streamed reply never becomes a wall of text. The caption rolls a
13
+ * trailing window (movie-subtitle style); the thought fades a capped block.
14
+ */
15
+ export interface AvatarCaptionProps {
16
+ /** Raw (possibly markdown, possibly mid-stream) spoken text. */
17
+ text?: string;
18
+ /** Trailing window size in characters. Default 160. */
19
+ maxChars?: number;
20
+ className?: string;
21
+ style?: React.CSSProperties;
22
+ }
23
+ /** Movie-style live caption: a clean, rolling trailing window of the speech. */
24
+ export declare function AvatarCaption({ text, maxChars, className, style }: AvatarCaptionProps): React.JSX.Element;
25
+ export interface AvatarThoughtProps {
26
+ /** Raw (possibly markdown, possibly mid-stream) reasoning text. */
27
+ text?: string;
28
+ /** Trailing window size in characters. Default 220. */
29
+ maxChars?: number;
30
+ /** Label above the reasoning. Default "Thought process". */
31
+ label?: string;
32
+ className?: string;
33
+ style?: React.CSSProperties;
34
+ }
35
+ /** Reasoning gist: a clean, capped trailing window with a pulsing label. */
36
+ export declare function AvatarThought({ text, maxChars, label, className, style, }: AvatarThoughtProps): React.JSX.Element;