Embeddable voice AI widget SDK for Kolari AI platform
npm install @kolari-ai/widget-sdk> Embeddable voice AI widget SDK for Kolari AI platform
A sleek, unobtrusive pill widget that adds voice AI to any website. Features a live 3D orb animation, one-click voice interaction, and seamless integration. Perfect for customer support, lead qualification, appointment booking, and more.
๐ Server URL: Use https://www.kolari.ai as your serverUrl in production.
๐ Found an issue? Report it on our GitHub Issues page.
- ๐๏ธ One-Click Voice Interaction - Users click to talk, click to end
- โจ 3D Animated Orb - Live visualization that responds to voice activity
- ๐ฑ Fully Responsive - Works on desktop and mobile browsers
- ๐จ Customizable Branding - Match your brand colors and style
- ๐ Secure & Private - HTTPS required, microphone permissions handled automatically
- โก Fast & Lightweight - Minimal bundle size, instant loading
``bash`
npm install @kolari-ai/widget-sdk
`html`
`html`
`html
Welcome to my website
`
That's it! The widget will appear as a pill at the bottom-right of your page with a live orb and "VOICE CHAT" button.
`tsx
import { KolariWidget } from '@kolari-ai/widget-sdk';
import { useEffect } from 'react';
function App() {
useEffect(() => {
const widget = KolariWidget.init({
apiKey: 'pk_live_...',
agentId: 'your-agent-id',
serverUrl: 'https://www.kolari.ai',
branding: {
primaryColor: '#6366f1',
agentName: 'Assistant'
},
onConnect: () => console.log('Connected!'),
onDisconnect: () => console.log('Disconnected'),
});
return () => widget.destroy();
}, []);
return
$3
`vue
Your app content
`$3
`tsx
'use client'; // For Next.js App Routerimport { useEffect } from 'react';
export default function Home() {
useEffect(() => {
// Dynamically import to avoid SSR issues
import('@kolari-ai/widget-sdk').then(({ KolariWidget }) => {
const widget = KolariWidget.init({
apiKey: process.env.NEXT_PUBLIC_KOLARI_API_KEY!,
agentId: process.env.NEXT_PUBLIC_KOLARI_AGENT_ID!,
serverUrl: process.env.NEXT_PUBLIC_KOLARI_SERVER_URL!,
branding: {
primaryColor: '#6366f1',
agentName: 'Support Assistant'
}
});
return () => widget.destroy();
});
}, []);
return Your app content ;
}
`Configuration Options
`typescript
interface WidgetConfig {
// ===== REQUIRED =====
apiKey: string; // Your public API key from Kolari dashboard
agentId: string; // The agent ID to connect to
serverUrl: string; // Kolari server URL
// Production: 'https://www.kolari.ai'
// Development: 'http://localhost:3000' // ===== BRANDING =====
branding?: {
primaryColor?: string; // Hex color code (e.g., '#6366f1')
secondaryColor?: string; // Hex color code (e.g., '#8b5cf6')
agentName?: string; // Display name for the agent (e.g., 'Support Assistant')
companyName?: string; // Your company name
};
// ===== DISPLAY =====
displayMode?: 'compact' | 'expanded'; // Widget display mode (default: 'compact')
// ===== BEHAVIOR =====
autoStart?: boolean; // Auto-connect on page load (default: false)
enableRecording?: boolean; // Enable session recording (default: true)
// ===== EVENT HANDLERS =====
onReady?: () => void; // Widget initialized and ready
onConnect?: () => void; // Connected to agent
onDisconnect?: () => void; // Disconnected from agent
onError?: (error: WidgetError) => void; // Error occurred
onStateChange?: (state: WidgetState) => void; // Widget state changed
onAudioLevel?: (level: number) => void; // Audio level update (0-1)
// ===== ADVANCED =====
debug?: boolean; // Enable debug logging (default: false)
reconnectAttempts?: number; // Max reconnection attempts (default: 3)
reconnectDelay?: number; // Delay between reconnects in ms (default: 2000)
}
`$3
Minimal Setup:
`typescript
KolariWidget.init({
apiKey: 'pk_live_...',
agentId: 'agent-id',
serverUrl: 'https://www.kolari.ai'
});
`With Branding:
`typescript
KolariWidget.init({
apiKey: 'pk_live_...',
agentId: 'agent-id',
serverUrl: 'https://www.kolari.ai', branding: {
agentName: 'Sales Assistant',
primaryColor: '#6366f1',
secondaryColor: '#8b5cf6'
}
});
`With Event Handlers:
`typescript
KolariWidget.init({
apiKey: 'pk_live_...',
agentId: 'agent-id',
serverUrl: 'https://www.kolari.ai', branding: {
agentName: 'Support Agent',
primaryColor: '#0066cc'
},
onReady: () => {
console.log('Widget is ready!');
},
onConnect: () => {
console.log('User connected to agent');
// Track analytics, update UI, etc.
},
onDisconnect: () => {
console.log('User disconnected');
},
onError: (error) => {
console.error('Widget error:', error.code, error.message);
// Show user-friendly error message
},
onStateChange: (state) => {
console.log('Widget state:', state);
// Update UI based on widget state
}
});
`Full Production Configuration:
`typescript
KolariWidget.init({
// Required
apiKey: process.env.KOLARI_API_KEY,
agentId: process.env.KOLARI_AGENT_ID,
serverUrl: 'https://www.kolari.ai', // Branding
branding: {
agentName: 'Customer Support',
primaryColor: '#6366f1',
secondaryColor: '#8b5cf6',
companyName: 'Acme Inc'
},
// Behavior
autoStart: false,
enableRecording: true,
// Advanced
reconnectAttempts: 3,
reconnectDelay: 2000,
debug: false,
// Event Handlers
onReady: () => trackEvent('widget_ready'),
onConnect: () => trackEvent('call_started'),
onDisconnect: () => trackEvent('call_ended'),
onError: (error) => {
logError(error);
showUserMessage('Connection failed. Please try again.');
}
});
`API Methods
`typescript
const widget = KolariWidget.init({ ... });// Connection control
await widget.connect(); // Connect to agent
await widget.disconnect(); // Disconnect from agent
// UI control
widget.show(); // Show the widget
widget.hide(); // Hide the widget
widget.minimize(); // Minimize the widget
widget.maximize(); // Maximize the widget
// Audio control
widget.mute(); // Mute microphone
widget.unmute(); // Unmute microphone
widget.setVolume(0.8); // Set volume (0-1)
// State queries
widget.isConnected(); // Check connection status
widget.isReady(); // Check if widget is ready
widget.getState(); // Get current widget state
widget.getSession(); // Get session metadata
widget.getStats(); // Get session statistics
// Event listeners
widget.on('connect', () => { ... });
widget.on('disconnect', () => { ... });
widget.on('error', (error) => { ... });
widget.on('stateChange', (state) => { ... });
widget.on('transcript', (message) => { ... });
// Cleanup
widget.destroy(); // Remove widget and cleanup
`Events
The widget emits the following events that you can listen to:
| Event | Payload | Description |
|-------|---------|-------------|
|
ready | void | Widget is initialized and ready |
| connect | void | Connected to voice agent |
| disconnect | void | Disconnected from agent |
| error | WidgetError | An error occurred |
| stateChange | WidgetState | Widget state changed |
| transcript | TranscriptMessage | New transcript message (expanded mode) |
| audioLevel | number | Audio level update (0-1) |
| participantJoined | Participant | Participant joined session |
| participantLeft | Participant | Participant left session |
| reconnecting | number | Attempting to reconnect (attempt number) |
| reconnected | void | Successfully reconnected |Example Usage:
`typescript
const widget = KolariWidget.init({ ... });widget.on('stateChange', (state) => {
console.log('Widget state:', state);
// Update your UI based on state
});
widget.on('error', (error) => {
console.error('Widget error:', error.code, error.message);
// Show error message to user
});
widget.on('audioLevel', (level) => {
// level is 0-1, visualize audio activity
updateAudioVisualizer(level);
});
`Widget States
The widget transitions through the following states:
| State | Description | Button Text (Compact) |
|-------|-------------|----------------------|
|
idle | Widget initialized, not connected | "VOICE CHAT" |
| connecting | Connecting to agent | "CONNECTING..." |
| connected | Connected and ready | "END CHAT" |
| listening | Listening for user input | "LISTENING..." |
| speaking | Agent is speaking | "SPEAKING..." |
| disconnected | Disconnected from agent | "VOICE CHAT" |
| error | Error occurred | "TRY AGAIN" |Microphone Permissions
The widget automatically handles microphone permissions through WebRTC connection. When a user clicks "VOICE CHAT" (or your custom connect button), the browser will:
1. Request microphone permission (first time only)
2. Remember the permission for future sessions
3. Auto-connect if permission was previously granted
Important Notes:
- Permission is requested only once - the widget delegates all microphone access to Kolari
- Users can revoke permissions through their browser settings
- HTTPS is required for microphone access (WebRTC security requirement)
- If permission is denied, the widget will show an error state
Testing Permissions:
`typescript
KolariWidget.init({
apiKey: 'pk_live_...',
agentId: 'agent-id',
serverUrl: 'https://www.kolari.ai',
displayMode: 'compact',
onError: (error) => {
if (error.code === 'MICROPHONE_PERMISSION_DENIED') {
console.error('Microphone access denied by user');
// Show custom UI to guide user to enable permissions
}
}
});
`Widget UI States
The widget provides visual feedback through:
$3
- Idle/Disconnected: "VOICE CHAT" with ๐ phone icon
- Connecting: "CONNECTING..." with ๐ phone icon
- Connected/Listening/Speaking: "END CHAT" with ๐ต phone-slash icon$3
- Idle: Static colored orb
- Connecting: Pulsing animation
- Listening: Gentle breathing animation
- Speaking: Active wave animation responding to audio levels$3
- Hover: Pill lifts up with enhanced shadow
- Active: Border changes to brand color
- Pulsing: Subtle opacity animation when connectedBrowser Support
$3
- โ
Chrome/Edge 90+
- โ
Firefox 88+
- โ
Safari 15+
- โ
Opera 76+$3
- โ
Mobile Safari (iOS 15+)
- โ
Chrome Android (90+)
- โ
Samsung Internet 14+$3
- โ Internet Explorer (any version)
- โ Legacy Edge (pre-Chromium)
- โ iOS Safari < 15Requirements
$3
- โ
HTTPS connection (required for WebRTC and microphone access)
- โ
Modern browser with WebRTC support
- โ
Microphone access (will be requested on first connection)
- โ
JavaScript enabled$3
- Node.js 18+ (for building from source)
- npm or yarn package manager$3
- Kolari API server
- Valid API keys and agent configurationTroubleshooting
$3
Problem: Widget doesn't render on the page
Solutions:
1. Check that the script is loaded:
`javascript
console.log(typeof KolariWidget); // Should output "function"
`
2. Verify API key and agent ID are correct
3. Check browser console for errors
4. Ensure you're calling KolariWidget.init() after the script loads$3
Problem: Permission requested multiple times or not working
Solutions:
1. Ensure you're using HTTPS (required for WebRTC)
2. Check browser microphone permissions in settings
3. Clear browser cache and test again
4. On iOS Safari, ensure iOS 15+ is being used
Checking Permissions:
`typescript
navigator.permissions.query({ name: 'microphone' as PermissionName })
.then((result) => {
console.log('Microphone permission:', result.state);
// 'granted', 'denied', or 'prompt'
});
`$3
Problem: Widget fails to connect to agent
Solutions:
1. Verify
serverUrl is correct and accessible
2. Check API key has proper permissions
3. Ensure agent ID exists and is active
4. Check network tab for failed requests
5. Enable debug mode: debug: trueDebug Mode:
`typescript
KolariWidget.init({
apiKey: '...',
agentId: '...',
serverUrl: '...',
debug: true, // Enable detailed logging
});
`$3
Problem: Connected but no audio or agent doesn't respond
Solutions:
1. Check system volume and browser audio settings
2. Verify agent has proper voice configuration
3. Test microphone with another application
4. Check for console errors related to audio
$3
Problem: Widget styling conflicts with site CSS
Solutions:
1. Widget uses isolated styles with
kw- prefix
2. Check for CSS conflicts in browser DevTools
3. Override styles if needed:
`css
/ Override widget styles /
.kw-compact-pill {
bottom: 30px !important;
}
`$3
Problem: Errors during server-side rendering
Solutions:
1. Use dynamic import to load widget client-side only:
`typescript
useEffect(() => {
import('@kolari-ai/widget-sdk').then(({ KolariWidget }) => {
// Initialize here
});
}, []);
`
2. For Next.js, use 'use client' directive
3. Ensure widget initialization happens in useEffect$3
Problem: Widget causing page slowdown
Solutions:
1. Don't initialize widget multiple times
2. Properly cleanup with
widget.destroy()
3. Check for memory leaks in browser DevTools
4. Disable debug mode in production (debug: false)$3
Still having issues? Here's how to get support:
1. Check the logs: Enable
debug: true and check console
2. Test in isolation: Create a minimal HTML test page
3. Browser compatibility: Verify you're using a supported browser
4. Network issues: Check network tab for failed requests
5. Contact support: Provide error messages, browser version, and code snippetLicense
MIT
Support
- Report Issues: https://github.com/Anpu-Labs/kolari-ai-widget-sdk/issues
- Email: support@kolari.ai
- Website: https://www.kolari.ai
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.
Changelog
$3
- โจ DEFAULT COMPACT MODE: Widget now defaults to 'compact' display mode
- ๐ Users no longer need to specify displayMode: 'compact'` - it's automatic