@convai/web-sdk 1.4.0-beta.2 → 1.5.0-beta.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 +73 -1668
- package/dist/core/AudioManager.d.ts +2 -2
- package/dist/core/AudioManager.d.ts.map +1 -1
- package/dist/core/AudioManager.js.map +1 -1
- package/dist/core/ConvaiClient.d.ts +14 -1
- package/dist/core/ConvaiClient.d.ts.map +1 -1
- package/dist/core/ConvaiClient.js +64 -19
- package/dist/core/ConvaiClient.js.map +1 -1
- package/dist/core/MessageHandler.d.ts.map +1 -1
- package/dist/core/MessageHandler.js +10 -1
- package/dist/core/MessageHandler.js.map +1 -1
- package/dist/core/types.d.ts +78 -10
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/react/hooks/useConvaiClient.d.ts.map +1 -1
- package/dist/react/hooks/useConvaiClient.js +2 -0
- package/dist/react/hooks/useConvaiClient.js.map +1 -1
- package/dist/{core → vanilla}/WebSocketSession.d.ts +1 -1
- package/dist/vanilla/WebSocketSession.d.ts.map +1 -0
- package/dist/{core → vanilla}/WebSocketSession.js +1 -1
- package/dist/vanilla/WebSocketSession.js.map +1 -0
- package/dist/vanilla/websocket.d.ts +2 -17
- package/dist/vanilla/websocket.d.ts.map +1 -1
- package/dist/vanilla/websocket.js +11 -6
- package/dist/vanilla/websocket.js.map +1 -1
- package/package.json +8 -4
- package/dist/core/WebSocketSession.d.ts.map +0 -1
- package/dist/core/WebSocketSession.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,1735 +1,140 @@
|
|
|
1
1
|
# @convai/web-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Real-time conversational AI characters for the web.**
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
- Direct core client usage for custom UIs and advanced integrations
|
|
8
|
-
- Optional lipsync data pipelines for ARKit and MetaHuman rigs
|
|
5
|
+
[](https://www.npmjs.com/package/@convai/web-sdk)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
TypeScript-first SDK for embedding Convai AI characters into React and vanilla JS applications. Voice, text, lipsync, emotions, video, and screen share — all in one package.
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- [1. Package Entry Points](#1-package-entry-points) — includes `@convai/web-sdk/vanilla/websocket` opt-in subpath
|
|
15
|
-
- [2. Installation and Requirements](#2-installation-and-requirements)
|
|
16
|
-
- [3. Credentials and Environment Setup](#3-credentials-and-environment-setup)
|
|
17
|
-
- [4. Quick Start](#4-quick-start)
|
|
18
|
-
- [5. Build a Chatbot from Scratch](#5-build-a-chatbot-from-scratch)
|
|
19
|
-
- [6. Core Concepts and Lifecycle](#6-core-concepts-and-lifecycle)
|
|
20
|
-
- [7. Configuration Reference (`ConvaiConfig`)](#7-configuration-reference-convaiconfig)
|
|
21
|
-
- [Transport Options](#transport-options)
|
|
22
|
-
- [8. Core API Reference (`ConvaiClient`)](#8-core-api-reference-convaiclient)
|
|
23
|
-
- [Idle management and LLM silence handling](#idle-management-and-llm-silence-handling)
|
|
24
|
-
- [Server acknowledgments (`serverResponse`)](#server-acknowledgments-serverresponse)
|
|
25
|
-
- [Interaction lifecycle (`interactionCreated`)](#interaction-lifecycle-interactioncreated)
|
|
26
|
-
- [9. Memory Management API](#9-memory-management-api)
|
|
27
|
-
- [10. Message Semantics and Turn Completion](#10-message-semantics-and-turn-completion)
|
|
28
|
-
- [11. React API Reference](#11-react-api-reference)
|
|
29
|
-
- [12. Vanilla API Reference](#12-vanilla-api-reference)
|
|
30
|
-
- [13. Audio Integration Best Practices (Vanilla TypeScript)](#13-audio-integration-best-practices-vanilla-typescript)
|
|
31
|
-
- [14. Error Handling and Reliability Patterns](#14-error-handling-and-reliability-patterns)
|
|
32
|
-
- [15. Troubleshooting](#15-troubleshooting)
|
|
33
|
-
- [16. Lipsync Helpers Reference](#16-lipsync-helpers-reference)
|
|
34
|
-
- [17. Examples](#17-examples)
|
|
35
|
-
|
|
36
|
-
## 1. Package Entry Points
|
|
37
|
-
|
|
38
|
-
The SDK is published with multiple entry points for different integration styles.
|
|
39
|
-
|
|
40
|
-
### `@convai/web-sdk` (default)
|
|
41
|
-
|
|
42
|
-
Primary exports:
|
|
43
|
-
|
|
44
|
-
- `useConvaiClient`
|
|
45
|
-
- `ConvaiWidget`
|
|
46
|
-
- `useCharacterInfo`
|
|
47
|
-
- `useLocalCameraTrack`
|
|
48
|
-
- `ConvaiClient`
|
|
49
|
-
- `AudioRenderer` (re-export of LiveKit `RoomAudioRenderer` for React usage)
|
|
50
|
-
- `AudioContext` (re-export of LiveKit `RoomContext`)
|
|
51
|
-
- Core types re-exported from `core/types`:
|
|
52
|
-
- `AudioSettings`
|
|
53
|
-
- `ConvaiConfig`
|
|
54
|
-
- `ChatMessage`
|
|
55
|
-
- `ConvaiClientState`
|
|
56
|
-
- `AudioControls`
|
|
57
|
-
- `VideoControls`
|
|
58
|
-
- `ScreenShareControls`
|
|
59
|
-
- `IConvaiClient`
|
|
60
|
-
- All exports from `@convai/web-sdk/lipsync-helpers`
|
|
61
|
-
|
|
62
|
-
### `@convai/web-sdk/react`
|
|
63
|
-
|
|
64
|
-
React-focused entry point, equivalent to the default React API surface.
|
|
65
|
-
|
|
66
|
-
### `@convai/web-sdk/vanilla`
|
|
67
|
-
|
|
68
|
-
Vanilla/browser-focused exports:
|
|
69
|
-
|
|
70
|
-
- `ConvaiClient`
|
|
71
|
-
- `AudioRenderer` (vanilla audio playback manager)
|
|
72
|
-
- `createConvaiWidget`
|
|
73
|
-
- `destroyConvaiWidget`
|
|
74
|
-
- Types:
|
|
75
|
-
- `VanillaWidget`
|
|
76
|
-
- `VanillaWidgetOptions`
|
|
77
|
-
- `IConvaiClient`
|
|
78
|
-
- `ConvaiConfig`
|
|
79
|
-
- `ConvaiClientState`
|
|
80
|
-
- `ChatMessage`
|
|
81
|
-
|
|
82
|
-
### `@convai/web-sdk/core`
|
|
83
|
-
|
|
84
|
-
Framework-agnostic low-level API:
|
|
85
|
-
|
|
86
|
-
- `ConvaiClient`
|
|
87
|
-
- `AudioManager`
|
|
88
|
-
- `VideoManager`
|
|
89
|
-
- `ScreenShareManager`
|
|
90
|
-
- `MessageHandler`
|
|
91
|
-
- `MemoryManager`
|
|
92
|
-
- `BlendshapeQueue`
|
|
93
|
-
- `EventEmitter`
|
|
94
|
-
- Type alias: `ConvaiClientType`
|
|
95
|
-
- All core types from `core/types`
|
|
96
|
-
- `TurnStats` type
|
|
97
|
-
|
|
98
|
-
### `@convai/web-sdk/vanilla/websocket`
|
|
99
|
-
|
|
100
|
-
Optional subpath for the WebSocket (Pipecat) transport. Import this alongside `@convai/web-sdk/vanilla` when you use `transport: "websocket"` in `ConvaiConfig`. Importing it signals to your bundler that `@pipecat-ai/client-js` and `@pipecat-ai/websocket-transport` should be included.
|
|
101
|
-
|
|
102
|
-
If you only use the default LiveKit transport you should **not** import this subpath — those pipecat packages will then be excluded from your bundle entirely.
|
|
103
|
-
|
|
104
|
-
Exports:
|
|
105
|
-
|
|
106
|
-
- `WebSocketSession`
|
|
10
|
+
---
|
|
107
11
|
|
|
108
|
-
|
|
12
|
+
## Features
|
|
109
13
|
|
|
110
|
-
|
|
14
|
+
- **React & vanilla JS** — `useConvaiClient` hook, `ConvaiWidget`, and a framework-agnostic core
|
|
15
|
+
- **Real-time audio/video** — full-duplex WebRTC with echo cancellation, camera, and screen share
|
|
16
|
+
- **Lipsync** — ARKit and MetaHuman blendshape streams for facial animation
|
|
17
|
+
- **Emotions** — per-turn emotion detection with intensity scale
|
|
18
|
+
- **Dynamic context** — inject game state, user preferences, or scene data mid-session
|
|
19
|
+
- **Long-term memory** — persistent cross-session memories scoped to each end user
|
|
20
|
+
- **File upload** — send images to the character during a live session
|
|
21
|
+
- **WebSocket transport** — opt-in alternative to WebRTC for constrained networks
|
|
22
|
+
- **Auth tokens** — server-side token exchange for production deployments
|
|
111
23
|
|
|
112
|
-
|
|
24
|
+
---
|
|
113
25
|
|
|
114
|
-
|
|
26
|
+
## Installation
|
|
115
27
|
|
|
116
28
|
```bash
|
|
117
29
|
npm install @convai/web-sdk
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
or
|
|
121
|
-
|
|
122
|
-
```bash
|
|
30
|
+
# or
|
|
123
31
|
pnpm add @convai/web-sdk
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
or
|
|
127
|
-
|
|
128
|
-
```bash
|
|
32
|
+
# or
|
|
129
33
|
yarn add @convai/web-sdk
|
|
130
34
|
```
|
|
131
35
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
- Modern browser with WebRTC support
|
|
135
|
-
- Secure context (`https://` or `http://localhost`) for microphone/camera/screen access
|
|
136
|
-
|
|
137
|
-
### Peer dependencies
|
|
138
|
-
|
|
139
|
-
If you are using React APIs:
|
|
36
|
+
**React peer dependencies:** `react` and `react-dom` `^18 || ^19`
|
|
140
37
|
|
|
141
|
-
|
|
142
|
-
- `react-dom` `^18 || ^19`
|
|
38
|
+
**Runtime requirement:** secure context (`https://` or `http://localhost`) for microphone/camera access.
|
|
143
39
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
### Obtain credentials
|
|
147
|
-
|
|
148
|
-
1. Create/login to your Convai account.
|
|
149
|
-
2. Create or select a character.
|
|
150
|
-
3. Copy:
|
|
151
|
-
- API key
|
|
152
|
-
- Character ID
|
|
153
|
-
|
|
154
|
-
### Store credentials in environment variables
|
|
155
|
-
|
|
156
|
-
Do not hardcode credentials in source files.
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# .env.local (example)
|
|
160
|
-
VITE_CONVAI_API_KEY=<YOUR_CONVAI_API_KEY>
|
|
161
|
-
VITE_CONVAI_CHARACTER_ID=<YOUR_CONVAI_CHARACTER_ID>
|
|
162
|
-
VITE_CONVAI_API_URL=<OPTIONAL_CONVAI_BASE_URL>
|
|
163
|
-
VITE_CONVAI_LOG_RTVI_MESSAGES=false # optional; incoming/outgoing RTVI logs are on by default
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Use these values through your build system (`import.meta.env`, process env injection, or server-provided config).
|
|
40
|
+
---
|
|
167
41
|
|
|
168
|
-
##
|
|
42
|
+
## Quick start
|
|
169
43
|
|
|
170
44
|
### React
|
|
171
45
|
|
|
172
46
|
```tsx
|
|
173
|
-
import {
|
|
47
|
+
import { useConvaiClient, ConvaiWidget } from '@convai/web-sdk';
|
|
174
48
|
|
|
175
49
|
export function App() {
|
|
176
|
-
const
|
|
50
|
+
const client = useConvaiClient({
|
|
177
51
|
apiKey: import.meta.env.VITE_CONVAI_API_KEY,
|
|
178
52
|
characterId: import.meta.env.VITE_CONVAI_CHARACTER_ID,
|
|
179
|
-
enableVideo: false,
|
|
180
|
-
startWithAudioOn: false,
|
|
181
53
|
});
|
|
182
54
|
|
|
183
|
-
return <ConvaiWidget convaiClient={
|
|
55
|
+
return <ConvaiWidget convaiClient={client} />;
|
|
184
56
|
}
|
|
185
57
|
```
|
|
186
58
|
|
|
187
59
|
### Vanilla TypeScript
|
|
188
60
|
|
|
189
61
|
```ts
|
|
190
|
-
import { ConvaiClient, createConvaiWidget } from
|
|
62
|
+
import { ConvaiClient, createConvaiWidget } from '@convai/web-sdk/vanilla';
|
|
191
63
|
|
|
192
64
|
const client = new ConvaiClient({
|
|
193
65
|
apiKey: import.meta.env.VITE_CONVAI_API_KEY,
|
|
194
66
|
characterId: import.meta.env.VITE_CONVAI_CHARACTER_ID,
|
|
195
|
-
enableVideo: false,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const widget = createConvaiWidget(document.body, {
|
|
199
|
-
convaiClient: client,
|
|
200
|
-
defaultVoiceMode: true,
|
|
201
|
-
onConnect: () => console.log("Connected"),
|
|
202
|
-
onDisconnect: () => console.log("Disconnected"),
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
window.addEventListener("beforeunload", () => {
|
|
206
|
-
widget.destroy();
|
|
207
|
-
void client.disconnect().catch(() => undefined);
|
|
208
|
-
});
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## 5. Build a Chatbot from Scratch
|
|
212
|
-
|
|
213
|
-
This section shows an end-to-end approach you can use in production.
|
|
214
|
-
|
|
215
|
-
### A) React from scratch (custom connection flow)
|
|
216
|
-
|
|
217
|
-
#### Step 1: Create the client
|
|
218
|
-
|
|
219
|
-
```tsx
|
|
220
|
-
import { useConvaiClient } from "@convai/web-sdk";
|
|
221
|
-
|
|
222
|
-
const convaiClient = useConvaiClient({
|
|
223
|
-
apiKey: import.meta.env.VITE_CONVAI_API_KEY,
|
|
224
|
-
characterId: import.meta.env.VITE_CONVAI_CHARACTER_ID,
|
|
225
|
-
endUserId: "<UNIQUE_END_USER_ID>",
|
|
226
|
-
endUserMetadata: {
|
|
227
|
-
name: "John Doe",
|
|
228
|
-
age: "30",
|
|
229
|
-
// Add any additional metadata you want to send
|
|
230
|
-
},
|
|
231
|
-
enableVideo: true,
|
|
232
|
-
startWithVideoOn: false,
|
|
233
|
-
startWithAudioOn: false,
|
|
234
|
-
ttsEnabled: true,
|
|
235
|
-
enableLipsync: true,
|
|
236
|
-
blendshapeConfig: {
|
|
237
|
-
format: "arkit",
|
|
238
|
-
frames_buffer_duration: 0.5,
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
#### Step 2: Connect from a user gesture with error handling
|
|
244
|
-
|
|
245
|
-
```tsx
|
|
246
|
-
async function handleConnect() {
|
|
247
|
-
try {
|
|
248
|
-
await convaiClient.connect();
|
|
249
|
-
} catch (error) {
|
|
250
|
-
console.error("Connection failed:", error);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
#### Step 3: Wait for readiness before sending text
|
|
256
|
-
|
|
257
|
-
```tsx
|
|
258
|
-
function sendMessage(text: string) {
|
|
259
|
-
if (!convaiClient.state.isConnected || !convaiClient.isBotReady) return;
|
|
260
|
-
convaiClient.sendUserTextMessage(text);
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
#### Step 4: Render the widget or your own UI
|
|
265
|
-
|
|
266
|
-
```tsx
|
|
267
|
-
import { ConvaiWidget } from "@convai/web-sdk";
|
|
268
|
-
|
|
269
|
-
<ConvaiWidget
|
|
270
|
-
convaiClient={convaiClient}
|
|
271
|
-
showVideo={true}
|
|
272
|
-
showScreenShare={true}
|
|
273
|
-
defaultVoiceMode={true}
|
|
274
|
-
/>;
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
#### Step 5: Subscribe to lifecycle events
|
|
278
|
-
|
|
279
|
-
```tsx
|
|
280
|
-
useEffect(() => {
|
|
281
|
-
const unsubError = convaiClient.on("error", (error) => {
|
|
282
|
-
console.error("Convai error:", error);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
const unsubState = convaiClient.on("stateChange", (state) => {
|
|
286
|
-
console.log("State:", state.agentState);
|
|
287
|
-
|
|
288
|
-
// Access end user information from connection response
|
|
289
|
-
if (state.endUserId) {
|
|
290
|
-
console.log("End User ID:", state.endUserId);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (state.endUserMetadata) {
|
|
294
|
-
console.log("End User Metadata:", state.endUserMetadata);
|
|
295
|
-
// Example: { name: 'John', age: '30' }
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const unsubMessages = convaiClient.on("messagesChange", (messages) => {
|
|
300
|
-
console.log("Messages:", messages.length);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
return () => {
|
|
304
|
-
unsubError();
|
|
305
|
-
unsubState();
|
|
306
|
-
unsubMessages();
|
|
307
|
-
};
|
|
308
|
-
}, [convaiClient]);
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
#### Step 6: Clean up on unmount
|
|
312
|
-
|
|
313
|
-
```tsx
|
|
314
|
-
useEffect(() => {
|
|
315
|
-
return () => {
|
|
316
|
-
void convaiClient.disconnect().catch(() => undefined);
|
|
317
|
-
};
|
|
318
|
-
}, [convaiClient]);
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
### B) Vanilla TypeScript from scratch (widget + custom hooks)
|
|
322
|
-
|
|
323
|
-
#### Step 1: Initialize client and widget
|
|
324
|
-
|
|
325
|
-
```ts
|
|
326
|
-
import { ConvaiClient, createConvaiWidget } from "@convai/web-sdk/vanilla";
|
|
327
|
-
|
|
328
|
-
const client = new ConvaiClient({
|
|
329
|
-
apiKey: "<YOUR_CONVAI_API_KEY>",
|
|
330
|
-
characterId: "<YOUR_CHARACTER_ID>",
|
|
331
|
-
endUserId: "<UNIQUE_END_USER_ID>",
|
|
332
|
-
enableVideo: true,
|
|
333
|
-
startWithVideoOn: false,
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
const widget = createConvaiWidget(document.body, {
|
|
337
|
-
convaiClient: client,
|
|
338
|
-
showVideo: true,
|
|
339
|
-
showScreenShare: true,
|
|
340
|
-
defaultVoiceMode: true,
|
|
341
|
-
onConnect: () => console.log("Connected"),
|
|
342
|
-
onDisconnect: () => console.log("Disconnected"),
|
|
343
|
-
onMessage: (message) => console.log("Message:", message),
|
|
344
67
|
});
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
#### Step 2: Add explicit error listeners
|
|
348
|
-
|
|
349
|
-
```ts
|
|
350
|
-
const unsubError = client.on("error", (error) => {
|
|
351
|
-
console.error("SDK error:", error);
|
|
352
|
-
});
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
#### Step 3: Add guarded send utility
|
|
356
|
-
|
|
357
|
-
```ts
|
|
358
|
-
function safeSend(text: string) {
|
|
359
|
-
if (!text.trim()) return;
|
|
360
|
-
if (!client.state.isConnected) return;
|
|
361
|
-
if (!client.isBotReady) return;
|
|
362
|
-
client.sendUserTextMessage(text);
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
68
|
|
|
366
|
-
|
|
69
|
+
const widget = createConvaiWidget(document.body, { convaiClient: client });
|
|
367
70
|
|
|
368
|
-
|
|
369
|
-
function destroy() {
|
|
370
|
-
unsubError();
|
|
71
|
+
window.addEventListener('beforeunload', () => {
|
|
371
72
|
widget.destroy();
|
|
372
|
-
void client.disconnect()
|
|
373
|
-
}
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
### C) Custom UI (framework-agnostic)
|
|
377
|
-
|
|
378
|
-
If you are not using the built-in widget:
|
|
379
|
-
|
|
380
|
-
- Use `ConvaiClient` from `@convai/web-sdk/core`
|
|
381
|
-
- Use `AudioRenderer` from `@convai/web-sdk/vanilla` for remote audio playback
|
|
382
|
-
- Render your own UI based on `stateChange`, `messagesChange`, and control manager events
|
|
383
|
-
|
|
384
|
-
```ts
|
|
385
|
-
import { ConvaiClient } from "@convai/web-sdk/core";
|
|
386
|
-
import { AudioRenderer } from "@convai/web-sdk/vanilla";
|
|
387
|
-
|
|
388
|
-
const client = new ConvaiClient({
|
|
389
|
-
apiKey: "<YOUR_CONVAI_API_KEY>",
|
|
390
|
-
characterId: "<YOUR_CHARACTER_ID>",
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
await client.connect();
|
|
394
|
-
const audioRenderer = new AudioRenderer(client.room);
|
|
395
|
-
|
|
396
|
-
// ... your custom UI logic
|
|
397
|
-
|
|
398
|
-
audioRenderer.destroy();
|
|
399
|
-
await client.disconnect();
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
## 6. Core Concepts and Lifecycle
|
|
403
|
-
|
|
404
|
-
### Connection lifecycle
|
|
405
|
-
|
|
406
|
-
1. `connect()` starts room and transport setup.
|
|
407
|
-
2. `state.isConnected` becomes true when room connection is established.
|
|
408
|
-
3. `botReady` event indicates the character is ready for interaction.
|
|
409
|
-
4. Messages stream through data events into `chatMessages`.
|
|
410
|
-
5. Audio/video/screen-share are managed through dedicated control managers.
|
|
411
|
-
6. `disconnect()` tears down the session.
|
|
412
|
-
|
|
413
|
-
### Activity lifecycle
|
|
414
|
-
|
|
415
|
-
- `state.isThinking`: model is generating response
|
|
416
|
-
- `state.isSpeaking`: model audio is currently speaking
|
|
417
|
-
- `state.agentState`: combined high-level state (`disconnected | connected | listening | thinking | speaking`)
|
|
418
|
-
- `state.endUserId`: end user ID returned from the connection response (if provided in config)
|
|
419
|
-
- `state.endUserMetadata`: end user metadata returned from the connection response (if provided in config)
|
|
420
|
-
- `state.metrics`: array of metrics events received during the current session (clears on `resetSession()`)
|
|
421
|
-
|
|
422
|
-
### Widget lifecycle
|
|
423
|
-
|
|
424
|
-
Both React and vanilla widgets:
|
|
425
|
-
|
|
426
|
-
- auto-connect on first user interaction
|
|
427
|
-
- expose optional callbacks/events
|
|
428
|
-
- need explicit cleanup on app teardown
|
|
429
|
-
|
|
430
|
-
## 7. Configuration Reference (`ConvaiConfig`)
|
|
431
|
-
|
|
432
|
-
| Field | Type | Required | Default | Description |
|
|
433
|
-
| ----------------------------------------- | ------------------ | -------- | -------------------- | ----------------------------------------------------------------------------------- |
|
|
434
|
-
| `transport` | `"livekit" \| "websocket"` | No | `"livekit"` | Transport layer for the session. See [Transport Options](#transport-options). |
|
|
435
|
-
| `apiKey` | `string` | Yes | - | Convai API key. |
|
|
436
|
-
| `characterId` | `string` | Yes | - | Target character identifier. |
|
|
437
|
-
| `endUserId` | `string` | No | `undefined` | Stable end-user identity for memory/analytics continuity. |
|
|
438
|
-
| `endUserMetadata` | `Record<string, unknown>` | No | `undefined` | Additional end-user metadata (e.g., name, age) sent with the connection request. |
|
|
439
|
-
| `url` | `string` | No | SDK internal default | Convai base URL. Set explicitly if your deployment requires a specific environment. |
|
|
440
|
-
| `enableVideo` | `boolean` | No | `false` | Enables video-capable connection type. |
|
|
441
|
-
| `startWithVideoOn` | `boolean` | No | `false` | Auto-enable camera after connect. |
|
|
442
|
-
| `startWithAudioOn` | `boolean` | No | `false` | Auto-enable microphone after connect. |
|
|
443
|
-
| `ttsEnabled` | `boolean` | No | `true` | Enables model text-to-speech output. |
|
|
444
|
-
| `enableLipsync` | `boolean` | No | `false` | Requests blendshape payloads for facial animation. |
|
|
445
|
-
| `blendshapeConfig.format` | `"arkit" \| "mha"` | No | `"mha"` | Blendshape output format. |
|
|
446
|
-
| `blendshapeConfig.frames_buffer_duration` | `number` | No | server-defined | Buffering hint for audio/blendshape synchronization. |
|
|
447
|
-
| `actionConfig` | object | No | `undefined` | Action and scene-context metadata (actions, characters, objects, attention object). |
|
|
448
|
-
| `dynamicInfo` | `string` | No | `undefined` | Dynamic contextual information about the current situation sent to the LLM. |
|
|
449
|
-
| `keepInContext` | `boolean` | No | `false` | Keep dynamic info in context as a static prompt. When true, persists throughout session; when false, allows updates via updateContext() or updateDynamicInfo(). |
|
|
450
|
-
| `logRtviMessages` | `boolean` | No | `true` | Log incoming and outgoing decoded RTVI data messages to the browser console. Set to `false` to disable. |
|
|
451
|
-
|
|
452
|
-
### Transport Options
|
|
453
|
-
|
|
454
|
-
The SDK supports two transport layers, selectable via the `transport` config field.
|
|
455
|
-
|
|
456
|
-
#### `"livekit"` (default)
|
|
457
|
-
|
|
458
|
-
Uses WebRTC via LiveKit. This is the default and is recommended for most use cases.
|
|
459
|
-
|
|
460
|
-
- Full duplex audio with echo cancellation and noise suppression
|
|
461
|
-
- Hardware-accelerated media pipeline in the browser
|
|
462
|
-
- Supports video, camera, and screen share
|
|
463
|
-
- Requires WebRTC support in the browser (all modern browsers)
|
|
464
|
-
|
|
465
|
-
#### `"websocket"` _(new in v1.4.0)_
|
|
466
|
-
|
|
467
|
-
Uses a WebSocket connection via the [Pipecat RTVI](https://github.com/pipecat-ai/pipecat) protocol. Useful when WebRTC is unavailable or undesirable (e.g. certain network environments, proxied deployments, or server-side rendering contexts).
|
|
468
|
-
|
|
469
|
-
- Audio over WebSocket — no WebRTC peer connection required
|
|
470
|
-
- Same event model and `ConvaiClient` API as the LiveKit transport
|
|
471
|
-
- Lipsync (`enableLipsync`) is supported
|
|
472
|
-
- Video, camera, and screen share are not available
|
|
473
|
-
- Slightly higher latency than WebRTC in typical network conditions
|
|
474
|
-
|
|
475
|
-
**Bundling — opt-in subpath import**
|
|
476
|
-
|
|
477
|
-
The WebSocket transport depends on `@pipecat-ai/client-js` and `@pipecat-ai/websocket-transport`. These packages are loaded lazily inside `ConvaiClient` and will not appear in your bundle unless you explicitly import the `@convai/web-sdk/vanilla/websocket` subpath. This keeps the default LiveKit-only bundle free of pipecat code.
|
|
478
|
-
|
|
479
|
-
When using the WebSocket transport, add one extra import alongside your normal SDK import:
|
|
480
|
-
|
|
481
|
-
```ts
|
|
482
|
-
// Opt in to the WebSocket transport (pulls pipecat into your bundle)
|
|
483
|
-
import "@convai/web-sdk/vanilla/websocket";
|
|
484
|
-
import { ConvaiClient } from "@convai/web-sdk/vanilla";
|
|
485
|
-
|
|
486
|
-
const client = new ConvaiClient({
|
|
487
|
-
apiKey: "...",
|
|
488
|
-
characterId: "...",
|
|
489
|
-
transport: "websocket",
|
|
490
|
-
});
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
If you only use LiveKit transport, omit the subpath import entirely:
|
|
494
|
-
|
|
495
|
-
```ts
|
|
496
|
-
// LiveKit only — pipecat packages are excluded from the bundle
|
|
497
|
-
import { ConvaiClient } from "@convai/web-sdk/vanilla";
|
|
498
|
-
|
|
499
|
-
const client = new ConvaiClient({
|
|
500
|
-
apiKey: "...",
|
|
501
|
-
characterId: "...",
|
|
502
|
-
// transport defaults to "livekit"
|
|
503
|
-
});
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
**React usage:**
|
|
507
|
-
|
|
508
|
-
```tsx
|
|
509
|
-
import "@convai/web-sdk/vanilla/websocket"; // add once, e.g. in your entry file
|
|
510
|
-
import { useConvaiClient } from "@convai/web-sdk";
|
|
511
|
-
|
|
512
|
-
const client = useConvaiClient({
|
|
513
|
-
apiKey: "...",
|
|
514
|
-
characterId: "...",
|
|
515
|
-
transport: "websocket",
|
|
73
|
+
void client.disconnect();
|
|
516
74
|
});
|
|
517
75
|
```
|
|
518
76
|
|
|
519
|
-
Everything else — events, `sendUserTextMessage`, `updateContext`, `actionConfig`, lipsync — works identically regardless of transport. You can switch between `"livekit"` and `"websocket"` by changing one field (and adding or removing the subpath import).
|
|
520
|
-
|
|
521
77
|
---
|
|
522
78
|
|
|
523
|
-
##
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
79
|
+
## Documentation
|
|
80
|
+
|
|
81
|
+
Full documentation is at **[docs.convai.com](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk)**.
|
|
82
|
+
|
|
83
|
+
| Guide | Description |
|
|
84
|
+
|---|---|
|
|
85
|
+
| [Quick Start](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | First working integration in under 5 minutes |
|
|
86
|
+
| [Configuration](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | All `ConvaiConfig` options |
|
|
87
|
+
| [React Integration](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | `useConvaiClient`, `ConvaiWidget`, and React-specific patterns |
|
|
88
|
+
| [Vanilla JS](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | `ConvaiClient`, `createConvaiWidget`, and audio setup |
|
|
89
|
+
| [Events](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | Full event reference — `botReady`, `stateChange`, `messagesChange`, `interactionCreated`, and more |
|
|
90
|
+
| [Context Management](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | Dynamic context, `updateContext`, file upload, session management |
|
|
91
|
+
| [Emotions](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | Per-turn emotion detection with provider options |
|
|
92
|
+
| [Lipsync](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | ARKit / MetaHuman blendshape streams and `BlendshapeQueue` API |
|
|
93
|
+
| [Actions](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | Trigger character behaviors and scene actions |
|
|
94
|
+
| [Memory](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | Long-term memory scoped to end users |
|
|
95
|
+
| [Audio & Video](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | Microphone, camera, screen share controls |
|
|
96
|
+
| [Error Handling](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | `error`, `disconnect`, `serverResponse`, retry patterns |
|
|
97
|
+
| [Auth Tokens](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | Server-side token exchange for production |
|
|
98
|
+
| [WebSocket Transport](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) | Alternative transport for WebRTC-constrained environments |
|
|
530
99
|
|
|
531
|
-
|
|
100
|
+
---
|
|
532
101
|
|
|
533
|
-
|
|
102
|
+
## Package entry points
|
|
534
103
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
-
|
|
538
|
-
-
|
|
539
|
-
-
|
|
540
|
-
-
|
|
541
|
-
-
|
|
542
|
-
-
|
|
543
|
-
- **`InteractionCreated`** — Payload of the `interactionCreated` event. Contains `interactionId` and `characterSessionId`. See [Interaction lifecycle](#interaction-lifecycle-interactioncreated).
|
|
104
|
+
| Import path | Contents |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `@convai/web-sdk` | React hooks, components, and re-exported core types |
|
|
107
|
+
| `@convai/web-sdk/react` | Same as default (React-explicit alias) |
|
|
108
|
+
| `@convai/web-sdk/vanilla` | `ConvaiClient`, `createConvaiWidget`, `AudioRenderer` |
|
|
109
|
+
| `@convai/web-sdk/core` | Framework-agnostic `ConvaiClient`, managers, and all types |
|
|
110
|
+
| `@convai/web-sdk/lipsync-helpers` | Blendshape format utilities and queue helpers |
|
|
111
|
+
| `@convai/web-sdk/vanilla/websocket` | **Opt-in.** Registers the WebSocket transport. Import alongside `/vanilla` when using `transport: "websocket"`. |
|
|
544
112
|
|
|
545
|
-
|
|
113
|
+
---
|
|
546
114
|
|
|
547
|
-
|
|
548
|
-
import type { IConvaiClient, ConvaiClientState, ConvaiConfig, ConvaiMetrics } from "@convai/web-sdk";
|
|
549
|
-
// or from "@convai/web-sdk/core" when using core-only
|
|
550
|
-
```
|
|
115
|
+
## WebSocket transport
|
|
551
116
|
|
|
552
|
-
|
|
117
|
+
The default transport is WebRTC (LiveKit). A WebSocket-based transport is available for environments where WebRTC is unavailable or undesirable.
|
|
553
118
|
|
|
554
119
|
```ts
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
### Properties
|
|
559
|
-
|
|
560
|
-
| Property | Type | Description |
|
|
561
|
-
| ----------------------- | ---------------------------- | -------------------------------------------------------- |
|
|
562
|
-
| `state` | `ConvaiClientState` | Real-time connection/activity state. |
|
|
563
|
-
| `connectionType` | `"audio" \| "video" \| null` | Active transport mode. |
|
|
564
|
-
| `apiKey` | `string \| null` | Active API key. |
|
|
565
|
-
| `characterId` | `string \| null` | Active character ID. |
|
|
566
|
-
| `speakerId` | `string \| null` | Resolved speaker identity. |
|
|
567
|
-
| `room` | `Room` | Internal LiveKit room instance. |
|
|
568
|
-
| `chatMessages` | `ChatMessage[]` | Conversation message store. |
|
|
569
|
-
| `userTranscription` | `string` | Current non-final voice transcription text. |
|
|
570
|
-
| `characterSessionId` | `string \| null` | Server conversation session identifier. |
|
|
571
|
-
| `isBotReady` | `boolean` | Character readiness flag. |
|
|
572
|
-
| `audioControls` | `AudioControls` | Microphone controls. |
|
|
573
|
-
| `videoControls` | `VideoControls` | Camera controls. |
|
|
574
|
-
| `screenShareControls` | `ScreenShareControls` | Screen sharing controls. |
|
|
575
|
-
| `blendshapeQueue` | `BlendshapeQueue` | Buffer queue for lipsync frames. |
|
|
576
|
-
| `conversationSessionId` | `number` | Incremental turn session ID used by conversation events. |
|
|
577
|
-
| `memoryManager` | `MemoryManager \| null` | Long-term memory API manager. See [Memory API docs](./docs/MEMORY_API.md). |
|
|
578
|
-
|
|
579
|
-
### Methods
|
|
580
|
-
|
|
581
|
-
| Method | Signature | Description |
|
|
582
|
-
| ---------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
583
|
-
| `connect` | `(config?: ConvaiConfig) => Promise<void>` | Connect using passed config or stored config. |
|
|
584
|
-
| `disconnect` | `() => Promise<void>` | Disconnect and release session resources. |
|
|
585
|
-
| `reconnect` | `() => Promise<void>` | Disconnect then connect with stored config. |
|
|
586
|
-
| `resetSession` | `() => void` | Reset character session and clear conversation history. |
|
|
587
|
-
| `sendUserTextMessage` | `(text: string) => void` | Send text message to character. |
|
|
588
|
-
| `sendTriggerMessage` | `(triggerName?: string, triggerMessage?: string) => void` | Send trigger/action message. |
|
|
589
|
-
| `sendInterruptMessage` | `() => void` | Interrupt current bot response. |
|
|
590
|
-
| `resetIdleTimer` | `() => void` | Reset the server-side idle timer to prevent disconnection. |
|
|
591
|
-
| `updateTemplateKeys` | `(templateKeys: Record<string, string>) => void` | Update runtime template variables. |
|
|
592
|
-
| `updateDynamicInfo` | `(dynamicInfo: string) => void` | Update dynamic context with a text description. |
|
|
593
|
-
| `toggleTts` | `(enabled: boolean) => void` | Enable/disable TTS for subsequent responses. |
|
|
594
|
-
| `on` | `(event: string, callback: (...args: any[]) => void) => () => void` | Subscribe to an event and receive an unsubscribe function. |
|
|
595
|
-
| `off` | `(event: string, callback: (...args: any[]) => void) => void` | Remove a specific listener. |
|
|
596
|
-
|
|
597
|
-
### Common event names and payloads
|
|
598
|
-
|
|
599
|
-
| Event | Payload | Notes |
|
|
600
|
-
| ------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
601
|
-
| `stateChange` | `ConvaiClientState` | Any state transition. |
|
|
602
|
-
| `message` | `ChatMessage` | Last message whenever `messagesChange` updates. |
|
|
603
|
-
| `messagesChange` | `ChatMessage[]` | Full message array update. |
|
|
604
|
-
| `userTranscriptionChange` | `string` | Live user speech text updates. |
|
|
605
|
-
| `speakingChange` | `boolean` | Bot speaking started/stopped. |
|
|
606
|
-
| `botReady` | `void` | Bot can now receive interaction. |
|
|
607
|
-
| `connect` | `void` | Client connected. |
|
|
608
|
-
| `disconnect` | `void` | Client disconnected. |
|
|
609
|
-
| `error` | `unknown` | Error surfaced by client. |
|
|
610
|
-
| `conversationStart` | `{ sessionId, userMessage, timestamp }` | Conversation turn started. |
|
|
611
|
-
| `turnEnd` | `{ sessionId, duration, timestamp }` | Server signaled end of turn (bot stopped speaking). Same semantics as `BlendshapeQueue.hasReceivedEndSignal()`. |
|
|
612
|
-
| `blendshapes` | `unknown` | Incoming blendshape chunk payload. |
|
|
613
|
-
| `blendshapeStatsReceived` | `unknown` | End-of-turn blendshape stats marker. |
|
|
614
|
-
| `metrics` | `Record<string, unknown>` | Metrics data received from server. Multiple metrics events may occur per conversation. |
|
|
615
|
-
| `idleWarning` | `{ remainingSeconds: number \| null }` | Server warns that the session will be disconnected due to inactivity. `remainingSeconds` is the countdown until disconnection. Call `resetIdleTimer()` on any user activity to keep the session alive. |
|
|
616
|
-
| `llmNoResponse` | `void` | LLM explicitly chose not to respond (e.g. via an abstain tool call). Use this to clear any "thinking" indicator without expecting a reply. |
|
|
617
|
-
| `serverResponse` | `ServerResponse` | Server acknowledgment for every client-sent message. Contains `event_type` (which message triggered it), `status` (`"success"` \| `"error"` \| `"processing"` \| `"pending"`), `message` (human-readable description), and `extras` (event-specific data such as token counts for `context-update`). See [Server acknowledgments](#server-acknowledgments-serverresponse). |
|
|
618
|
-
| `interactionCreated` | `InteractionCreated` | Server assigned an interaction ID to this session. Contains `interactionId` and `characterSessionId`. Fire-once per session; useful for analytics and session tracking. See [Interaction lifecycle](#interaction-lifecycle-interactioncreated). |
|
|
619
|
-
|
|
620
|
-
### Control manager APIs
|
|
621
|
-
|
|
622
|
-
#### `audioControls`
|
|
623
|
-
|
|
624
|
-
Properties:
|
|
625
|
-
|
|
626
|
-
- `isAudioEnabled`
|
|
627
|
-
- `isAudioMuted`
|
|
628
|
-
- `audioLevel`
|
|
629
|
-
|
|
630
|
-
Methods:
|
|
631
|
-
|
|
632
|
-
- `enableAudio()`
|
|
633
|
-
- `disableAudio()`
|
|
634
|
-
- `muteAudio()`
|
|
635
|
-
- `unmuteAudio()`
|
|
636
|
-
- `toggleAudio()`
|
|
637
|
-
- `setAudioDevice(deviceId)`
|
|
638
|
-
- `getAudioDevices()`
|
|
639
|
-
- `startAudioLevelMonitoring()`
|
|
640
|
-
- `stopAudioLevelMonitoring()`
|
|
641
|
-
- `on("audioStateChange", callback)`
|
|
642
|
-
- `off("audioStateChange", callback)`
|
|
643
|
-
|
|
644
|
-
#### `videoControls`
|
|
645
|
-
|
|
646
|
-
Properties:
|
|
647
|
-
|
|
648
|
-
- `isVideoEnabled`
|
|
649
|
-
- `isVideoHidden`
|
|
650
|
-
|
|
651
|
-
Methods:
|
|
652
|
-
|
|
653
|
-
- `enableVideo()`
|
|
654
|
-
- `disableVideo()`
|
|
655
|
-
- `hideVideo()`
|
|
656
|
-
- `showVideo()`
|
|
657
|
-
- `toggleVideo()`
|
|
658
|
-
- `setVideoDevice(deviceId)`
|
|
659
|
-
- `getVideoDevices()`
|
|
660
|
-
- `setVideoQuality("low" | "medium" | "high")`
|
|
661
|
-
- `on("videoStateChange", callback)`
|
|
662
|
-
- `off("videoStateChange", callback)`
|
|
663
|
-
|
|
664
|
-
#### `screenShareControls`
|
|
665
|
-
|
|
666
|
-
Properties:
|
|
667
|
-
|
|
668
|
-
- `isScreenShareEnabled`
|
|
669
|
-
- `isScreenShareActive`
|
|
670
|
-
|
|
671
|
-
Methods:
|
|
672
|
-
|
|
673
|
-
- `enableScreenShare()`
|
|
674
|
-
- `disableScreenShare()`
|
|
675
|
-
- `toggleScreenShare()`
|
|
676
|
-
- `enableScreenShareWithAudio()`
|
|
677
|
-
- `getScreenShareTracks()`
|
|
678
|
-
- `on("screenShareStateChange", callback)`
|
|
679
|
-
- `off("screenShareStateChange", callback)`
|
|
120
|
+
// Add this import once (e.g. in your app entry file)
|
|
121
|
+
import '@convai/web-sdk/vanilla/websocket';
|
|
122
|
+
import { ConvaiClient } from '@convai/web-sdk/vanilla';
|
|
680
123
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
The `updateDynamicInfo` method allows you to send real-time context to the character using a simple text description.
|
|
684
|
-
|
|
685
|
-
#### Basic usage
|
|
686
|
-
|
|
687
|
-
```tsx
|
|
688
|
-
// Simple text description
|
|
689
|
-
convaiClient.updateDynamicInfo("Player health is low");
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
#### Advanced usage with detailed context
|
|
693
|
-
|
|
694
|
-
```tsx
|
|
695
|
-
// Game state example
|
|
696
|
-
convaiClient.updateDynamicInfo(
|
|
697
|
-
"Player is in combat with 25% health and 40% stamina at battlefield location, 3 enemies nearby, equipped with sword"
|
|
698
|
-
);
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
#### Using in connect config
|
|
702
|
-
|
|
703
|
-
```tsx
|
|
704
|
-
const config: ConvaiConfig = {
|
|
705
|
-
apiKey: 'your-api-key',
|
|
706
|
-
characterId: 'your-character-id',
|
|
707
|
-
dynamicInfo: "Initial game state: Player at spawn point, level 1, tutorial not complete",
|
|
708
|
-
keepInContext: false, // Allow updates via updateDynamicInfo() (default behavior)
|
|
709
|
-
// Set to true to make dynamicInfo behave as a static prompt for the session
|
|
710
|
-
// Other config fields...
|
|
711
|
-
};
|
|
712
|
-
|
|
713
|
-
await convaiClient.connect(config);
|
|
714
|
-
```
|
|
715
|
-
|
|
716
|
-
#### Real-time updates example
|
|
717
|
-
|
|
718
|
-
```tsx
|
|
719
|
-
// Update context as game state changes
|
|
720
|
-
function updateCharacterContext(player: Player) {
|
|
721
|
-
const contextText = `Player health: ${player.health}%, mana: ${player.mana}%, location: ${player.location}, status: ${player.status}`;
|
|
722
|
-
|
|
723
|
-
convaiClient.updateDynamicInfo(contextText);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// Call whenever game state changes
|
|
727
|
-
player.on('stateChange', () => updateCharacterContext(player));
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
### Context management modes
|
|
731
|
-
|
|
732
|
-
There are three ways to manage the LLM's runtime context, each suited to a different update pattern.
|
|
733
|
-
|
|
734
|
-
#### Mode 1 — Static prompt via `keepInContext: true`
|
|
735
|
-
|
|
736
|
-
When you set `keepInContext: true` in the connect config (or pass `keep_in_context: true` in the `dynamic_info` payload), the `text` field is treated as a **static system prompt** for the lifetime of that WebRTC connection. The LLM receives it on every turn without you needing to resend it.
|
|
737
|
-
|
|
738
|
-
```tsx
|
|
739
|
-
await convaiClient.connect({
|
|
740
|
-
apiKey: '...',
|
|
741
|
-
characterId: '...',
|
|
742
|
-
dynamicInfo: "This NPC is a blacksmith who only discusses weapons and armor.",
|
|
743
|
-
keepInContext: true, // persists as a static prompt for this session
|
|
744
|
-
});
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
> If you disconnect and reconnect, the static prompt is cleared — you must pass it again in the new `connect()` call.
|
|
748
|
-
|
|
749
|
-
#### Mode 2 — Mutable context via `keepInContext: false` (default)
|
|
750
|
-
|
|
751
|
-
When `keepInContext` is `false` (the default), the dynamic info is **not** kept as a standing prompt. You can overwrite or clear it at any time by calling `updateDynamicInfo()` again, or by sending a `context-update` / `update-dynamic-info` message directly.
|
|
752
|
-
|
|
753
|
-
```tsx
|
|
754
|
-
// Initial context at connect time (will be replaceable)
|
|
755
|
-
await convaiClient.connect({
|
|
124
|
+
const client = new ConvaiClient({
|
|
756
125
|
apiKey: '...',
|
|
757
126
|
characterId: '...',
|
|
758
|
-
|
|
759
|
-
keepInContext: false,
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
// Later — overwrite with fresh state
|
|
763
|
-
convaiClient.updateDynamicInfo("Player just entered the dungeon, health 80%.");
|
|
764
|
-
|
|
765
|
-
// Or clear it entirely
|
|
766
|
-
convaiClient.updateDynamicInfo("");
|
|
767
|
-
```
|
|
768
|
-
|
|
769
|
-
#### Mode 3 — Fine-grained context patches via `updateContext()`
|
|
770
|
-
|
|
771
|
-
`updateContext()` gives you surgical control over the system instruction. It supports three modes and a `run_llm` flag that controls whether the LLM is triggered after the update.
|
|
772
|
-
|
|
773
|
-
| Field | Values | Description |
|
|
774
|
-
|----------------------------|-----------------------------------------|--------------------------------------------------------------|
|
|
775
|
-
| `text` | `string` | The context text to apply. Required unless `mode` is `"reset"` or only `current_attention_object` is being updated. |
|
|
776
|
-
| `mode` | `"append"` \| `"replace"` \| `"reset"` | How the text is applied to the existing context. Defaults to `"append"`. |
|
|
777
|
-
| `run_llm` | `"true"` \| `"false"` \| `"auto"` | `"true"` runs the LLM immediately; `"false"` only updates the prompt; `"auto"` lets the LLM decide. Defaults to `"auto"`. |
|
|
778
|
-
| `current_attention_object` | `string` | Updates the object the character is currently focused on. Must match one of the `objects[].name` values from `actionConfig`. Pass an empty string to clear. |
|
|
779
|
-
| `remove_static` | `boolean` | Only applies when `mode` is `"reset"`. If `true`, also clears the static context (the `dynamicInfo` set at connect time). Defaults to `false`. |
|
|
780
|
-
|
|
781
|
-
```tsx
|
|
782
|
-
// Append new information and run the LLM immediately
|
|
783
|
-
convaiClient.updateContext({
|
|
784
|
-
text: "User just completed the dragon quest and received a golden sword.",
|
|
785
|
-
mode: "append",
|
|
786
|
-
run_llm: "true",
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
// Silently replace the full context without triggering a response
|
|
790
|
-
convaiClient.updateContext({
|
|
791
|
-
text: "Game state: night-time, market district, raining.",
|
|
792
|
-
mode: "replace",
|
|
793
|
-
run_llm: "false",
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
// Clear runtime context only (preserves static context from connect-time dynamicInfo)
|
|
797
|
-
convaiClient.updateContext({
|
|
798
|
-
mode: "reset",
|
|
799
|
-
run_llm: "auto",
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
// Clear everything — both runtime and static context
|
|
803
|
-
convaiClient.updateContext({
|
|
804
|
-
mode: "reset",
|
|
805
|
-
remove_static: true,
|
|
806
|
-
run_llm: "false",
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
// Update only the attention object without touching context text
|
|
810
|
-
convaiClient.updateContext({
|
|
811
|
-
current_attention_object: "golden_sword",
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
// Clear the attention object
|
|
815
|
-
convaiClient.updateContext({
|
|
816
|
-
current_attention_object: "",
|
|
127
|
+
transport: 'websocket',
|
|
817
128
|
});
|
|
818
129
|
```
|
|
819
130
|
|
|
820
|
-
**
|
|
821
|
-
|
|
822
|
-
- `"true"` — the update is itself an event worth responding to (e.g. quest completion, NPC interaction).
|
|
823
|
-
- `"false"` — background state update the player won't notice (e.g. syncing health/location every few seconds).
|
|
824
|
-
- `"auto"` — the LLM reads the new context and decides on its own whether a response is appropriate.
|
|
131
|
+
The WebSocket packages (`@pipecat-ai/client-js`, `@pipecat-ai/websocket-transport`) are **excluded from your bundle** unless you import the `/vanilla/websocket` subpath — so LiveKit-only apps pay no bundle cost.
|
|
825
132
|
|
|
826
|
-
|
|
133
|
+
See the [WebSocket Transport guide](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk) for the full feature comparison.
|
|
827
134
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
#### Accessing metrics from client state
|
|
831
|
-
|
|
832
|
-
```tsx
|
|
833
|
-
// Access metrics array from state
|
|
834
|
-
const metrics = convaiClient.state.metrics;
|
|
835
|
-
|
|
836
|
-
// Log all metrics
|
|
837
|
-
console.log('Total metrics received:', metrics.length);
|
|
838
|
-
metrics.forEach(metric => {
|
|
839
|
-
console.log('Metric ID:', metric.id);
|
|
840
|
-
console.log('Timestamp:', metric.timestamp);
|
|
841
|
-
console.log('Data:', metric.data);
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
// Get latest metric
|
|
845
|
-
const latestMetric = metrics[metrics.length - 1];
|
|
846
|
-
if (latestMetric) {
|
|
847
|
-
console.log('Latest metric data:', latestMetric.data);
|
|
848
|
-
}
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
#### Listening to metrics events in real-time
|
|
852
|
-
|
|
853
|
-
```tsx
|
|
854
|
-
// Subscribe to metrics events
|
|
855
|
-
const unsubMetrics = convaiClient.on('metrics', (metricsData) => {
|
|
856
|
-
console.log('New metrics received:', metricsData);
|
|
857
|
-
|
|
858
|
-
// Process metrics data
|
|
859
|
-
if (metricsData.processingTime) {
|
|
860
|
-
console.log('Processing time:', metricsData.processingTime);
|
|
861
|
-
}
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
// Cleanup
|
|
865
|
-
unsubMetrics();
|
|
866
|
-
```
|
|
867
|
-
|
|
868
|
-
#### Accessing metrics in React
|
|
869
|
-
|
|
870
|
-
```tsx
|
|
871
|
-
import { useConvaiClient } from '@convai/web-sdk';
|
|
872
|
-
import { useEffect, useState } from 'react';
|
|
873
|
-
|
|
874
|
-
function MetricsDisplay() {
|
|
875
|
-
const convaiClient = useConvaiClient();
|
|
876
|
-
const [metrics, setMetrics] = useState<ConvaiMetrics[]>([]);
|
|
877
|
-
|
|
878
|
-
useEffect(() => {
|
|
879
|
-
// Subscribe to state changes
|
|
880
|
-
const unsubState = convaiClient.on('stateChange', (state) => {
|
|
881
|
-
setMetrics(state.metrics);
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
// Or subscribe to individual metrics events
|
|
885
|
-
const unsubMetrics = convaiClient.on('metrics', (metricsData) => {
|
|
886
|
-
console.log('New metrics:', metricsData);
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
return () => {
|
|
890
|
-
unsubState();
|
|
891
|
-
unsubMetrics();
|
|
892
|
-
};
|
|
893
|
-
}, [convaiClient]);
|
|
894
|
-
|
|
895
|
-
return (
|
|
896
|
-
<div>
|
|
897
|
-
<h3>Metrics ({metrics.length})</h3>
|
|
898
|
-
{metrics.map(metric => (
|
|
899
|
-
<div key={metric.id}>
|
|
900
|
-
<p>Time: {new Date(metric.timestamp).toLocaleTimeString()}</p>
|
|
901
|
-
<pre>{JSON.stringify(metric.data, null, 2)}</pre>
|
|
902
|
-
</div>
|
|
903
|
-
))}
|
|
904
|
-
</div>
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
```
|
|
908
|
-
|
|
909
|
-
#### Clearing metrics
|
|
910
|
-
|
|
911
|
-
Metrics are automatically cleared when you call `resetSession()` or `disconnect()`:
|
|
912
|
-
|
|
913
|
-
```tsx
|
|
914
|
-
// Clear conversation history and metrics
|
|
915
|
-
convaiClient.resetSession();
|
|
916
|
-
|
|
917
|
-
// Or disconnect (also clears metrics)
|
|
918
|
-
await convaiClient.disconnect();
|
|
919
|
-
```
|
|
920
|
-
|
|
921
|
-
#### Analyzing aggregated metrics
|
|
922
|
-
|
|
923
|
-
```tsx
|
|
924
|
-
function analyzeMetrics(client: IConvaiClient) {
|
|
925
|
-
const metrics = client.state.metrics;
|
|
926
|
-
|
|
927
|
-
// Calculate average processing time if present
|
|
928
|
-
const processingTimes = metrics
|
|
929
|
-
.map(m => m.data.processingTime as number)
|
|
930
|
-
.filter(t => typeof t === 'number');
|
|
931
|
-
|
|
932
|
-
if (processingTimes.length > 0) {
|
|
933
|
-
const avgTime = processingTimes.reduce((a, b) => a + b, 0) / processingTimes.length;
|
|
934
|
-
console.log('Average processing time:', avgTime.toFixed(2), 'ms');
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// Count metrics by type if available
|
|
938
|
-
const metricsByType = metrics.reduce((acc, metric) => {
|
|
939
|
-
const type = (metric.data.type as string) || 'unknown';
|
|
940
|
-
acc[type] = (acc[type] || 0) + 1;
|
|
941
|
-
return acc;
|
|
942
|
-
}, {} as Record<string, number>);
|
|
943
|
-
|
|
944
|
-
console.log('Metrics by type:', metricsByType);
|
|
945
|
-
}
|
|
946
|
-
```
|
|
947
|
-
|
|
948
|
-
### Idle management and LLM silence handling
|
|
949
|
-
|
|
950
|
-
#### Idle warnings and LLM no-response as chat messages
|
|
951
|
-
|
|
952
|
-
Both events automatically appear in `chatMessages` with distinct `type` values so you can handle them inline when rendering your message list — no extra event wiring required.
|
|
953
|
-
|
|
954
|
-
| `type` | `content` | Purpose |
|
|
955
|
-
|------------------|-----------------------------------|----------------------------------------------|
|
|
956
|
-
| `idle-warning` | Remaining seconds as string, e.g. `"45"` | Display a warning prompt in the chat |
|
|
957
|
-
| `llm-no-response`| `""` (always empty) | Signal that no reply will come; hide or skip |
|
|
958
|
-
|
|
959
|
-
```tsx
|
|
960
|
-
// React — render chat messages with idle/no-response handling
|
|
961
|
-
{chatMessages.map((msg) => {
|
|
962
|
-
if (msg.type === "idle-warning") {
|
|
963
|
-
const seconds = parseInt(msg.content, 10);
|
|
964
|
-
return (
|
|
965
|
-
<SystemNotice key={msg.id} variant="warning">
|
|
966
|
-
Session idle — disconnecting in {seconds}s.{" "}
|
|
967
|
-
<button onClick={() => convaiClient.resetIdleTimer()}>Stay connected</button>
|
|
968
|
-
</SystemNotice>
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
if (msg.type === "llm-no-response") {
|
|
973
|
-
// LLM chose not to reply — render nothing (or a subtle indicator)
|
|
974
|
-
return null;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
return <ChatBubble key={msg.id} message={msg} />;
|
|
978
|
-
})}
|
|
979
|
-
```
|
|
980
|
-
|
|
981
|
-
#### Receiving idle warnings via event
|
|
982
|
-
|
|
983
|
-
If you prefer to react outside the message list (e.g. a toast or overlay):
|
|
984
|
-
|
|
985
|
-
```ts
|
|
986
|
-
const unsubIdle = client.on("idleWarning", ({ remainingSeconds }) => {
|
|
987
|
-
if (remainingSeconds !== null) {
|
|
988
|
-
showToast(`Session will end in ${remainingSeconds}s due to inactivity.`);
|
|
989
|
-
}
|
|
990
|
-
});
|
|
991
|
-
```
|
|
992
|
-
|
|
993
|
-
#### Resetting the idle timer
|
|
994
|
-
|
|
995
|
-
Call `resetIdleTimer()` whenever user activity is detected (clicks, keystrokes, UI interactions) to keep the session alive.
|
|
996
|
-
|
|
997
|
-
```ts
|
|
998
|
-
// Reset on any click anywhere on the page
|
|
999
|
-
document.addEventListener("click", () => client.resetIdleTimer());
|
|
1000
|
-
|
|
1001
|
-
// Or reset on specific UI interactions
|
|
1002
|
-
sendButton.addEventListener("click", () => {
|
|
1003
|
-
client.resetIdleTimer();
|
|
1004
|
-
client.sendUserTextMessage(inputField.value);
|
|
1005
|
-
});
|
|
1006
|
-
```
|
|
1007
|
-
|
|
1008
|
-
#### React example — activity-aware idle management
|
|
1009
|
-
|
|
1010
|
-
```tsx
|
|
1011
|
-
useEffect(() => {
|
|
1012
|
-
const handleActivity = () => {
|
|
1013
|
-
convaiClient.resetIdleTimer();
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
window.addEventListener("click", handleActivity);
|
|
1017
|
-
window.addEventListener("keydown", handleActivity);
|
|
1018
|
-
|
|
1019
|
-
return () => {
|
|
1020
|
-
window.removeEventListener("click", handleActivity);
|
|
1021
|
-
window.removeEventListener("keydown", handleActivity);
|
|
1022
|
-
};
|
|
1023
|
-
}, [convaiClient]);
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
#### Handling LLM no-response via event
|
|
1027
|
-
|
|
1028
|
-
When the LLM deliberately abstains from replying (e.g. via an abstain tool call), the server emits `llmNoResponse`. If you are driving a custom "thinking" indicator outside of `chatMessages`, use this event to dismiss it.
|
|
1029
|
-
|
|
1030
|
-
```ts
|
|
1031
|
-
const unsubNoResponse = client.on("llmNoResponse", () => {
|
|
1032
|
-
setIsThinking(false);
|
|
1033
|
-
});
|
|
1034
|
-
```
|
|
1035
|
-
|
|
1036
|
-
### Server acknowledgments (`serverResponse`)
|
|
1037
|
-
|
|
1038
|
-
The server sends a `serverResponse` back for every message your client sends (`context-update`, `tts-toggle`, `trigger-message`, etc.). It tells you whether the server accepted or rejected the request, and for `context-update` it returns the current token budget so you can manage context size in real time.
|
|
1039
|
-
|
|
1040
|
-
The dynamic context budget is **50,000 estimated tokens** total: up to 20,000 for static context (set at connect time via `dynamicInfo`) and up to 30,000 for runtime context (`updateContext` calls). When the runtime budget fills up the server keeps the newest updates and silently trims the oldest, so track `remaining_tokens` if your application sends context at high frequency.
|
|
1041
|
-
|
|
1042
|
-
#### React
|
|
1043
|
-
|
|
1044
|
-
```tsx
|
|
1045
|
-
import { useConvaiClient } from "@convai/web-sdk";
|
|
1046
|
-
import { useState, useEffect } from "react";
|
|
1047
|
-
import type { ServerResponse } from "@convai/web-sdk";
|
|
1048
|
-
|
|
1049
|
-
function ContextMonitor() {
|
|
1050
|
-
const convaiClient = useConvaiClient({ /* config */ });
|
|
1051
|
-
const [tokenUsage, setTokenUsage] = useState<{ used: number; max: number } | null>(null);
|
|
1052
|
-
const [contextError, setContextError] = useState<string | null>(null);
|
|
1053
|
-
|
|
1054
|
-
useEffect(() => {
|
|
1055
|
-
const unsub = convaiClient.on("serverResponse", (response: ServerResponse) => {
|
|
1056
|
-
// Track token budget after every context update
|
|
1057
|
-
if (response.event_type === "context-update" && response.extras) {
|
|
1058
|
-
const { token_count, max_tokens } = response.extras;
|
|
1059
|
-
setTokenUsage({ used: token_count ?? 0, max: max_tokens ?? 50000 });
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// Surface errors to the UI
|
|
1063
|
-
if (response.status === "error") {
|
|
1064
|
-
setContextError(`[${response.event_type}] ${response.message}`);
|
|
1065
|
-
} else {
|
|
1066
|
-
setContextError(null);
|
|
1067
|
-
}
|
|
1068
|
-
});
|
|
1069
|
-
|
|
1070
|
-
return unsub;
|
|
1071
|
-
}, [convaiClient]);
|
|
1072
|
-
|
|
1073
|
-
const pushContext = () => {
|
|
1074
|
-
convaiClient.updateContext({ text: "Player entered the dungeon.", mode: "append", run_llm: "false" });
|
|
1075
|
-
};
|
|
1076
|
-
|
|
1077
|
-
const resetContext = () => {
|
|
1078
|
-
convaiClient.updateContext({ mode: "reset", remove_static: false });
|
|
1079
|
-
};
|
|
1080
|
-
|
|
1081
|
-
return (
|
|
1082
|
-
<div>
|
|
1083
|
-
<button onClick={pushContext}>Push context</button>
|
|
1084
|
-
<button onClick={resetContext}>Reset context</button>
|
|
1085
|
-
{tokenUsage && (
|
|
1086
|
-
<p>
|
|
1087
|
-
Context: {tokenUsage.used} / {tokenUsage.max} tokens
|
|
1088
|
-
{tokenUsage.used / tokenUsage.max > 0.8 && " ⚠️ approaching limit"}
|
|
1089
|
-
</p>
|
|
1090
|
-
)}
|
|
1091
|
-
{contextError && <p style={{ color: "red" }}>Error: {contextError}</p>}
|
|
1092
|
-
</div>
|
|
1093
|
-
);
|
|
1094
|
-
}
|
|
1095
|
-
```
|
|
1096
|
-
|
|
1097
|
-
#### Vanilla TypeScript
|
|
1098
|
-
|
|
1099
|
-
```ts
|
|
1100
|
-
import { ConvaiClient } from "@convai/web-sdk/core";
|
|
1101
|
-
import type { ServerResponse } from "@convai/web-sdk/core";
|
|
1102
|
-
|
|
1103
|
-
const client = new ConvaiClient({ apiKey: "...", characterId: "..." });
|
|
1104
|
-
await client.connect();
|
|
1105
|
-
|
|
1106
|
-
client.on("serverResponse", (response: ServerResponse) => {
|
|
1107
|
-
if (response.event_type === "context-update" && response.extras) {
|
|
1108
|
-
const { token_count, max_tokens, remaining_tokens } = response.extras;
|
|
1109
|
-
updateTokenBar(token_count ?? 0, max_tokens ?? 50000);
|
|
1110
|
-
|
|
1111
|
-
if ((remaining_tokens ?? Infinity) < 5000) {
|
|
1112
|
-
showWarning("Context nearing limit — oldest updates will be trimmed.");
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
if (response.status === "error") {
|
|
1117
|
-
showError(`${response.event_type} failed: ${response.message}`);
|
|
1118
|
-
|
|
1119
|
-
// Context limit exceeded — reset runtime context and retry
|
|
1120
|
-
if (response.event_type === "context-update") {
|
|
1121
|
-
client.updateContext({ mode: "reset" });
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
// Push game state silently (no LLM trigger)
|
|
1127
|
-
function syncGameState(state: GameState) {
|
|
1128
|
-
client.updateContext({
|
|
1129
|
-
text: `Health: ${state.health}%, location: ${state.location}, enemies: ${state.enemies}`,
|
|
1130
|
-
mode: "replace",
|
|
1131
|
-
run_llm: "false",
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// Notify the bot of a narrative event and trigger a response
|
|
1136
|
-
function onQuestComplete(questName: string) {
|
|
1137
|
-
client.updateContext({
|
|
1138
|
-
text: `Player just completed: ${questName}`,
|
|
1139
|
-
mode: "append",
|
|
1140
|
-
run_llm: "true",
|
|
1141
|
-
});
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
// Clear everything at scene change
|
|
1145
|
-
function onSceneChange() {
|
|
1146
|
-
client.updateContext({ mode: "reset", remove_static: false });
|
|
1147
|
-
}
|
|
1148
|
-
```
|
|
1149
|
-
|
|
1150
|
-
### Interaction lifecycle (`interactionCreated`)
|
|
1151
|
-
|
|
1152
|
-
The server fires `interactionCreated` once per session after connecting, assigning a stable `interactionId` to the conversation. Use it for analytics, crash reporting, or correlating client-side events with server-side logs.
|
|
1153
|
-
|
|
1154
|
-
#### React
|
|
1155
|
-
|
|
1156
|
-
```tsx
|
|
1157
|
-
import { useConvaiClient } from "@convai/web-sdk";
|
|
1158
|
-
import { useEffect, useRef } from "react";
|
|
1159
|
-
import type { InteractionCreated } from "@convai/web-sdk";
|
|
1160
|
-
|
|
1161
|
-
function ConvaiSession() {
|
|
1162
|
-
const convaiClient = useConvaiClient({ /* config */ });
|
|
1163
|
-
const interactionIdRef = useRef<string | null>(null);
|
|
1164
|
-
|
|
1165
|
-
useEffect(() => {
|
|
1166
|
-
const unsub = convaiClient.on("interactionCreated", ({ interactionId, characterSessionId }: InteractionCreated) => {
|
|
1167
|
-
interactionIdRef.current = interactionId;
|
|
1168
|
-
|
|
1169
|
-
// Tag your analytics session
|
|
1170
|
-
analytics.identify({ interactionId, characterSessionId });
|
|
1171
|
-
});
|
|
1172
|
-
|
|
1173
|
-
return unsub;
|
|
1174
|
-
}, [convaiClient]);
|
|
1175
|
-
|
|
1176
|
-
const handleSend = (text: string) => {
|
|
1177
|
-
convaiClient.sendUserTextMessage(text);
|
|
1178
|
-
|
|
1179
|
-
// Attach the interaction ID to any events you fire
|
|
1180
|
-
analytics.track("message_sent", { interactionId: interactionIdRef.current, text });
|
|
1181
|
-
};
|
|
1182
|
-
|
|
1183
|
-
return <YourChatUI onSend={handleSend} />;
|
|
1184
|
-
}
|
|
1185
|
-
```
|
|
1186
|
-
|
|
1187
|
-
#### Vanilla TypeScript
|
|
1188
|
-
|
|
1189
|
-
```ts
|
|
1190
|
-
import { ConvaiClient } from "@convai/web-sdk/core";
|
|
1191
|
-
import type { InteractionCreated } from "@convai/web-sdk/core";
|
|
1192
|
-
|
|
1193
|
-
const client = new ConvaiClient({ apiKey: "...", characterId: "..." });
|
|
1194
|
-
|
|
1195
|
-
let interactionId: string | null = null;
|
|
1196
|
-
|
|
1197
|
-
client.on("interactionCreated", ({ interactionId: id, characterSessionId }: InteractionCreated) => {
|
|
1198
|
-
interactionId = id;
|
|
1199
|
-
sessionStorage.setItem("convai_interaction_id", id);
|
|
1200
|
-
sessionStorage.setItem("convai_session_id", characterSessionId);
|
|
1201
|
-
});
|
|
1202
|
-
|
|
1203
|
-
await client.connect();
|
|
1204
|
-
|
|
1205
|
-
// Later — include the ID when reporting errors or events
|
|
1206
|
-
window.onerror = (msg) => {
|
|
1207
|
-
reportError({ msg, interactionId });
|
|
1208
|
-
};
|
|
1209
|
-
```
|
|
1210
|
-
|
|
1211
|
-
### Advanced core classes (`@convai/web-sdk/core`)
|
|
1212
|
-
|
|
1213
|
-
These are exported for advanced and custom pipeline use-cases.
|
|
1214
|
-
|
|
1215
|
-
#### `BlendshapeQueue`
|
|
1216
|
-
|
|
1217
|
-
Buffer for lipsync frames. Use `isConversationEnded()` for definitive end-of-conversation: it returns true only when the server has sent `blendshape-turn-stats` and either all expected frames have been consumed or the queue is empty (handles dropped frames). Use `hasReceivedEndSignal()` when you only need to know that the server signaled end (e.g. to keep playing remaining frames).
|
|
1218
|
-
|
|
1219
|
-
Methods:
|
|
1220
|
-
|
|
1221
|
-
- `addChunk(blendshapes)`
|
|
1222
|
-
- `getFrames()`
|
|
1223
|
-
- `getFrame(index)`
|
|
1224
|
-
- `getFrameWithAlpha(index)`
|
|
1225
|
-
- `consumeFrames(count)`
|
|
1226
|
-
- `hasFrames()`
|
|
1227
|
-
- `isConversationActive()`
|
|
1228
|
-
- `isConversationEnded()` — true when server signaled end and playback is complete (all frames consumed or queue empty)
|
|
1229
|
-
- `hasReceivedEndSignal()` — true when server sent `blendshape-turn-stats` (does not check frame consumption)
|
|
1230
|
-
- `startConversation()`
|
|
1231
|
-
- `startBotSpeaking()`
|
|
1232
|
-
- `stopBotSpeaking()`
|
|
1233
|
-
- `isBotSpeaking()`
|
|
1234
|
-
- `endConversation(stats?)`
|
|
1235
|
-
- `interrupt()`
|
|
1236
|
-
- `getTurnStats()`
|
|
1237
|
-
- `getFramesConsumed()`
|
|
1238
|
-
- `getTimeLeftMs()`
|
|
1239
|
-
- `isAllFramesConsumed()`
|
|
1240
|
-
- `reset()`
|
|
1241
|
-
- `getFrameAtTime(elapsedTime)`
|
|
1242
|
-
- `getDebugInfo()`
|
|
1243
|
-
|
|
1244
|
-
Properties:
|
|
1245
|
-
|
|
1246
|
-
- `length`
|
|
1247
|
-
|
|
1248
|
-
#### `MessageHandler`
|
|
1249
|
-
|
|
1250
|
-
Methods:
|
|
1251
|
-
|
|
1252
|
-
- `getBlendshapeQueue()`
|
|
1253
|
-
- `getChatMessages()`
|
|
1254
|
-
- `getUserTranscription()`
|
|
1255
|
-
- `getIsBotResponding()`
|
|
1256
|
-
- `getIsSpeaking()`
|
|
1257
|
-
- `setRoom(room)`
|
|
1258
|
-
- `reset()`
|
|
1259
|
-
- inherited event APIs from `EventEmitter`:
|
|
1260
|
-
- `on(event, callback)`
|
|
1261
|
-
- `off(event, callback)`
|
|
1262
|
-
|
|
1263
|
-
#### `EventEmitter`
|
|
1264
|
-
|
|
1265
|
-
Methods:
|
|
1266
|
-
|
|
1267
|
-
- `on(event, callback)`
|
|
1268
|
-
- `off(event, callback)`
|
|
1269
|
-
- `emit(event, ...args)`
|
|
1270
|
-
- `removeAllListeners()`
|
|
1271
|
-
- `listenerCount(event)`
|
|
1272
|
-
|
|
1273
|
-
## 9. Memory Management API
|
|
1274
|
-
|
|
1275
|
-
The Convai SDK includes a comprehensive Memory Management API that enables long-term memory storage for characters. Memories are scoped to a `(character_id, end_user_id)` pair, allowing each user to have personalized experiences that persist across conversation sessions.
|
|
1276
|
-
|
|
1277
|
-
### Quick Start
|
|
1278
|
-
|
|
1279
|
-
```typescript
|
|
1280
|
-
import { ConvaiClient } from "@convai/web-sdk/core";
|
|
1281
|
-
|
|
1282
|
-
const client = new ConvaiClient({
|
|
1283
|
-
apiKey: 'your-api-key',
|
|
1284
|
-
characterId: 'your-character-id',
|
|
1285
|
-
endUserId: 'user@example.com', // Required for memory operations
|
|
1286
|
-
});
|
|
1287
|
-
|
|
1288
|
-
await client.connect();
|
|
1289
|
-
|
|
1290
|
-
// Access memory manager
|
|
1291
|
-
const memoryManager = client.memoryManager;
|
|
1292
|
-
|
|
1293
|
-
if (memoryManager) {
|
|
1294
|
-
// Add memories
|
|
1295
|
-
await memoryManager.addMemories([
|
|
1296
|
-
'User prefers outdoor activities',
|
|
1297
|
-
'User is allergic to peanuts'
|
|
1298
|
-
]);
|
|
1299
|
-
|
|
1300
|
-
// List memories
|
|
1301
|
-
const memories = await memoryManager.listMemories();
|
|
1302
|
-
console.log(`Total memories: ${memories.total_count}`);
|
|
1303
|
-
|
|
1304
|
-
// Get a specific memory
|
|
1305
|
-
const memory = await memoryManager.getMemory(memoryId);
|
|
1306
|
-
|
|
1307
|
-
// Delete a memory
|
|
1308
|
-
await memoryManager.deleteMemory(memoryId);
|
|
1309
|
-
}
|
|
1310
|
-
```
|
|
1311
|
-
|
|
1312
|
-
### Requirements
|
|
1313
|
-
|
|
1314
|
-
The `memoryManager` is available when:
|
|
1315
|
-
- You provide either an `apiKey` or `authToken` in the config
|
|
1316
|
-
- You provide an `endUserId` in the config
|
|
1317
|
-
|
|
1318
|
-
### Memory Manager Methods
|
|
1319
|
-
|
|
1320
|
-
| Method | Description |
|
|
1321
|
-
|--------|-------------|
|
|
1322
|
-
| `addMemories(memories: string[])` | Add one or more memory strings |
|
|
1323
|
-
| `listMemories(options?)` | List memories with pagination |
|
|
1324
|
-
| `getMemory(memoryId: string)` | Fetch a single memory by ID |
|
|
1325
|
-
| `deleteMemory(memoryId: string)` | Delete a single memory |
|
|
1326
|
-
| `deleteAllMemories()` | Delete all memories for current user/character |
|
|
1327
|
-
| `setEndUserId(endUserId: string)` | Switch to a different user context |
|
|
1328
|
-
| `setCharacterId(characterId: string)` | Switch to a different character context |
|
|
1329
|
-
|
|
1330
|
-
### Standalone Usage
|
|
1331
|
-
|
|
1332
|
-
You can also use the `MemoryManager` independently:
|
|
1333
|
-
|
|
1334
|
-
```typescript
|
|
1335
|
-
import { MemoryManager } from "@convai/web-sdk/core";
|
|
1336
|
-
|
|
1337
|
-
const memoryManager = new MemoryManager(
|
|
1338
|
-
'your-api-key',
|
|
1339
|
-
'your-character-id',
|
|
1340
|
-
'user@example.com'
|
|
1341
|
-
);
|
|
1342
|
-
|
|
1343
|
-
const result = await memoryManager.addMemories(['User likes coffee']);
|
|
1344
|
-
```
|
|
1345
|
-
|
|
1346
|
-
### Complete Documentation
|
|
1347
|
-
|
|
1348
|
-
For detailed documentation, examples, and API reference, see:
|
|
1349
|
-
- **[Memory API Documentation](./docs/MEMORY_API.md)** - Complete guide with examples
|
|
1350
|
-
- **[Memory API Usage Examples](./examples/memory-api-usage.ts)** - Runnable code examples
|
|
1351
|
-
|
|
1352
|
-
### Memory Types
|
|
1353
|
-
|
|
1354
|
-
All memory-related types are exported from the core package:
|
|
1355
|
-
|
|
1356
|
-
```typescript
|
|
1357
|
-
import type {
|
|
1358
|
-
Memory,
|
|
1359
|
-
MemoryAddResponse,
|
|
1360
|
-
MemoryListResponse,
|
|
1361
|
-
MemoryGetResponse,
|
|
1362
|
-
MemoryDeleteResponse,
|
|
1363
|
-
MemoryDeleteAllResponse,
|
|
1364
|
-
MemoryError,
|
|
1365
|
-
} from "@convai/web-sdk/core";
|
|
1366
|
-
```
|
|
1367
|
-
|
|
1368
|
-
## 10. Message Semantics and Turn Completion
|
|
1369
|
-
|
|
1370
|
-
### `ChatMessage` model
|
|
1371
|
-
|
|
1372
|
-
`ChatMessage` includes:
|
|
1373
|
-
|
|
1374
|
-
- `id`
|
|
1375
|
-
- `type`
|
|
1376
|
-
- `content`
|
|
1377
|
-
- `timestamp`
|
|
1378
|
-
- `isStreaming?` — `true` while the message is still streaming (mutable), `false` when finalized
|
|
1379
|
-
|
|
1380
|
-
Supported message `type` values include:
|
|
1381
|
-
|
|
1382
|
-
- `user`
|
|
1383
|
-
- `convai`
|
|
1384
|
-
- `emotion`
|
|
1385
|
-
- `behavior-tree`
|
|
1386
|
-
- `action`
|
|
1387
|
-
- `user-transcription`
|
|
1388
|
-
- `bot-llm-text`
|
|
1389
|
-
- `bot-emotion`
|
|
1390
|
-
- `user-llm-text`
|
|
1391
|
-
- `interrupt-bot`
|
|
1392
|
-
- `idle-warning` — server idle-timeout warning; `content` holds remaining seconds as a numeric string (e.g. `"45"`). Render differently from normal bot messages to prompt user activity.
|
|
1393
|
-
- `llm-no-response` — LLM deliberately chose not to respond; `content` is always `""`. Use the type to hide or suppress the message in your UI rather than showing an empty bubble.
|
|
1394
|
-
|
|
1395
|
-
### Recommended way to detect response completion
|
|
1396
|
-
|
|
1397
|
-
Use events instead of checking `isStreaming`:
|
|
1398
|
-
|
|
1399
|
-
- `turnEnd` for the server turn-end signal (bot stopped speaking; same as `hasReceivedEndSignal()`)
|
|
1400
|
-
- `blendshapeStatsReceived` as additional completion marker when lipsync/animation output is enabled
|
|
1401
|
-
|
|
1402
|
-
When driving lipsync from `BlendshapeQueue`, use `blendshapeQueue.isConversationEnded()` for definitive end-of-conversation. It returns true only when the server has signaled end and playback is complete (all expected frames consumed or queue empty). Call `blendshapeQueue.reset()` and your `onConversationEnded` when it becomes true. Use `hasReceivedEndSignal()` only when you need the raw server signal (e.g. to decide whether to keep playing remaining frames).
|
|
1403
|
-
|
|
1404
|
-
Example:
|
|
1405
|
-
|
|
1406
|
-
```ts
|
|
1407
|
-
type TurnCompletionOptions = {
|
|
1408
|
-
expectBlendshapes: boolean;
|
|
1409
|
-
onComplete: () => void;
|
|
1410
|
-
};
|
|
1411
|
-
|
|
1412
|
-
function subscribeTurnCompletion(client: any, options: TurnCompletionOptions) {
|
|
1413
|
-
let spokenDone = false;
|
|
1414
|
-
let animationDone = !options.expectBlendshapes;
|
|
1415
|
-
|
|
1416
|
-
const invokeOnCompleteIfReady = () => {
|
|
1417
|
-
if (spokenDone && animationDone) {
|
|
1418
|
-
options.onComplete();
|
|
1419
|
-
}
|
|
1420
|
-
};
|
|
1421
|
-
|
|
1422
|
-
const unsubTurnEnd = client.on("turnEnd", () => {
|
|
1423
|
-
spokenDone = true;
|
|
1424
|
-
invokeOnCompleteIfReady();
|
|
1425
|
-
});
|
|
1426
|
-
|
|
1427
|
-
const unsubBlendshapeStats = client.on("blendshapeStatsReceived", () => {
|
|
1428
|
-
animationDone = true;
|
|
1429
|
-
invokeOnCompleteIfReady();
|
|
1430
|
-
});
|
|
1431
|
-
|
|
1432
|
-
return () => {
|
|
1433
|
-
unsubTurnEnd();
|
|
1434
|
-
unsubBlendshapeStats();
|
|
1435
|
-
};
|
|
1436
|
-
}
|
|
1437
|
-
```
|
|
1438
|
-
|
|
1439
|
-
When to use both signals: You only need to wait for both `turnEnd` and `blendshapeStatsReceived` when you use lipsync. Set `expectBlendshapes: false` when you do not use facial animation; then `animationDone` is effectively always true and completion runs as soon as `turnEnd` fires. Set `expectBlendshapes: true` when you drive lipsync from the queue; speech and blendshape data are separate pipelines and can finish in either order, so waiting for both ensures "turn complete" means both speech and animation are done before you run `onComplete`.
|
|
1440
|
-
|
|
1441
|
-
## 11. React API Reference
|
|
1442
|
-
|
|
1443
|
-
### `useConvaiClient(config?)`
|
|
1444
|
-
|
|
1445
|
-
Import:
|
|
1446
|
-
|
|
1447
|
-
```tsx
|
|
1448
|
-
import { useConvaiClient } from "@convai/web-sdk";
|
|
1449
|
-
```
|
|
1450
|
-
|
|
1451
|
-
Returns full `IConvaiClient` plus React-friendly reactive fields:
|
|
1452
|
-
|
|
1453
|
-
- `activity`
|
|
1454
|
-
- `chatMessages`
|
|
1455
|
-
- `isAudioMuted`
|
|
1456
|
-
- `isVideoEnabled`
|
|
1457
|
-
- `isScreenShareActive`
|
|
1458
|
-
|
|
1459
|
-
### `ConvaiWidget`
|
|
1460
|
-
|
|
1461
|
-
Import:
|
|
1462
|
-
|
|
1463
|
-
```tsx
|
|
1464
|
-
import { ConvaiWidget } from "@convai/web-sdk";
|
|
1465
|
-
```
|
|
1466
|
-
|
|
1467
|
-
Props:
|
|
1468
|
-
|
|
1469
|
-
| Prop | Type | Default | Description |
|
|
1470
|
-
| ------------------ | --------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------ |
|
|
1471
|
-
| `convaiClient` | `IConvaiClient & { activity?: string; isAudioMuted: boolean; isVideoEnabled: boolean; isScreenShareActive: boolean }` | required | Client instance returned by `useConvaiClient`. |
|
|
1472
|
-
| `showVideo` | `boolean` | `true` | Shows video toggle in settings if connection type is video. |
|
|
1473
|
-
| `showScreenShare` | `boolean` | `true` | Shows screen-share toggle in settings if connection type is video. |
|
|
1474
|
-
| `defaultVoiceMode` | `boolean` | `true` | Opens in voice mode on first widget session. |
|
|
1475
|
-
|
|
1476
|
-
### `useCharacterInfo(characterId?, apiKey?)`
|
|
1477
|
-
|
|
1478
|
-
Returns:
|
|
1479
|
-
|
|
1480
|
-
- `name`
|
|
1481
|
-
- `image`
|
|
1482
|
-
- `isLoading`
|
|
1483
|
-
- `error`
|
|
1484
|
-
|
|
1485
|
-
### `useLocalCameraTrack()`
|
|
1486
|
-
|
|
1487
|
-
Returns a LiveKit `TrackReferenceOrPlaceholder` for local camera rendering in custom React video UIs.
|
|
1488
|
-
|
|
1489
|
-
### React audio utility exports
|
|
1490
|
-
|
|
1491
|
-
- `AudioRenderer` from LiveKit React components
|
|
1492
|
-
- `AudioContext` from LiveKit React components
|
|
1493
|
-
|
|
1494
|
-
## 12. Vanilla API Reference
|
|
1495
|
-
|
|
1496
|
-
### `createConvaiWidget(container, options)`
|
|
1497
|
-
|
|
1498
|
-
```ts
|
|
1499
|
-
import { createConvaiWidget } from "@convai/web-sdk/vanilla";
|
|
1500
|
-
```
|
|
1501
|
-
|
|
1502
|
-
Creates and mounts a complete floating chat widget.
|
|
1503
|
-
|
|
1504
|
-
#### `VanillaWidgetOptions`
|
|
1505
|
-
|
|
1506
|
-
| Field | Type | Required | Default | Description |
|
|
1507
|
-
| ------------------ | -------------------------------- | -------- | ----------- | -------------------------------------------------- |
|
|
1508
|
-
| `convaiClient` | `IConvaiClient` | No\* | - | Existing client instance. |
|
|
1509
|
-
| `apiKey` | `string` | No\* | - | Used only when `convaiClient` is not provided. |
|
|
1510
|
-
| `characterId` | `string` | No\* | - | Used only when `convaiClient` is not provided. |
|
|
1511
|
-
| `enableVideo` | `boolean` | No | `false` | Used for auto-created client only. |
|
|
1512
|
-
| `startWithVideoOn` | `boolean` | No | `false` | Used for auto-created client only. |
|
|
1513
|
-
| `enableLipsync` | `boolean` | No | `false` | Used for auto-created client only. |
|
|
1514
|
-
| `blendshapeConfig` | object | No | `undefined` | Used for auto-created client only. |
|
|
1515
|
-
| `showVideo` | `boolean` | No | `true` | Show video toggle in settings. |
|
|
1516
|
-
| `showScreenShare` | `boolean` | No | `true` | Show screen-share toggle in settings. |
|
|
1517
|
-
| `defaultVoiceMode` | `boolean` | No | `true` | Start in voice mode when opened. |
|
|
1518
|
-
| `onConnect` | `() => void` | No | `undefined` | Called when widget client connects. |
|
|
1519
|
-
| `onDisconnect` | `() => void` | No | `undefined` | Called when widget client disconnects. |
|
|
1520
|
-
| `onMessage` | `(message: ChatMessage) => void` | No | `undefined` | Called on each message change with latest message. |
|
|
1521
|
-
|
|
1522
|
-
\* You must provide either `convaiClient` OR both `apiKey` and `characterId`.
|
|
1523
|
-
|
|
1524
|
-
#### Return type: `VanillaWidget`
|
|
1525
|
-
|
|
1526
|
-
- `element`: root widget element
|
|
1527
|
-
- `client`: resolved client instance
|
|
1528
|
-
- `destroy()`: unmount and cleanup
|
|
1529
|
-
- `update?`: optional future extension field
|
|
1530
|
-
|
|
1531
|
-
### `destroyConvaiWidget(widget)`
|
|
1532
|
-
|
|
1533
|
-
Convenience wrapper that calls `widget.destroy()`.
|
|
1534
|
-
|
|
1535
|
-
### `AudioRenderer` (vanilla)
|
|
1536
|
-
|
|
1537
|
-
`AudioRenderer` listens to LiveKit room track subscriptions and auto-attaches remote audio tracks to hidden `audio` elements for playback. Use one renderer instance per active room session and destroy it during cleanup.
|
|
1538
|
-
|
|
1539
|
-
## 13. Audio Integration Best Practices (Vanilla TypeScript)
|
|
1540
|
-
|
|
1541
|
-
This section provides the recommended integration for stable audio playback.
|
|
1542
|
-
|
|
1543
|
-
### Recommended reference implementation
|
|
1544
|
-
|
|
1545
|
-
```ts
|
|
1546
|
-
import { ConvaiClient } from "@convai/web-sdk/core";
|
|
1547
|
-
import { AudioRenderer } from "@convai/web-sdk/vanilla";
|
|
1548
|
-
|
|
1549
|
-
class ConvaiAudioSession {
|
|
1550
|
-
private client: ConvaiClient;
|
|
1551
|
-
private audioRenderer: AudioRenderer | null = null;
|
|
1552
|
-
private audioContext: AudioContext | null = null;
|
|
1553
|
-
|
|
1554
|
-
constructor() {
|
|
1555
|
-
this.client = new ConvaiClient({
|
|
1556
|
-
apiKey: "<YOUR_CONVAI_API_KEY>",
|
|
1557
|
-
characterId: "<YOUR_CHARACTER_ID>",
|
|
1558
|
-
ttsEnabled: true,
|
|
1559
|
-
});
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
async connectFromUserGesture(): Promise<void> {
|
|
1563
|
-
await this.client.connect();
|
|
1564
|
-
|
|
1565
|
-
// Required for remote audio playback wiring.
|
|
1566
|
-
this.audioRenderer = new AudioRenderer(this.client.room);
|
|
1567
|
-
|
|
1568
|
-
// Optional: if your app performs WebAudio analysis/effects.
|
|
1569
|
-
if (!this.audioContext) {
|
|
1570
|
-
this.audioContext = new AudioContext();
|
|
1571
|
-
}
|
|
1572
|
-
if (this.audioContext.state === "suspended") {
|
|
1573
|
-
await this.audioContext.resume();
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
async disconnect(): Promise<void> {
|
|
1578
|
-
if (this.audioRenderer) {
|
|
1579
|
-
this.audioRenderer.destroy();
|
|
1580
|
-
this.audioRenderer = null;
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
await this.client.disconnect();
|
|
1584
|
-
|
|
1585
|
-
if (this.audioContext && this.audioContext.state !== "closed") {
|
|
1586
|
-
await this.audioContext.close();
|
|
1587
|
-
this.audioContext = null;
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
```
|
|
1592
|
-
|
|
1593
|
-
### AudioContext guidance
|
|
1594
|
-
|
|
1595
|
-
- Create/resume `AudioContext` only after user interaction in browsers that enforce autoplay policy.
|
|
1596
|
-
- If you are not processing audio with WebAudio, you do not need a custom `AudioContext`; `AudioRenderer` is enough for playback.
|
|
1597
|
-
- Always close your custom `AudioContext` in teardown.
|
|
1598
|
-
|
|
1599
|
-
### Lifecycle and cleanup order
|
|
1600
|
-
|
|
1601
|
-
Recommended shutdown order:
|
|
1602
|
-
|
|
1603
|
-
1. Stop UI input loops/listeners
|
|
1604
|
-
2. Destroy `AudioRenderer`
|
|
1605
|
-
3. Disconnect `ConvaiClient`
|
|
1606
|
-
4. Close custom `AudioContext` (if created)
|
|
1607
|
-
|
|
1608
|
-
### Common failure modes and fixes
|
|
1609
|
-
|
|
1610
|
-
| Symptom | Likely cause | Recommended action |
|
|
1611
|
-
| ----------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
1612
|
-
| No AI audio output | `AudioRenderer` not created | Instantiate `new AudioRenderer(client.room)` immediately after successful connect. |
|
|
1613
|
-
| No AI audio output | Browser autoplay restriction | Trigger connect/playback from a user click, and resume `AudioContext` if suspended. |
|
|
1614
|
-
| No AI audio output | TTS disabled | Ensure `ttsEnabled` is true for sessions that need speech output. |
|
|
1615
|
-
| Intermittent playback | Multiple renderers or stale room instance | Use one renderer per session and always destroy old renderer before reconnecting. |
|
|
1616
|
-
| Works once, then silent | Incomplete cleanup on previous session | Destroy renderer and disconnect client on teardown; avoid reusing invalid room state. |
|
|
1617
|
-
| Random muted behavior | App-side muting of remote tracks | Verify no custom code is muting remote publications or media elements. |
|
|
1618
|
-
|
|
1619
|
-
## 14. Error Handling and Reliability Patterns
|
|
1620
|
-
|
|
1621
|
-
### Pattern 1: Centralized SDK error handling
|
|
1622
|
-
|
|
1623
|
-
```ts
|
|
1624
|
-
const unsubError = client.on("error", (error) => {
|
|
1625
|
-
console.error("Convai SDK error:", error);
|
|
1626
|
-
// Optional: route to telemetry/monitoring
|
|
1627
|
-
});
|
|
1628
|
-
```
|
|
1629
|
-
|
|
1630
|
-
### Pattern 2: Retry connect with exponential backoff
|
|
1631
|
-
|
|
1632
|
-
```ts
|
|
1633
|
-
async function connectWithRetry(
|
|
1634
|
-
client: any,
|
|
1635
|
-
attempts = 3,
|
|
1636
|
-
initialDelayMs = 500,
|
|
1637
|
-
): Promise<void> {
|
|
1638
|
-
let delay = initialDelayMs;
|
|
1639
|
-
|
|
1640
|
-
for (let i = 1; i <= attempts; i++) {
|
|
1641
|
-
try {
|
|
1642
|
-
await client.connect();
|
|
1643
|
-
return;
|
|
1644
|
-
} catch (error) {
|
|
1645
|
-
if (i === attempts) throw error;
|
|
1646
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1647
|
-
delay *= 2;
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
```
|
|
1652
|
-
|
|
1653
|
-
### Pattern 3: Safe send guard
|
|
1654
|
-
|
|
1655
|
-
```ts
|
|
1656
|
-
function safeSendText(client: any, text: string) {
|
|
1657
|
-
if (!text.trim()) return;
|
|
1658
|
-
if (!client.state.isConnected) return;
|
|
1659
|
-
if (!client.isBotReady) return;
|
|
1660
|
-
client.sendUserTextMessage(text);
|
|
1661
|
-
}
|
|
1662
|
-
```
|
|
1663
|
-
|
|
1664
|
-
### Pattern 4: Protect media control calls
|
|
1665
|
-
|
|
1666
|
-
```ts
|
|
1667
|
-
async function safeToggleMic(client: any) {
|
|
1668
|
-
try {
|
|
1669
|
-
await client.audioControls.toggleAudio();
|
|
1670
|
-
} catch (error) {
|
|
1671
|
-
console.error("Failed to toggle microphone:", error);
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
```
|
|
1675
|
-
|
|
1676
|
-
### Pattern 5: Always unsubscribe listeners
|
|
1677
|
-
|
|
1678
|
-
```ts
|
|
1679
|
-
const unsubscribers = [
|
|
1680
|
-
client.on("stateChange", () => {}),
|
|
1681
|
-
client.on("messagesChange", () => {}),
|
|
1682
|
-
];
|
|
1683
|
-
|
|
1684
|
-
function cleanupListeners() {
|
|
1685
|
-
for (const unsub of unsubscribers) unsub();
|
|
1686
|
-
}
|
|
1687
|
-
```
|
|
1688
|
-
|
|
1689
|
-
## 15. Troubleshooting
|
|
1690
|
-
|
|
1691
|
-
### Connection issues
|
|
1692
|
-
|
|
1693
|
-
- Verify API key and character ID are valid.
|
|
1694
|
-
- Ensure requests are allowed from your browser origin.
|
|
1695
|
-
- Set `url` explicitly if your environment does not use the SDK default endpoint.
|
|
1696
|
-
- Listen to `error` and inspect failed network calls in browser devtools.
|
|
1697
|
-
|
|
1698
|
-
### `connect()` succeeds but bot never responds
|
|
1699
|
-
|
|
1700
|
-
- Wait for `botReady` before sending messages.
|
|
1701
|
-
- Confirm `ttsEnabled` and message flow are configured as expected.
|
|
1702
|
-
- Verify `messagesChange` receives content.
|
|
1703
|
-
|
|
1704
|
-
### Audio does not play
|
|
1705
|
-
|
|
1706
|
-
- Ensure an `AudioRenderer` is active for the connected room (vanilla custom UI).
|
|
1707
|
-
- Ensure playback starts from a user gesture path to satisfy autoplay policies.
|
|
1708
|
-
- Confirm no custom muting code is muting remote tracks.
|
|
1709
|
-
|
|
1710
|
-
### Microphone does not capture user voice
|
|
1711
|
-
|
|
1712
|
-
- Ensure app is served over secure context.
|
|
1713
|
-
- Verify browser microphone permission.
|
|
1714
|
-
- Handle permission errors from `audioControls.enableAudio()/unmuteAudio()`.
|
|
1715
|
-
|
|
1716
|
-
### Video or screen share controls fail
|
|
1717
|
-
|
|
1718
|
-
- Use `enableVideo: true` in config when you need video capabilities.
|
|
1719
|
-
- Screen share can be blocked by browser policy or user denial.
|
|
1720
|
-
- Wrap calls in `try/catch` and provide fallback UX.
|
|
1721
|
-
|
|
1722
|
-
### Lipsync appears out of sync or shape
|
|
1723
|
-
|
|
1724
|
-
- Validate blendshape format (`arkit` vs `mha`) matches your rig expectations.
|
|
1725
|
-
- Tune `frames_buffer_duration` so you atleast have some duration of blendshapes before the audio starts playing.
|
|
1726
|
-
- Align lipsync start and stop with the queue: start playback when the bot starts speaking (`isBotSpeaking()` true) and treat the turn as finished when `blendshapeQueue.isConversationEnded()` is true before resetting.
|
|
1727
|
-
- Drive blendshape application from a single loop (e.g. `requestAnimationFrame`) and advance frame index at 60fps so mouth movement stays in sync with audio.
|
|
135
|
+
---
|
|
1728
136
|
|
|
1729
|
-
##
|
|
137
|
+
## Example app
|
|
1730
138
|
|
|
1731
|
-
|
|
139
|
+
A full React Three Fiber integration demo is in [`examples/react-three-fiber`](./examples/react-three-fiber/). It demonstrates lipsync on a 3D character, emotion display, dynamic context, and file upload.
|
|
1732
140
|
|
|
1733
|
-
- `examples/react-three-fiber`
|
|
1734
|
-
- `examples/three-vanilla`
|
|
1735
|
-
- `examples/README.md` for example-level setup notes
|