@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/dist/index.js CHANGED
@@ -1,30 +1,30 @@
1
- import { createClient as _ } from "@keyframelabs/sdk";
1
+ import { createClient as v } from "@keyframelabs/sdk";
2
2
  const u = 24e3;
3
- function f(s) {
3
+ function w(s) {
4
4
  const e = atob(s), t = new Uint8Array(e.length);
5
- for (let n = 0; n < e.length; n++)
6
- t[n] = e.charCodeAt(n);
5
+ for (let i = 0; i < e.length; i++)
6
+ t[i] = e.charCodeAt(i);
7
7
  return t;
8
8
  }
9
- function S(s) {
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 p(s, e, t) {
15
+ function g(s, e, t) {
16
16
  if (e === t)
17
17
  return s;
18
- const n = new Int16Array(s.buffer, s.byteOffset, s.length / 2), a = e / t, i = Math.floor(n.length / a), c = new Int16Array(i);
19
- for (let l = 0; l < i; l++) {
20
- const m = l * a, d = Math.floor(m), w = Math.min(d + 1, n.length - 1), g = m - d;
21
- c[l] = Math.round(
22
- n[d] * (1 - g) + n[w] * g
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(c.buffer);
25
+ return new Uint8Array(r.buffer);
26
26
  }
27
- function C() {
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((n) => n(t));
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 v(s) {
44
+ function E(s) {
45
45
  const e = new Int16Array(s.length);
46
46
  for (let t = 0; t < s.length; t++) {
47
- const n = Math.max(-1, Math.min(1, s[t]));
48
- e[t] = n < 0 ? n * 32768 : n * 32767;
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 E = 16e3;
53
- class y {
52
+ const A = 16e3;
53
+ class S {
54
54
  ws = null;
55
55
  _state = "idle";
56
- events = C();
57
- inputSampleRate = E;
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 A = ["neutral", "angry", "sad", "happy"], I = "wss://api.elevenlabs.io/v1/convai/conversation";
117
- class R extends y {
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 = `${I}?agent_id=${e.agentId}`, e.apiKey && (t += `&xi-api-key=${e.apiKey}`)), new Promise((n, a) => {
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"), n();
144
+ this.setState("listening"), i();
145
145
  }, this.ws.onerror = () => {
146
- a(new Error("Failed to connect to ElevenLabs"));
147
- }, this.ws.onclose = (i) => {
148
- this.ws = null, this.setState("idle"), this.emitClosed(i.code, i.reason);
149
- }, this.ws.onmessage = (i) => {
150
- this.handleMessage(i.data);
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 n = t.agent_output_audio_format.match(/pcm_(\d+)/);
188
- n && (this.outputSampleRate = parseInt(n[1], 10));
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 n = t.user_input_audio_format.match(/pcm_(\d+)/);
192
- n && (this.expectedInputSampleRate = parseInt(n[1], 10));
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 a = f(t.audio_base_64);
209
- this.outputSampleRate !== u && (a = p(a, this.outputSampleRate, u)), this.events.emit("audio", a);
210
- const i = a.length / 2 / u * 1e3;
211
- this.turnStartTime === 0 && (this.turnStartTime = Date.now()), this.accumulatedDurationMs += i, console.debug(
212
- `[ElevenLabs] audio chunk: ${a.length} bytes, +${i.toFixed(0)}ms, totalDuration=${this.accumulatedDurationMs.toFixed(0)}ms, agentResponse=${this.agentResponseReceived}`
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 n = t.parameters?.emotion?.toLowerCase();
256
- n && A.includes(n) && this.events.emit("emotion", n);
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 = p(e, this.sourceInputSampleRate, this.expectedInputSampleRate)), this.ws.send(JSON.stringify({
277
- user_audio_chunk: S(t)
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 T = ["neutral", "angry", "sad", "happy"], k = "wss://api.openai.com/v1/realtime", M = "gpt-realtime", h = 24e3, O = {
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 P extends y {
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 ?? M;
345
- return this.initialSessionUpdate = this.buildSessionUpdate(e, t), new Promise((n, a) => {
346
- this.connectResolve = n, this.connectReject = a, this.connectTimeout = setTimeout(() => {
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
- `${k}?model=${encodeURIComponent(t)}`,
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 = (i) => {
354
+ }, this.ws.onclose = (o) => {
355
355
  if (this.clearConnectTimeout(), this.connectReject) {
356
- const c = i.reason ? `: ${i.reason}` : "";
357
- this.rejectPendingConnect(new Error(`OpenAI Realtime closed before initialization (${i.code}${c})`));
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(i.code, i.reason);
360
- }, this.ws.onmessage = (i) => {
361
- this.handleMessage(i.data);
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 n = performance.now() - this.pendingFunctionCallStartedAtMs;
380
+ const i = performance.now() - this.pendingFunctionCallStartedAtMs;
381
381
  console.debug("[OpenAIRealtime] Function call latency", {
382
382
  calls: this.pendingFunctionCallNames,
383
- latencyMs: Math.round(n)
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", f(t.delta));
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 n = t.error?.message ?? t.message ?? "Unknown OpenAI Realtime error";
416
- this.rejectPendingConnect(new Error(n)), console.error("[OpenAIRealtime] Server error:", t);
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 !== h && (t = p(e, this.sourceInputSampleRate, h)), this.ws.send(JSON.stringify({
427
+ this.sourceInputSampleRate !== p && (t = g(e, this.sourceInputSampleRate, p)), this.ws.send(JSON.stringify({
428
428
  type: "input_audio_buffer.append",
429
- audio: S(t)
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 n = e.turnDetection ?? { type: "semantic_vad", eagerness: "high" };
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: h
448
+ rate: p
449
449
  },
450
- turn_detection: n
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: [O],
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(x);
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 n = [];
483
- for (const a of e) {
484
- if (!a.call_id || this.handledFunctionCallIds.has(a.call_id))
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(a.call_id), n.push(a.name ?? "unknown");
487
- const i = this.handleFunctionCall(a);
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: a.call_id,
493
- output: JSON.stringify(i)
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 = n, console.debug("[OpenAIRealtime] Function call received", {
498
- calls: n
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 n = (e.arguments ? JSON.parse(e.arguments) : {}).emotion?.toLowerCase();
506
- return n && T.includes(n) ? (this.events.emit("emotion", n), { result: "ok" }) : { error: "Invalid emotion" };
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 x(s) {
536
+ function D(s) {
537
537
  return s.type === "function_call";
538
538
  }
539
- const D = [
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 b(s) {
543
+ function k(s) {
544
544
  switch (s) {
545
545
  case "elevenlabs":
546
- return new R();
546
+ return new M();
547
547
  case "openai":
548
- return new P();
548
+ return new L();
549
549
  default:
550
550
  throw new Error(`Unknown agent type: ${s}`);
551
551
  }
552
552
  }
553
- function N(s) {
554
- return D.find((e) => e.id === s);
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 o = /* @__PURE__ */ new Set();
565
- class L {
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 (o.has(this.publishableKey)) {
609
+ if (d.has(this.publishableKey)) {
610
610
  console.log("[PersonaEmbed] Connection already in progress, skipping");
611
611
  return;
612
612
  }
613
- o.add(this.publishableKey), this.mounted = !0, this.abortController = new AbortController(), this.setStatus("connecting");
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
- o.delete(this.publishableKey);
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(), o.delete(this.publishableKey);
621
+ this.cleanup(), d.delete(this.publishableKey);
622
622
  return;
623
623
  }
624
624
  this.setStatus("connected");
625
625
  } catch (e) {
626
- if (o.delete(this.publishableKey), e instanceof Error && e.name === "AbortError")
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, o.delete(this.publishableKey), this.cleanup(), this.setStatus("disconnected");
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 n;
653
+ let i;
654
654
  try {
655
- n = await t.json();
655
+ i = await t.json();
656
656
  } catch {
657
657
  }
658
658
  throw new F({
659
- message: n?.message ?? "create_session failed",
659
+ message: i?.message ?? "create_session failed",
660
660
  status: t.status,
661
- payload: n,
661
+ payload: i,
662
662
  url: t.url
663
663
  });
664
664
  }
665
665
  if (!t.ok) {
666
- const n = await t.json().catch(() => null);
667
- throw new Error(`create_session failed: ${t.status} ${JSON.stringify(n)}`);
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((n) => console.warn("[PersonaEmbed] Video play failed:", n));
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 = b(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", () => {
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 n = v(t.inputBuffer.getChannelData(0));
709
- this.agent?.sendAudio(n);
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 r = /* @__PURE__ */ new Set();
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 (r.has(this.connectionId)) {
777
+ if (h.has(this.connectionId)) {
778
778
  console.log("[PersonaView] Connection already in progress, skipping");
779
779
  return;
780
780
  }
781
- r.add(this.connectionId), this.mounted = !0, this.setStatus("connecting");
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(), r.delete(this.connectionId);
784
+ this.cleanup(), h.delete(this.connectionId);
785
785
  return;
786
786
  }
787
787
  this.setStatus("connected");
788
788
  } catch (e) {
789
- r.delete(this.connectionId), console.error("[PersonaView]", e), this.mounted && (this.setStatus("error"), this.callbacks.onError?.(e));
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, r.delete(this.connectionId), this.cleanup(), this.setStatus("disconnected");
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 = b(this.voiceAgentDetails.type), this.agent.on("audio", (e) => this.session?.sendAudio(e)), this.agent.on("turnEnd", () => this.session?.endAudioTurn()), this.agent.on("interrupted", () => {
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 n = v(t.inputBuffer.getChannelData(0));
844
- this.agent?.sendAudio(n);
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
- D as AGENT_REGISTRY,
869
- y as BaseAgent,
870
- R as ElevenLabsAgent,
1321
+ j as AGENT_REGISTRY,
1322
+ S as BaseAgent,
1323
+ M as ElevenLabsAgent,
871
1324
  F as KeyframeApiError,
872
- P as OpenAIRealtimeAgent,
873
- L as PersonaEmbed,
874
- $ as PersonaView,
1325
+ ee as KflEmbedElement,
1326
+ L as OpenAIRealtimeAgent,
1327
+ P as PersonaEmbed,
1328
+ Q as PersonaView,
875
1329
  u as SAMPLE_RATE,
876
- f as base64ToBytes,
877
- S as bytesToBase64,
878
- b as createAgent,
879
- C as createEventEmitter,
880
- v as floatTo16BitPCM,
881
- N as getAgentInfo,
882
- p as resamplePcm
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
  };