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

@makemore/agent-frontend

v2.8.1

Published

A lightweight chat widget for AI agents. Use as an embeddable script tag or import directly into React/Preact projects.

Readme

Agent Frontend

A lightweight chat widget for AI agents built with Preact. Embed conversational AI into any website with a single script tag.

Why Agent Frontend?

Most chat widgets are tightly coupled to specific frameworks or require complex build setups. Agent Frontend is different:

  • Lightweight - Built with Preact (~3kb) for minimal bundle size
  • CSS isolated - Won't conflict with your existing styles (uses all: initial reset)
  • SSE streaming - Real-time token-by-token responses, not polling
  • Production ready - Session management, error handling, conversation persistence
  • Component-based - Clean architecture with hooks for state management

Features

| Feature | Description | |---------|-------------| | 💬 Real-time Streaming | SSE-based message streaming for instant, token-by-token responses | | 📎 File Uploads | Attach files to messages with drag-drop, preview chips, and progress indicators | | 🖼️ Document Preview | Image thumbnails and file icons in chat messages | | 🔊 Text-to-Speech | ElevenLabs integration with secure Django proxy support | | 🎨 Theming | Customize colors, titles, messages, and position | | 🌙 Dark Mode | Automatic dark mode based on system preferences | | 📱 Responsive | Works seamlessly on desktop and mobile | | 🔧 Debug Mode | Toggle visibility of tool calls and results | | 🤖 Demo Flows | Built-in auto-run mode with automatic, confirm, and manual modes | | 🔒 Sessions | Automatic anonymous session creation and management | | 💾 Persistence | Conversations persist across page reloads via localStorage | | 🛡️ Isolated CSS | Scoped styles that won't leak into or from your page | | 🎯 Configurable APIs | Customize backend endpoints to match your server structure | | 📝 Enhanced Markdown | Optional rich markdown with tables, code blocks, and syntax highlighting |

Installation

Via npm

npm install @makemore/agent-frontend

Then include in your HTML:

<link rel="stylesheet" href="node_modules/@makemore/agent-frontend/dist/chat-widget.css">
<script src="node_modules/@makemore/agent-frontend/dist/chat-widget.js"></script>

Via CDN (unpkg)

<link rel="stylesheet" href="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.css">
<script src="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.js"></script>

Via CDN (jsDelivr)

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@makemore/agent-frontend/dist/chat-widget.css">
<script src="https://cdn.jsdelivr.net/npm/@makemore/agent-frontend/dist/chat-widget.js"></script>

Optional: Enhanced Markdown Support

For full-featured markdown rendering (tables, code blocks with syntax highlighting, etc.), include the optional markdown addon:

<!-- Core widget -->
<link rel="stylesheet" href="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.css">
<script src="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.js"></script>

<!-- Optional: Enhanced markdown with marked.js -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget-markdown.js"></script>

<!-- Optional: Syntax highlighting for code blocks -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11/styles/github-dark.min.css">
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/languages/javascript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/languages/python.min.js"></script>

The widget automatically detects and uses the enhanced markdown parser if available. Without it, a basic markdown parser is used.

Quick Start

Basic Setup

<script>
  ChatWidget.init({
    backendUrl: 'https://your-api.com',
    agentKey: 'your-agent',
    title: 'Support Chat',
    primaryColor: '#0066cc',
  });
</script>

With Text-to-Speech (Recommended: Django Proxy)

<script>
  ChatWidget.init({
    backendUrl: 'https://your-api.com',
    agentKey: 'your-agent',
    title: 'Voice-Enabled Chat',
    primaryColor: '#0066cc',
    enableTTS: true,
    ttsProxyUrl: 'https://your-api.com/api/tts/speak/',
  });
</script>

See django-tts-example.py for the complete Django backend implementation.

With custom API paths

<script>
  ChatWidget.init({
    backendUrl: 'https://your-api.com',
    agentKey: 'your-agent',
    title: 'Support Chat',
    primaryColor: '#0066cc',
    apiPaths: {
      anonymousSession: '/api/auth/session/',
      runs: '/api/chat/runs/',
      runEvents: '/api/chat/runs/{runId}/events/',
      simulateCustomer: '/api/chat/simulate-customer/',
      ttsVoices: '/api/tts/voices/',           // For voice settings UI (proxy mode)
      ttsSetVoice: '/api/tts/set-voice/',      // For voice settings UI (proxy mode)
    },
  });
</script>

Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | backendUrl | string | 'http://localhost:8000' | Backend API URL | | agentKey | string | 'insurance-agent' | Agent identifier | | title | string | 'Chat Assistant' | Widget header title | | subtitle | string | 'How can we help you today?' | Widget subtitle | | primaryColor | string | '#0066cc' | Primary theme color | | position | string | 'bottom-right' | Widget position (bottom-right or bottom-left) | | defaultJourneyType | string | 'general' | Default journey type | | enableDebugMode | boolean | true | Show debug toggle button | | enableAutoRun | boolean | true | Show demo flows dropdown | | placeholder | string | 'Type your message...' | Input placeholder text | | emptyStateTitle | string | 'Start a Conversation' | Empty state heading | | emptyStateMessage | string | 'Send a message to get started.' | Empty state description | | journeyTypes | object | {} | Journey type definitions for demo flows | | anonymousTokenHeader | string | 'X-Anonymous-Token' | Header name for auth token | | conversationIdKey | string | 'chat_widget_conversation_id' | localStorage key for conversation ID | | sessionTokenKey | string | 'chat_widget_session_token' | localStorage key for session token | | apiPaths | object | See below | API endpoint paths (customizable for different backends) | | autoRunMode | string | 'automatic' | Demo flow mode: 'automatic', 'confirm', or 'manual' | | autoRunDelay | number | 1000 | Delay in milliseconds before auto-generating next message (automatic mode) | | enableTTS | boolean | false | Enable text-to-speech for messages | | ttsProxyUrl | string | null | Django proxy URL for TTS (recommended for security) | | elevenLabsApiKey | string | null | ElevenLabs API key (only if not using proxy) | | ttsVoices | object | { assistant: null, user: null } | Voice IDs (only if not using proxy) | | ttsModel | string | 'eleven_turbo_v2_5' | ElevenLabs model (only if not using proxy) | | ttsSettings | object | See below | ElevenLabs voice settings (only if not using proxy) | | availableVoices | array | [] | List of available voices (auto-populated from ElevenLabs API) | | showClearButton | boolean | true | Show clear conversation button in header | | showDebugButton | boolean | true | Show debug mode toggle button in header | | showTTSButton | boolean | true | Show TTS toggle button in header | | showVoiceSettings | boolean | true | Show voice settings button in header (works with proxy and direct API) | | showExpandButton | boolean | true | Show expand/minimize button in header | | showConversationSidebar | boolean | true | Show conversation history sidebar with hamburger menu | | onEvent | function | null | Callback for SSE events: (eventType, payload) => void | | authStrategy | string | null | Auth strategy: 'token', 'jwt', 'session', 'anonymous', 'none' (auto-detected if null) | | authToken | string | null | Token value for 'token' or 'jwt' strategies | | authHeader | string | null | Custom header name (defaults based on strategy) | | authTokenPrefix | string | null | Custom token prefix (defaults based on strategy) | | anonymousSessionEndpoint | string | null | Endpoint for anonymous session (defaults to apiPaths.anonymousSession) | | anonymousTokenKey | string | 'chat_widget_anonymous_token' | localStorage key for anonymous token | | onAuthError | function | null | Callback for auth errors: (error) => void |

Authentication

The widget supports multiple authentication strategies with sensible defaults:

Token Authentication (Django REST Framework)

ChatWidget.init({
  backendUrl: 'https://api.example.com',
  agentKey: 'my-agent',
  authStrategy: 'token',
  authToken: 'abc123...',
  // Sends: Authorization: Token abc123...
});

JWT/Bearer Authentication

ChatWidget.init({
  backendUrl: 'https://api.example.com',
  agentKey: 'my-agent',
  authStrategy: 'jwt',
  authToken: 'eyJ...',
  // Sends: Authorization: Bearer eyJ...
});

Session-Based Authentication (Cookies)

ChatWidget.init({
  backendUrl: 'https://api.example.com',
  agentKey: 'my-agent',
  authStrategy: 'session',
  // Sends requests with credentials: 'include'
  // No auth header, relies on session cookie
});

Anonymous Session Tokens

ChatWidget.init({
  backendUrl: 'https://api.example.com',
  agentKey: 'my-agent',
  authStrategy: 'anonymous',
  anonymousSessionEndpoint: '/api/accounts/anonymous-session/',
  // On first request: fetches anonymous token from endpoint
  // Persists token to localStorage
  // Sends: X-Anonymous-Token: {token}
});

No Authentication (Public Endpoints)

ChatWidget.init({
  backendUrl: 'https://api.example.com',
  agentKey: 'my-agent',
  authStrategy: 'none',
  // No auth headers sent
});

Custom Headers and Prefixes

ChatWidget.init({
  authStrategy: 'token',
  authToken: 'mytoken123',
  authHeader: 'X-API-Key',        // Custom header name
  authTokenPrefix: '',             // No prefix (just the token)
  // Sends: X-API-Key: mytoken123
});

Dynamic Token Updates

Update authentication after initialization (e.g., after user login):

// After user logs in
ChatWidget.setAuth({
  strategy: 'jwt',
  token: 'new-jwt-token-after-login'
});

// After user logs out
ChatWidget.clearAuth();

// Handle token refresh on auth errors
ChatWidget.init({
  authStrategy: 'jwt',
  authToken: initialToken,
  onAuthError: async (error) => {
    if (error.status === 401) {
      const newToken = await refreshToken();
      ChatWidget.setAuth({ token: newToken });
    }
  }
});

Auto-Detection

If no authStrategy is specified, the widget auto-detects based on config:

  • If authToken is provided → uses 'token' strategy
  • If anonymousSessionEndpoint or apiPaths.anonymousSession is configured → uses 'anonymous' strategy
  • Otherwise → uses 'none'

Event Callback

The onEvent callback allows your application to react to all SSE events from the agent:

ChatWidget.init({
  backendUrl: 'http://localhost:8000',
  agentKey: 'your-agent',
  onEvent: (eventType, payload) => {
    console.log('Event:', eventType, payload);

    // Example: Navigate when a session is created
    if (eventType === 'tool.result' && payload.result?.session_id) {
      window.location.href = `/session/${payload.result.session_id}`;
    }

    // Example: Track tool usage
    if (eventType === 'tool.call') {
      analytics.track('Tool Called', { tool: payload.name });
    }
  },
});

Event Types:

  • assistant.message - Streaming assistant responses (payload: { content: string })
  • tool.call - Tool being called (payload: { name: string, arguments: object })
  • tool.result - Tool result (payload: { result: any })
  • run.succeeded - Run completed successfully
  • run.failed - Run failed (payload: { error: string })
  • run.cancelled - Run was cancelled
  • run.timed_out - Run timed out
  • Custom events emitted by your agent

Text-to-Speech (ElevenLabs)

Add realistic voice narration to your chat widget using ElevenLabs. Two integration options:

Option 1: Secure Django Proxy (Recommended)

Keep your API key secure on the server:

ChatWidget.init({
  enableTTS: true,
  ttsProxyUrl: 'https://your-backend.com/api/tts/speak/',
  // No API key or voice IDs needed - configured on server
});

Django Setup:

See django-tts-example.py for a complete Django REST Framework implementation. Quick setup:

  1. Install: pip install requests
  2. Add to settings.py:
ELEVENLABS_API_KEY = 'your_api_key_here'
ELEVENLABS_VOICES = {
    'assistant': 'EXAVITQu4vr4xnSDxMaL',  # Bella
    'user': 'pNInz6obpgDQGcFmaJgB',       # Adam
}
  1. Add views from django-tts-example.py to your Django app
  2. Add URL routes:
path('api/tts/speak/', views.text_to_speech),
path('api/tts/voices/', views.get_voices),      # For voice settings UI
path('api/tts/set-voice/', views.set_voice),    # For voice settings UI

Voice Settings Support:

The widget now supports voice settings UI in proxy mode! Add these endpoints to enable the voice picker:

# Get available voices
@api_view(['GET'])
def get_voices(request):
    """Fetch available voices from ElevenLabs"""
    try:
        response = requests.get(
            'https://api.elevenlabs.io/v1/voices',
            headers={'xi-api-key': settings.ELEVENLABS_API_KEY}
        )
        return JsonResponse(response.json())
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)

# Set voice for user session
@api_view(['POST'])
def set_voice(request):
    """Update voice preference for user's session"""
    role = request.data.get('role')  # 'assistant' or 'user'
    voice_id = request.data.get('voice_id')

    # Store in session or database
    if not hasattr(request, 'session'):
        return JsonResponse({'error': 'Session not available'}, status=400)

    if role not in ['assistant', 'user']:
        return JsonResponse({'error': 'Invalid role'}, status=400)

    # Store voice preference in session
    if 'tts_voices' not in request.session:
        request.session['tts_voices'] = {}
    request.session['tts_voices'][role] = voice_id
    request.session.modified = True

    return JsonResponse({'success': True, 'role': role, 'voice_id': voice_id})

# Update text_to_speech view to use session voices
@api_view(['POST'])
def text_to_speech(request):
    text = request.data.get('text', '')
    role = request.data.get('role', 'assistant')

    # Get voice from session or fall back to settings
    session_voices = request.session.get('tts_voices', {})
    voice_id = session_voices.get(role) or settings.ELEVENLABS_VOICES.get(role)

    # ... rest of TTS implementation

Option 2: Direct API (Client-Side)

For testing or simple deployments:

ChatWidget.init({
  enableTTS: true,
  elevenLabsApiKey: 'your_elevenlabs_api_key',  // ⚠️ Exposed to client
  ttsVoices: {
    assistant: 'EXAVITQu4vr4xnSDxMaL',  // Bella
    user: 'pNInz6obpgDQGcFmaJgB',       // Adam
  },
  ttsModel: 'eleven_turbo_v2_5',
  ttsSettings: {
    stability: 0.5,
    similarity_boost: 0.75,
    style: 0.0,
    use_speaker_boost: true,
  },
});

Features:

  • Speaks assistant responses automatically
  • Speaks simulated user messages in demo mode
  • Queues messages to prevent overlap
  • Waits for speech to finish before continuing demo (automatic mode)
  • Toggle TTS on/off with button in header
  • Visual indicator when speaking (pulsing icon)

Get Voice IDs:

  1. Go to https://elevenlabs.io/app/voice-library
  2. Choose voices and copy their IDs
  3. Or use the API: https://api.elevenlabs.io/v1/voices

Control TTS:

ChatWidget.toggleTTS();  // Toggle on/off
ChatWidget.stopSpeech(); // Stop current speech and clear queue
ChatWidget.setVoice('assistant', 'voice_id'); // Change assistant voice
ChatWidget.setVoice('user', 'voice_id'); // Change user voice

Voice Settings UI:

A voice settings button (🎙️) appears in the header when TTS is enabled. Click it to:

  • Select assistant voice from dropdown
  • Select customer voice for demo mode
  • Voices are automatically fetched from your ElevenLabs account (direct API) or Django backend (proxy mode)

Works with both proxy and direct API modes! Just implement the /api/tts/voices/ and /api/tts/set-voice/ endpoints in your Django backend (see above).

Customize Header Buttons:

ChatWidget.init({
  showClearButton: true,      // Show/hide clear button
  showDebugButton: true,       // Show/hide debug button
  showTTSButton: true,         // Show/hide TTS toggle
  showVoiceSettings: true,     // Show/hide voice settings (direct API only)
  showExpandButton: true,      // Show/hide expand button
});

Demo Flow Control

The widget supports three modes for demo flows:

  • Automatic (autoRunMode: 'automatic'): Continuously generates customer responses with a configurable delay
  • Confirm Next (autoRunMode: 'confirm'): Pauses after each assistant response and waits for user to click "Continue"
  • Manual (autoRunMode: 'manual'): Stops auto-generation; user must manually type responses

These settings can be changed in real-time via the demo controls dropdown (visible when a demo is running).

ChatWidget.init({
  autoRunMode: 'confirm',  // Start in confirm mode
  autoRunDelay: 2000,      // 2 second delay in automatic mode
});

// Change mode programmatically
ChatWidget.setAutoRunMode('automatic');
ChatWidget.setAutoRunDelay(500);  // 0.5 second delay

API Paths Configuration

The apiPaths option allows you to customize the backend API endpoints. This is useful when integrating with different backend frameworks or URL structures.

Default values:

apiPaths: {
  anonymousSession: '/api/accounts/anonymous-session/',
  runs: '/api/agent-runtime/runs/',
  runEvents: '/api/agent-runtime/runs/{runId}/events/',
  simulateCustomer: '/api/agent-runtime/simulate-customer/',
}

Example - Custom Django backend:

ChatWidget.init({
  backendUrl: 'https://your-api.com',
  agentKey: 'chat-agent',
  apiPaths: {
    anonymousSession: '/api/ai/agent/anonymous-session/',
    runs: '/api/ai/agent/runs/',
    runEvents: '/api/ai/agent/runs/{runId}/events/',
    // simulateCustomer uses default value
  },
});

You only need to specify the paths you want to override; unspecified paths will use the defaults.

Journey Types Configuration

Define demo flows that users can trigger from the dropdown:

ChatWidget.init({
  // ... other options
  journeyTypes: {
    quote: {
      label: '🏠 Get a Quote',
      description: 'Get an insurance quote',
      initialMessage: "Hi, I'd like to get a quote for home insurance.",
    },
    claim: {
      label: '📋 File a Claim',
      description: 'File an insurance claim',
      initialMessage: 'Hi, I need to file a claim.',
    },
  },
});

JavaScript API

Methods

// Initialize the widget
ChatWidget.init(config);

// Open the chat widget
ChatWidget.open();

// Close the chat widget
ChatWidget.close();

// Send a message programmatically
ChatWidget.send('Hello, I need help!');

// Clear the conversation
ChatWidget.clearMessages();

// Conversation sidebar controls
ChatWidget.toggleSidebar();  // Open/close conversation sidebar
ChatWidget.newConversation();  // Start a new conversation
ChatWidget.switchConversation('conversation-id');  // Switch to a specific conversation
ChatWidget.loadMoreMessages();  // Load older messages

// Text-to-speech controls
ChatWidget.toggleTTS();  // Toggle TTS on/off
ChatWidget.stopSpeech(); // Stop current speech and clear queue
ChatWidget.setVoice('assistant', 'voice_id'); // Change assistant voice
ChatWidget.setVoice('user', 'voice_id'); // Change user voice

// Start a demo flow
ChatWidget.startDemoFlow('quote');

// Stop demo flow
ChatWidget.stopAutoRun();

// Continue demo flow (when in confirm mode and paused)
ChatWidget.continueAutoRun();

// Change demo flow mode
ChatWidget.setAutoRunMode('automatic');  // 'automatic', 'confirm', or 'manual'

// Change auto-run delay (in milliseconds)
ChatWidget.setAutoRunDelay(2000);

// Authentication methods
ChatWidget.setAuth({ strategy: 'jwt', token: 'new-token' }); // Update auth
ChatWidget.clearAuth(); // Clear authentication

// Remove the widget from the page
ChatWidget.destroy();

// Get current state (read-only)
const state = ChatWidget.getState();

// Get current config (read-only)
const config = ChatWidget.getConfig();

Markdown Support

The widget includes built-in markdown rendering for assistant messages:

Basic Markdown (Built-in)

The widget includes a lightweight markdown parser that supports:

  • Bold (**text** or __text__)
  • Italic (*text* or _text_)
  • Inline code (`code`)
  • Links ([text](url))
  • Lists (- item or * item)
  • Line breaks

Enhanced Markdown (Optional)

Include chat-widget-markdown.js for full-featured markdown:

  • Tables - Full GFM table support
  • Code blocks - Multi-line code with syntax highlighting
  • Blockquotes - > quoted text
  • Headings - # H1 through ###### H6
  • Horizontal rules - --- or ***
  • Task lists - - [ ] todo and - [x] done
  • Strikethrough - ~~text~~

Supported languages for syntax highlighting: Add highlight.js language modules as needed (JavaScript, Python, TypeScript, Go, Rust, etc.)

Backend Requirements

The widget expects a backend API with the following endpoints:

Create Anonymous Session

POST /api/accounts/anonymous-session/
Response: { "token": "..." }

Create Agent Run

POST /api/agent-runtime/runs/
Headers: { "X-Anonymous-Token": "..." }
Body: {
  "agentKey": "...",
  "conversationId": "...",
  "messages": [{ "role": "user", "content": "..." }],
  "metadata": { "journey_type": "..." }
}
Response: { "id": "...", "conversationId": "..." }

SSE Events Stream

GET /api/agent-runtime/runs/{runId}/events/?anonymous_token=...
Events: assistant.message, tool.call, tool.result, run.succeeded, run.failed

Simulate Customer (for demo flows)

POST /api/agent-runtime/simulate-customer/
Body: { "messages": [...], "journey_type": "..." }
Response: { "response": "..." }

CSS Isolation

The widget uses multiple techniques to prevent style conflicts:

.cw-container {
  all: initial;           /* Reset all inherited styles */
  /* ... widget styles scoped here */
}

.cw-container *,
.cw-container *::before,
.cw-container *::after {
  box-sizing: border-box; /* Consistent box model */
}
  • All CSS classes prefixed with cw-
  • CSS variables scoped to .cw-container, not :root
  • High z-index (99999) to stay above host page content
  • Font smoothing reset for consistent text rendering

Development

# Clone and serve locally
git clone <repo-url>
cd agent-frontend
python -m http.server 8080

# Open http://localhost:8080/demo.html

File Structure

agent-frontend/
├── dist/
│   ├── chat-widget.js    # Main library (~750 lines, ~15kb)
│   └── chat-widget.css   # Styles (~500 lines, ~8kb)
├── demo.html             # Interactive demo page
└── README.md

Browser Support

| Browser | Version | |---------|---------| | Chrome | 60+ | | Firefox | 55+ | | Safari | 11+ | | Edge | 79+ |

Requires: EventSource (SSE), fetch, localStorage

Multiple Instances

You can create multiple independent chat widgets on the same page using createInstance():

Basic Multi-Instance Setup

<div id="chat-1" style="width: 400px; height: 500px;"></div>
<div id="chat-2" style="width: 400px; height: 500px;"></div>

<script>
  // Create first widget
  const widget1 = ChatWidget.createInstance({
    containerId: 'chat-1',
    backendUrl: 'https://your-api.com',
    agentKey: 'support-agent',
    title: 'Support Chat',
    primaryColor: '#0066cc',
    embedded: true,
  });

  // Create second widget
  const widget2 = ChatWidget.createInstance({
    containerId: 'chat-2',
    backendUrl: 'https://your-api.com',
    agentKey: 'sales-agent',
    title: 'Sales Chat',
    primaryColor: '#00cc66',
    embedded: true,
  });
</script>

Instance Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | containerId | string | null | ID of the container element for embedded mode | | embedded | boolean | false | If true, renders inline in container instead of floating | | metadata | object | {} | Custom metadata to send with each request |

Instance Methods

Each instance returned by createInstance() has its own methods:

const widget = ChatWidget.createInstance({ ... });

// Control the widget
widget.open();
widget.close();
widget.send('Hello!');
widget.clearMessages();

// TTS controls
widget.toggleTTS();
widget.stopSpeech();

// Authentication
widget.setAuth({ strategy: 'jwt', token: 'new-token' });
widget.clearAuth();

// Get state/config
const state = widget.getState();
const config = widget.getConfig();

// Destroy the widget
widget.destroy();

Managing Multiple Instances

// Get a specific instance by ID
const widget = ChatWidget.getInstance('cw-1');

// Get all instances
const allWidgets = ChatWidget.getAllInstances();

// Destroy all instances
ChatWidget.getAllInstances().forEach(w => w.destroy());

Embedded vs Floating Mode

Embedded Mode (embedded: true):

  • Widget renders inside the specified container
  • No floating button or close button
  • Widget is always visible
  • Perfect for split-pane layouts, dashboards, or dedicated chat pages

Floating Mode (embedded: false, default):

  • Widget appears as a floating button in the corner
  • Clicking opens the chat panel
  • Has close and expand buttons
  • Traditional chat widget behavior

Storage Isolation

Each embedded instance uses isolated localStorage keys based on containerId:

  • chat_widget_conversation_id_chat-1 for widget in #chat-1
  • chat_widget_conversation_id_chat-2 for widget in #chat-2

This ensures conversations don't get mixed up between instances.

Version History

v2.7.0 (Latest)

  • 🔧 Embedded Scroll Fix: Fixed scrolling in embedded mode with proper flex layout
  • 📐 Added flex-direction: column, overflow: hidden to .cw-widget-embedded
  • 📜 Added min-height: 0 to .cw-messages for proper flex child scrolling

v2.6.0

  • ✏️ Edit & Retry: Edit user messages and retry from any point in conversation
  • 📋 Task List: Agents can track work with task management UI

v2.1.0

  • 🎤 Voice Input: Speech-to-text input using Web Speech API
  • 🎙️ Microphone button with visual recording indicator
  • 🌐 Automatic language detection from browser settings
  • ⚙️ Configurable via enableVoice option (enabled by default)

v2.0.1

  • 🔄 Preact Rewrite: Complete rewrite using Preact for better maintainability
  • 🧩 Component Architecture: Modular components (ChatWidget, Header, InputForm, Message, MessageList, Sidebar)
  • 🪝 React-style Hooks: useChat and useModels hooks for state management
  • 🎛️ Model Selector: Built-in model selection dropdown
  • 📦 Smaller Bundle: Optimized build with esbuild
  • 🔧 Better Developer Experience: Watch mode, source maps, cleaner code structure

v1.10.1

  • 📚 Conversation Sidebar: Browse and switch between past conversations via hamburger menu
  • 📜 Message Pagination: Load older messages with "load more" functionality
  • 🔐 CSRF Support: Automatic CSRF token handling for Django session auth
  • New Conversation: Start fresh conversations from the sidebar
  • 🔄 Auto-restore: Automatically loads messages when returning to a conversation

v1.5.0

  • 🔀 Multiple Instances: Create multiple independent chat widgets on the same page
  • 📦 Embedded Mode: Render widgets inline in containers for dashboards and split-pane layouts
  • 🔒 Storage Isolation: Each instance has isolated localStorage for conversations
  • 🎯 Instance API: Full control over individual widget instances

v1.4.0

  • Text-to-Speech: ElevenLabs integration with secure Django proxy support
  • 🔊 Automatic speech for assistant and simulated user messages
  • 🎛️ Smart speech queuing to prevent overlap
  • 🔐 Secure proxy approach keeps API keys on server

v1.3.0

  • 🎮 Demo Flow Control: Three modes (automatic, confirm-next, manual)
  • ⏱️ Configurable delay for automatic mode (0-5000ms)
  • 🎯 Real-time mode switching via dropdown menu
  • ▶️ Continue button for confirm mode

v1.2.0

  • 📝 Enhanced Markdown: Optional rich markdown with tables and code blocks
  • 🎨 Syntax highlighting support via highlight.js
  • 🔧 Automatic detection of markdown addon

v1.1.0

  • 🔌 Configurable API Paths: Customize backend endpoints
  • 🛠️ Support for different backend URL structures

v1.0.0

  • 🎉 Initial release
  • 💬 Real-time SSE streaming
  • 🎨 Theming and customization
  • 🤖 Demo flows
  • 🔒 Session management

License

MIT © 2025