@makemore/agent-frontend 1.1.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 +218 -3
- package/dist/chat-widget-markdown.js +110 -0
- package/dist/chat-widget.css +199 -0
- package/dist/chat-widget.js +278 -22
- package/package.json +2 -1
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
|
|
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
|
|
|
@@ -61,9 +64,31 @@ Then include in your HTML:
|
|
|
61
64
|
<script src="https://cdn.jsdelivr.net/npm/@makemore/agent-frontend/dist/chat-widget.js"></script>
|
|
62
65
|
```
|
|
63
66
|
|
|
67
|
+
### Optional: Enhanced Markdown Support
|
|
68
|
+
|
|
69
|
+
For full-featured markdown rendering (tables, code blocks with syntax highlighting, etc.), include the optional markdown addon:
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<!-- Core widget -->
|
|
73
|
+
<link rel="stylesheet" href="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.css">
|
|
74
|
+
<script src="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.js"></script>
|
|
75
|
+
|
|
76
|
+
<!-- Optional: Enhanced markdown with marked.js -->
|
|
77
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
78
|
+
<script src="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget-markdown.js"></script>
|
|
79
|
+
|
|
80
|
+
<!-- Optional: Syntax highlighting for code blocks -->
|
|
81
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11/styles/github-dark.min.css">
|
|
82
|
+
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/core.min.js"></script>
|
|
83
|
+
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/languages/javascript.min.js"></script>
|
|
84
|
+
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/languages/python.min.js"></script>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The widget automatically detects and uses the enhanced markdown parser if available. Without it, a basic markdown parser is used.
|
|
88
|
+
|
|
64
89
|
## Quick Start
|
|
65
90
|
|
|
66
|
-
###
|
|
91
|
+
### Basic Setup
|
|
67
92
|
|
|
68
93
|
```html
|
|
69
94
|
<script>
|
|
@@ -76,6 +101,23 @@ Then include in your HTML:
|
|
|
76
101
|
</script>
|
|
77
102
|
```
|
|
78
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
|
+
|
|
79
121
|
### With custom API paths
|
|
80
122
|
|
|
81
123
|
```html
|
|
@@ -115,6 +157,108 @@ Then include in your HTML:
|
|
|
115
157
|
| `conversationIdKey` | string | `'chat_widget_conversation_id'` | localStorage key for conversation ID |
|
|
116
158
|
| `sessionTokenKey` | string | `'chat_widget_session_token'` | localStorage key for session token |
|
|
117
159
|
| `apiPaths` | object | See below | API endpoint paths (customizable for different backends) |
|
|
160
|
+
| `autoRunMode` | string | `'automatic'` | Demo flow mode: `'automatic'`, `'confirm'`, or `'manual'` |
|
|
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
|
+
```
|
|
241
|
+
|
|
242
|
+
### Demo Flow Control
|
|
243
|
+
|
|
244
|
+
The widget supports three modes for demo flows:
|
|
245
|
+
|
|
246
|
+
- **Automatic** (`autoRunMode: 'automatic'`): Continuously generates customer responses with a configurable delay
|
|
247
|
+
- **Confirm Next** (`autoRunMode: 'confirm'`): Pauses after each assistant response and waits for user to click "Continue"
|
|
248
|
+
- **Manual** (`autoRunMode: 'manual'`): Stops auto-generation; user must manually type responses
|
|
249
|
+
|
|
250
|
+
These settings can be changed in real-time via the demo controls dropdown (visible when a demo is running).
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
ChatWidget.init({
|
|
254
|
+
autoRunMode: 'confirm', // Start in confirm mode
|
|
255
|
+
autoRunDelay: 2000, // 2 second delay in automatic mode
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Change mode programmatically
|
|
259
|
+
ChatWidget.setAutoRunMode('automatic');
|
|
260
|
+
ChatWidget.setAutoRunDelay(500); // 0.5 second delay
|
|
261
|
+
```
|
|
118
262
|
|
|
119
263
|
### API Paths Configuration
|
|
120
264
|
|
|
@@ -188,12 +332,25 @@ ChatWidget.send('Hello, I need help!');
|
|
|
188
332
|
// Clear the conversation
|
|
189
333
|
ChatWidget.clearMessages();
|
|
190
334
|
|
|
335
|
+
// Text-to-speech controls
|
|
336
|
+
ChatWidget.toggleTTS(); // Toggle TTS on/off
|
|
337
|
+
ChatWidget.stopSpeech(); // Stop current speech and clear queue
|
|
338
|
+
|
|
191
339
|
// Start a demo flow
|
|
192
340
|
ChatWidget.startDemoFlow('quote');
|
|
193
341
|
|
|
194
|
-
// Stop
|
|
342
|
+
// Stop demo flow
|
|
195
343
|
ChatWidget.stopAutoRun();
|
|
196
344
|
|
|
345
|
+
// Continue demo flow (when in confirm mode and paused)
|
|
346
|
+
ChatWidget.continueAutoRun();
|
|
347
|
+
|
|
348
|
+
// Change demo flow mode
|
|
349
|
+
ChatWidget.setAutoRunMode('automatic'); // 'automatic', 'confirm', or 'manual'
|
|
350
|
+
|
|
351
|
+
// Change auto-run delay (in milliseconds)
|
|
352
|
+
ChatWidget.setAutoRunDelay(2000);
|
|
353
|
+
|
|
197
354
|
// Remove the widget from the page
|
|
198
355
|
ChatWidget.destroy();
|
|
199
356
|
|
|
@@ -204,6 +361,34 @@ const state = ChatWidget.getState();
|
|
|
204
361
|
const config = ChatWidget.getConfig();
|
|
205
362
|
```
|
|
206
363
|
|
|
364
|
+
## Markdown Support
|
|
365
|
+
|
|
366
|
+
The widget includes built-in markdown rendering for assistant messages:
|
|
367
|
+
|
|
368
|
+
### Basic Markdown (Built-in)
|
|
369
|
+
|
|
370
|
+
The widget includes a lightweight markdown parser that supports:
|
|
371
|
+
- **Bold** (`**text**` or `__text__`)
|
|
372
|
+
- *Italic* (`*text*` or `_text_`)
|
|
373
|
+
- `Inline code` (`` `code` ``)
|
|
374
|
+
- [Links](url) (`[text](url)`)
|
|
375
|
+
- Lists (`- item` or `* item`)
|
|
376
|
+
- Line breaks
|
|
377
|
+
|
|
378
|
+
### Enhanced Markdown (Optional)
|
|
379
|
+
|
|
380
|
+
Include `chat-widget-markdown.js` for full-featured markdown:
|
|
381
|
+
- **Tables** - Full GFM table support
|
|
382
|
+
- **Code blocks** - Multi-line code with syntax highlighting
|
|
383
|
+
- **Blockquotes** - `> quoted text`
|
|
384
|
+
- **Headings** - `# H1` through `###### H6`
|
|
385
|
+
- **Horizontal rules** - `---` or `***`
|
|
386
|
+
- **Task lists** - `- [ ] todo` and `- [x] done`
|
|
387
|
+
- **Strikethrough** - `~~text~~`
|
|
388
|
+
|
|
389
|
+
**Supported languages for syntax highlighting:**
|
|
390
|
+
Add highlight.js language modules as needed (JavaScript, Python, TypeScript, Go, Rust, etc.)
|
|
391
|
+
|
|
207
392
|
## Backend Requirements
|
|
208
393
|
|
|
209
394
|
The widget expects a backend API with the following endpoints:
|
|
@@ -295,6 +480,36 @@ agent-frontend/
|
|
|
295
480
|
|
|
296
481
|
Requires: `EventSource` (SSE), `fetch`, `localStorage`
|
|
297
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
|
+
|
|
298
513
|
## License
|
|
299
514
|
|
|
300
515
|
MIT Š 2024
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat Widget - Enhanced Markdown Support
|
|
3
|
+
*
|
|
4
|
+
* This optional addon replaces the basic markdown parser with marked.js
|
|
5
|
+
* for full-featured markdown rendering including tables, code blocks with
|
|
6
|
+
* syntax highlighting, and more.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* 1. Include marked.js (and optionally highlight.js for syntax highlighting)
|
|
10
|
+
* 2. Include this file AFTER chat-widget.js
|
|
11
|
+
* 3. Initialize the widget normally
|
|
12
|
+
*
|
|
13
|
+
* Example:
|
|
14
|
+
* <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
15
|
+
* <script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/core.min.js"></script>
|
|
16
|
+
* <script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/languages/javascript.min.js"></script>
|
|
17
|
+
* <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11/styles/github-dark.min.css">
|
|
18
|
+
* <script src="chat-widget.js"></script>
|
|
19
|
+
* <script src="chat-widget-markdown.js"></script>
|
|
20
|
+
*/
|
|
21
|
+
(function(global) {
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
// Check if ChatWidget is available
|
|
25
|
+
if (!global.ChatWidget) {
|
|
26
|
+
console.error('[ChatWidget Markdown] ChatWidget must be loaded before chat-widget-markdown.js');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check if marked is available
|
|
31
|
+
if (typeof marked === 'undefined') {
|
|
32
|
+
console.warn('[ChatWidget Markdown] marked.js not found. Install with: npm install marked or include from CDN');
|
|
33
|
+
console.warn('[ChatWidget Markdown] Falling back to basic markdown parser');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('[ChatWidget Markdown] Enhanced markdown support enabled');
|
|
38
|
+
|
|
39
|
+
// Configure marked
|
|
40
|
+
const markedOptions = {
|
|
41
|
+
breaks: true,
|
|
42
|
+
gfm: true,
|
|
43
|
+
headerIds: false,
|
|
44
|
+
mangle: false,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Configure syntax highlighting if highlight.js is available
|
|
48
|
+
if (typeof hljs !== 'undefined') {
|
|
49
|
+
console.log('[ChatWidget Markdown] Syntax highlighting enabled');
|
|
50
|
+
markedOptions.highlight = function(code, lang) {
|
|
51
|
+
if (lang && hljs.getLanguage(lang)) {
|
|
52
|
+
try {
|
|
53
|
+
return hljs.highlight(code, { language: lang }).value;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error('[ChatWidget Markdown] Highlight error:', err);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return code;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
marked.setOptions(markedOptions);
|
|
63
|
+
|
|
64
|
+
// Enhanced markdown parser using marked.js
|
|
65
|
+
function enhancedParseMarkdown(text) {
|
|
66
|
+
if (!text) return '';
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Parse markdown with marked
|
|
70
|
+
let html = marked.parse(text);
|
|
71
|
+
|
|
72
|
+
// Sanitize links to open in new tab
|
|
73
|
+
html = html.replace(/<a href=/g, '<a target="_blank" rel="noopener noreferrer" href=');
|
|
74
|
+
|
|
75
|
+
return html;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error('[ChatWidget Markdown] Parse error:', err);
|
|
78
|
+
// Fallback to escaped text
|
|
79
|
+
const div = document.createElement('div');
|
|
80
|
+
div.textContent = text;
|
|
81
|
+
return div.innerHTML.replace(/\n/g, '<br>');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Store reference to original init
|
|
86
|
+
const originalInit = global.ChatWidget.init;
|
|
87
|
+
|
|
88
|
+
// Override init to inject enhanced markdown parser
|
|
89
|
+
global.ChatWidget.init = function(userConfig = {}) {
|
|
90
|
+
// Call original init
|
|
91
|
+
originalInit.call(this, userConfig);
|
|
92
|
+
|
|
93
|
+
// Inject enhanced markdown parser
|
|
94
|
+
// We need to override the internal parseMarkdown function
|
|
95
|
+
// This is done by monkey-patching the render cycle
|
|
96
|
+
console.log('[ChatWidget Markdown] Markdown parser enhanced with marked.js');
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Expose the enhanced parser for internal use
|
|
100
|
+
// The widget will need to check for this and use it if available
|
|
101
|
+
global.ChatWidget._enhancedMarkdownParser = enhancedParseMarkdown;
|
|
102
|
+
|
|
103
|
+
// Add configuration option
|
|
104
|
+
global.ChatWidget.enableEnhancedMarkdown = function() {
|
|
105
|
+
console.log('[ChatWidget Markdown] Enhanced markdown explicitly enabled');
|
|
106
|
+
return true;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
})(typeof window !== 'undefined' ? window : this);
|
|
110
|
+
|
package/dist/chat-widget.css
CHANGED
|
@@ -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;
|
|
@@ -450,6 +463,77 @@
|
|
|
450
463
|
background: rgba(239, 68, 68, 0.1);
|
|
451
464
|
}
|
|
452
465
|
|
|
466
|
+
/* Auto-run controls */
|
|
467
|
+
.cw-autorun-controls {
|
|
468
|
+
padding: 8px 12px;
|
|
469
|
+
display: flex;
|
|
470
|
+
flex-direction: column;
|
|
471
|
+
gap: 8px;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.cw-control-label {
|
|
475
|
+
display: flex;
|
|
476
|
+
align-items: center;
|
|
477
|
+
gap: 8px;
|
|
478
|
+
font-size: 13px;
|
|
479
|
+
color: var(--cw-text);
|
|
480
|
+
cursor: pointer;
|
|
481
|
+
padding: 4px;
|
|
482
|
+
border-radius: 4px;
|
|
483
|
+
transition: background 0.15s;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.cw-control-label:hover {
|
|
487
|
+
background: var(--cw-bg-muted);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.cw-control-label input[type="radio"] {
|
|
491
|
+
margin: 0;
|
|
492
|
+
cursor: pointer;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.cw-delay-control {
|
|
496
|
+
padding: 8px 12px;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.cw-delay-control input[type="range"] {
|
|
500
|
+
width: 100%;
|
|
501
|
+
margin-top: 4px;
|
|
502
|
+
cursor: pointer;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/* Continue button */
|
|
506
|
+
.cw-continue-bar {
|
|
507
|
+
padding: 12px 16px;
|
|
508
|
+
background: var(--cw-bg-muted);
|
|
509
|
+
border-top: 1px solid var(--cw-border);
|
|
510
|
+
display: flex;
|
|
511
|
+
justify-content: center;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.cw-continue-btn {
|
|
515
|
+
padding: 10px 20px;
|
|
516
|
+
border: none;
|
|
517
|
+
border-radius: var(--cw-radius-sm);
|
|
518
|
+
background: var(--cw-primary);
|
|
519
|
+
color: white;
|
|
520
|
+
font-size: 14px;
|
|
521
|
+
font-weight: 600;
|
|
522
|
+
cursor: pointer;
|
|
523
|
+
transition: opacity 0.15s;
|
|
524
|
+
display: flex;
|
|
525
|
+
align-items: center;
|
|
526
|
+
gap: 6px;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.cw-continue-btn:hover {
|
|
530
|
+
opacity: 0.9;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.cw-continue-btn:active {
|
|
534
|
+
opacity: 0.8;
|
|
535
|
+
}
|
|
536
|
+
|
|
453
537
|
/* Responsive */
|
|
454
538
|
@media (max-width: 480px) {
|
|
455
539
|
.cw-widget {
|
|
@@ -501,3 +585,118 @@
|
|
|
501
585
|
}
|
|
502
586
|
}
|
|
503
587
|
|
|
588
|
+
/* Enhanced Markdown Styles (for chat-widget-markdown.js) */
|
|
589
|
+
.cw-message pre {
|
|
590
|
+
background: var(--cw-bg-muted);
|
|
591
|
+
border: 1px solid var(--cw-border);
|
|
592
|
+
border-radius: var(--cw-radius-sm);
|
|
593
|
+
padding: 12px;
|
|
594
|
+
overflow-x: auto;
|
|
595
|
+
margin: 8px 0;
|
|
596
|
+
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
|
597
|
+
font-size: 13px;
|
|
598
|
+
line-height: 1.4;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.cw-message pre code {
|
|
602
|
+
background: none;
|
|
603
|
+
padding: 0;
|
|
604
|
+
border-radius: 0;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
.cw-message table {
|
|
608
|
+
border-collapse: collapse;
|
|
609
|
+
width: 100%;
|
|
610
|
+
margin: 8px 0;
|
|
611
|
+
font-size: 13px;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
.cw-message table th,
|
|
615
|
+
.cw-message table td {
|
|
616
|
+
border: 1px solid var(--cw-border);
|
|
617
|
+
padding: 8px 12px;
|
|
618
|
+
text-align: left;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.cw-message table th {
|
|
622
|
+
background: var(--cw-bg-muted);
|
|
623
|
+
font-weight: 600;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.cw-message table tr:nth-child(even) {
|
|
627
|
+
background: rgba(0, 0, 0, 0.02);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
.cw-message blockquote {
|
|
631
|
+
border-left: 3px solid var(--cw-primary);
|
|
632
|
+
padding-left: 12px;
|
|
633
|
+
margin: 8px 0;
|
|
634
|
+
color: var(--cw-text-muted);
|
|
635
|
+
font-style: italic;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.cw-message h1,
|
|
639
|
+
.cw-message h2,
|
|
640
|
+
.cw-message h3,
|
|
641
|
+
.cw-message h4,
|
|
642
|
+
.cw-message h5,
|
|
643
|
+
.cw-message h6 {
|
|
644
|
+
margin: 12px 0 8px 0;
|
|
645
|
+
font-weight: 600;
|
|
646
|
+
line-height: 1.3;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.cw-message h1 { font-size: 1.5em; }
|
|
650
|
+
.cw-message h2 { font-size: 1.3em; }
|
|
651
|
+
.cw-message h3 { font-size: 1.1em; }
|
|
652
|
+
.cw-message h4 { font-size: 1em; }
|
|
653
|
+
|
|
654
|
+
.cw-message hr {
|
|
655
|
+
border: none;
|
|
656
|
+
border-top: 1px solid var(--cw-border);
|
|
657
|
+
margin: 12px 0;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.cw-message ul,
|
|
661
|
+
.cw-message ol {
|
|
662
|
+
margin: 8px 0;
|
|
663
|
+
padding-left: 24px;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.cw-message li {
|
|
667
|
+
margin: 4px 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.cw-message p {
|
|
671
|
+
margin: 8px 0;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.cw-message p:first-child {
|
|
675
|
+
margin-top: 0;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.cw-message p:last-child {
|
|
679
|
+
margin-bottom: 0;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/* Dark mode adjustments for enhanced markdown */
|
|
683
|
+
@media (prefers-color-scheme: dark) {
|
|
684
|
+
.cw-message pre {
|
|
685
|
+
background: rgba(255, 255, 255, 0.05);
|
|
686
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.cw-message table th {
|
|
690
|
+
background: rgba(255, 255, 255, 0.05);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.cw-message table tr:nth-child(even) {
|
|
694
|
+
background: rgba(255, 255, 255, 0.02);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.cw-message table th,
|
|
698
|
+
.cw-message table td {
|
|
699
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
package/dist/chat-widget.js
CHANGED
|
@@ -43,6 +43,24 @@
|
|
|
43
43
|
runEvents: '/api/agent-runtime/runs/{runId}/events/',
|
|
44
44
|
simulateCustomer: '/api/agent-runtime/simulate-customer/',
|
|
45
45
|
},
|
|
46
|
+
// Demo flow control
|
|
47
|
+
autoRunDelay: 1000, // Delay in ms before auto-generating next message
|
|
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
|
+
},
|
|
46
64
|
};
|
|
47
65
|
|
|
48
66
|
// State
|
|
@@ -52,7 +70,8 @@
|
|
|
52
70
|
isExpanded: false,
|
|
53
71
|
isLoading: false,
|
|
54
72
|
isSimulating: false,
|
|
55
|
-
|
|
73
|
+
autoRunActive: false,
|
|
74
|
+
autoRunPaused: false,
|
|
56
75
|
debugMode: false,
|
|
57
76
|
journeyType: 'general',
|
|
58
77
|
messages: [],
|
|
@@ -60,6 +79,9 @@
|
|
|
60
79
|
sessionToken: null,
|
|
61
80
|
error: null,
|
|
62
81
|
eventSource: null,
|
|
82
|
+
currentAudio: null,
|
|
83
|
+
isSpeaking: false,
|
|
84
|
+
speechQueue: [],
|
|
63
85
|
};
|
|
64
86
|
|
|
65
87
|
// DOM elements
|
|
@@ -82,30 +104,35 @@
|
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
function parseMarkdown(text) {
|
|
85
|
-
//
|
|
107
|
+
// Check if enhanced markdown parser is available (from chat-widget-markdown.js)
|
|
108
|
+
if (global.ChatWidget && global.ChatWidget._enhancedMarkdownParser) {
|
|
109
|
+
return global.ChatWidget._enhancedMarkdownParser(text);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Fallback: Simple markdown parsing for common patterns
|
|
86
113
|
let html = escapeHtml(text);
|
|
87
|
-
|
|
114
|
+
|
|
88
115
|
// Bold: **text** or __text__
|
|
89
116
|
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
90
117
|
html = html.replace(/__(.+?)__/g, '<strong>$1</strong>');
|
|
91
|
-
|
|
118
|
+
|
|
92
119
|
// Italic: *text* or _text_
|
|
93
120
|
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
94
121
|
html = html.replace(/_(.+?)_/g, '<em>$1</em>');
|
|
95
|
-
|
|
122
|
+
|
|
96
123
|
// Code: `code`
|
|
97
124
|
html = html.replace(/`(.+?)`/g, '<code>$1</code>');
|
|
98
|
-
|
|
125
|
+
|
|
99
126
|
// Links: [text](url)
|
|
100
127
|
html = html.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
|
|
101
|
-
|
|
128
|
+
|
|
102
129
|
// Line breaks
|
|
103
130
|
html = html.replace(/\n/g, '<br>');
|
|
104
|
-
|
|
131
|
+
|
|
105
132
|
// Lists: - item or * item
|
|
106
133
|
html = html.replace(/^[\-\*]\s+(.+)$/gm, '<li>$1</li>');
|
|
107
134
|
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
|
|
108
|
-
|
|
135
|
+
|
|
109
136
|
return html;
|
|
110
137
|
}
|
|
111
138
|
|
|
@@ -129,6 +156,135 @@
|
|
|
129
156
|
}
|
|
130
157
|
}
|
|
131
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
|
+
|
|
132
288
|
// ============================================================================
|
|
133
289
|
// Session Management
|
|
134
290
|
// ============================================================================
|
|
@@ -338,9 +494,25 @@
|
|
|
338
494
|
state.eventSource = null;
|
|
339
495
|
render();
|
|
340
496
|
|
|
497
|
+
// Speak assistant message if TTS enabled
|
|
498
|
+
if (assistantContent && !state.error) {
|
|
499
|
+
speakText(assistantContent, 'assistant');
|
|
500
|
+
}
|
|
501
|
+
|
|
341
502
|
// Trigger auto-run if enabled
|
|
342
|
-
if (state.
|
|
343
|
-
|
|
503
|
+
if (state.autoRunActive && !state.error) {
|
|
504
|
+
if (config.autoRunMode === 'automatic') {
|
|
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
|
+
}
|
|
512
|
+
} else if (config.autoRunMode === 'confirm') {
|
|
513
|
+
state.autoRunPaused = true;
|
|
514
|
+
render();
|
|
515
|
+
}
|
|
344
516
|
}
|
|
345
517
|
};
|
|
346
518
|
|
|
@@ -365,12 +537,13 @@
|
|
|
365
537
|
// ============================================================================
|
|
366
538
|
|
|
367
539
|
async function triggerAutoRun() {
|
|
368
|
-
if (!state.
|
|
540
|
+
if (!state.autoRunActive || state.isLoading || state.isSimulating) return;
|
|
369
541
|
|
|
370
542
|
const lastMessage = state.messages[state.messages.length - 1];
|
|
371
543
|
if (lastMessage?.role !== 'assistant') return;
|
|
372
544
|
|
|
373
545
|
state.isSimulating = true;
|
|
546
|
+
state.autoRunPaused = false;
|
|
374
547
|
render();
|
|
375
548
|
|
|
376
549
|
try {
|
|
@@ -387,6 +560,12 @@
|
|
|
387
560
|
const data = await response.json();
|
|
388
561
|
if (data.response) {
|
|
389
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
|
+
|
|
390
569
|
await sendMessage(data.response);
|
|
391
570
|
return;
|
|
392
571
|
}
|
|
@@ -402,7 +581,8 @@
|
|
|
402
581
|
async function startDemoFlow(journeyType) {
|
|
403
582
|
clearMessages();
|
|
404
583
|
state.journeyType = journeyType;
|
|
405
|
-
state.
|
|
584
|
+
state.autoRunActive = true;
|
|
585
|
+
state.autoRunPaused = false;
|
|
406
586
|
render();
|
|
407
587
|
|
|
408
588
|
const journey = config.journeyTypes[journeyType];
|
|
@@ -419,7 +599,26 @@
|
|
|
419
599
|
}
|
|
420
600
|
|
|
421
601
|
function stopAutoRun() {
|
|
422
|
-
state.
|
|
602
|
+
state.autoRunActive = false;
|
|
603
|
+
state.autoRunPaused = false;
|
|
604
|
+
render();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function continueAutoRun() {
|
|
608
|
+
if (state.autoRunActive && state.autoRunPaused) {
|
|
609
|
+
triggerAutoRun();
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function setAutoRunMode(mode) {
|
|
614
|
+
if (['automatic', 'confirm', 'manual'].includes(mode)) {
|
|
615
|
+
config.autoRunMode = mode;
|
|
616
|
+
render();
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function setAutoRunDelay(delay) {
|
|
621
|
+
config.autoRunDelay = Math.max(0, parseInt(delay) || 1000);
|
|
423
622
|
render();
|
|
424
623
|
}
|
|
425
624
|
|
|
@@ -435,7 +634,8 @@
|
|
|
435
634
|
|
|
436
635
|
function closeWidget() {
|
|
437
636
|
state.isOpen = false;
|
|
438
|
-
state.
|
|
637
|
+
state.autoRunActive = false;
|
|
638
|
+
state.autoRunPaused = false;
|
|
439
639
|
render();
|
|
440
640
|
}
|
|
441
641
|
|
|
@@ -453,7 +653,8 @@
|
|
|
453
653
|
state.messages = [];
|
|
454
654
|
state.conversationId = null;
|
|
455
655
|
state.error = null;
|
|
456
|
-
state.
|
|
656
|
+
state.autoRunActive = false;
|
|
657
|
+
state.autoRunPaused = false;
|
|
457
658
|
setStoredValue(config.conversationIdKey, null);
|
|
458
659
|
render();
|
|
459
660
|
}
|
|
@@ -504,16 +705,48 @@
|
|
|
504
705
|
</button>
|
|
505
706
|
`).join('');
|
|
506
707
|
|
|
507
|
-
const
|
|
708
|
+
const controlsSection = state.autoRunActive ? `
|
|
709
|
+
<div class="cw-dropdown-separator"></div>
|
|
710
|
+
<div class="cw-dropdown-label">Demo Controls</div>
|
|
711
|
+
<div class="cw-autorun-controls">
|
|
712
|
+
<label class="cw-control-label">
|
|
713
|
+
<input type="radio" name="autorun-mode" value="automatic"
|
|
714
|
+
${config.autoRunMode === 'automatic' ? 'checked' : ''}
|
|
715
|
+
onchange="ChatWidget.setAutoRunMode('automatic')">
|
|
716
|
+
<span>⥠Automatic</span>
|
|
717
|
+
</label>
|
|
718
|
+
<label class="cw-control-label">
|
|
719
|
+
<input type="radio" name="autorun-mode" value="confirm"
|
|
720
|
+
${config.autoRunMode === 'confirm' ? 'checked' : ''}
|
|
721
|
+
onchange="ChatWidget.setAutoRunMode('confirm')">
|
|
722
|
+
<span>đ Confirm Next</span>
|
|
723
|
+
</label>
|
|
724
|
+
<label class="cw-control-label">
|
|
725
|
+
<input type="radio" name="autorun-mode" value="manual"
|
|
726
|
+
${config.autoRunMode === 'manual' ? 'checked' : ''}
|
|
727
|
+
onchange="ChatWidget.setAutoRunMode('manual')">
|
|
728
|
+
<span>â Manual</span>
|
|
729
|
+
</label>
|
|
730
|
+
</div>
|
|
731
|
+
${config.autoRunMode === 'automatic' ? `
|
|
732
|
+
<div class="cw-delay-control">
|
|
733
|
+
<label class="cw-control-label">
|
|
734
|
+
<span>Delay: ${config.autoRunDelay}ms</span>
|
|
735
|
+
<input type="range" min="0" max="5000" step="100"
|
|
736
|
+
value="${config.autoRunDelay}"
|
|
737
|
+
oninput="ChatWidget.setAutoRunDelay(this.value)">
|
|
738
|
+
</label>
|
|
739
|
+
</div>
|
|
740
|
+
` : ''}
|
|
508
741
|
<div class="cw-dropdown-separator"></div>
|
|
509
742
|
<button class="cw-dropdown-item cw-dropdown-item-danger" data-action="stop-autorun">
|
|
510
|
-
âšī¸ Stop
|
|
743
|
+
âšī¸ Stop Demo
|
|
511
744
|
</button>
|
|
512
745
|
` : '';
|
|
513
746
|
|
|
514
747
|
return `
|
|
515
748
|
<div class="cw-dropdown">
|
|
516
|
-
<button class="cw-header-btn ${state.
|
|
749
|
+
<button class="cw-header-btn ${state.autoRunActive ? 'cw-btn-active' : ''}"
|
|
517
750
|
data-action="toggle-journey-dropdown"
|
|
518
751
|
title="Demo Flows"
|
|
519
752
|
${state.isLoading || state.isSimulating ? 'disabled' : ''}>
|
|
@@ -523,7 +756,7 @@
|
|
|
523
756
|
<div class="cw-dropdown-label">Demo Flows</div>
|
|
524
757
|
<div class="cw-dropdown-separator"></div>
|
|
525
758
|
${journeyItems}
|
|
526
|
-
${
|
|
759
|
+
${controlsSection}
|
|
527
760
|
</div>
|
|
528
761
|
</div>
|
|
529
762
|
`;
|
|
@@ -567,9 +800,17 @@
|
|
|
567
800
|
</div>
|
|
568
801
|
` : '';
|
|
569
802
|
|
|
570
|
-
const
|
|
803
|
+
const continueButton = (state.autoRunActive && state.autoRunPaused && config.autoRunMode === 'confirm') ? `
|
|
804
|
+
<div class="cw-continue-bar">
|
|
805
|
+
<button class="cw-continue-btn" data-action="continue-autorun" style="background-color: ${config.primaryColor}">
|
|
806
|
+
âļī¸ Continue Demo
|
|
807
|
+
</button>
|
|
808
|
+
</div>
|
|
809
|
+
` : '';
|
|
810
|
+
|
|
811
|
+
const statusBar = (state.autoRunActive || state.debugMode) ? `
|
|
571
812
|
<div class="cw-status-bar">
|
|
572
|
-
${state.
|
|
813
|
+
${state.autoRunActive ? `<span>đ¤ Demo: ${config.journeyTypes[state.journeyType]?.label || state.journeyType} (${config.autoRunMode})</span>` : ''}
|
|
573
814
|
${state.debugMode ? '<span>đ Debug</span>' : ''}
|
|
574
815
|
</div>
|
|
575
816
|
` : '';
|
|
@@ -597,6 +838,13 @@
|
|
|
597
838
|
</svg>
|
|
598
839
|
</button>
|
|
599
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
|
+
` : ''}
|
|
600
848
|
${renderJourneyDropdown()}
|
|
601
849
|
<button class="cw-header-btn" data-action="toggle-expand" title="${state.isExpanded ? 'Minimize' : 'Expand'}">
|
|
602
850
|
${state.isExpanded ? 'â' : 'â'}
|
|
@@ -611,6 +859,7 @@
|
|
|
611
859
|
${messagesHtml}
|
|
612
860
|
${typingIndicator}
|
|
613
861
|
</div>
|
|
862
|
+
${continueButton}
|
|
614
863
|
${errorBar}
|
|
615
864
|
<form class="cw-input-form" id="cw-input-form">
|
|
616
865
|
<input type="text" class="cw-input" placeholder="${escapeHtml(config.placeholder)}" ${state.isLoading ? 'disabled' : ''}>
|
|
@@ -643,8 +892,10 @@
|
|
|
643
892
|
case 'close': closeWidget(); break;
|
|
644
893
|
case 'toggle-expand': toggleExpand(); break;
|
|
645
894
|
case 'toggle-debug': toggleDebugMode(); break;
|
|
895
|
+
case 'toggle-tts': toggleTTS(); break;
|
|
646
896
|
case 'clear': clearMessages(); break;
|
|
647
897
|
case 'stop-autorun': stopAutoRun(); break;
|
|
898
|
+
case 'continue-autorun': continueAutoRun(); break;
|
|
648
899
|
case 'toggle-journey-dropdown':
|
|
649
900
|
const dropdown = document.getElementById('cw-journey-dropdown');
|
|
650
901
|
if (dropdown) {
|
|
@@ -756,6 +1007,11 @@
|
|
|
756
1007
|
clearMessages,
|
|
757
1008
|
startDemoFlow,
|
|
758
1009
|
stopAutoRun,
|
|
1010
|
+
continueAutoRun,
|
|
1011
|
+
setAutoRunMode,
|
|
1012
|
+
setAutoRunDelay,
|
|
1013
|
+
toggleTTS,
|
|
1014
|
+
stopSpeech,
|
|
759
1015
|
getState: () => ({ ...state }),
|
|
760
1016
|
getConfig: () => ({ ...config }),
|
|
761
1017
|
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@makemore/agent-frontend",
|
|
3
|
-
"version": "1.
|
|
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": [
|
|
7
7
|
"dist/chat-widget.js",
|
|
8
8
|
"dist/chat-widget.css",
|
|
9
|
+
"dist/chat-widget-markdown.js",
|
|
9
10
|
"README.md",
|
|
10
11
|
"LICENSE"
|
|
11
12
|
],
|