@keyframelabs/elements 0.4.0 → 0.5.0

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