@makemore/agent-frontend 1.3.0 → 1.4.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
@@ -23,14 +23,17 @@ Most chat widgets are tightly coupled to specific frameworks or require complex
23
23
  | Feature | Description |
24
24
  |---------|-------------|
25
25
  | 💬 **Real-time Streaming** | SSE-based message streaming for instant, token-by-token responses |
26
+ | 🔊 **Text-to-Speech** | ElevenLabs integration with secure Django proxy support |
26
27
  | 🎨 **Theming** | Customize colors, titles, messages, and position |
27
28
  | 🌙 **Dark Mode** | Automatic dark mode based on system preferences |
28
29
  | 📱 **Responsive** | Works seamlessly on desktop and mobile |
29
30
  | 🔧 **Debug Mode** | Toggle visibility of tool calls and results |
30
- | 🤖 **Demo Flows** | Built-in auto-run mode for showcasing agent journeys |
31
+ | 🤖 **Demo Flows** | Built-in auto-run mode with automatic, confirm, and manual modes |
31
32
  | 🔒 **Sessions** | Automatic anonymous session creation and management |
32
33
  | 💾 **Persistence** | Conversations persist across page reloads via localStorage |
33
34
  | 🛡️ **Isolated CSS** | Scoped styles that won't leak into or from your page |
35
+ | 🎯 **Configurable APIs** | Customize backend endpoints to match your server structure |
36
+ | 📝 **Enhanced Markdown** | Optional rich markdown with tables, code blocks, and syntax highlighting |
34
37
 
35
38
  ## Installation
36
39
 
@@ -85,7 +88,7 @@ The widget automatically detects and uses the enhanced markdown parser if availa
85
88
 
86
89
  ## Quick Start
87
90
 
88
- ### Initialize the widget
91
+ ### Basic Setup
89
92
 
90
93
  ```html
91
94
  <script>
@@ -98,6 +101,23 @@ The widget automatically detects and uses the enhanced markdown parser if availa
98
101
  </script>
99
102
  ```
100
103
 
104
+ ### With Text-to-Speech (Recommended: Django Proxy)
105
+
106
+ ```html
107
+ <script>
108
+ ChatWidget.init({
109
+ backendUrl: 'https://your-api.com',
110
+ agentKey: 'your-agent',
111
+ title: 'Voice-Enabled Chat',
112
+ primaryColor: '#0066cc',
113
+ enableTTS: true,
114
+ ttsProxyUrl: 'https://your-api.com/api/tts/speak/',
115
+ });
116
+ </script>
117
+ ```
118
+
119
+ See `django-tts-example.py` for the complete Django backend implementation.
120
+
101
121
  ### With custom API paths
102
122
 
103
123
  ```html
@@ -139,6 +159,85 @@ The widget automatically detects and uses the enhanced markdown parser if availa
139
159
  | `apiPaths` | object | See below | API endpoint paths (customizable for different backends) |
140
160
  | `autoRunMode` | string | `'automatic'` | Demo flow mode: `'automatic'`, `'confirm'`, or `'manual'` |
141
161
  | `autoRunDelay` | number | `1000` | Delay in milliseconds before auto-generating next message (automatic mode) |
162
+ | `enableTTS` | boolean | `false` | Enable text-to-speech for messages |
163
+ | `ttsProxyUrl` | string | `null` | Django proxy URL for TTS (recommended for security) |
164
+ | `elevenLabsApiKey` | string | `null` | ElevenLabs API key (only if not using proxy) |
165
+ | `ttsVoices` | object | `{ assistant: null, user: null }` | Voice IDs (only if not using proxy) |
166
+ | `ttsModel` | string | `'eleven_turbo_v2_5'` | ElevenLabs model (only if not using proxy) |
167
+ | `ttsSettings` | object | See below | ElevenLabs voice settings (only if not using proxy) |
168
+
169
+ ### Text-to-Speech (ElevenLabs)
170
+
171
+ Add realistic voice narration to your chat widget using ElevenLabs. Two integration options:
172
+
173
+ #### Option 1: Secure Django Proxy (Recommended)
174
+
175
+ Keep your API key secure on the server:
176
+
177
+ ```javascript
178
+ ChatWidget.init({
179
+ enableTTS: true,
180
+ ttsProxyUrl: 'https://your-backend.com/api/tts/speak/',
181
+ // No API key or voice IDs needed - configured on server
182
+ });
183
+ ```
184
+
185
+ **Django Setup:**
186
+
187
+ See `django-tts-example.py` for a complete Django REST Framework implementation. Quick setup:
188
+
189
+ 1. Install: `pip install requests`
190
+ 2. Add to `settings.py`:
191
+ ```python
192
+ ELEVENLABS_API_KEY = 'your_api_key_here'
193
+ ELEVENLABS_VOICES = {
194
+ 'assistant': 'EXAVITQu4vr4xnSDxMaL', # Bella
195
+ 'user': 'pNInz6obpgDQGcFmaJgB', # Adam
196
+ }
197
+ ```
198
+ 3. Add view from `django-tts-example.py` to your Django app
199
+ 4. Add URL route: `path('api/tts/speak/', views.text_to_speech)`
200
+
201
+ #### Option 2: Direct API (Client-Side)
202
+
203
+ For testing or simple deployments:
204
+
205
+ ```javascript
206
+ ChatWidget.init({
207
+ enableTTS: true,
208
+ elevenLabsApiKey: 'your_elevenlabs_api_key', // ⚠️ Exposed to client
209
+ ttsVoices: {
210
+ assistant: 'EXAVITQu4vr4xnSDxMaL', // Bella
211
+ user: 'pNInz6obpgDQGcFmaJgB', // Adam
212
+ },
213
+ ttsModel: 'eleven_turbo_v2_5',
214
+ ttsSettings: {
215
+ stability: 0.5,
216
+ similarity_boost: 0.75,
217
+ style: 0.0,
218
+ use_speaker_boost: true,
219
+ },
220
+ });
221
+ ```
222
+
223
+ **Features:**
224
+ - Speaks assistant responses automatically
225
+ - Speaks simulated user messages in demo mode
226
+ - Queues messages to prevent overlap
227
+ - Waits for speech to finish before continuing demo (automatic mode)
228
+ - Toggle TTS on/off with button in header
229
+ - Visual indicator when speaking (pulsing icon)
230
+
231
+ **Get Voice IDs:**
232
+ 1. Go to https://elevenlabs.io/app/voice-library
233
+ 2. Choose voices and copy their IDs
234
+ 3. Or use the API: https://api.elevenlabs.io/v1/voices
235
+
236
+ **Control TTS:**
237
+ ```javascript
238
+ ChatWidget.toggleTTS(); // Toggle on/off
239
+ ChatWidget.stopSpeech(); // Stop current speech and clear queue
240
+ ```
142
241
 
143
242
  ### Demo Flow Control
144
243
 
@@ -233,6 +332,10 @@ ChatWidget.send('Hello, I need help!');
233
332
  // Clear the conversation
234
333
  ChatWidget.clearMessages();
235
334
 
335
+ // Text-to-speech controls
336
+ ChatWidget.toggleTTS(); // Toggle TTS on/off
337
+ ChatWidget.stopSpeech(); // Stop current speech and clear queue
338
+
236
339
  // Start a demo flow
237
340
  ChatWidget.startDemoFlow('quote');
238
341
 
@@ -377,6 +480,36 @@ agent-frontend/
377
480
 
378
481
  Requires: `EventSource` (SSE), `fetch`, `localStorage`
379
482
 
483
+ ## Version History
484
+
485
+ ### v1.4.0 (Latest)
486
+ - ✨ **Text-to-Speech**: ElevenLabs integration with secure Django proxy support
487
+ - 🔊 Automatic speech for assistant and simulated user messages
488
+ - 🎛️ Smart speech queuing to prevent overlap
489
+ - 🔐 Secure proxy approach keeps API keys on server
490
+
491
+ ### v1.3.0
492
+ - 🎮 **Demo Flow Control**: Three modes (automatic, confirm-next, manual)
493
+ - ⏱️ Configurable delay for automatic mode (0-5000ms)
494
+ - 🎯 Real-time mode switching via dropdown menu
495
+ - ▶️ Continue button for confirm mode
496
+
497
+ ### v1.2.0
498
+ - 📝 **Enhanced Markdown**: Optional rich markdown with tables and code blocks
499
+ - 🎨 Syntax highlighting support via highlight.js
500
+ - 🔧 Automatic detection of markdown addon
501
+
502
+ ### v1.1.0
503
+ - 🔌 **Configurable API Paths**: Customize backend endpoints
504
+ - 🛠️ Support for different backend URL structures
505
+
506
+ ### v1.0.0
507
+ - 🎉 Initial release
508
+ - 💬 Real-time SSE streaming
509
+ - 🎨 Theming and customization
510
+ - 🤖 Demo flows
511
+ - 🔒 Session management
512
+
380
513
  ## License
381
514
 
382
515
  MIT © 2024
@@ -159,6 +159,19 @@
159
159
  color: #ffd700;
160
160
  }
161
161
 
162
+ .cw-btn-speaking {
163
+ animation: pulse-speaking 1.5s ease-in-out infinite;
164
+ }
165
+
166
+ @keyframes pulse-speaking {
167
+ 0%, 100% {
168
+ background: rgba(255, 255, 255, 0.3);
169
+ }
170
+ 50% {
171
+ background: rgba(255, 255, 255, 0.5);
172
+ }
173
+ }
174
+
162
175
  /* Status bar */
163
176
  .cw-status-bar {
164
177
  display: flex;
@@ -46,6 +46,21 @@
46
46
  // Demo flow control
47
47
  autoRunDelay: 1000, // Delay in ms before auto-generating next message
48
48
  autoRunMode: 'automatic', // 'automatic', 'confirm', or 'manual'
49
+ // Text-to-speech (ElevenLabs)
50
+ enableTTS: false,
51
+ ttsProxyUrl: null, // If set, uses Django proxy instead of direct API calls
52
+ elevenLabsApiKey: null, // Only needed if not using proxy
53
+ ttsVoices: {
54
+ assistant: null, // ElevenLabs voice ID for assistant (not needed if using proxy)
55
+ user: null, // ElevenLabs voice ID for simulated user (not needed if using proxy)
56
+ },
57
+ ttsModel: 'eleven_turbo_v2_5', // ElevenLabs model (not needed if using proxy)
58
+ ttsSettings: {
59
+ stability: 0.5,
60
+ similarity_boost: 0.75,
61
+ style: 0.0,
62
+ use_speaker_boost: true,
63
+ },
49
64
  };
50
65
 
51
66
  // State
@@ -64,6 +79,9 @@
64
79
  sessionToken: null,
65
80
  error: null,
66
81
  eventSource: null,
82
+ currentAudio: null,
83
+ isSpeaking: false,
84
+ speechQueue: [],
67
85
  };
68
86
 
69
87
  // DOM elements
@@ -138,6 +156,135 @@
138
156
  }
139
157
  }
140
158
 
159
+ // ============================================================================
160
+ // Text-to-Speech (ElevenLabs)
161
+ // ============================================================================
162
+
163
+ async function speakText(text, role) {
164
+ if (!config.enableTTS) return;
165
+
166
+ // Check if we have either proxy or direct API access
167
+ if (!config.ttsProxyUrl && !config.elevenLabsApiKey) return;
168
+
169
+ // If using direct API, check for voice ID
170
+ if (!config.ttsProxyUrl) {
171
+ const voiceId = role === 'assistant' ? config.ttsVoices.assistant : config.ttsVoices.user;
172
+ if (!voiceId) return;
173
+ }
174
+
175
+ // Add to queue
176
+ state.speechQueue.push({ text, role });
177
+
178
+ // Process queue if not already speaking
179
+ if (!state.isSpeaking) {
180
+ processSpeechQueue();
181
+ }
182
+ }
183
+
184
+ async function processSpeechQueue() {
185
+ if (state.speechQueue.length === 0) {
186
+ state.isSpeaking = false;
187
+ render();
188
+
189
+ // If auto-run is waiting for speech to finish, continue
190
+ if (state.autoRunActive && state.autoRunPaused && config.autoRunMode === 'automatic') {
191
+ setTimeout(() => {
192
+ if (state.autoRunActive && !state.isSpeaking) {
193
+ continueAutoRun();
194
+ }
195
+ }, config.autoRunDelay);
196
+ }
197
+ return;
198
+ }
199
+
200
+ state.isSpeaking = true;
201
+ render();
202
+
203
+ const { text, role } = state.speechQueue.shift();
204
+
205
+ try {
206
+ let response;
207
+
208
+ if (config.ttsProxyUrl) {
209
+ // Use Django proxy
210
+ response = await fetch(config.ttsProxyUrl, {
211
+ method: 'POST',
212
+ headers: {
213
+ 'Content-Type': 'application/json',
214
+ ...(state.sessionToken ? { [config.anonymousTokenHeader]: state.sessionToken } : {}),
215
+ },
216
+ body: JSON.stringify({
217
+ text: text,
218
+ role: role,
219
+ }),
220
+ });
221
+ } else {
222
+ // Direct ElevenLabs API call
223
+ const voiceId = role === 'assistant' ? config.ttsVoices.assistant : config.ttsVoices.user;
224
+ response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
225
+ method: 'POST',
226
+ headers: {
227
+ 'Accept': 'audio/mpeg',
228
+ 'Content-Type': 'application/json',
229
+ 'xi-api-key': config.elevenLabsApiKey,
230
+ },
231
+ body: JSON.stringify({
232
+ text: text,
233
+ model_id: config.ttsModel,
234
+ voice_settings: config.ttsSettings,
235
+ }),
236
+ });
237
+ }
238
+
239
+ if (!response.ok) {
240
+ throw new Error(`TTS API error: ${response.status}`);
241
+ }
242
+
243
+ const audioBlob = await response.blob();
244
+ const audioUrl = URL.createObjectURL(audioBlob);
245
+ const audio = new Audio(audioUrl);
246
+
247
+ state.currentAudio = audio;
248
+
249
+ audio.onended = () => {
250
+ URL.revokeObjectURL(audioUrl);
251
+ state.currentAudio = null;
252
+ processSpeechQueue();
253
+ };
254
+
255
+ audio.onerror = () => {
256
+ console.error('[ChatWidget] Audio playback error');
257
+ URL.revokeObjectURL(audioUrl);
258
+ state.currentAudio = null;
259
+ processSpeechQueue();
260
+ };
261
+
262
+ await audio.play();
263
+ } catch (err) {
264
+ console.error('[ChatWidget] TTS error:', err);
265
+ state.currentAudio = null;
266
+ processSpeechQueue();
267
+ }
268
+ }
269
+
270
+ function stopSpeech() {
271
+ if (state.currentAudio) {
272
+ state.currentAudio.pause();
273
+ state.currentAudio = null;
274
+ }
275
+ state.speechQueue = [];
276
+ state.isSpeaking = false;
277
+ render();
278
+ }
279
+
280
+ function toggleTTS() {
281
+ config.enableTTS = !config.enableTTS;
282
+ if (!config.enableTTS) {
283
+ stopSpeech();
284
+ }
285
+ render();
286
+ }
287
+
141
288
  // ============================================================================
142
289
  // Session Management
143
290
  // ============================================================================
@@ -347,10 +494,21 @@
347
494
  state.eventSource = null;
348
495
  render();
349
496
 
497
+ // Speak assistant message if TTS enabled
498
+ if (assistantContent && !state.error) {
499
+ speakText(assistantContent, 'assistant');
500
+ }
501
+
350
502
  // Trigger auto-run if enabled
351
503
  if (state.autoRunActive && !state.error) {
352
504
  if (config.autoRunMode === 'automatic') {
353
- setTimeout(() => triggerAutoRun(), config.autoRunDelay);
505
+ // Wait for speech to finish before continuing
506
+ if (config.enableTTS && assistantContent) {
507
+ state.autoRunPaused = true;
508
+ // processSpeechQueue will continue when done
509
+ } else {
510
+ setTimeout(() => triggerAutoRun(), config.autoRunDelay);
511
+ }
354
512
  } else if (config.autoRunMode === 'confirm') {
355
513
  state.autoRunPaused = true;
356
514
  render();
@@ -402,6 +560,12 @@
402
560
  const data = await response.json();
403
561
  if (data.response) {
404
562
  state.isSimulating = false;
563
+
564
+ // Speak simulated user message if TTS enabled
565
+ if (config.enableTTS && config.ttsVoices.user) {
566
+ await speakText(data.response, 'user');
567
+ }
568
+
405
569
  await sendMessage(data.response);
406
570
  return;
407
571
  }
@@ -674,6 +838,13 @@
674
838
  </svg>
675
839
  </button>
676
840
  ` : ''}
841
+ ${config.elevenLabsApiKey ? `
842
+ <button class="cw-header-btn ${config.enableTTS ? 'cw-btn-active' : ''} ${state.isSpeaking ? 'cw-btn-speaking' : ''}"
843
+ data-action="toggle-tts"
844
+ title="${config.enableTTS ? (state.isSpeaking ? 'Speaking...' : 'TTS Enabled') : 'TTS Disabled'}">
845
+ ${state.isSpeaking ? '🔊' : (config.enableTTS ? '🔉' : '🔇')}
846
+ </button>
847
+ ` : ''}
677
848
  ${renderJourneyDropdown()}
678
849
  <button class="cw-header-btn" data-action="toggle-expand" title="${state.isExpanded ? 'Minimize' : 'Expand'}">
679
850
  ${state.isExpanded ? '⊖' : '⊕'}
@@ -721,6 +892,7 @@
721
892
  case 'close': closeWidget(); break;
722
893
  case 'toggle-expand': toggleExpand(); break;
723
894
  case 'toggle-debug': toggleDebugMode(); break;
895
+ case 'toggle-tts': toggleTTS(); break;
724
896
  case 'clear': clearMessages(); break;
725
897
  case 'stop-autorun': stopAutoRun(); break;
726
898
  case 'continue-autorun': continueAutoRun(); break;
@@ -838,6 +1010,8 @@
838
1010
  continueAutoRun,
839
1011
  setAutoRunMode,
840
1012
  setAutoRunDelay,
1013
+ toggleTTS,
1014
+ stopSpeech,
841
1015
  getState: () => ({ ...state }),
842
1016
  getConfig: () => ({ ...config }),
843
1017
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makemore/agent-frontend",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "A standalone, zero-dependency chat widget for AI agents. Embed conversational AI into any website with a single script tag.",
5
5
  "main": "dist/chat-widget.js",
6
6
  "files": [