@voqalize/avatar 0.2.1 → 0.3.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.
- package/LICENSE +21 -661
- package/README.md +144 -663
- package/client/Avatar.tsx +57 -0
- package/client/AvatarClient.ts +523 -0
- package/client/arjun.ts +24 -0
- package/client/createAvatar.ts +139 -0
- package/client/createCanvasAvatar.ts +72 -0
- package/client/index.ts +39 -0
- package/client/internal.ts +80 -0
- package/client/interviewer-female.ts +4 -0
- package/client/interviewer-male.ts +4 -0
- package/client/ishita.ts +24 -0
- package/client/kabir.ts +24 -0
- package/client/meera.ts +24 -0
- package/client/naina.ts +24 -0
- package/client/professional-female-a.ts +4 -0
- package/client/professional-female-b.ts +4 -0
- package/client/professional-male-a.ts +4 -0
- package/client/professional-male-b.ts +4 -0
- package/client/react.ts +13 -0
- package/client/types.ts +146 -0
- package/client/useAvatar.ts +74 -0
- package/client/vikram.ts +24 -0
- package/dist/Avatar.d.ts +44 -0
- package/dist/Avatar.d.ts.map +1 -0
- package/{client/dist → dist}/Avatar.js +2 -2
- package/dist/Avatar.js.map +1 -0
- package/dist/AvatarClient.d.ts +196 -0
- package/dist/AvatarClient.d.ts.map +1 -0
- package/dist/AvatarClient.js +450 -0
- package/dist/AvatarClient.js.map +1 -0
- package/dist/arjun.d.ts +6 -0
- package/dist/arjun.d.ts.map +1 -0
- package/dist/arjun.js +18 -0
- package/dist/arjun.js.map +1 -0
- package/dist/createAvatar.d.ts +99 -0
- package/dist/createAvatar.d.ts.map +1 -0
- package/dist/createAvatar.js +75 -0
- package/dist/createAvatar.js.map +1 -0
- package/dist/createCanvasAvatar.d.ts +22 -0
- package/dist/createCanvasAvatar.d.ts.map +1 -0
- package/dist/createCanvasAvatar.js +47 -0
- package/dist/createCanvasAvatar.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +30 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +33 -0
- package/dist/internal.js.map +1 -0
- package/dist/interviewer-female.d.ts +4 -0
- package/dist/interviewer-female.d.ts.map +1 -0
- package/dist/interviewer-female.js +3 -0
- package/dist/interviewer-female.js.map +1 -0
- package/dist/interviewer-male.d.ts +4 -0
- package/dist/interviewer-male.d.ts.map +1 -0
- package/dist/interviewer-male.js +3 -0
- package/dist/interviewer-male.js.map +1 -0
- package/dist/ishita.d.ts +6 -0
- package/dist/ishita.d.ts.map +1 -0
- package/dist/ishita.js +18 -0
- package/dist/ishita.js.map +1 -0
- package/dist/kabir.d.ts +6 -0
- package/dist/kabir.d.ts.map +1 -0
- package/dist/kabir.js +18 -0
- package/dist/kabir.js.map +1 -0
- package/dist/meera.d.ts +6 -0
- package/dist/meera.d.ts.map +1 -0
- package/dist/meera.js +18 -0
- package/dist/meera.js.map +1 -0
- package/dist/naina.d.ts +6 -0
- package/dist/naina.d.ts.map +1 -0
- package/dist/naina.js +18 -0
- package/dist/naina.js.map +1 -0
- package/dist/professional-female-a.d.ts +4 -0
- package/dist/professional-female-a.d.ts.map +1 -0
- package/dist/professional-female-a.js +3 -0
- package/dist/professional-female-a.js.map +1 -0
- package/dist/professional-female-b.d.ts +4 -0
- package/dist/professional-female-b.d.ts.map +1 -0
- package/dist/professional-female-b.js +3 -0
- package/dist/professional-female-b.js.map +1 -0
- package/dist/professional-male-a.d.ts +4 -0
- package/dist/professional-male-a.d.ts.map +1 -0
- package/dist/professional-male-a.js +3 -0
- package/dist/professional-male-a.js.map +1 -0
- package/dist/professional-male-b.d.ts +4 -0
- package/dist/professional-male-b.d.ts.map +1 -0
- package/dist/professional-male-b.js +3 -0
- package/dist/professional-male-b.js.map +1 -0
- package/dist/react.d.ts +13 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +13 -0
- package/dist/react.js.map +1 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +93 -0
- package/dist/types.js.map +1 -0
- package/dist/useAvatar.d.ts +39 -0
- package/dist/useAvatar.d.ts.map +1 -0
- package/dist/useAvatar.js +35 -0
- package/dist/useAvatar.js.map +1 -0
- package/dist/vikram.d.ts +6 -0
- package/dist/vikram.d.ts.map +1 -0
- package/dist/vikram.js +18 -0
- package/dist/vikram.js.map +1 -0
- package/package.json +81 -24
- package/src/avatar.d.ts +161 -120
- package/src/avatar.js +234 -146
- package/src/behavior.d.ts +26 -0
- package/src/behavior.js +102 -0
- package/src/camera.js +29 -0
- package/src/canvas/author/parts/eye.mjs +722 -0
- package/src/canvas/author/parts/hand.mjs +1156 -0
- package/src/canvas/author/parts/mouth.mjs +741 -0
- package/src/canvas/author/parts/nose.mjs +100 -0
- package/src/canvas/author/parts/skin-detail.mjs +67 -0
- package/src/canvas/author/path.mjs +283 -0
- package/src/canvas/author/rig.mjs +405 -0
- package/src/canvas/avatars/round/face.d.mts +3 -0
- package/src/canvas/avatars/round/face.mjs +1307 -0
- package/src/canvas/create-rig.d.ts +15 -0
- package/src/canvas/create-rig.js +100 -0
- package/src/canvas/data/img/professional-female-a-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-female-a-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-female-a-top-body.webp +0 -0
- package/src/canvas/data/img/professional-female-b-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-female-b-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-female-b-top-body.webp +0 -0
- package/src/canvas/data/img/professional-male-a-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-male-a-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-male-a-top-body.webp +0 -0
- package/src/canvas/data/img/professional-male-b-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-male-b-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-male-b-top-body.webp +0 -0
- package/src/canvas/data/img/round-m3-hair-back.webp +0 -0
- package/src/canvas/data/img/round-m3-hair-front.webp +0 -0
- package/src/canvas/data/img/round-m3-top-body.webp +0 -0
- package/src/canvas/data/img/round-w1-hair-back.webp +0 -0
- package/src/canvas/data/img/round-w1-hair-front.webp +0 -0
- package/src/canvas/data/img/round-w1-top-body.webp +0 -0
- package/src/canvas/data/interviewer-female.rig.json +1 -0
- package/src/canvas/data/interviewer-male.rig.json +1 -0
- package/src/canvas/data/professional-female-a.rig.json +1 -0
- package/src/canvas/data/professional-female-b.rig.json +1 -0
- package/src/canvas/data/professional-male-a.rig.json +1 -0
- package/src/canvas/data/professional-male-b.rig.json +1 -0
- package/src/canvas/src/live.js +508 -0
- package/src/canvas/src/render2d.js +218 -0
- package/src/canvas/src/rig.js +297 -0
- package/src/canvas/src/vocab.js +96 -0
- package/src/clips.js +18 -7
- package/src/conformance.js +119 -0
- package/src/emotions.js +4 -3
- package/src/face-core.js +27 -1
- package/src/face-myna.d.ts +7 -0
- package/src/face-myna.js +192 -131
- package/src/face-peep-control-plane.js +167 -0
- package/src/face-peep.d.ts +7 -0
- package/src/face-peep.js +274 -116
- package/src/face-wren.d.ts +7 -0
- package/src/face-wren.js +17 -19
- package/src/faces.d.ts +12 -0
- package/src/faces.js +53 -0
- package/src/gaze.js +119 -4
- package/src/hand.js +120 -91
- package/src/idle.js +18 -101
- package/src/interjections.js +156 -31
- package/src/params.js +8 -4
- package/src/perform.js +7 -9
- package/src/rig.d.ts +24 -0
- package/src/rig.js +32 -0
- package/src/speech-timing.js +23 -0
- package/src/visemes.js +28 -12
- package/client/dist/Avatar.d.ts +0 -27
- package/client/dist/Avatar.d.ts.map +0 -1
- package/client/dist/Avatar.js.map +0 -1
- package/client/dist/AvatarClient.d.ts +0 -139
- package/client/dist/AvatarClient.d.ts.map +0 -1
- package/client/dist/AvatarClient.js +0 -223
- package/client/dist/AvatarClient.js.map +0 -1
- package/client/dist/index.d.ts +0 -22
- package/client/dist/index.d.ts.map +0 -1
- package/client/dist/index.js +0 -22
- package/client/dist/index.js.map +0 -1
- package/client/dist/types.d.ts +0 -86
- package/client/dist/types.d.ts.map +0 -1
- package/client/dist/types.js +0 -31
- package/client/dist/types.js.map +0 -1
- package/client/dist/useAvatar.d.ts +0 -42
- package/client/dist/useAvatar.d.ts.map +0 -1
- package/client/dist/useAvatar.js +0 -51
- package/client/dist/useAvatar.js.map +0 -1
- package/client/src/Avatar.tsx +0 -33
- package/client/src/AvatarClient.ts +0 -270
- package/client/src/index.ts +0 -22
- package/client/src/types.ts +0 -104
- package/client/src/useAvatar.ts +0 -85
- package/docs/contract-avatar.md +0 -371
- package/docs/contract-protocol.md +0 -461
package/src/interjections.js
CHANGED
|
@@ -26,9 +26,13 @@ const nod = (peak, at = 0.24, rebound = 0.56) => [
|
|
|
26
26
|
[0, 0], [at, peak], [rebound, -peak * 0.24], [1, 0],
|
|
27
27
|
];
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// This is the authoring library. It intentionally contains exploratory clips
|
|
30
|
+
// as well as production ones; only the small `ACTIONS` export below is
|
|
31
|
+
// part of the public action contract.
|
|
32
|
+
const CLIPS = {
|
|
30
33
|
// ---------------------------------------------------------------------
|
|
31
|
-
//
|
|
34
|
+
// Wordless acknowledgement clips. Every one is an explicit backend or
|
|
35
|
+
// application decision; the client never fires them from VAD or a timer.
|
|
32
36
|
// ---------------------------------------------------------------------
|
|
33
37
|
// The nod peaks below are pre-compensated for the head's 160ms time constant.
|
|
34
38
|
// Authored at their perceptual value (~0.3) they rendered at ~0.6x and read as
|
|
@@ -44,26 +48,27 @@ export const INTERJECTIONS = {
|
|
|
44
48
|
// drops further than the trend — and everything stays under the 1.5 Hz line
|
|
45
49
|
// where sustained attention turns into impatience.
|
|
46
50
|
NOD_SMALL: {
|
|
47
|
-
id: 'NOD_SMALL', label: 'nod (continuer)', text: '', duration:
|
|
51
|
+
id: 'NOD_SMALL', label: 'nod (continuer)', text: '', duration: 900,
|
|
48
52
|
keys: {
|
|
49
|
-
// One
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
// One readable stroke, not an ambient bob. The peak is deliberately
|
|
54
|
+
// authored above its desired rendered travel because the common head
|
|
55
|
+
// smoothing absorbs a fast impulse before it reaches the SVG.
|
|
56
|
+
headPitch: [[0, 0], [0.25, 0.74], [0.38, 0.70], [0.68, -0.12], [1, 0]],
|
|
57
|
+
browRaiseL: [[0, 0], [0.34, 0.16], [1, 0]],
|
|
58
|
+
browRaiseR: [[0, 0], [0.34, 0.16], [1, 0]],
|
|
54
59
|
},
|
|
55
60
|
},
|
|
56
61
|
NOD_SLOW: {
|
|
57
|
-
id: 'NOD_SLOW', label: 'nod (assessment
|
|
62
|
+
id: 'NOD_SLOW', label: 'nod (assessment / receipt)', text: '', duration: 1200,
|
|
58
63
|
keys: {
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// A receipt needs an arrival. The former two fast peaks registered as
|
|
65
|
+
// idle bobbing at call-tile size, so this is one deep downstroke with a
|
|
66
|
+
// short dwell and a slower recovery. The tiny torso commitment keeps the
|
|
67
|
+
// head from looking like it is sliding independently of the body.
|
|
68
|
+
headPitch: [[0, 0], [0.18, 0.18], [0.39, 1.06], [0.52, 1.02], [0.78, -0.16], [1, 0]],
|
|
69
|
+
torsoLean: [[0, 0], [0.40, 0.09], [0.58, 0.07], [1, 0]],
|
|
70
|
+
browRaiseL: [[0, 0], [0.23, 0.12], [0.62, 0.08], [1, 0]],
|
|
71
|
+
browRaiseR: [[0, 0], [0.23, 0.10], [0.62, 0.07], [1, 0]],
|
|
67
72
|
},
|
|
68
73
|
},
|
|
69
74
|
// The "ah — I see" nod. What separates it from agreement is the upswing:
|
|
@@ -88,13 +93,93 @@ export const INTERJECTIONS = {
|
|
|
88
93
|
keys: {
|
|
89
94
|
browRaiseL: [[0, 0], [0.24, 0.30], [0.7, 0.06], [1, 0]],
|
|
90
95
|
browRaiseR: [[0, 0], [0.24, 0.26], [0.7, 0.06], [1, 0]],
|
|
91
|
-
headPitch: [[0, 0], [0.3, 0.08], [1, 0]],
|
|
92
96
|
mouthCornerL: [[0, 0], [0.35, 0.14], [1, 0]],
|
|
93
97
|
mouthCornerR: [[0, 0], [0.35, 0.14], [1, 0]],
|
|
94
98
|
},
|
|
95
99
|
blinkAt: [0.22],
|
|
96
100
|
},
|
|
97
|
-
//
|
|
101
|
+
// ---------------------------------------------------------------------
|
|
102
|
+
// Understanding beats — eye/face/body composites, deliberately separate
|
|
103
|
+
// from nods. They are the shared low-authoring-cost alternative when a
|
|
104
|
+
// portrait's pitch geometry is too limited to carry acknowledgement alone.
|
|
105
|
+
// ---------------------------------------------------------------------
|
|
106
|
+
ACK_CONTINUE: {
|
|
107
|
+
id: 'ACK_CONTINUE', label: 'acknowledge: continue', text: '', duration: 620,
|
|
108
|
+
keys: {
|
|
109
|
+
// Eyes acknowledge first; nothing here claims agreement or closes the
|
|
110
|
+
// conversational floor while the user is still talking.
|
|
111
|
+
browRaiseL: [[0, 0], [0.18, 0.14], [0.48, 0.06], [1, 0]],
|
|
112
|
+
browRaiseR: [[0, 0], [0.18, 0.12], [0.48, 0.05], [1, 0]],
|
|
113
|
+
lidL: [[0, 0], [0.20, 0.035], [0.58, 0.01], [1, 0]],
|
|
114
|
+
lidR: [[0, 0], [0.20, 0.035], [0.58, 0.01], [1, 0]],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
ACK_RECEIVE: {
|
|
118
|
+
id: 'ACK_RECEIVE', label: 'acknowledge: received', text: '', duration: 1120,
|
|
119
|
+
keys: {
|
|
120
|
+
// Recognition → take it in → settle. The face does not nod; it lands in
|
|
121
|
+
// a quiet, held receipt, leaving room for the user to see the response.
|
|
122
|
+
browRaiseL: [[0, 0], [0.12, 0.16], [0.34, 0.055], [1, 0]],
|
|
123
|
+
browRaiseR: [[0, 0], [0.12, 0.13], [0.34, 0.045], [1, 0]],
|
|
124
|
+
lidL: [[0, 0], [0.16, 0.15], [0.49, 0.14], [0.82, 0.035], [1, 0]],
|
|
125
|
+
lidR: [[0, 0], [0.16, 0.15], [0.49, 0.14], [0.82, 0.035], [1, 0]],
|
|
126
|
+
mouthPress: [[0, 0], [0.23, 0.20], [0.60, 0.17], [1, 0]],
|
|
127
|
+
mouthCornerL: [[0, 0], [0.34, 0.08], [0.80, 0.055], [1, 0]],
|
|
128
|
+
mouthCornerR: [[0, 0], [0.34, 0.08], [0.80, 0.055], [1, 0]],
|
|
129
|
+
torsoLean: [[0, 0], [0.34, 0.13], [0.70, 0.10], [1, 0]],
|
|
130
|
+
shoulderL: [[0, 0], [0.50, 0.070], [0.76, 0.045], [1, 0]],
|
|
131
|
+
shoulderR: [[0, 0], [0.50, 0.070], [0.76, 0.045], [1, 0]],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
ACK_REALIZE: {
|
|
135
|
+
id: 'ACK_REALIZE', label: 'acknowledge: realization', text: '', duration: 980,
|
|
136
|
+
keys: {
|
|
137
|
+
// The brows and widened lid lead: this means "the point connected", not
|
|
138
|
+
// the generic social smile which looks like agreement in a small tile.
|
|
139
|
+
browRaiseL: [[0, 0], [0.15, 0.36], [0.42, 0.20], [1, 0]],
|
|
140
|
+
browRaiseR: [[0, 0], [0.15, 0.32], [0.42, 0.18], [1, 0]],
|
|
141
|
+
lidL: [[0, 0], [0.16, -0.07], [0.48, -0.025], [1, 0]],
|
|
142
|
+
lidR: [[0, 0], [0.16, -0.07], [0.48, -0.025], [1, 0]],
|
|
143
|
+
mouthCornerL: [[0, 0], [0.35, 0.12], [0.80, 0.09], [1, 0]],
|
|
144
|
+
mouthCornerR: [[0, 0], [0.35, 0.12], [0.80, 0.09], [1, 0]],
|
|
145
|
+
torsoLean: [[0, 0], [0.38, 0.07], [0.72, 0.05], [1, 0]],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
ACK_EMPATHIZE: {
|
|
149
|
+
id: 'ACK_EMPATHIZE', label: 'acknowledge: empathy', text: '', duration: 1180,
|
|
150
|
+
keys: {
|
|
151
|
+
// Inner brows and softened lids acknowledge the affect without signalling
|
|
152
|
+
// agreement. Keep the mouth nearly neutral for professional contexts.
|
|
153
|
+
browInnerL: [[0, 0], [0.22, 0.24], [0.68, 0.16], [1, 0]],
|
|
154
|
+
browInnerR: [[0, 0], [0.22, 0.20], [0.68, 0.13], [1, 0]],
|
|
155
|
+
browRaiseL: [[0, 0], [0.22, 0.07], [0.68, 0.05], [1, 0]],
|
|
156
|
+
browRaiseR: [[0, 0], [0.22, 0.06], [0.68, 0.04], [1, 0]],
|
|
157
|
+
lidL: [[0, 0], [0.25, 0.06], [0.72, 0.04], [1, 0]],
|
|
158
|
+
lidR: [[0, 0], [0.25, 0.06], [0.72, 0.04], [1, 0]],
|
|
159
|
+
mouthPress: [[0, 0], [0.34, 0.06], [0.72, 0.045], [1, 0]],
|
|
160
|
+
torsoLean: [[0, 0], [0.40, 0.065], [0.76, 0.045], [1, 0]],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
// A comparison nod, not the director's default. Two quick decaying strokes
|
|
164
|
+
// with a barely-there listening tilt reflect the common conversational form
|
|
165
|
+
// the current single deep receipt does not show. It stays comparison-only:
|
|
166
|
+
// the host can decide whether this faster cadence fits its conversation.
|
|
167
|
+
ACK_NOD: {
|
|
168
|
+
id: 'ACK_NOD', label: 'acknowledge: nod', text: '', duration: 1120,
|
|
169
|
+
keys: {
|
|
170
|
+
// Values are intentionally a little theatrical at close range: at a
|
|
171
|
+
// video-call tile the head's 160 ms smoothing otherwise eats the second
|
|
172
|
+
// beat and the slight listening tilt entirely.
|
|
173
|
+
headPitch: [[0, 0], [0.12, -0.15], [0.30, 0.98], [0.46, -0.13], [0.64, 0.68], [0.82, -0.09], [1, 0]],
|
|
174
|
+
headRoll: [[0, 0], [0.18, -0.090], [0.68, -0.070], [1, 0]],
|
|
175
|
+
torsoLean: [[0, 0], [0.31, 0.115], [0.68, 0.075], [1, 0]],
|
|
176
|
+
shoulderL: [[0, 0], [0.43, 0.055], [0.73, 0.032], [1, 0]],
|
|
177
|
+
shoulderR: [[0, 0], [0.43, 0.055], [0.73, 0.032], [1, 0]],
|
|
178
|
+
lidL: [[0, 0], [0.30, 0.065], [0.64, 0.038], [1, 0]],
|
|
179
|
+
lidR: [[0, 0], [0.30, 0.065], [0.64, 0.038], [1, 0]],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
// The disagree family follows the same three laws as explicit nods (§3.4): the
|
|
98
183
|
// first swing is the biggest, every cycle decays, and the whole gesture
|
|
99
184
|
// stays at or under ~1.5 Hz. Both are SERVER-SENT ONLY — an agent must
|
|
100
185
|
// never disagree autonomously, so neither is in the listening engine's
|
|
@@ -211,6 +296,24 @@ export const INTERJECTIONS = {
|
|
|
211
296
|
blinkAt: [0.1],
|
|
212
297
|
},
|
|
213
298
|
|
|
299
|
+
/** Server-confirmed cut-off after playout stopped. This is not a state: the
|
|
300
|
+
* clip explains the abrupt transition, then lands in whatever factual pose
|
|
301
|
+
* Pipecat has resolved underneath it. */
|
|
302
|
+
RESPONSE_INTERRUPTED: {
|
|
303
|
+
id: 'RESPONSE_INTERRUPTED', label: 'response: interrupted', text: '', duration: 1550,
|
|
304
|
+
keys: {
|
|
305
|
+
mouthOpen: [[0, 0], [0.06, 0.36], [0.80, 0.36], [1, 0]],
|
|
306
|
+
jaw: [[0, 0], [0.08, 0.20], [0.80, 0.20], [1, 0]],
|
|
307
|
+
browRaiseL: [[0, 0], [0.12, 0.46], [0.72, 0.34], [1, 0]],
|
|
308
|
+
browRaiseR: [[0, 0], [0.12, 0.40], [0.72, 0.30], [1, 0]],
|
|
309
|
+
lidL: [[0, 0], [0.12, -0.18], [0.72, -0.12], [1, 0]],
|
|
310
|
+
lidR: [[0, 0], [0.12, -0.16], [0.72, -0.10], [1, 0]],
|
|
311
|
+
torsoLean: [[0, 0], [0.18, -0.16], [0.72, -0.10], [1, 0]],
|
|
312
|
+
shoulderL: [[0, 0], [0.18, -0.12], [0.72, -0.08], [1, 0]],
|
|
313
|
+
shoulderR: [[0, 0], [0.18, -0.12], [0.72, -0.08], [1, 0]],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
|
|
214
317
|
/**
|
|
215
318
|
* "May I come in." This used to be a raised palm, and it is now the same
|
|
216
319
|
* signal without one: the body claims a little space and the face asks. The
|
|
@@ -273,8 +376,8 @@ export const INTERJECTIONS = {
|
|
|
273
376
|
* oscillation left to fill the time; a held smile past about a second stops
|
|
274
377
|
* being a greeting and becomes an expression.
|
|
275
378
|
*/
|
|
276
|
-
|
|
277
|
-
id: '
|
|
379
|
+
GESTURE_GREET: {
|
|
380
|
+
id: 'GESTURE_GREET', label: 'gesture: greet', text: '', duration: 1300,
|
|
278
381
|
keys: {
|
|
279
382
|
browRaiseL: [[0, 0], [0.11, 0.85], [0.4, 0.22], [0.75, 0.12], [1, 0]],
|
|
280
383
|
browRaiseR: [[0, 0], [0.11, 0.80], [0.4, 0.20], [0.75, 0.10], [1, 0]],
|
|
@@ -304,8 +407,8 @@ export const INTERJECTIONS = {
|
|
|
304
407
|
* Deep enough to be unmistakable: peak 0.62 against NOD_SMALL's 0.52, over
|
|
305
408
|
* nearly three times the duration.
|
|
306
409
|
*/
|
|
307
|
-
|
|
308
|
-
id: '
|
|
410
|
+
GESTURE_APPROVE: {
|
|
411
|
+
id: 'GESTURE_APPROVE', label: 'gesture: approve', text: '', duration: 1500,
|
|
309
412
|
keys: {
|
|
310
413
|
headPitch: [[0, 0], [0.3, 0.62], [0.58, 0.20], [0.78, 0.34], [1, 0]],
|
|
311
414
|
mouthCornerL: [[0, 0], [0.36, 0.58], [0.8, 0.48], [1, 0]],
|
|
@@ -473,8 +576,8 @@ export const INTERJECTIONS = {
|
|
|
473
576
|
torsoLean: [[0, 0], [0.25, 0.10], [0.7, 0.08], [1, 0]],
|
|
474
577
|
},
|
|
475
578
|
},
|
|
476
|
-
|
|
477
|
-
id: '
|
|
579
|
+
GESTURE_WAIT: {
|
|
580
|
+
id: 'GESTURE_WAIT', label: 'gesture: wait', text: 'one moment', duration: 1350,
|
|
478
581
|
// w · ʌ · n · m · oʊ · m · ə · n · t
|
|
479
582
|
mouthCues: [
|
|
480
583
|
{ t: 0, v: 'F' }, { t: 110, v: 'C' }, { t: 210, v: 'A' }, { t: 320, v: 'F' },
|
|
@@ -557,19 +660,41 @@ export const INTERJECTIONS = {
|
|
|
557
660
|
},
|
|
558
661
|
};
|
|
559
662
|
|
|
560
|
-
|
|
663
|
+
/** Full local authoring library. Not a server action vocabulary. */
|
|
664
|
+
export const INTERNAL_CLIPS = CLIPS;
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Public, server-addressable actions. Naming is `CATEGORY_INTENT`:
|
|
668
|
+
* acknowledgements are `ACK_*`, the special transition is `RESPONSE_*`, and
|
|
669
|
+
* visible hand/body movements are `GESTURE_*`. This is deliberately an intent
|
|
670
|
+
* vocabulary — `ACK_NOD` is one implementation, not a promise about anatomy.
|
|
671
|
+
*/
|
|
672
|
+
export const ACTION_IDS = Object.freeze([
|
|
673
|
+
'ACK_RECEIVE', 'ACK_NOD',
|
|
674
|
+
'RESPONSE_INTERRUPTED',
|
|
675
|
+
'GESTURE_GREET', 'GESTURE_GOODBYE', 'GESTURE_APPROVE', 'GESTURE_WAIT',
|
|
676
|
+
]);
|
|
561
677
|
|
|
562
|
-
/**
|
|
563
|
-
export const
|
|
678
|
+
/** Face-capable subset of the action contract. */
|
|
679
|
+
export const ACTIONS = Object.freeze({
|
|
680
|
+
...Object.fromEntries(ACTION_IDS.filter((id) => CLIPS[id]).map((id) => [id, CLIPS[id]])),
|
|
681
|
+
// The goodbye face intentionally reuses the greeting brow-flash while its
|
|
682
|
+
// hand owns the distinct, longer farewell motion. It still needs metadata so
|
|
683
|
+
// a client can enumerate every public action without knowing that detail.
|
|
684
|
+
GESTURE_GOODBYE: {
|
|
685
|
+
...CLIPS.GESTURE_GREET,
|
|
686
|
+
id: 'GESTURE_GOODBYE', label: 'gesture: goodbye', duration: 1550,
|
|
687
|
+
},
|
|
688
|
+
});
|
|
564
689
|
|
|
565
690
|
/**
|
|
566
|
-
* Attach audio to
|
|
567
|
-
*
|
|
691
|
+
* Attach audio to an internal authoring clip. Production actions are silent
|
|
692
|
+
* sequences; response speech always belongs to Pipecat's audio/viseme track.
|
|
568
693
|
* The baked viseme track is then scheduled against that file's clock instead of
|
|
569
694
|
* the local timer, so any timing drift resolves in the audio's favour.
|
|
570
695
|
*/
|
|
571
696
|
export function attachAudio(id, url) {
|
|
572
|
-
const clip =
|
|
697
|
+
const clip = CLIPS[id];
|
|
573
698
|
if (!clip) throw new Error(`unknown interjection: ${id}`);
|
|
574
699
|
const el = new Audio(url);
|
|
575
700
|
el.preload = 'auto';
|
package/src/params.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* mouthCornerL/R + up (smile)
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { JAW_RESPONSE_TAU_S, MOUTH_RESPONSE_TAU_S } from './speech-timing.js';
|
|
18
|
+
|
|
17
19
|
export const REST = {
|
|
18
20
|
// --- mouth -------------------------------------------------------------
|
|
19
21
|
mouthOpen: 0.02, // vertical aperture, 0..1
|
|
@@ -21,8 +23,10 @@ export const REST = {
|
|
|
21
23
|
mouthRound: 0.1, // pucker / lip protrusion
|
|
22
24
|
mouthPress: 0.15, // lips thinned & pressed together
|
|
23
25
|
mouthTuck: 0.0, // lower lip drawn under upper teeth (F/V)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
// Availability is not a permanent smile. Warmth and encouragement are
|
|
27
|
+
// explicit behavioral choices layered above this neutral resting shape.
|
|
28
|
+
mouthCornerL: 0.0, // -1 frown .. +1 smile
|
|
29
|
+
mouthCornerR: 0.0,
|
|
26
30
|
teethUpper: 0.0, // how far the upper teeth show, 0..1
|
|
27
31
|
tongue: 0.0, // tongue raised into the aperture, 0..1
|
|
28
32
|
jaw: 0.0, // extra chin drop, follows mouthOpen but slower
|
|
@@ -118,7 +122,7 @@ export const GROUPS = {
|
|
|
118
122
|
export const TAU = (() => {
|
|
119
123
|
const t = {};
|
|
120
124
|
for (const c of CHANNELS) t[c] = 0.09;
|
|
121
|
-
for (const c of GROUPS.mouth) t[c] =
|
|
125
|
+
for (const c of GROUPS.mouth) t[c] = MOUTH_RESPONSE_TAU_S;
|
|
122
126
|
for (const c of GROUPS.smile) t[c] = 0.13;
|
|
123
127
|
t.lidL = t.lidR = 0.018; // blinks must be crisp
|
|
124
128
|
t.squintL = t.squintR = 0.12;
|
|
@@ -126,7 +130,7 @@ export const TAU = (() => {
|
|
|
126
130
|
for (const c of GROUPS.brows) t[c] = 0.08;
|
|
127
131
|
for (const c of GROUPS.head) t[c] = 0.16; // the head has real mass
|
|
128
132
|
t.breath = 0.25;
|
|
129
|
-
t.jaw =
|
|
133
|
+
t.jaw = JAW_RESPONSE_TAU_S; // the jaw lags the lips slightly
|
|
130
134
|
// The torso has more mass than the head and reads wrong when it hasn't.
|
|
131
135
|
t.shoulderL = t.shoulderR = 0.19;
|
|
132
136
|
t.torsoLean = 0.24;
|
package/src/perform.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A performance is the server's choreography: timed verbs `{t, do, ...}` fired
|
|
5
5
|
* against a clock, where every verb resolves to one of the widget's own enums —
|
|
6
|
-
* states, emotions, gaze targets,
|
|
6
|
+
* states, emotions, gaze targets, and semantic actions. The vocabulary is deliberately
|
|
7
7
|
* closed: the backend sequences what the rig already does well, it cannot
|
|
8
8
|
* invent motion. That constraint is what makes the wire format assemblable by
|
|
9
9
|
* a dialogue manager and reviewable by a human.
|
|
@@ -14,25 +14,23 @@
|
|
|
14
14
|
* tab is backgrounded. One deliberate difference: beat times fire *verbatim*,
|
|
15
15
|
* with no LEAD_MS. Visemes lead the sound because phoneme sync is
|
|
16
16
|
* frame-critical; a gesture arrives through its channels' own smoothing lag,
|
|
17
|
-
* and any deliberate lead
|
|
18
|
-
*
|
|
17
|
+
* and any deliberate lead is authored into the times by the composer, not
|
|
18
|
+
* imposed here.
|
|
19
19
|
*
|
|
20
20
|
* Seeking the audio backward does not re-fire earlier actions: verbs have side
|
|
21
21
|
* effects, and replaying a nod is worse than missing one.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
const VERBS = new Set(['state', 'emotion', 'gaze', '
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
const ID_VERBS = new Set(['interject', 'gesture']);
|
|
24
|
+
const VERBS = new Set(['state', 'emotion', 'gaze', 'action']);
|
|
25
|
+
// `action` is addressed by id; the remaining verbs name an enum value.
|
|
26
|
+
const ID_VERBS = new Set(['action']);
|
|
28
27
|
|
|
29
28
|
/**
|
|
30
29
|
* Shape hygiene for action arrays, in the spirit of normalizeCues: sort by
|
|
31
30
|
* time, drop what cannot possibly fire — no finite `t`, an unknown verb, a
|
|
32
31
|
* missing `name`/`id` — each with a console warning, never a throw.
|
|
33
32
|
*
|
|
34
|
-
* This checks *shape* only. Enum values
|
|
35
|
-
* an interjection?) are checked when the verb fires, by the dispatcher in
|
|
33
|
+
* This checks *shape* only. Enum values are checked when the verb fires, by the dispatcher in
|
|
36
34
|
* avatar.js — deliberately, and not just because importing the enums here
|
|
37
35
|
* would be a dependency cycle: a track half-composed against a newer widget
|
|
38
36
|
* should lose the verbs the widget doesn't know, not the whole performance.
|
package/src/rig.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The mixer→rig pose model, internal to the SVG renderer; see docs/internal-rig.md. */
|
|
2
|
+
export type RigChannel = string;
|
|
3
|
+
export type RigPose = Readonly<Record<RigChannel, number>>;
|
|
4
|
+
|
|
5
|
+
export interface HandFrame {
|
|
6
|
+
gesture: "greet" | "farewell" | "approve" | "wait";
|
|
7
|
+
progress: number;
|
|
8
|
+
side: "left" | "right";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AvatarFrame {
|
|
12
|
+
pose: RigPose;
|
|
13
|
+
hand?: HandFrame;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AvatarRig {
|
|
17
|
+
apply(frame: AvatarFrame): void;
|
|
18
|
+
destroy(): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type AvatarRigFactory = (mount: HTMLElement, options?: unknown) => AvatarRig;
|
|
22
|
+
|
|
23
|
+
export const HAND_GESTURE_NAMES: ReadonlyArray<HandFrame["gesture"]>;
|
|
24
|
+
export function avatarFrame(pose: RigPose, hand?: HandFrame): AvatarFrame;
|
package/src/rig.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer-agnostic frame types expressed as runtime helpers.
|
|
3
|
+
*
|
|
4
|
+
* `createSvgRig()` is a migration adapter for the existing SVG faces. It lets
|
|
5
|
+
* the mixer submit one AvatarFrame per tick today while new renderers can
|
|
6
|
+
* implement the same small `{ apply(frame), destroy() }` contract directly.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const HAND_GESTURE_NAMES = Object.freeze(['greet', 'farewell', 'approve', 'wait']);
|
|
10
|
+
|
|
11
|
+
/** @param {Record<string, number>} pose @param {object|undefined} hand */
|
|
12
|
+
export function avatarFrame(pose, hand) {
|
|
13
|
+
return hand ? { pose, hand } : { pose };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Adapt an existing SVG face plus its first-class hand renderer to AvatarRig.
|
|
18
|
+
* `face` and `hand` retain their own SVG-private geometry; callers only submit
|
|
19
|
+
* semantic frames.
|
|
20
|
+
*/
|
|
21
|
+
export function createSvgRig(face, hand = null) {
|
|
22
|
+
return {
|
|
23
|
+
apply(frame) {
|
|
24
|
+
face.apply(frame.pose);
|
|
25
|
+
if (hand && typeof hand.applyFrame === 'function') hand.applyFrame(frame.hand);
|
|
26
|
+
},
|
|
27
|
+
destroy() {
|
|
28
|
+
if (hand) hand.destroy();
|
|
29
|
+
face.destroy();
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timing policy for the mouth track.
|
|
3
|
+
*
|
|
4
|
+
* Keep perceptual cleanup, cue presentation, and mouth response constants
|
|
5
|
+
* named by their job. Network delivery is deliberately absent: it cannot be
|
|
6
|
+
* corrected by shifting every cue on the utterance clock.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Cues shorter than this do not survive visual normalization. */
|
|
10
|
+
export const MIN_VISIBLE_CUE_MS = 30;
|
|
11
|
+
|
|
12
|
+
/** Cue timestamps render verbatim; the clock's epoch is supplied by the caller. */
|
|
13
|
+
export const CUE_TRACK_LEAD_MS = 0;
|
|
14
|
+
|
|
15
|
+
/** Extra time after a final silence cue before a general speech track ends. */
|
|
16
|
+
export const SPEECH_TRACK_TAIL_MS = 120;
|
|
17
|
+
|
|
18
|
+
/** Shorter completion tail for self-contained spoken interjection clips. */
|
|
19
|
+
export const INTERJECTION_TRACK_TAIL_MS = 60;
|
|
20
|
+
|
|
21
|
+
/** First-order response constants: lips settle before the jaw does. */
|
|
22
|
+
export const MOUTH_RESPONSE_TAU_S = 0.042;
|
|
23
|
+
export const JAW_RESPONSE_TAU_S = 0.07;
|
package/src/visemes.js
CHANGED
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
* the same viseme shouted and murmured should not look identical.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
import {
|
|
27
|
+
CUE_TRACK_LEAD_MS,
|
|
28
|
+
MIN_VISIBLE_CUE_MS,
|
|
29
|
+
SPEECH_TRACK_TAIL_MS,
|
|
30
|
+
} from './speech-timing.js';
|
|
31
|
+
|
|
26
32
|
export const VISEME_SHAPES = {
|
|
27
33
|
X: { mouthOpen: 0.02, mouthWidth: 0.42, mouthRound: 0.10, mouthPress: 0.15, mouthTuck: 0, teethUpper: 0.00, tongue: 0.0 },
|
|
28
34
|
A: { mouthOpen: 0.00, mouthWidth: 0.40, mouthRound: 0.18, mouthPress: 0.55, mouthTuck: 0, teethUpper: 0.00, tongue: 0.0 },
|
|
@@ -63,7 +69,6 @@ export function shapeFor(letter, intensity = 1) {
|
|
|
63
69
|
// ---------------------------------------------------------------------------
|
|
64
70
|
// Cue track hygiene
|
|
65
71
|
// ---------------------------------------------------------------------------
|
|
66
|
-
const MIN_CUE_MS = 30; // shorter than this and the mouth just flutters
|
|
67
72
|
|
|
68
73
|
/**
|
|
69
74
|
* Sort, merge consecutive duplicates, and drop sub-perceptual cues. Servers
|
|
@@ -76,10 +81,20 @@ export function normalizeCues(cues) {
|
|
|
76
81
|
const v = VISEME_SHAPES[c.v] ? c.v : SILENT;
|
|
77
82
|
const prev = out[out.length - 1];
|
|
78
83
|
if (prev && prev.v === v) continue; // merge repeats
|
|
79
|
-
if (prev && c.t - prev.t <
|
|
84
|
+
if (prev && c.t - prev.t < MIN_VISIBLE_CUE_MS) {
|
|
80
85
|
// Too short to read. Keep whichever is more visually salient: a closure
|
|
81
86
|
// (A/G) carries more lip-reading information than a mid-open vowel.
|
|
82
|
-
if (v === 'A' || v === 'G')
|
|
87
|
+
if (v === 'A' || v === 'G') {
|
|
88
|
+
// The short cue can sit between two identical closures (G → F → G).
|
|
89
|
+
// Its replacement would otherwise create a duplicate visible shape;
|
|
90
|
+
// preserving the first G is both the stable wire form and the face the
|
|
91
|
+
// viewer actually saw.
|
|
92
|
+
if (out.length > 1 && out[out.length - 2].v === v) out.pop();
|
|
93
|
+
// A winning closure replaces the preceding shape for the entire
|
|
94
|
+
// sub-perceptual interval. Preserve that cue's timestamp while taking
|
|
95
|
+
// the closure's intensity, matching the server-side wire normalizer.
|
|
96
|
+
else out[out.length - 1] = { ...c, t: prev.t, v };
|
|
97
|
+
}
|
|
83
98
|
continue;
|
|
84
99
|
}
|
|
85
100
|
out.push({ t: c.t, v, i: c.i == null ? 1 : c.i });
|
|
@@ -88,17 +103,18 @@ export function normalizeCues(cues) {
|
|
|
88
103
|
}
|
|
89
104
|
|
|
90
105
|
/**
|
|
91
|
-
* Schedules a cue track against an
|
|
106
|
+
* Schedules a cue track against an utterance clock.
|
|
92
107
|
*
|
|
93
|
-
*
|
|
94
|
-
* `AudioContext.currentTime`)
|
|
95
|
-
* playback
|
|
108
|
+
* An audio-owned clock (`audioEl.currentTime * 1000` or
|
|
109
|
+
* `AudioContext.currentTime`) is the strongest source when the caller owns
|
|
110
|
+
* playback. The Pipecat adapter cannot see browser device playout; it supplies
|
|
111
|
+
* elapsed time from `botStartedSpeaking`, Pipecat's output-lifecycle epoch.
|
|
96
112
|
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
113
|
+
* There is deliberately no renderer-wide lead. Network/media skew cannot be
|
|
114
|
+
* corrected by moving every cue, and the predicted backend leg owns its own
|
|
115
|
+
* explicit prediction cushion.
|
|
100
116
|
*/
|
|
101
|
-
export const LEAD_MS =
|
|
117
|
+
export const LEAD_MS = CUE_TRACK_LEAD_MS;
|
|
102
118
|
|
|
103
119
|
export class VisemeTrack {
|
|
104
120
|
constructor() {
|
|
@@ -107,7 +123,7 @@ export class VisemeTrack {
|
|
|
107
123
|
this.playing = false;
|
|
108
124
|
this._idx = 0;
|
|
109
125
|
this.onEnd = null;
|
|
110
|
-
this.tailMs =
|
|
126
|
+
this.tailMs = SPEECH_TRACK_TAIL_MS;
|
|
111
127
|
}
|
|
112
128
|
|
|
113
129
|
/** @param {() => number} clock returns elapsed ms of the audio being played */
|
package/client/dist/Avatar.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Avatar — a call-tile-ready wrapper around the widget, and the package's
|
|
3
|
-
* whole public surface.
|
|
4
|
-
*
|
|
5
|
-
* <Avatar client={session.client} className="avatar-tile" />
|
|
6
|
-
*
|
|
7
|
-
* Two props of its own; everything else is forwarded to the mount `<div>`, so
|
|
8
|
-
* it sizes and styles like the tile it lives in. There is nothing to configure
|
|
9
|
-
* because there is nothing the server does not already say: the
|
|
10
|
-
* `AvatarProcessor` in the pipeline drives the state, the gaze and the mouth.
|
|
11
|
-
*
|
|
12
|
-
* The widget has no hot-swap-avatar API — `createFace` runs once per mount —
|
|
13
|
-
* so `avatar` is read once, at mount. To switch faces at runtime, remount with
|
|
14
|
-
* a `key` prop:
|
|
15
|
-
*
|
|
16
|
-
* <Avatar key={name} avatar={name} client={session.client} />
|
|
17
|
-
*/
|
|
18
|
-
import type { HTMLAttributes } from "react";
|
|
19
|
-
import type { PipecatClient } from "@pipecat-ai/client-js";
|
|
20
|
-
export interface AvatarProps extends HTMLAttributes<HTMLDivElement> {
|
|
21
|
-
/** The live `PipecatClient`, or `null` before connect. */
|
|
22
|
-
client?: PipecatClient | null;
|
|
23
|
-
/** Which face. Omit for the default. Read at mount only — see above. */
|
|
24
|
-
avatar?: string;
|
|
25
|
-
}
|
|
26
|
-
export declare function Avatar({ client, avatar, ...rest }: AvatarProps): import("react").JSX.Element;
|
|
27
|
-
//# sourceMappingURL=Avatar.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Avatar.d.ts","sourceRoot":"","sources":["../src/Avatar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,MAAM,WAAW,WAAY,SAAQ,cAAc,CAAC,cAAc,CAAC;IACjE,0DAA0D;IAC1D,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,WAAW,+BAG9D"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Avatar.js","sourceRoot":"","sources":["../src/Avatar.tsx"],"names":[],"mappings":";AAoBA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAS3C,MAAM,UAAU,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAe;IAC7D,MAAM,EAAE,YAAY,EAAE,GAAG,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvD,OAAO,cAAK,IAAI,EAAC,KAAK,gBAAY,QAAQ,KAAK,IAAI,EAAE,GAAG,EAAE,YAAY,GAAI,CAAC;AAC7E,CAAC"}
|