@keyframelabs/elements 0.4.0 → 0.5.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 +3 -3
- package/dist/KflEmbedElement.d.ts +23 -1
- package/dist/agents/audio-utils.d.ts +5 -0
- package/dist/agents/index.d.ts +7 -3
- package/dist/agents/kfl.d.ts +26 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +447 -162
- package/dist/kfl-embed.js +1888 -1611
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { createClient as
|
|
2
|
-
const
|
|
3
|
-
function
|
|
1
|
+
import { createClient as T } from "@keyframelabs/sdk";
|
|
2
|
+
const h = 24e3;
|
|
3
|
+
function _(s) {
|
|
4
4
|
const e = atob(s), t = new Uint8Array(e.length);
|
|
5
5
|
for (let i = 0; i < e.length; i++)
|
|
6
6
|
t[i] = e.charCodeAt(i);
|
|
7
7
|
return t;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
9
|
+
function b(s) {
|
|
10
10
|
let e = "";
|
|
11
11
|
for (let t = 0; t < s.length; t++)
|
|
12
12
|
e += String.fromCharCode(s[t]);
|
|
13
13
|
return btoa(e);
|
|
14
14
|
}
|
|
15
|
-
function
|
|
15
|
+
function u(s, e, t) {
|
|
16
16
|
if (e === t)
|
|
17
17
|
return s;
|
|
18
|
-
const i = new Int16Array(s.buffer, s.byteOffset, s.length / 2), n = e / t, o = Math.floor(i.length / n),
|
|
19
|
-
for (let
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
i[
|
|
18
|
+
const i = new Int16Array(s.buffer, s.byteOffset, s.length / 2), n = e / t, o = Math.floor(i.length / n), c = new Int16Array(o);
|
|
19
|
+
for (let d = 0; d < o; d++) {
|
|
20
|
+
const w = d * n, p = Math.floor(w), O = Math.min(p + 1, i.length - 1), y = w - p;
|
|
21
|
+
c[d] = Math.round(
|
|
22
|
+
i[p] * (1 - y) + i[O] * y
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
|
-
return new Uint8Array(
|
|
25
|
+
return new Uint8Array(c.buffer);
|
|
26
26
|
}
|
|
27
|
-
function
|
|
27
|
+
function L() {
|
|
28
28
|
const s = /* @__PURE__ */ new Map();
|
|
29
29
|
return {
|
|
30
30
|
on(e, t) {
|
|
@@ -41,7 +41,7 @@ function x() {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function
|
|
44
|
+
function A(s) {
|
|
45
45
|
const e = new Int16Array(s.length);
|
|
46
46
|
for (let t = 0; t < s.length; t++) {
|
|
47
47
|
const i = Math.max(-1, Math.min(1, s[t]));
|
|
@@ -49,12 +49,49 @@ function E(s) {
|
|
|
49
49
|
}
|
|
50
50
|
return new Uint8Array(e.buffer);
|
|
51
51
|
}
|
|
52
|
-
const
|
|
53
|
-
class
|
|
52
|
+
const I = "pcm-capture", P = `
|
|
53
|
+
class PcmCaptureProcessor extends AudioWorkletProcessor {
|
|
54
|
+
constructor() {
|
|
55
|
+
super();
|
|
56
|
+
this._buf = new Float32Array(1024);
|
|
57
|
+
this._pos = 0;
|
|
58
|
+
}
|
|
59
|
+
process(inputs) {
|
|
60
|
+
const ch = inputs[0]?.[0];
|
|
61
|
+
if (!ch) return true;
|
|
62
|
+
let i = 0;
|
|
63
|
+
while (i < ch.length) {
|
|
64
|
+
const n = Math.min(this._buf.length - this._pos, ch.length - i);
|
|
65
|
+
this._buf.set(ch.subarray(i, i + n), this._pos);
|
|
66
|
+
this._pos += n;
|
|
67
|
+
i += n;
|
|
68
|
+
if (this._pos >= this._buf.length) {
|
|
69
|
+
this.port.postMessage(this._buf);
|
|
70
|
+
this._buf = new Float32Array(1024);
|
|
71
|
+
this._pos = 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
registerProcessor('${I}', PcmCaptureProcessor);
|
|
78
|
+
`;
|
|
79
|
+
let g = null;
|
|
80
|
+
async function x(s) {
|
|
81
|
+
return g || (g = URL.createObjectURL(
|
|
82
|
+
new Blob([P], { type: "application/javascript" })
|
|
83
|
+
)), await s.audioWorklet.addModule(g), new AudioWorkletNode(s, I, {
|
|
84
|
+
numberOfInputs: 1,
|
|
85
|
+
numberOfOutputs: 1,
|
|
86
|
+
channelCount: 1
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
const M = 16e3;
|
|
90
|
+
class v {
|
|
54
91
|
ws = null;
|
|
55
92
|
_state = "idle";
|
|
56
|
-
events =
|
|
57
|
-
inputSampleRate =
|
|
93
|
+
events = L();
|
|
94
|
+
inputSampleRate = M;
|
|
58
95
|
/** Current agent state */
|
|
59
96
|
get state() {
|
|
60
97
|
return this._state;
|
|
@@ -113,8 +150,8 @@ class S {
|
|
|
113
150
|
this.events.emit("closed", { code: e, reason: t });
|
|
114
151
|
}
|
|
115
152
|
}
|
|
116
|
-
const
|
|
117
|
-
class
|
|
153
|
+
const N = ["neutral", "angry", "sad", "happy"], B = "wss://api.elevenlabs.io/v1/convai/conversation";
|
|
154
|
+
class F extends v {
|
|
118
155
|
agentName = "ElevenLabs";
|
|
119
156
|
outputSampleRate = 24e3;
|
|
120
157
|
// Default, updated from metadata
|
|
@@ -139,7 +176,7 @@ class M extends S {
|
|
|
139
176
|
throw new Error("ElevenLabs agent ID or signed URL is required");
|
|
140
177
|
e.inputSampleRate && (this.sourceInputSampleRate = e.inputSampleRate);
|
|
141
178
|
let t;
|
|
142
|
-
return e.signedUrl ? t = e.signedUrl : (t = `${
|
|
179
|
+
return e.signedUrl ? t = e.signedUrl : (t = `${B}?agent_id=${e.agentId}`, e.apiKey && (t += `&xi-api-key=${e.apiKey}`)), new Promise((i, n) => {
|
|
143
180
|
this.ws = new WebSocket(t), this.ws.onopen = () => {
|
|
144
181
|
this.setState("listening"), i();
|
|
145
182
|
}, this.ws.onerror = () => {
|
|
@@ -205,9 +242,9 @@ class M extends S {
|
|
|
205
242
|
if (!t?.audio_base_64 || (t.event_id ?? 0) <= this.lastInterruptId)
|
|
206
243
|
return;
|
|
207
244
|
this._state !== "speaking" && (this.events.emit("turnStart", void 0), this.setState("speaking"));
|
|
208
|
-
let n =
|
|
209
|
-
this.outputSampleRate !==
|
|
210
|
-
const o = n.length / 2 /
|
|
245
|
+
let n = _(t.audio_base_64);
|
|
246
|
+
this.outputSampleRate !== h && (n = u(n, this.outputSampleRate, h)), this.events.emit("audio", n);
|
|
247
|
+
const o = n.length / 2 / h * 1e3;
|
|
211
248
|
this.turnStartTime === 0 && (this.turnStartTime = Date.now()), this.accumulatedDurationMs += o, console.debug(
|
|
212
249
|
`[ElevenLabs] audio chunk: ${n.length} bytes, +${o.toFixed(0)}ms, totalDuration=${this.accumulatedDurationMs.toFixed(0)}ms, agentResponse=${this.agentResponseReceived}`
|
|
213
250
|
), this.scheduleVirtualBufferCheck();
|
|
@@ -253,7 +290,7 @@ class M extends S {
|
|
|
253
290
|
if (t) {
|
|
254
291
|
if (t.tool_name === "set_emotion") {
|
|
255
292
|
const i = t.parameters?.emotion?.toLowerCase();
|
|
256
|
-
i &&
|
|
293
|
+
i && N.includes(i) && this.events.emit("emotion", i);
|
|
257
294
|
}
|
|
258
295
|
this.ws && this.ws.readyState === WebSocket.OPEN && this.ws.send(JSON.stringify({
|
|
259
296
|
type: "client_tool_result",
|
|
@@ -273,8 +310,8 @@ class M extends S {
|
|
|
273
310
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.initialized)
|
|
274
311
|
return;
|
|
275
312
|
let t = e;
|
|
276
|
-
this.sourceInputSampleRate !== this.expectedInputSampleRate && (t =
|
|
277
|
-
user_audio_chunk:
|
|
313
|
+
this.sourceInputSampleRate !== this.expectedInputSampleRate && (t = u(e, this.sourceInputSampleRate, this.expectedInputSampleRate)), this.ws.send(JSON.stringify({
|
|
314
|
+
user_audio_chunk: b(t)
|
|
278
315
|
}));
|
|
279
316
|
}
|
|
280
317
|
/**
|
|
@@ -307,7 +344,7 @@ class M extends S {
|
|
|
307
344
|
this.initialized = !1, this.lastInterruptId = 0, this.resetTurnState(), super.close();
|
|
308
345
|
}
|
|
309
346
|
}
|
|
310
|
-
const
|
|
347
|
+
const D = ["neutral", "angry", "sad", "happy"], j = "wss://api.openai.com/v1/realtime", U = "gpt-realtime", m = 24e3, z = {
|
|
311
348
|
type: "function",
|
|
312
349
|
name: "set_emotion",
|
|
313
350
|
description: "Set the emotional expression of the avatar. Call this on every turn to reflect the tone of your response.",
|
|
@@ -323,7 +360,7 @@ const R = ["neutral", "angry", "sad", "happy"], O = "wss://api.openai.com/v1/rea
|
|
|
323
360
|
required: ["emotion"]
|
|
324
361
|
}
|
|
325
362
|
};
|
|
326
|
-
class
|
|
363
|
+
class $ extends v {
|
|
327
364
|
agentName = "OpenAIRealtime";
|
|
328
365
|
connectResolve = null;
|
|
329
366
|
connectReject = null;
|
|
@@ -341,20 +378,20 @@ class L extends S {
|
|
|
341
378
|
if (!e.apiKey)
|
|
342
379
|
throw new Error("OpenAI Realtime token is required");
|
|
343
380
|
e.inputSampleRate && (this.sourceInputSampleRate = e.inputSampleRate);
|
|
344
|
-
const t = e.model ??
|
|
381
|
+
const t = e.model ?? U;
|
|
345
382
|
return this.initialSessionUpdate = this.buildSessionUpdate(e, t), new Promise((i, n) => {
|
|
346
383
|
this.connectResolve = i, this.connectReject = n, this.connectTimeout = setTimeout(() => {
|
|
347
384
|
this.rejectPendingConnect(new Error("Timed out waiting for OpenAI Realtime session setup")), this.close();
|
|
348
385
|
}, 1e4), this.ws = new WebSocket(
|
|
349
|
-
`${
|
|
386
|
+
`${j}?model=${encodeURIComponent(t)}`,
|
|
350
387
|
["realtime", `openai-insecure-api-key.${e.apiKey}`]
|
|
351
388
|
), this.ws.onopen = () => {
|
|
352
389
|
}, this.ws.onerror = () => {
|
|
353
390
|
this.rejectPendingConnect(new Error("Failed to connect to OpenAI Realtime"));
|
|
354
391
|
}, this.ws.onclose = (o) => {
|
|
355
392
|
if (this.clearConnectTimeout(), this.connectReject) {
|
|
356
|
-
const
|
|
357
|
-
this.rejectPendingConnect(new Error(`OpenAI Realtime closed before initialization (${o.code}${
|
|
393
|
+
const c = o.reason ? `: ${o.reason}` : "";
|
|
394
|
+
this.rejectPendingConnect(new Error(`OpenAI Realtime closed before initialization (${o.code}${c})`));
|
|
358
395
|
}
|
|
359
396
|
this.resetTurnState(), this.initialSessionUpdate = null, this.ws = null, this.setState("idle"), this.emitClosed(o.code, o.reason);
|
|
360
397
|
}, this.ws.onmessage = (o) => {
|
|
@@ -385,7 +422,7 @@ class L extends S {
|
|
|
385
422
|
}
|
|
386
423
|
this.currentResponseHasAudio = !0, this.events.emit("turnStart", void 0), this.setState("speaking");
|
|
387
424
|
}
|
|
388
|
-
this.events.emit("audio",
|
|
425
|
+
this.events.emit("audio", _(t.delta));
|
|
389
426
|
break;
|
|
390
427
|
case "response.output_audio_transcript.delta":
|
|
391
428
|
if (!t.delta)
|
|
@@ -424,9 +461,9 @@ class L extends S {
|
|
|
424
461
|
return;
|
|
425
462
|
}
|
|
426
463
|
let t = e;
|
|
427
|
-
this.sourceInputSampleRate !==
|
|
464
|
+
this.sourceInputSampleRate !== m && (t = u(e, this.sourceInputSampleRate, m)), this.ws.send(JSON.stringify({
|
|
428
465
|
type: "input_audio_buffer.append",
|
|
429
|
-
audio:
|
|
466
|
+
audio: b(t)
|
|
430
467
|
}));
|
|
431
468
|
}
|
|
432
469
|
close() {
|
|
@@ -445,19 +482,19 @@ class L extends S {
|
|
|
445
482
|
input: {
|
|
446
483
|
format: {
|
|
447
484
|
type: "audio/pcm",
|
|
448
|
-
rate:
|
|
485
|
+
rate: m
|
|
449
486
|
},
|
|
450
487
|
turn_detection: i
|
|
451
488
|
},
|
|
452
489
|
output: {
|
|
453
490
|
format: {
|
|
454
491
|
type: "audio/pcm",
|
|
455
|
-
rate:
|
|
492
|
+
rate: h
|
|
456
493
|
},
|
|
457
494
|
...e.voice ? { voice: e.voice } : {}
|
|
458
495
|
}
|
|
459
496
|
},
|
|
460
|
-
tools: [
|
|
497
|
+
tools: [z],
|
|
461
498
|
tool_choice: "auto"
|
|
462
499
|
}
|
|
463
500
|
};
|
|
@@ -470,7 +507,7 @@ class L extends S {
|
|
|
470
507
|
this.currentResponseHasAudio && this.finishAudioTurn();
|
|
471
508
|
return;
|
|
472
509
|
}
|
|
473
|
-
const t = e.output.filter(
|
|
510
|
+
const t = e.output.filter(H);
|
|
474
511
|
if (t.length > 0) {
|
|
475
512
|
this.handleFunctionCalls(t);
|
|
476
513
|
return;
|
|
@@ -503,7 +540,7 @@ class L extends S {
|
|
|
503
540
|
return { error: `Unsupported function: ${e.name}` };
|
|
504
541
|
try {
|
|
505
542
|
const i = (e.arguments ? JSON.parse(e.arguments) : {}).emotion?.toLowerCase();
|
|
506
|
-
return i &&
|
|
543
|
+
return i && D.includes(i) ? (this.events.emit("emotion", i), { result: "ok" }) : { error: "Invalid emotion" };
|
|
507
544
|
} catch {
|
|
508
545
|
return { error: "Invalid function arguments" };
|
|
509
546
|
}
|
|
@@ -533,27 +570,150 @@ class L extends S {
|
|
|
533
570
|
this.connectTimeout !== null && (clearTimeout(this.connectTimeout), this.connectTimeout = null);
|
|
534
571
|
}
|
|
535
572
|
}
|
|
536
|
-
function
|
|
573
|
+
function H(s) {
|
|
537
574
|
return s.type === "function_call";
|
|
538
575
|
}
|
|
539
|
-
const
|
|
576
|
+
const k = 16e3, W = 1e4, V = 1e4, K = ["neutral", "angry", "sad", "happy"];
|
|
577
|
+
class J extends v {
|
|
578
|
+
agentName = "KflVoiceAgent";
|
|
579
|
+
connectResolve = null;
|
|
580
|
+
connectReject = null;
|
|
581
|
+
connectTimeout = null;
|
|
582
|
+
pingInterval = null;
|
|
583
|
+
sourceInputSampleRate = M;
|
|
584
|
+
turnEnded = !1;
|
|
585
|
+
async connect(e) {
|
|
586
|
+
if (this.ws)
|
|
587
|
+
throw new Error("Already connected");
|
|
588
|
+
if (!e.wsUrl)
|
|
589
|
+
throw new Error("KFL voice agent wsUrl is required");
|
|
590
|
+
return e.inputSampleRate && (this.sourceInputSampleRate = e.inputSampleRate), new Promise((t, i) => {
|
|
591
|
+
this.connectResolve = t, this.connectReject = i, this.connectTimeout = setTimeout(() => {
|
|
592
|
+
this.rejectPendingConnect(new Error("Timed out waiting for kfl-voice-agent session.ready")), this.close();
|
|
593
|
+
}, V), this.ws = new WebSocket(e.wsUrl), this.ws.onopen = () => {
|
|
594
|
+
this.startPings(e.pingIntervalMs);
|
|
595
|
+
}, this.ws.onerror = () => {
|
|
596
|
+
this.rejectPendingConnect(new Error("Failed to connect to kfl-voice-agent"));
|
|
597
|
+
}, this.ws.onclose = (n) => {
|
|
598
|
+
if (this.stopPings(), this.connectReject) {
|
|
599
|
+
const o = n.reason ? `: ${n.reason}` : "";
|
|
600
|
+
this.rejectPendingConnect(
|
|
601
|
+
new Error(`kfl-voice-agent closed before initialization (${n.code}${o})`)
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
this.ws = null, this.setState("idle"), this.emitClosed(n.code, n.reason);
|
|
605
|
+
}, this.ws.onmessage = (n) => {
|
|
606
|
+
this.handleMessage(n.data);
|
|
607
|
+
};
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
sendAudio(e) {
|
|
611
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
612
|
+
console.warn("[KflVoiceAgent] Cannot send audio: not connected");
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
let t = e;
|
|
616
|
+
this.sourceInputSampleRate !== k && (t = u(e, this.sourceInputSampleRate, k)), this.ws.send(JSON.stringify({
|
|
617
|
+
type: "input_audio_buffer.append",
|
|
618
|
+
audio: b(t)
|
|
619
|
+
}));
|
|
620
|
+
}
|
|
621
|
+
close() {
|
|
622
|
+
this.rejectPendingConnect(new Error("Connection closed")), this.clearConnectTimeout(), this.stopPings(), super.close();
|
|
623
|
+
}
|
|
624
|
+
handleParsedMessage(e) {
|
|
625
|
+
const t = e, i = t.type;
|
|
626
|
+
if (i)
|
|
627
|
+
switch (i) {
|
|
628
|
+
case "session.ready":
|
|
629
|
+
this.setState("listening"), this.resolvePendingConnect();
|
|
630
|
+
break;
|
|
631
|
+
case "session.pong":
|
|
632
|
+
break;
|
|
633
|
+
case "turn_start":
|
|
634
|
+
this.turnEnded = !1, this.events.emit("turnStart", void 0), this.setState("speaking");
|
|
635
|
+
break;
|
|
636
|
+
case "output_audio.chunk":
|
|
637
|
+
t.audio && this.events.emit("audio", _(t.audio));
|
|
638
|
+
break;
|
|
639
|
+
case "output_audio.done":
|
|
640
|
+
this.turnEnded || (this.turnEnded = !0, this.events.emit("turnEnd", void 0));
|
|
641
|
+
break;
|
|
642
|
+
case "playback_done":
|
|
643
|
+
this.turnEnded || (this.turnEnded = !0, this.events.emit("turnEnd", void 0)), this.setState("listening");
|
|
644
|
+
break;
|
|
645
|
+
case "interrupted":
|
|
646
|
+
this.turnEnded = !0, this.events.emit("interrupted", void 0), this.setState("listening");
|
|
647
|
+
break;
|
|
648
|
+
case "user_transcript":
|
|
649
|
+
t.text && this.events.emit("transcript", {
|
|
650
|
+
role: "user",
|
|
651
|
+
text: t.text,
|
|
652
|
+
isFinal: !0
|
|
653
|
+
});
|
|
654
|
+
break;
|
|
655
|
+
case "assistant_done":
|
|
656
|
+
t.text && this.events.emit("transcript", {
|
|
657
|
+
role: "assistant",
|
|
658
|
+
text: t.text,
|
|
659
|
+
isFinal: !0
|
|
660
|
+
});
|
|
661
|
+
break;
|
|
662
|
+
case "emotion":
|
|
663
|
+
t.emotion && K.includes(t.emotion) && this.events.emit("emotion", t.emotion);
|
|
664
|
+
break;
|
|
665
|
+
case "error": {
|
|
666
|
+
const n = t.message ?? "Unknown kfl-voice-agent error";
|
|
667
|
+
this.rejectPendingConnect(new Error(n)), console.error("[KflVoiceAgent] Server error:", t);
|
|
668
|
+
break;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
startPings(e) {
|
|
673
|
+
this.stopPings();
|
|
674
|
+
const t = e ?? W;
|
|
675
|
+
this.pingInterval = setInterval(() => {
|
|
676
|
+
!this.ws || this.ws.readyState !== WebSocket.OPEN || this.ws.send(JSON.stringify({ type: "session.ping" }));
|
|
677
|
+
}, t);
|
|
678
|
+
}
|
|
679
|
+
stopPings() {
|
|
680
|
+
this.pingInterval && (clearInterval(this.pingInterval), this.pingInterval = null);
|
|
681
|
+
}
|
|
682
|
+
clearConnectTimeout() {
|
|
683
|
+
this.connectTimeout && (clearTimeout(this.connectTimeout), this.connectTimeout = null);
|
|
684
|
+
}
|
|
685
|
+
resolvePendingConnect() {
|
|
686
|
+
this.clearConnectTimeout();
|
|
687
|
+
const e = this.connectResolve;
|
|
688
|
+
this.connectResolve = null, this.connectReject = null, e?.();
|
|
689
|
+
}
|
|
690
|
+
rejectPendingConnect(e) {
|
|
691
|
+
this.clearConnectTimeout();
|
|
692
|
+
const t = this.connectReject;
|
|
693
|
+
this.connectResolve = null, this.connectReject = null, t?.(e);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const q = [
|
|
540
697
|
{ id: "elevenlabs", name: "ElevenLabs", description: "ElevenLabs Conversational AI" },
|
|
541
|
-
{ id: "openai", name: "OpenAI Realtime", description: "OpenAI Realtime API" }
|
|
698
|
+
{ id: "openai", name: "OpenAI Realtime", description: "OpenAI Realtime API" },
|
|
699
|
+
{ id: "kfl", name: "KFL Voice Agent", description: "kfl-voice-agent WebSocket API" }
|
|
542
700
|
];
|
|
543
|
-
function
|
|
701
|
+
function R(s) {
|
|
544
702
|
switch (s) {
|
|
545
703
|
case "elevenlabs":
|
|
546
|
-
return new
|
|
704
|
+
return new F();
|
|
547
705
|
case "openai":
|
|
548
|
-
return new
|
|
706
|
+
return new $();
|
|
707
|
+
case "kfl":
|
|
708
|
+
return new J();
|
|
549
709
|
default:
|
|
550
710
|
throw new Error(`Unknown agent type: ${s}`);
|
|
551
711
|
}
|
|
552
712
|
}
|
|
553
|
-
function
|
|
554
|
-
return
|
|
713
|
+
function _e(s) {
|
|
714
|
+
return q.find((e) => e.id === s);
|
|
555
715
|
}
|
|
556
|
-
class
|
|
716
|
+
class G extends Error {
|
|
557
717
|
status;
|
|
558
718
|
payload;
|
|
559
719
|
url;
|
|
@@ -561,8 +721,8 @@ class F extends Error {
|
|
|
561
721
|
super(e.message), this.name = "ApiError", this.status = e.status, this.payload = e.payload, this.url = e.url;
|
|
562
722
|
}
|
|
563
723
|
}
|
|
564
|
-
const
|
|
565
|
-
class
|
|
724
|
+
const r = /* @__PURE__ */ new Set();
|
|
725
|
+
class Y {
|
|
566
726
|
apiBaseUrl;
|
|
567
727
|
publishableKey;
|
|
568
728
|
callbacks;
|
|
@@ -606,31 +766,31 @@ class P {
|
|
|
606
766
|
}
|
|
607
767
|
/** Connect to the embed session */
|
|
608
768
|
async connect() {
|
|
609
|
-
if (
|
|
769
|
+
if (r.has(this.publishableKey)) {
|
|
610
770
|
console.log("[PersonaEmbed] Connection already in progress, skipping");
|
|
611
771
|
return;
|
|
612
772
|
}
|
|
613
|
-
|
|
773
|
+
r.add(this.publishableKey), this.mounted = !0, this.abortController = new AbortController(), this.setStatus("connecting");
|
|
614
774
|
try {
|
|
615
775
|
const e = await this.fetchSession(this.abortController.signal);
|
|
616
776
|
if (!this.mounted) {
|
|
617
|
-
|
|
777
|
+
r.delete(this.publishableKey);
|
|
618
778
|
return;
|
|
619
779
|
}
|
|
620
780
|
if (await this.initSession(e), await this.initMicrophone(), await this.connectAgent(e.voice_agent_details), !this.mounted) {
|
|
621
|
-
this.cleanup(),
|
|
781
|
+
this.cleanup(), r.delete(this.publishableKey);
|
|
622
782
|
return;
|
|
623
783
|
}
|
|
624
784
|
this.setStatus("connected");
|
|
625
785
|
} catch (e) {
|
|
626
|
-
if (
|
|
786
|
+
if (r.delete(this.publishableKey), e instanceof Error && e.name === "AbortError")
|
|
627
787
|
return;
|
|
628
788
|
console.error("[PersonaEmbed]", e), this.mounted && (this.setStatus("error"), this.callbacks.onError?.(e));
|
|
629
789
|
}
|
|
630
790
|
}
|
|
631
791
|
/** Disconnect and cleanup */
|
|
632
792
|
disconnect() {
|
|
633
|
-
this.mounted = !1, this.abortController?.abort(), this.abortController = null,
|
|
793
|
+
this.mounted = !1, this.abortController?.abort(), this.abortController = null, r.delete(this.publishableKey), this.cleanup(), this.setStatus("disconnected");
|
|
634
794
|
}
|
|
635
795
|
/** Toggle microphone mute */
|
|
636
796
|
toggleMute() {
|
|
@@ -655,7 +815,7 @@ class P {
|
|
|
655
815
|
i = await t.json();
|
|
656
816
|
} catch {
|
|
657
817
|
}
|
|
658
|
-
throw new
|
|
818
|
+
throw new G({
|
|
659
819
|
message: i?.message ?? "create_session failed",
|
|
660
820
|
status: t.status,
|
|
661
821
|
payload: i,
|
|
@@ -669,7 +829,7 @@ class P {
|
|
|
669
829
|
return t.json();
|
|
670
830
|
}
|
|
671
831
|
async initSession(e) {
|
|
672
|
-
this.session =
|
|
832
|
+
this.session = T({
|
|
673
833
|
serverUrl: e.session_details.server_url,
|
|
674
834
|
participantToken: e.session_details.participant_token,
|
|
675
835
|
agentIdentity: e.session_details.agent_identity,
|
|
@@ -681,7 +841,7 @@ class P {
|
|
|
681
841
|
});
|
|
682
842
|
},
|
|
683
843
|
onStateChange: (t) => {
|
|
684
|
-
this.mounted && t === "disconnected" && (this.
|
|
844
|
+
this.mounted && t === "disconnected" && (this.disconnect(), this.callbacks.onDisconnect?.());
|
|
685
845
|
},
|
|
686
846
|
onAgentStateChange: (t) => {
|
|
687
847
|
this.mounted && this.setAgentState(t);
|
|
@@ -692,10 +852,10 @@ class P {
|
|
|
692
852
|
onClose: () => {
|
|
693
853
|
this.mounted && this.callbacks.onDisconnect?.();
|
|
694
854
|
}
|
|
695
|
-
}), this.agent =
|
|
855
|
+
}), this.agent = R(e.voice_agent_details.type), this.agent.on("audio", (t) => this.session?.sendAudio(t)), this.agent.on("turnEnd", () => this.session?.endAudioTurn()), this.agent.on("interrupted", () => {
|
|
696
856
|
this.session?.endAudioTurn(), this.session?.interrupt();
|
|
697
857
|
}), this.agent.on("closed", () => {
|
|
698
|
-
this.mounted && this.callbacks.onDisconnect?.();
|
|
858
|
+
this.mounted && (this.disconnect(), this.callbacks.onDisconnect?.());
|
|
699
859
|
}), this.agent.on("emotion", (t) => this.session?.setEmotion(t)), await this.session.connect();
|
|
700
860
|
}
|
|
701
861
|
async initMicrophone() {
|
|
@@ -703,9 +863,9 @@ class P {
|
|
|
703
863
|
audio: { sampleRate: 16e3, echoCancellation: !0, noiseSuppression: !0 }
|
|
704
864
|
}), this.audioContext = new AudioContext({ sampleRate: 16e3 });
|
|
705
865
|
const e = this.audioContext.createMediaStreamSource(this.stream);
|
|
706
|
-
this.processor = this.audioContext
|
|
866
|
+
this.processor = await x(this.audioContext), this.processor.port.onmessage = (t) => {
|
|
707
867
|
if (!this._isMuted) {
|
|
708
|
-
const i =
|
|
868
|
+
const i = A(t.data);
|
|
709
869
|
this.agent?.sendAudio(i);
|
|
710
870
|
}
|
|
711
871
|
}, e.connect(this.processor), this.processor.connect(this.audioContext.destination);
|
|
@@ -713,24 +873,35 @@ class P {
|
|
|
713
873
|
async connectAgent(e) {
|
|
714
874
|
if (!this.agent) return;
|
|
715
875
|
const t = { inputSampleRate: 16e3 };
|
|
716
|
-
e.type === "elevenlabs"
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
876
|
+
if (e.type === "elevenlabs")
|
|
877
|
+
await this.agent.connect({
|
|
878
|
+
...t,
|
|
879
|
+
agentId: e.agent_id,
|
|
880
|
+
signedUrl: e.signed_url
|
|
881
|
+
});
|
|
882
|
+
else if (e.type === "openai")
|
|
883
|
+
await this.agent.connect({
|
|
884
|
+
...t,
|
|
885
|
+
apiKey: e.token,
|
|
886
|
+
systemPrompt: e.system_prompt,
|
|
887
|
+
voice: e.voice,
|
|
888
|
+
turnDetection: e.turn_detection
|
|
889
|
+
});
|
|
890
|
+
else if (e.type === "kfl") {
|
|
891
|
+
if (!e.signed_url)
|
|
892
|
+
throw new Error("KFL voice agent requires signed_url");
|
|
893
|
+
await this.agent.connect({
|
|
894
|
+
...t,
|
|
895
|
+
wsUrl: e.signed_url
|
|
896
|
+
});
|
|
897
|
+
}
|
|
727
898
|
}
|
|
728
899
|
cleanup() {
|
|
729
900
|
this.stream?.getTracks().forEach((e) => e.stop()), this.processor?.disconnect(), this.audioContext?.close(), this.agent?.close(), this.session?.close(), this.stream = null, this.processor = null, this.audioContext = null, this.agent = null, this.session = null;
|
|
730
901
|
}
|
|
731
902
|
}
|
|
732
|
-
const
|
|
733
|
-
class
|
|
903
|
+
const l = /* @__PURE__ */ new Set();
|
|
904
|
+
class be {
|
|
734
905
|
voiceAgentDetails;
|
|
735
906
|
sessionDetails;
|
|
736
907
|
callbacks;
|
|
@@ -774,24 +945,24 @@ class Q {
|
|
|
774
945
|
}
|
|
775
946
|
/** Connect to the session */
|
|
776
947
|
async connect() {
|
|
777
|
-
if (
|
|
948
|
+
if (l.has(this.connectionId)) {
|
|
778
949
|
console.log("[PersonaView] Connection already in progress, skipping");
|
|
779
950
|
return;
|
|
780
951
|
}
|
|
781
|
-
|
|
952
|
+
l.add(this.connectionId), this.mounted = !0, this.setStatus("connecting");
|
|
782
953
|
try {
|
|
783
954
|
if (await this.initSession(), await this.initMicrophone(), await this.connectAgent(), !this.mounted) {
|
|
784
|
-
this.cleanup(),
|
|
955
|
+
this.cleanup(), l.delete(this.connectionId);
|
|
785
956
|
return;
|
|
786
957
|
}
|
|
787
958
|
this.setStatus("connected");
|
|
788
959
|
} catch (e) {
|
|
789
|
-
|
|
960
|
+
l.delete(this.connectionId), console.error("[PersonaView]", e), this.mounted && (this.setStatus("error"), this.callbacks.onError?.(e));
|
|
790
961
|
}
|
|
791
962
|
}
|
|
792
963
|
/** Disconnect and cleanup */
|
|
793
964
|
disconnect() {
|
|
794
|
-
this.mounted = !1,
|
|
965
|
+
this.mounted = !1, l.delete(this.connectionId), this.cleanup(), this.setStatus("disconnected");
|
|
795
966
|
}
|
|
796
967
|
/** Toggle microphone mute */
|
|
797
968
|
toggleMute() {
|
|
@@ -804,7 +975,7 @@ class Q {
|
|
|
804
975
|
this._agentState !== e && (this._agentState = e, this.callbacks.onAgentStateChange?.(e));
|
|
805
976
|
}
|
|
806
977
|
async initSession() {
|
|
807
|
-
this.session =
|
|
978
|
+
this.session = T({
|
|
808
979
|
serverUrl: this.sessionDetails.server_url,
|
|
809
980
|
participantToken: this.sessionDetails.participant_token,
|
|
810
981
|
agentIdentity: this.sessionDetails.agent_identity,
|
|
@@ -816,7 +987,7 @@ class Q {
|
|
|
816
987
|
});
|
|
817
988
|
},
|
|
818
989
|
onStateChange: (e) => {
|
|
819
|
-
this.mounted && e === "disconnected" && (this.
|
|
990
|
+
this.mounted && e === "disconnected" && (this.disconnect(), this.callbacks.onDisconnect?.());
|
|
820
991
|
},
|
|
821
992
|
onAgentStateChange: (e) => {
|
|
822
993
|
this.mounted && this.setAgentState(e);
|
|
@@ -827,10 +998,10 @@ class Q {
|
|
|
827
998
|
onClose: () => {
|
|
828
999
|
this.mounted && this.callbacks.onDisconnect?.();
|
|
829
1000
|
}
|
|
830
|
-
}), this.agent =
|
|
1001
|
+
}), this.agent = R(this.voiceAgentDetails.type), this.agent.on("audio", (e) => this.session?.sendAudio(e)), this.agent.on("turnEnd", () => this.session?.endAudioTurn()), this.agent.on("interrupted", () => {
|
|
831
1002
|
this.session?.endAudioTurn(), this.session?.interrupt();
|
|
832
1003
|
}), this.agent.on("closed", () => {
|
|
833
|
-
this.mounted && this.callbacks.onDisconnect?.();
|
|
1004
|
+
this.mounted && (this.disconnect(), this.callbacks.onDisconnect?.());
|
|
834
1005
|
}), this.agent.on("emotion", (e) => this.session?.setEmotion(e)), await this.session.connect();
|
|
835
1006
|
}
|
|
836
1007
|
async initMicrophone() {
|
|
@@ -838,9 +1009,9 @@ class Q {
|
|
|
838
1009
|
audio: { sampleRate: 16e3, echoCancellation: !0, noiseSuppression: !0 }
|
|
839
1010
|
}), this.audioContext = new AudioContext({ sampleRate: 16e3 });
|
|
840
1011
|
const e = this.audioContext.createMediaStreamSource(this.stream);
|
|
841
|
-
this.processor = this.audioContext
|
|
1012
|
+
this.processor = await x(this.audioContext), this.processor.port.onmessage = (t) => {
|
|
842
1013
|
if (!this._isMuted) {
|
|
843
|
-
const i =
|
|
1014
|
+
const i = A(t.data);
|
|
844
1015
|
this.agent?.sendAudio(i);
|
|
845
1016
|
}
|
|
846
1017
|
}, e.connect(this.processor), this.processor.connect(this.audioContext.destination);
|
|
@@ -848,23 +1019,34 @@ class Q {
|
|
|
848
1019
|
async connectAgent() {
|
|
849
1020
|
if (!this.agent) return;
|
|
850
1021
|
const e = this.voiceAgentDetails, t = { inputSampleRate: 16e3 };
|
|
851
|
-
e.type === "elevenlabs"
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1022
|
+
if (e.type === "elevenlabs")
|
|
1023
|
+
await this.agent.connect({
|
|
1024
|
+
...t,
|
|
1025
|
+
agentId: e.agent_id,
|
|
1026
|
+
signedUrl: e.signed_url
|
|
1027
|
+
});
|
|
1028
|
+
else if (e.type === "openai")
|
|
1029
|
+
await this.agent.connect({
|
|
1030
|
+
...t,
|
|
1031
|
+
apiKey: e.token,
|
|
1032
|
+
systemPrompt: e.system_prompt,
|
|
1033
|
+
voice: e.voice,
|
|
1034
|
+
turnDetection: e.turn_detection
|
|
1035
|
+
});
|
|
1036
|
+
else if (e.type === "kfl") {
|
|
1037
|
+
if (!e.signed_url)
|
|
1038
|
+
throw new Error("KFL voice agent requires signed_url");
|
|
1039
|
+
await this.agent.connect({
|
|
1040
|
+
...t,
|
|
1041
|
+
wsUrl: e.signed_url
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
862
1044
|
}
|
|
863
1045
|
cleanup() {
|
|
864
1046
|
this.stream?.getTracks().forEach((e) => e.stop()), this.processor?.disconnect(), this.audioContext?.close(), this.agent?.close(), this.session?.close(), this.stream = null, this.processor = null, this.audioContext = null, this.agent = null, this.session = null;
|
|
865
1047
|
}
|
|
866
1048
|
}
|
|
867
|
-
const f = ["minimized", "active", "hidden"],
|
|
1049
|
+
const f = ["minimized", "active", "hidden"], Z = ["bottom-right", "bottom-left", "top-right", "top-left"], X = [
|
|
868
1050
|
"publishable-key",
|
|
869
1051
|
"api-base-url",
|
|
870
1052
|
"initial-state",
|
|
@@ -881,14 +1063,15 @@ const f = ["minimized", "active", "hidden"], U = ["bottom-right", "bottom-left",
|
|
|
881
1063
|
"button-color",
|
|
882
1064
|
"button-color-opacity",
|
|
883
1065
|
"video-fit",
|
|
884
|
-
"preview-image"
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1066
|
+
"preview-image",
|
|
1067
|
+
"call-to-action"
|
|
1068
|
+
], E = "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap";
|
|
1069
|
+
function Q() {
|
|
1070
|
+
if (document.querySelector(`link[href="${E}"]`)) return;
|
|
888
1071
|
const s = document.createElement("link");
|
|
889
|
-
s.rel = "stylesheet", s.href =
|
|
1072
|
+
s.rel = "stylesheet", s.href = E, document.head.appendChild(s);
|
|
890
1073
|
}
|
|
891
|
-
const
|
|
1074
|
+
const ee = `
|
|
892
1075
|
:host {
|
|
893
1076
|
display: block;
|
|
894
1077
|
font-family: 'Noto Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
@@ -1143,16 +1326,33 @@ const H = `
|
|
|
1143
1326
|
opacity: 1;
|
|
1144
1327
|
transform: translateY(0);
|
|
1145
1328
|
}
|
|
1146
|
-
`,
|
|
1147
|
-
class
|
|
1329
|
+
`, te = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>', ie = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91"/><line x1="23" y1="1" x2="1" y2="23"/></svg>', ne = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" y1="10" x2="21" y2="3"/><line x1="3" y1="21" x2="10" y2="14"/></svg>', se = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>', oe = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>', re = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>', S = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg>', ae = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="2" x2="22" y1="2" y2="22"/><path d="M18.89 13.23A7.12 7.12 0 0 0 19 12v-2"/><path d="M5 10v2a7 7 0 0 0 12 5"/><path d="M15 9.34V5a3 3 0 0 0-5.68-1.33"/><path d="M9 9v3a3 3 0 0 0 5.12 2.12"/><line x1="12" x2="12" y1="19" y2="22"/></svg>', le = "Join call", ce = 252, de = 377, he = 144, ue = 216, pe = "#676565", ge = 0.7, a = "16px", C = 40, me = `
|
|
1330
|
+
<div class="kfl-widget">
|
|
1331
|
+
<div class="kfl-widget-inner">
|
|
1332
|
+
<img class="kfl-preview-img" alt="Persona preview" draggable="false" />
|
|
1333
|
+
<div class="kfl-embed-container"></div>
|
|
1334
|
+
<div class="kfl-spinner"><div class="kfl-spinner-ring"></div></div>
|
|
1335
|
+
<div class="kfl-error-toast"></div>
|
|
1336
|
+
<button type="button" class="kfl-join-btn"></button>
|
|
1337
|
+
<div class="kfl-toolbar">
|
|
1338
|
+
<button type="button" class="kfl-mic-btn"></button>
|
|
1339
|
+
<button type="button" class="kfl-endcall-btn"></button>
|
|
1340
|
+
</div>
|
|
1341
|
+
<button type="button" class="kfl-toggle-btn"></button>
|
|
1342
|
+
</div>
|
|
1343
|
+
<button type="button" class="kfl-reveal-btn"></button>
|
|
1344
|
+
</div>
|
|
1345
|
+
`;
|
|
1346
|
+
class ve extends HTMLElement {
|
|
1148
1347
|
static get observedAttributes() {
|
|
1149
|
-
return [...
|
|
1348
|
+
return [...X];
|
|
1150
1349
|
}
|
|
1151
1350
|
shadow;
|
|
1152
1351
|
embed = null;
|
|
1153
1352
|
_widgetState = "minimized";
|
|
1154
1353
|
_connected = !1;
|
|
1155
1354
|
_connecting = !1;
|
|
1355
|
+
_initialized = !1;
|
|
1156
1356
|
widgetEl;
|
|
1157
1357
|
innerEl;
|
|
1158
1358
|
containerEl;
|
|
@@ -1166,25 +1366,43 @@ class ee extends HTMLElement {
|
|
|
1166
1366
|
revealBtn;
|
|
1167
1367
|
toolbarEl;
|
|
1168
1368
|
micBtn;
|
|
1369
|
+
_onJoinClick = (e) => {
|
|
1370
|
+
e.stopPropagation(), this._handleJoinCall();
|
|
1371
|
+
};
|
|
1372
|
+
_onEndCallClick = () => this._handleEndCall();
|
|
1373
|
+
_onMicClick = () => this._handleMicToggle();
|
|
1374
|
+
_onToggleClick = (e) => {
|
|
1375
|
+
e.stopPropagation(), this._handleToggle();
|
|
1376
|
+
};
|
|
1377
|
+
_onRevealClick = () => this._handleReveal();
|
|
1378
|
+
_onInnerClick = (e) => this._handleInnerClick(e);
|
|
1169
1379
|
constructor() {
|
|
1170
1380
|
super(), this.shadow = this.attachShadow({ mode: "open" });
|
|
1171
1381
|
}
|
|
1172
1382
|
connectedCallback() {
|
|
1173
|
-
|
|
1174
|
-
const e = document.createElement("style");
|
|
1175
|
-
e.textContent = H, this.shadow.appendChild(e), this.widgetEl = document.createElement("div"), this.widgetEl.className = "kfl-widget", this.innerEl = document.createElement("div"), this.innerEl.className = "kfl-widget-inner", this.containerEl = document.createElement("div"), this.containerEl.className = "kfl-embed-container", this.previewImg = document.createElement("img"), this.previewImg.className = "kfl-preview-img", this.previewImg.alt = "Persona preview", this.previewImg.draggable = !1;
|
|
1176
|
-
const t = this.getAttribute("preview-image");
|
|
1177
|
-
t ? (this.previewImg.src = t, this.previewImg.style.display = "block") : this.previewImg.style.display = "none", this.spinnerEl = document.createElement("div"), this.spinnerEl.className = "kfl-spinner", this.spinnerEl.innerHTML = '<div class="kfl-spinner-ring"></div>', this.errorToast = document.createElement("div"), this.errorToast.className = "kfl-error-toast", this.joinBtn = document.createElement("button"), this.joinBtn.className = "kfl-join-btn", this.joinBtn.innerHTML = `${V} Join call`, this.joinBtn.addEventListener("click", () => this._handleJoinCall()), this.endCallBtn = document.createElement("button"), this.endCallBtn.className = "kfl-endcall-btn", this.endCallBtn.innerHTML = K, this.endCallBtn.addEventListener("click", () => this._handleEndCall()), this.micBtn = document.createElement("button"), this.micBtn.className = "kfl-mic-btn", this.micBtn.innerHTML = _, this.micBtn.addEventListener("click", () => this._handleMicToggle()), this.toolbarEl = document.createElement("div"), this.toolbarEl.className = "kfl-toolbar", this.toolbarEl.appendChild(this.micBtn), this.toolbarEl.appendChild(this.endCallBtn), this.toggleBtn = document.createElement("button"), this.toggleBtn.className = "kfl-toggle-btn", this.toggleBtn.addEventListener("click", () => this._handleToggle()), this.revealBtn = document.createElement("button"), this.revealBtn.className = "kfl-reveal-btn", this.revealBtn.addEventListener("click", () => this._handleReveal()), this.innerEl.appendChild(this.previewImg), this.innerEl.appendChild(this.containerEl), this.innerEl.appendChild(this.spinnerEl), this.innerEl.appendChild(this.errorToast), this.innerEl.appendChild(this.joinBtn), this.innerEl.appendChild(this.toolbarEl), this.innerEl.appendChild(this.toggleBtn), this.widgetEl.appendChild(this.innerEl), this.widgetEl.appendChild(this.revealBtn), this.shadow.appendChild(this.widgetEl);
|
|
1178
|
-
const i = this.getAttribute("initial-state");
|
|
1179
|
-
this._widgetState = i && f.includes(i) ? i : "minimized", this._applyState();
|
|
1383
|
+
Q(), this._initialized || (this._initDom(), this._initialized = !0), this._widgetState = this._resolveInitialState(), this._applyState();
|
|
1180
1384
|
}
|
|
1181
1385
|
disconnectedCallback() {
|
|
1182
|
-
this.errorTimer && clearTimeout(this.errorTimer), this.
|
|
1386
|
+
this.errorTimer && clearTimeout(this.errorTimer), this._disconnectSession(), this._setWidgetState("minimized", { emit: !1 });
|
|
1183
1387
|
}
|
|
1184
1388
|
attributeChangedCallback(e, t, i) {
|
|
1185
|
-
|
|
1389
|
+
if (this._initialized) {
|
|
1390
|
+
if (e === "call-to-action") {
|
|
1391
|
+
this._renderJoinLabel();
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
if (e === "preview-image") {
|
|
1395
|
+
this._applyPreviewImage(i);
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (e === "controlled-widget-state") {
|
|
1399
|
+
i && f.includes(i) ? this._setWidgetState(i, { emit: !1 }) : this._applyState();
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
this._applyState();
|
|
1403
|
+
}
|
|
1186
1404
|
}
|
|
1187
|
-
// --- Public API
|
|
1405
|
+
// --- Public API ---
|
|
1188
1406
|
async mute() {
|
|
1189
1407
|
this.embed && (this.embed.audioElement.muted = !0);
|
|
1190
1408
|
}
|
|
@@ -1198,7 +1416,7 @@ class ee extends HTMLElement {
|
|
|
1198
1416
|
return this._connected;
|
|
1199
1417
|
}
|
|
1200
1418
|
async micOn() {
|
|
1201
|
-
this._connected || (this.
|
|
1419
|
+
this._connected || (this._connecting = !0, this._setWidgetState("active", { emit: !1 }), await this._connect()), this.embed && this.embed.isMuted && this.embed.toggleMute(), this._updateMicIcon();
|
|
1202
1420
|
}
|
|
1203
1421
|
async micOff() {
|
|
1204
1422
|
this.embed && !this.embed.isMuted && this.embed.toggleMute(), this._updateMicIcon();
|
|
@@ -1213,6 +1431,51 @@ class ee extends HTMLElement {
|
|
|
1213
1431
|
console.warn("[kfl-embed] setEmotion requires direct session access (not yet exposed by PersonaEmbed)");
|
|
1214
1432
|
}
|
|
1215
1433
|
// --- Internals ---
|
|
1434
|
+
_initDom() {
|
|
1435
|
+
const e = document.createElement("style");
|
|
1436
|
+
e.textContent = ee, this.shadow.appendChild(e);
|
|
1437
|
+
const t = document.createElement("template");
|
|
1438
|
+
t.innerHTML = me;
|
|
1439
|
+
const i = t.content.cloneNode(!0);
|
|
1440
|
+
this.widgetEl = this._requireElement(i, ".kfl-widget"), this.innerEl = this._requireElement(i, ".kfl-widget-inner"), this.containerEl = this._requireElement(i, ".kfl-embed-container"), this.previewImg = this._requireElement(i, ".kfl-preview-img"), this.spinnerEl = this._requireElement(i, ".kfl-spinner"), this.errorToast = this._requireElement(i, ".kfl-error-toast"), this.joinBtn = this._requireElement(i, ".kfl-join-btn"), this.toolbarEl = this._requireElement(i, ".kfl-toolbar"), this.micBtn = this._requireElement(i, ".kfl-mic-btn"), this.endCallBtn = this._requireElement(i, ".kfl-endcall-btn"), this.toggleBtn = this._requireElement(i, ".kfl-toggle-btn"), this.revealBtn = this._requireElement(i, ".kfl-reveal-btn"), this.endCallBtn.innerHTML = ie, this.micBtn.innerHTML = S, this._renderJoinLabel(), this._applyPreviewImage(this.getAttribute("preview-image")), this.joinBtn.addEventListener("click", this._onJoinClick), this.endCallBtn.addEventListener("click", this._onEndCallClick), this.micBtn.addEventListener("click", this._onMicClick), this.toggleBtn.addEventListener("click", this._onToggleClick), this.revealBtn.addEventListener("click", this._onRevealClick), this.innerEl.addEventListener("click", this._onInnerClick), this.shadow.appendChild(i);
|
|
1441
|
+
}
|
|
1442
|
+
_requireElement(e, t) {
|
|
1443
|
+
const i = e.querySelector(t);
|
|
1444
|
+
if (!i) throw new Error(`[kfl-embed] Missing template node: ${t}`);
|
|
1445
|
+
return i;
|
|
1446
|
+
}
|
|
1447
|
+
_resolveInitialState() {
|
|
1448
|
+
const e = this.getAttribute("controlled-widget-state");
|
|
1449
|
+
if (e && f.includes(e))
|
|
1450
|
+
return e;
|
|
1451
|
+
const t = this.getAttribute("initial-state");
|
|
1452
|
+
return t && f.includes(t) ? t : "minimized";
|
|
1453
|
+
}
|
|
1454
|
+
_renderJoinLabel() {
|
|
1455
|
+
this.joinBtn.innerHTML = `${te} ${this.getAttribute("call-to-action") || le}`;
|
|
1456
|
+
}
|
|
1457
|
+
_applyPreviewImage(e) {
|
|
1458
|
+
if (e) {
|
|
1459
|
+
this.previewImg.src = e, this.previewImg.style.display = this._connected ? "none" : "block";
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
this.previewImg.removeAttribute("src"), this.previewImg.style.display = "none";
|
|
1463
|
+
}
|
|
1464
|
+
_readRenderConfig() {
|
|
1465
|
+
const e = this._widgetState === "minimized", t = this._widgetState === "hidden";
|
|
1466
|
+
return {
|
|
1467
|
+
hideUI: this.hasAttribute("hide-ui"),
|
|
1468
|
+
isInline: this.hasAttribute("inline"),
|
|
1469
|
+
showMin: this._shouldShowMinimizeButton(),
|
|
1470
|
+
isMinimized: e,
|
|
1471
|
+
isHidden: t,
|
|
1472
|
+
isActive: this._widgetState === "active",
|
|
1473
|
+
canExpandFromMinimized: e && this._hasLiveSession(),
|
|
1474
|
+
corner: this._getCorner(),
|
|
1475
|
+
buttonColor: this.getAttribute("button-color") || pe,
|
|
1476
|
+
buttonOpacity: this._getAttrNum("button-color-opacity", ge)
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1216
1479
|
_getAttrNum(e, t) {
|
|
1217
1480
|
const i = this.getAttribute(e);
|
|
1218
1481
|
if (!i) return t;
|
|
@@ -1221,47 +1484,62 @@ class ee extends HTMLElement {
|
|
|
1221
1484
|
}
|
|
1222
1485
|
_getCorner() {
|
|
1223
1486
|
const e = this.getAttribute("corner");
|
|
1224
|
-
return e &&
|
|
1487
|
+
return e && Z.includes(e) ? e : "bottom-right";
|
|
1225
1488
|
}
|
|
1226
|
-
_applyLayout() {
|
|
1489
|
+
_applyLayout(e) {
|
|
1227
1490
|
if (!this.widgetEl) return;
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
this.widgetEl.style.position = "relative", this.widgetEl.style.inset = "";
|
|
1231
|
-
else {
|
|
1232
|
-
this.widgetEl.style.position = "fixed", this.widgetEl.style.top = "", this.widgetEl.style.bottom = "", this.widgetEl.style.left = "", this.widgetEl.style.right = "";
|
|
1233
|
-
const a = "16px";
|
|
1234
|
-
t.includes("bottom") && (this.widgetEl.style.bottom = a), t.includes("top") && (this.widgetEl.style.top = a), t.includes("right") && (this.widgetEl.style.right = n ? "0" : a), t.includes("left") && (this.widgetEl.style.left = n ? "0" : a);
|
|
1235
|
-
}
|
|
1491
|
+
const t = e.isHidden ? C : e.isActive ? this._getAttrNum("active-width", ce) : this._getAttrNum("minimized-width", he), i = e.isHidden ? C : e.isActive ? this._getAttrNum("active-height", de) : this._getAttrNum("minimized-height", ue);
|
|
1492
|
+
this.widgetEl.style.width = `${t}px`, this.widgetEl.style.height = `${i}px`, e.isInline ? (this.widgetEl.style.position = "relative", this.widgetEl.style.inset = "") : (this.widgetEl.style.position = "fixed", this.widgetEl.style.top = "", this.widgetEl.style.bottom = "", this.widgetEl.style.left = "", this.widgetEl.style.right = "", e.corner.includes("bottom") && (this.widgetEl.style.bottom = a), e.corner.includes("top") && (this.widgetEl.style.top = a), e.corner.includes("right") && (this.widgetEl.style.right = e.isHidden ? "0" : a), e.corner.includes("left") && (this.widgetEl.style.left = e.isHidden ? "0" : a));
|
|
1236
1493
|
}
|
|
1237
1494
|
_applyState() {
|
|
1238
1495
|
if (!this.widgetEl) return;
|
|
1239
|
-
const e = this.
|
|
1240
|
-
this.widgetEl.classList.toggle("kfl-widget--hidden",
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1496
|
+
const e = this._readRenderConfig();
|
|
1497
|
+
this.widgetEl.classList.toggle("kfl-widget--hidden", e.isHidden), this._renderJoinButton(e), this._renderToolbar(e), this._renderJoinButtonColor(e), this._renderToggleButton(e), this._renderRevealButton(e), this.innerEl.style.cursor = e.canExpandFromMinimized ? "pointer" : "", this.spinnerEl.classList.toggle("kfl-spinner--visible", this._connecting && !this._connected), this._updateMicIcon(), this._applyLayout(e);
|
|
1498
|
+
}
|
|
1499
|
+
_renderJoinButton(e) {
|
|
1500
|
+
this.joinBtn.style.display = this._connected || this._connecting || e.isHidden ? "none" : "flex";
|
|
1501
|
+
}
|
|
1502
|
+
_renderToolbar(e) {
|
|
1503
|
+
this.toolbarEl.style.display = this._connected && !e.hideUI && !e.isMinimized && !e.isHidden ? "flex" : "none";
|
|
1504
|
+
}
|
|
1505
|
+
_renderToggleButton(e) {
|
|
1506
|
+
!e.hideUI && e.isMinimized && e.showMin && !e.isInline ? (this.toggleBtn.style.display = "flex", this.toggleBtn.innerHTML = se) : !e.hideUI && !e.isMinimized && !e.isHidden && this._connected ? (this.toggleBtn.style.display = "flex", this.toggleBtn.innerHTML = ne) : this.toggleBtn.style.display = "none";
|
|
1507
|
+
}
|
|
1508
|
+
_renderRevealButton(e) {
|
|
1509
|
+
const t = e.corner.includes("right");
|
|
1510
|
+
this.revealBtn.className = `kfl-reveal-btn ${t ? "kfl-reveal-btn--right" : "kfl-reveal-btn--left"}`, this.revealBtn.innerHTML = t ? oe : re, e.isHidden && (this.revealBtn.style.top = "", this.revealBtn.style.bottom = "", e.corner.includes("bottom") ? this.revealBtn.style.bottom = a : this.revealBtn.style.top = a);
|
|
1511
|
+
}
|
|
1512
|
+
_renderJoinButtonColor(e) {
|
|
1513
|
+
const t = Math.max(0, Math.min(1, e.buttonOpacity)), i = Math.round(t * 255).toString(16).padStart(2, "0");
|
|
1514
|
+
this.joinBtn.style.backgroundColor = `${e.buttonColor}${i}`;
|
|
1245
1515
|
}
|
|
1246
1516
|
_shouldShowMinimizeButton() {
|
|
1247
1517
|
const e = this.getAttribute("controlled-show-minimize-button");
|
|
1248
1518
|
return e !== null ? e !== "false" : this.getAttribute("show-minimize-button") !== "false";
|
|
1249
1519
|
}
|
|
1250
1520
|
_handleJoinCall() {
|
|
1251
|
-
this.
|
|
1521
|
+
this._connecting = !0, this._setWidgetState("active", { emit: !1 }), this._connect();
|
|
1252
1522
|
}
|
|
1253
1523
|
_handleToggle() {
|
|
1254
1524
|
if (this._widgetState === "minimized") {
|
|
1255
1525
|
if (this.hasAttribute("inline")) return;
|
|
1256
|
-
this.
|
|
1257
|
-
} else this._widgetState === "active" &&
|
|
1258
|
-
|
|
1526
|
+
this._hasLiveSession() && this._disconnectSession(), this._setWidgetState("hidden");
|
|
1527
|
+
} else this._widgetState === "active" && this._setWidgetState("minimized");
|
|
1528
|
+
}
|
|
1529
|
+
_handleInnerClick(e) {
|
|
1530
|
+
if (this._widgetState !== "minimized" || !this._hasLiveSession()) return;
|
|
1531
|
+
const t = e.composedPath();
|
|
1532
|
+
t.includes(this.joinBtn) || t.includes(this.toggleBtn) || this._setWidgetState("active");
|
|
1259
1533
|
}
|
|
1260
1534
|
_handleEndCall() {
|
|
1261
|
-
this._widgetState === "active" && (this.
|
|
1535
|
+
this._widgetState === "active" && (this._disconnectSession(), this._setWidgetState("minimized"));
|
|
1262
1536
|
}
|
|
1263
1537
|
_handleReveal() {
|
|
1264
|
-
this.
|
|
1538
|
+
this._setWidgetState("minimized");
|
|
1539
|
+
}
|
|
1540
|
+
_setWidgetState(e, t) {
|
|
1541
|
+
const i = t?.emit ?? !0, n = this._widgetState !== e;
|
|
1542
|
+
this._widgetState = e, n && i && this.dispatchEvent(new CustomEvent("widgetstatechange", { detail: { state: e } })), this._applyState();
|
|
1265
1543
|
}
|
|
1266
1544
|
_handleMicToggle() {
|
|
1267
1545
|
this.embed && (this.embed.toggleMute(), this._updateMicIcon());
|
|
@@ -1269,15 +1547,21 @@ class ee extends HTMLElement {
|
|
|
1269
1547
|
_updateMicIcon() {
|
|
1270
1548
|
if (!this.micBtn) return;
|
|
1271
1549
|
const e = this.embed ? this.embed.isMuted : !1;
|
|
1272
|
-
this.micBtn.innerHTML = e ?
|
|
1550
|
+
this.micBtn.innerHTML = e ? ae : S;
|
|
1273
1551
|
}
|
|
1274
|
-
|
|
1552
|
+
_hasLiveSession() {
|
|
1553
|
+
return this._connected || this._connecting || !!this.embed;
|
|
1554
|
+
}
|
|
1555
|
+
_disconnectSession() {
|
|
1275
1556
|
try {
|
|
1276
1557
|
this.embed?.disconnect();
|
|
1277
1558
|
} catch (e) {
|
|
1278
1559
|
console.warn("[kfl-embed] Disconnect error:", e);
|
|
1279
1560
|
}
|
|
1280
|
-
this._connected = !1, this._connecting = !1, this.embed = null, this.containerEl.innerHTML = "", this.containerEl.removeAttribute("style"), this.
|
|
1561
|
+
this._connected = !1, this._connecting = !1, this.embed = null, this.containerEl.innerHTML = "", this.containerEl.removeAttribute("style"), this._applyPreviewImage(this.getAttribute("preview-image"));
|
|
1562
|
+
}
|
|
1563
|
+
_resetToMinimized() {
|
|
1564
|
+
this._disconnectSession(), this._setWidgetState("minimized", { emit: !1 });
|
|
1281
1565
|
}
|
|
1282
1566
|
_showError(e) {
|
|
1283
1567
|
this.errorTimer && clearTimeout(this.errorTimer), this.errorToast.textContent = e, this.errorToast.classList.add("kfl-error-toast--visible"), this.errorTimer = setTimeout(() => {
|
|
@@ -1294,7 +1578,7 @@ class ee extends HTMLElement {
|
|
|
1294
1578
|
const t = this.getAttribute("api-base-url") || void 0, i = this.getAttribute("video-fit") || "cover";
|
|
1295
1579
|
this.previewImg.style.display = "none";
|
|
1296
1580
|
try {
|
|
1297
|
-
this.embed = new
|
|
1581
|
+
this.embed = new Y({
|
|
1298
1582
|
container: this.containerEl,
|
|
1299
1583
|
publishableKey: e,
|
|
1300
1584
|
apiBaseUrl: t,
|
|
@@ -1318,20 +1602,21 @@ class ee extends HTMLElement {
|
|
|
1318
1602
|
}
|
|
1319
1603
|
}
|
|
1320
1604
|
export {
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1605
|
+
q as AGENT_REGISTRY,
|
|
1606
|
+
v as BaseAgent,
|
|
1607
|
+
F as ElevenLabsAgent,
|
|
1608
|
+
G as KeyframeApiError,
|
|
1609
|
+
J as KflAgent,
|
|
1610
|
+
ve as KflEmbedElement,
|
|
1611
|
+
$ as OpenAIRealtimeAgent,
|
|
1612
|
+
Y as PersonaEmbed,
|
|
1613
|
+
be as PersonaView,
|
|
1614
|
+
h as SAMPLE_RATE,
|
|
1615
|
+
_ as base64ToBytes,
|
|
1616
|
+
b as bytesToBase64,
|
|
1617
|
+
R as createAgent,
|
|
1618
|
+
L as createEventEmitter,
|
|
1619
|
+
A as floatTo16BitPCM,
|
|
1620
|
+
_e as getAgentInfo,
|
|
1621
|
+
u as resamplePcm
|
|
1337
1622
|
};
|