@keyframelabs/elements 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,9 +18,10 @@ pnpm add @keyframelabs/elements
18
18
 
19
19
  ## Usage
20
20
 
21
- This package provides two primary classes depending on your integration strategy:
21
+ This package provides three integration options depending on your strategy:
22
22
  1. **`PersonaEmbed`**: Fully managed. Uses a publishable key. Best for rapid frontend integration.
23
23
  2. **`PersonaView`**: Bring your own backend. Uses session tokens generated by your server.
24
+ 3. **`<kfl-embed>`**: Drop-in web component. Wraps `PersonaEmbed` with a complete widget UI (states, controls, positioning). Zero framework dependencies.
24
25
 
25
26
  ### Option A: PersonaEmbed (managed)
26
27
 
@@ -69,9 +70,96 @@ await view.connect();
69
70
  view.disconnect();
70
71
  ```
71
72
 
73
+ ### Option C: `<kfl-embed>` Web Component
74
+
75
+ A self-registering custom element that wraps `PersonaEmbed` with a widget UI shell: minimized/active/hidden states, "Join call" button, minimize/expand toggle, corner positioning, and button color styling. Drop it into any page with zero framework dependencies.
76
+
77
+ ```html
78
+ <kfl-embed
79
+ publishable-key="kfl_pk_live_..."
80
+ initial-state="minimized"
81
+ corner="bottom-right"
82
+ minimized-width="144"
83
+ minimized-height="216"
84
+ active-width="252"
85
+ active-height="377"
86
+ button-color="#919191"
87
+ button-color-opacity="0.3"
88
+ video-fit="cover"
89
+ preview-image="https://example.com/avatar.png"></kfl-embed>
90
+ <script type="module" src="https://unpkg.com/@keyframelabs/elements/dist/kfl-embed.js"></script>
91
+ ```
92
+
93
+ #### Attributes
94
+
95
+ | Attribute | Type | Default | Description |
96
+ | -------------------------------- | ---------------------------------------------------------- | ---------------- | ------------------------------------------------------------------ |
97
+ | `publishable-key` | `string` | Required | Your publishable embed key. |
98
+ | `api-base-url` | `string` | Keyframe default | Base URL for the Keyframe API. |
99
+ | `initial-state` | `'minimized' \| 'active' \| 'hidden'` | `'minimized'` | Widget state on first load. |
100
+ | `controlled-widget-state` | `'minimized' \| 'active' \| 'hidden'` | — | Externally control the widget state (overrides internal state). |
101
+ | `corner` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | `'bottom-right'` | Which corner to anchor the widget (fixed mode only). |
102
+ | `inline` | boolean attribute | — | Use `position: relative` instead of `fixed`. |
103
+ | `minimized-width` | `number` | `144` | Width in px when minimized. |
104
+ | `minimized-height` | `number` | `216` | Height in px when minimized. |
105
+ | `active-width` | `number` | `252` | Width in px when active (in-call). |
106
+ | `active-height` | `number` | `377` | Height in px when active (in-call). |
107
+ | `hide-ui` | boolean attribute | — | Hides all overlay controls. Useful for building your own UI shell. |
108
+ | `show-minimize-button` | `'true' \| 'false'` | `'true'` | Show the X/minimize button on hover. |
109
+ | `controlled-show-minimize-button`| `'true' \| 'false'` | — | Externally control the minimize button visibility. |
110
+ | `button-color` | hex color string | `'#919191'` | Color of the "Join call" button. |
111
+ | `button-color-opacity` | `number` (0–1) | `0.3` | Opacity of the "Join call" button background. |
112
+ | `video-fit` | `'cover' \| 'contain'` | `'cover'` | Video scaling mode. |
113
+ | `preview-image` | URL string | — | Image shown in the widget before a call starts. |
114
+
115
+ #### Events
116
+
117
+ | Event | `detail` | Description |
118
+ | ------------------ | --------------------------------------- | -------------------------------------- |
119
+ | `statechange` | `{ status: EmbedStatus }` | Connection status changed. |
120
+ | `agentstatechange` | `{ state: AgentState }` | Avatar playback state changed. |
121
+ | `widgetstatechange`| `{ state: 'minimized' \| 'active' \| 'hidden' }` | Widget UI state changed. |
122
+ | `disconnected` | — | Session disconnected. |
123
+ | `error` | `{ error: Error }` | A fatal error occurred. |
124
+
125
+ #### JavaScript API
126
+
127
+ ```ts
128
+ const el = document.querySelector('kfl-embed');
129
+
130
+ await el.micOn(); // Start session if needed, then unmute mic
131
+ await el.micOff(); // Mute mic
132
+ el.isMicOn(); // boolean
133
+
134
+ await el.mute(); // Mute speaker audio
135
+ await el.unmute(); // Unmute speaker audio
136
+ el.isMuted(); // boolean
137
+ ```
138
+
139
+ #### Build
140
+
141
+ The web component is built as a self-contained ES module via `vite.config.embed.ts`:
142
+
143
+ ```bash
144
+ pnpm build:embed # → dist/kfl-embed.js
145
+ ```
146
+
147
+ Import the auto-registering entry point (registers `<kfl-embed>` on `customElements`):
148
+
149
+ ```ts
150
+ import '@keyframelabs/elements/kfl-embed';
151
+ ```
152
+
153
+ Or import the class directly for manual registration:
154
+
155
+ ```ts
156
+ import { KflEmbedElement } from '@keyframelabs/elements';
157
+ customElements.define('my-embed', KflEmbedElement);
158
+ ```
159
+
72
160
  ## Supported agents and real-time LLMs
73
161
 
74
- Supports ElevenLabs and OpenAI Realtime.
162
+ Supports ElevenLabs, OpenAI Realtime, and KFL Voice Agent.
75
163
 
76
164
  For `PersonaEmbed`, this is determined by the values you set in the Keyframe platform dashboard.
77
165
 
@@ -184,10 +272,10 @@ type SessionDetails = {
184
272
  };
185
273
 
186
274
  type VoiceAgentDetails = {
187
- type: 'elevenlabs' | 'openai';
275
+ type: 'elevenlabs' | 'openai' | 'kfl';
188
276
  token?: string; // For openai (ephemeral client secret)
189
277
  agent_id?: string; // For elevenlabs
190
- signed_url?: string; // For elevenlabs
278
+ signed_url?: string; // For elevenlabs/kfl
191
279
  system_prompt?: string; // For openai
192
280
  voice?: string; // For openai
193
281
  };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * <kfl-embed> Web Component
3
+ *
4
+ * Composes PersonaEmbed internally -- does NOT reimplement session/video/audio logic.
5
+ * Adds the widget UI shell: minimized/active/hidden states, "Join call" button,
6
+ * minimize/expand toggle, corner positioning CSS, button color styling.
7
+ */
8
+ export declare class KflEmbedElement extends HTMLElement {
9
+ static get observedAttributes(): ("publishable-key" | "api-base-url" | "initial-state" | "controlled-widget-state" | "active-width" | "active-height" | "minimized-width" | "minimized-height" | "inline" | "corner" | "hide-ui" | "show-minimize-button" | "controlled-show-minimize-button" | "button-color" | "button-color-opacity" | "video-fit" | "preview-image" | "call-to-action")[];
10
+ private shadow;
11
+ private embed;
12
+ private _widgetState;
13
+ private _connected;
14
+ private _connecting;
15
+ private _initialized;
16
+ private widgetEl;
17
+ private innerEl;
18
+ private containerEl;
19
+ private previewImg;
20
+ private spinnerEl;
21
+ private errorToast;
22
+ private errorTimer;
23
+ private joinBtn;
24
+ private endCallBtn;
25
+ private toggleBtn;
26
+ private revealBtn;
27
+ private toolbarEl;
28
+ private micBtn;
29
+ private readonly _onJoinClick;
30
+ private readonly _onEndCallClick;
31
+ private readonly _onMicClick;
32
+ private readonly _onToggleClick;
33
+ private readonly _onRevealClick;
34
+ private readonly _onInnerClick;
35
+ constructor();
36
+ connectedCallback(): void;
37
+ disconnectedCallback(): void;
38
+ attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
39
+ mute(): Promise<void>;
40
+ unmute(): Promise<void>;
41
+ isMuted(): boolean;
42
+ canUnmute(): boolean;
43
+ micOn(): Promise<void>;
44
+ micOff(): Promise<void>;
45
+ isMicOn(): boolean;
46
+ canTurnOnMic(): boolean;
47
+ setEmotion(_emotion: 'neutral' | 'angry' | 'sad' | 'happy'): void;
48
+ private _initDom;
49
+ private _requireElement;
50
+ private _resolveInitialState;
51
+ private _renderJoinLabel;
52
+ private _applyPreviewImage;
53
+ private _readRenderConfig;
54
+ private _getAttrNum;
55
+ private _getCorner;
56
+ private _applyLayout;
57
+ private _applyState;
58
+ private _renderJoinButton;
59
+ private _renderToolbar;
60
+ private _renderToggleButton;
61
+ private _renderRevealButton;
62
+ private _renderJoinButtonColor;
63
+ private _shouldShowMinimizeButton;
64
+ private _handleJoinCall;
65
+ private _handleToggle;
66
+ private _handleInnerClick;
67
+ private _handleEndCall;
68
+ private _handleReveal;
69
+ private _setWidgetState;
70
+ private _handleMicToggle;
71
+ private _updateMicIcon;
72
+ private _hasLiveSession;
73
+ private _disconnectSession;
74
+ private _resetToMinimized;
75
+ private _showError;
76
+ private _connect;
77
+ }
@@ -28,3 +28,8 @@ export declare function createEventEmitter<T extends Record<string, any>>(): {
28
28
  removeAllListeners(): void;
29
29
  };
30
30
  export declare function floatTo16BitPCM(float32: Float32Array): Uint8Array;
31
+ /**
32
+ * Create an AudioWorkletNode that captures mono PCM input and posts
33
+ * Float32Array chunks (1024 samples = 64ms at 16kHz) to its message port.
34
+ */
35
+ export declare function createPcmWorkletNode(ctx: AudioContext): Promise<AudioWorkletNode>;
@@ -1,5 +1,6 @@
1
1
  import { ElevenLabsAgent, ElevenLabsConfig } from './elevenlabs';
2
2
  import { OpenAIRealtimeAgent, OpenAIRealtimeConfig, TurnDetection } from './openai-realtime';
3
+ import { KflAgent, KflAgentConfig } from './kfl';
3
4
  /**
4
5
  * Agent implementations for voice AI platforms.
5
6
  *
@@ -10,9 +11,10 @@ export { BaseAgent, DEFAULT_INPUT_SAMPLE_RATE } from './base';
10
11
  export type { Agent, AgentConfig, AgentEventMap, AgentState, Emotion } from './types';
11
12
  export { ElevenLabsAgent, type ElevenLabsConfig };
12
13
  export { OpenAIRealtimeAgent, type OpenAIRealtimeConfig, type TurnDetection };
13
- export { SAMPLE_RATE, base64ToBytes, bytesToBase64, resamplePcm, createEventEmitter, floatTo16BitPCM } from './audio-utils';
14
+ export { KflAgent, type KflAgentConfig };
15
+ export { SAMPLE_RATE, base64ToBytes, bytesToBase64, resamplePcm, createEventEmitter, floatTo16BitPCM, createPcmWorkletNode } from './audio-utils';
14
16
  /** Supported agent types */
15
- export type AgentType = 'elevenlabs' | 'openai';
17
+ export type AgentType = 'elevenlabs' | 'openai' | 'kfl';
16
18
  /** Agent type metadata */
17
19
  export interface AgentTypeInfo {
18
20
  id: AgentType;
@@ -25,9 +27,10 @@ export declare const AGENT_REGISTRY: AgentTypeInfo[];
25
27
  export interface AgentConfigMap {
26
28
  elevenlabs: ElevenLabsConfig;
27
29
  openai: OpenAIRealtimeConfig;
30
+ kfl: KflAgentConfig;
28
31
  }
29
32
  /** Union type of all agent instances */
30
- export type AnyAgent = ElevenLabsAgent | OpenAIRealtimeAgent;
33
+ export type AnyAgent = ElevenLabsAgent | OpenAIRealtimeAgent | KflAgent;
31
34
  /**
32
35
  * Create an agent instance by type.
33
36
  *
@@ -39,6 +42,7 @@ export type AnyAgent = ElevenLabsAgent | OpenAIRealtimeAgent;
39
42
  */
40
43
  export declare function createAgent(type: 'elevenlabs'): ElevenLabsAgent;
41
44
  export declare function createAgent(type: 'openai'): OpenAIRealtimeAgent;
45
+ export declare function createAgent(type: 'kfl'): KflAgent;
42
46
  export declare function createAgent(type: AgentType): AnyAgent;
43
47
  /**
44
48
  * Get agent type metadata by ID.
@@ -0,0 +1,26 @@
1
+ import { AgentConfig } from './types';
2
+ import { BaseAgent } from './base';
3
+ export interface KflAgentConfig extends AgentConfig {
4
+ /** WebSocket endpoint for kfl-voice-agent (wss://.../ws) */
5
+ wsUrl: string;
6
+ /** Optional ping interval override (ms) */
7
+ pingIntervalMs?: number;
8
+ }
9
+ export declare class KflAgent extends BaseAgent {
10
+ protected readonly agentName = "KflVoiceAgent";
11
+ private connectResolve;
12
+ private connectReject;
13
+ private connectTimeout;
14
+ private pingInterval;
15
+ private sourceInputSampleRate;
16
+ private turnEnded;
17
+ connect(config: KflAgentConfig): Promise<void>;
18
+ sendAudio(pcmData: Uint8Array): void;
19
+ close(): void;
20
+ protected handleParsedMessage(message: unknown): void;
21
+ private startPings;
22
+ private stopPings;
23
+ private clearConnectTimeout;
24
+ private resolvePendingConnect;
25
+ private rejectPendingConnect;
26
+ }
package/dist/index.d.ts CHANGED
@@ -3,9 +3,10 @@ export type { PersonaEmbedOptions } from './PersonaEmbed';
3
3
  export { PersonaView } from './PersonaView';
4
4
  export type { PersonaViewOptions } from './PersonaView';
5
5
  export type { EmbedStatus, VideoFit, VoiceAgentDetails, SessionDetails, BaseCallbacks, } from './types';
6
- export { createAgent, ElevenLabsAgent, OpenAIRealtimeAgent, BaseAgent, AGENT_REGISTRY, getAgentInfo, } from './agents';
7
- export type { AgentType, AgentConfig, AgentEventMap, Agent, AnyAgent, AgentTypeInfo, ElevenLabsConfig, OpenAIRealtimeConfig, TurnDetection, } from './agents';
6
+ export { createAgent, ElevenLabsAgent, OpenAIRealtimeAgent, KflAgent, BaseAgent, AGENT_REGISTRY, getAgentInfo, } from './agents';
7
+ export type { AgentType, AgentConfig, AgentEventMap, Agent, AnyAgent, AgentTypeInfo, ElevenLabsConfig, OpenAIRealtimeConfig, KflAgentConfig, TurnDetection, } from './agents';
8
8
  export type { AgentState } from '@keyframelabs/sdk';
9
9
  export { floatTo16BitPCM, resamplePcm, base64ToBytes, bytesToBase64, SAMPLE_RATE, createEventEmitter, } from './agents';
10
+ export { KflEmbedElement } from './KflEmbedElement';
10
11
  export { ApiError as KeyframeApiError } from './ApiError';
11
12
  export type { ApiErrorPayload as KeyframeApiErrorPayload } from './ApiError';