npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kolari-ai/widget-sdk

v0.1.10

Published

Embeddable voice AI widget SDK for Kolari AI platform

Readme

@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.

Features

  • 🎙️ 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

Installation

Via npm

npm install @kolari-ai/widget-sdk

Via Local Build (Development)

<!-- Use the locally built browser bundle -->
<script src="./dist/index.browser.js"></script>

Via CDN (Coming Soon)

<script src="https://cdn.kolari.ai/widget/latest/kolari-widget.browser.js"></script>

Quick Start

<!DOCTYPE html>
<html>
<head>
  <title>My Website</title>
</head>
<body>
  <h1>Welcome to my website</h1>

  <!-- Load the widget SDK -->
  <script src="./dist/index.browser.js"></script>

  <!-- Initialize the widget -->
  <script>
    KolariWidget.init({
      // Required
      apiKey: 'pk_live_...',           // Your public API key
      agentId: 'your-agent-id',        // Your agent ID
      serverUrl: 'https://www.kolari.ai', // Kolari server URL

      // Branding (optional)
      branding: {
        agentName: 'Support Assistant',
        primaryColor: '#6366f1',
        secondaryColor: '#8b5cf6'
      },

      // Behavior (optional)
      autoStart: false,  // Set true to auto-connect on page load
      debug: false,      // Enable for development debugging

      // Event handlers (optional)
      onReady: () => console.log('Widget ready!'),
      onConnect: () => console.log('Connected to agent'),
      onDisconnect: () => console.log('Disconnected'),
      onError: (error) => console.error('Widget error:', error)
    });
  </script>
</body>
</html>

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.

Using with React

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 <div>Your app content</div>;
}

Using with Vue

<template>
  <div>Your app content</div>
</template>

<script>
import { KolariWidget } from '@kolari-ai/widget-sdk';

export default {
  mounted() {
    this.widget = KolariWidget.init({
      apiKey: 'pk_live_...',
      agentId: 'your-agent-id',
      serverUrl: 'https://www.kolari.ai',

      branding: {
        primaryColor: '#6366f1',
        agentName: 'Assistant'
      }
    });
  },
  beforeUnmount() {
    this.widget?.destroy();
  }
};
</script>

Using with Next.js

'use client'; // For Next.js App Router

import { 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 <main>Your app content</main>;
}

Configuration Options

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)
}

Configuration Examples

Minimal Setup:

KolariWidget.init({
  apiKey: 'pk_live_...',
  agentId: 'agent-id',
  serverUrl: 'https://www.kolari.ai'
});

With Branding:

KolariWidget.init({
  apiKey: 'pk_live_...',
  agentId: 'agent-id',
  serverUrl: 'https://www.kolari.ai',

  branding: {
    agentName: 'Sales Assistant',
    primaryColor: '#6366f1',
    secondaryColor: '#8b5cf6'
  }
});

With Event Handlers:

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:

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

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:

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:

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:

Button Text Changes

  • Idle/Disconnected: "VOICE CHAT" with 📞 phone icon
  • Connecting: "CONNECTING..." with 📞 phone icon
  • Connected/Listening/Speaking: "END CHAT" with 📵 phone-slash icon

Orb Animation

  • Idle: Static colored orb
  • Connecting: Pulsing animation
  • Listening: Gentle breathing animation
  • Speaking: Active wave animation responding to audio levels

Visual States

  • Hover: Pill lifts up with enhanced shadow
  • Active: Border changes to brand color
  • Pulsing: Subtle opacity animation when connected

Browser Support

Desktop Browsers

  • ✅ Chrome/Edge 90+
  • ✅ Firefox 88+
  • ✅ Safari 15+
  • ✅ Opera 76+

Mobile Browsers

  • ✅ Mobile Safari (iOS 15+)
  • ✅ Chrome Android (90+)
  • ✅ Samsung Internet 14+

Not Supported

  • ❌ Internet Explorer (any version)
  • ❌ Legacy Edge (pre-Chromium)
  • ❌ iOS Safari < 15

Requirements

Technical Requirements

  • HTTPS connection (required for WebRTC and microphone access)
  • Modern browser with WebRTC support
  • Microphone access (will be requested on first connection)
  • JavaScript enabled

Development Requirements

  • Node.js 18+ (for building from source)
  • npm or yarn package manager

Server Requirements

  • Kolari API server
  • Valid API keys and agent configuration

Troubleshooting

Widget Not Appearing

Problem: Widget doesn't render on the page

Solutions:

  1. Check that the script is loaded:
    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

Microphone Permission Issues

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:

navigator.permissions.query({ name: 'microphone' as PermissionName })
  .then((result) => {
    console.log('Microphone permission:', result.state);
    // 'granted', 'denied', or 'prompt'
  });

Connection Errors

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: true

Debug Mode:

KolariWidget.init({
  apiKey: '...',
  agentId: '...',
  serverUrl: '...',
  debug: true,  // Enable detailed logging
});

No Audio or Voice

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

Styling Issues

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:
    /* Override widget styles */
    .kw-compact-pill {
      bottom: 30px !important;
    }

React/Next.js SSR Issues

Problem: Errors during server-side rendering

Solutions:

  1. Use dynamic import to load widget client-side only:
    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

Performance Issues

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)

Getting Help

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 snippet

License

MIT

Support

  • Report Issues: https://github.com/Anpu-Labs/kolari-ai-widget-sdk/issues
  • Email: [email protected]
  • Website: https://www.kolari.ai

Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.

Changelog

v0.1.4 (Latest)

  • DEFAULT COMPACT MODE: Widget now defaults to 'compact' display mode
  • 📝 Users no longer need to specify displayMode: 'compact' - it's automatic
  • 🎯 Improved out-of-the-box experience with sleek pill widget by default

v0.1.3

  • 🐛 CRITICAL FIX: Resolved duplicate widget pills appearing on page
  • 🔒 Added singleton pattern to prevent multiple widget instances
  • 🛡️ Implemented render guard in UI renderer to prevent duplicate rendering
  • 🧹 Enhanced destroy() method with robust error handling and cleanup verification
  • 🔄 Added persistent initialization flag for React Strict Mode compatibility
  • 🧰 Improved DOM cleanup to remove orphaned widget elements
  • 📊 Enhanced logging for better debugging of widget lifecycle
  • ✨ Widget now properly handles React component remounts in development
  • 🎯 All widget operations now have proper error boundaries

v0.1.0

  • ✨ Added compact display mode
  • ✨ Dynamic phone icon (📞/📵) based on connection state
  • ✨ 3D animated orb with audio level visualization
  • ✨ Single microphone permission request
  • ✨ Improved state management and visual feedback
  • 🐛 Fixed multiple permission request issue
  • 🐛 Fixed button color transitions in compact mode
  • 📝 Comprehensive README and documentation
  • 🎨 Enhanced branding customization options