@keyframelabs/elements 0.3.0 → 0.4.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 +89 -1
- package/dist/KflEmbedElement.d.ts +55 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +582 -128
- package/dist/kfl-embed-register.d.ts +1 -0
- package/dist/kfl-embed.js +18690 -0
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import { createClient as
|
|
1
|
+
import { createClient as v } from "@keyframelabs/sdk";
|
|
2
2
|
const u = 24e3;
|
|
3
|
-
function
|
|
3
|
+
function w(s) {
|
|
4
4
|
const e = atob(s), t = new Uint8Array(e.length);
|
|
5
|
-
for (let
|
|
6
|
-
t[
|
|
5
|
+
for (let i = 0; i < e.length; i++)
|
|
6
|
+
t[i] = e.charCodeAt(i);
|
|
7
7
|
return t;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
9
|
+
function y(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 g(s, e, t) {
|
|
16
16
|
if (e === t)
|
|
17
17
|
return s;
|
|
18
|
-
const
|
|
19
|
-
for (let l = 0; l <
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
const i = new Int16Array(s.buffer, s.byteOffset, s.length / 2), n = e / t, o = Math.floor(i.length / n), r = new Int16Array(o);
|
|
19
|
+
for (let l = 0; l < o; l++) {
|
|
20
|
+
const a = l * n, c = Math.floor(a), C = Math.min(c + 1, i.length - 1), m = a - c;
|
|
21
|
+
r[l] = Math.round(
|
|
22
|
+
i[c] * (1 - m) + i[C] * m
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
|
-
return new Uint8Array(
|
|
25
|
+
return new Uint8Array(r.buffer);
|
|
26
26
|
}
|
|
27
|
-
function
|
|
27
|
+
function x() {
|
|
28
28
|
const s = /* @__PURE__ */ new Map();
|
|
29
29
|
return {
|
|
30
30
|
on(e, t) {
|
|
@@ -34,27 +34,27 @@ function C() {
|
|
|
34
34
|
s.get(e)?.delete(t);
|
|
35
35
|
},
|
|
36
36
|
emit(e, t) {
|
|
37
|
-
s.get(e)?.forEach((
|
|
37
|
+
s.get(e)?.forEach((i) => i(t));
|
|
38
38
|
},
|
|
39
39
|
removeAllListeners() {
|
|
40
40
|
s.clear();
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function
|
|
44
|
+
function E(s) {
|
|
45
45
|
const e = new Int16Array(s.length);
|
|
46
46
|
for (let t = 0; t < s.length; t++) {
|
|
47
|
-
const
|
|
48
|
-
e[t] =
|
|
47
|
+
const i = Math.max(-1, Math.min(1, s[t]));
|
|
48
|
+
e[t] = i < 0 ? i * 32768 : i * 32767;
|
|
49
49
|
}
|
|
50
50
|
return new Uint8Array(e.buffer);
|
|
51
51
|
}
|
|
52
|
-
const
|
|
53
|
-
class
|
|
52
|
+
const A = 16e3;
|
|
53
|
+
class S {
|
|
54
54
|
ws = null;
|
|
55
55
|
_state = "idle";
|
|
56
|
-
events =
|
|
57
|
-
inputSampleRate =
|
|
56
|
+
events = x();
|
|
57
|
+
inputSampleRate = A;
|
|
58
58
|
/** Current agent state */
|
|
59
59
|
get state() {
|
|
60
60
|
return this._state;
|
|
@@ -113,8 +113,8 @@ class y {
|
|
|
113
113
|
this.events.emit("closed", { code: e, reason: t });
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
-
const
|
|
117
|
-
class
|
|
116
|
+
const I = ["neutral", "angry", "sad", "happy"], T = "wss://api.elevenlabs.io/v1/convai/conversation";
|
|
117
|
+
class M extends S {
|
|
118
118
|
agentName = "ElevenLabs";
|
|
119
119
|
outputSampleRate = 24e3;
|
|
120
120
|
// Default, updated from metadata
|
|
@@ -139,15 +139,15 @@ class R extends y {
|
|
|
139
139
|
throw new Error("ElevenLabs agent ID or signed URL is required");
|
|
140
140
|
e.inputSampleRate && (this.sourceInputSampleRate = e.inputSampleRate);
|
|
141
141
|
let t;
|
|
142
|
-
return e.signedUrl ? t = e.signedUrl : (t = `${
|
|
142
|
+
return e.signedUrl ? t = e.signedUrl : (t = `${T}?agent_id=${e.agentId}`, e.apiKey && (t += `&xi-api-key=${e.apiKey}`)), new Promise((i, n) => {
|
|
143
143
|
this.ws = new WebSocket(t), this.ws.onopen = () => {
|
|
144
|
-
this.setState("listening"),
|
|
144
|
+
this.setState("listening"), i();
|
|
145
145
|
}, this.ws.onerror = () => {
|
|
146
|
-
|
|
147
|
-
}, this.ws.onclose = (
|
|
148
|
-
this.ws = null, this.setState("idle"), this.emitClosed(
|
|
149
|
-
}, this.ws.onmessage = (
|
|
150
|
-
this.handleMessage(
|
|
146
|
+
n(new Error("Failed to connect to ElevenLabs"));
|
|
147
|
+
}, this.ws.onclose = (o) => {
|
|
148
|
+
this.ws = null, this.setState("idle"), this.emitClosed(o.code, o.reason);
|
|
149
|
+
}, this.ws.onmessage = (o) => {
|
|
150
|
+
this.handleMessage(o.data);
|
|
151
151
|
};
|
|
152
152
|
});
|
|
153
153
|
}
|
|
@@ -184,12 +184,12 @@ class R extends y {
|
|
|
184
184
|
const t = e.conversation_initiation_metadata_event;
|
|
185
185
|
if (t) {
|
|
186
186
|
if (t.agent_output_audio_format) {
|
|
187
|
-
const
|
|
188
|
-
|
|
187
|
+
const i = t.agent_output_audio_format.match(/pcm_(\d+)/);
|
|
188
|
+
i && (this.outputSampleRate = parseInt(i[1], 10));
|
|
189
189
|
}
|
|
190
190
|
if (t.user_input_audio_format) {
|
|
191
|
-
const
|
|
192
|
-
|
|
191
|
+
const i = t.user_input_audio_format.match(/pcm_(\d+)/);
|
|
192
|
+
i && (this.expectedInputSampleRate = parseInt(i[1], 10));
|
|
193
193
|
}
|
|
194
194
|
this.initialized = !0;
|
|
195
195
|
}
|
|
@@ -205,11 +205,11 @@ class R extends y {
|
|
|
205
205
|
if (!t?.audio_base_64 || (t.event_id ?? 0) <= this.lastInterruptId)
|
|
206
206
|
return;
|
|
207
207
|
this._state !== "speaking" && (this.events.emit("turnStart", void 0), this.setState("speaking"));
|
|
208
|
-
let
|
|
209
|
-
this.outputSampleRate !== u && (
|
|
210
|
-
const
|
|
211
|
-
this.turnStartTime === 0 && (this.turnStartTime = Date.now()), this.accumulatedDurationMs +=
|
|
212
|
-
`[ElevenLabs] audio chunk: ${
|
|
208
|
+
let n = w(t.audio_base_64);
|
|
209
|
+
this.outputSampleRate !== u && (n = g(n, this.outputSampleRate, u)), this.events.emit("audio", n);
|
|
210
|
+
const o = n.length / 2 / u * 1e3;
|
|
211
|
+
this.turnStartTime === 0 && (this.turnStartTime = Date.now()), this.accumulatedDurationMs += o, console.debug(
|
|
212
|
+
`[ElevenLabs] audio chunk: ${n.length} bytes, +${o.toFixed(0)}ms, totalDuration=${this.accumulatedDurationMs.toFixed(0)}ms, agentResponse=${this.agentResponseReceived}`
|
|
213
213
|
), this.scheduleVirtualBufferCheck();
|
|
214
214
|
}
|
|
215
215
|
handleUserTranscript(e) {
|
|
@@ -252,8 +252,8 @@ class R extends y {
|
|
|
252
252
|
const t = e.client_tool_call;
|
|
253
253
|
if (t) {
|
|
254
254
|
if (t.tool_name === "set_emotion") {
|
|
255
|
-
const
|
|
256
|
-
|
|
255
|
+
const i = t.parameters?.emotion?.toLowerCase();
|
|
256
|
+
i && I.includes(i) && this.events.emit("emotion", i);
|
|
257
257
|
}
|
|
258
258
|
this.ws && this.ws.readyState === WebSocket.OPEN && this.ws.send(JSON.stringify({
|
|
259
259
|
type: "client_tool_result",
|
|
@@ -273,8 +273,8 @@ class R extends y {
|
|
|
273
273
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.initialized)
|
|
274
274
|
return;
|
|
275
275
|
let t = e;
|
|
276
|
-
this.sourceInputSampleRate !== this.expectedInputSampleRate && (t =
|
|
277
|
-
user_audio_chunk:
|
|
276
|
+
this.sourceInputSampleRate !== this.expectedInputSampleRate && (t = g(e, this.sourceInputSampleRate, this.expectedInputSampleRate)), this.ws.send(JSON.stringify({
|
|
277
|
+
user_audio_chunk: y(t)
|
|
278
278
|
}));
|
|
279
279
|
}
|
|
280
280
|
/**
|
|
@@ -307,7 +307,7 @@ class R extends y {
|
|
|
307
307
|
this.initialized = !1, this.lastInterruptId = 0, this.resetTurnState(), super.close();
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
|
-
const
|
|
310
|
+
const R = ["neutral", "angry", "sad", "happy"], O = "wss://api.openai.com/v1/realtime", N = "gpt-realtime", p = 24e3, B = {
|
|
311
311
|
type: "function",
|
|
312
312
|
name: "set_emotion",
|
|
313
313
|
description: "Set the emotional expression of the avatar. Call this on every turn to reflect the tone of your response.",
|
|
@@ -323,7 +323,7 @@ const T = ["neutral", "angry", "sad", "happy"], k = "wss://api.openai.com/v1/rea
|
|
|
323
323
|
required: ["emotion"]
|
|
324
324
|
}
|
|
325
325
|
};
|
|
326
|
-
class
|
|
326
|
+
class L extends S {
|
|
327
327
|
agentName = "OpenAIRealtime";
|
|
328
328
|
connectResolve = null;
|
|
329
329
|
connectReject = null;
|
|
@@ -341,24 +341,24 @@ class P extends y {
|
|
|
341
341
|
if (!e.apiKey)
|
|
342
342
|
throw new Error("OpenAI Realtime token is required");
|
|
343
343
|
e.inputSampleRate && (this.sourceInputSampleRate = e.inputSampleRate);
|
|
344
|
-
const t = e.model ??
|
|
345
|
-
return this.initialSessionUpdate = this.buildSessionUpdate(e, t), new Promise((
|
|
346
|
-
this.connectResolve =
|
|
344
|
+
const t = e.model ?? N;
|
|
345
|
+
return this.initialSessionUpdate = this.buildSessionUpdate(e, t), new Promise((i, n) => {
|
|
346
|
+
this.connectResolve = i, this.connectReject = n, this.connectTimeout = setTimeout(() => {
|
|
347
347
|
this.rejectPendingConnect(new Error("Timed out waiting for OpenAI Realtime session setup")), this.close();
|
|
348
348
|
}, 1e4), this.ws = new WebSocket(
|
|
349
|
-
`${
|
|
349
|
+
`${O}?model=${encodeURIComponent(t)}`,
|
|
350
350
|
["realtime", `openai-insecure-api-key.${e.apiKey}`]
|
|
351
351
|
), this.ws.onopen = () => {
|
|
352
352
|
}, this.ws.onerror = () => {
|
|
353
353
|
this.rejectPendingConnect(new Error("Failed to connect to OpenAI Realtime"));
|
|
354
|
-
}, this.ws.onclose = (
|
|
354
|
+
}, this.ws.onclose = (o) => {
|
|
355
355
|
if (this.clearConnectTimeout(), this.connectReject) {
|
|
356
|
-
const
|
|
357
|
-
this.rejectPendingConnect(new Error(`OpenAI Realtime closed before initialization (${
|
|
356
|
+
const r = o.reason ? `: ${o.reason}` : "";
|
|
357
|
+
this.rejectPendingConnect(new Error(`OpenAI Realtime closed before initialization (${o.code}${r})`));
|
|
358
358
|
}
|
|
359
|
-
this.resetTurnState(), this.initialSessionUpdate = null, this.ws = null, this.setState("idle"), this.emitClosed(
|
|
360
|
-
}, this.ws.onmessage = (
|
|
361
|
-
this.handleMessage(
|
|
359
|
+
this.resetTurnState(), this.initialSessionUpdate = null, this.ws = null, this.setState("idle"), this.emitClosed(o.code, o.reason);
|
|
360
|
+
}, this.ws.onmessage = (o) => {
|
|
361
|
+
this.handleMessage(o.data);
|
|
362
362
|
};
|
|
363
363
|
});
|
|
364
364
|
}
|
|
@@ -377,15 +377,15 @@ class P extends y {
|
|
|
377
377
|
return;
|
|
378
378
|
if (!this.currentResponseHasAudio) {
|
|
379
379
|
if (this.pendingFunctionCallStartedAtMs !== null) {
|
|
380
|
-
const
|
|
380
|
+
const i = performance.now() - this.pendingFunctionCallStartedAtMs;
|
|
381
381
|
console.debug("[OpenAIRealtime] Function call latency", {
|
|
382
382
|
calls: this.pendingFunctionCallNames,
|
|
383
|
-
latencyMs: Math.round(
|
|
383
|
+
latencyMs: Math.round(i)
|
|
384
384
|
}), this.pendingFunctionCallStartedAtMs = null, this.pendingFunctionCallNames = [];
|
|
385
385
|
}
|
|
386
386
|
this.currentResponseHasAudio = !0, this.events.emit("turnStart", void 0), this.setState("speaking");
|
|
387
387
|
}
|
|
388
|
-
this.events.emit("audio",
|
|
388
|
+
this.events.emit("audio", w(t.delta));
|
|
389
389
|
break;
|
|
390
390
|
case "response.output_audio_transcript.delta":
|
|
391
391
|
if (!t.delta)
|
|
@@ -412,8 +412,8 @@ class P extends y {
|
|
|
412
412
|
this.handleResponseDone(t.response);
|
|
413
413
|
break;
|
|
414
414
|
case "error": {
|
|
415
|
-
const
|
|
416
|
-
this.rejectPendingConnect(new Error(
|
|
415
|
+
const i = t.error?.message ?? t.message ?? "Unknown OpenAI Realtime error";
|
|
416
|
+
this.rejectPendingConnect(new Error(i)), console.error("[OpenAIRealtime] Server error:", t);
|
|
417
417
|
break;
|
|
418
418
|
}
|
|
419
419
|
}
|
|
@@ -424,16 +424,16 @@ class P extends y {
|
|
|
424
424
|
return;
|
|
425
425
|
}
|
|
426
426
|
let t = e;
|
|
427
|
-
this.sourceInputSampleRate !==
|
|
427
|
+
this.sourceInputSampleRate !== p && (t = g(e, this.sourceInputSampleRate, p)), this.ws.send(JSON.stringify({
|
|
428
428
|
type: "input_audio_buffer.append",
|
|
429
|
-
audio:
|
|
429
|
+
audio: y(t)
|
|
430
430
|
}));
|
|
431
431
|
}
|
|
432
432
|
close() {
|
|
433
433
|
this.rejectPendingConnect(new Error("Connection closed")), this.clearConnectTimeout(), this.resetTurnState(), this.initialSessionUpdate = null, this.handledFunctionCallIds.clear(), super.close();
|
|
434
434
|
}
|
|
435
435
|
buildSessionUpdate(e, t) {
|
|
436
|
-
const
|
|
436
|
+
const i = e.turnDetection ?? { type: "semantic_vad", eagerness: "high" };
|
|
437
437
|
return {
|
|
438
438
|
type: "session.update",
|
|
439
439
|
session: {
|
|
@@ -445,9 +445,9 @@ class P extends y {
|
|
|
445
445
|
input: {
|
|
446
446
|
format: {
|
|
447
447
|
type: "audio/pcm",
|
|
448
|
-
rate:
|
|
448
|
+
rate: p
|
|
449
449
|
},
|
|
450
|
-
turn_detection:
|
|
450
|
+
turn_detection: i
|
|
451
451
|
},
|
|
452
452
|
output: {
|
|
453
453
|
format: {
|
|
@@ -457,7 +457,7 @@ class P extends y {
|
|
|
457
457
|
...e.voice ? { voice: e.voice } : {}
|
|
458
458
|
}
|
|
459
459
|
},
|
|
460
|
-
tools: [
|
|
460
|
+
tools: [B],
|
|
461
461
|
tool_choice: "auto"
|
|
462
462
|
}
|
|
463
463
|
};
|
|
@@ -470,7 +470,7 @@ class P extends y {
|
|
|
470
470
|
this.currentResponseHasAudio && this.finishAudioTurn();
|
|
471
471
|
return;
|
|
472
472
|
}
|
|
473
|
-
const t = e.output.filter(
|
|
473
|
+
const t = e.output.filter(D);
|
|
474
474
|
if (t.length > 0) {
|
|
475
475
|
this.handleFunctionCalls(t);
|
|
476
476
|
return;
|
|
@@ -479,31 +479,31 @@ class P extends y {
|
|
|
479
479
|
}
|
|
480
480
|
handleFunctionCalls(e) {
|
|
481
481
|
let t = !1;
|
|
482
|
-
const
|
|
483
|
-
for (const
|
|
484
|
-
if (!
|
|
482
|
+
const i = [];
|
|
483
|
+
for (const n of e) {
|
|
484
|
+
if (!n.call_id || this.handledFunctionCallIds.has(n.call_id))
|
|
485
485
|
continue;
|
|
486
|
-
this.handledFunctionCallIds.add(
|
|
487
|
-
const
|
|
486
|
+
this.handledFunctionCallIds.add(n.call_id), i.push(n.name ?? "unknown");
|
|
487
|
+
const o = this.handleFunctionCall(n);
|
|
488
488
|
this.sendEvent({
|
|
489
489
|
type: "conversation.item.create",
|
|
490
490
|
item: {
|
|
491
491
|
type: "function_call_output",
|
|
492
|
-
call_id:
|
|
493
|
-
output: JSON.stringify(
|
|
492
|
+
call_id: n.call_id,
|
|
493
|
+
output: JSON.stringify(o)
|
|
494
494
|
}
|
|
495
495
|
}), t = !0;
|
|
496
496
|
}
|
|
497
|
-
t && (this.pendingFunctionCallStartedAtMs = performance.now(), this.pendingFunctionCallNames =
|
|
498
|
-
calls:
|
|
497
|
+
t && (this.pendingFunctionCallStartedAtMs = performance.now(), this.pendingFunctionCallNames = i, console.debug("[OpenAIRealtime] Function call received", {
|
|
498
|
+
calls: i
|
|
499
499
|
}), this.sendEvent({ type: "response.create" }));
|
|
500
500
|
}
|
|
501
501
|
handleFunctionCall(e) {
|
|
502
502
|
if (e.name !== "set_emotion")
|
|
503
503
|
return { error: `Unsupported function: ${e.name}` };
|
|
504
504
|
try {
|
|
505
|
-
const
|
|
506
|
-
return
|
|
505
|
+
const i = (e.arguments ? JSON.parse(e.arguments) : {}).emotion?.toLowerCase();
|
|
506
|
+
return i && R.includes(i) ? (this.events.emit("emotion", i), { result: "ok" }) : { error: "Invalid emotion" };
|
|
507
507
|
} catch {
|
|
508
508
|
return { error: "Invalid function arguments" };
|
|
509
509
|
}
|
|
@@ -533,25 +533,25 @@ class P extends y {
|
|
|
533
533
|
this.connectTimeout !== null && (clearTimeout(this.connectTimeout), this.connectTimeout = null);
|
|
534
534
|
}
|
|
535
535
|
}
|
|
536
|
-
function
|
|
536
|
+
function D(s) {
|
|
537
537
|
return s.type === "function_call";
|
|
538
538
|
}
|
|
539
|
-
const
|
|
539
|
+
const j = [
|
|
540
540
|
{ id: "elevenlabs", name: "ElevenLabs", description: "ElevenLabs Conversational AI" },
|
|
541
541
|
{ id: "openai", name: "OpenAI Realtime", description: "OpenAI Realtime API" }
|
|
542
542
|
];
|
|
543
|
-
function
|
|
543
|
+
function k(s) {
|
|
544
544
|
switch (s) {
|
|
545
545
|
case "elevenlabs":
|
|
546
|
-
return new
|
|
546
|
+
return new M();
|
|
547
547
|
case "openai":
|
|
548
|
-
return new
|
|
548
|
+
return new L();
|
|
549
549
|
default:
|
|
550
550
|
throw new Error(`Unknown agent type: ${s}`);
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
|
-
function
|
|
554
|
-
return
|
|
553
|
+
function X(s) {
|
|
554
|
+
return j.find((e) => e.id === s);
|
|
555
555
|
}
|
|
556
556
|
class F extends Error {
|
|
557
557
|
status;
|
|
@@ -561,8 +561,8 @@ class F extends Error {
|
|
|
561
561
|
super(e.message), this.name = "ApiError", this.status = e.status, this.payload = e.payload, this.url = e.url;
|
|
562
562
|
}
|
|
563
563
|
}
|
|
564
|
-
const
|
|
565
|
-
class
|
|
564
|
+
const d = /* @__PURE__ */ new Set();
|
|
565
|
+
class P {
|
|
566
566
|
apiBaseUrl;
|
|
567
567
|
publishableKey;
|
|
568
568
|
callbacks;
|
|
@@ -606,31 +606,31 @@ class L {
|
|
|
606
606
|
}
|
|
607
607
|
/** Connect to the embed session */
|
|
608
608
|
async connect() {
|
|
609
|
-
if (
|
|
609
|
+
if (d.has(this.publishableKey)) {
|
|
610
610
|
console.log("[PersonaEmbed] Connection already in progress, skipping");
|
|
611
611
|
return;
|
|
612
612
|
}
|
|
613
|
-
|
|
613
|
+
d.add(this.publishableKey), this.mounted = !0, this.abortController = new AbortController(), this.setStatus("connecting");
|
|
614
614
|
try {
|
|
615
615
|
const e = await this.fetchSession(this.abortController.signal);
|
|
616
616
|
if (!this.mounted) {
|
|
617
|
-
|
|
617
|
+
d.delete(this.publishableKey);
|
|
618
618
|
return;
|
|
619
619
|
}
|
|
620
620
|
if (await this.initSession(e), await this.initMicrophone(), await this.connectAgent(e.voice_agent_details), !this.mounted) {
|
|
621
|
-
this.cleanup(),
|
|
621
|
+
this.cleanup(), d.delete(this.publishableKey);
|
|
622
622
|
return;
|
|
623
623
|
}
|
|
624
624
|
this.setStatus("connected");
|
|
625
625
|
} catch (e) {
|
|
626
|
-
if (
|
|
626
|
+
if (d.delete(this.publishableKey), e instanceof Error && e.name === "AbortError")
|
|
627
627
|
return;
|
|
628
628
|
console.error("[PersonaEmbed]", e), this.mounted && (this.setStatus("error"), this.callbacks.onError?.(e));
|
|
629
629
|
}
|
|
630
630
|
}
|
|
631
631
|
/** Disconnect and cleanup */
|
|
632
632
|
disconnect() {
|
|
633
|
-
this.mounted = !1, this.abortController?.abort(), this.abortController = null,
|
|
633
|
+
this.mounted = !1, this.abortController?.abort(), this.abortController = null, d.delete(this.publishableKey), this.cleanup(), this.setStatus("disconnected");
|
|
634
634
|
}
|
|
635
635
|
/** Toggle microphone mute */
|
|
636
636
|
toggleMute() {
|
|
@@ -650,31 +650,31 @@ class L {
|
|
|
650
650
|
signal: e
|
|
651
651
|
});
|
|
652
652
|
if (!t.ok) {
|
|
653
|
-
let
|
|
653
|
+
let i;
|
|
654
654
|
try {
|
|
655
|
-
|
|
655
|
+
i = await t.json();
|
|
656
656
|
} catch {
|
|
657
657
|
}
|
|
658
658
|
throw new F({
|
|
659
|
-
message:
|
|
659
|
+
message: i?.message ?? "create_session failed",
|
|
660
660
|
status: t.status,
|
|
661
|
-
payload:
|
|
661
|
+
payload: i,
|
|
662
662
|
url: t.url
|
|
663
663
|
});
|
|
664
664
|
}
|
|
665
665
|
if (!t.ok) {
|
|
666
|
-
const
|
|
667
|
-
throw new Error(`create_session failed: ${t.status} ${JSON.stringify(
|
|
666
|
+
const i = await t.json().catch(() => null);
|
|
667
|
+
throw new Error(`create_session failed: ${t.status} ${JSON.stringify(i)}`);
|
|
668
668
|
}
|
|
669
669
|
return t.json();
|
|
670
670
|
}
|
|
671
671
|
async initSession(e) {
|
|
672
|
-
this.session =
|
|
672
|
+
this.session = v({
|
|
673
673
|
serverUrl: e.session_details.server_url,
|
|
674
674
|
participantToken: e.session_details.participant_token,
|
|
675
675
|
agentIdentity: e.session_details.agent_identity,
|
|
676
676
|
onVideoTrack: (t) => {
|
|
677
|
-
console.log("[PersonaEmbed] Setting video track", t.readyState, t.enabled), this._video.srcObject = new MediaStream([t]), this._video.play().catch((
|
|
677
|
+
console.log("[PersonaEmbed] Setting video track", t.readyState, t.enabled), this._video.srcObject = new MediaStream([t]), this._video.play().catch((i) => console.warn("[PersonaEmbed] Video play failed:", i));
|
|
678
678
|
},
|
|
679
679
|
onAudioTrack: (t) => {
|
|
680
680
|
this._audio.srcObject = new MediaStream([t]), this._audio.play().catch(() => {
|
|
@@ -692,7 +692,7 @@ class L {
|
|
|
692
692
|
onClose: () => {
|
|
693
693
|
this.mounted && this.callbacks.onDisconnect?.();
|
|
694
694
|
}
|
|
695
|
-
}), this.agent =
|
|
695
|
+
}), this.agent = k(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
696
|
this.session?.endAudioTurn(), this.session?.interrupt();
|
|
697
697
|
}), this.agent.on("closed", () => {
|
|
698
698
|
this.mounted && this.callbacks.onDisconnect?.();
|
|
@@ -705,8 +705,8 @@ class L {
|
|
|
705
705
|
const e = this.audioContext.createMediaStreamSource(this.stream);
|
|
706
706
|
this.processor = this.audioContext.createScriptProcessor(4096, 1, 1), this.processor.onaudioprocess = (t) => {
|
|
707
707
|
if (!this._isMuted) {
|
|
708
|
-
const
|
|
709
|
-
this.agent?.sendAudio(
|
|
708
|
+
const i = E(t.inputBuffer.getChannelData(0));
|
|
709
|
+
this.agent?.sendAudio(i);
|
|
710
710
|
}
|
|
711
711
|
}, e.connect(this.processor), this.processor.connect(this.audioContext.destination);
|
|
712
712
|
}
|
|
@@ -729,8 +729,8 @@ class L {
|
|
|
729
729
|
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
730
|
}
|
|
731
731
|
}
|
|
732
|
-
const
|
|
733
|
-
class
|
|
732
|
+
const h = /* @__PURE__ */ new Set();
|
|
733
|
+
class Q {
|
|
734
734
|
voiceAgentDetails;
|
|
735
735
|
sessionDetails;
|
|
736
736
|
callbacks;
|
|
@@ -774,24 +774,24 @@ class $ {
|
|
|
774
774
|
}
|
|
775
775
|
/** Connect to the session */
|
|
776
776
|
async connect() {
|
|
777
|
-
if (
|
|
777
|
+
if (h.has(this.connectionId)) {
|
|
778
778
|
console.log("[PersonaView] Connection already in progress, skipping");
|
|
779
779
|
return;
|
|
780
780
|
}
|
|
781
|
-
|
|
781
|
+
h.add(this.connectionId), this.mounted = !0, this.setStatus("connecting");
|
|
782
782
|
try {
|
|
783
783
|
if (await this.initSession(), await this.initMicrophone(), await this.connectAgent(), !this.mounted) {
|
|
784
|
-
this.cleanup(),
|
|
784
|
+
this.cleanup(), h.delete(this.connectionId);
|
|
785
785
|
return;
|
|
786
786
|
}
|
|
787
787
|
this.setStatus("connected");
|
|
788
788
|
} catch (e) {
|
|
789
|
-
|
|
789
|
+
h.delete(this.connectionId), console.error("[PersonaView]", e), this.mounted && (this.setStatus("error"), this.callbacks.onError?.(e));
|
|
790
790
|
}
|
|
791
791
|
}
|
|
792
792
|
/** Disconnect and cleanup */
|
|
793
793
|
disconnect() {
|
|
794
|
-
this.mounted = !1,
|
|
794
|
+
this.mounted = !1, h.delete(this.connectionId), this.cleanup(), this.setStatus("disconnected");
|
|
795
795
|
}
|
|
796
796
|
/** Toggle microphone mute */
|
|
797
797
|
toggleMute() {
|
|
@@ -804,7 +804,7 @@ class $ {
|
|
|
804
804
|
this._agentState !== e && (this._agentState = e, this.callbacks.onAgentStateChange?.(e));
|
|
805
805
|
}
|
|
806
806
|
async initSession() {
|
|
807
|
-
this.session =
|
|
807
|
+
this.session = v({
|
|
808
808
|
serverUrl: this.sessionDetails.server_url,
|
|
809
809
|
participantToken: this.sessionDetails.participant_token,
|
|
810
810
|
agentIdentity: this.sessionDetails.agent_identity,
|
|
@@ -827,7 +827,7 @@ class $ {
|
|
|
827
827
|
onClose: () => {
|
|
828
828
|
this.mounted && this.callbacks.onDisconnect?.();
|
|
829
829
|
}
|
|
830
|
-
}), this.agent =
|
|
830
|
+
}), this.agent = k(this.voiceAgentDetails.type), this.agent.on("audio", (e) => this.session?.sendAudio(e)), this.agent.on("turnEnd", () => this.session?.endAudioTurn()), this.agent.on("interrupted", () => {
|
|
831
831
|
this.session?.endAudioTurn(), this.session?.interrupt();
|
|
832
832
|
}), this.agent.on("closed", () => {
|
|
833
833
|
this.mounted && this.callbacks.onDisconnect?.();
|
|
@@ -840,8 +840,8 @@ class $ {
|
|
|
840
840
|
const e = this.audioContext.createMediaStreamSource(this.stream);
|
|
841
841
|
this.processor = this.audioContext.createScriptProcessor(4096, 1, 1), this.processor.onaudioprocess = (t) => {
|
|
842
842
|
if (!this._isMuted) {
|
|
843
|
-
const
|
|
844
|
-
this.agent?.sendAudio(
|
|
843
|
+
const i = E(t.inputBuffer.getChannelData(0));
|
|
844
|
+
this.agent?.sendAudio(i);
|
|
845
845
|
}
|
|
846
846
|
}, e.connect(this.processor), this.processor.connect(this.audioContext.destination);
|
|
847
847
|
}
|
|
@@ -864,20 +864,474 @@ class $ {
|
|
|
864
864
|
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
865
|
}
|
|
866
866
|
}
|
|
867
|
+
const f = ["minimized", "active", "hidden"], U = ["bottom-right", "bottom-left", "top-right", "top-left"], z = [
|
|
868
|
+
"publishable-key",
|
|
869
|
+
"api-base-url",
|
|
870
|
+
"initial-state",
|
|
871
|
+
"controlled-widget-state",
|
|
872
|
+
"active-width",
|
|
873
|
+
"active-height",
|
|
874
|
+
"minimized-width",
|
|
875
|
+
"minimized-height",
|
|
876
|
+
"inline",
|
|
877
|
+
"corner",
|
|
878
|
+
"hide-ui",
|
|
879
|
+
"show-minimize-button",
|
|
880
|
+
"controlled-show-minimize-button",
|
|
881
|
+
"button-color",
|
|
882
|
+
"button-color-opacity",
|
|
883
|
+
"video-fit",
|
|
884
|
+
"preview-image"
|
|
885
|
+
], b = "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap";
|
|
886
|
+
function $() {
|
|
887
|
+
if (document.querySelector(`link[href="${b}"]`)) return;
|
|
888
|
+
const s = document.createElement("link");
|
|
889
|
+
s.rel = "stylesheet", s.href = b, document.head.appendChild(s);
|
|
890
|
+
}
|
|
891
|
+
const H = `
|
|
892
|
+
:host {
|
|
893
|
+
display: block;
|
|
894
|
+
font-family: 'Noto Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.kfl-widget {
|
|
898
|
+
position: fixed;
|
|
899
|
+
z-index: 2147483647;
|
|
900
|
+
transition: width 0.2s ease, height 0.2s ease;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
:host([inline]) .kfl-widget {
|
|
904
|
+
position: relative;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
.kfl-widget--hidden .kfl-widget-inner {
|
|
908
|
+
display: none;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.kfl-reveal-btn {
|
|
912
|
+
position: absolute;
|
|
913
|
+
width: 40px;
|
|
914
|
+
height: 40px;
|
|
915
|
+
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
916
|
+
background: rgba(30, 30, 30, 0.95);
|
|
917
|
+
color: rgba(255, 255, 255, 0.7);
|
|
918
|
+
cursor: pointer;
|
|
919
|
+
display: none;
|
|
920
|
+
align-items: center;
|
|
921
|
+
justify-content: center;
|
|
922
|
+
transition: background 0.15s, color 0.15s;
|
|
923
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
.kfl-reveal-btn:hover {
|
|
927
|
+
background: rgba(50, 50, 50, 0.95);
|
|
928
|
+
color: #fff;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.kfl-reveal-btn svg {
|
|
932
|
+
width: 16px;
|
|
933
|
+
height: 16px;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
.kfl-reveal-btn--left {
|
|
937
|
+
border-radius: 0 50% 50% 0;
|
|
938
|
+
left: 0;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
.kfl-reveal-btn--right {
|
|
942
|
+
border-radius: 50% 0 0 50%;
|
|
943
|
+
right: 0;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
.kfl-widget--hidden .kfl-reveal-btn {
|
|
947
|
+
display: flex;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
.kfl-widget-inner {
|
|
951
|
+
width: 100%;
|
|
952
|
+
height: 100%;
|
|
953
|
+
border-radius: 12px;
|
|
954
|
+
overflow: hidden;
|
|
955
|
+
position: relative;
|
|
956
|
+
background: #000;
|
|
957
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.kfl-join-btn {
|
|
961
|
+
position: absolute;
|
|
962
|
+
bottom: 8px;
|
|
963
|
+
left: 8px;
|
|
964
|
+
right: 8px;
|
|
965
|
+
z-index: 3;
|
|
966
|
+
display: flex;
|
|
967
|
+
align-items: center;
|
|
968
|
+
justify-content: center;
|
|
969
|
+
gap: 6px;
|
|
970
|
+
padding: 6px 12px;
|
|
971
|
+
border-radius: 999px;
|
|
972
|
+
border: none;
|
|
973
|
+
color: #fff;
|
|
974
|
+
font-size: 12px;
|
|
975
|
+
font-weight: 500;
|
|
976
|
+
cursor: pointer;
|
|
977
|
+
transition: opacity 0.15s;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.kfl-join-btn:hover {
|
|
981
|
+
opacity: 0.9;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.kfl-join-btn svg {
|
|
985
|
+
width: 14px;
|
|
986
|
+
height: 14px;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.kfl-toolbar {
|
|
990
|
+
position: absolute;
|
|
991
|
+
bottom: 12px;
|
|
992
|
+
left: 50%;
|
|
993
|
+
transform: translateX(-50%);
|
|
994
|
+
z-index: 3;
|
|
995
|
+
display: none;
|
|
996
|
+
align-items: center;
|
|
997
|
+
justify-content: center;
|
|
998
|
+
gap: 8px;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.kfl-mic-btn {
|
|
1002
|
+
display: flex;
|
|
1003
|
+
align-items: center;
|
|
1004
|
+
justify-content: center;
|
|
1005
|
+
width: 40px;
|
|
1006
|
+
height: 40px;
|
|
1007
|
+
border-radius: 9999px;
|
|
1008
|
+
border: none;
|
|
1009
|
+
background: rgba(0, 0, 0, 0.4);
|
|
1010
|
+
backdrop-filter: blur(4px);
|
|
1011
|
+
color: rgba(255, 255, 255, 0.8);
|
|
1012
|
+
cursor: pointer;
|
|
1013
|
+
transition: background 0.15s, color 0.15s;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
.kfl-mic-btn:hover {
|
|
1017
|
+
background: rgba(0, 0, 0, 0.6);
|
|
1018
|
+
color: #fff;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
.kfl-mic-btn svg {
|
|
1022
|
+
width: 20px;
|
|
1023
|
+
height: 20px;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
.kfl-endcall-btn {
|
|
1027
|
+
display: flex;
|
|
1028
|
+
align-items: center;
|
|
1029
|
+
justify-content: center;
|
|
1030
|
+
width: 40px;
|
|
1031
|
+
height: 40px;
|
|
1032
|
+
border-radius: 9999px;
|
|
1033
|
+
border: none;
|
|
1034
|
+
background-color: #dc2626;
|
|
1035
|
+
color: #fff;
|
|
1036
|
+
cursor: pointer;
|
|
1037
|
+
box-shadow: 0 10px 15px -3px rgba(127, 29, 29, 0.2);
|
|
1038
|
+
transition: background-color 0.15s, box-shadow 0.15s;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
.kfl-endcall-btn:hover {
|
|
1042
|
+
background-color: #b91c1c;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
.kfl-endcall-btn svg {
|
|
1046
|
+
width: 20px;
|
|
1047
|
+
height: 20px;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
.kfl-toggle-btn {
|
|
1051
|
+
position: absolute;
|
|
1052
|
+
top: 6px;
|
|
1053
|
+
right: 6px;
|
|
1054
|
+
z-index: 3;
|
|
1055
|
+
width: 24px;
|
|
1056
|
+
height: 24px;
|
|
1057
|
+
border-radius: 50%;
|
|
1058
|
+
background: rgba(0, 0, 0, 0.4);
|
|
1059
|
+
backdrop-filter: blur(4px);
|
|
1060
|
+
border: none;
|
|
1061
|
+
color: rgba(255, 255, 255, 0.8);
|
|
1062
|
+
cursor: pointer;
|
|
1063
|
+
display: flex;
|
|
1064
|
+
align-items: center;
|
|
1065
|
+
justify-content: center;
|
|
1066
|
+
transition: background 0.15s, color 0.15s;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.kfl-toggle-btn:hover {
|
|
1070
|
+
background: rgba(0, 0, 0, 0.6);
|
|
1071
|
+
color: #fff;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.kfl-toggle-btn svg {
|
|
1075
|
+
width: 12px;
|
|
1076
|
+
height: 12px;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
.kfl-embed-container {
|
|
1080
|
+
position: relative;
|
|
1081
|
+
width: 100%;
|
|
1082
|
+
height: 100%;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
.kfl-preview-img {
|
|
1086
|
+
position: absolute;
|
|
1087
|
+
inset: 0;
|
|
1088
|
+
width: 100%;
|
|
1089
|
+
height: 100%;
|
|
1090
|
+
object-fit: cover;
|
|
1091
|
+
pointer-events: none;
|
|
1092
|
+
z-index: 1;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.kfl-spinner {
|
|
1096
|
+
position: absolute;
|
|
1097
|
+
inset: 0;
|
|
1098
|
+
z-index: 2;
|
|
1099
|
+
display: none;
|
|
1100
|
+
align-items: center;
|
|
1101
|
+
justify-content: center;
|
|
1102
|
+
background: rgba(0, 0, 0, 0.3);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.kfl-spinner--visible {
|
|
1106
|
+
display: flex;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
.kfl-spinner-ring {
|
|
1110
|
+
width: 28px;
|
|
1111
|
+
height: 28px;
|
|
1112
|
+
border: 2.5px solid rgba(255, 255, 255, 0.2);
|
|
1113
|
+
border-top-color: #fff;
|
|
1114
|
+
border-radius: 50%;
|
|
1115
|
+
animation: kfl-spin 0.7s linear infinite;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
@keyframes kfl-spin {
|
|
1119
|
+
to { transform: rotate(360deg); }
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
.kfl-error-toast {
|
|
1123
|
+
position: absolute;
|
|
1124
|
+
bottom: 44px;
|
|
1125
|
+
left: 8px;
|
|
1126
|
+
right: 8px;
|
|
1127
|
+
padding: 6px 10px;
|
|
1128
|
+
border-radius: 8px;
|
|
1129
|
+
background: rgba(220, 38, 38, 0.85);
|
|
1130
|
+
backdrop-filter: blur(4px);
|
|
1131
|
+
color: #fff;
|
|
1132
|
+
font-size: 11px;
|
|
1133
|
+
line-height: 1.4;
|
|
1134
|
+
text-align: center;
|
|
1135
|
+
opacity: 0;
|
|
1136
|
+
transform: translateY(4px);
|
|
1137
|
+
transition: opacity 0.2s, transform 0.2s;
|
|
1138
|
+
pointer-events: none;
|
|
1139
|
+
z-index: 5;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.kfl-error-toast--visible {
|
|
1143
|
+
opacity: 1;
|
|
1144
|
+
transform: translateY(0);
|
|
1145
|
+
}
|
|
1146
|
+
`, V = '<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>', K = '<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>', J = '<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>', W = '<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>', q = '<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>', G = '<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>', _ = '<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>', Y = '<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>';
|
|
1147
|
+
class ee extends HTMLElement {
|
|
1148
|
+
static get observedAttributes() {
|
|
1149
|
+
return [...z];
|
|
1150
|
+
}
|
|
1151
|
+
shadow;
|
|
1152
|
+
embed = null;
|
|
1153
|
+
_widgetState = "minimized";
|
|
1154
|
+
_connected = !1;
|
|
1155
|
+
_connecting = !1;
|
|
1156
|
+
widgetEl;
|
|
1157
|
+
innerEl;
|
|
1158
|
+
containerEl;
|
|
1159
|
+
previewImg;
|
|
1160
|
+
spinnerEl;
|
|
1161
|
+
errorToast;
|
|
1162
|
+
errorTimer = null;
|
|
1163
|
+
joinBtn;
|
|
1164
|
+
endCallBtn;
|
|
1165
|
+
toggleBtn;
|
|
1166
|
+
revealBtn;
|
|
1167
|
+
toolbarEl;
|
|
1168
|
+
micBtn;
|
|
1169
|
+
constructor() {
|
|
1170
|
+
super(), this.shadow = this.attachShadow({ mode: "open" });
|
|
1171
|
+
}
|
|
1172
|
+
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();
|
|
1180
|
+
}
|
|
1181
|
+
disconnectedCallback() {
|
|
1182
|
+
this.errorTimer && clearTimeout(this.errorTimer), this.embed?.disconnect(), this.embed = null, this._connected = !1;
|
|
1183
|
+
}
|
|
1184
|
+
attributeChangedCallback(e, t, i) {
|
|
1185
|
+
this.widgetEl && (e === "preview-image" ? this.previewImg && (i ? (this.previewImg.src = i, this.previewImg.style.display = this._connected ? "none" : "block") : this.previewImg.style.display = "none") : e === "controlled-widget-state" && i ? (f.includes(i) && (this._widgetState = i), this._applyState()) : this._applyState());
|
|
1186
|
+
}
|
|
1187
|
+
// --- Public API (LemonSlice-compatible) ---
|
|
1188
|
+
async mute() {
|
|
1189
|
+
this.embed && (this.embed.audioElement.muted = !0);
|
|
1190
|
+
}
|
|
1191
|
+
async unmute() {
|
|
1192
|
+
this.embed && (this.embed.audioElement.muted = !1);
|
|
1193
|
+
}
|
|
1194
|
+
isMuted() {
|
|
1195
|
+
return this.embed?.audioElement.muted ?? !1;
|
|
1196
|
+
}
|
|
1197
|
+
canUnmute() {
|
|
1198
|
+
return this._connected;
|
|
1199
|
+
}
|
|
1200
|
+
async micOn() {
|
|
1201
|
+
this._connected || (this._widgetState = "active", this._applyState(), await this._connect()), this.embed && this.embed.isMuted && this.embed.toggleMute(), this._updateMicIcon();
|
|
1202
|
+
}
|
|
1203
|
+
async micOff() {
|
|
1204
|
+
this.embed && !this.embed.isMuted && this.embed.toggleMute(), this._updateMicIcon();
|
|
1205
|
+
}
|
|
1206
|
+
isMicOn() {
|
|
1207
|
+
return this.embed ? !this.embed.isMuted : !0;
|
|
1208
|
+
}
|
|
1209
|
+
canTurnOnMic() {
|
|
1210
|
+
return !!this.getAttribute("publishable-key");
|
|
1211
|
+
}
|
|
1212
|
+
setEmotion(e) {
|
|
1213
|
+
console.warn("[kfl-embed] setEmotion requires direct session access (not yet exposed by PersonaEmbed)");
|
|
1214
|
+
}
|
|
1215
|
+
// --- Internals ---
|
|
1216
|
+
_getAttrNum(e, t) {
|
|
1217
|
+
const i = this.getAttribute(e);
|
|
1218
|
+
if (!i) return t;
|
|
1219
|
+
const n = parseFloat(i);
|
|
1220
|
+
return isNaN(n) ? t : n;
|
|
1221
|
+
}
|
|
1222
|
+
_getCorner() {
|
|
1223
|
+
const e = this.getAttribute("corner");
|
|
1224
|
+
return e && U.includes(e) ? e : "bottom-right";
|
|
1225
|
+
}
|
|
1226
|
+
_applyLayout() {
|
|
1227
|
+
if (!this.widgetEl) return;
|
|
1228
|
+
const e = this.hasAttribute("inline"), t = this._getCorner(), i = this._widgetState === "active", n = this._widgetState === "hidden", o = 40, r = n ? o : i ? this._getAttrNum("active-width", 252) : this._getAttrNum("minimized-width", 144), l = n ? o : i ? this._getAttrNum("active-height", 377) : this._getAttrNum("minimized-height", 216);
|
|
1229
|
+
if (this.widgetEl.style.width = `${r}px`, this.widgetEl.style.height = `${l}px`, e)
|
|
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
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
_applyState() {
|
|
1238
|
+
if (!this.widgetEl) return;
|
|
1239
|
+
const e = this.hasAttribute("hide-ui"), t = this._shouldShowMinimizeButton(), i = this._widgetState === "minimized", n = this._widgetState === "hidden";
|
|
1240
|
+
this.widgetEl.classList.toggle("kfl-widget--hidden", n), this.joinBtn.style.display = this._connected || this._connecting || e || n ? "none" : "flex", this.toolbarEl.style.display = this._connected && !e && !i && !n ? "flex" : "none", this._updateMicIcon(), this.spinnerEl.classList.toggle("kfl-spinner--visible", this._connecting && !this._connected);
|
|
1241
|
+
const o = this.getAttribute("button-color") || "#919191", r = this._getAttrNum("button-color-opacity", 0.3), l = Math.round(r * 255).toString(16).padStart(2, "0");
|
|
1242
|
+
this.joinBtn.style.backgroundColor = `${o}${l}`, !e && i && t ? (this.toggleBtn.style.display = "flex", this.toggleBtn.innerHTML = W) : !e && !i && !n ? (this.toggleBtn.style.display = "flex", this.toggleBtn.innerHTML = J) : this.toggleBtn.style.display = "none";
|
|
1243
|
+
const a = this._getCorner(), c = a.includes("right");
|
|
1244
|
+
this.revealBtn.className = `kfl-reveal-btn ${c ? "kfl-reveal-btn--right" : "kfl-reveal-btn--left"}`, this.revealBtn.innerHTML = c ? q : G, n && (this.revealBtn.style.top = "", this.revealBtn.style.bottom = "", a.includes("bottom") ? this.revealBtn.style.bottom = "16px" : this.revealBtn.style.top = "16px"), this._applyLayout();
|
|
1245
|
+
}
|
|
1246
|
+
_shouldShowMinimizeButton() {
|
|
1247
|
+
const e = this.getAttribute("controlled-show-minimize-button");
|
|
1248
|
+
return e !== null ? e !== "false" : this.getAttribute("show-minimize-button") !== "false";
|
|
1249
|
+
}
|
|
1250
|
+
_handleJoinCall() {
|
|
1251
|
+
this._widgetState = "active", this._connecting = !0, this._applyState(), this._connect();
|
|
1252
|
+
}
|
|
1253
|
+
_handleToggle() {
|
|
1254
|
+
if (this._widgetState === "minimized") {
|
|
1255
|
+
if (this.hasAttribute("inline")) return;
|
|
1256
|
+
this._widgetState = "hidden", this.dispatchEvent(new CustomEvent("widgetstatechange", { detail: { state: "hidden" } }));
|
|
1257
|
+
} else this._widgetState === "active" && (this.embed?.disconnect(), this._connected = !1, this._connecting = !1, this.embed = null, this._widgetState = "minimized", this.getAttribute("preview-image") && (this.previewImg.style.display = "block"), this.dispatchEvent(new CustomEvent("widgetstatechange", { detail: { state: "minimized" } })));
|
|
1258
|
+
this._applyState();
|
|
1259
|
+
}
|
|
1260
|
+
_handleEndCall() {
|
|
1261
|
+
this._widgetState === "active" && (this.embed?.disconnect(), this._connected = !1, this._connecting = !1, this.embed = null, this._widgetState = "minimized", this.getAttribute("preview-image") && (this.previewImg.style.display = "block"), this.dispatchEvent(new CustomEvent("widgetstatechange", { detail: { state: "minimized" } })), this._applyState());
|
|
1262
|
+
}
|
|
1263
|
+
_handleReveal() {
|
|
1264
|
+
this._widgetState = "minimized", this.dispatchEvent(new CustomEvent("widgetstatechange", { detail: { state: "minimized" } })), this._applyState();
|
|
1265
|
+
}
|
|
1266
|
+
_handleMicToggle() {
|
|
1267
|
+
this.embed && (this.embed.toggleMute(), this._updateMicIcon());
|
|
1268
|
+
}
|
|
1269
|
+
_updateMicIcon() {
|
|
1270
|
+
if (!this.micBtn) return;
|
|
1271
|
+
const e = this.embed ? this.embed.isMuted : !1;
|
|
1272
|
+
this.micBtn.innerHTML = e ? Y : _;
|
|
1273
|
+
}
|
|
1274
|
+
_resetToMinimized() {
|
|
1275
|
+
try {
|
|
1276
|
+
this.embed?.disconnect();
|
|
1277
|
+
} catch (e) {
|
|
1278
|
+
console.warn("[kfl-embed] Disconnect error:", e);
|
|
1279
|
+
}
|
|
1280
|
+
this._connected = !1, this._connecting = !1, this.embed = null, this.containerEl.innerHTML = "", this.containerEl.removeAttribute("style"), this._widgetState = "minimized", this.getAttribute("preview-image") && (this.previewImg.style.display = "block"), this._applyState();
|
|
1281
|
+
}
|
|
1282
|
+
_showError(e) {
|
|
1283
|
+
this.errorTimer && clearTimeout(this.errorTimer), this.errorToast.textContent = e, this.errorToast.classList.add("kfl-error-toast--visible"), this.errorTimer = setTimeout(() => {
|
|
1284
|
+
this.errorToast.classList.remove("kfl-error-toast--visible"), this.errorTimer = null;
|
|
1285
|
+
}, 4e3);
|
|
1286
|
+
}
|
|
1287
|
+
async _connect() {
|
|
1288
|
+
const e = this.getAttribute("publishable-key");
|
|
1289
|
+
if (!e) {
|
|
1290
|
+
console.error("[kfl-embed] publishable-key attribute is required"), this._resetToMinimized(), this._showError("Missing publishable key");
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
if (this._connected || this.embed) return;
|
|
1294
|
+
const t = this.getAttribute("api-base-url") || void 0, i = this.getAttribute("video-fit") || "cover";
|
|
1295
|
+
this.previewImg.style.display = "none";
|
|
1296
|
+
try {
|
|
1297
|
+
this.embed = new P({
|
|
1298
|
+
container: this.containerEl,
|
|
1299
|
+
publishableKey: e,
|
|
1300
|
+
apiBaseUrl: t,
|
|
1301
|
+
videoFit: i,
|
|
1302
|
+
onStateChange: (n) => {
|
|
1303
|
+
n === "connected" && (this._connected = !0, this._connecting = !1, this._applyState()), this.dispatchEvent(new CustomEvent("statechange", { detail: { status: n } }));
|
|
1304
|
+
},
|
|
1305
|
+
onDisconnect: () => {
|
|
1306
|
+
this._resetToMinimized(), this.dispatchEvent(new CustomEvent("disconnected"));
|
|
1307
|
+
},
|
|
1308
|
+
onError: (n) => {
|
|
1309
|
+
console.error("[kfl-embed] Error:", n), this._resetToMinimized(), this._showError("Unable to connect"), this.dispatchEvent(new CustomEvent("error", { detail: { error: n } }));
|
|
1310
|
+
},
|
|
1311
|
+
onAgentStateChange: (n) => {
|
|
1312
|
+
this.dispatchEvent(new CustomEvent("agentstatechange", { detail: { state: n } }));
|
|
1313
|
+
}
|
|
1314
|
+
}), await this.embed.connect();
|
|
1315
|
+
} catch (n) {
|
|
1316
|
+
console.error("[kfl-embed] Connection failed:", n), this._resetToMinimized(), this._showError("Unable to connect"), this.dispatchEvent(new CustomEvent("error", { detail: { error: n } }));
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
867
1320
|
export {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1321
|
+
j as AGENT_REGISTRY,
|
|
1322
|
+
S as BaseAgent,
|
|
1323
|
+
M as ElevenLabsAgent,
|
|
871
1324
|
F as KeyframeApiError,
|
|
872
|
-
|
|
873
|
-
L as
|
|
874
|
-
|
|
1325
|
+
ee as KflEmbedElement,
|
|
1326
|
+
L as OpenAIRealtimeAgent,
|
|
1327
|
+
P as PersonaEmbed,
|
|
1328
|
+
Q as PersonaView,
|
|
875
1329
|
u as SAMPLE_RATE,
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1330
|
+
w as base64ToBytes,
|
|
1331
|
+
y as bytesToBase64,
|
|
1332
|
+
k as createAgent,
|
|
1333
|
+
x as createEventEmitter,
|
|
1334
|
+
E as floatTo16BitPCM,
|
|
1335
|
+
X as getAgentInfo,
|
|
1336
|
+
g as resamplePcm
|
|
883
1337
|
};
|