@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
@@ -1,30 +0,0 @@
1
- /**
2
- * tanvi — Indian, and the first character whose hair is not part of her head.
3
- *
4
- * Her GLB is the shared scripts run with her landmarks (`characters/tanvi/`).
5
- * Her long waves fall past both shoulders onto her chest, which a `Hair` shell
6
- * painted into the head's atlas cannot carry: it can only be where the head is.
7
- * So her photograph is split into her with the hair pulled back, which is what
8
- * every other script reads, and the hair alone, which becomes a `HairLayer`
9
- * mesh of its own — turning with the skull, holding back its share of a roll
10
- * like tanya's hanging hair, lifting with the shoulders below the chin, and
11
- * drawn over everything behind it (`character-rig.ts`).
12
- *
13
- * Her head angles have not been read off her yet. `motion-limits.json` says so
14
- * rather than guessing, and `headHold` gives her the tightest budget any face
15
- * is still vouched for until the owner drives her.
16
- */
17
-
18
- import { character } from "./createCharacter.js";
19
- import type { CharacterAvatarOptions } from "./createCharacter.js";
20
- import { BLENDER_SUPPORTS } from "./sequences.js";
21
- import { TANVI_GLB } from "./tanvi-asset.js";
22
-
23
- export type { CharacterAvatarOptions as AvatarOptions };
24
- export type { AvatarInstance } from "./createCharacter.js";
25
-
26
- /** The optional driving-UI declaration; one list for every Blender character
27
- * (`sequences.ts`), because they are one rig. */
28
- export const supports = BLENDER_SUPPORTS;
29
-
30
- export const createAvatar = character("tanvi", TANVI_GLB);
@@ -1,6 +0,0 @@
1
- /**
2
- * Where tanya's compiled GLB is. One character, one module — see
3
- * [tara-asset.ts](./tara-asset.ts) for why that separation is load-bearing and
4
- * why the `new URL` literal is spelled exactly this way.
5
- */
6
- export const TANYA_GLB = new URL("../../assets/tanya.glb", import.meta.url).href;
@@ -1,29 +0,0 @@
1
- /**
2
- * tanya — the third 3-D character, and the first whose atlas is not tara's
3
- * window.
4
- *
5
- * Her GLB is the shared scripts run with her landmarks and atlas layout
6
- * (`characters/tanya/`, whose README has the provenance and the commands).
7
- * Her hair is the silhouette down past the jaw, out to |u| 0.761 where tara's
8
- * window stops at 0.64, so `characters/tanya/face_texture.py` widens it to
9
- * 1408x1568 at the same 800 pixels per face height. That is a fact about the
10
- * asset and costs this file nothing — the rig reads the window from the GLB.
11
- *
12
- * Hair that hangs is what else makes her the third rather than a fourth copy:
13
- * hers is the only `Hair` mesh that carries the roll channels, because it is
14
- * the only one with anything below the hold line to lag behind a tilt.
15
- */
16
-
17
- import { character } from "./createCharacter.js";
18
- import type { CharacterAvatarOptions } from "./createCharacter.js";
19
- import { BLENDER_SUPPORTS } from "./sequences.js";
20
- import { TANYA_GLB } from "./tanya-asset.js";
21
-
22
- export type { CharacterAvatarOptions as AvatarOptions };
23
- export type { AvatarInstance } from "./createCharacter.js";
24
-
25
- /** The optional driving-UI declaration; one list for every Blender character
26
- * (`sequences.ts`), because they are one rig. */
27
- export const supports = BLENDER_SUPPORTS;
28
-
29
- export const createAvatar = character("tanya", TANYA_GLB);
@@ -1,25 +0,0 @@
1
- /**
2
- * Where tara's compiled GLB is — one character, one module, on purpose.
3
- *
4
- * `new URL(…, import.meta.url)` and not a bundler's asset import: these files
5
- * are fetched at runtime by a consumer's app, and the spelling has to survive
6
- * Vite, webpack, Rollup, esbuild, a plain `tsc` output and a browser loading the
7
- * module directly. The query-suffix form this used to carry is Vite syntax —
8
- * every other toolchain, including the compiler that now builds this directory,
9
- * passes it through verbatim and produces an import of a file that is not there.
10
- *
11
- * The depth is the same from the source tree and from the compiled one
12
- * (`client/three/` and `dist/three/` are siblings), so one literal is correct in
13
- * both — and literal is load-bearing, because a bundler can only follow this
14
- * pattern when it can read the path without running anything.
15
- *
16
- * **One module per character is the whole point, and it is a bundling fact, not
17
- * tidiness.** A bundler that can follow `new URL` emits the file it names, and
18
- * it decides what to emit per *module*: three literals in one object meant a
19
- * consumer who imported one character shipped all three GLBs — ~1.3 MB of a
20
- * character they never mount, in every build we checked. Splitting them is what
21
- * makes `@voqalize/avatar/avatars/tara` cost tara. Nothing that a character
22
- * module loads may reach for another character's URL, which is also why the rig
23
- * takes its `url` from its caller instead of defaulting to tara's.
24
- */
25
- export const TARA_GLB = new URL("../../assets/tara.glb", import.meta.url).href;
@@ -1,36 +0,0 @@
1
- /**
2
- * tara — the first 3-D character, as a complete `createAvatar` module.
3
- *
4
- * Structurally this is the canvas avatars' shape, one layer down: the mixer
5
- * (`@voqalize/avatar/internal`) does states, layers, gaze, idle, blinks, clips
6
- * and per-channel smoothing exactly as it does for every SVG face, and the only
7
- * thing a Blender character replaces is the renderer at the end of it. That is
8
- * the whole argument for authoring her morph targets under the library's pose
9
- * channel names: nothing above this line knows there is a GPU involved, and
10
- * every clip, viseme and co-articulation rule the SVG faces have works here
11
- * unmodified.
12
- *
13
- * How a Blender character is driven is `createCharacter.ts`, and it is the same
14
- * for all of them. What is hers is the mesh — and, until 2026-09-23, rather
15
- * more than that: this file was the original, the others were copies of it, and
16
- * the performance every character inherits was measured on her face. It is the
17
- * house performance now (`CHARACTER_TUNING`), which changes nothing about how
18
- * she looks and everything about where it is decided.
19
- *
20
- * The Three.js import lives behind this module rather than in the barrel so an
21
- * SVG or Canvas consumer never downloads it.
22
- */
23
-
24
- import { character } from "./createCharacter.js";
25
- import type { CharacterAvatarOptions } from "./createCharacter.js";
26
- import { BLENDER_SUPPORTS } from "./sequences.js";
27
- import { TARA_GLB } from "./tara-asset.js";
28
-
29
- export type { CharacterAvatarOptions as AvatarOptions };
30
- export type { AvatarInstance } from "./createCharacter.js";
31
-
32
- /** The optional driving-UI declaration; one list for every Blender character
33
- * (`sequences.ts`), because they are one rig. */
34
- export const supports = BLENDER_SUPPORTS;
35
-
36
- export const createAvatar = character("tara", TARA_GLB);
@@ -1,6 +0,0 @@
1
- /**
2
- * Where tess's compiled GLB is. One character, one module — see
3
- * [tara-asset.ts](./tara-asset.ts) for why that separation is load-bearing and
4
- * why the `new URL` literal is spelled exactly this way.
5
- */
6
- export const TESS_GLB = new URL("../../assets/tess.glb", import.meta.url).href;
@@ -1,30 +0,0 @@
1
- /**
2
- * tess — American, and the leanest character in the forge.
3
- *
4
- * Her GLB is the shared scripts run with her landmarks (`characters/tess/`,
5
- * whose README has the provenance and the commands). She is back inside tara's
6
- * atlas window — her hair is drawn into a low tail that clears both ears and
7
- * reaches |u| 0.492 against a window that stops at 0.64 — so unlike tanya she
8
- * needs no `face_texture.py` of her own, and her `Hair` shell gives up no roll:
9
- * nothing of hers hangs past the jaw to lag. One supplied sheet, no paid edit,
10
- * no module of her own; she is what a new character costs at the floor, which
11
- * is why the README builds its template from her.
12
- *
13
- * Her head angles have not been read off her yet. `motion-limits.json` says so
14
- * rather than guessing, and `headHold` gives her the tightest budget any face
15
- * is still vouched for until the owner drives her.
16
- */
17
-
18
- import { character } from "./createCharacter.js";
19
- import type { CharacterAvatarOptions } from "./createCharacter.js";
20
- import { BLENDER_SUPPORTS } from "./sequences.js";
21
- import { TESS_GLB } from "./tess-asset.js";
22
-
23
- export type { CharacterAvatarOptions as AvatarOptions };
24
- export type { AvatarInstance } from "./createCharacter.js";
25
-
26
- /** The optional driving-UI declaration; one list for every Blender character
27
- * (`sequences.ts`), because they are one rig. */
28
- export const supports = BLENDER_SUPPORTS;
29
-
30
- export const createAvatar = character("tess", TESS_GLB);
@@ -1,6 +0,0 @@
1
- /**
2
- * Where tushar's compiled GLB is. One character, one module — see
3
- * [tara-asset.ts](./tara-asset.ts) for why that separation is load-bearing and
4
- * why the `new URL` literal is spelled exactly this way.
5
- */
6
- export const TUSHAR_GLB = new URL("../../assets/tushar.glb", import.meta.url).href;
@@ -1,28 +0,0 @@
1
- /**
2
- * tushar — the second 3-D character, and the one who proved the factory.
3
- *
4
- * His GLB is the shared scripts run with his landmarks and atlas layout in
5
- * place of tara's (`characters/tushar/`, whose README has the provenance and
6
- * the commands). He was a deliberate copy of `tara.ts` for as long as there was
7
- * a `tara.ts` to copy; since 2026-09-23 there is one Blender entry point and he
8
- * is its second caller rather than her second draft (`createCharacter.ts`).
9
- *
10
- * He is also the face that turned the ledger from a question into a decision.
11
- * What was marked TARA-SPECIFIC was marked that way because one face cannot
12
- * tell a convention from a coincidence; he agreed with her, and so did the two
13
- * after him.
14
- */
15
-
16
- import { character } from "./createCharacter.js";
17
- import type { CharacterAvatarOptions } from "./createCharacter.js";
18
- import { BLENDER_SUPPORTS } from "./sequences.js";
19
- import { TUSHAR_GLB } from "./tushar-asset.js";
20
-
21
- export type { CharacterAvatarOptions as AvatarOptions };
22
- export type { AvatarInstance } from "./createCharacter.js";
23
-
24
- /** The optional driving-UI declaration; one list for every Blender character
25
- * (`sequences.ts`), because they are one rig. */
26
- export const supports = BLENDER_SUPPORTS;
27
-
28
- export const createAvatar = character("tushar", TUSHAR_GLB);
package/client/types.ts DELETED
@@ -1,235 +0,0 @@
1
- /**
2
- * types.ts — the avatar wire vocabulary, client side.
3
- *
4
- * The binding definition is `docs/contract-wire.md`; this file is its
5
- * TypeScript restatement and must not drift from it. The Python half of the
6
- * same vocabulary is `packages/avatar-py/src/voqalize_avatar/messages.py` — the three are
7
- * maintained together, and a command added to one without the others is
8
- * incomplete.
9
- *
10
- * A server pushes these as RTVI `server-message`s under the envelope
11
- * `{ type: "avatar", ...cmd-specific fields }`. {@link AvatarCommand} describes
12
- * what rides inside that envelope; {@link isAvatarMessage} is the envelope
13
- * itself, and is the only definition of "this message is for the avatar" the
14
- * client has. There is no protocol version field — an unknown `cmd` is simply
15
- * ignored, which is the whole forward-compatibility rule.
16
- */
17
-
18
- import { VISEME_LETTERS } from "../src/avatar.js";
19
- import type { VisemeLetter } from "../src/avatar.js";
20
-
21
- /** A viseme cue: `t` is a ms offset into the utterance's clock, `v` is a Rhubarb A–H (or X) letter. */
22
- export interface AvatarCue {
23
- t: number;
24
- v: VisemeLetter;
25
- /** 0..1 loudness. Absent means full. */
26
- i?: number;
27
- /**
28
- * The phone being articulated under `v`, absent during silence.
29
- *
30
- * `v` is a nine-way projection of the recogniser's ~40 phones and the loss is
31
- * concentrated — `B` alone absorbs IY, IH, T, D, CH, JH, TH, DH, S, Z, SH,
32
- * ZH, N and Y — so a renderer with a mouth for "tongue between the teeth"
33
- * cannot ask for it from `v` and can from here. **Not a closed set**: it is
34
- * whatever the engine labels its own segments with, which today is Arpabet
35
- * plus `Schwa` and four non-speech labels, and a later engine may say
36
- * something else. Validated as an identifier and passed through unread —
37
- * the letter stays the thing every face must handle, and a face that wants
38
- * the finer signal opts in.
39
- */
40
- p?: string;
41
- }
42
-
43
- /**
44
- * A durable, lower-priority server state. `null` explicitly clears it.
45
- *
46
- * Three values, and the server can send no others: the remaining six of the
47
- * nine are Pipecat facts the browser already holds, and a server spelling of a
48
- * fact would be a second, lower-authority copy of it. It was `cmd: "claim"`
49
- * until the wire redesign, when the concept and the command were given the one
50
- * name the rest of the system already used for it.
51
- */
52
- export interface AvatarStateCmd {
53
- cmd: "state";
54
- state: "CANT_HEAR" | "THINKING" | "WORKING" | null;
55
- }
56
-
57
- /**
58
- * One self-completing motion, by name: face, body, and optionally a hand.
59
- *
60
- * **The id is open.** Two of them — {@link CORE_ACTION_IDS} — are intents every
61
- * renderer owes a server and are the only ones a server may send without
62
- * knowing what is mounted. Everything else is a name from the mounted avatar's
63
- * own catalogue, and **an unknown one is ignored, not an error**: that is the
64
- * same forward-compatibility rule an unknown `cmd` gets, arrived at from the
65
- * other direction. There, a newer server meets an older widget; here, any
66
- * server meets a face that cannot do the thing. Neither is worth breaking a
67
- * call over, and there is deliberately no fallback — an action sits on top of a
68
- * state, so nothing is missing when one is dropped.
69
- *
70
- * This replaced a closed seven-id vocabulary plus a second command,
71
- * `sequence`, for a renderer's own motions. The split cost a promotion ritual
72
- * for every new portable intent and still could not say what the closed set was
73
- * for: the listening research separates a continuer nod from an assessment nod
74
- * from a realisation, and all three were the one id `ACK_NOD` spelled. Now the
75
- * server says *when* to acknowledge and the avatar owns the variety.
76
- */
77
- export interface AvatarActionCmd {
78
- cmd: "action";
79
- id: string;
80
- }
81
-
82
- /**
83
- * The two actions every renderer must answer to.
84
- *
85
- * `ACKNOWLEDGE` is the whole backchannel family in one word — receipt, nod,
86
- * realisation, empathy — because which of those a face does is a rendering
87
- * decision and the server is not the one holding the drawing.
88
- * `RESPONSE_INTERRUPTED` is the one transition a server can explain and the
89
- * browser cannot infer.
90
- *
91
- * A renderer may draw either in its own shape; it may never redefine one, and
92
- * a name in its own catalogue never shadows one of these.
93
- */
94
- export const CORE_ACTION_IDS = Object.freeze(["ACKNOWLEDGE", "RESPONSE_INTERRUPTED"] as const);
95
-
96
- export type CoreActionId = (typeof CORE_ACTION_IDS)[number];
97
-
98
- export interface AvatarCuesCmd {
99
- cmd: "cues";
100
- ctx: string;
101
- /** Discard queued cues at or after this offset (ms), then append `cues`. */
102
- from_ms: number;
103
- cues: AvatarCue[];
104
- /**
105
- * True on the one chunk that completes this turn's track: the TTS context is
106
- * closed, so no further chunk will splice into `ctx`. What a client may
107
- * assume, exactly — nothing about playout. The audio it describes is still
108
- * ahead, and Pipecat's `botStoppedSpeaking` remains the end of the turn. It
109
- * is safe to stop expecting more cue chunks after `final`.
110
- *
111
- * Absent on an interrupted turn, deliberately: a turn that was cut never
112
- * claims to have completed. Absent chunks are the normal case — the widget's
113
- * own track already completes on the trailing `X`, so ignoring `final`
114
- * entirely is a correct implementation.
115
- */
116
- final?: boolean;
117
- }
118
-
119
- export type AvatarCommand =
120
- | AvatarStateCmd
121
- | AvatarActionCmd
122
- | AvatarCuesCmd;
123
-
124
- /** The full server-message payload: the envelope plus its command. */
125
- export type AvatarServerMessage = AvatarCommand & { type: "avatar" };
126
-
127
- /** An envelope addressed to the avatar, before its payload has been read. */
128
- export type AvatarEnvelope = { type: "avatar"; cmd: string } & Record<string, unknown>;
129
-
130
- /**
131
- * Is this server-message payload the avatar's? The envelope is the whole
132
- * answer: `{type:"avatar"}` with a string `cmd`. It used to be a per-deployment
133
- * `accept` predicate on the client, which meant the library could not state
134
- * what an avatar message *is*.
135
- *
136
- * Addressed to us is not the same as understood by us: {@link parseAvatarCommand}
137
- * is the second half.
138
- */
139
- export function isAvatarMessage(msg: unknown): msg is AvatarEnvelope {
140
- if (typeof msg !== "object" || msg === null) return false;
141
- const m = msg as Record<string, unknown>;
142
- return m.type === AVATAR_MESSAGE_TYPE && typeof m.cmd === "string";
143
- }
144
-
145
- /**
146
- * What an action id may look like. Not which ones exist — that is the mounted
147
- * avatar's business and this function has never met it. `CATEGORY_INTENT` in
148
- * upper case, bounded so a malformed or hostile message cannot smuggle
149
- * anything through as a name.
150
- */
151
- const ACTION_ID = /^[A-Z][A-Z0-9_]{1,63}$/;
152
- const LETTERS = new Set<string>(VISEME_LETTERS);
153
- /**
154
- * What a phone label may look like. Letters only, because every label any
155
- * engine we have seen is one word of them — `AO`, `NG`, `Schwa`, `Breath` —
156
- * and the bound is what stops a name being a payload.
157
- */
158
- const PHONE_LABEL = /^[A-Za-z]{1,12}$/;
159
-
160
- /**
161
- * Read an envelope's payload into the wire vocabulary, or `null` if this build
162
- * cannot act on it — an unknown `cmd`, a state outside the three, an action id
163
- * that is not a name, a malformed `cues` chunk.
164
- *
165
- * `null` is the forward-compat rule with a type attached: a newer server
166
- * talking to an older widget is *expected*, and the older widget ignores what
167
- * it does not know rather than guessing. Doing the check here rather than at
168
- * each use site is what lets the wire types be closed unions instead of
169
- * `string` — the boundary is one function, so it can be the only place that
170
- * has to be honest about untrusted input.
171
- *
172
- * Cues survive individually: one unrecognised letter in a chunk drops that cue,
173
- * not the utterance around it. Losing a frame of articulation is a far smaller
174
- * regression than losing a sentence of it.
175
- */
176
- export function parseAvatarCommand(msg: AvatarEnvelope): AvatarCommand | null {
177
- switch (msg.cmd) {
178
- // `claim` was this command's name, and `STRAINING` one of its values, until
179
- // the wire redesign. Both are accepted and translated here, in the one
180
- // place that reads untrusted input, so no layer above has ever heard of
181
- // either spelling. Servers that predate the rename keep working; the old
182
- // names appear nowhere else and come out when the last one has shipped.
183
- case "claim":
184
- case "state": {
185
- const state = msg.state === "STRAINING" ? "CANT_HEAR" : msg.state;
186
- if (state === null || state === "CANT_HEAR" || state === "THINKING" || state === "WORKING") {
187
- return { cmd: "state", state };
188
- }
189
- return null;
190
- }
191
- // `sequence` was a second command for the same thing: a name resolved
192
- // against the mounted avatar rather than against a closed list. Now that
193
- // `action` is that, the two are one command and the old one is an alias.
194
- case "sequence":
195
- case "action":
196
- // Shape only, both for a core id and for one of the avatar's own. There
197
- // is nothing to check a name against here — the catalogue belongs to
198
- // whatever is mounted, and this function has never met it.
199
- return typeof msg.id === "string" && ACTION_ID.test(msg.id)
200
- ? { cmd: "action", id: msg.id }
201
- : null;
202
- case "cues": {
203
- if (typeof msg.ctx !== "string" || !Number.isFinite(msg.from_ms) || !Array.isArray(msg.cues)) {
204
- return null;
205
- }
206
- const cues: AvatarCue[] = [];
207
- for (const c of msg.cues as unknown[]) {
208
- if (typeof c !== "object" || c === null) continue;
209
- const { t, v, i, p } = c as Record<string, unknown>;
210
- if (typeof t !== "number" || !Number.isFinite(t)) continue;
211
- if (typeof v !== "string" || !LETTERS.has(v)) continue;
212
- const cue: AvatarCue = { t, v: v as VisemeLetter };
213
- if (typeof i === "number") cue.i = i;
214
- // Bounded as a name, not checked against a list. The set belongs to
215
- // whatever produced the cue, so enumerating it here would make this
216
- // library the thing that has to be released before a better recogniser
217
- // can say a new word.
218
- if (typeof p === "string" && PHONE_LABEL.test(p)) cue.p = p;
219
- cues.push(cue);
220
- }
221
- return {
222
- cmd: "cues",
223
- ctx: msg.ctx,
224
- from_ms: msg.from_ms as number,
225
- cues,
226
- ...(msg.final === true ? { final: true as const } : {}),
227
- };
228
- }
229
- default:
230
- return null;
231
- }
232
- }
233
-
234
- /** The envelope `type` the protocol reserves for avatar traffic. */
235
- export const AVATAR_MESSAGE_TYPE = "avatar";
@@ -1,74 +0,0 @@
1
- /**
2
- * useAvatar — mount an avatar into a ref'd element for as long as there is a
3
- * client to embody, and tear it down after.
4
- *
5
- * Internal; `<Avatar>` is the only thing the React entry exports. The whole
6
- * hook is one effect, because the factory takes the client at construction:
7
- * there is no separate attach step to keep in its own lifecycle any more.
8
- */
9
-
10
- import { useEffect, useRef } from "react";
11
- import type { PipecatClient } from "@pipecat-ai/client-js";
12
- import {
13
- createAvatar as createSvgAvatar,
14
- type AvatarFactory,
15
- type AvatarOptions,
16
- type SvgAvatarOptions,
17
- } from "./createAvatar.js";
18
-
19
- export interface UseAvatarOptions<O extends AvatarOptions = SvgAvatarOptions> {
20
- /** The live `PipecatClient`, or `null` before connect. Nothing mounts until
21
- * this is non-null — an avatar with nothing to embody has nothing to do. */
22
- client?: PipecatClient | null;
23
- /** The avatar implementation. Defaults to the bundled SVG faces. */
24
- create?: AvatarFactory<O>;
25
- /** Implementation options, forwarded verbatim. Read at mount only. */
26
- options?: Omit<O, keyof AvatarOptions>;
27
- }
28
-
29
- /**
30
- * The mount ref's type, written out rather than named as React's `RefObject`.
31
- *
32
- * React 18 and 19 declare that alias with different type arguments — 18's
33
- * `useRef<T>(null)` yields `RefObject<T>`, 19's yields `RefObject<T | null>` —
34
- * and because both are the *same alias*, TypeScript compares them by variance
35
- * and rejects whichever one we didn't pick. An anonymous shape forces a
36
- * structural comparison instead, which both versions satisfy, and which the
37
- * `ref` prop accepts on both. This is the only place the 18-vs-19 split shows
38
- * up in the binding; keep it that way.
39
- */
40
- export type AvatarMountRef = { current: HTMLDivElement | null };
41
-
42
- export interface UseAvatarHandle {
43
- /** Attach to the mount element: `<div ref={containerRef} />`. */
44
- containerRef: AvatarMountRef;
45
- }
46
-
47
- export function useAvatar<O extends AvatarOptions = SvgAvatarOptions>(
48
- { client, create, options }: UseAvatarOptions<O> = {},
49
- ): UseAvatarHandle {
50
- const containerRef = useRef<HTMLDivElement>(null);
51
-
52
- // Latest-options ref, so the mount effect reads the live values without
53
- // remounting the face every time a caller passes a fresh object literal.
54
- const optionsRef = useRef(options);
55
- optionsRef.current = options;
56
-
57
- useEffect(() => {
58
- const mount = containerRef.current;
59
- if (!mount || !client) return;
60
- const factory = (create ?? createSvgAvatar) as AvatarFactory<O>;
61
- // The cast is the seam between "O minus the two we supply" and O. It is
62
- // sound by construction and TypeScript cannot see through the spread of a
63
- // generic; the two halves are typed at the boundary the caller touches.
64
- const instance = factory({ mount, client, ...optionsRef.current } as unknown as O);
65
- return () => instance.destroy();
66
- // A new client identity is a new thing to embody, so the avatar is rebuilt
67
- // rather than re-pointed. Hosts keep one `PipecatClient` across
68
- // connect/disconnect cycles, so this does not fire on an ordinary
69
- // reconnect; if yours constructs a fresh client per session, expect the
70
- // face to remount with it.
71
- }, [client, create]);
72
-
73
- return { containerRef };
74
- }
package/dist/Avatar.d.ts DELETED
@@ -1,44 +0,0 @@
1
- /**
2
- * Avatar — `createAvatar` as a call tile, and the whole of `@voqalize/avatar/react`.
3
- *
4
- * <Avatar client={session.client} className="avatar-tile" />
5
- *
6
- * Three props of its own; everything else is forwarded to the mount `<div>`, so
7
- * it sizes and styles like the tile it lives in. There is nothing to configure
8
- * because there is nothing the server does not already say: the
9
- * `AvatarProcessor` in the pipeline drives the state, the gaze and the mouth.
10
- *
11
- * `create` is how you use a different avatar — any module exporting
12
- * `createAvatar` (docs/design-avatar-interface.md), including a Rive or WebGL
13
- * one. The component is generic over that implementation's options, so
14
- * `options` is checked against the factory you passed rather than being a bag:
15
- *
16
- * <Avatar client={c} create={createMascot} options={{ mood: "sly" }} />
17
- *
18
- * With no `create` it is the bundled SVG avatar and `options` is
19
- * `SvgAvatarOptions` minus `mount`/`client`, which the component supplies.
20
- *
21
- * The component renders an empty div and a static `role="img"`. It does not
22
- * label the avatar with its current state: the implementation owns the DOM
23
- * inside the mount and is the only thing that knows what it is portraying —
24
- * and a live label would be this package reading back an avatar's internal
25
- * state, which is the one thing the interface refuses to promise. Pass your
26
- * own `aria-label` to override.
27
- */
28
- import type { HTMLAttributes } from "react";
29
- import type { PipecatClient } from "@pipecat-ai/client-js";
30
- import type { AvatarFactory, AvatarOptions, SvgAvatarOptions } from "./createAvatar.js";
31
- /** An implementation's own options: everything it takes past the two the
32
- * component supplies itself. */
33
- export type ImplementationOptions<O extends AvatarOptions> = Omit<O, keyof AvatarOptions>;
34
- export type AvatarProps<O extends AvatarOptions = SvgAvatarOptions> = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
35
- /** The live `PipecatClient`, or `null` before connect. Nothing renders
36
- * until it is non-null. */
37
- client?: PipecatClient | null;
38
- /** An avatar implementation. Omit for the bundled SVG faces. */
39
- create?: AvatarFactory<O>;
40
- /** Options for that implementation. Read at mount only. */
41
- options?: ImplementationOptions<O>;
42
- };
43
- export declare function Avatar<O extends AvatarOptions = SvgAvatarOptions>({ client, create, options, ...rest }: AvatarProps<O>): import("react").JSX.Element;
44
- //# sourceMappingURL=Avatar.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../client/Avatar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAExF;gCACgC;AAChC,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,aAAa,IAAI,IAAI,CAAC,CAAC,EAAE,MAAM,aAAa,CAAC,CAAC;AAE1F,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,aAAa,GAAG,gBAAgB,IAChE,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,GAAG;IACjD;+BAC2B;IAC3B,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,gEAAgE;IAChE,MAAM,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1B,2DAA2D;IAC3D,OAAO,CAAC,EAAE,qBAAqB,CAAC,CAAC,CAAC,CAAC;CACpC,CAAC;AAEJ,wBAAgB,MAAM,CAAC,CAAC,SAAS,aAAa,GAAG,gBAAgB,EAAE,EACjE,MAAM,EACN,MAAM,EACN,OAAO,EACP,GAAG,IAAI,EACR,EAAE,WAAW,CAAC,CAAC,CAAC,+BAGhB"}
package/dist/Avatar.js DELETED
@@ -1,7 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useAvatar } from "./useAvatar.js";
3
- export function Avatar({ client, create, options, ...rest }) {
4
- const { containerRef } = useAvatar({ client, create, options });
5
- return _jsx("div", { role: "img", "aria-label": "avatar", ...rest, ref: containerRef });
6
- }
7
- //# sourceMappingURL=Avatar.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Avatar.js","sourceRoot":"","sources":["../client/Avatar.tsx"],"names":[],"mappings":";AA8BA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAkB3C,MAAM,UAAU,MAAM,CAA6C,EACjE,MAAM,EACN,MAAM,EACN,OAAO,EACP,GAAG,IAAI,EACQ;IACf,MAAM,EAAE,YAAY,EAAE,GAAG,SAAS,CAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACnE,OAAO,cAAK,IAAI,EAAC,KAAK,gBAAY,QAAQ,KAAK,IAAI,EAAE,GAAG,EAAE,YAAY,GAAI,CAAC;AAC7E,CAAC"}