@keyframelabs/elements 0.0.7 → 0.0.8
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 +37 -0
- package/dist/agents/elevenlabs.d.ts +1 -0
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/types.d.ts +4 -0
- package/dist/index.js +88 -86
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -77,6 +77,41 @@ For `PersonaEmbed`, this is determined by the values you set in the Keyframe pla
|
|
|
77
77
|
|
|
78
78
|
For `PersonaView`, this is determined by `voiceAgentDetails`.
|
|
79
79
|
|
|
80
|
+
## Emotion Controls
|
|
81
|
+
|
|
82
|
+
The avatar can display emotional expressions (`neutral`, `angry`, `sad`, `happy`) that affect its facial expression and demeanor.
|
|
83
|
+
|
|
84
|
+
### Automatic Emotion Detection (ElevenLabs)
|
|
85
|
+
|
|
86
|
+
When using ElevenLabs as the voice agent, emotions are automatically detected from the agent's speech. The ElevenLabs agent parses emotion tags from audio alignment data (e.g., `[angry]`, `[happy]`) and the avatar expression updates in real-time.
|
|
87
|
+
|
|
88
|
+
This requires no additional configuration—just configure your ElevenLabs agent to include emotion tags in its responses.
|
|
89
|
+
|
|
90
|
+
### Manual Emotion Control
|
|
91
|
+
|
|
92
|
+
For other agents or custom emotion logic, you can access the underlying session to set emotions manually:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// Access the underlying SDK session for manual control
|
|
96
|
+
// (Available when using @keyframelabs/sdk directly)
|
|
97
|
+
import { createClient } from '@keyframelabs/sdk';
|
|
98
|
+
|
|
99
|
+
const session = createClient({ ... });
|
|
100
|
+
await session.setEmotion('happy');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Agent Events
|
|
104
|
+
|
|
105
|
+
The `emotion` event is emitted when an agent detects an emotion change:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
agent.on('emotion', (emotion) => {
|
|
109
|
+
console.log('Emotion detected:', emotion); // 'neutral' | 'angry' | 'sad' | 'happy'
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Currently, only the ElevenLabs agent emits emotion events.
|
|
114
|
+
|
|
80
115
|
## API
|
|
81
116
|
|
|
82
117
|
### `PersonaEmbed`
|
|
@@ -150,6 +185,8 @@ type VoiceAgentDetails = {
|
|
|
150
185
|
agent_id?: string; // For elevenlabs, cartesia
|
|
151
186
|
signed_url?: string; // For elevenlabs, vapi
|
|
152
187
|
};
|
|
188
|
+
|
|
189
|
+
type Emotion = 'neutral' | 'angry' | 'sad' | 'happy';
|
|
153
190
|
```
|
|
154
191
|
|
|
155
192
|
## License
|
|
@@ -20,6 +20,7 @@ export declare class ElevenLabsAgent extends BaseAgent {
|
|
|
20
20
|
private sourceInputSampleRate;
|
|
21
21
|
private initialized;
|
|
22
22
|
private lastInterruptId;
|
|
23
|
+
private emotionEmittedForEventId;
|
|
23
24
|
connect(config: ElevenLabsConfig): Promise<void>;
|
|
24
25
|
protected handleParsedMessage(message: unknown): void;
|
|
25
26
|
private handleInitMetadata;
|
package/dist/agents/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { VapiAgent, VapiConfig } from './vapi';
|
|
|
9
9
|
* various voice AI backends to the Persona SDK.
|
|
10
10
|
*/
|
|
11
11
|
export { BaseAgent, DEFAULT_INPUT_SAMPLE_RATE } from './base';
|
|
12
|
-
export type { Agent, AgentConfig, AgentEventMap, AgentState } from './types';
|
|
12
|
+
export type { Agent, AgentConfig, AgentEventMap, AgentState, Emotion } from './types';
|
|
13
13
|
export { GeminiLiveAgent, type GeminiLiveConfig };
|
|
14
14
|
export { ElevenLabsAgent, type ElevenLabsConfig };
|
|
15
15
|
export { CartesiaAgent, type CartesiaConfig };
|
package/dist/agents/types.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
/** Agent state for UI */
|
|
10
10
|
export type AgentState = 'idle' | 'listening' | 'thinking' | 'speaking';
|
|
11
|
+
/** Emotion states for avatar expression */
|
|
12
|
+
export type Emotion = 'neutral' | 'angry' | 'sad' | 'happy';
|
|
11
13
|
/** Agent configuration */
|
|
12
14
|
export interface AgentConfig {
|
|
13
15
|
/** System prompt for the agent */
|
|
@@ -37,6 +39,8 @@ export interface AgentEventMap {
|
|
|
37
39
|
text: string;
|
|
38
40
|
isFinal: boolean;
|
|
39
41
|
};
|
|
42
|
+
/** Emotion change (currently only supported by ElevenLabs agent) */
|
|
43
|
+
emotion: Emotion;
|
|
40
44
|
/** Agent connection closed (unexpected disconnect) */
|
|
41
45
|
closed: {
|
|
42
46
|
code?: number;
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { createClient as
|
|
2
|
-
const
|
|
3
|
-
function
|
|
1
|
+
import { createClient as w } from "@keyframelabs/sdk";
|
|
2
|
+
const l = 24e3;
|
|
3
|
+
function m(i) {
|
|
4
4
|
const e = atob(i), t = new Uint8Array(e.length);
|
|
5
5
|
for (let s = 0; s < e.length; s++)
|
|
6
6
|
t[s] = e.charCodeAt(s);
|
|
7
7
|
return t;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
9
|
+
function g(i) {
|
|
10
10
|
let e = "";
|
|
11
11
|
for (let t = 0; t < i.length; t++)
|
|
12
12
|
e += String.fromCharCode(i[t]);
|
|
@@ -15,16 +15,16 @@ function m(i) {
|
|
|
15
15
|
function c(i, e, t) {
|
|
16
16
|
if (e === t)
|
|
17
17
|
return i;
|
|
18
|
-
const s = new Int16Array(i.buffer, i.byteOffset, i.length / 2), n = e / t, a = Math.floor(s.length / n),
|
|
19
|
-
for (let
|
|
20
|
-
const
|
|
21
|
-
o
|
|
22
|
-
s[p] * (1 -
|
|
18
|
+
const s = new Int16Array(i.buffer, i.byteOffset, i.length / 2), n = e / t, a = Math.floor(s.length / n), r = new Int16Array(a);
|
|
19
|
+
for (let o = 0; o < a; o++) {
|
|
20
|
+
const _ = o * n, p = Math.floor(_), b = Math.min(p + 1, s.length - 1), v = _ - p;
|
|
21
|
+
r[o] = Math.round(
|
|
22
|
+
s[p] * (1 - v) + s[b] * v
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
|
-
return new Uint8Array(
|
|
25
|
+
return new Uint8Array(r.buffer);
|
|
26
26
|
}
|
|
27
|
-
function
|
|
27
|
+
function E() {
|
|
28
28
|
const i = /* @__PURE__ */ new Map();
|
|
29
29
|
return {
|
|
30
30
|
on(e, t) {
|
|
@@ -41,7 +41,7 @@ function b() {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function
|
|
44
|
+
function f(i) {
|
|
45
45
|
const e = new Int16Array(i.length);
|
|
46
46
|
for (let t = 0; t < i.length; t++) {
|
|
47
47
|
const s = Math.max(-1, Math.min(1, i[t]));
|
|
@@ -53,7 +53,7 @@ const I = 16e3;
|
|
|
53
53
|
class u {
|
|
54
54
|
ws = null;
|
|
55
55
|
_state = "idle";
|
|
56
|
-
events =
|
|
56
|
+
events = E();
|
|
57
57
|
inputSampleRate = I;
|
|
58
58
|
/** Current agent state */
|
|
59
59
|
get state() {
|
|
@@ -113,8 +113,8 @@ class u {
|
|
|
113
113
|
this.events.emit("closed", { code: e, reason: t });
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
-
const
|
|
117
|
-
class
|
|
116
|
+
const C = "gemini-2.5-flash-native-audio-preview-12-2025", k = "wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent", A = "wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained";
|
|
117
|
+
class R extends u {
|
|
118
118
|
agentName = "GeminiLive";
|
|
119
119
|
async connect(e) {
|
|
120
120
|
if (this.ws)
|
|
@@ -122,10 +122,10 @@ class A extends u {
|
|
|
122
122
|
if (!e.apiKey)
|
|
123
123
|
throw new Error("Gemini API key is required");
|
|
124
124
|
e.inputSampleRate && (this.inputSampleRate = e.inputSampleRate);
|
|
125
|
-
const t = e.model ??
|
|
126
|
-
return new Promise((a,
|
|
125
|
+
const t = e.model ?? C, n = (e.authType ?? "api_key") === "ephemeral_token" ? `${A}?access_token=${encodeURIComponent(e.apiKey)}` : `${k}?key=${encodeURIComponent(e.apiKey)}`;
|
|
126
|
+
return new Promise((a, r) => {
|
|
127
127
|
this.ws = new WebSocket(n), this.ws.onopen = () => {
|
|
128
|
-
const
|
|
128
|
+
const o = {
|
|
129
129
|
setup: {
|
|
130
130
|
model: `models/${t}`,
|
|
131
131
|
generationConfig: {
|
|
@@ -134,13 +134,13 @@ class A extends u {
|
|
|
134
134
|
systemInstruction: e.systemPrompt ? { parts: [{ text: e.systemPrompt }] } : void 0
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
|
-
this.ws.send(JSON.stringify(
|
|
137
|
+
this.ws.send(JSON.stringify(o)), this.setState("listening"), a();
|
|
138
138
|
}, this.ws.onerror = () => {
|
|
139
|
-
|
|
140
|
-
}, this.ws.onclose = (
|
|
141
|
-
this.ws = null, this.setState("idle"), this.emitClosed(
|
|
142
|
-
}, this.ws.onmessage = (
|
|
143
|
-
this.handleMessage(
|
|
139
|
+
r(new Error("Failed to connect to Gemini Live"));
|
|
140
|
+
}, this.ws.onclose = (o) => {
|
|
141
|
+
this.ws = null, this.setState("idle"), this.emitClosed(o.code, o.reason);
|
|
142
|
+
}, this.ws.onmessage = (o) => {
|
|
143
|
+
this.handleMessage(o.data);
|
|
144
144
|
};
|
|
145
145
|
});
|
|
146
146
|
}
|
|
@@ -159,7 +159,7 @@ class A extends u {
|
|
|
159
159
|
this._state !== "speaking" && (this.events.emit("turnStart", void 0), this.setState("speaking"));
|
|
160
160
|
for (const n of s.modelTurn.parts) {
|
|
161
161
|
if (n.inlineData?.data) {
|
|
162
|
-
const a =
|
|
162
|
+
const a = m(n.inlineData.data);
|
|
163
163
|
this.events.emit("audio", a);
|
|
164
164
|
}
|
|
165
165
|
n.text && this.events.emit("transcript", {
|
|
@@ -181,7 +181,7 @@ class A extends u {
|
|
|
181
181
|
mediaChunks: [
|
|
182
182
|
{
|
|
183
183
|
mimeType: `audio/pcm;rate=${this.inputSampleRate}`,
|
|
184
|
-
data:
|
|
184
|
+
data: g(e)
|
|
185
185
|
}
|
|
186
186
|
]
|
|
187
187
|
}
|
|
@@ -189,8 +189,8 @@ class A extends u {
|
|
|
189
189
|
this.ws.send(JSON.stringify(t));
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
-
const
|
|
193
|
-
class
|
|
192
|
+
const M = ["neutral", "angry", "sad", "happy"], x = "wss://api.elevenlabs.io/v1/convai/conversation";
|
|
193
|
+
class S extends u {
|
|
194
194
|
agentName = "ElevenLabs";
|
|
195
195
|
outputSampleRate = 24e3;
|
|
196
196
|
// Default, updated from metadata
|
|
@@ -202,6 +202,8 @@ class M extends u {
|
|
|
202
202
|
// True after conversation_initiation_metadata received
|
|
203
203
|
lastInterruptId = 0;
|
|
204
204
|
// Track interruptions to filter stale audio
|
|
205
|
+
emotionEmittedForEventId = -1;
|
|
206
|
+
// Track which turn's emotion we've already emitted
|
|
205
207
|
async connect(e) {
|
|
206
208
|
if (this.ws)
|
|
207
209
|
throw new Error("Already connected");
|
|
@@ -209,7 +211,7 @@ class M extends u {
|
|
|
209
211
|
throw new Error("ElevenLabs agent ID or signed URL is required");
|
|
210
212
|
e.inputSampleRate && (this.sourceInputSampleRate = e.inputSampleRate);
|
|
211
213
|
let t;
|
|
212
|
-
return e.signedUrl ? t = e.signedUrl : (t = `${
|
|
214
|
+
return e.signedUrl ? t = e.signedUrl : (t = `${x}?agent_id=${e.agentId}`, e.apiKey && (t += `&xi-api-key=${e.apiKey}`)), new Promise((s, n) => {
|
|
213
215
|
this.ws = new WebSocket(t), this.ws.onopen = () => {
|
|
214
216
|
this.setState("listening"), s();
|
|
215
217
|
}, this.ws.onerror = () => {
|
|
@@ -269,19 +271,19 @@ class M extends u {
|
|
|
269
271
|
}
|
|
270
272
|
handleAudio(e) {
|
|
271
273
|
const t = e.audio_event;
|
|
272
|
-
if (!t?.audio_base_64
|
|
274
|
+
if (!t?.audio_base_64) return;
|
|
275
|
+
const s = t.event_id ?? 0;
|
|
276
|
+
if (s <= this.lastInterruptId)
|
|
273
277
|
return;
|
|
274
|
-
this._state !== "speaking" && (this.events.emit("turnStart", void 0), this.setState("speaking"))
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.events.emit("audio", n);
|
|
280
|
-
else
|
|
281
|
-
for (let o = 0; o < n.length; o += a) {
|
|
282
|
-
const r = n.slice(o, Math.min(o + a, n.length));
|
|
283
|
-
this.events.emit("audio", r);
|
|
278
|
+
if (this._state !== "speaking" && (this.events.emit("turnStart", void 0), this.setState("speaking")), this.emotionEmittedForEventId !== s && t.alignment?.chars) {
|
|
279
|
+
const r = t.alignment.chars.join("").match(/\[(\w+)\]/);
|
|
280
|
+
if (r) {
|
|
281
|
+
const o = r[1].toLowerCase();
|
|
282
|
+
M.includes(o) && (this.events.emit("emotion", o), this.emotionEmittedForEventId = s);
|
|
284
283
|
}
|
|
284
|
+
}
|
|
285
|
+
let n = m(t.audio_base_64);
|
|
286
|
+
this.outputSampleRate !== l && (n = c(n, this.outputSampleRate, l)), this.events.emit("audio", n);
|
|
285
287
|
}
|
|
286
288
|
handleUserTranscript(e) {
|
|
287
289
|
const t = e.user_transcription_event;
|
|
@@ -308,7 +310,7 @@ class M extends u {
|
|
|
308
310
|
return;
|
|
309
311
|
let t = e;
|
|
310
312
|
this.sourceInputSampleRate !== this.expectedInputSampleRate && (t = c(e, this.sourceInputSampleRate, this.expectedInputSampleRate)), this.ws.send(JSON.stringify({
|
|
311
|
-
user_audio_chunk:
|
|
313
|
+
user_audio_chunk: g(t)
|
|
312
314
|
}));
|
|
313
315
|
}
|
|
314
316
|
/**
|
|
@@ -341,8 +343,8 @@ class M extends u {
|
|
|
341
343
|
this.initialized = !1, this.lastInterruptId = 0, super.close();
|
|
342
344
|
}
|
|
343
345
|
}
|
|
344
|
-
const
|
|
345
|
-
class
|
|
346
|
+
const P = "wss://api.cartesia.ai/agents/stream", T = "2025-04-16";
|
|
347
|
+
class O extends u {
|
|
346
348
|
agentName = "Cartesia";
|
|
347
349
|
// Audio configuration
|
|
348
350
|
cartesiaInputFormat = "pcm_16000";
|
|
@@ -361,7 +363,7 @@ class T extends u {
|
|
|
361
363
|
if (!e.apiKey)
|
|
362
364
|
throw new Error("Cartesia API Key is required");
|
|
363
365
|
e.inputSampleRate && (this.inputSampleRate = e.inputSampleRate), this.inputSampleRate === 16e3 ? this.cartesiaInputFormat = "pcm_16000" : this.inputSampleRate === 24e3 ? this.cartesiaInputFormat = "pcm_24000" : this.inputSampleRate === 44100 ? this.cartesiaInputFormat = "pcm_44100" : this.cartesiaInputFormat = "pcm_16000";
|
|
364
|
-
const t = `${
|
|
366
|
+
const t = `${P}/${e.agentId}?api_key=${e.apiKey}&cartesia_version=${T}`;
|
|
365
367
|
return new Promise((s, n) => {
|
|
366
368
|
this.ws = new WebSocket(t), this.ws.onopen = () => {
|
|
367
369
|
this.sendStartEvent(), this.startHeartbeat(), s();
|
|
@@ -423,8 +425,8 @@ class T extends u {
|
|
|
423
425
|
handleMediaOutput(e) {
|
|
424
426
|
if (!e.media?.payload) return;
|
|
425
427
|
this._state !== "speaking" && (this.events.emit("turnStart", void 0), this.setState("speaking"));
|
|
426
|
-
let t =
|
|
427
|
-
this.cartesiaOutputRate !==
|
|
428
|
+
let t = m(e.media.payload);
|
|
429
|
+
this.cartesiaOutputRate !== l && (t = c(t, this.cartesiaOutputRate, l)), this.events.emit("audio", t);
|
|
428
430
|
}
|
|
429
431
|
handleClear() {
|
|
430
432
|
this.events.emit("interrupted", void 0), this.setState("listening");
|
|
@@ -438,7 +440,7 @@ class T extends u {
|
|
|
438
440
|
event: "media_input",
|
|
439
441
|
stream_id: this.streamId,
|
|
440
442
|
media: {
|
|
441
|
-
payload:
|
|
443
|
+
payload: g(t)
|
|
442
444
|
}
|
|
443
445
|
}));
|
|
444
446
|
}
|
|
@@ -446,7 +448,7 @@ class T extends u {
|
|
|
446
448
|
this.stopHeartbeat(), this.isReady = !1, this.streamId = null, super.close();
|
|
447
449
|
}
|
|
448
450
|
}
|
|
449
|
-
class
|
|
451
|
+
class N extends u {
|
|
450
452
|
agentName = "Vapi";
|
|
451
453
|
// Audio configuration - Vapi uses 16kHz PCM by default
|
|
452
454
|
vapiSampleRate = 16e3;
|
|
@@ -473,7 +475,7 @@ class O extends u {
|
|
|
473
475
|
*/
|
|
474
476
|
handleBinaryAudio(e) {
|
|
475
477
|
this._state !== "speaking" && (this.events.emit("turnStart", void 0), this.setState("speaking"));
|
|
476
|
-
const t = new Uint8Array(e), s = this.vapiSampleRate !==
|
|
478
|
+
const t = new Uint8Array(e), s = this.vapiSampleRate !== l ? c(t, this.vapiSampleRate, l) : t;
|
|
477
479
|
this.events.emit("audio", s);
|
|
478
480
|
}
|
|
479
481
|
handleParsedMessage(e) {
|
|
@@ -525,7 +527,7 @@ class O extends u {
|
|
|
525
527
|
this.hangup(), super.close();
|
|
526
528
|
}
|
|
527
529
|
}
|
|
528
|
-
const
|
|
530
|
+
const U = [
|
|
529
531
|
{ id: "gemini", name: "Gemini Live", description: "Google Gemini Live API" },
|
|
530
532
|
{ id: "elevenlabs", name: "ElevenLabs", description: "ElevenLabs Conversational AI" },
|
|
531
533
|
{ id: "cartesia", name: "Cartesia", description: "Cartesia Agents API" },
|
|
@@ -534,21 +536,21 @@ const N = [
|
|
|
534
536
|
function y(i) {
|
|
535
537
|
switch (i) {
|
|
536
538
|
case "gemini":
|
|
537
|
-
return new
|
|
539
|
+
return new R();
|
|
538
540
|
case "elevenlabs":
|
|
539
|
-
return new
|
|
541
|
+
return new S();
|
|
540
542
|
case "cartesia":
|
|
541
|
-
return new T();
|
|
542
|
-
case "vapi":
|
|
543
543
|
return new O();
|
|
544
|
+
case "vapi":
|
|
545
|
+
return new N();
|
|
544
546
|
default:
|
|
545
547
|
throw new Error(`Unknown agent type: ${i}`);
|
|
546
548
|
}
|
|
547
549
|
}
|
|
548
|
-
function
|
|
549
|
-
return
|
|
550
|
+
function F(i) {
|
|
551
|
+
return U.find((e) => e.id === i);
|
|
550
552
|
}
|
|
551
|
-
class
|
|
553
|
+
class D extends Error {
|
|
552
554
|
status;
|
|
553
555
|
payload;
|
|
554
556
|
url;
|
|
@@ -556,8 +558,8 @@ class U extends Error {
|
|
|
556
558
|
super(e.message), this.name = "ApiError", this.status = e.status, this.payload = e.payload, this.url = e.url;
|
|
557
559
|
}
|
|
558
560
|
}
|
|
559
|
-
const
|
|
560
|
-
class
|
|
561
|
+
const h = /* @__PURE__ */ new Set();
|
|
562
|
+
class K {
|
|
561
563
|
apiBaseUrl;
|
|
562
564
|
publishableKey;
|
|
563
565
|
callbacks;
|
|
@@ -601,31 +603,31 @@ class F {
|
|
|
601
603
|
}
|
|
602
604
|
/** Connect to the embed session */
|
|
603
605
|
async connect() {
|
|
604
|
-
if (
|
|
606
|
+
if (h.has(this.publishableKey)) {
|
|
605
607
|
console.log("[PersonaEmbed] Connection already in progress, skipping");
|
|
606
608
|
return;
|
|
607
609
|
}
|
|
608
|
-
|
|
610
|
+
h.add(this.publishableKey), this.mounted = !0, this.abortController = new AbortController(), this.setStatus("connecting");
|
|
609
611
|
try {
|
|
610
612
|
const e = await this.fetchSession(this.abortController.signal);
|
|
611
613
|
if (!this.mounted) {
|
|
612
|
-
|
|
614
|
+
h.delete(this.publishableKey);
|
|
613
615
|
return;
|
|
614
616
|
}
|
|
615
617
|
if (await this.initSession(e), await this.initMicrophone(), await this.connectAgent(e.voice_agent_details), !this.mounted) {
|
|
616
|
-
this.cleanup(),
|
|
618
|
+
this.cleanup(), h.delete(this.publishableKey);
|
|
617
619
|
return;
|
|
618
620
|
}
|
|
619
621
|
this.setStatus("connected");
|
|
620
622
|
} catch (e) {
|
|
621
|
-
if (
|
|
623
|
+
if (h.delete(this.publishableKey), e instanceof Error && e.name === "AbortError")
|
|
622
624
|
return;
|
|
623
625
|
console.error("[PersonaEmbed]", e), this.mounted && (this.setStatus("error"), this.callbacks.onError?.(e));
|
|
624
626
|
}
|
|
625
627
|
}
|
|
626
628
|
/** Disconnect and cleanup */
|
|
627
629
|
disconnect() {
|
|
628
|
-
this.mounted = !1, this.abortController?.abort(), this.abortController = null,
|
|
630
|
+
this.mounted = !1, this.abortController?.abort(), this.abortController = null, h.delete(this.publishableKey), this.cleanup(), this.setStatus("disconnected");
|
|
629
631
|
}
|
|
630
632
|
/** Toggle microphone mute */
|
|
631
633
|
toggleMute() {
|
|
@@ -650,7 +652,7 @@ class F {
|
|
|
650
652
|
s = await t.json();
|
|
651
653
|
} catch {
|
|
652
654
|
}
|
|
653
|
-
throw new
|
|
655
|
+
throw new D({
|
|
654
656
|
message: s?.message ?? "create_session failed",
|
|
655
657
|
status: t.status,
|
|
656
658
|
payload: s,
|
|
@@ -664,7 +666,7 @@ class F {
|
|
|
664
666
|
return t.json();
|
|
665
667
|
}
|
|
666
668
|
async initSession(e) {
|
|
667
|
-
this.session =
|
|
669
|
+
this.session = w({
|
|
668
670
|
serverUrl: e.session_details.server_url,
|
|
669
671
|
participantToken: e.session_details.participant_token,
|
|
670
672
|
agentIdentity: e.session_details.agent_identity,
|
|
@@ -686,7 +688,7 @@ class F {
|
|
|
686
688
|
}
|
|
687
689
|
}), this.agent = y(e.voice_agent_details.type), this.agent.on("audio", (t) => this.session?.sendAudio(t)), this.agent.on("interrupted", () => this.session?.interrupt()), this.agent.on("stateChange", (t) => this.setAgentState(t)), this.agent.on("closed", () => {
|
|
688
690
|
this.mounted && this.callbacks.onDisconnect?.();
|
|
689
|
-
}), await this.session.connect();
|
|
691
|
+
}), this.agent instanceof S && this.agent.on("emotion", (t) => this.session?.setEmotion(t)), await this.session.connect();
|
|
690
692
|
}
|
|
691
693
|
async initMicrophone() {
|
|
692
694
|
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
@@ -695,7 +697,7 @@ class F {
|
|
|
695
697
|
const e = this.audioContext.createMediaStreamSource(this.stream);
|
|
696
698
|
this.processor = this.audioContext.createScriptProcessor(4096, 1, 1), this.processor.onaudioprocess = (t) => {
|
|
697
699
|
if (!this._isMuted) {
|
|
698
|
-
const s =
|
|
700
|
+
const s = f(t.inputBuffer.getChannelData(0));
|
|
699
701
|
this.agent?.sendAudio(s);
|
|
700
702
|
}
|
|
701
703
|
}, e.connect(this.processor), this.processor.connect(this.audioContext.destination);
|
|
@@ -725,7 +727,7 @@ class F {
|
|
|
725
727
|
}
|
|
726
728
|
}
|
|
727
729
|
const d = /* @__PURE__ */ new Set();
|
|
728
|
-
class
|
|
730
|
+
class B {
|
|
729
731
|
voiceAgentDetails;
|
|
730
732
|
sessionDetails;
|
|
731
733
|
callbacks;
|
|
@@ -799,7 +801,7 @@ class K {
|
|
|
799
801
|
this._agentState !== e && (this._agentState = e, this.callbacks.onAgentStateChange?.(e));
|
|
800
802
|
}
|
|
801
803
|
async initSession() {
|
|
802
|
-
this.session =
|
|
804
|
+
this.session = w({
|
|
803
805
|
serverUrl: this.sessionDetails.server_url,
|
|
804
806
|
participantToken: this.sessionDetails.participant_token,
|
|
805
807
|
agentIdentity: this.sessionDetails.agent_identity,
|
|
@@ -821,7 +823,7 @@ class K {
|
|
|
821
823
|
}
|
|
822
824
|
}), this.agent = y(this.voiceAgentDetails.type), this.agent.on("audio", (e) => this.session?.sendAudio(e)), this.agent.on("interrupted", () => this.session?.interrupt()), this.agent.on("stateChange", (e) => this.setAgentState(e)), this.agent.on("closed", () => {
|
|
823
825
|
this.mounted && this.callbacks.onDisconnect?.();
|
|
824
|
-
}), await this.session.connect();
|
|
826
|
+
}), this.agent instanceof S && this.agent.on("emotion", (e) => this.session?.setEmotion(e)), await this.session.connect();
|
|
825
827
|
}
|
|
826
828
|
async initMicrophone() {
|
|
827
829
|
this.stream = await navigator.mediaDevices.getUserMedia({
|
|
@@ -830,7 +832,7 @@ class K {
|
|
|
830
832
|
const e = this.audioContext.createMediaStreamSource(this.stream);
|
|
831
833
|
this.processor = this.audioContext.createScriptProcessor(4096, 1, 1), this.processor.onaudioprocess = (t) => {
|
|
832
834
|
if (!this._isMuted) {
|
|
833
|
-
const s =
|
|
835
|
+
const s = f(t.inputBuffer.getChannelData(0));
|
|
834
836
|
this.agent?.sendAudio(s);
|
|
835
837
|
}
|
|
836
838
|
}, e.connect(this.processor), this.processor.connect(this.audioContext.destination);
|
|
@@ -860,20 +862,20 @@ class K {
|
|
|
860
862
|
}
|
|
861
863
|
}
|
|
862
864
|
export {
|
|
863
|
-
|
|
865
|
+
U as AGENT_REGISTRY,
|
|
864
866
|
u as BaseAgent,
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
867
|
+
O as CartesiaAgent,
|
|
868
|
+
S as ElevenLabsAgent,
|
|
869
|
+
R as GeminiLiveAgent,
|
|
870
|
+
D as KeyframeApiError,
|
|
871
|
+
K as PersonaEmbed,
|
|
872
|
+
B as PersonaView,
|
|
873
|
+
l as SAMPLE_RATE,
|
|
874
|
+
m as base64ToBytes,
|
|
875
|
+
g as bytesToBase64,
|
|
874
876
|
y as createAgent,
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
877
|
+
E as createEventEmitter,
|
|
878
|
+
f as floatTo16BitPCM,
|
|
879
|
+
F as getAgentInfo,
|
|
878
880
|
c as resamplePcm
|
|
879
881
|
};
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "0.0.
|
|
7
|
+
"version": "0.0.8",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"sideEffects": false,
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@keyframelabs/sdk": "0.1.
|
|
22
|
+
"@keyframelabs/sdk": "0.1.6"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^25.0.9",
|