@makemore/agent-frontend 1.4.0 → 1.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
@@ -165,6 +165,12 @@ See `django-tts-example.py` for the complete Django backend implementation.
165
165
  | `ttsVoices` | object | `{ assistant: null, user: null }` | Voice IDs (only if not using proxy) |
166
166
  | `ttsModel` | string | `'eleven_turbo_v2_5'` | ElevenLabs model (only if not using proxy) |
167
167
  | `ttsSettings` | object | See below | ElevenLabs voice settings (only if not using proxy) |
168
+ | `availableVoices` | array | `[]` | List of available voices (auto-populated from ElevenLabs API) |
169
+ | `showClearButton` | boolean | `true` | Show clear conversation button in header |
170
+ | `showDebugButton` | boolean | `true` | Show debug mode toggle button in header |
171
+ | `showTTSButton` | boolean | `true` | Show TTS toggle button in header |
172
+ | `showVoiceSettings` | boolean | `true` | Show voice settings button in header (direct API only) |
173
+ | `showExpandButton` | boolean | `true` | Show expand/minimize button in header |
168
174
 
169
175
  ### Text-to-Speech (ElevenLabs)
170
176
 
@@ -237,6 +243,26 @@ ChatWidget.init({
237
243
  ```javascript
238
244
  ChatWidget.toggleTTS(); // Toggle on/off
239
245
  ChatWidget.stopSpeech(); // Stop current speech and clear queue
246
+ ChatWidget.setVoice('assistant', 'voice_id'); // Change assistant voice
247
+ ChatWidget.setVoice('user', 'voice_id'); // Change user voice
248
+ ```
249
+
250
+ **Voice Settings UI:**
251
+
252
+ When using direct API mode (not proxy), a voice settings button (🎙️) appears in the header. Click it to:
253
+ - Select assistant voice from dropdown
254
+ - Select customer voice for demo mode
255
+ - Voices are automatically fetched from your ElevenLabs account
256
+
257
+ **Customize Header Buttons:**
258
+ ```javascript
259
+ ChatWidget.init({
260
+ showClearButton: true, // Show/hide clear button
261
+ showDebugButton: true, // Show/hide debug button
262
+ showTTSButton: true, // Show/hide TTS toggle
263
+ showVoiceSettings: true, // Show/hide voice settings (direct API only)
264
+ showExpandButton: true, // Show/hide expand button
265
+ });
240
266
  ```
241
267
 
242
268
  ### Demo Flow Control
@@ -335,6 +361,8 @@ ChatWidget.clearMessages();
335
361
  // Text-to-speech controls
336
362
  ChatWidget.toggleTTS(); // Toggle TTS on/off
337
363
  ChatWidget.stopSpeech(); // Stop current speech and clear queue
364
+ ChatWidget.setVoice('assistant', 'voice_id'); // Change assistant voice
365
+ ChatWidget.setVoice('user', 'voice_id'); // Change user voice
338
366
 
339
367
  // Start a demo flow
340
368
  ChatWidget.startDemoFlow('quote');
@@ -172,6 +172,95 @@
172
172
  }
173
173
  }
174
174
 
175
+ /* Voice Settings */
176
+ .cw-voice-settings {
177
+ background: var(--cw-bg-muted);
178
+ border-bottom: 1px solid var(--cw-border);
179
+ animation: slideDown 0.2s ease-out;
180
+ }
181
+
182
+ @keyframes slideDown {
183
+ from {
184
+ max-height: 0;
185
+ opacity: 0;
186
+ }
187
+ to {
188
+ max-height: 200px;
189
+ opacity: 1;
190
+ }
191
+ }
192
+
193
+ .cw-voice-settings-header {
194
+ display: flex;
195
+ justify-content: space-between;
196
+ align-items: center;
197
+ padding: 8px 16px;
198
+ font-size: 13px;
199
+ font-weight: 600;
200
+ color: var(--cw-text);
201
+ border-bottom: 1px solid var(--cw-border);
202
+ }
203
+
204
+ .cw-voice-settings-close {
205
+ all: initial;
206
+ font-family: inherit;
207
+ background: none;
208
+ border: none;
209
+ color: var(--cw-text-muted);
210
+ cursor: pointer;
211
+ font-size: 16px;
212
+ padding: 4px;
213
+ line-height: 1;
214
+ transition: color 0.15s;
215
+ }
216
+
217
+ .cw-voice-settings-close:hover {
218
+ color: var(--cw-text);
219
+ }
220
+
221
+ .cw-voice-settings-content {
222
+ padding: 12px 16px;
223
+ display: flex;
224
+ flex-direction: column;
225
+ gap: 12px;
226
+ }
227
+
228
+ .cw-voice-setting {
229
+ display: flex;
230
+ flex-direction: column;
231
+ gap: 4px;
232
+ }
233
+
234
+ .cw-voice-setting label {
235
+ font-size: 12px;
236
+ font-weight: 500;
237
+ color: var(--cw-text-muted);
238
+ }
239
+
240
+ .cw-voice-select {
241
+ all: initial;
242
+ font-family: inherit;
243
+ width: 100%;
244
+ padding: 8px 12px;
245
+ border: 1px solid var(--cw-border);
246
+ border-radius: 6px;
247
+ background: var(--cw-bg);
248
+ color: var(--cw-text);
249
+ font-size: 13px;
250
+ cursor: pointer;
251
+ transition: border-color 0.15s;
252
+ }
253
+
254
+ .cw-voice-select:hover {
255
+ border-color: var(--cw-primary);
256
+ }
257
+
258
+ .cw-voice-select:focus {
259
+ outline: none;
260
+ border-color: var(--cw-primary);
261
+ box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);
262
+ }
263
+
175
264
  /* Status bar */
176
265
  .cw-status-bar {
177
266
  display: flex;
@@ -61,6 +61,13 @@
61
61
  style: 0.0,
62
62
  use_speaker_boost: true,
63
63
  },
64
+ availableVoices: [], // List of available voices for UI dropdown
65
+ // UI visibility controls
66
+ showClearButton: true,
67
+ showDebugButton: true,
68
+ showTTSButton: true,
69
+ showVoiceSettings: true,
70
+ showExpandButton: true,
64
71
  };
65
72
 
66
73
  // State
@@ -82,6 +89,7 @@
82
89
  currentAudio: null,
83
90
  isSpeaking: false,
84
91
  speechQueue: [],
92
+ voiceSettingsOpen: false,
85
93
  };
86
94
 
87
95
  // DOM elements
@@ -285,6 +293,35 @@
285
293
  render();
286
294
  }
287
295
 
296
+ function toggleVoiceSettings() {
297
+ state.voiceSettingsOpen = !state.voiceSettingsOpen;
298
+ render();
299
+ }
300
+
301
+ function setVoice(role, voiceId) {
302
+ config.ttsVoices[role] = voiceId;
303
+ render();
304
+ }
305
+
306
+ async function fetchAvailableVoices() {
307
+ if (!config.elevenLabsApiKey) return;
308
+
309
+ try {
310
+ const response = await fetch('https://api.elevenlabs.io/v1/voices', {
311
+ headers: {
312
+ 'xi-api-key': config.elevenLabsApiKey,
313
+ },
314
+ });
315
+
316
+ if (response.ok) {
317
+ const data = await response.json();
318
+ config.availableVoices = data.voices || [];
319
+ }
320
+ } catch (err) {
321
+ console.error('[ChatWidget] Failed to fetch voices:', err);
322
+ }
323
+ }
324
+
288
325
  // ============================================================================
289
326
  // Session Management
290
327
  // ============================================================================
@@ -694,6 +731,44 @@
694
731
  `;
695
732
  }
696
733
 
734
+ function renderVoiceSettings() {
735
+ if (!state.voiceSettingsOpen) return '';
736
+
737
+ const voiceOptions = (role) => {
738
+ if (config.availableVoices.length === 0) {
739
+ return '<option value="">Loading voices...</option>';
740
+ }
741
+ return config.availableVoices.map(voice => `
742
+ <option value="${voice.voice_id}" ${config.ttsVoices[role] === voice.voice_id ? 'selected' : ''}>
743
+ ${escapeHtml(voice.name)}
744
+ </option>
745
+ `).join('');
746
+ };
747
+
748
+ return `
749
+ <div class="cw-voice-settings">
750
+ <div class="cw-voice-settings-header">
751
+ <span>🎙️ Voice Settings</span>
752
+ <button class="cw-voice-settings-close" data-action="toggle-voice-settings">✕</button>
753
+ </div>
754
+ <div class="cw-voice-settings-content">
755
+ <div class="cw-voice-setting">
756
+ <label>Assistant Voice</label>
757
+ <select class="cw-voice-select" data-role="assistant" onchange="ChatWidget.setVoice('assistant', this.value)">
758
+ ${voiceOptions('assistant')}
759
+ </select>
760
+ </div>
761
+ <div class="cw-voice-setting">
762
+ <label>Customer Voice (Demo)</label>
763
+ <select class="cw-voice-select" data-role="user" onchange="ChatWidget.setVoice('user', this.value)">
764
+ ${voiceOptions('user')}
765
+ </select>
766
+ </div>
767
+ </div>
768
+ </div>
769
+ `;
770
+ }
771
+
697
772
  function renderJourneyDropdown() {
698
773
  if (!config.enableAutoRun || Object.keys(config.journeyTypes).length === 0) {
699
774
  return '';
@@ -824,13 +899,15 @@
824
899
  <div class="cw-header" style="background-color: ${config.primaryColor}">
825
900
  <span class="cw-title">${escapeHtml(config.title)}</span>
826
901
  <div class="cw-header-actions">
827
- <button class="cw-header-btn" data-action="clear" title="Clear Conversation" ${state.isLoading || state.messages.length === 0 ? 'disabled' : ''}>
828
- <svg class="cw-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
829
- <polyline points="3 6 5 6 21 6"></polyline>
830
- <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
831
- </svg>
832
- </button>
833
- ${config.enableDebugMode ? `
902
+ ${config.showClearButton ? `
903
+ <button class="cw-header-btn" data-action="clear" title="Clear Conversation" ${state.isLoading || state.messages.length === 0 ? 'disabled' : ''}>
904
+ <svg class="cw-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
905
+ <polyline points="3 6 5 6 21 6"></polyline>
906
+ <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
907
+ </svg>
908
+ </button>
909
+ ` : ''}
910
+ ${config.showDebugButton && config.enableDebugMode ? `
834
911
  <button class="cw-header-btn ${state.debugMode ? 'cw-btn-active' : ''}" data-action="toggle-debug" title="${state.debugMode ? 'Hide Debug Info' : 'Show Debug Info'}">
835
912
  <svg class="cw-icon-sm ${state.debugMode ? 'cw-icon-warning' : ''}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
836
913
  <path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 8 0 0 1-8 8z"></path>
@@ -838,22 +915,30 @@
838
915
  </svg>
839
916
  </button>
840
917
  ` : ''}
841
- ${config.elevenLabsApiKey ? `
918
+ ${config.showTTSButton && (config.elevenLabsApiKey || config.ttsProxyUrl) ? `
842
919
  <button class="cw-header-btn ${config.enableTTS ? 'cw-btn-active' : ''} ${state.isSpeaking ? 'cw-btn-speaking' : ''}"
843
920
  data-action="toggle-tts"
844
921
  title="${config.enableTTS ? (state.isSpeaking ? 'Speaking...' : 'TTS Enabled') : 'TTS Disabled'}">
845
922
  ${state.isSpeaking ? '🔊' : (config.enableTTS ? '🔉' : '🔇')}
846
923
  </button>
847
924
  ` : ''}
925
+ ${config.showVoiceSettings && config.elevenLabsApiKey && !config.ttsProxyUrl ? `
926
+ <button class="cw-header-btn ${state.voiceSettingsOpen ? 'cw-btn-active' : ''}" data-action="toggle-voice-settings" title="Voice Settings">
927
+ 🎙️
928
+ </button>
929
+ ` : ''}
848
930
  ${renderJourneyDropdown()}
849
- <button class="cw-header-btn" data-action="toggle-expand" title="${state.isExpanded ? 'Minimize' : 'Expand'}">
850
- ${state.isExpanded ? '' : ''}
851
- </button>
931
+ ${config.showExpandButton ? `
932
+ <button class="cw-header-btn" data-action="toggle-expand" title="${state.isExpanded ? 'Minimize' : 'Expand'}">
933
+ ${state.isExpanded ? '⊖' : '⊕'}
934
+ </button>
935
+ ` : ''}
852
936
  <button class="cw-header-btn" data-action="close" title="Close">
853
937
 
854
938
  </button>
855
939
  </div>
856
940
  </div>
941
+ ${renderVoiceSettings()}
857
942
  ${statusBar}
858
943
  <div class="cw-messages" id="cw-messages">
859
944
  ${messagesHtml}
@@ -893,6 +978,7 @@
893
978
  case 'toggle-expand': toggleExpand(); break;
894
979
  case 'toggle-debug': toggleDebugMode(); break;
895
980
  case 'toggle-tts': toggleTTS(); break;
981
+ case 'toggle-voice-settings': toggleVoiceSettings(); break;
896
982
  case 'clear': clearMessages(); break;
897
983
  case 'stop-autorun': stopAutoRun(); break;
898
984
  case 'continue-autorun': continueAutoRun(); break;
@@ -969,6 +1055,11 @@
969
1055
  // Initial render
970
1056
  render();
971
1057
 
1058
+ // Fetch available voices if using direct API
1059
+ if (config.elevenLabsApiKey && !config.ttsProxyUrl) {
1060
+ fetchAvailableVoices();
1061
+ }
1062
+
972
1063
  console.log('[ChatWidget] Initialized with config:', config);
973
1064
  }
974
1065
 
@@ -1012,6 +1103,7 @@
1012
1103
  setAutoRunDelay,
1013
1104
  toggleTTS,
1014
1105
  stopSpeech,
1106
+ setVoice,
1015
1107
  getState: () => ({ ...state }),
1016
1108
  getConfig: () => ({ ...config }),
1017
1109
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makemore/agent-frontend",
3
- "version": "1.4.0",
3
+ "version": "1.5.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": [