@zeroweight/react 0.2.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.
@@ -0,0 +1,607 @@
1
+ import { useRef as y, useState as f, useCallback as v, useEffect as b } from "react";
2
+ import { ZeroWeightRenderer as ge, ActionQueue as me, VoiceActivityDetector as he } from "@zeroweight/renderer";
3
+ import { jsxs as x, jsx as n, Fragment as P } from "react/jsx-runtime";
4
+ import { useVoiceAssistant as ve, useLocalParticipant as ye, useTrackTranscription as be, useDataChannel as xe, RoomAudioRenderer as Se, LiveKitRoom as we } from "@livekit/components-react";
5
+ import "@livekit/components-styles";
6
+ import { Loader2 as Z, MicOff as ke, Mic as Te, Power as Ae, Activity as Re } from "lucide-react";
7
+ import { Track as Ce } from "livekit-client";
8
+ const Me = 3e4, Ie = 120, De = () => {
9
+ const s = ["Happy", "Swift", "Bright", "Cool", "Smart"], o = ["User", "Guest", "Visitor", "Agent", "Caller"], e = Math.floor(Math.random() * 1e3);
10
+ return `${s[Math.floor(Math.random() * s.length)]}${o[Math.floor(Math.random() * o.length)]}${e}`;
11
+ };
12
+ function ze(s) {
13
+ const {
14
+ avatarId: o,
15
+ api: e,
16
+ livekitUrl: k = "",
17
+ sessionDuration: c = Ie,
18
+ inactivityTimeout: d = Me
19
+ } = s, g = k, C = y(null), a = y(null), i = y(null), l = y(null), [S, m] = f("idle"), [T, u] = f(null), [r, A] = f(
20
+ /* @__PURE__ */ new Set(["listening"])
21
+ ), [j, I] = f({
22
+ listening: { kind: "looped" },
23
+ speaking: { kind: "looped" }
24
+ }), [z, _] = f(!1), [X, B] = f(null), [F, E] = f(!1), [N, K] = f(!1), [ee, U] = f(c), R = y(null), O = y(() => {
25
+ }), [te, ne] = f(!1), [re, H] = f(0.8), M = y(null), V = S === "ready", Q = v(() => {
26
+ const t = C.current;
27
+ if (!t) return null;
28
+ let p = t.querySelector("canvas");
29
+ return p || (p = document.createElement("canvas"), p.style.width = "100%", p.style.height = "100%", p.style.display = "block", t.appendChild(p)), a.current = p, p;
30
+ }, []);
31
+ b(() => {
32
+ let t = !1;
33
+ return (async () => {
34
+ const L = Q();
35
+ if (!L) return;
36
+ const h = new ge();
37
+ i.current = h;
38
+ const $ = new me((w, D) => {
39
+ h.play(w, D);
40
+ });
41
+ l.current = $, h.on("stateChanged", (w) => {
42
+ t || m(w);
43
+ }), h.on("dimensions", (w, D) => {
44
+ t || u({ width: w, height: D });
45
+ }), h.on("actionLoaded", (w) => {
46
+ t || A((D) => {
47
+ const G = new Set(D);
48
+ return G.add(w), G;
49
+ });
50
+ }), h.on("allActionsLoaded", () => {
51
+ t || _(!1);
52
+ }), h.on("ready", () => {
53
+ t || (I(h.getActionMetadata()), $.setActionMetadata(h.getActionMetadata()));
54
+ });
55
+ try {
56
+ _(!0);
57
+ const w = await e.getBundle(o);
58
+ if (t || (await h.init(L, { payload: w.payload }), t)) return;
59
+ I(h.getActionMetadata()), $.setActionMetadata(h.getActionMetadata());
60
+ } catch (w) {
61
+ console.error("[useAvatarSession] Init failed:", w);
62
+ }
63
+ })(), () => {
64
+ t = !0, i.current?.destroy(), i.current = null, l.current = null;
65
+ };
66
+ }, [o, Q, e]);
67
+ const q = y(!1);
68
+ b(() => {
69
+ V && i.current && r.has("wave_hand") && !q.current && (i.current.play("wave_hand", "listening"), q.current = !0);
70
+ }, [V, r]);
71
+ const ie = v(async () => {
72
+ if (!(F || N)) {
73
+ E(!0);
74
+ try {
75
+ await navigator.mediaDevices.getUserMedia({ audio: !0 });
76
+ const t = De(), p = await e.getLiveKitToken(o, t);
77
+ B(p.token);
78
+ } catch (t) {
79
+ console.error("[useAvatarSession] Failed to connect:", t), E(!1);
80
+ }
81
+ }
82
+ }, [F, N, o, e]), W = v(() => {
83
+ R.current && (clearInterval(R.current), R.current = null), U(c), M.current && (clearTimeout(M.current), M.current = null), B(null), K(!1), E(!1), l.current?.forceListening();
84
+ }, [c]);
85
+ b(() => {
86
+ O.current = W;
87
+ }, [W]);
88
+ const oe = v(() => {
89
+ K(!0), E(!1);
90
+ }, []), ae = v(() => {
91
+ R.current && clearInterval(R.current), U(c), R.current = setInterval(() => {
92
+ U((t) => t <= 1 ? (clearInterval(R.current), R.current = null, setTimeout(() => O.current(), 0), 0) : t - 1);
93
+ }, 1e3);
94
+ }, [c]), se = v((t) => {
95
+ const p = Math.floor(t / 60).toString().padStart(2, "0"), L = (t % 60).toString().padStart(2, "0");
96
+ return `${p}:${L}`;
97
+ }, []);
98
+ b(() => () => {
99
+ R.current && clearInterval(R.current);
100
+ }, []);
101
+ const ce = v(() => {
102
+ M.current && clearTimeout(M.current), M.current = setTimeout(() => {
103
+ O.current();
104
+ }, d);
105
+ }, [d]), le = v(() => {
106
+ ne((t) => !t);
107
+ }, []), de = v((t) => {
108
+ H(t);
109
+ }, []), ue = v(() => {
110
+ H((t) => t > 0 ? 0 : 1);
111
+ }, []), pe = v(() => {
112
+ i.current?.interrupt();
113
+ }, []), fe = v((t) => {
114
+ l.current ? l.current.dispatch(t) : i.current?.play(t, "listening");
115
+ }, []);
116
+ return {
117
+ containerRef: C,
118
+ renderer: i.current,
119
+ actionQueue: l.current,
120
+ rendererState: S,
121
+ avatarDimensions: T,
122
+ loadedActions: r,
123
+ actionMetadata: j,
124
+ isLoadingActions: z,
125
+ isEngineReady: V,
126
+ token: X,
127
+ isConnecting: F,
128
+ isConnected: N,
129
+ livekitUrl: g,
130
+ timeRemaining: ee,
131
+ formatTime: se,
132
+ micMuted: te,
133
+ volume: re,
134
+ connect: ie,
135
+ disconnect: W,
136
+ toggleMic: le,
137
+ setVolume: de,
138
+ toggleVolume: ue,
139
+ interrupt: pe,
140
+ runAction: fe,
141
+ startSessionTimer: ae,
142
+ markConnected: oe,
143
+ onInactivityReset: ce
144
+ };
145
+ }
146
+ const Ee = ({
147
+ session: s,
148
+ style: o,
149
+ loadingContent: e
150
+ }) => /* @__PURE__ */ x(
151
+ "div",
152
+ {
153
+ style: {
154
+ position: "absolute",
155
+ inset: 0,
156
+ width: "100%",
157
+ height: "100%",
158
+ zIndex: 0,
159
+ pointerEvents: "auto",
160
+ display: "flex",
161
+ justifyContent: "center",
162
+ alignItems: "center",
163
+ overflow: "hidden",
164
+ ...o
165
+ },
166
+ children: [
167
+ /* @__PURE__ */ n("style", { children: `
168
+ @keyframes zwr-spin {
169
+ from { transform: rotate(0deg); }
170
+ to { transform: rotate(360deg); }
171
+ }
172
+ @keyframes zwr-pulse {
173
+ 0%, 100% { opacity: 1; }
174
+ 50% { opacity: 0.5; }
175
+ }
176
+ ` }),
177
+ /* @__PURE__ */ n(
178
+ "div",
179
+ {
180
+ ref: s.containerRef,
181
+ style: {
182
+ position: "relative",
183
+ width: "100%",
184
+ height: "100%",
185
+ transition: "all 0.5s ease-in-out"
186
+ },
187
+ children: !s.isEngineReady && /* @__PURE__ */ n(
188
+ "div",
189
+ {
190
+ style: {
191
+ position: "absolute",
192
+ inset: 0,
193
+ display: "flex",
194
+ alignItems: "center",
195
+ justifyContent: "center",
196
+ background: "rgba(0,0,0,0.4)",
197
+ backdropFilter: "blur(4px)",
198
+ zIndex: 10
199
+ },
200
+ children: e || /* @__PURE__ */ x(
201
+ "div",
202
+ {
203
+ style: {
204
+ display: "flex",
205
+ flexDirection: "column",
206
+ alignItems: "center",
207
+ gap: 16
208
+ },
209
+ children: [
210
+ /* @__PURE__ */ n(
211
+ Z,
212
+ {
213
+ style: {
214
+ width: 40,
215
+ height: 40,
216
+ color: "rgba(255,255,255,0.5)",
217
+ animation: "zwr-spin 1s linear infinite"
218
+ }
219
+ }
220
+ ),
221
+ /* @__PURE__ */ n(
222
+ "div",
223
+ {
224
+ style: {
225
+ fontSize: 14,
226
+ color: "rgba(255,255,255,0.5)",
227
+ fontWeight: 500,
228
+ letterSpacing: "0.1em",
229
+ textTransform: "uppercase",
230
+ animation: "zwr-pulse 2s ease-in-out infinite"
231
+ },
232
+ children: "Initializing Neural Engine..."
233
+ }
234
+ )
235
+ ]
236
+ }
237
+ )
238
+ }
239
+ )
240
+ }
241
+ )
242
+ ]
243
+ }
244
+ ), J = {
245
+ flexShrink: 0,
246
+ borderRadius: 9999,
247
+ padding: 16,
248
+ transition: "all 0.3s",
249
+ border: "none",
250
+ cursor: "pointer",
251
+ display: "inline-flex",
252
+ alignItems: "center",
253
+ justifyContent: "center"
254
+ }, Y = {
255
+ position: "relative",
256
+ display: "inline-flex",
257
+ alignItems: "center",
258
+ justifyContent: "center",
259
+ gap: 12,
260
+ borderRadius: 9999,
261
+ padding: "16px 24px",
262
+ fontSize: 16,
263
+ fontWeight: 600,
264
+ color: "#fff",
265
+ whiteSpace: "nowrap",
266
+ transition: "all 0.3s",
267
+ border: "none",
268
+ cursor: "pointer"
269
+ }, Le = ({
270
+ session: s,
271
+ style: o
272
+ }) => {
273
+ const {
274
+ micMuted: e,
275
+ toggleMic: k,
276
+ isConnected: c,
277
+ isConnecting: d,
278
+ connect: g,
279
+ disconnect: C
280
+ } = s, [a, i] = f(!1), [l, S] = f(!1), m = e ? {
281
+ ...J,
282
+ background: "rgba(239,68,68,0.1)",
283
+ color: "#f87171",
284
+ boxShadow: "inset 0 0 0 1px rgba(239,68,68,0.3)",
285
+ ...a ? { background: "rgba(239,68,68,0.2)" } : {}
286
+ } : {
287
+ ...J,
288
+ background: a ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.4)",
289
+ color: "#fff",
290
+ boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.1)"
291
+ }, T = c ? {
292
+ ...Y,
293
+ background: l ? "rgba(0,0,0,0.6)" : "rgba(0,0,0,0.4)",
294
+ boxShadow: "inset 0 0 0 1px rgba(239,68,68,0.5), 0 0 20px rgba(239,68,68,0.2)"
295
+ } : {
296
+ ...Y,
297
+ background: "linear-gradient(to right, #7c3aed, #db2777, #f97316)",
298
+ boxShadow: "0 0 30px rgba(236,72,153,0.3)",
299
+ opacity: l ? 0.9 : 1
300
+ };
301
+ return d && (T.opacity = 0.5, T.cursor = "not-allowed"), /* @__PURE__ */ x(
302
+ "div",
303
+ {
304
+ style: {
305
+ width: "100%",
306
+ marginTop: "auto",
307
+ display: "flex",
308
+ flexDirection: "column",
309
+ alignItems: "center",
310
+ gap: 16,
311
+ paddingBottom: 16,
312
+ ...o
313
+ },
314
+ children: [
315
+ /* @__PURE__ */ n("style", { children: `
316
+ @keyframes zwr-spin {
317
+ from { transform: rotate(0deg); }
318
+ to { transform: rotate(360deg); }
319
+ }
320
+ ` }),
321
+ /* @__PURE__ */ x(
322
+ "div",
323
+ {
324
+ style: {
325
+ display: "flex",
326
+ flexWrap: "wrap",
327
+ alignItems: "center",
328
+ justifyContent: "center",
329
+ gap: 16,
330
+ width: "100%",
331
+ pointerEvents: "auto"
332
+ },
333
+ children: [
334
+ /* @__PURE__ */ n(
335
+ "button",
336
+ {
337
+ type: "button",
338
+ onClick: k,
339
+ onMouseEnter: () => i(!0),
340
+ onMouseLeave: () => i(!1),
341
+ style: m,
342
+ title: e ? "Unmute mic" : "Mute mic",
343
+ children: e ? /* @__PURE__ */ n(ke, { size: 24 }) : /* @__PURE__ */ n(Te, { size: 24 })
344
+ }
345
+ ),
346
+ /* @__PURE__ */ n(
347
+ "button",
348
+ {
349
+ type: "button",
350
+ onClick: c ? C : g,
351
+ onMouseEnter: () => S(!0),
352
+ onMouseLeave: () => S(!1),
353
+ disabled: d,
354
+ style: T,
355
+ children: d ? /* @__PURE__ */ x(P, { children: [
356
+ /* @__PURE__ */ n(Z, { size: 20, style: { animation: "zwr-spin 1s linear infinite" } }),
357
+ /* @__PURE__ */ n("span", { children: "Connecting..." })
358
+ ] }) : c ? /* @__PURE__ */ x(P, { children: [
359
+ /* @__PURE__ */ n(Ae, { size: 20 }),
360
+ /* @__PURE__ */ n("span", { children: "End Session" })
361
+ ] }) : /* @__PURE__ */ x(P, { children: [
362
+ /* @__PURE__ */ n(Re, { size: 20 }),
363
+ /* @__PURE__ */ n("span", { children: "Start Session" })
364
+ ] })
365
+ }
366
+ )
367
+ ]
368
+ }
369
+ )
370
+ ]
371
+ }
372
+ );
373
+ }, Pe = ({
374
+ session: s
375
+ }) => {
376
+ const { isConnected: o, timeRemaining: e, formatTime: k } = s, c = o && e <= 30, d = o && e > 30 && e <= 60, g = {
377
+ display: "flex",
378
+ alignItems: "center",
379
+ gap: 8,
380
+ backdropFilter: "blur(12px)",
381
+ padding: "6px 12px",
382
+ borderRadius: 9999,
383
+ border: "1px solid",
384
+ pointerEvents: "auto",
385
+ transition: "all 0.3s",
386
+ ...c ? {
387
+ background: "rgba(239,68,68,0.3)",
388
+ borderColor: "rgba(239,68,68,0.4)",
389
+ boxShadow: "0 0 15px rgba(239,68,68,0.3)",
390
+ animation: "zwr-pulse 2s ease-in-out infinite"
391
+ } : d ? {
392
+ background: "rgba(249,115,22,0.2)",
393
+ borderColor: "rgba(249,115,22,0.3)",
394
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)"
395
+ } : {
396
+ background: "rgba(0,0,0,0.4)",
397
+ borderColor: "rgba(255,255,255,0.1)",
398
+ boxShadow: "0 4px 12px rgba(0,0,0,0.3)"
399
+ }
400
+ };
401
+ return /* @__PURE__ */ x(P, { children: [
402
+ /* @__PURE__ */ n("style", { children: `
403
+ @keyframes zwr-pulse {
404
+ 0%, 100% { opacity: 1; }
405
+ 50% { opacity: 0.5; }
406
+ }
407
+ ` }),
408
+ /* @__PURE__ */ x("div", { style: g, children: [
409
+ /* @__PURE__ */ n(
410
+ "div",
411
+ {
412
+ style: {
413
+ height: 8,
414
+ width: 8,
415
+ borderRadius: "50%",
416
+ ...o ? {
417
+ background: "#22c55e",
418
+ boxShadow: "0 0 10px rgba(34,197,94,0.5)"
419
+ } : {
420
+ background: "rgba(239,68,68,0.5)"
421
+ }
422
+ }
423
+ }
424
+ ),
425
+ /* @__PURE__ */ n(
426
+ "span",
427
+ {
428
+ style: {
429
+ fontSize: 10,
430
+ fontWeight: 700,
431
+ letterSpacing: "0.05em",
432
+ color: "rgba(255,255,255,0.7)",
433
+ textTransform: "uppercase"
434
+ },
435
+ children: o ? `Online ${k(e)}` : "Offline"
436
+ }
437
+ )
438
+ ] })
439
+ ] });
440
+ }, je = ({
441
+ session: s
442
+ }) => {
443
+ const { renderer: o, actionQueue: e, micMuted: k, volume: c, onInactivityReset: d, loadedActions: g } = s, C = y(!1), { state: a, audioTrack: i } = ve(), l = ye(), { segments: S } = be({
444
+ publication: l.microphoneTrack,
445
+ source: Ce.Source.Microphone,
446
+ participant: l.localParticipant
447
+ }), m = y(null), T = y(g);
448
+ b(() => {
449
+ T.current = g;
450
+ }, [g]), b(() => {
451
+ if (!e) return;
452
+ const r = new he({
453
+ threshold: 8e-3,
454
+ analyseIntervalMs: 30,
455
+ speechStartFrames: 1,
456
+ speechPauseFrames: 30,
457
+ turnEndFrames: 50
458
+ });
459
+ return m.current = r, r.on("speechStart", () => {
460
+ e.setTurnActive(!0), e.setSpeechState("speaking");
461
+ }), r.on("turnEnd", () => {
462
+ e.setTurnActive(!1);
463
+ }), () => {
464
+ r.stop(), m.current = null;
465
+ };
466
+ }, [e]), b(() => {
467
+ const r = m.current;
468
+ if (r)
469
+ if (i?.publication?.track) {
470
+ const A = i.publication.track.mediaStreamTrack;
471
+ A && r.start(A);
472
+ } else
473
+ r.stop();
474
+ }, [i?.publication?.track]);
475
+ const u = y(null);
476
+ return b(() => {
477
+ if (!e) return;
478
+ const r = a;
479
+ r === "speaking" ? (u.current && (clearTimeout(u.current), u.current = null), e.setTurnActive(!0), e.setSpeechState("speaking")) : r === "listening" || r === "idle" ? (u.current && (clearTimeout(u.current), u.current = null), m.current?.endTurn(), e.setTurnActive(!1)) : r === "thinking" && (u.current || (u.current = setTimeout(() => {
480
+ u.current = null, m.current?.endTurn(), e.setTurnActive(!1);
481
+ }, 500)));
482
+ }, [a, e]), b(() => () => {
483
+ u.current && clearTimeout(u.current);
484
+ }, []), xe((r) => {
485
+ try {
486
+ const j = new TextDecoder().decode(r.payload), I = JSON.parse(j);
487
+ if (I.type === "AVATAR_UPDATE") {
488
+ const z = I.action;
489
+ if (!T.current.has(z))
490
+ return;
491
+ e?.dispatch(z);
492
+ }
493
+ } catch (A) {
494
+ console.error("[LiveKitAvatarProvider] Failed to parse data message:", A);
495
+ }
496
+ }), b(() => {
497
+ d();
498
+ }, [S, a, d]), b(() => {
499
+ if (!e) return;
500
+ const r = !!i;
501
+ (!r || a === "disconnected") && (m.current?.endTurn(), e.forceListening()), C.current = r;
502
+ }, [i, a, e]), b(() => {
503
+ const r = l.localParticipant;
504
+ r && r.setMicrophoneEnabled(!k).catch((A) => {
505
+ console.error("[LiveKitAvatarProvider] Failed to set mic state:", A);
506
+ });
507
+ }, [k, l.localParticipant]), /* @__PURE__ */ n("div", { style: { position: "absolute", bottom: 80, left: 8, right: 8, display: "flex", flexDirection: "column", gap: 8 }, children: /* @__PURE__ */ n(Se, { volume: c }) });
508
+ }, _e = ({
509
+ avatarId: s,
510
+ api: o,
511
+ livekitUrl: e,
512
+ style: k,
513
+ className: c,
514
+ loadingContent: d,
515
+ customControls: g,
516
+ customStatusBadge: C
517
+ }) => {
518
+ const a = ze({ avatarId: s, api: o, livekitUrl: e }), {
519
+ token: i,
520
+ isConnected: l,
521
+ avatarDimensions: S,
522
+ disconnect: m,
523
+ startSessionTimer: T
524
+ } = a;
525
+ return /* @__PURE__ */ x(
526
+ "section",
527
+ {
528
+ className: c,
529
+ style: {
530
+ position: "relative",
531
+ display: "flex",
532
+ flexDirection: "column",
533
+ alignItems: "center",
534
+ justifyContent: "center",
535
+ overflow: "hidden",
536
+ borderRadius: 16,
537
+ border: "1px solid rgba(255,255,255,0.1)",
538
+ boxShadow: "0 25px 50px -12px rgba(0,0,0,0.5)",
539
+ height: "80vh",
540
+ width: "auto",
541
+ maxWidth: "100%",
542
+ aspectRatio: S ? `${S.width} / ${S.height}` : "3 / 4",
543
+ ...k
544
+ },
545
+ children: [
546
+ /* @__PURE__ */ n(Ee, { session: a, loadingContent: d }),
547
+ /* @__PURE__ */ x(
548
+ "div",
549
+ {
550
+ style: {
551
+ position: "absolute",
552
+ inset: 0,
553
+ zIndex: 20,
554
+ pointerEvents: "none",
555
+ display: "flex",
556
+ flexDirection: "column",
557
+ justifyContent: "space-between",
558
+ padding: 16
559
+ },
560
+ children: [
561
+ /* @__PURE__ */ x(
562
+ "div",
563
+ {
564
+ style: {
565
+ display: "flex",
566
+ width: "100%",
567
+ alignItems: "flex-start",
568
+ justifyContent: "space-between"
569
+ },
570
+ children: [
571
+ C ? C(a) : /* @__PURE__ */ n(Pe, { session: a }),
572
+ /* @__PURE__ */ n("div", {})
573
+ ]
574
+ }
575
+ ),
576
+ g ? g(a) : /* @__PURE__ */ n(Le, { session: a })
577
+ ]
578
+ }
579
+ ),
580
+ /* @__PURE__ */ n("div", { style: { display: "none" }, children: i && e && /* @__PURE__ */ n(
581
+ we,
582
+ {
583
+ serverUrl: e,
584
+ token: i,
585
+ connect: !0,
586
+ video: !1,
587
+ audio: !0,
588
+ onConnected: () => {
589
+ a.markConnected(), T();
590
+ },
591
+ onDisconnected: m,
592
+ children: /* @__PURE__ */ n(je, { session: a })
593
+ }
594
+ ) })
595
+ ]
596
+ }
597
+ );
598
+ };
599
+ export {
600
+ Ee as AvatarCanvas,
601
+ Le as AvatarControls,
602
+ Pe as AvatarStatusBadge,
603
+ je as LiveKitAvatarProvider,
604
+ _e as LiveKitAvatarSession,
605
+ ze as useAvatarSession
606
+ };
607
+ //# sourceMappingURL=zeroweight-renderer-react.es.js.map