@makemore/agent-frontend 1.0.0 → 1.3.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
@@ -32,16 +32,73 @@ Most chat widgets are tightly coupled to specific frameworks or require complex
32
32
  | 💾 **Persistence** | Conversations persist across page reloads via localStorage |
33
33
  | đŸ›Ąī¸ **Isolated CSS** | Scoped styles that won't leak into or from your page |
34
34
 
35
+ ## Installation
36
+
37
+ ### Via npm
38
+
39
+ ```bash
40
+ npm install @makemore/agent-frontend
41
+ ```
42
+
43
+ Then include in your HTML:
44
+
45
+ ```html
46
+ <link rel="stylesheet" href="node_modules/@makemore/agent-frontend/dist/chat-widget.css">
47
+ <script src="node_modules/@makemore/agent-frontend/dist/chat-widget.js"></script>
48
+ ```
49
+
50
+ ### Via CDN (unpkg)
51
+
52
+ ```html
53
+ <link rel="stylesheet" href="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.css">
54
+ <script src="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.js"></script>
55
+ ```
56
+
57
+ ### Via CDN (jsDelivr)
58
+
59
+ ```html
60
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@makemore/agent-frontend/dist/chat-widget.css">
61
+ <script src="https://cdn.jsdelivr.net/npm/@makemore/agent-frontend/dist/chat-widget.js"></script>
62
+ ```
63
+
64
+ ### Optional: Enhanced Markdown Support
65
+
66
+ For full-featured markdown rendering (tables, code blocks with syntax highlighting, etc.), include the optional markdown addon:
67
+
68
+ ```html
69
+ <!-- Core widget -->
70
+ <link rel="stylesheet" href="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.css">
71
+ <script src="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget.js"></script>
72
+
73
+ <!-- Optional: Enhanced markdown with marked.js -->
74
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
75
+ <script src="https://unpkg.com/@makemore/agent-frontend/dist/chat-widget-markdown.js"></script>
76
+
77
+ <!-- Optional: Syntax highlighting for code blocks -->
78
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11/styles/github-dark.min.css">
79
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/core.min.js"></script>
80
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/languages/javascript.min.js"></script>
81
+ <script src="https://cdn.jsdelivr.net/npm/highlight.js@11/lib/languages/python.min.js"></script>
82
+ ```
83
+
84
+ The widget automatically detects and uses the enhanced markdown parser if available. Without it, a basic markdown parser is used.
85
+
35
86
  ## Quick Start
36
87
 
37
- ### 1. Include the files
88
+ ### Initialize the widget
38
89
 
39
90
  ```html
40
- <link rel="stylesheet" href="https://your-cdn.com/chat-widget.css">
41
- <script src="https://your-cdn.com/chat-widget.js"></script>
91
+ <script>
92
+ ChatWidget.init({
93
+ backendUrl: 'https://your-api.com',
94
+ agentKey: 'your-agent',
95
+ title: 'Support Chat',
96
+ primaryColor: '#0066cc',
97
+ });
98
+ </script>
42
99
  ```
43
100
 
44
- ### 2. Initialize the widget
101
+ ### With custom API paths
45
102
 
46
103
  ```html
47
104
  <script>
@@ -50,6 +107,11 @@ Most chat widgets are tightly coupled to specific frameworks or require complex
50
107
  agentKey: 'your-agent',
51
108
  title: 'Support Chat',
52
109
  primaryColor: '#0066cc',
110
+ apiPaths: {
111
+ anonymousSession: '/api/auth/session/',
112
+ runs: '/api/chat/runs/',
113
+ runEvents: '/api/chat/runs/{runId}/events/',
114
+ },
53
115
  });
54
116
  </script>
55
117
  ```
@@ -74,6 +136,60 @@ Most chat widgets are tightly coupled to specific frameworks or require complex
74
136
  | `anonymousTokenHeader` | string | `'X-Anonymous-Token'` | Header name for auth token |
75
137
  | `conversationIdKey` | string | `'chat_widget_conversation_id'` | localStorage key for conversation ID |
76
138
  | `sessionTokenKey` | string | `'chat_widget_session_token'` | localStorage key for session token |
139
+ | `apiPaths` | object | See below | API endpoint paths (customizable for different backends) |
140
+ | `autoRunMode` | string | `'automatic'` | Demo flow mode: `'automatic'`, `'confirm'`, or `'manual'` |
141
+ | `autoRunDelay` | number | `1000` | Delay in milliseconds before auto-generating next message (automatic mode) |
142
+
143
+ ### Demo Flow Control
144
+
145
+ The widget supports three modes for demo flows:
146
+
147
+ - **Automatic** (`autoRunMode: 'automatic'`): Continuously generates customer responses with a configurable delay
148
+ - **Confirm Next** (`autoRunMode: 'confirm'`): Pauses after each assistant response and waits for user to click "Continue"
149
+ - **Manual** (`autoRunMode: 'manual'`): Stops auto-generation; user must manually type responses
150
+
151
+ These settings can be changed in real-time via the demo controls dropdown (visible when a demo is running).
152
+
153
+ ```javascript
154
+ ChatWidget.init({
155
+ autoRunMode: 'confirm', // Start in confirm mode
156
+ autoRunDelay: 2000, // 2 second delay in automatic mode
157
+ });
158
+
159
+ // Change mode programmatically
160
+ ChatWidget.setAutoRunMode('automatic');
161
+ ChatWidget.setAutoRunDelay(500); // 0.5 second delay
162
+ ```
163
+
164
+ ### API Paths Configuration
165
+
166
+ The `apiPaths` option allows you to customize the backend API endpoints. This is useful when integrating with different backend frameworks or URL structures.
167
+
168
+ **Default values:**
169
+ ```javascript
170
+ apiPaths: {
171
+ anonymousSession: '/api/accounts/anonymous-session/',
172
+ runs: '/api/agent-runtime/runs/',
173
+ runEvents: '/api/agent-runtime/runs/{runId}/events/',
174
+ simulateCustomer: '/api/agent-runtime/simulate-customer/',
175
+ }
176
+ ```
177
+
178
+ **Example - Custom Django backend:**
179
+ ```javascript
180
+ ChatWidget.init({
181
+ backendUrl: 'https://your-api.com',
182
+ agentKey: 'chat-agent',
183
+ apiPaths: {
184
+ anonymousSession: '/api/ai/agent/anonymous-session/',
185
+ runs: '/api/ai/agent/runs/',
186
+ runEvents: '/api/ai/agent/runs/{runId}/events/',
187
+ // simulateCustomer uses default value
188
+ },
189
+ });
190
+ ```
191
+
192
+ You only need to specify the paths you want to override; unspecified paths will use the defaults.
77
193
 
78
194
  ## Journey Types Configuration
79
195
 
@@ -120,9 +236,18 @@ ChatWidget.clearMessages();
120
236
  // Start a demo flow
121
237
  ChatWidget.startDemoFlow('quote');
122
238
 
123
- // Stop auto-run mode
239
+ // Stop demo flow
124
240
  ChatWidget.stopAutoRun();
125
241
 
242
+ // Continue demo flow (when in confirm mode and paused)
243
+ ChatWidget.continueAutoRun();
244
+
245
+ // Change demo flow mode
246
+ ChatWidget.setAutoRunMode('automatic'); // 'automatic', 'confirm', or 'manual'
247
+
248
+ // Change auto-run delay (in milliseconds)
249
+ ChatWidget.setAutoRunDelay(2000);
250
+
126
251
  // Remove the widget from the page
127
252
  ChatWidget.destroy();
128
253
 
@@ -133,6 +258,34 @@ const state = ChatWidget.getState();
133
258
  const config = ChatWidget.getConfig();
134
259
  ```
135
260
 
261
+ ## Markdown Support
262
+
263
+ The widget includes built-in markdown rendering for assistant messages:
264
+
265
+ ### Basic Markdown (Built-in)
266
+
267
+ The widget includes a lightweight markdown parser that supports:
268
+ - **Bold** (`**text**` or `__text__`)
269
+ - *Italic* (`*text*` or `_text_`)
270
+ - `Inline code` (`` `code` ``)
271
+ - [Links](url) (`[text](url)`)
272
+ - Lists (`- item` or `* item`)
273
+ - Line breaks
274
+
275
+ ### Enhanced Markdown (Optional)
276
+
277
+ Include `chat-widget-markdown.js` for full-featured markdown:
278
+ - **Tables** - Full GFM table support
279
+ - **Code blocks** - Multi-line code with syntax highlighting
280
+ - **Blockquotes** - `> quoted text`
281
+ - **Headings** - `# H1` through `###### H6`
282
+ - **Horizontal rules** - `---` or `***`
283
+ - **Task lists** - `- [ ] todo` and `- [x] done`
284
+ - **Strikethrough** - `~~text~~`
285
+
286
+ **Supported languages for syntax highlighting:**
287
+ Add highlight.js language modules as needed (JavaScript, Python, TypeScript, Go, Rust, etc.)
288
+
136
289
  ## Backend Requirements
137
290
 
138
291
  The widget expects a backend API with the following endpoints:
@@ -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
+
@@ -450,6 +450,77 @@
450
450
  background: rgba(239, 68, 68, 0.1);
451
451
  }
452
452
 
453
+ /* Auto-run controls */
454
+ .cw-autorun-controls {
455
+ padding: 8px 12px;
456
+ display: flex;
457
+ flex-direction: column;
458
+ gap: 8px;
459
+ }
460
+
461
+ .cw-control-label {
462
+ display: flex;
463
+ align-items: center;
464
+ gap: 8px;
465
+ font-size: 13px;
466
+ color: var(--cw-text);
467
+ cursor: pointer;
468
+ padding: 4px;
469
+ border-radius: 4px;
470
+ transition: background 0.15s;
471
+ }
472
+
473
+ .cw-control-label:hover {
474
+ background: var(--cw-bg-muted);
475
+ }
476
+
477
+ .cw-control-label input[type="radio"] {
478
+ margin: 0;
479
+ cursor: pointer;
480
+ }
481
+
482
+ .cw-delay-control {
483
+ padding: 8px 12px;
484
+ }
485
+
486
+ .cw-delay-control input[type="range"] {
487
+ width: 100%;
488
+ margin-top: 4px;
489
+ cursor: pointer;
490
+ }
491
+
492
+ /* Continue button */
493
+ .cw-continue-bar {
494
+ padding: 12px 16px;
495
+ background: var(--cw-bg-muted);
496
+ border-top: 1px solid var(--cw-border);
497
+ display: flex;
498
+ justify-content: center;
499
+ }
500
+
501
+ .cw-continue-btn {
502
+ padding: 10px 20px;
503
+ border: none;
504
+ border-radius: var(--cw-radius-sm);
505
+ background: var(--cw-primary);
506
+ color: white;
507
+ font-size: 14px;
508
+ font-weight: 600;
509
+ cursor: pointer;
510
+ transition: opacity 0.15s;
511
+ display: flex;
512
+ align-items: center;
513
+ gap: 6px;
514
+ }
515
+
516
+ .cw-continue-btn:hover {
517
+ opacity: 0.9;
518
+ }
519
+
520
+ .cw-continue-btn:active {
521
+ opacity: 0.8;
522
+ }
523
+
453
524
  /* Responsive */
454
525
  @media (max-width: 480px) {
455
526
  .cw-widget {
@@ -501,3 +572,118 @@
501
572
  }
502
573
  }
503
574
 
575
+ /* Enhanced Markdown Styles (for chat-widget-markdown.js) */
576
+ .cw-message pre {
577
+ background: var(--cw-bg-muted);
578
+ border: 1px solid var(--cw-border);
579
+ border-radius: var(--cw-radius-sm);
580
+ padding: 12px;
581
+ overflow-x: auto;
582
+ margin: 8px 0;
583
+ font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
584
+ font-size: 13px;
585
+ line-height: 1.4;
586
+ }
587
+
588
+ .cw-message pre code {
589
+ background: none;
590
+ padding: 0;
591
+ border-radius: 0;
592
+ }
593
+
594
+ .cw-message table {
595
+ border-collapse: collapse;
596
+ width: 100%;
597
+ margin: 8px 0;
598
+ font-size: 13px;
599
+ }
600
+
601
+ .cw-message table th,
602
+ .cw-message table td {
603
+ border: 1px solid var(--cw-border);
604
+ padding: 8px 12px;
605
+ text-align: left;
606
+ }
607
+
608
+ .cw-message table th {
609
+ background: var(--cw-bg-muted);
610
+ font-weight: 600;
611
+ }
612
+
613
+ .cw-message table tr:nth-child(even) {
614
+ background: rgba(0, 0, 0, 0.02);
615
+ }
616
+
617
+ .cw-message blockquote {
618
+ border-left: 3px solid var(--cw-primary);
619
+ padding-left: 12px;
620
+ margin: 8px 0;
621
+ color: var(--cw-text-muted);
622
+ font-style: italic;
623
+ }
624
+
625
+ .cw-message h1,
626
+ .cw-message h2,
627
+ .cw-message h3,
628
+ .cw-message h4,
629
+ .cw-message h5,
630
+ .cw-message h6 {
631
+ margin: 12px 0 8px 0;
632
+ font-weight: 600;
633
+ line-height: 1.3;
634
+ }
635
+
636
+ .cw-message h1 { font-size: 1.5em; }
637
+ .cw-message h2 { font-size: 1.3em; }
638
+ .cw-message h3 { font-size: 1.1em; }
639
+ .cw-message h4 { font-size: 1em; }
640
+
641
+ .cw-message hr {
642
+ border: none;
643
+ border-top: 1px solid var(--cw-border);
644
+ margin: 12px 0;
645
+ }
646
+
647
+ .cw-message ul,
648
+ .cw-message ol {
649
+ margin: 8px 0;
650
+ padding-left: 24px;
651
+ }
652
+
653
+ .cw-message li {
654
+ margin: 4px 0;
655
+ }
656
+
657
+ .cw-message p {
658
+ margin: 8px 0;
659
+ }
660
+
661
+ .cw-message p:first-child {
662
+ margin-top: 0;
663
+ }
664
+
665
+ .cw-message p:last-child {
666
+ margin-bottom: 0;
667
+ }
668
+
669
+ /* Dark mode adjustments for enhanced markdown */
670
+ @media (prefers-color-scheme: dark) {
671
+ .cw-message pre {
672
+ background: rgba(255, 255, 255, 0.05);
673
+ border-color: rgba(255, 255, 255, 0.1);
674
+ }
675
+
676
+ .cw-message table th {
677
+ background: rgba(255, 255, 255, 0.05);
678
+ }
679
+
680
+ .cw-message table tr:nth-child(even) {
681
+ background: rgba(255, 255, 255, 0.02);
682
+ }
683
+
684
+ .cw-message table th,
685
+ .cw-message table td {
686
+ border-color: rgba(255, 255, 255, 0.1);
687
+ }
688
+ }
689
+
@@ -36,6 +36,16 @@
36
36
  anonymousTokenHeader: 'X-Anonymous-Token',
37
37
  conversationIdKey: 'chat_widget_conversation_id',
38
38
  sessionTokenKey: 'chat_widget_session_token',
39
+ // API endpoint paths (can be customized for different backend setups)
40
+ apiPaths: {
41
+ anonymousSession: '/api/accounts/anonymous-session/',
42
+ runs: '/api/agent-runtime/runs/',
43
+ runEvents: '/api/agent-runtime/runs/{runId}/events/',
44
+ simulateCustomer: '/api/agent-runtime/simulate-customer/',
45
+ },
46
+ // Demo flow control
47
+ autoRunDelay: 1000, // Delay in ms before auto-generating next message
48
+ autoRunMode: 'automatic', // 'automatic', 'confirm', or 'manual'
39
49
  };
40
50
 
41
51
  // State
@@ -45,7 +55,8 @@
45
55
  isExpanded: false,
46
56
  isLoading: false,
47
57
  isSimulating: false,
48
- autoRunMode: false,
58
+ autoRunActive: false,
59
+ autoRunPaused: false,
49
60
  debugMode: false,
50
61
  journeyType: 'general',
51
62
  messages: [],
@@ -75,30 +86,35 @@
75
86
  }
76
87
 
77
88
  function parseMarkdown(text) {
78
- // Simple markdown parsing for common patterns
89
+ // Check if enhanced markdown parser is available (from chat-widget-markdown.js)
90
+ if (global.ChatWidget && global.ChatWidget._enhancedMarkdownParser) {
91
+ return global.ChatWidget._enhancedMarkdownParser(text);
92
+ }
93
+
94
+ // Fallback: Simple markdown parsing for common patterns
79
95
  let html = escapeHtml(text);
80
-
96
+
81
97
  // Bold: **text** or __text__
82
98
  html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
83
99
  html = html.replace(/__(.+?)__/g, '<strong>$1</strong>');
84
-
100
+
85
101
  // Italic: *text* or _text_
86
102
  html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
87
103
  html = html.replace(/_(.+?)_/g, '<em>$1</em>');
88
-
104
+
89
105
  // Code: `code`
90
106
  html = html.replace(/`(.+?)`/g, '<code>$1</code>');
91
-
107
+
92
108
  // Links: [text](url)
93
109
  html = html.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
94
-
110
+
95
111
  // Line breaks
96
112
  html = html.replace(/\n/g, '<br>');
97
-
113
+
98
114
  // Lists: - item or * item
99
115
  html = html.replace(/^[\-\*]\s+(.+)$/gm, '<li>$1</li>');
100
116
  html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
101
-
117
+
102
118
  return html;
103
119
  }
104
120
 
@@ -140,7 +156,7 @@
140
156
 
141
157
  // Create new anonymous session
142
158
  try {
143
- const response = await fetch(`${config.backendUrl}/api/accounts/anonymous-session/`, {
159
+ const response = await fetch(`${config.backendUrl}${config.apiPaths.anonymousSession}`, {
144
160
  method: 'POST',
145
161
  headers: { 'Content-Type': 'application/json' },
146
162
  });
@@ -191,7 +207,7 @@
191
207
  state.conversationId = getStoredValue(config.conversationIdKey);
192
208
  }
193
209
 
194
- const response = await fetch(`${config.backendUrl}/api/agent-runtime/runs/`, {
210
+ const response = await fetch(`${config.backendUrl}${config.apiPaths.runs}`, {
195
211
  method: 'POST',
196
212
  headers,
197
213
  body: JSON.stringify({
@@ -231,7 +247,8 @@
231
247
  state.eventSource.close();
232
248
  }
233
249
 
234
- let url = `${config.backendUrl}/api/agent-runtime/runs/${runId}/events/`;
250
+ const eventPath = config.apiPaths.runEvents.replace('{runId}', runId);
251
+ let url = `${config.backendUrl}${eventPath}`;
235
252
  if (token) {
236
253
  url += `?anonymous_token=${encodeURIComponent(token)}`;
237
254
  }
@@ -331,8 +348,13 @@
331
348
  render();
332
349
 
333
350
  // Trigger auto-run if enabled
334
- if (state.autoRunMode && !state.error) {
335
- setTimeout(() => triggerAutoRun(), 1000);
351
+ if (state.autoRunActive && !state.error) {
352
+ if (config.autoRunMode === 'automatic') {
353
+ setTimeout(() => triggerAutoRun(), config.autoRunDelay);
354
+ } else if (config.autoRunMode === 'confirm') {
355
+ state.autoRunPaused = true;
356
+ render();
357
+ }
336
358
  }
337
359
  };
338
360
 
@@ -357,16 +379,17 @@
357
379
  // ============================================================================
358
380
 
359
381
  async function triggerAutoRun() {
360
- if (!state.autoRunMode || state.isLoading || state.isSimulating) return;
382
+ if (!state.autoRunActive || state.isLoading || state.isSimulating) return;
361
383
 
362
384
  const lastMessage = state.messages[state.messages.length - 1];
363
385
  if (lastMessage?.role !== 'assistant') return;
364
386
 
365
387
  state.isSimulating = true;
388
+ state.autoRunPaused = false;
366
389
  render();
367
390
 
368
391
  try {
369
- const response = await fetch(`${config.backendUrl}/api/agent-runtime/simulate-customer/`, {
392
+ const response = await fetch(`${config.backendUrl}${config.apiPaths.simulateCustomer}`, {
370
393
  method: 'POST',
371
394
  headers: { 'Content-Type': 'application/json' },
372
395
  body: JSON.stringify({
@@ -394,7 +417,8 @@
394
417
  async function startDemoFlow(journeyType) {
395
418
  clearMessages();
396
419
  state.journeyType = journeyType;
397
- state.autoRunMode = true;
420
+ state.autoRunActive = true;
421
+ state.autoRunPaused = false;
398
422
  render();
399
423
 
400
424
  const journey = config.journeyTypes[journeyType];
@@ -411,7 +435,26 @@
411
435
  }
412
436
 
413
437
  function stopAutoRun() {
414
- state.autoRunMode = false;
438
+ state.autoRunActive = false;
439
+ state.autoRunPaused = false;
440
+ render();
441
+ }
442
+
443
+ function continueAutoRun() {
444
+ if (state.autoRunActive && state.autoRunPaused) {
445
+ triggerAutoRun();
446
+ }
447
+ }
448
+
449
+ function setAutoRunMode(mode) {
450
+ if (['automatic', 'confirm', 'manual'].includes(mode)) {
451
+ config.autoRunMode = mode;
452
+ render();
453
+ }
454
+ }
455
+
456
+ function setAutoRunDelay(delay) {
457
+ config.autoRunDelay = Math.max(0, parseInt(delay) || 1000);
415
458
  render();
416
459
  }
417
460
 
@@ -427,7 +470,8 @@
427
470
 
428
471
  function closeWidget() {
429
472
  state.isOpen = false;
430
- state.autoRunMode = false;
473
+ state.autoRunActive = false;
474
+ state.autoRunPaused = false;
431
475
  render();
432
476
  }
433
477
 
@@ -445,7 +489,8 @@
445
489
  state.messages = [];
446
490
  state.conversationId = null;
447
491
  state.error = null;
448
- state.autoRunMode = false;
492
+ state.autoRunActive = false;
493
+ state.autoRunPaused = false;
449
494
  setStoredValue(config.conversationIdKey, null);
450
495
  render();
451
496
  }
@@ -496,16 +541,48 @@
496
541
  </button>
497
542
  `).join('');
498
543
 
499
- const stopButton = state.autoRunMode ? `
544
+ const controlsSection = state.autoRunActive ? `
545
+ <div class="cw-dropdown-separator"></div>
546
+ <div class="cw-dropdown-label">Demo Controls</div>
547
+ <div class="cw-autorun-controls">
548
+ <label class="cw-control-label">
549
+ <input type="radio" name="autorun-mode" value="automatic"
550
+ ${config.autoRunMode === 'automatic' ? 'checked' : ''}
551
+ onchange="ChatWidget.setAutoRunMode('automatic')">
552
+ <span>⚡ Automatic</span>
553
+ </label>
554
+ <label class="cw-control-label">
555
+ <input type="radio" name="autorun-mode" value="confirm"
556
+ ${config.autoRunMode === 'confirm' ? 'checked' : ''}
557
+ onchange="ChatWidget.setAutoRunMode('confirm')">
558
+ <span>👆 Confirm Next</span>
559
+ </label>
560
+ <label class="cw-control-label">
561
+ <input type="radio" name="autorun-mode" value="manual"
562
+ ${config.autoRunMode === 'manual' ? 'checked' : ''}
563
+ onchange="ChatWidget.setAutoRunMode('manual')">
564
+ <span>✋ Manual</span>
565
+ </label>
566
+ </div>
567
+ ${config.autoRunMode === 'automatic' ? `
568
+ <div class="cw-delay-control">
569
+ <label class="cw-control-label">
570
+ <span>Delay: ${config.autoRunDelay}ms</span>
571
+ <input type="range" min="0" max="5000" step="100"
572
+ value="${config.autoRunDelay}"
573
+ oninput="ChatWidget.setAutoRunDelay(this.value)">
574
+ </label>
575
+ </div>
576
+ ` : ''}
500
577
  <div class="cw-dropdown-separator"></div>
501
578
  <button class="cw-dropdown-item cw-dropdown-item-danger" data-action="stop-autorun">
502
- âšī¸ Stop Auto-Run
579
+ âšī¸ Stop Demo
503
580
  </button>
504
581
  ` : '';
505
582
 
506
583
  return `
507
584
  <div class="cw-dropdown">
508
- <button class="cw-header-btn ${state.autoRunMode ? 'cw-btn-active' : ''}"
585
+ <button class="cw-header-btn ${state.autoRunActive ? 'cw-btn-active' : ''}"
509
586
  data-action="toggle-journey-dropdown"
510
587
  title="Demo Flows"
511
588
  ${state.isLoading || state.isSimulating ? 'disabled' : ''}>
@@ -515,7 +592,7 @@
515
592
  <div class="cw-dropdown-label">Demo Flows</div>
516
593
  <div class="cw-dropdown-separator"></div>
517
594
  ${journeyItems}
518
- ${stopButton}
595
+ ${controlsSection}
519
596
  </div>
520
597
  </div>
521
598
  `;
@@ -559,9 +636,17 @@
559
636
  </div>
560
637
  ` : '';
561
638
 
562
- const statusBar = (state.autoRunMode || state.debugMode) ? `
639
+ const continueButton = (state.autoRunActive && state.autoRunPaused && config.autoRunMode === 'confirm') ? `
640
+ <div class="cw-continue-bar">
641
+ <button class="cw-continue-btn" data-action="continue-autorun" style="background-color: ${config.primaryColor}">
642
+ â–ļī¸ Continue Demo
643
+ </button>
644
+ </div>
645
+ ` : '';
646
+
647
+ const statusBar = (state.autoRunActive || state.debugMode) ? `
563
648
  <div class="cw-status-bar">
564
- ${state.autoRunMode ? `<span>🤖 Auto-run: ${config.journeyTypes[state.journeyType]?.label || state.journeyType}</span>` : ''}
649
+ ${state.autoRunActive ? `<span>🤖 Demo: ${config.journeyTypes[state.journeyType]?.label || state.journeyType} (${config.autoRunMode})</span>` : ''}
565
650
  ${state.debugMode ? '<span>🐛 Debug</span>' : ''}
566
651
  </div>
567
652
  ` : '';
@@ -603,6 +688,7 @@
603
688
  ${messagesHtml}
604
689
  ${typingIndicator}
605
690
  </div>
691
+ ${continueButton}
606
692
  ${errorBar}
607
693
  <form class="cw-input-form" id="cw-input-form">
608
694
  <input type="text" class="cw-input" placeholder="${escapeHtml(config.placeholder)}" ${state.isLoading ? 'disabled' : ''}>
@@ -637,6 +723,7 @@
637
723
  case 'toggle-debug': toggleDebugMode(); break;
638
724
  case 'clear': clearMessages(); break;
639
725
  case 'stop-autorun': stopAutoRun(); break;
726
+ case 'continue-autorun': continueAutoRun(); break;
640
727
  case 'toggle-journey-dropdown':
641
728
  const dropdown = document.getElementById('cw-journey-dropdown');
642
729
  if (dropdown) {
@@ -685,7 +772,17 @@
685
772
  // ============================================================================
686
773
 
687
774
  function init(userConfig = {}) {
688
- config = { ...DEFAULT_CONFIG, ...userConfig };
775
+ // Deep merge apiPaths to allow partial overrides
776
+ const mergedApiPaths = {
777
+ ...DEFAULT_CONFIG.apiPaths,
778
+ ...(userConfig.apiPaths || {}),
779
+ };
780
+
781
+ config = {
782
+ ...DEFAULT_CONFIG,
783
+ ...userConfig,
784
+ apiPaths: mergedApiPaths,
785
+ };
689
786
  state.journeyType = config.defaultJourneyType;
690
787
 
691
788
  // Restore conversation ID
@@ -738,6 +835,9 @@
738
835
  clearMessages,
739
836
  startDemoFlow,
740
837
  stopAutoRun,
838
+ continueAutoRun,
839
+ setAutoRunMode,
840
+ setAutoRunDelay,
741
841
  getState: () => ({ ...state }),
742
842
  getConfig: () => ({ ...config }),
743
843
  };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@makemore/agent-frontend",
3
- "version": "1.0.0",
3
+ "version": "1.3.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
  ],
@@ -16,7 +17,7 @@
16
17
  },
17
18
  "repository": {
18
19
  "type": "git",
19
- "url": "git+https://github.com/yourusername/agent-frontend.git"
20
+ "url": "git+https://github.com/makemore/agent-frontend.git"
20
21
  },
21
22
  "keywords": [
22
23
  "chat",
@@ -33,12 +34,12 @@
33
34
  "conversational-ai",
34
35
  "customer-support"
35
36
  ],
36
- "author": "Your Name",
37
+ "author": "makemore",
37
38
  "license": "MIT",
38
39
  "bugs": {
39
- "url": "https://github.com/yourusername/agent-frontend/issues"
40
+ "url": "https://github.com/makemore/agent-frontend/issues"
40
41
  },
41
- "homepage": "https://github.com/yourusername/agent-frontend#readme",
42
+ "homepage": "https://github.com/makemore/agent-frontend#readme",
42
43
  "engines": {
43
44
  "node": ">=12.0.0"
44
45
  }