@keyframelabs/elements 0.4.0 → 0.5.1

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 j = ["neutral", "angry", "sad", "happy"], U = "wss://api.openai.com/v1/realtime", D = "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;
345
- return this.initialSessionUpdate = this.buildSessionUpdate(e, t), new Promise((i, n) => {
381
+ const t = e.model ?? D;
382
+ return this.initialSessionUpdate = this.buildSessionUpdate(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
+ `${U}?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,40 +461,36 @@ 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() {
433
470
  this.rejectPendingConnect(new Error("Connection closed")), this.clearConnectTimeout(), this.resetTurnState(), this.initialSessionUpdate = null, this.handledFunctionCallIds.clear(), super.close();
434
471
  }
435
- buildSessionUpdate(e, t) {
436
- const i = e.turnDetection ?? { type: "semantic_vad", eagerness: "high" };
472
+ buildSessionUpdate(e) {
437
473
  return {
438
474
  type: "session.update",
439
475
  session: {
440
476
  type: "realtime",
441
- model: t,
477
+ model: e,
442
478
  output_modalities: ["audio"],
443
- instructions: e.systemPrompt,
444
479
  audio: {
445
480
  input: {
446
481
  format: {
447
482
  type: "audio/pcm",
448
- rate: p
449
- },
450
- turn_detection: i
483
+ rate: m
484
+ }
451
485
  },
452
486
  output: {
453
487
  format: {
454
488
  type: "audio/pcm",
455
- rate: u
456
- },
457
- ...e.voice ? { voice: e.voice } : {}
489
+ rate: h
490
+ }
458
491
  }
459
492
  },
460
- tools: [B],
493
+ tools: [z],
461
494
  tool_choice: "auto"
462
495
  }
463
496
  };
@@ -470,7 +503,7 @@ class L extends S {
470
503
  this.currentResponseHasAudio && this.finishAudioTurn();
471
504
  return;
472
505
  }
473
- const t = e.output.filter(D);
506
+ const t = e.output.filter(H);
474
507
  if (t.length > 0) {
475
508
  this.handleFunctionCalls(t);
476
509
  return;
@@ -503,7 +536,7 @@ class L extends S {
503
536
  return { error: `Unsupported function: ${e.name}` };
504
537
  try {
505
538
  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" };
539
+ return i && j.includes(i) ? (this.events.emit("emotion", i), { result: "ok" }) : { error: "Invalid emotion" };
507
540
  } catch {
508
541
  return { error: "Invalid function arguments" };
509
542
  }
@@ -533,27 +566,150 @@ class L extends S {
533
566
  this.connectTimeout !== null && (clearTimeout(this.connectTimeout), this.connectTimeout = null);
534
567
  }
535
568
  }
536
- function D(s) {
569
+ function H(s) {
537
570
  return s.type === "function_call";
538
571
  }
539
- const j = [
572
+ const k = 16e3, W = 1e4, V = 1e4, K = ["neutral", "angry", "sad", "happy"];
573
+ class J extends v {
574
+ agentName = "KflVoiceAgent";
575
+ connectResolve = null;
576
+ connectReject = null;
577
+ connectTimeout = null;
578
+ pingInterval = null;
579
+ sourceInputSampleRate = M;
580
+ turnEnded = !1;
581
+ async connect(e) {
582
+ if (this.ws)
583
+ throw new Error("Already connected");
584
+ if (!e.wsUrl)
585
+ throw new Error("KFL voice agent wsUrl is required");
586
+ return e.inputSampleRate && (this.sourceInputSampleRate = e.inputSampleRate), new Promise((t, i) => {
587
+ this.connectResolve = t, this.connectReject = i, this.connectTimeout = setTimeout(() => {
588
+ this.rejectPendingConnect(new Error("Timed out waiting for kfl-voice-agent session.ready")), this.close();
589
+ }, V), this.ws = new WebSocket(e.wsUrl), this.ws.onopen = () => {
590
+ this.startPings(e.pingIntervalMs);
591
+ }, this.ws.onerror = () => {
592
+ this.rejectPendingConnect(new Error("Failed to connect to kfl-voice-agent"));
593
+ }, this.ws.onclose = (n) => {
594
+ if (this.stopPings(), this.connectReject) {
595
+ const o = n.reason ? `: ${n.reason}` : "";
596
+ this.rejectPendingConnect(
597
+ new Error(`kfl-voice-agent closed before initialization (${n.code}${o})`)
598
+ );
599
+ }
600
+ this.ws = null, this.setState("idle"), this.emitClosed(n.code, n.reason);
601
+ }, this.ws.onmessage = (n) => {
602
+ this.handleMessage(n.data);
603
+ };
604
+ });
605
+ }
606
+ sendAudio(e) {
607
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
608
+ console.warn("[KflVoiceAgent] Cannot send audio: not connected");
609
+ return;
610
+ }
611
+ let t = e;
612
+ this.sourceInputSampleRate !== k && (t = u(e, this.sourceInputSampleRate, k)), this.ws.send(JSON.stringify({
613
+ type: "input_audio_buffer.append",
614
+ audio: b(t)
615
+ }));
616
+ }
617
+ close() {
618
+ this.rejectPendingConnect(new Error("Connection closed")), this.clearConnectTimeout(), this.stopPings(), super.close();
619
+ }
620
+ handleParsedMessage(e) {
621
+ const t = e, i = t.type;
622
+ if (i)
623
+ switch (i) {
624
+ case "session.ready":
625
+ this.setState("listening"), this.resolvePendingConnect();
626
+ break;
627
+ case "session.pong":
628
+ break;
629
+ case "turn_start":
630
+ this.turnEnded = !1, this.events.emit("turnStart", void 0), this.setState("speaking");
631
+ break;
632
+ case "output_audio.chunk":
633
+ t.audio && this.events.emit("audio", _(t.audio));
634
+ break;
635
+ case "output_audio.done":
636
+ this.turnEnded || (this.turnEnded = !0, this.events.emit("turnEnd", void 0));
637
+ break;
638
+ case "playback_done":
639
+ this.turnEnded || (this.turnEnded = !0, this.events.emit("turnEnd", void 0)), this.setState("listening");
640
+ break;
641
+ case "interrupted":
642
+ this.turnEnded = !0, this.events.emit("interrupted", void 0), this.setState("listening");
643
+ break;
644
+ case "user_transcript":
645
+ t.text && this.events.emit("transcript", {
646
+ role: "user",
647
+ text: t.text,
648
+ isFinal: !0
649
+ });
650
+ break;
651
+ case "assistant_done":
652
+ t.text && this.events.emit("transcript", {
653
+ role: "assistant",
654
+ text: t.text,
655
+ isFinal: !0
656
+ });
657
+ break;
658
+ case "emotion":
659
+ t.emotion && K.includes(t.emotion) && this.events.emit("emotion", t.emotion);
660
+ break;
661
+ case "error": {
662
+ const n = t.message ?? "Unknown kfl-voice-agent error";
663
+ this.rejectPendingConnect(new Error(n)), console.error("[KflVoiceAgent] Server error:", t);
664
+ break;
665
+ }
666
+ }
667
+ }
668
+ startPings(e) {
669
+ this.stopPings();
670
+ const t = e ?? W;
671
+ this.pingInterval = setInterval(() => {
672
+ !this.ws || this.ws.readyState !== WebSocket.OPEN || this.ws.send(JSON.stringify({ type: "session.ping" }));
673
+ }, t);
674
+ }
675
+ stopPings() {
676
+ this.pingInterval && (clearInterval(this.pingInterval), this.pingInterval = null);
677
+ }
678
+ clearConnectTimeout() {
679
+ this.connectTimeout && (clearTimeout(this.connectTimeout), this.connectTimeout = null);
680
+ }
681
+ resolvePendingConnect() {
682
+ this.clearConnectTimeout();
683
+ const e = this.connectResolve;
684
+ this.connectResolve = null, this.connectReject = null, e?.();
685
+ }
686
+ rejectPendingConnect(e) {
687
+ this.clearConnectTimeout();
688
+ const t = this.connectReject;
689
+ this.connectResolve = null, this.connectReject = null, t?.(e);
690
+ }
691
+ }
692
+ const q = [
540
693
  { id: "elevenlabs", name: "ElevenLabs", description: "ElevenLabs Conversational AI" },
541
- { id: "openai", name: "OpenAI Realtime", description: "OpenAI Realtime API" }
694
+ { id: "openai", name: "OpenAI Realtime", description: "OpenAI Realtime API" },
695
+ { id: "kfl", name: "KFL Voice Agent", description: "kfl-voice-agent WebSocket API" }
542
696
  ];
543
- function k(s) {
697
+ function R(s) {
544
698
  switch (s) {
545
699
  case "elevenlabs":
546
- return new M();
700
+ return new F();
547
701
  case "openai":
548
- return new L();
702
+ return new $();
703
+ case "kfl":
704
+ return new J();
549
705
  default:
550
706
  throw new Error(`Unknown agent type: ${s}`);
551
707
  }
552
708
  }
553
- function X(s) {
554
- return j.find((e) => e.id === s);
709
+ function _e(s) {
710
+ return q.find((e) => e.id === s);
555
711
  }
556
- class F extends Error {
712
+ class G extends Error {
557
713
  status;
558
714
  payload;
559
715
  url;
@@ -561,8 +717,8 @@ class F extends Error {
561
717
  super(e.message), this.name = "ApiError", this.status = e.status, this.payload = e.payload, this.url = e.url;
562
718
  }
563
719
  }
564
- const d = /* @__PURE__ */ new Set();
565
- class P {
720
+ const r = /* @__PURE__ */ new Set();
721
+ class Y {
566
722
  apiBaseUrl;
567
723
  publishableKey;
568
724
  callbacks;
@@ -606,31 +762,31 @@ class P {
606
762
  }
607
763
  /** Connect to the embed session */
608
764
  async connect() {
609
- if (d.has(this.publishableKey)) {
765
+ if (r.has(this.publishableKey)) {
610
766
  console.log("[PersonaEmbed] Connection already in progress, skipping");
611
767
  return;
612
768
  }
613
- d.add(this.publishableKey), this.mounted = !0, this.abortController = new AbortController(), this.setStatus("connecting");
769
+ r.add(this.publishableKey), this.mounted = !0, this.abortController = new AbortController(), this.setStatus("connecting");
614
770
  try {
615
771
  const e = await this.fetchSession(this.abortController.signal);
616
772
  if (!this.mounted) {
617
- d.delete(this.publishableKey);
773
+ r.delete(this.publishableKey);
618
774
  return;
619
775
  }
620
776
  if (await this.initSession(e), await this.initMicrophone(), await this.connectAgent(e.voice_agent_details), !this.mounted) {
621
- this.cleanup(), d.delete(this.publishableKey);
777
+ this.cleanup(), r.delete(this.publishableKey);
622
778
  return;
623
779
  }
624
780
  this.setStatus("connected");
625
781
  } catch (e) {
626
- if (d.delete(this.publishableKey), e instanceof Error && e.name === "AbortError")
782
+ if (r.delete(this.publishableKey), e instanceof Error && e.name === "AbortError")
627
783
  return;
628
784
  console.error("[PersonaEmbed]", e), this.mounted && (this.setStatus("error"), this.callbacks.onError?.(e));
629
785
  }
630
786
  }
631
787
  /** Disconnect and cleanup */
632
788
  disconnect() {
633
- this.mounted = !1, this.abortController?.abort(), this.abortController = null, d.delete(this.publishableKey), this.cleanup(), this.setStatus("disconnected");
789
+ this.mounted = !1, this.abortController?.abort(), this.abortController = null, r.delete(this.publishableKey), this.cleanup(), this.setStatus("disconnected");
634
790
  }
635
791
  /** Toggle microphone mute */
636
792
  toggleMute() {
@@ -655,7 +811,7 @@ class P {
655
811
  i = await t.json();
656
812
  } catch {
657
813
  }
658
- throw new F({
814
+ throw new G({
659
815
  message: i?.message ?? "create_session failed",
660
816
  status: t.status,
661
817
  payload: i,
@@ -669,7 +825,7 @@ class P {
669
825
  return t.json();
670
826
  }
671
827
  async initSession(e) {
672
- this.session = v({
828
+ this.session = T({
673
829
  serverUrl: e.session_details.server_url,
674
830
  participantToken: e.session_details.participant_token,
675
831
  agentIdentity: e.session_details.agent_identity,
@@ -681,7 +837,7 @@ class P {
681
837
  });
682
838
  },
683
839
  onStateChange: (t) => {
684
- this.mounted && t === "disconnected" && (this.setStatus("disconnected"), this.callbacks.onDisconnect?.());
840
+ this.mounted && t === "disconnected" && (this.disconnect(), this.callbacks.onDisconnect?.());
685
841
  },
686
842
  onAgentStateChange: (t) => {
687
843
  this.mounted && this.setAgentState(t);
@@ -692,10 +848,10 @@ class P {
692
848
  onClose: () => {
693
849
  this.mounted && this.callbacks.onDisconnect?.();
694
850
  }
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", () => {
851
+ }), 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
852
  this.session?.endAudioTurn(), this.session?.interrupt();
697
853
  }), this.agent.on("closed", () => {
698
- this.mounted && this.callbacks.onDisconnect?.();
854
+ this.mounted && (this.disconnect(), this.callbacks.onDisconnect?.());
699
855
  }), this.agent.on("emotion", (t) => this.session?.setEmotion(t)), await this.session.connect();
700
856
  }
701
857
  async initMicrophone() {
@@ -703,9 +859,9 @@ class P {
703
859
  audio: { sampleRate: 16e3, echoCancellation: !0, noiseSuppression: !0 }
704
860
  }), this.audioContext = new AudioContext({ sampleRate: 16e3 });
705
861
  const e = this.audioContext.createMediaStreamSource(this.stream);
706
- this.processor = this.audioContext.createScriptProcessor(4096, 1, 1), this.processor.onaudioprocess = (t) => {
862
+ this.processor = await x(this.audioContext), this.processor.port.onmessage = (t) => {
707
863
  if (!this._isMuted) {
708
- const i = E(t.inputBuffer.getChannelData(0));
864
+ const i = A(t.data);
709
865
  this.agent?.sendAudio(i);
710
866
  }
711
867
  }, e.connect(this.processor), this.processor.connect(this.audioContext.destination);
@@ -713,24 +869,32 @@ class P {
713
869
  async connectAgent(e) {
714
870
  if (!this.agent) return;
715
871
  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
- });
872
+ if (e.type === "elevenlabs")
873
+ await this.agent.connect({
874
+ ...t,
875
+ agentId: e.agent_id,
876
+ signedUrl: e.signed_url
877
+ });
878
+ else if (e.type === "openai")
879
+ await this.agent.connect({
880
+ ...t,
881
+ apiKey: e.token
882
+ });
883
+ else if (e.type === "kfl") {
884
+ if (!e.signed_url)
885
+ throw new Error("KFL voice agent requires signed_url");
886
+ await this.agent.connect({
887
+ ...t,
888
+ wsUrl: e.signed_url
889
+ });
890
+ }
727
891
  }
728
892
  cleanup() {
729
893
  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
894
  }
731
895
  }
732
- const h = /* @__PURE__ */ new Set();
733
- class Q {
896
+ const l = /* @__PURE__ */ new Set();
897
+ class be {
734
898
  voiceAgentDetails;
735
899
  sessionDetails;
736
900
  callbacks;
@@ -774,24 +938,24 @@ class Q {
774
938
  }
775
939
  /** Connect to the session */
776
940
  async connect() {
777
- if (h.has(this.connectionId)) {
941
+ if (l.has(this.connectionId)) {
778
942
  console.log("[PersonaView] Connection already in progress, skipping");
779
943
  return;
780
944
  }
781
- h.add(this.connectionId), this.mounted = !0, this.setStatus("connecting");
945
+ l.add(this.connectionId), this.mounted = !0, this.setStatus("connecting");
782
946
  try {
783
947
  if (await this.initSession(), await this.initMicrophone(), await this.connectAgent(), !this.mounted) {
784
- this.cleanup(), h.delete(this.connectionId);
948
+ this.cleanup(), l.delete(this.connectionId);
785
949
  return;
786
950
  }
787
951
  this.setStatus("connected");
788
952
  } catch (e) {
789
- h.delete(this.connectionId), console.error("[PersonaView]", e), this.mounted && (this.setStatus("error"), this.callbacks.onError?.(e));
953
+ l.delete(this.connectionId), console.error("[PersonaView]", e), this.mounted && (this.setStatus("error"), this.callbacks.onError?.(e));
790
954
  }
791
955
  }
792
956
  /** Disconnect and cleanup */
793
957
  disconnect() {
794
- this.mounted = !1, h.delete(this.connectionId), this.cleanup(), this.setStatus("disconnected");
958
+ this.mounted = !1, l.delete(this.connectionId), this.cleanup(), this.setStatus("disconnected");
795
959
  }
796
960
  /** Toggle microphone mute */
797
961
  toggleMute() {
@@ -804,7 +968,7 @@ class Q {
804
968
  this._agentState !== e && (this._agentState = e, this.callbacks.onAgentStateChange?.(e));
805
969
  }
806
970
  async initSession() {
807
- this.session = v({
971
+ this.session = T({
808
972
  serverUrl: this.sessionDetails.server_url,
809
973
  participantToken: this.sessionDetails.participant_token,
810
974
  agentIdentity: this.sessionDetails.agent_identity,
@@ -816,7 +980,7 @@ class Q {
816
980
  });
817
981
  },
818
982
  onStateChange: (e) => {
819
- this.mounted && e === "disconnected" && (this.setStatus("disconnected"), this.callbacks.onDisconnect?.());
983
+ this.mounted && e === "disconnected" && (this.disconnect(), this.callbacks.onDisconnect?.());
820
984
  },
821
985
  onAgentStateChange: (e) => {
822
986
  this.mounted && this.setAgentState(e);
@@ -827,10 +991,10 @@ class Q {
827
991
  onClose: () => {
828
992
  this.mounted && this.callbacks.onDisconnect?.();
829
993
  }
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", () => {
994
+ }), 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
995
  this.session?.endAudioTurn(), this.session?.interrupt();
832
996
  }), this.agent.on("closed", () => {
833
- this.mounted && this.callbacks.onDisconnect?.();
997
+ this.mounted && (this.disconnect(), this.callbacks.onDisconnect?.());
834
998
  }), this.agent.on("emotion", (e) => this.session?.setEmotion(e)), await this.session.connect();
835
999
  }
836
1000
  async initMicrophone() {
@@ -838,9 +1002,9 @@ class Q {
838
1002
  audio: { sampleRate: 16e3, echoCancellation: !0, noiseSuppression: !0 }
839
1003
  }), this.audioContext = new AudioContext({ sampleRate: 16e3 });
840
1004
  const e = this.audioContext.createMediaStreamSource(this.stream);
841
- this.processor = this.audioContext.createScriptProcessor(4096, 1, 1), this.processor.onaudioprocess = (t) => {
1005
+ this.processor = await x(this.audioContext), this.processor.port.onmessage = (t) => {
842
1006
  if (!this._isMuted) {
843
- const i = E(t.inputBuffer.getChannelData(0));
1007
+ const i = A(t.data);
844
1008
  this.agent?.sendAudio(i);
845
1009
  }
846
1010
  }, e.connect(this.processor), this.processor.connect(this.audioContext.destination);
@@ -848,23 +1012,31 @@ class Q {
848
1012
  async connectAgent() {
849
1013
  if (!this.agent) return;
850
1014
  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
- });
1015
+ if (e.type === "elevenlabs")
1016
+ await this.agent.connect({
1017
+ ...t,
1018
+ agentId: e.agent_id,
1019
+ signedUrl: e.signed_url
1020
+ });
1021
+ else if (e.type === "openai")
1022
+ await this.agent.connect({
1023
+ ...t,
1024
+ apiKey: e.token
1025
+ });
1026
+ else if (e.type === "kfl") {
1027
+ if (!e.signed_url)
1028
+ throw new Error("KFL voice agent requires signed_url");
1029
+ await this.agent.connect({
1030
+ ...t,
1031
+ wsUrl: e.signed_url
1032
+ });
1033
+ }
862
1034
  }
863
1035
  cleanup() {
864
1036
  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
1037
  }
866
1038
  }
867
- const f = ["minimized", "active", "hidden"], U = ["bottom-right", "bottom-left", "top-right", "top-left"], z = [
1039
+ const f = ["minimized", "active", "hidden"], Z = ["bottom-right", "bottom-left", "top-right", "top-left"], X = [
868
1040
  "publishable-key",
869
1041
  "api-base-url",
870
1042
  "initial-state",
@@ -881,14 +1053,15 @@ const f = ["minimized", "active", "hidden"], U = ["bottom-right", "bottom-left",
881
1053
  "button-color",
882
1054
  "button-color-opacity",
883
1055
  "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;
1056
+ "preview-image",
1057
+ "call-to-action"
1058
+ ], E = "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap";
1059
+ function Q() {
1060
+ if (document.querySelector(`link[href="${E}"]`)) return;
888
1061
  const s = document.createElement("link");
889
- s.rel = "stylesheet", s.href = b, document.head.appendChild(s);
1062
+ s.rel = "stylesheet", s.href = E, document.head.appendChild(s);
890
1063
  }
891
- const H = `
1064
+ const ee = `
892
1065
  :host {
893
1066
  display: block;
894
1067
  font-family: 'Noto Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -1143,16 +1316,33 @@ const H = `
1143
1316
  opacity: 1;
1144
1317
  transform: translateY(0);
1145
1318
  }
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 {
1319
+ `, 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 = `
1320
+ <div class="kfl-widget">
1321
+ <div class="kfl-widget-inner">
1322
+ <img class="kfl-preview-img" alt="Persona preview" draggable="false" />
1323
+ <div class="kfl-embed-container"></div>
1324
+ <div class="kfl-spinner"><div class="kfl-spinner-ring"></div></div>
1325
+ <div class="kfl-error-toast"></div>
1326
+ <button type="button" class="kfl-join-btn"></button>
1327
+ <div class="kfl-toolbar">
1328
+ <button type="button" class="kfl-mic-btn"></button>
1329
+ <button type="button" class="kfl-endcall-btn"></button>
1330
+ </div>
1331
+ <button type="button" class="kfl-toggle-btn"></button>
1332
+ </div>
1333
+ <button type="button" class="kfl-reveal-btn"></button>
1334
+ </div>
1335
+ `;
1336
+ class ve extends HTMLElement {
1148
1337
  static get observedAttributes() {
1149
- return [...z];
1338
+ return [...X];
1150
1339
  }
1151
1340
  shadow;
1152
1341
  embed = null;
1153
1342
  _widgetState = "minimized";
1154
1343
  _connected = !1;
1155
1344
  _connecting = !1;
1345
+ _initialized = !1;
1156
1346
  widgetEl;
1157
1347
  innerEl;
1158
1348
  containerEl;
@@ -1166,25 +1356,43 @@ class ee extends HTMLElement {
1166
1356
  revealBtn;
1167
1357
  toolbarEl;
1168
1358
  micBtn;
1359
+ _onJoinClick = (e) => {
1360
+ e.stopPropagation(), this._handleJoinCall();
1361
+ };
1362
+ _onEndCallClick = () => this._handleEndCall();
1363
+ _onMicClick = () => this._handleMicToggle();
1364
+ _onToggleClick = (e) => {
1365
+ e.stopPropagation(), this._handleToggle();
1366
+ };
1367
+ _onRevealClick = () => this._handleReveal();
1368
+ _onInnerClick = (e) => this._handleInnerClick(e);
1169
1369
  constructor() {
1170
1370
  super(), this.shadow = this.attachShadow({ mode: "open" });
1171
1371
  }
1172
1372
  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();
1373
+ Q(), this._initialized || (this._initDom(), this._initialized = !0), this._widgetState = this._resolveInitialState(), this._applyState();
1180
1374
  }
1181
1375
  disconnectedCallback() {
1182
- this.errorTimer && clearTimeout(this.errorTimer), this.embed?.disconnect(), this.embed = null, this._connected = !1;
1376
+ this.errorTimer && clearTimeout(this.errorTimer), this._disconnectSession(), this._setWidgetState("minimized", { emit: !1 });
1183
1377
  }
1184
1378
  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());
1379
+ if (this._initialized) {
1380
+ if (e === "call-to-action") {
1381
+ this._renderJoinLabel();
1382
+ return;
1383
+ }
1384
+ if (e === "preview-image") {
1385
+ this._applyPreviewImage(i);
1386
+ return;
1387
+ }
1388
+ if (e === "controlled-widget-state") {
1389
+ i && f.includes(i) ? this._setWidgetState(i, { emit: !1 }) : this._applyState();
1390
+ return;
1391
+ }
1392
+ this._applyState();
1393
+ }
1186
1394
  }
1187
- // --- Public API (LemonSlice-compatible) ---
1395
+ // --- Public API ---
1188
1396
  async mute() {
1189
1397
  this.embed && (this.embed.audioElement.muted = !0);
1190
1398
  }
@@ -1198,7 +1406,7 @@ class ee extends HTMLElement {
1198
1406
  return this._connected;
1199
1407
  }
1200
1408
  async micOn() {
1201
- this._connected || (this._widgetState = "active", this._applyState(), await this._connect()), this.embed && this.embed.isMuted && this.embed.toggleMute(), this._updateMicIcon();
1409
+ this._connected || (this._connecting = !0, this._setWidgetState("active", { emit: !1 }), await this._connect()), this.embed && this.embed.isMuted && this.embed.toggleMute(), this._updateMicIcon();
1202
1410
  }
1203
1411
  async micOff() {
1204
1412
  this.embed && !this.embed.isMuted && this.embed.toggleMute(), this._updateMicIcon();
@@ -1213,6 +1421,51 @@ class ee extends HTMLElement {
1213
1421
  console.warn("[kfl-embed] setEmotion requires direct session access (not yet exposed by PersonaEmbed)");
1214
1422
  }
1215
1423
  // --- Internals ---
1424
+ _initDom() {
1425
+ const e = document.createElement("style");
1426
+ e.textContent = ee, this.shadow.appendChild(e);
1427
+ const t = document.createElement("template");
1428
+ t.innerHTML = me;
1429
+ const i = t.content.cloneNode(!0);
1430
+ 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);
1431
+ }
1432
+ _requireElement(e, t) {
1433
+ const i = e.querySelector(t);
1434
+ if (!i) throw new Error(`[kfl-embed] Missing template node: ${t}`);
1435
+ return i;
1436
+ }
1437
+ _resolveInitialState() {
1438
+ const e = this.getAttribute("controlled-widget-state");
1439
+ if (e && f.includes(e))
1440
+ return e;
1441
+ const t = this.getAttribute("initial-state");
1442
+ return t && f.includes(t) ? t : "minimized";
1443
+ }
1444
+ _renderJoinLabel() {
1445
+ this.joinBtn.innerHTML = `${te} ${this.getAttribute("call-to-action") || le}`;
1446
+ }
1447
+ _applyPreviewImage(e) {
1448
+ if (e) {
1449
+ this.previewImg.src = e, this.previewImg.style.display = this._connected ? "none" : "block";
1450
+ return;
1451
+ }
1452
+ this.previewImg.removeAttribute("src"), this.previewImg.style.display = "none";
1453
+ }
1454
+ _readRenderConfig() {
1455
+ const e = this._widgetState === "minimized", t = this._widgetState === "hidden";
1456
+ return {
1457
+ hideUI: this.hasAttribute("hide-ui"),
1458
+ isInline: this.hasAttribute("inline"),
1459
+ showMin: this._shouldShowMinimizeButton(),
1460
+ isMinimized: e,
1461
+ isHidden: t,
1462
+ isActive: this._widgetState === "active",
1463
+ canExpandFromMinimized: e && this._hasLiveSession(),
1464
+ corner: this._getCorner(),
1465
+ buttonColor: this.getAttribute("button-color") || pe,
1466
+ buttonOpacity: this._getAttrNum("button-color-opacity", ge)
1467
+ };
1468
+ }
1216
1469
  _getAttrNum(e, t) {
1217
1470
  const i = this.getAttribute(e);
1218
1471
  if (!i) return t;
@@ -1221,47 +1474,62 @@ class ee extends HTMLElement {
1221
1474
  }
1222
1475
  _getCorner() {
1223
1476
  const e = this.getAttribute("corner");
1224
- return e && U.includes(e) ? e : "bottom-right";
1477
+ return e && Z.includes(e) ? e : "bottom-right";
1225
1478
  }
1226
- _applyLayout() {
1479
+ _applyLayout(e) {
1227
1480
  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
- }
1481
+ 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);
1482
+ 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
1483
  }
1237
1484
  _applyState() {
1238
1485
  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();
1486
+ const e = this._readRenderConfig();
1487
+ 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);
1488
+ }
1489
+ _renderJoinButton(e) {
1490
+ this.joinBtn.style.display = this._connected || this._connecting || e.isHidden ? "none" : "flex";
1491
+ }
1492
+ _renderToolbar(e) {
1493
+ this.toolbarEl.style.display = this._connected && !e.hideUI && !e.isMinimized && !e.isHidden ? "flex" : "none";
1494
+ }
1495
+ _renderToggleButton(e) {
1496
+ !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";
1497
+ }
1498
+ _renderRevealButton(e) {
1499
+ const t = e.corner.includes("right");
1500
+ 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);
1501
+ }
1502
+ _renderJoinButtonColor(e) {
1503
+ const t = Math.max(0, Math.min(1, e.buttonOpacity)), i = Math.round(t * 255).toString(16).padStart(2, "0");
1504
+ this.joinBtn.style.backgroundColor = `${e.buttonColor}${i}`;
1245
1505
  }
1246
1506
  _shouldShowMinimizeButton() {
1247
1507
  const e = this.getAttribute("controlled-show-minimize-button");
1248
1508
  return e !== null ? e !== "false" : this.getAttribute("show-minimize-button") !== "false";
1249
1509
  }
1250
1510
  _handleJoinCall() {
1251
- this._widgetState = "active", this._connecting = !0, this._applyState(), this._connect();
1511
+ this._connecting = !0, this._setWidgetState("active", { emit: !1 }), this._connect();
1252
1512
  }
1253
1513
  _handleToggle() {
1254
1514
  if (this._widgetState === "minimized") {
1255
1515
  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();
1516
+ this._hasLiveSession() && this._disconnectSession(), this._setWidgetState("hidden");
1517
+ } else this._widgetState === "active" && this._setWidgetState("minimized");
1518
+ }
1519
+ _handleInnerClick(e) {
1520
+ if (this._widgetState !== "minimized" || !this._hasLiveSession()) return;
1521
+ const t = e.composedPath();
1522
+ t.includes(this.joinBtn) || t.includes(this.toggleBtn) || this._setWidgetState("active");
1259
1523
  }
1260
1524
  _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());
1525
+ this._widgetState === "active" && (this._disconnectSession(), this._setWidgetState("minimized"));
1262
1526
  }
1263
1527
  _handleReveal() {
1264
- this._widgetState = "minimized", this.dispatchEvent(new CustomEvent("widgetstatechange", { detail: { state: "minimized" } })), this._applyState();
1528
+ this._setWidgetState("minimized");
1529
+ }
1530
+ _setWidgetState(e, t) {
1531
+ const i = t?.emit ?? !0, n = this._widgetState !== e;
1532
+ this._widgetState = e, n && i && this.dispatchEvent(new CustomEvent("widgetstatechange", { detail: { state: e } })), this._applyState();
1265
1533
  }
1266
1534
  _handleMicToggle() {
1267
1535
  this.embed && (this.embed.toggleMute(), this._updateMicIcon());
@@ -1269,15 +1537,21 @@ class ee extends HTMLElement {
1269
1537
  _updateMicIcon() {
1270
1538
  if (!this.micBtn) return;
1271
1539
  const e = this.embed ? this.embed.isMuted : !1;
1272
- this.micBtn.innerHTML = e ? Y : _;
1540
+ this.micBtn.innerHTML = e ? ae : S;
1273
1541
  }
1274
- _resetToMinimized() {
1542
+ _hasLiveSession() {
1543
+ return this._connected || this._connecting || !!this.embed;
1544
+ }
1545
+ _disconnectSession() {
1275
1546
  try {
1276
1547
  this.embed?.disconnect();
1277
1548
  } catch (e) {
1278
1549
  console.warn("[kfl-embed] Disconnect error:", e);
1279
1550
  }
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();
1551
+ this._connected = !1, this._connecting = !1, this.embed = null, this.containerEl.innerHTML = "", this.containerEl.removeAttribute("style"), this._applyPreviewImage(this.getAttribute("preview-image"));
1552
+ }
1553
+ _resetToMinimized() {
1554
+ this._disconnectSession(), this._setWidgetState("minimized", { emit: !1 });
1281
1555
  }
1282
1556
  _showError(e) {
1283
1557
  this.errorTimer && clearTimeout(this.errorTimer), this.errorToast.textContent = e, this.errorToast.classList.add("kfl-error-toast--visible"), this.errorTimer = setTimeout(() => {
@@ -1294,7 +1568,7 @@ class ee extends HTMLElement {
1294
1568
  const t = this.getAttribute("api-base-url") || void 0, i = this.getAttribute("video-fit") || "cover";
1295
1569
  this.previewImg.style.display = "none";
1296
1570
  try {
1297
- this.embed = new P({
1571
+ this.embed = new Y({
1298
1572
  container: this.containerEl,
1299
1573
  publishableKey: e,
1300
1574
  apiBaseUrl: t,
@@ -1318,20 +1592,21 @@ class ee extends HTMLElement {
1318
1592
  }
1319
1593
  }
1320
1594
  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
1595
+ q as AGENT_REGISTRY,
1596
+ v as BaseAgent,
1597
+ F as ElevenLabsAgent,
1598
+ G as KeyframeApiError,
1599
+ J as KflAgent,
1600
+ ve as KflEmbedElement,
1601
+ $ as OpenAIRealtimeAgent,
1602
+ Y as PersonaEmbed,
1603
+ be as PersonaView,
1604
+ h as SAMPLE_RATE,
1605
+ _ as base64ToBytes,
1606
+ b as bytesToBase64,
1607
+ R as createAgent,
1608
+ L as createEventEmitter,
1609
+ A as floatTo16BitPCM,
1610
+ _e as getAgentInfo,
1611
+ u as resamplePcm
1337
1612
  };