@voqalize/avatar 0.4.4 → 0.5.0

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.
Files changed (180) hide show
  1. package/README.md +65 -154
  2. package/dist/index.d.ts +37 -26
  3. package/dist/index.js +48 -25
  4. package/dist/pin.d.ts +2 -0
  5. package/dist/pin.js +3 -0
  6. package/dist/react.d.ts +21 -6
  7. package/dist/react.js +24 -6
  8. package/package.json +8 -67
  9. package/LICENSE-APACHE-2.0 +0 -201
  10. package/LICENSE-CC-BY-4.0 +0 -396
  11. package/assets/README.md +0 -60
  12. package/assets/tanvi.glb +0 -0
  13. package/assets/tanya.glb +0 -0
  14. package/assets/tara.glb +0 -0
  15. package/assets/tess.glb +0 -0
  16. package/assets/tushar.glb +0 -0
  17. package/client/Avatar.tsx +0 -57
  18. package/client/AvatarClient.ts +0 -818
  19. package/client/createAvatar.ts +0 -177
  20. package/client/index.ts +0 -44
  21. package/client/internal.ts +0 -132
  22. package/client/playout.ts +0 -95
  23. package/client/react.ts +0 -13
  24. package/client/supports.ts +0 -27
  25. package/client/three/assets.ts +0 -26
  26. package/client/three/budgets.ts +0 -27
  27. package/client/three/character-rig.ts +0 -1746
  28. package/client/three/createCharacter.ts +0 -109
  29. package/client/three/holds.ts +0 -69
  30. package/client/three/internal.ts +0 -55
  31. package/client/three/motion-limits.json +0 -124
  32. package/client/three/sequences.ts +0 -281
  33. package/client/three/tanvi-asset.ts +0 -6
  34. package/client/three/tanvi.ts +0 -30
  35. package/client/three/tanya-asset.ts +0 -6
  36. package/client/three/tanya.ts +0 -29
  37. package/client/three/tara-asset.ts +0 -25
  38. package/client/three/tara.ts +0 -36
  39. package/client/three/tess-asset.ts +0 -6
  40. package/client/three/tess.ts +0 -30
  41. package/client/three/tushar-asset.ts +0 -6
  42. package/client/three/tushar.ts +0 -28
  43. package/client/types.ts +0 -235
  44. package/client/useAvatar.ts +0 -74
  45. package/dist/Avatar.d.ts +0 -44
  46. package/dist/Avatar.d.ts.map +0 -1
  47. package/dist/Avatar.js +0 -7
  48. package/dist/Avatar.js.map +0 -1
  49. package/dist/AvatarClient.d.ts +0 -274
  50. package/dist/AvatarClient.d.ts.map +0 -1
  51. package/dist/AvatarClient.js +0 -712
  52. package/dist/AvatarClient.js.map +0 -1
  53. package/dist/createAvatar.d.ts +0 -135
  54. package/dist/createAvatar.d.ts.map +0 -1
  55. package/dist/createAvatar.js +0 -75
  56. package/dist/createAvatar.js.map +0 -1
  57. package/dist/index.d.ts.map +0 -1
  58. package/dist/index.js.map +0 -1
  59. package/dist/internal.d.ts +0 -34
  60. package/dist/internal.d.ts.map +0 -1
  61. package/dist/internal.js +0 -56
  62. package/dist/internal.js.map +0 -1
  63. package/dist/playout.d.ts +0 -42
  64. package/dist/playout.d.ts.map +0 -1
  65. package/dist/playout.js +0 -85
  66. package/dist/playout.js.map +0 -1
  67. package/dist/react.d.ts.map +0 -1
  68. package/dist/react.js.map +0 -1
  69. package/dist/supports.d.ts +0 -19
  70. package/dist/supports.d.ts.map +0 -1
  71. package/dist/supports.js +0 -25
  72. package/dist/supports.js.map +0 -1
  73. package/dist/three/assets.d.ts +0 -10
  74. package/dist/three/assets.d.ts.map +0 -1
  75. package/dist/three/assets.js +0 -23
  76. package/dist/three/assets.js.map +0 -1
  77. package/dist/three/budgets.d.ts +0 -17
  78. package/dist/three/budgets.d.ts.map +0 -1
  79. package/dist/three/budgets.js +0 -21
  80. package/dist/three/budgets.js.map +0 -1
  81. package/dist/three/character-rig.d.ts +0 -366
  82. package/dist/three/character-rig.d.ts.map +0 -1
  83. package/dist/three/character-rig.js +0 -1644
  84. package/dist/three/character-rig.js.map +0 -1
  85. package/dist/three/createCharacter.d.ts +0 -60
  86. package/dist/three/createCharacter.d.ts.map +0 -1
  87. package/dist/three/createCharacter.js +0 -84
  88. package/dist/three/createCharacter.js.map +0 -1
  89. package/dist/three/holds.d.ts +0 -38
  90. package/dist/three/holds.d.ts.map +0 -1
  91. package/dist/three/holds.js +0 -61
  92. package/dist/three/holds.js.map +0 -1
  93. package/dist/three/internal.d.ts +0 -52
  94. package/dist/three/internal.d.ts.map +0 -1
  95. package/dist/three/internal.js +0 -49
  96. package/dist/three/internal.js.map +0 -1
  97. package/dist/three/motion-limits.json +0 -124
  98. package/dist/three/sequences.d.ts +0 -71
  99. package/dist/three/sequences.d.ts.map +0 -1
  100. package/dist/three/sequences.js +0 -262
  101. package/dist/three/sequences.js.map +0 -1
  102. package/dist/three/tanvi-asset.d.ts +0 -7
  103. package/dist/three/tanvi-asset.d.ts.map +0 -1
  104. package/dist/three/tanvi-asset.js +0 -7
  105. package/dist/three/tanvi-asset.js.map +0 -1
  106. package/dist/three/tanvi.d.ts +0 -24
  107. package/dist/three/tanvi.d.ts.map +0 -1
  108. package/dist/three/tanvi.js +0 -24
  109. package/dist/three/tanvi.js.map +0 -1
  110. package/dist/three/tanya-asset.d.ts +0 -7
  111. package/dist/three/tanya-asset.d.ts.map +0 -1
  112. package/dist/three/tanya-asset.js +0 -7
  113. package/dist/three/tanya-asset.js.map +0 -1
  114. package/dist/three/tanya.d.ts +0 -23
  115. package/dist/three/tanya.d.ts.map +0 -1
  116. package/dist/three/tanya.js +0 -23
  117. package/dist/three/tanya.js.map +0 -1
  118. package/dist/three/tara-asset.d.ts +0 -26
  119. package/dist/three/tara-asset.d.ts.map +0 -1
  120. package/dist/three/tara-asset.js +0 -26
  121. package/dist/three/tara-asset.js.map +0 -1
  122. package/dist/three/tara.d.ts +0 -30
  123. package/dist/three/tara.d.ts.map +0 -1
  124. package/dist/three/tara.js +0 -30
  125. package/dist/three/tara.js.map +0 -1
  126. package/dist/three/tess-asset.d.ts +0 -7
  127. package/dist/three/tess-asset.d.ts.map +0 -1
  128. package/dist/three/tess-asset.js +0 -7
  129. package/dist/three/tess-asset.js.map +0 -1
  130. package/dist/three/tess.d.ts +0 -24
  131. package/dist/three/tess.d.ts.map +0 -1
  132. package/dist/three/tess.js +0 -24
  133. package/dist/three/tess.js.map +0 -1
  134. package/dist/three/tushar-asset.d.ts +0 -7
  135. package/dist/three/tushar-asset.d.ts.map +0 -1
  136. package/dist/three/tushar-asset.js +0 -7
  137. package/dist/three/tushar-asset.js.map +0 -1
  138. package/dist/three/tushar.d.ts +0 -22
  139. package/dist/three/tushar.d.ts.map +0 -1
  140. package/dist/three/tushar.js +0 -22
  141. package/dist/three/tushar.js.map +0 -1
  142. package/dist/types.d.ts +0 -149
  143. package/dist/types.d.ts.map +0 -1
  144. package/dist/types.js +0 -140
  145. package/dist/types.js.map +0 -1
  146. package/dist/useAvatar.d.ts +0 -39
  147. package/dist/useAvatar.d.ts.map +0 -1
  148. package/dist/useAvatar.js +0 -35
  149. package/dist/useAvatar.js.map +0 -1
  150. package/src/avatar.d.ts +0 -436
  151. package/src/avatar.js +0 -1378
  152. package/src/behavior.d.ts +0 -42
  153. package/src/behavior.js +0 -114
  154. package/src/camera.js +0 -29
  155. package/src/clips.js +0 -155
  156. package/src/conformance.js +0 -119
  157. package/src/emotions.js +0 -58
  158. package/src/face-core.js +0 -180
  159. package/src/face-myna.d.ts +0 -7
  160. package/src/face-myna.js +0 -786
  161. package/src/face-peep-control-plane.js +0 -167
  162. package/src/face-peep.d.ts +0 -7
  163. package/src/face-peep.js +0 -925
  164. package/src/face-wren.d.ts +0 -7
  165. package/src/face-wren.js +0 -468
  166. package/src/faces.d.ts +0 -12
  167. package/src/faces.js +0 -53
  168. package/src/gaze.js +0 -504
  169. package/src/hand.js +0 -709
  170. package/src/head.js +0 -134
  171. package/src/idle.js +0 -668
  172. package/src/interjections.js +0 -747
  173. package/src/line-art.js +0 -137
  174. package/src/params.js +0 -180
  175. package/src/perform.js +0 -107
  176. package/src/prosody.js +0 -741
  177. package/src/rig.d.ts +0 -31
  178. package/src/rig.js +0 -32
  179. package/src/speech-timing.js +0 -51
  180. package/src/visemes.js +0 -410
package/README.md CHANGED
@@ -1,187 +1,98 @@
1
1
  # @voqalize/avatar
2
2
 
3
- A 2-D talking head for AI voice calls, driven by your pipecat client. The
4
- avatars are lip-synced to the audio and they are state aware: they know when
5
- they have been interrupted, when the user is talking versus idle, when a tool
6
- call has started and stopped.
7
-
8
- No video track, no per-minute avatar vendor, no second media path. SVG faces
9
- and 2.5-D characters ship with it, one per entry point, so you pay for the one
10
- you import, and you can author your own.
11
-
12
- This is the browser half. The pipeline half is
13
- [`voqalize-avatar`](https://pypi.org/project/voqalize-avatar/) on PyPI; they are
14
- two ends of one wire format and release independently, kept compatible by the
15
- wire contract rather than a shared version number
16
- ([RELEASING.md § Compatibility](https://github.com/voqalize/avatar/blob/main/RELEASING.md#compatibility)).
17
-
18
- **Licence: MIT for the code and the SVG avatars, CC-BY 4.0 for the
19
- 2.5-D character binaries** (`assets/*.glb` — see `assets/README.md` for the
20
- credit line). Which applies is decided by the kind of avatar, so adding a
21
- character never moves the line. The manifest declares the pair as
22
- `MIT AND CC-BY-4.0`.
3
+ A talking head for AI voice calls. Give it your live `PipecatClient` and an
4
+ element, and a character in that element speaks the bot's audio, lip-synced,
5
+ and shows the state of the call: listening, thinking, being interrupted.
6
+
7
+ This package is the loader. The avatar itself (the characters, the renderer and
8
+ everything that follows the call) is the Voqalize avatar runtime, which this
9
+ package imports from `https://avatar.voqalize.com` when the first avatar mounts.
10
+ Each release of this package is pinned to the runtime it was released with, so
11
+ upgrading the package is how you upgrade the avatar.
23
12
 
24
13
  ## Install
25
14
 
26
15
  ```sh
27
- npm install @voqalize/avatar # this package, the browser half
28
- pip install voqalize-avatar # the pipecat half
16
+ npm install @voqalize/avatar
29
17
  ```
30
18
 
31
- Node 20+. The package is ESM with **no runtime dependencies** and ships its own
32
- types. `@pipecat-ai/client-js` (`>=1.4 <2`) and React (`>=18`) are declared as
33
- *optional* peers: the pipecat import is types-only, so nothing fails to load
34
- without it, and React is only for `@voqalize/avatar/react`.
19
+ The package is ESM, has no runtime dependencies and ships its own types.
20
+ `@pipecat-ai/client-js` (`>=1.4 <2`) and React (`>=18`) are optional peers. The
21
+ pipecat import is used only for types, and React is used only by
22
+ `@voqalize/avatar/react`.
35
23
 
36
- ## Getting started
24
+ ## Mount a character
37
25
 
38
- In the browser, wherever your app already renders the bot's tile:
26
+ Mount it wherever your app renders the bot's tile:
39
27
 
40
28
  ```js
41
- import { createAvatar } from '@voqalize/avatar';
29
+ import { createAvatar } from "@voqalize/avatar";
42
30
 
43
- const avatar = createAvatar({ mount: el, client: pipecatClient });
44
- ```
31
+ const avatar = createAvatar({
32
+ mount: document.querySelector("#bot-tile"),
33
+ client: pipecatClient,
34
+ character: "<name>",
35
+ });
45
36
 
46
- ```jsx
47
- import { Avatar } from '@voqalize/avatar/react';
48
- import { wren } from '@voqalize/avatar/faces/wren'; // `peep` is the default
49
-
50
- <Avatar client={pipecatClient} options={{ face: wren }} className="call-tile" />
37
+ // when the call ends, or the tile goes away
38
+ avatar.destroy();
51
39
  ```
52
40
 
53
- In the pipeline, between the TTS service and the transport's output — the seat
54
- where it can see the audio that is about to be spoken, at generation speed:
55
-
56
- ```python
57
- from voqalize_avatar import AvatarProcessor
58
-
59
- pipeline = Pipeline([..., tts, AvatarProcessor(), transport.output()])
60
- ```
61
-
62
- That is the integration, both halves of it. Neither takes an argument.
63
- `createAvatar` returns `{ destroy() }` and nothing else — the avatar is an
64
- embodiment of your `PipecatClient` and reacts to it, so there is no avatar to
65
- drive and no state to read back.
66
-
67
- ## What you get for free
68
-
69
- Most of the avatar works on any pipecat application without customization — not
70
- because integrations were enumerated, but because the behaviour is derived from
71
- frames and events a pipecat pipeline already emits. `SPEAKING`, `LISTENING`,
72
- `MUTED`, `OFFLINE` and `DEGRADED` come from your `PipecatClient` with no backend
73
- involvement at all; `THINKING`, `WORKING` and `CANT_HEAR` come from
74
- `AvatarProcessor` watching turn boundaries, LLM response boundaries and
75
- function-call frames; lipsync comes from the same karaoke frames pipecat already
76
- pushes for word-level captions; blink, breath, gaze aversion and idle motion are
77
- always the renderer's.
78
-
79
- What is left over is small, specific, and each item is a case the library
80
- refuses to guess at — a deliberate nod or greeting, a tool whose calls never
81
- enter your pipeline, a pose richer than the core states, a backend that is not
82
- ours. [The wire
83
- contract](https://github.com/voqalize/avatar/blob/main/docs/contract-wire.md) is
84
- the canonical reference for all of it.
85
-
86
- ## The wire protocol
87
-
88
- `state`, `action` and `cues`, one envelope
89
- ([contract-wire.md](https://github.com/voqalize/avatar/blob/main/docs/contract-wire.md)):
90
-
91
- ```json
92
- { "type": "avatar", "cmd": "state", "state": "WORKING" }
93
- { "type": "avatar", "cmd": "action", "id": "ACKNOWLEDGE" }
94
- { "type": "avatar", "cmd": "cues", "ctx": "tts-context", "from_ms": 0, "cues": [] }
95
- ```
41
+ `createAvatar` returns at once, and the face appears when the runtime and the
42
+ character have loaded. The avatar owns the contents of `mount` and sizes itself
43
+ to it. `destroy()` is safe to call at any time, including before the face has
44
+ appeared.
96
45
 
97
- **States are durable and they are prioritised.** A state holds until the facts
98
- change; it does not complete on a timer. **The state pipecat reports always
99
- wins** — bot-output lifecycle and user speech are observed Pipecat facts, and a
100
- state the server sends is a *candidate* underneath them. **Actions are
101
- point-in-time animations** that land on top of whatever state is effective at
102
- the time; they are finite, they complete on their own, and they never establish
103
- state.
46
+ `character` names one of the characters you can meet at
47
+ https://voqalize.com/demos/avatar. Each release of this package can mount a
48
+ fixed set of them, and a name it does not have throws, listing the ones it
49
+ does.
104
50
 
105
- Emission is overwrite, never merge: a `cues` message says "discard everything
106
- queued at or after `from_ms`, then append these". The server decides; the client
107
- has no say and no way to refuse.
51
+ The gains are optional, from `0` to `2`, and `1` is as authored:
108
52
 
109
- **Not using our backend?** Any server can produce cues; the ways, best first:
110
- If your TTS emits native viseme events, map the integer ids through
111
- `AZURE_VISEME_TO_LETTER` and ship `{t, v}` as they stream. Otherwise force-align
112
- the text against the audio and map ARPAbet through `ARPABET_TO_VISEME`. With no
113
- server work at all, `textToCues(text)` is a crude grapheme guesser, fit for
114
- previews only. Each is exported from `@voqalize/avatar/internal`.
53
+ - `mouthGain` sets how far the mouth moves.
54
+ - `gestureGain` sets the size of nods and other gestures.
55
+ - `motionGain` sets the amount of idle motion.
115
56
 
116
- ## The faces
57
+ The client must be connected to a Voqalize session that has the avatar turned on.
117
58
 
118
- All hand-authored line art: **`peep`** (the default),
119
- **`wren`**, **`myna`**. Each is its own entry point, and you pass the value
120
- rather than a name:
59
+ ### React
121
60
 
122
- ```js
123
- import { myna } from '@voqalize/avatar/faces/myna';
61
+ ```jsx
62
+ import { Avatar } from "@voqalize/avatar/react";
124
63
 
125
- createAvatar({ mount, client, face: myna });
64
+ <Avatar client={client} character="<name>" className="bot-tile" />
126
65
  ```
127
66
 
128
- A name would need a table, and a table is a dynamic index no bundler can shake —
129
- every drawing in every consumer's bundle to render one.
130
-
131
- ## The 2.5-D characters
132
-
133
- The characters ship as compiled binaries, each its own `createAvatar` module:
67
+ Nothing mounts while `client` is `null`. A new `client` or `character` rebuilds
68
+ the avatar. The gains are read when the avatar mounts. Every other prop goes to
69
+ the `<div>` the avatar renders into.
134
70
 
135
- ```js
136
- import { createAvatar } from '@voqalize/avatar/avatars/tara';
137
- // or: @voqalize/avatar/avatars/tushar
138
- // @voqalize/avatar/avatars/tanya
139
- // @voqalize/avatar/avatars/tess
140
- // @voqalize/avatar/avatars/tanvi
141
-
142
- const avatar = createAvatar({ mount, client: pipecatClient });
143
- ```
71
+ ## Content Security Policy
144
72
 
145
- A photograph of a face projected onto shallow geometry, with the parts that have
146
- to move — eyes, teeth, the lip line — built as geometry rather than painted.
147
- Three.js is an *optional* peer (`three`, `>=0.180 <0.187`) behind those
148
- entry points only, so an SVG consumer never downloads it, and the `.glb`
149
- is fetched when the avatar mounts.
73
+ If your page sets a Content-Security-Policy, add these sources:
150
74
 
151
- Nothing above the renderer changes: the same wire, the same states, the same
152
- cue-synced mouth, and a server that has never heard of these characters drives one
153
- correctly. How far a character's head may turn is not an option either: it is a
154
- limit read off that face by eye, carried per character in
155
- `client/three/motion-limits.json`, which `HEAD_DEG` in
156
- `client/three/character-rig.ts` turns into a pose channel's degrees.
75
+ - `script-src https://avatar.voqalize.com`, because the runtime is a module
76
+ imported from there.
77
+ - `connect-src https://avatar.voqalize.com blob:`, because the runtime fetches
78
+ the character from there and decodes the character's textures from `blob:`
79
+ URLs.
80
+ - `img-src https://avatar.voqalize.com blob:`. The asset host serves the
81
+ still photograph shown where a browser has no WebGL 2, as a call whose other
82
+ side has turned their camera off; without it that tile shows an empty circle.
83
+ `blob:` is for Safari before 17 and Firefox before 98, which decode the
84
+ character's textures through an image element.
157
85
 
158
- **The binaries are artwork under CC-BY 4.0**, separately from the MIT code
159
- around them; the credit line is in `assets/README.md`. Mounting, sizing, the asset
160
- budget and what the characters can be asked to do:
161
- [characters.md](https://github.com/voqalize/avatar/blob/main/docs/characters.md).
86
+ The runtime needs nothing else: no workers, no inline styles and no `eval`.
162
87
 
163
- ## Shipping your own avatar
88
+ ## Bundlers
164
89
 
165
- A whole different rendering technology is not a face; it is a different
166
- `createAvatar`, published as its own module — which is why there is no registry.
167
- The interface is small enough to state in one line:
90
+ The runtime is imported with a literal `import()` of its URL, marked so that
91
+ Vite and webpack leave it alone. Your bundle carries only this package, a few
92
+ kilobytes, and the browser fetches the runtime once per page however many
93
+ avatars mount.
168
94
 
169
- ```ts
170
- createAvatar({ mount, client, ...yourOptions }) -> { destroy() }
171
- ```
95
+ ## Licence
172
96
 
173
- What such an implementation needs to understand is the *wire*, because states,
174
- actions and cues are all an avatar is ever told. `VisemeTrack` in
175
- `@voqalize/avatar/internal` turns a cue array plus a clock into the mouth shape
176
- for the current frame; every renderer needs that and none should write it twice.
177
-
178
- An implementation may also export `supports` — one object naming the action ids
179
- it answers to. Nothing in the library reads it; it is there because the wire's
180
- action id is open and an unknown one is ignored in silence, so a page that
181
- drives an avatar otherwise cannot tell a face that has no such motion from one
182
- that did nothing ([design-avatar-interface.md](https://github.com/voqalize/avatar/blob/main/docs/design-avatar-interface.md)).
183
- Omitting it is conforming.
184
-
185
- **There is deliberately no renderer interface.** The pose channels our SVG mixer
186
- uses to talk to our faces are internal, and a second public contract stays
187
- premature until a second renderer says what it needs.
97
+ This package is MIT; see [LICENSE](./LICENSE). The runtime it loads is not open
98
+ source. Its terms are at https://avatar.voqalize.com/LICENSE.
package/dist/index.d.ts CHANGED
@@ -3,32 +3,43 @@
3
3
  *
4
4
  * import { createAvatar } from "@voqalize/avatar";
5
5
  *
6
- * const avatar = createAvatar({ mount: el, client: pipecatClient });
6
+ * const avatar = createAvatar({ mount: el, client: pipecatClient, character: "tara" });
7
+ * // …
8
+ * avatar.destroy();
7
9
  *
8
- * That is the whole public surface. Put an `AvatarProcessor` in the pipecat
9
- * pipeline (`pip install voqalize-avatar`), mount this in the bot's tile, and
10
- * the face listens, thinks, claims the floor and lipsyncs what the TTS says.
10
+ * This package is the loader and nothing else. The avatar itself — the
11
+ * characters, the renderer and everything that reads the call — is the
12
+ * Voqalize avatar runtime, which this module imports from
13
+ * `avatar.voqalize.com` at the version it was released with and which is
14
+ * licensed separately. `createAvatar` returns at once; the face appears when
15
+ * the runtime and the character have arrived.
11
16
  *
12
- * Entry points, and the split is the design rather than packaging taste:
13
- *
14
- * - `@voqalize/avatar` — this one. Framework-free. `@pipecat-ai/client-js` is
15
- * a type-only import, so even that peer is genuinely optional at runtime.
16
- * It carries exactly one drawing: `peep`, the default face.
17
- * - `@voqalize/avatar/faces/{peep,wren,myna}` — one drawing each. Import the
18
- * one you want and pass it as `face`; the others never enter your bundle.
19
- * - `@voqalize/avatar/react` — `<Avatar>`. Pulls React; nothing here does.
20
- * - `@voqalize/avatar/internal` — the SVG widget, the behavior catalog and the
21
- * viseme clock. **No semver promise**: these move in any minor. They are
22
- * exported because an avatar author building on our renderer needs them, not
23
- * because they are an interface.
24
- *
25
- * To ship your own avatar, publish a module exporting `createAvatar` and import
26
- * that instead — see `createAvatar.ts` and docs/design-avatar-interface.md. A
27
- * module may also export `supports`, which is how a page that *drives* an
28
- * avatar learns which action ids this one answers to; every consumer that only
29
- * mounts a face can ignore it.
17
+ * A page with a Content-Security-Policy needs `https://avatar.voqalize.com` in
18
+ * `script-src` (the runtime), `connect-src` (the character it fetches) and
19
+ * `img-src` (the camera-off still, where there is no WebGL 2), and `blob:` in
20
+ * `connect-src` (the character's textures are decoded from blob URLs;
21
+ * `img-src` too on Safari before 17 and Firefox before 98).
30
22
  */
31
- export { createAvatar } from "./createAvatar.js";
32
- export { supports } from "./supports.js";
33
- export type { AvatarOptions, AvatarInstance, AvatarFactory, AvatarSupport, SvgAvatarOptions, Face, FaceTheme, Gain, HandSide, } from "./createAvatar.js";
34
- //# sourceMappingURL=index.d.ts.map
23
+ import type { PipecatClient } from "@pipecat-ai/client-js";
24
+ /** Amplitude, 0..2. `1` is as authored. */
25
+ export type Gain = number;
26
+ export interface AvatarOptions {
27
+ /** The element to render into. The avatar owns its contents. */
28
+ readonly mount: HTMLElement;
29
+ /** The live `PipecatClient` the avatar embodies. */
30
+ readonly client: PipecatClient;
31
+ /** Which character, by name — `"tara"`, for example. */
32
+ readonly character: string;
33
+ /** Mouth movement. */
34
+ readonly mouthGain?: Gain;
35
+ /** Nods and other gestures. */
36
+ readonly gestureGain?: Gain;
37
+ /** Idle motion. */
38
+ readonly motionGain?: Gain;
39
+ }
40
+ export interface AvatarInstance {
41
+ /** Stop embodying the client and empty the mount. Safe to call at any time,
42
+ * including before the avatar has appeared. */
43
+ destroy(): void;
44
+ }
45
+ export declare function createAvatar(options: AvatarOptions): AvatarInstance;
package/dist/index.js CHANGED
@@ -3,31 +3,54 @@
3
3
  *
4
4
  * import { createAvatar } from "@voqalize/avatar";
5
5
  *
6
- * const avatar = createAvatar({ mount: el, client: pipecatClient });
6
+ * const avatar = createAvatar({ mount: el, client: pipecatClient, character: "tara" });
7
+ * // …
8
+ * avatar.destroy();
7
9
  *
8
- * That is the whole public surface. Put an `AvatarProcessor` in the pipecat
9
- * pipeline (`pip install voqalize-avatar`), mount this in the bot's tile, and
10
- * the face listens, thinks, claims the floor and lipsyncs what the TTS says.
10
+ * This package is the loader and nothing else. The avatar itself — the
11
+ * characters, the renderer and everything that reads the call — is the
12
+ * Voqalize avatar runtime, which this module imports from
13
+ * `avatar.voqalize.com` at the version it was released with and which is
14
+ * licensed separately. `createAvatar` returns at once; the face appears when
15
+ * the runtime and the character have arrived.
11
16
  *
12
- * Entry points, and the split is the design rather than packaging taste:
13
- *
14
- * - `@voqalize/avatar` — this one. Framework-free. `@pipecat-ai/client-js` is
15
- * a type-only import, so even that peer is genuinely optional at runtime.
16
- * It carries exactly one drawing: `peep`, the default face.
17
- * - `@voqalize/avatar/faces/{peep,wren,myna}` — one drawing each. Import the
18
- * one you want and pass it as `face`; the others never enter your bundle.
19
- * - `@voqalize/avatar/react` — `<Avatar>`. Pulls React; nothing here does.
20
- * - `@voqalize/avatar/internal` — the SVG widget, the behavior catalog and the
21
- * viseme clock. **No semver promise**: these move in any minor. They are
22
- * exported because an avatar author building on our renderer needs them, not
23
- * because they are an interface.
24
- *
25
- * To ship your own avatar, publish a module exporting `createAvatar` and import
26
- * that instead — see `createAvatar.ts` and docs/design-avatar-interface.md. A
27
- * module may also export `supports`, which is how a page that *drives* an
28
- * avatar learns which action ids this one answers to; every consumer that only
29
- * mounts a face can ignore it.
17
+ * A page with a Content-Security-Policy needs `https://avatar.voqalize.com` in
18
+ * `script-src` (the runtime), `connect-src` (the character it fetches) and
19
+ * `img-src` (the camera-off still, where there is no WebGL 2), and `blob:` in
20
+ * `connect-src` (the character's textures are decoded from blob URLs;
21
+ * `img-src` too on Safari before 17 and Firefox before 98).
30
22
  */
31
- export { createAvatar } from "./createAvatar.js";
32
- export { supports } from "./supports.js";
33
- //# sourceMappingURL=index.js.map
23
+ import { PROTOCOL, RUNTIME_URL } from "./pin.js";
24
+ let runtime;
25
+ function load() {
26
+ // One import per page, however many avatars mount; a failure is forgotten so
27
+ // the next mount tries again rather than inheriting a dead promise.
28
+ runtime ??= import(/* @vite-ignore */ /* webpackIgnore: true */ RUNTIME_URL).catch((error) => {
29
+ runtime = undefined;
30
+ throw error;
31
+ });
32
+ return runtime;
33
+ }
34
+ export function createAvatar(options) {
35
+ if (!options?.mount)
36
+ throw new TypeError("createAvatar: `mount` is required");
37
+ if (!options.client)
38
+ throw new TypeError("createAvatar: `client` is required");
39
+ if (!options.character)
40
+ throw new TypeError("createAvatar: `character` is required");
41
+ let instance;
42
+ let destroyed = false;
43
+ load()
44
+ .then((rt) => {
45
+ if (!destroyed)
46
+ instance = rt.mount({ ...options, protocol: PROTOCOL });
47
+ })
48
+ .catch((error) => console.error(`[avatar] could not start the avatar from ${RUNTIME_URL}`, error));
49
+ return {
50
+ destroy() {
51
+ destroyed = true;
52
+ instance?.destroy();
53
+ instance = undefined;
54
+ },
55
+ };
56
+ }
package/dist/pin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const RUNTIME_URL = "https://avatar.voqalize.com/runtime/runtime.Y7EOKTPQ.js";
2
+ export declare const PROTOCOL = 1;
package/dist/pin.js ADDED
@@ -0,0 +1,3 @@
1
+ // Written by scripts/build-runtime.mjs — the runtime this shim is released with.
2
+ export const RUNTIME_URL = "https://avatar.voqalize.com/runtime/runtime.Y7EOKTPQ.js";
3
+ export const PROTOCOL = 1;
package/dist/react.d.ts CHANGED
@@ -3,11 +3,26 @@
3
3
  *
4
4
  * import { Avatar } from "@voqalize/avatar/react";
5
5
  *
6
- * <Avatar client={session.client} className="avatar-tile" />
6
+ * <Avatar client={session.client} character="tara" className="avatar-tile" />
7
7
  *
8
- * Separate from the main entry so that `createAvatar` costs nothing to a
9
- * caller who is not on React — React is an optional peer, and a barrel that
10
- * re-exported this would make it a hard one.
8
+ * Separate from the main entry so that `createAvatar` costs nothing to a caller
9
+ * who is not on React. Everything but the avatar's own props is forwarded to
10
+ * the mount `<div>`, so it sizes and styles like the tile it lives in.
11
11
  */
12
- export { Avatar, type AvatarProps } from "./Avatar.js";
13
- //# sourceMappingURL=react.d.ts.map
12
+ import { type HTMLAttributes } from "react";
13
+ import type { PipecatClient } from "@pipecat-ai/client-js";
14
+ import { type Gain } from "./index.js";
15
+ export type AvatarProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
16
+ /** The live `PipecatClient`, or `null` before connect. Nothing mounts until
17
+ * it is non-null. */
18
+ client?: PipecatClient | null;
19
+ /** Which character, by name. */
20
+ character: string;
21
+ /** Read at mount only. */
22
+ mouthGain?: Gain;
23
+ /** Read at mount only. */
24
+ gestureGain?: Gain;
25
+ /** Read at mount only. */
26
+ motionGain?: Gain;
27
+ };
28
+ export declare function Avatar({ client, character, mouthGain, gestureGain, motionGain, ...rest }: AvatarProps): import("react").JSX.Element;
package/dist/react.js CHANGED
@@ -1,13 +1,31 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
1
2
  /**
2
3
  * `@voqalize/avatar/react` — the avatar as one component.
3
4
  *
4
5
  * import { Avatar } from "@voqalize/avatar/react";
5
6
  *
6
- * <Avatar client={session.client} className="avatar-tile" />
7
+ * <Avatar client={session.client} character="tara" className="avatar-tile" />
7
8
  *
8
- * Separate from the main entry so that `createAvatar` costs nothing to a
9
- * caller who is not on React — React is an optional peer, and a barrel that
10
- * re-exported this would make it a hard one.
9
+ * Separate from the main entry so that `createAvatar` costs nothing to a caller
10
+ * who is not on React. Everything but the avatar's own props is forwarded to
11
+ * the mount `<div>`, so it sizes and styles like the tile it lives in.
11
12
  */
12
- export { Avatar } from "./Avatar.js";
13
- //# sourceMappingURL=react.js.map
13
+ import { useEffect, useRef } from "react";
14
+ import { createAvatar } from "./index.js";
15
+ export function Avatar({ client, character, mouthGain, gestureGain, motionGain, ...rest }) {
16
+ const ref = useRef(null);
17
+ // The gains are read at mount through a ref, so a fresh literal on every
18
+ // render does not rebuild the face.
19
+ const gains = useRef({ mouthGain, gestureGain, motionGain });
20
+ gains.current = { mouthGain, gestureGain, motionGain };
21
+ useEffect(() => {
22
+ const mount = ref.current;
23
+ if (!mount || !client)
24
+ return;
25
+ const avatar = createAvatar({ mount, client, character, ...gains.current });
26
+ return () => avatar.destroy();
27
+ // A new client or character is a new thing to embody, so the avatar is
28
+ // rebuilt rather than re-pointed.
29
+ }, [client, character]);
30
+ return _jsx("div", { role: "img", "aria-label": "avatar", ...rest, ref: ref });
31
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@voqalize/avatar",
3
- "version": "0.4.4",
4
- "description": "A JavaScript-programmable talking head for AI voice calls: line-art or 2.5-D faces, server-synced lipsync, state through motion, no runtime dependencies.",
5
- "license": "MIT AND CC-BY-4.0 AND Apache-2.0",
3
+ "version": "0.5.0",
4
+ "description": "A talking head for AI voice calls that embodies your pipecat client. This package loads the Voqalize avatar runtime.",
5
+ "license": "MIT",
6
6
  "author": "Voqalize",
7
7
  "homepage": "https://github.com/voqalize/avatar#readme",
8
8
  "repository": {
@@ -17,14 +17,8 @@
17
17
  "avatar",
18
18
  "talking-head",
19
19
  "lipsync",
20
- "viseme",
21
- "rhubarb",
22
20
  "pipecat",
23
- "voice-agent",
24
- "svg",
25
- "animation",
26
- "3d",
27
- "threejs"
21
+ "voice-agent"
28
22
  ],
29
23
  "engines": {
30
24
  "node": ">=20"
@@ -45,66 +39,20 @@
45
39
  "types": "./dist/react.d.ts",
46
40
  "default": "./dist/react.js"
47
41
  },
48
- "./internal": {
49
- "types": "./dist/internal.d.ts",
50
- "default": "./dist/internal.js"
51
- },
52
- "./internal/three": {
53
- "types": "./dist/three/internal.d.ts",
54
- "default": "./dist/three/internal.js"
55
- },
56
- "./faces/peep": {
57
- "types": "./src/face-peep.d.ts",
58
- "default": "./src/face-peep.js"
59
- },
60
- "./faces/wren": {
61
- "types": "./src/face-wren.d.ts",
62
- "default": "./src/face-wren.js"
63
- },
64
- "./faces/myna": {
65
- "types": "./src/face-myna.d.ts",
66
- "default": "./src/face-myna.js"
67
- },
68
- "./avatars/tara": {
69
- "types": "./dist/three/tara.d.ts",
70
- "default": "./dist/three/tara.js"
71
- },
72
- "./avatars/tushar": {
73
- "types": "./dist/three/tushar.d.ts",
74
- "default": "./dist/three/tushar.js"
75
- },
76
- "./avatars/tanya": {
77
- "types": "./dist/three/tanya.d.ts",
78
- "default": "./dist/three/tanya.js"
79
- },
80
- "./avatars/tess": {
81
- "types": "./dist/three/tess.d.ts",
82
- "default": "./dist/three/tess.js"
83
- },
84
- "./avatars/tanvi": {
85
- "types": "./dist/three/tanvi.d.ts",
86
- "default": "./dist/three/tanvi.js"
87
- },
88
42
  "./package.json": "./package.json"
89
43
  },
90
44
  "files": [
91
- "src",
92
- "client",
93
- "dist",
94
- "assets",
95
- "LICENSE-CC-BY-4.0",
96
- "LICENSE-APACHE-2.0"
45
+ "dist"
97
46
  ],
98
47
  "scripts": {
99
- "build": "tsc -p tsconfig.json",
48
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
100
49
  "typecheck": "tsc -p tsconfig.json --noEmit",
101
- "prepare": "tsc -p tsconfig.json"
50
+ "prepare": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json"
102
51
  },
103
52
  "peerDependencies": {
104
53
  "@pipecat-ai/client-js": ">=1.4 <2",
105
54
  "react": ">=18",
106
- "react-dom": ">=18",
107
- "three": ">=0.180 <0.187"
55
+ "react-dom": ">=18"
108
56
  },
109
57
  "peerDependenciesMeta": {
110
58
  "@pipecat-ai/client-js": {
@@ -115,13 +63,6 @@
115
63
  },
116
64
  "react-dom": {
117
65
  "optional": true
118
- },
119
- "three": {
120
- "optional": true
121
66
  }
122
- },
123
- "devDependencies": {
124
- "@types/three": "^0.185.4",
125
- "three": "^0.180.0"
126
67
  }
127
68
  }