@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.
- package/README.md +180 -0
- package/dist/AvatarCanvas.d.ts +19 -0
- package/dist/AvatarCanvas.d.ts.map +1 -0
- package/dist/AvatarControls.d.ts +17 -0
- package/dist/AvatarControls.d.ts.map +1 -0
- package/dist/AvatarStatusBadge.d.ts +12 -0
- package/dist/AvatarStatusBadge.d.ts.map +1 -0
- package/dist/LiveKitAvatarProvider.d.ts +14 -0
- package/dist/LiveKitAvatarProvider.d.ts.map +1 -0
- package/dist/LiveKitAvatarSession.d.ts +39 -0
- package/dist/LiveKitAvatarSession.d.ts.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/useAvatarSession.d.ts +64 -0
- package/dist/useAvatarSession.d.ts.map +1 -0
- package/dist/zeroweight-renderer-react.cjs.js +21 -0
- package/dist/zeroweight-renderer-react.cjs.js.map +1 -0
- package/dist/zeroweight-renderer-react.es.js +607 -0
- package/dist/zeroweight-renderer-react.es.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# @zeroweight/react
|
|
2
|
+
|
|
3
|
+
React hooks and components for the [ZeroWeight](https://www.zeroweight.com) avatar renderer. Drop-in LiveKit-powered avatar sessions for React and Next.js.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @zeroweight/react @zeroweight/renderer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer Dependencies
|
|
12
|
+
|
|
13
|
+
The following are required in your project:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install react react-dom @livekit/components-react @livekit/components-styles livekit-client lucide-react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> LiveKit packages are optional if you only use the renderer without voice chat.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Drop-in Component (Simplest)
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { LiveKitAvatarSession } from "@zeroweight/react";
|
|
27
|
+
|
|
28
|
+
function App() {
|
|
29
|
+
return (
|
|
30
|
+
<LiveKitAvatarSession
|
|
31
|
+
avatarId="your-avatar-id"
|
|
32
|
+
livekitUrl="wss://your-livekit-server.example.com"
|
|
33
|
+
api={{
|
|
34
|
+
getBundle: (avatarId) =>
|
|
35
|
+
fetch(`/api/avatars/bundle/${avatarId}`).then((r) => r.json()),
|
|
36
|
+
getLiveKitToken: (avatarId, userName) =>
|
|
37
|
+
fetch(
|
|
38
|
+
`/api/livekit/token?avatar_id=${avatarId}&name=${userName}`,
|
|
39
|
+
).then((r) => r.json()),
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This renders a full avatar session with canvas, controls, status badge, and LiveKit voice chat — all wired up automatically.
|
|
47
|
+
|
|
48
|
+
### Custom UI (Full Control)
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import {
|
|
52
|
+
useAvatarSession,
|
|
53
|
+
LiveKitAvatarProvider,
|
|
54
|
+
} from "@zeroweight/react";
|
|
55
|
+
import { LiveKitRoom } from "@livekit/components-react";
|
|
56
|
+
|
|
57
|
+
function CustomAvatar() {
|
|
58
|
+
const session = useAvatarSession({
|
|
59
|
+
avatarId: "your-avatar-id",
|
|
60
|
+
livekitUrl: "wss://...",
|
|
61
|
+
api: { getBundle, getLiveKitToken },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div>
|
|
66
|
+
{/* Canvas container — renderer injects <canvas> here */}
|
|
67
|
+
<div ref={session.containerRef} style={{ width: 640, height: 480 }} />
|
|
68
|
+
|
|
69
|
+
{/* Your own UI */}
|
|
70
|
+
<p>State: {session.rendererState}</p>
|
|
71
|
+
<button onClick={session.connect}>Connect</button>
|
|
72
|
+
<button onClick={session.disconnect}>Disconnect</button>
|
|
73
|
+
<button onClick={() => session.runAction("wave_hand")}>Wave</button>
|
|
74
|
+
|
|
75
|
+
{/* LiveKit room for voice (hidden) */}
|
|
76
|
+
{session.token && (
|
|
77
|
+
<LiveKitRoom
|
|
78
|
+
serverUrl={session.livekitUrl}
|
|
79
|
+
token={session.token}
|
|
80
|
+
connect
|
|
81
|
+
audio
|
|
82
|
+
onConnected={session.markConnected}
|
|
83
|
+
onDisconnected={session.disconnect}
|
|
84
|
+
>
|
|
85
|
+
<LiveKitAvatarProvider session={session} />
|
|
86
|
+
</LiveKitRoom>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API
|
|
94
|
+
|
|
95
|
+
### `ZeroWeightApi` Interface
|
|
96
|
+
|
|
97
|
+
You provide your own backend integration via this interface:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
interface ZeroWeightApi {
|
|
101
|
+
getBundle: (avatarId: string) => Promise<{ payload: any }>;
|
|
102
|
+
getLiveKitToken: (
|
|
103
|
+
avatarId: string,
|
|
104
|
+
userName: string,
|
|
105
|
+
) => Promise<{ token: string }>;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `useAvatarSession(config)`
|
|
110
|
+
|
|
111
|
+
The core React hook. Manages the renderer lifecycle and returns reactive state + imperative methods.
|
|
112
|
+
|
|
113
|
+
**Config:**
|
|
114
|
+
|
|
115
|
+
| Prop | Type | Required | Description |
|
|
116
|
+
| ------------------- | --------------- | -------- | ---------------------------------------------- |
|
|
117
|
+
| `avatarId` | `string` | ✅ | Avatar ID to load |
|
|
118
|
+
| `api` | `ZeroWeightApi` | ✅ | Your API implementation |
|
|
119
|
+
| `livekitUrl` | `string` | – | LiveKit server URL |
|
|
120
|
+
| `sessionDuration` | `number` | – | Session limit in seconds (default: 120) |
|
|
121
|
+
| `inactivityTimeout` | `number` | – | Auto-disconnect timeout in ms (default: 30000) |
|
|
122
|
+
|
|
123
|
+
**Returns:**
|
|
124
|
+
|
|
125
|
+
| Property | Type | Description |
|
|
126
|
+
| --------------- | ----------- | -------------------------------------------------------- |
|
|
127
|
+
| `containerRef` | `RefObject` | Attach to a `<div>` — renderer creates `<canvas>` inside |
|
|
128
|
+
| `rendererState` | `string` | `"idle"` \| `"loading"` \| `"ready"` \| `"error"` |
|
|
129
|
+
| `isEngineReady` | `boolean` | `true` when renderer is ready |
|
|
130
|
+
| `isConnected` | `boolean` | LiveKit connection status |
|
|
131
|
+
| `connect()` | `function` | Start voice session |
|
|
132
|
+
| `disconnect()` | `function` | End voice session |
|
|
133
|
+
| `runAction(id)` | `function` | Trigger an avatar action |
|
|
134
|
+
| `toggleMic()` | `function` | Toggle microphone mute |
|
|
135
|
+
| `setVolume(v)` | `function` | Set audio volume (0–1) |
|
|
136
|
+
|
|
137
|
+
### Components
|
|
138
|
+
|
|
139
|
+
| Component | Description |
|
|
140
|
+
| ----------------------- | -------------------------------------------------- |
|
|
141
|
+
| `LiveKitAvatarSession` | Full drop-in: canvas + controls + LiveKit |
|
|
142
|
+
| `AvatarCanvas` | Canvas container with loading overlay |
|
|
143
|
+
| `AvatarControls` | Default mic/connect buttons |
|
|
144
|
+
| `AvatarStatusBadge` | Online/offline status indicator |
|
|
145
|
+
| `LiveKitAvatarProvider` | LiveKit ↔ renderer bridge (inside `<LiveKitRoom>`) |
|
|
146
|
+
|
|
147
|
+
All components accept a `session` prop from `useAvatarSession()`.
|
|
148
|
+
|
|
149
|
+
## Integration Example
|
|
150
|
+
|
|
151
|
+
Adapting an existing API service:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { api } from "./my-api";
|
|
155
|
+
import type { ZeroWeightApi } from "@zeroweight/react";
|
|
156
|
+
|
|
157
|
+
const zeroWeightApi: ZeroWeightApi = {
|
|
158
|
+
getBundle: (avatarId) => api.getBundle(avatarId),
|
|
159
|
+
getLiveKitToken: (_avatarId, userName) => api.getToken(userName),
|
|
160
|
+
};
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Build from Source
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
git clone <repo-url>
|
|
167
|
+
cd zeroweight-renderer-react
|
|
168
|
+
npm install
|
|
169
|
+
npm run build
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Output in `dist/`:
|
|
173
|
+
|
|
174
|
+
- `zeroweight-renderer-react.es.js` — ES module (14 KB)
|
|
175
|
+
- `zeroweight-renderer-react.cjs.js` — CommonJS (11 KB)
|
|
176
|
+
- `*.d.ts` — TypeScript declarations
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AvatarCanvas — Canvas container for the avatar renderer.
|
|
3
|
+
*
|
|
4
|
+
* Provides the div container where the renderer creates and manages a <canvas>.
|
|
5
|
+
* Shows loading overlay when the engine is initializing.
|
|
6
|
+
* Uses inline styles only — no CSS framework dependency.
|
|
7
|
+
*/
|
|
8
|
+
import React from "react";
|
|
9
|
+
import type { AvatarSessionReturn } from "./useAvatarSession";
|
|
10
|
+
interface AvatarCanvasProps {
|
|
11
|
+
session: AvatarSessionReturn;
|
|
12
|
+
/** Optional style overrides for the outer container. */
|
|
13
|
+
style?: React.CSSProperties;
|
|
14
|
+
/** Custom loading component. */
|
|
15
|
+
loadingContent?: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export declare const AvatarCanvas: React.FC<AvatarCanvasProps>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=AvatarCanvas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AvatarCanvas.d.ts","sourceRoot":"","sources":["../src/AvatarCanvas.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,UAAU,iBAAiB;IACzB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,wDAAwD;IACxD,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,gCAAgC;IAChC,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAClC;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA0FpD,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AvatarControls — Default control bar for the avatar session.
|
|
3
|
+
*
|
|
4
|
+
* Mic toggle, connect/disconnect button.
|
|
5
|
+
* Can be replaced entirely by a custom UI.
|
|
6
|
+
* Uses inline styles only — no CSS framework dependency.
|
|
7
|
+
*/
|
|
8
|
+
import React from "react";
|
|
9
|
+
import type { AvatarSessionReturn } from "./useAvatarSession";
|
|
10
|
+
interface AvatarControlsProps {
|
|
11
|
+
session: AvatarSessionReturn;
|
|
12
|
+
/** Optional style overrides for the wrapper. */
|
|
13
|
+
style?: React.CSSProperties;
|
|
14
|
+
}
|
|
15
|
+
export declare const AvatarControls: React.FC<AvatarControlsProps>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=AvatarControls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AvatarControls.d.ts","sourceRoot":"","sources":["../src/AvatarControls.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAmB,MAAM,OAAO,CAAC;AAQxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,UAAU,mBAAmB;IAC3B,OAAO,EAAE,mBAAmB,CAAC;IAC7B,gDAAgD;IAChD,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AA+BD,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAwHxD,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AvatarStatusBadge — Connection status indicator with session timer.
|
|
3
|
+
* Uses inline styles only — no CSS framework dependency.
|
|
4
|
+
*/
|
|
5
|
+
import React from "react";
|
|
6
|
+
import type { AvatarSessionReturn } from "./useAvatarSession";
|
|
7
|
+
interface AvatarStatusBadgeProps {
|
|
8
|
+
session: AvatarSessionReturn;
|
|
9
|
+
}
|
|
10
|
+
export declare const AvatarStatusBadge: React.FC<AvatarStatusBadgeProps>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=AvatarStatusBadge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AvatarStatusBadge.d.ts","sourceRoot":"","sources":["../src/AvatarStatusBadge.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,UAAU,sBAAsB;IAC9B,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CA4E9D,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiveKitAvatarProvider — Bridge between LiveKit hooks and the ActionQueue/VAD.
|
|
3
|
+
* Uses inline styles only — no CSS framework dependency.
|
|
4
|
+
*
|
|
5
|
+
* This component MUST be rendered inside a <LiveKitRoom>.
|
|
6
|
+
*/
|
|
7
|
+
import React from "react";
|
|
8
|
+
import type { AvatarSessionReturn } from "./useAvatarSession";
|
|
9
|
+
interface LiveKitAvatarProviderProps {
|
|
10
|
+
session: AvatarSessionReturn;
|
|
11
|
+
}
|
|
12
|
+
export declare const LiveKitAvatarProvider: React.FC<LiveKitAvatarProviderProps>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=LiveKitAvatarProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveKitAvatarProvider.d.ts","sourceRoot":"","sources":["../src/LiveKitAvatarProvider.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAA4B,MAAM,OAAO,CAAC;AAUjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,UAAU,0BAA0B;IAClC,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAwKtE,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LiveKitAvatarSession — Full drop-in avatar component.
|
|
3
|
+
* Uses inline styles only — no CSS framework dependency.
|
|
4
|
+
*
|
|
5
|
+
* Simplest usage:
|
|
6
|
+
* import { LiveKitAvatarSession } from "@zeroweight/react";
|
|
7
|
+
* <LiveKitAvatarSession
|
|
8
|
+
* avatarId="abc123"
|
|
9
|
+
* livekitUrl="wss://..."
|
|
10
|
+
* api={{
|
|
11
|
+
* getBundle: (id) => fetch(`/api/bundle/${id}`).then(r => r.json()),
|
|
12
|
+
* getLiveKitToken: (id, name) => fetch(`/api/token?name=${name}`).then(r => r.json()),
|
|
13
|
+
* }}
|
|
14
|
+
* />
|
|
15
|
+
*/
|
|
16
|
+
import React from "react";
|
|
17
|
+
import "@livekit/components-styles";
|
|
18
|
+
import type { AvatarSessionReturn } from "./useAvatarSession";
|
|
19
|
+
import type { ZeroWeightApi } from "./types";
|
|
20
|
+
interface LiveKitAvatarSessionProps {
|
|
21
|
+
avatarId: string;
|
|
22
|
+
/** Injectable API — provide your own fetch functions. */
|
|
23
|
+
api: ZeroWeightApi;
|
|
24
|
+
/** LiveKit server URL (e.g. "wss://your-livekit.example.com"). */
|
|
25
|
+
livekitUrl: string;
|
|
26
|
+
/** Optional style overrides for the outer section. */
|
|
27
|
+
style?: React.CSSProperties;
|
|
28
|
+
/** Optional class name overrides for the outer section. */
|
|
29
|
+
className?: string;
|
|
30
|
+
/** Custom loading UI for the canvas. */
|
|
31
|
+
loadingContent?: React.ReactNode;
|
|
32
|
+
/** Custom controls component. If provided, replaces the default controls. */
|
|
33
|
+
customControls?: (session: AvatarSessionReturn) => React.ReactNode;
|
|
34
|
+
/** Custom status badge. If provided, replaces the default badge. */
|
|
35
|
+
customStatusBadge?: (session: AvatarSessionReturn) => React.ReactNode;
|
|
36
|
+
}
|
|
37
|
+
export declare const LiveKitAvatarSession: React.FC<LiveKitAvatarSessionProps>;
|
|
38
|
+
export {};
|
|
39
|
+
//# sourceMappingURL=LiveKitAvatarSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveKitAvatarSession.d.ts","sourceRoot":"","sources":["../src/LiveKitAvatarSession.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAK9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,UAAU,yBAAyB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,GAAG,EAAE,aAAa,CAAC;IACnB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,cAAc,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACjC,6EAA6E;IAC7E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,KAAK,CAAC,SAAS,CAAC;IACnE,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,KAAK,CAAC,SAAS,CAAC;CACvE;AAED,eAAO,MAAM,oBAAoB,EAAE,KAAK,CAAC,EAAE,CAAC,yBAAyB,CAwGpE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zeroweight-renderer-react — Public API
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* // Drop-in (simplest):
|
|
6
|
+
* import { LiveKitAvatarSession } from "@zeroweight/react";
|
|
7
|
+
* <LiveKitAvatarSession avatarId="abc123" api={...} livekitUrl="wss://..." />
|
|
8
|
+
*
|
|
9
|
+
* // Custom UI:
|
|
10
|
+
* import { useAvatarSession, AvatarCanvas } from "@zeroweight/react";
|
|
11
|
+
* const session = useAvatarSession({ avatarId: 'abc', api: {...} });
|
|
12
|
+
* <AvatarCanvas session={session} />
|
|
13
|
+
* // ... your own controls
|
|
14
|
+
*/
|
|
15
|
+
export { useAvatarSession } from "./useAvatarSession";
|
|
16
|
+
export type { AvatarSessionConfig, AvatarSessionReturn } from "./useAvatarSession";
|
|
17
|
+
export { LiveKitAvatarSession } from "./LiveKitAvatarSession";
|
|
18
|
+
export { AvatarCanvas } from "./AvatarCanvas";
|
|
19
|
+
export { AvatarControls } from "./AvatarControls";
|
|
20
|
+
export { AvatarStatusBadge } from "./AvatarStatusBadge";
|
|
21
|
+
export { LiveKitAvatarProvider } from "./LiveKitAvatarProvider";
|
|
22
|
+
export type { ZeroWeightApi } from "./types";
|
|
23
|
+
export type { ActionKind, ActionMetadata, RendererState, RendererConfig, RendererEvents, SpeechState, ActionQueueEvents, VADConfig, VADEvents, } from "@zeroweight/renderer";
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGnF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,YAAY,EACV,UAAU,EACV,cAAc,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,SAAS,EACT,SAAS,GACV,MAAM,sBAAsB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zeroweight-renderer-react — Types
|
|
3
|
+
*
|
|
4
|
+
* Injectable API types and shared interfaces for the React module.
|
|
5
|
+
*/
|
|
6
|
+
export interface ZeroWeightApi {
|
|
7
|
+
/** Fetch the avatar bundle payload. */
|
|
8
|
+
getBundle: (avatarId: string) => Promise<{
|
|
9
|
+
payload: any;
|
|
10
|
+
}>;
|
|
11
|
+
/** Fetch a LiveKit token for voice chat. */
|
|
12
|
+
getLiveKitToken: (avatarId: string, userName: string) => Promise<{
|
|
13
|
+
token: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IAC3D,4CAA4C;IAC5C,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrF"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAvatarSession — Main React hook for avatar rendering.
|
|
3
|
+
*
|
|
4
|
+
* Wraps ZeroWeightRenderer + ActionQueue in React lifecycle.
|
|
5
|
+
* Returns reactive state and imperative methods.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const session = useAvatarSession({
|
|
9
|
+
* avatarId: 'abc123',
|
|
10
|
+
* api: {
|
|
11
|
+
* getBundle: (id) => fetch(`/api/bundle/${id}`).then(r => r.json()),
|
|
12
|
+
* getLiveKitToken: (id, name) => fetch(`/api/token?...`).then(r => r.json()),
|
|
13
|
+
* },
|
|
14
|
+
* });
|
|
15
|
+
*/
|
|
16
|
+
import { ZeroWeightRenderer, ActionQueue } from "@zeroweight/renderer";
|
|
17
|
+
import type { ActionMetadata, RendererState } from "@zeroweight/renderer";
|
|
18
|
+
import type { ZeroWeightApi } from "./types";
|
|
19
|
+
export interface AvatarSessionConfig {
|
|
20
|
+
avatarId: string;
|
|
21
|
+
/** Injectable API — provide your own fetch functions. */
|
|
22
|
+
api: ZeroWeightApi;
|
|
23
|
+
/** LiveKit server URL. */
|
|
24
|
+
livekitUrl?: string;
|
|
25
|
+
/** Session duration in seconds. Default: 120 */
|
|
26
|
+
sessionDuration?: number;
|
|
27
|
+
/** Inactivity timeout in ms. Default: 30000 */
|
|
28
|
+
inactivityTimeout?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface AvatarSessionReturn {
|
|
31
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
32
|
+
renderer: ZeroWeightRenderer | null;
|
|
33
|
+
actionQueue: ActionQueue | null;
|
|
34
|
+
rendererState: RendererState;
|
|
35
|
+
avatarDimensions: {
|
|
36
|
+
width: number;
|
|
37
|
+
height: number;
|
|
38
|
+
} | null;
|
|
39
|
+
loadedActions: Set<string>;
|
|
40
|
+
actionMetadata: Record<string, ActionMetadata>;
|
|
41
|
+
isLoadingActions: boolean;
|
|
42
|
+
isEngineReady: boolean;
|
|
43
|
+
token: string | null;
|
|
44
|
+
isConnecting: boolean;
|
|
45
|
+
isConnected: boolean;
|
|
46
|
+
livekitUrl: string;
|
|
47
|
+
timeRemaining: number;
|
|
48
|
+
formatTime: (seconds: number) => string;
|
|
49
|
+
micMuted: boolean;
|
|
50
|
+
volume: number;
|
|
51
|
+
connect: () => Promise<void>;
|
|
52
|
+
disconnect: () => void;
|
|
53
|
+
toggleMic: () => void;
|
|
54
|
+
setVolume: (v: number) => void;
|
|
55
|
+
toggleVolume: () => void;
|
|
56
|
+
interrupt: () => void;
|
|
57
|
+
runAction: (actionId: string) => void;
|
|
58
|
+
startSessionTimer: () => void;
|
|
59
|
+
/** Called by LiveKitRoom onConnected */
|
|
60
|
+
markConnected: () => void;
|
|
61
|
+
onInactivityReset: () => void;
|
|
62
|
+
}
|
|
63
|
+
export declare function useAvatarSession(config: AvatarSessionConfig): AvatarSessionReturn;
|
|
64
|
+
//# sourceMappingURL=useAvatarSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAvatarSession.d.ts","sourceRoot":"","sources":["../src/useAvatarSession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAc7C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,GAAG,EAAE,aAAa,CAAC;IACnB,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,+CAA+C;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAElC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAGrD,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACpC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAGhC,aAAa,EAAE,aAAa,CAAC;IAC7B,gBAAgB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC/C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IAGvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IAGnB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IAGxC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IAGf,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,wCAAwC;IACxC,aAAa,EAAE,MAAM,IAAI,CAAC;IAG1B,iBAAiB,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,mBAAmB,CAwTjF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),O=require("@zeroweight/renderer"),t=require("react/jsx-runtime"),A=require("@livekit/components-react");require("@livekit/components-styles");const R=require("lucide-react"),me=require("livekit-client"),ve=3e4,xe=120,be=()=>{const c=["Happy","Swift","Bright","Cool","Smart"],i=["User","Guest","Visitor","Agent","Caller"],n=Math.floor(Math.random()*1e3);return`${c[Math.floor(Math.random()*c.length)]}${i[Math.floor(Math.random()*i.length)]}${n}`};function J(c){const{avatarId:i,api:n,livekitUrl:b="",sessionDuration:l=xe,inactivityTimeout:d=ve}=c,g=b,j=e.useRef(null),o=e.useRef(null),a=e.useRef(null),u=e.useRef(null),[v,h]=e.useState("idle"),[y,f]=e.useState(null),[s,S]=e.useState(new Set(["listening"])),[D,C]=e.useState({listening:{kind:"looped"},speaking:{kind:"looped"}}),[M,U]=e.useState(!1),[te,K]=e.useState(null),[z,I]=e.useState(!1),[L,V]=e.useState(!1),[ne,P]=e.useState(l),k=e.useRef(null),F=e.useRef(()=>{}),[re,se]=e.useState(!1),[ae,W]=e.useState(.8),w=e.useRef(null),$=v==="ready",B=e.useCallback(()=>{const r=j.current;if(!r)return null;let p=r.querySelector("canvas");return p||(p=document.createElement("canvas"),p.style.width="100%",p.style.height="100%",p.style.display="block",r.appendChild(p)),o.current=p,p},[]);e.useEffect(()=>{let r=!1;return(async()=>{const E=B();if(!E)return;const m=new O.ZeroWeightRenderer;a.current=m;const N=new O.ActionQueue((x,T)=>{m.play(x,T)});u.current=N,m.on("stateChanged",x=>{r||h(x)}),m.on("dimensions",(x,T)=>{r||f({width:x,height:T})}),m.on("actionLoaded",x=>{r||S(T=>{const H=new Set(T);return H.add(x),H})}),m.on("allActionsLoaded",()=>{r||U(!1)}),m.on("ready",()=>{r||(C(m.getActionMetadata()),N.setActionMetadata(m.getActionMetadata()))});try{U(!0);const x=await n.getBundle(i);if(r||(await m.init(E,{payload:x.payload}),r))return;C(m.getActionMetadata()),N.setActionMetadata(m.getActionMetadata())}catch(x){console.error("[useAvatarSession] Init failed:",x)}})(),()=>{r=!0,a.current?.destroy(),a.current=null,u.current=null}},[i,B,n]);const _=e.useRef(!1);e.useEffect(()=>{$&&a.current&&s.has("wave_hand")&&!_.current&&(a.current.play("wave_hand","listening"),_.current=!0)},[$,s]);const ie=e.useCallback(async()=>{if(!(z||L)){I(!0);try{await navigator.mediaDevices.getUserMedia({audio:!0});const r=be(),p=await n.getLiveKitToken(i,r);K(p.token)}catch(r){console.error("[useAvatarSession] Failed to connect:",r),I(!1)}}},[z,L,i,n]),q=e.useCallback(()=>{k.current&&(clearInterval(k.current),k.current=null),P(l),w.current&&(clearTimeout(w.current),w.current=null),K(null),V(!1),I(!1),u.current?.forceListening()},[l]);e.useEffect(()=>{F.current=q},[q]);const oe=e.useCallback(()=>{V(!0),I(!1)},[]),ce=e.useCallback(()=>{k.current&&clearInterval(k.current),P(l),k.current=setInterval(()=>{P(r=>r<=1?(clearInterval(k.current),k.current=null,setTimeout(()=>F.current(),0),0):r-1)},1e3)},[l]),le=e.useCallback(r=>{const p=Math.floor(r/60).toString().padStart(2,"0"),E=(r%60).toString().padStart(2,"0");return`${p}:${E}`},[]);e.useEffect(()=>()=>{k.current&&clearInterval(k.current)},[]);const ue=e.useCallback(()=>{w.current&&clearTimeout(w.current),w.current=setTimeout(()=>{F.current()},d)},[d]),de=e.useCallback(()=>{se(r=>!r)},[]),fe=e.useCallback(r=>{W(r)},[]),pe=e.useCallback(()=>{W(r=>r>0?0:1)},[]),ge=e.useCallback(()=>{a.current?.interrupt()},[]),he=e.useCallback(r=>{u.current?u.current.dispatch(r):a.current?.play(r,"listening")},[]);return{containerRef:j,renderer:a.current,actionQueue:u.current,rendererState:v,avatarDimensions:y,loadedActions:s,actionMetadata:D,isLoadingActions:M,isEngineReady:$,token:te,isConnecting:z,isConnected:L,livekitUrl:g,timeRemaining:ne,formatTime:le,micMuted:re,volume:ae,connect:ie,disconnect:q,toggleMic:de,setVolume:fe,toggleVolume:pe,interrupt:ge,runAction:he,startSessionTimer:ce,markConnected:oe,onInactivityReset:ue}}const Y=({session:c,style:i,loadingContent:n})=>t.jsxs("div",{style:{position:"absolute",inset:0,width:"100%",height:"100%",zIndex:0,pointerEvents:"auto",display:"flex",justifyContent:"center",alignItems:"center",overflow:"hidden",...i},children:[t.jsx("style",{children:`
|
|
2
|
+
@keyframes zwr-spin {
|
|
3
|
+
from { transform: rotate(0deg); }
|
|
4
|
+
to { transform: rotate(360deg); }
|
|
5
|
+
}
|
|
6
|
+
@keyframes zwr-pulse {
|
|
7
|
+
0%, 100% { opacity: 1; }
|
|
8
|
+
50% { opacity: 0.5; }
|
|
9
|
+
}
|
|
10
|
+
`}),t.jsx("div",{ref:c.containerRef,style:{position:"relative",width:"100%",height:"100%",transition:"all 0.5s ease-in-out"},children:!c.isEngineReady&&t.jsx("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(0,0,0,0.4)",backdropFilter:"blur(4px)",zIndex:10},children:n||t.jsxs("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",gap:16},children:[t.jsx(R.Loader2,{style:{width:40,height:40,color:"rgba(255,255,255,0.5)",animation:"zwr-spin 1s linear infinite"}}),t.jsx("div",{style:{fontSize:14,color:"rgba(255,255,255,0.5)",fontWeight:500,letterSpacing:"0.1em",textTransform:"uppercase",animation:"zwr-pulse 2s ease-in-out infinite"},children:"Initializing Neural Engine..."})]})})})]}),Q={flexShrink:0,borderRadius:9999,padding:16,transition:"all 0.3s",border:"none",cursor:"pointer",display:"inline-flex",alignItems:"center",justifyContent:"center"},G={position:"relative",display:"inline-flex",alignItems:"center",justifyContent:"center",gap:12,borderRadius:9999,padding:"16px 24px",fontSize:16,fontWeight:600,color:"#fff",whiteSpace:"nowrap",transition:"all 0.3s",border:"none",cursor:"pointer"},Z=({session:c,style:i})=>{const{micMuted:n,toggleMic:b,isConnected:l,isConnecting:d,connect:g,disconnect:j}=c,[o,a]=e.useState(!1),[u,v]=e.useState(!1),h=n?{...Q,background:"rgba(239,68,68,0.1)",color:"#f87171",boxShadow:"inset 0 0 0 1px rgba(239,68,68,0.3)",...o?{background:"rgba(239,68,68,0.2)"}:{}}:{...Q,background:o?"rgba(255,255,255,0.1)":"rgba(0,0,0,0.4)",color:"#fff",boxShadow:"inset 0 0 0 1px rgba(255,255,255,0.1)"},y=l?{...G,background:u?"rgba(0,0,0,0.6)":"rgba(0,0,0,0.4)",boxShadow:"inset 0 0 0 1px rgba(239,68,68,0.5), 0 0 20px rgba(239,68,68,0.2)"}:{...G,background:"linear-gradient(to right, #7c3aed, #db2777, #f97316)",boxShadow:"0 0 30px rgba(236,72,153,0.3)",opacity:u?.9:1};return d&&(y.opacity=.5,y.cursor="not-allowed"),t.jsxs("div",{style:{width:"100%",marginTop:"auto",display:"flex",flexDirection:"column",alignItems:"center",gap:16,paddingBottom:16,...i},children:[t.jsx("style",{children:`
|
|
11
|
+
@keyframes zwr-spin {
|
|
12
|
+
from { transform: rotate(0deg); }
|
|
13
|
+
to { transform: rotate(360deg); }
|
|
14
|
+
}
|
|
15
|
+
`}),t.jsxs("div",{style:{display:"flex",flexWrap:"wrap",alignItems:"center",justifyContent:"center",gap:16,width:"100%",pointerEvents:"auto"},children:[t.jsx("button",{type:"button",onClick:b,onMouseEnter:()=>a(!0),onMouseLeave:()=>a(!1),style:h,title:n?"Unmute mic":"Mute mic",children:n?t.jsx(R.MicOff,{size:24}):t.jsx(R.Mic,{size:24})}),t.jsx("button",{type:"button",onClick:l?j:g,onMouseEnter:()=>v(!0),onMouseLeave:()=>v(!1),disabled:d,style:y,children:d?t.jsxs(t.Fragment,{children:[t.jsx(R.Loader2,{size:20,style:{animation:"zwr-spin 1s linear infinite"}}),t.jsx("span",{children:"Connecting..."})]}):l?t.jsxs(t.Fragment,{children:[t.jsx(R.Power,{size:20}),t.jsx("span",{children:"End Session"})]}):t.jsxs(t.Fragment,{children:[t.jsx(R.Activity,{size:20}),t.jsx("span",{children:"Start Session"})]})})]})]})},X=({session:c})=>{const{isConnected:i,timeRemaining:n,formatTime:b}=c,l=i&&n<=30,d=i&&n>30&&n<=60,g={display:"flex",alignItems:"center",gap:8,backdropFilter:"blur(12px)",padding:"6px 12px",borderRadius:9999,border:"1px solid",pointerEvents:"auto",transition:"all 0.3s",...l?{background:"rgba(239,68,68,0.3)",borderColor:"rgba(239,68,68,0.4)",boxShadow:"0 0 15px rgba(239,68,68,0.3)",animation:"zwr-pulse 2s ease-in-out infinite"}:d?{background:"rgba(249,115,22,0.2)",borderColor:"rgba(249,115,22,0.3)",boxShadow:"0 4px 12px rgba(0,0,0,0.3)"}:{background:"rgba(0,0,0,0.4)",borderColor:"rgba(255,255,255,0.1)",boxShadow:"0 4px 12px rgba(0,0,0,0.3)"}};return t.jsxs(t.Fragment,{children:[t.jsx("style",{children:`
|
|
16
|
+
@keyframes zwr-pulse {
|
|
17
|
+
0%, 100% { opacity: 1; }
|
|
18
|
+
50% { opacity: 0.5; }
|
|
19
|
+
}
|
|
20
|
+
`}),t.jsxs("div",{style:g,children:[t.jsx("div",{style:{height:8,width:8,borderRadius:"50%",...i?{background:"#22c55e",boxShadow:"0 0 10px rgba(34,197,94,0.5)"}:{background:"rgba(239,68,68,0.5)"}}}),t.jsx("span",{style:{fontSize:10,fontWeight:700,letterSpacing:"0.05em",color:"rgba(255,255,255,0.7)",textTransform:"uppercase"},children:i?`Online ${b(n)}`:"Offline"})]})]})},ee=({session:c})=>{const{renderer:i,actionQueue:n,micMuted:b,volume:l,onInactivityReset:d,loadedActions:g}=c,j=e.useRef(!1),{state:o,audioTrack:a}=A.useVoiceAssistant(),u=A.useLocalParticipant(),{segments:v}=A.useTrackTranscription({publication:u.microphoneTrack,source:me.Track.Source.Microphone,participant:u.localParticipant}),h=e.useRef(null),y=e.useRef(g);e.useEffect(()=>{y.current=g},[g]),e.useEffect(()=>{if(!n)return;const s=new O.VoiceActivityDetector({threshold:.008,analyseIntervalMs:30,speechStartFrames:1,speechPauseFrames:30,turnEndFrames:50});return h.current=s,s.on("speechStart",()=>{n.setTurnActive(!0),n.setSpeechState("speaking")}),s.on("turnEnd",()=>{n.setTurnActive(!1)}),()=>{s.stop(),h.current=null}},[n]),e.useEffect(()=>{const s=h.current;if(s)if(a?.publication?.track){const S=a.publication.track.mediaStreamTrack;S&&s.start(S)}else s.stop()},[a?.publication?.track]);const f=e.useRef(null);return e.useEffect(()=>{if(!n)return;const s=o;s==="speaking"?(f.current&&(clearTimeout(f.current),f.current=null),n.setTurnActive(!0),n.setSpeechState("speaking")):s==="listening"||s==="idle"?(f.current&&(clearTimeout(f.current),f.current=null),h.current?.endTurn(),n.setTurnActive(!1)):s==="thinking"&&(f.current||(f.current=setTimeout(()=>{f.current=null,h.current?.endTurn(),n.setTurnActive(!1)},500)))},[o,n]),e.useEffect(()=>()=>{f.current&&clearTimeout(f.current)},[]),A.useDataChannel(s=>{try{const D=new TextDecoder().decode(s.payload),C=JSON.parse(D);if(C.type==="AVATAR_UPDATE"){const M=C.action;if(!y.current.has(M))return;n?.dispatch(M)}}catch(S){console.error("[LiveKitAvatarProvider] Failed to parse data message:",S)}}),e.useEffect(()=>{d()},[v,o,d]),e.useEffect(()=>{if(!n)return;const s=!!a;(!s||o==="disconnected")&&(h.current?.endTurn(),n.forceListening()),j.current=s},[a,o,n]),e.useEffect(()=>{const s=u.localParticipant;s&&s.setMicrophoneEnabled(!b).catch(S=>{console.error("[LiveKitAvatarProvider] Failed to set mic state:",S)})},[b,u.localParticipant]),t.jsx("div",{style:{position:"absolute",bottom:80,left:8,right:8,display:"flex",flexDirection:"column",gap:8},children:t.jsx(A.RoomAudioRenderer,{volume:l})})},ye=({avatarId:c,api:i,livekitUrl:n,style:b,className:l,loadingContent:d,customControls:g,customStatusBadge:j})=>{const o=J({avatarId:c,api:i,livekitUrl:n}),{token:a,isConnected:u,avatarDimensions:v,disconnect:h,startSessionTimer:y}=o;return t.jsxs("section",{className:l,style:{position:"relative",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",overflow:"hidden",borderRadius:16,border:"1px solid rgba(255,255,255,0.1)",boxShadow:"0 25px 50px -12px rgba(0,0,0,0.5)",height:"80vh",width:"auto",maxWidth:"100%",aspectRatio:v?`${v.width} / ${v.height}`:"3 / 4",...b},children:[t.jsx(Y,{session:o,loadingContent:d}),t.jsxs("div",{style:{position:"absolute",inset:0,zIndex:20,pointerEvents:"none",display:"flex",flexDirection:"column",justifyContent:"space-between",padding:16},children:[t.jsxs("div",{style:{display:"flex",width:"100%",alignItems:"flex-start",justifyContent:"space-between"},children:[j?j(o):t.jsx(X,{session:o}),t.jsx("div",{})]}),g?g(o):t.jsx(Z,{session:o})]}),t.jsx("div",{style:{display:"none"},children:a&&n&&t.jsx(A.LiveKitRoom,{serverUrl:n,token:a,connect:!0,video:!1,audio:!0,onConnected:()=>{o.markConnected(),y()},onDisconnected:h,children:t.jsx(ee,{session:o})})})]})};exports.AvatarCanvas=Y;exports.AvatarControls=Z;exports.AvatarStatusBadge=X;exports.LiveKitAvatarProvider=ee;exports.LiveKitAvatarSession=ye;exports.useAvatarSession=J;
|
|
21
|
+
//# sourceMappingURL=zeroweight-renderer-react.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zeroweight-renderer-react.cjs.js","sources":["../src/useAvatarSession.ts","../src/AvatarCanvas.tsx","../src/AvatarControls.tsx","../src/AvatarStatusBadge.tsx","../src/LiveKitAvatarProvider.tsx","../src/LiveKitAvatarSession.tsx"],"sourcesContent":["/**\n * useAvatarSession — Main React hook for avatar rendering.\n *\n * Wraps ZeroWeightRenderer + ActionQueue in React lifecycle.\n * Returns reactive state and imperative methods.\n *\n * Usage:\n * const session = useAvatarSession({\n * avatarId: 'abc123',\n * api: {\n * getBundle: (id) => fetch(`/api/bundle/${id}`).then(r => r.json()),\n * getLiveKitToken: (id, name) => fetch(`/api/token?...`).then(r => r.json()),\n * },\n * });\n */\n\nimport { useEffect, useRef, useState, useCallback, useMemo } from \"react\";\nimport { ZeroWeightRenderer, ActionQueue } from \"@zeroweight/renderer\";\nimport type { ActionMetadata, RendererState } from \"@zeroweight/renderer\";\nimport type { ZeroWeightApi } from \"./types\";\n\nconst INACTIVITY_TIMEOUT_MS = 30000;\nconst SESSION_DURATION_SECONDS = 120;\n\nconst generateRandomName = () => {\n const adjectives = [\"Happy\", \"Swift\", \"Bright\", \"Cool\", \"Smart\"];\n const nouns = [\"User\", \"Guest\", \"Visitor\", \"Agent\", \"Caller\"];\n const randomNum = Math.floor(Math.random() * 1000);\n return `${adjectives[Math.floor(Math.random() * adjectives.length)]}${\n nouns[Math.floor(Math.random() * nouns.length)]\n }${randomNum}`;\n};\n\nexport interface AvatarSessionConfig {\n avatarId: string;\n /** Injectable API — provide your own fetch functions. */\n api: ZeroWeightApi;\n /** LiveKit server URL. */\n livekitUrl?: string;\n /** Session duration in seconds. Default: 120 */\n sessionDuration?: number;\n /** Inactivity timeout in ms. Default: 30000 */\n inactivityTimeout?: number;\n}\n\nexport interface AvatarSessionReturn {\n // Refs\n containerRef: React.RefObject<HTMLDivElement | null>;\n\n // Renderer instance (for advanced use)\n renderer: ZeroWeightRenderer | null;\n actionQueue: ActionQueue | null;\n\n // Reactive state\n rendererState: RendererState;\n avatarDimensions: { width: number; height: number } | null;\n loadedActions: Set<string>;\n actionMetadata: Record<string, ActionMetadata>;\n isLoadingActions: boolean;\n isEngineReady: boolean;\n\n // Connection state\n token: string | null;\n isConnecting: boolean;\n isConnected: boolean;\n livekitUrl: string;\n\n // Session\n timeRemaining: number;\n formatTime: (seconds: number) => string;\n\n // Controls\n micMuted: boolean;\n volume: number;\n\n // Methods\n connect: () => Promise<void>;\n disconnect: () => void;\n toggleMic: () => void;\n setVolume: (v: number) => void;\n toggleVolume: () => void;\n interrupt: () => void;\n runAction: (actionId: string) => void;\n startSessionTimer: () => void;\n /** Called by LiveKitRoom onConnected */\n markConnected: () => void;\n\n // For LiveKitAvatarProvider internal use\n onInactivityReset: () => void;\n}\n\nexport function useAvatarSession(config: AvatarSessionConfig): AvatarSessionReturn {\n const {\n avatarId,\n api,\n livekitUrl: livekitUrlProp = \"\",\n sessionDuration = SESSION_DURATION_SECONDS,\n inactivityTimeout = INACTIVITY_TIMEOUT_MS,\n } = config;\n\n const livekitUrl = livekitUrlProp;\n\n // Refs\n const containerRef = useRef<HTMLDivElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const rendererRef = useRef<ZeroWeightRenderer | null>(null);\n const actionQueueRef = useRef<ActionQueue | null>(null);\n\n // Engine state\n const [rendererState, setRendererState] = useState<RendererState>(\"idle\");\n const [avatarDimensions, setAvatarDimensions] = useState<{\n width: number;\n height: number;\n } | null>(null);\n const [loadedActions, setLoadedActions] = useState<Set<string>>(\n new Set([\"listening\"])\n );\n const [actionMetadata, setActionMetadata] = useState<\n Record<string, ActionMetadata>\n >({\n listening: { kind: \"looped\" },\n speaking: { kind: \"looped\" },\n });\n const [isLoadingActions, setIsLoadingActions] = useState(false);\n\n // Connection state\n const [token, setToken] = useState<string | null>(null);\n const [isConnecting, setIsConnecting] = useState(false);\n const [isConnected, setIsConnected] = useState(false);\n\n // Session timer\n const [timeRemaining, setTimeRemaining] = useState(sessionDuration);\n const sessionTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n const handleDisconnectRef = useRef<() => void>(() => {});\n\n // Controls\n const [micMuted, setMicMuted] = useState(false);\n const [volume, setVolumeState] = useState(0.8);\n\n // Inactivity\n const inactivityTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const isEngineReady = rendererState === \"ready\";\n\n // ─── Ensure Canvas ──────────────────────────────────────────────\n\n const ensureCanvas = useCallback(() => {\n const container = containerRef.current;\n if (!container) return null;\n\n let canvas = container.querySelector(\"canvas\") as HTMLCanvasElement | null;\n if (!canvas) {\n canvas = document.createElement(\"canvas\");\n canvas.style.width = \"100%\";\n canvas.style.height = \"100%\";\n canvas.style.display = \"block\";\n container.appendChild(canvas);\n }\n canvasRef.current = canvas;\n return canvas;\n }, []);\n\n // ─── Init Renderer ──────────────────────────────────────────────\n\n useEffect(() => {\n let cancelled = false;\n\n const initRenderer = async () => {\n const canvas = ensureCanvas();\n if (!canvas) return;\n\n // Create renderer\n const renderer = new ZeroWeightRenderer();\n rendererRef.current = renderer;\n\n // Create action queue\n const queue = new ActionQueue((actionId, fallback) => {\n renderer.play(actionId, fallback);\n });\n actionQueueRef.current = queue;\n\n // Wire up renderer events\n renderer.on(\"stateChanged\", (state) => {\n if (!cancelled) setRendererState(state);\n });\n\n renderer.on(\"dimensions\", (w, h) => {\n if (!cancelled) setAvatarDimensions({ width: w, height: h });\n });\n\n renderer.on(\"actionLoaded\", (actionId) => {\n if (!cancelled) {\n setLoadedActions((prev) => {\n const next = new Set(prev);\n next.add(actionId);\n return next;\n });\n }\n });\n\n renderer.on(\"allActionsLoaded\", () => {\n if (!cancelled) setIsLoadingActions(false);\n });\n\n renderer.on(\"ready\", () => {\n // Update action metadata from renderer\n if (!cancelled) {\n setActionMetadata(renderer.getActionMetadata());\n queue.setActionMetadata(renderer.getActionMetadata());\n }\n });\n\n // Fetch bundle and init\n try {\n setIsLoadingActions(true);\n const data = await api.getBundle(avatarId);\n if (cancelled) return;\n\n await renderer.init(canvas, { payload: data.payload });\n if (cancelled) return;\n\n // After init, update metadata again with all loaded data\n setActionMetadata(renderer.getActionMetadata());\n queue.setActionMetadata(renderer.getActionMetadata());\n } catch (e) {\n console.error(\"[useAvatarSession] Init failed:\", e);\n }\n };\n\n initRenderer();\n\n return () => {\n cancelled = true;\n rendererRef.current?.destroy();\n rendererRef.current = null;\n actionQueueRef.current = null;\n };\n }, [avatarId, ensureCanvas, api]);\n\n // ─── Auto-wave on load ──────────────────────────────────────────\n\n const hasWavedRef = useRef(false);\n useEffect(() => {\n if (\n isEngineReady &&\n rendererRef.current &&\n loadedActions.has(\"wave_hand\") &&\n !hasWavedRef.current\n ) {\n rendererRef.current.play(\"wave_hand\", \"listening\");\n hasWavedRef.current = true;\n }\n }, [isEngineReady, loadedActions]);\n\n // ─── Connection ─────────────────────────────────────────────────\n\n const connect = useCallback(async () => {\n if (isConnecting || isConnected) return;\n\n setIsConnecting(true);\n try {\n await navigator.mediaDevices.getUserMedia({ audio: true });\n const userName = generateRandomName();\n const data = await api.getLiveKitToken(avatarId, userName);\n setToken(data.token);\n } catch (error) {\n console.error(\"[useAvatarSession] Failed to connect:\", error);\n setIsConnecting(false);\n }\n }, [isConnecting, isConnected, avatarId, api]);\n\n const disconnect = useCallback(() => {\n // Clear session timer\n if (sessionTimerRef.current) {\n clearInterval(sessionTimerRef.current);\n sessionTimerRef.current = null;\n }\n setTimeRemaining(sessionDuration);\n\n // Clear inactivity timer\n if (inactivityTimeoutRef.current) {\n clearTimeout(inactivityTimeoutRef.current);\n inactivityTimeoutRef.current = null;\n }\n\n setToken(null);\n setIsConnected(false);\n setIsConnecting(false);\n\n // Reset avatar to listening\n actionQueueRef.current?.forceListening();\n }, [sessionDuration]);\n\n // Keep disconnect ref current for timer\n useEffect(() => {\n handleDisconnectRef.current = disconnect;\n }, [disconnect]);\n\n const markConnected = useCallback(() => {\n setIsConnected(true);\n setIsConnecting(false);\n }, []);\n\n // ─── Session Timer ──────────────────────────────────────────────\n\n const startSessionTimer = useCallback(() => {\n if (sessionTimerRef.current) clearInterval(sessionTimerRef.current);\n setTimeRemaining(sessionDuration);\n\n sessionTimerRef.current = setInterval(() => {\n setTimeRemaining((prev) => {\n if (prev <= 1) {\n clearInterval(sessionTimerRef.current!);\n sessionTimerRef.current = null;\n setTimeout(() => handleDisconnectRef.current(), 0);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n }, [sessionDuration]);\n\n const formatTime = useCallback((seconds: number) => {\n const m = Math.floor(seconds / 60)\n .toString()\n .padStart(2, \"0\");\n const s = (seconds % 60).toString().padStart(2, \"0\");\n return `${m}:${s}`;\n }, []);\n\n // Cleanup timer on unmount\n useEffect(() => {\n return () => {\n if (sessionTimerRef.current) clearInterval(sessionTimerRef.current);\n };\n }, []);\n\n // ─── Inactivity ─────────────────────────────────────────────────\n\n const onInactivityReset = useCallback(() => {\n if (inactivityTimeoutRef.current) {\n clearTimeout(inactivityTimeoutRef.current);\n }\n inactivityTimeoutRef.current = setTimeout(() => {\n handleDisconnectRef.current();\n }, inactivityTimeout);\n }, [inactivityTimeout]);\n\n // ─── Controls ───────────────────────────────────────────────────\n\n const toggleMic = useCallback(() => {\n setMicMuted((v) => !v);\n }, []);\n\n const setVolume = useCallback((v: number) => {\n setVolumeState(v);\n }, []);\n\n const toggleVolume = useCallback(() => {\n setVolumeState((v) => (v > 0 ? 0 : 1));\n }, []);\n\n const interrupt = useCallback(() => {\n rendererRef.current?.interrupt();\n }, []);\n\n const runAction = useCallback((actionId: string) => {\n if (actionQueueRef.current) {\n actionQueueRef.current.dispatch(actionId);\n } else {\n rendererRef.current?.play(actionId, \"listening\");\n }\n }, []);\n\n return {\n containerRef,\n renderer: rendererRef.current,\n actionQueue: actionQueueRef.current,\n rendererState,\n avatarDimensions,\n loadedActions,\n actionMetadata,\n isLoadingActions,\n isEngineReady,\n token,\n isConnecting,\n isConnected,\n livekitUrl,\n timeRemaining,\n formatTime,\n micMuted,\n volume,\n connect,\n disconnect,\n toggleMic,\n setVolume,\n toggleVolume,\n interrupt,\n runAction,\n startSessionTimer,\n markConnected,\n onInactivityReset,\n };\n}\n","/**\n * AvatarCanvas — Canvas container for the avatar renderer.\n *\n * Provides the div container where the renderer creates and manages a <canvas>.\n * Shows loading overlay when the engine is initializing.\n * Uses inline styles only — no CSS framework dependency.\n */\n\nimport React from \"react\";\nimport { Loader2 } from \"lucide-react\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface AvatarCanvasProps {\n session: AvatarSessionReturn;\n /** Optional style overrides for the outer container. */\n style?: React.CSSProperties;\n /** Custom loading component. */\n loadingContent?: React.ReactNode;\n}\n\nexport const AvatarCanvas: React.FC<AvatarCanvasProps> = ({\n session,\n style,\n loadingContent,\n}) => {\n return (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n zIndex: 0,\n pointerEvents: \"auto\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n overflow: \"hidden\",\n ...style,\n }}\n >\n <style>{`\n @keyframes zwr-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n @keyframes zwr-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n `}</style>\n <div\n ref={session.containerRef}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n transition: \"all 0.5s ease-in-out\",\n }}\n >\n {/* Canvas is injected here by the renderer */}\n {!session.isEngineReady && (\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: \"rgba(0,0,0,0.4)\",\n backdropFilter: \"blur(4px)\",\n zIndex: 10,\n }}\n >\n {loadingContent || (\n <div\n style={{\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: 16,\n }}\n >\n <Loader2\n style={{\n width: 40,\n height: 40,\n color: \"rgba(255,255,255,0.5)\",\n animation: \"zwr-spin 1s linear infinite\",\n }}\n />\n <div\n style={{\n fontSize: 14,\n color: \"rgba(255,255,255,0.5)\",\n fontWeight: 500,\n letterSpacing: \"0.1em\",\n textTransform: \"uppercase\",\n animation: \"zwr-pulse 2s ease-in-out infinite\",\n }}\n >\n Initializing Neural Engine...\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n );\n};\n","/**\n * AvatarControls — Default control bar for the avatar session.\n *\n * Mic toggle, connect/disconnect button.\n * Can be replaced entirely by a custom UI.\n * Uses inline styles only — no CSS framework dependency.\n */\n\nimport React, { useState } from \"react\";\nimport {\n Mic,\n MicOff,\n Power,\n Activity,\n Loader2,\n} from \"lucide-react\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface AvatarControlsProps {\n session: AvatarSessionReturn;\n /** Optional style overrides for the wrapper. */\n style?: React.CSSProperties;\n}\n\nconst btnBase: React.CSSProperties = {\n flexShrink: 0,\n borderRadius: 9999,\n padding: 16,\n transition: \"all 0.3s\",\n border: \"none\",\n cursor: \"pointer\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n};\n\nconst connectBtnBase: React.CSSProperties = {\n position: \"relative\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 12,\n borderRadius: 9999,\n padding: \"16px 24px\",\n fontSize: 16,\n fontWeight: 600,\n color: \"#fff\",\n whiteSpace: \"nowrap\",\n transition: \"all 0.3s\",\n border: \"none\",\n cursor: \"pointer\",\n};\n\nexport const AvatarControls: React.FC<AvatarControlsProps> = ({\n session,\n style,\n}) => {\n const {\n micMuted,\n toggleMic,\n isConnected,\n isConnecting,\n connect,\n disconnect,\n } = session;\n\n const [micHover, setMicHover] = useState(false);\n const [connectHover, setConnectHover] = useState(false);\n\n const micStyle: React.CSSProperties = micMuted\n ? {\n ...btnBase,\n background: \"rgba(239,68,68,0.1)\",\n color: \"#f87171\",\n boxShadow: \"inset 0 0 0 1px rgba(239,68,68,0.3)\",\n ...(micHover ? { background: \"rgba(239,68,68,0.2)\" } : {}),\n }\n : {\n ...btnBase,\n background: micHover ? \"rgba(255,255,255,0.1)\" : \"rgba(0,0,0,0.4)\",\n color: \"#fff\",\n boxShadow: \"inset 0 0 0 1px rgba(255,255,255,0.1)\",\n };\n\n const connectStyle: React.CSSProperties = isConnected\n ? {\n ...connectBtnBase,\n background: connectHover ? \"rgba(0,0,0,0.6)\" : \"rgba(0,0,0,0.4)\",\n boxShadow: \"inset 0 0 0 1px rgba(239,68,68,0.5), 0 0 20px rgba(239,68,68,0.2)\",\n }\n : {\n ...connectBtnBase,\n background: \"linear-gradient(to right, #7c3aed, #db2777, #f97316)\",\n boxShadow: \"0 0 30px rgba(236,72,153,0.3)\",\n opacity: connectHover ? 0.9 : 1,\n };\n\n if (isConnecting) {\n connectStyle.opacity = 0.5;\n connectStyle.cursor = \"not-allowed\";\n }\n\n return (\n <div\n style={{\n width: \"100%\",\n marginTop: \"auto\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n gap: 16,\n paddingBottom: 16,\n ...style,\n }}\n >\n <style>{`\n @keyframes zwr-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n `}</style>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 16,\n width: \"100%\",\n pointerEvents: \"auto\",\n }}\n >\n {/* Mic Toggle */}\n <button\n type=\"button\"\n onClick={toggleMic}\n onMouseEnter={() => setMicHover(true)}\n onMouseLeave={() => setMicHover(false)}\n style={micStyle}\n title={micMuted ? \"Unmute mic\" : \"Mute mic\"}\n >\n {micMuted ? <MicOff size={24} /> : <Mic size={24} />}\n </button>\n\n {/* Main Connect Button */}\n <button\n type=\"button\"\n onClick={isConnected ? disconnect : connect}\n onMouseEnter={() => setConnectHover(true)}\n onMouseLeave={() => setConnectHover(false)}\n disabled={isConnecting}\n style={connectStyle}\n >\n {isConnecting ? (\n <>\n <Loader2 size={20} style={{ animation: \"zwr-spin 1s linear infinite\" }} />\n <span>Connecting...</span>\n </>\n ) : isConnected ? (\n <>\n <Power size={20} />\n <span>End Session</span>\n </>\n ) : (\n <>\n <Activity size={20} />\n <span>Start Session</span>\n </>\n )}\n </button>\n </div>\n </div>\n );\n};\n","/**\n * AvatarStatusBadge — Connection status indicator with session timer.\n * Uses inline styles only — no CSS framework dependency.\n */\n\nimport React from \"react\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface AvatarStatusBadgeProps {\n session: AvatarSessionReturn;\n}\n\nexport const AvatarStatusBadge: React.FC<AvatarStatusBadgeProps> = ({\n session,\n}) => {\n const { isConnected, timeRemaining, formatTime } = session;\n\n const isUrgent = isConnected && timeRemaining <= 30;\n const isWarning = isConnected && timeRemaining > 30 && timeRemaining <= 60;\n\n const wrapperStyle: React.CSSProperties = {\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n backdropFilter: \"blur(12px)\",\n padding: \"6px 12px\",\n borderRadius: 9999,\n border: \"1px solid\",\n pointerEvents: \"auto\",\n transition: \"all 0.3s\",\n ...(isUrgent\n ? {\n background: \"rgba(239,68,68,0.3)\",\n borderColor: \"rgba(239,68,68,0.4)\",\n boxShadow: \"0 0 15px rgba(239,68,68,0.3)\",\n animation: \"zwr-pulse 2s ease-in-out infinite\",\n }\n : isWarning\n ? {\n background: \"rgba(249,115,22,0.2)\",\n borderColor: \"rgba(249,115,22,0.3)\",\n boxShadow: \"0 4px 12px rgba(0,0,0,0.3)\",\n }\n : {\n background: \"rgba(0,0,0,0.4)\",\n borderColor: \"rgba(255,255,255,0.1)\",\n boxShadow: \"0 4px 12px rgba(0,0,0,0.3)\",\n }),\n };\n\n return (\n <>\n <style>{`\n @keyframes zwr-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.5; }\n }\n `}</style>\n <div style={wrapperStyle}>\n <div\n style={{\n height: 8,\n width: 8,\n borderRadius: \"50%\",\n ...(isConnected\n ? {\n background: \"#22c55e\",\n boxShadow: \"0 0 10px rgba(34,197,94,0.5)\",\n }\n : {\n background: \"rgba(239,68,68,0.5)\",\n }),\n }}\n />\n <span\n style={{\n fontSize: 10,\n fontWeight: 700,\n letterSpacing: \"0.05em\",\n color: \"rgba(255,255,255,0.7)\",\n textTransform: \"uppercase\",\n }}\n >\n {isConnected ? `Online ${formatTime(timeRemaining)}` : \"Offline\"}\n </span>\n </div>\n </>\n );\n};\n","/**\n * LiveKitAvatarProvider — Bridge between LiveKit hooks and the ActionQueue/VAD.\n * Uses inline styles only — no CSS framework dependency.\n *\n * This component MUST be rendered inside a <LiveKitRoom>.\n */\n\nimport React, { useEffect, useRef } from \"react\";\nimport {\n useVoiceAssistant,\n RoomAudioRenderer,\n useTrackTranscription,\n useDataChannel,\n useLocalParticipant,\n} from \"@livekit/components-react\";\nimport { Track } from \"livekit-client\";\nimport { VoiceActivityDetector } from \"@zeroweight/renderer\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\n\ninterface LiveKitAvatarProviderProps {\n session: AvatarSessionReturn;\n}\n\nexport const LiveKitAvatarProvider: React.FC<LiveKitAvatarProviderProps> = ({\n session,\n}) => {\n const { renderer, actionQueue, micMuted, volume, onInactivityReset, loadedActions } = session;\n\n const prevAudioTrackRef = useRef<boolean>(false);\n const { state, audioTrack } = useVoiceAssistant();\n const localParticipant = useLocalParticipant();\n const { segments: userTranscriptions } = useTrackTranscription({\n publication: localParticipant.microphoneTrack,\n source: Track.Source.Microphone,\n participant: localParticipant.localParticipant,\n });\n\n // VAD instance\n const vadRef = useRef<VoiceActivityDetector | null>(null);\n\n // Ref to latest loadedActions so data channel callback doesn't go stale\n const loadedActionsRef = useRef(loadedActions);\n useEffect(() => {\n loadedActionsRef.current = loadedActions;\n }, [loadedActions]);\n\n // ─── VAD Setup ──────────────────────────────────────────────────\n\n useEffect(() => {\n if (!actionQueue) return;\n\n const vad = new VoiceActivityDetector({\n threshold: 0.008,\n analyseIntervalMs: 30,\n speechStartFrames: 1,\n speechPauseFrames: 30,\n turnEndFrames: 50,\n });\n vadRef.current = vad;\n\n vad.on(\"speechStart\", () => {\n actionQueue.setTurnActive(true);\n actionQueue.setSpeechState(\"speaking\");\n });\n\n vad.on(\"turnEnd\", () => {\n actionQueue.setTurnActive(false);\n });\n\n return () => {\n vad.stop();\n vadRef.current = null;\n };\n }, [actionQueue]);\n\n // ─── Connect/disconnect VAD to audio track ──────────────────────\n\n useEffect(() => {\n const vad = vadRef.current;\n if (!vad) return;\n\n if (audioTrack?.publication?.track) {\n const mediaTrack = audioTrack.publication.track.mediaStreamTrack;\n if (mediaTrack) {\n vad.start(mediaTrack);\n }\n } else {\n vad.stop();\n }\n }, [audioTrack?.publication?.track]);\n\n // ─── LiveKit state → turn management ─────────────────────────────\n\n const turnEndTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (!actionQueue) return;\n\n const stateStr = state as string;\n\n if (stateStr === \"speaking\") {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n turnEndTimerRef.current = null;\n }\n actionQueue.setTurnActive(true);\n actionQueue.setSpeechState(\"speaking\");\n } else if (stateStr === \"listening\" || stateStr === \"idle\") {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n turnEndTimerRef.current = null;\n }\n vadRef.current?.endTurn();\n actionQueue.setTurnActive(false);\n } else if (stateStr === \"thinking\") {\n if (!turnEndTimerRef.current) {\n turnEndTimerRef.current = setTimeout(() => {\n turnEndTimerRef.current = null;\n vadRef.current?.endTurn();\n actionQueue.setTurnActive(false);\n }, 500);\n }\n }\n }, [state, actionQueue]);\n\n // Cleanup turn-end timer on unmount\n useEffect(() => {\n return () => {\n if (turnEndTimerRef.current) {\n clearTimeout(turnEndTimerRef.current);\n }\n };\n }, []);\n\n // ─── Data channel (backend-triggered actions) ───────────────────\n\n useDataChannel((msg) => {\n try {\n const decoder = new TextDecoder();\n const strData = decoder.decode(msg.payload);\n const data = JSON.parse(strData);\n\n if (data.type === \"AVATAR_UPDATE\") {\n const actionId = data.action;\n if (!loadedActionsRef.current.has(actionId)) {\n return;\n }\n actionQueue?.dispatch(actionId);\n }\n } catch (err) {\n console.error(\"[LiveKitAvatarProvider] Failed to parse data message:\", err);\n }\n });\n\n // ─── Inactivity reset on user speech or agent activity ──────────\n\n useEffect(() => {\n onInactivityReset();\n }, [userTranscriptions, state, onInactivityReset]);\n\n // ─── Audio loss / disconnect fallback ───────────────────────────\n\n useEffect(() => {\n if (!actionQueue) return;\n\n const hasAudio = !!audioTrack;\n const isDisconnectedState = state === \"disconnected\";\n\n if (!hasAudio || isDisconnectedState) {\n vadRef.current?.endTurn();\n actionQueue.forceListening();\n }\n prevAudioTrackRef.current = hasAudio;\n }, [audioTrack, state, actionQueue]);\n\n // ─── Mic mute sync ─────────────────────────────────────────────\n\n useEffect(() => {\n const participant = localParticipant.localParticipant;\n if (!participant) return;\n\n participant.setMicrophoneEnabled(!micMuted).catch((err) => {\n console.error(\"[LiveKitAvatarProvider] Failed to set mic state:\", err);\n });\n }, [micMuted, localParticipant.localParticipant]);\n\n return (\n <div style={{ position: \"absolute\", bottom: 80, left: 8, right: 8, display: \"flex\", flexDirection: \"column\", gap: 8 }}>\n <RoomAudioRenderer volume={volume} />\n </div>\n );\n};\n","/**\n * LiveKitAvatarSession — Full drop-in avatar component.\n * Uses inline styles only — no CSS framework dependency.\n *\n * Simplest usage:\n * import { LiveKitAvatarSession } from \"@zeroweight/react\";\n * <LiveKitAvatarSession\n * avatarId=\"abc123\"\n * livekitUrl=\"wss://...\"\n * api={{\n * getBundle: (id) => fetch(`/api/bundle/${id}`).then(r => r.json()),\n * getLiveKitToken: (id, name) => fetch(`/api/token?name=${name}`).then(r => r.json()),\n * }}\n * />\n */\n\nimport React from \"react\";\nimport { LiveKitRoom } from \"@livekit/components-react\";\nimport \"@livekit/components-styles\";\n\nimport { useAvatarSession } from \"./useAvatarSession\";\nimport type { AvatarSessionReturn } from \"./useAvatarSession\";\nimport { AvatarCanvas } from \"./AvatarCanvas\";\nimport { AvatarControls } from \"./AvatarControls\";\nimport { AvatarStatusBadge } from \"./AvatarStatusBadge\";\nimport { LiveKitAvatarProvider } from \"./LiveKitAvatarProvider\";\nimport type { ZeroWeightApi } from \"./types\";\n\ninterface LiveKitAvatarSessionProps {\n avatarId: string;\n /** Injectable API — provide your own fetch functions. */\n api: ZeroWeightApi;\n /** LiveKit server URL (e.g. \"wss://your-livekit.example.com\"). */\n livekitUrl: string;\n /** Optional style overrides for the outer section. */\n style?: React.CSSProperties;\n /** Optional class name overrides for the outer section. */\n className?: string;\n /** Custom loading UI for the canvas. */\n loadingContent?: React.ReactNode;\n /** Custom controls component. If provided, replaces the default controls. */\n customControls?: (session: AvatarSessionReturn) => React.ReactNode;\n /** Custom status badge. If provided, replaces the default badge. */\n customStatusBadge?: (session: AvatarSessionReturn) => React.ReactNode;\n}\n\nexport const LiveKitAvatarSession: React.FC<LiveKitAvatarSessionProps> = ({\n avatarId,\n api,\n livekitUrl,\n style,\n className,\n loadingContent,\n customControls,\n customStatusBadge,\n}) => {\n const session = useAvatarSession({ avatarId, api, livekitUrl });\n\n const {\n token,\n isConnected,\n avatarDimensions,\n disconnect,\n startSessionTimer,\n } = session;\n\n return (\n <section\n className={className}\n style={{\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n overflow: \"hidden\",\n borderRadius: 16,\n border: \"1px solid rgba(255,255,255,0.1)\",\n boxShadow: \"0 25px 50px -12px rgba(0,0,0,0.5)\",\n height: \"80vh\",\n width: \"auto\",\n maxWidth: \"100%\",\n aspectRatio: avatarDimensions\n ? `${avatarDimensions.width} / ${avatarDimensions.height}`\n : \"3 / 4\",\n ...style,\n }}\n >\n {/* 1. Canvas Layer */}\n <AvatarCanvas session={session} loadingContent={loadingContent} />\n\n {/* 2. UI Overlay Layer */}\n <div\n style={{\n position: \"absolute\",\n inset: 0,\n zIndex: 20,\n pointerEvents: \"none\",\n display: \"flex\",\n flexDirection: \"column\",\n justifyContent: \"space-between\",\n padding: 16,\n }}\n >\n {/* Top Header / Status */}\n <div\n style={{\n display: \"flex\",\n width: \"100%\",\n alignItems: \"flex-start\",\n justifyContent: \"space-between\",\n }}\n >\n {customStatusBadge ? (\n customStatusBadge(session)\n ) : (\n <AvatarStatusBadge session={session} />\n )}\n <div />\n </div>\n\n {/* Bottom Controls */}\n {customControls ? (\n customControls(session)\n ) : (\n <AvatarControls session={session} />\n )}\n </div>\n\n {/* 3. LiveKit Room (hidden, audio-only) */}\n <div style={{ display: \"none\" }}>\n {token && livekitUrl && (\n <LiveKitRoom\n serverUrl={livekitUrl}\n token={token}\n connect={true}\n video={false}\n audio={true}\n onConnected={() => {\n session.markConnected();\n startSessionTimer();\n }}\n onDisconnected={disconnect}\n >\n <LiveKitAvatarProvider session={session} />\n </LiveKitRoom>\n )}\n </div>\n </section>\n );\n};\n"],"names":["INACTIVITY_TIMEOUT_MS","SESSION_DURATION_SECONDS","generateRandomName","adjectives","nouns","randomNum","useAvatarSession","config","avatarId","api","livekitUrlProp","sessionDuration","inactivityTimeout","livekitUrl","containerRef","useRef","canvasRef","rendererRef","actionQueueRef","rendererState","setRendererState","useState","avatarDimensions","setAvatarDimensions","loadedActions","setLoadedActions","actionMetadata","setActionMetadata","isLoadingActions","setIsLoadingActions","token","setToken","isConnecting","setIsConnecting","isConnected","setIsConnected","timeRemaining","setTimeRemaining","sessionTimerRef","handleDisconnectRef","micMuted","setMicMuted","volume","setVolumeState","inactivityTimeoutRef","isEngineReady","ensureCanvas","useCallback","container","canvas","useEffect","cancelled","renderer","ZeroWeightRenderer","queue","ActionQueue","actionId","fallback","state","w","h","prev","next","data","e","hasWavedRef","connect","userName","error","disconnect","markConnected","startSessionTimer","formatTime","seconds","m","s","onInactivityReset","toggleMic","v","setVolume","toggleVolume","interrupt","runAction","AvatarCanvas","session","style","loadingContent","jsxs","jsx","Loader2","btnBase","connectBtnBase","AvatarControls","micHover","setMicHover","connectHover","setConnectHover","micStyle","connectStyle","MicOff","Mic","Fragment","Power","Activity","AvatarStatusBadge","isUrgent","isWarning","wrapperStyle","LiveKitAvatarProvider","actionQueue","prevAudioTrackRef","audioTrack","useVoiceAssistant","localParticipant","useLocalParticipant","userTranscriptions","useTrackTranscription","Track","vadRef","loadedActionsRef","vad","VoiceActivityDetector","mediaTrack","turnEndTimerRef","stateStr","useDataChannel","msg","strData","err","hasAudio","participant","RoomAudioRenderer","LiveKitAvatarSession","className","customControls","customStatusBadge","LiveKitRoom"],"mappings":"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"}
|