@makemore/agent-frontend 1.8.0 → 2.0.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.
@@ -1,1390 +1,319 @@
1
- /**
2
- * Embeddable Chat Widget
3
- * A standalone chat widget that can be embedded in any website.
4
- *
5
- * Usage:
6
- * <script src="chat-widget.js"></script>
7
- * <link rel="stylesheet" href="chat-widget.css">
8
- * <script>
9
- * ChatWidget.init({
10
- * backendUrl: 'https://your-api.com',
11
- * agentKey: 'your-agent',
12
- * title: 'Support Chat',
13
- * primaryColor: '#0066cc',
14
- * });
15
- * </script>
16
- */
17
- (function(global) {
18
- 'use strict';
19
-
20
- // Default configuration
21
- const DEFAULT_CONFIG = {
22
- backendUrl: 'http://localhost:8000',
23
- agentKey: 'insurance-agent',
24
- title: 'Chat Assistant',
25
- subtitle: 'How can we help you today?',
26
- primaryColor: '#0066cc',
27
- position: 'bottom-right', // bottom-right, bottom-left
28
- defaultJourneyType: 'general',
29
- enableDebugMode: true,
30
- enableAutoRun: true,
31
- journeyTypes: {},
32
- customerPrompts: {},
33
- placeholder: 'Type your message...',
34
- emptyStateTitle: 'Start a Conversation',
35
- emptyStateMessage: 'Send a message to get started.',
36
- // Authentication configuration
37
- authStrategy: null, // 'token' | 'jwt' | 'session' | 'anonymous' | 'none' (auto-detected if null)
38
- authToken: null, // Token value for 'token' or 'jwt' strategies
39
- authHeader: null, // Custom header name (defaults based on strategy)
40
- authTokenPrefix: null, // Custom token prefix (defaults based on strategy)
41
- anonymousSessionEndpoint: null, // Endpoint for anonymous session (defaults to apiPaths.anonymousSession)
42
- anonymousTokenKey: 'chat_widget_anonymous_token', // Storage key for anonymous token
43
- onAuthError: null, // Callback for auth errors: (error) => void
44
- // Legacy config (deprecated but still supported)
45
- anonymousTokenHeader: 'X-Anonymous-Token',
46
- conversationIdKey: 'chat_widget_conversation_id',
47
- sessionTokenKey: 'chat_widget_session_token', // Deprecated: use anonymousTokenKey
48
- // API endpoint paths (can be customized for different backend setups)
49
- apiPaths: {
50
- anonymousSession: '/api/accounts/anonymous-session/',
51
- runs: '/api/agent-runtime/runs/',
52
- runEvents: '/api/agent-runtime/runs/{runId}/events/',
53
- simulateCustomer: '/api/agent-runtime/simulate-customer/',
54
- ttsVoices: '/api/tts/voices/', // For fetching available voices (proxy mode)
55
- ttsSetVoice: '/api/tts/set-voice/', // For setting voice (proxy mode)
56
- },
57
- // Demo flow control
58
- autoRunDelay: 1000, // Delay in ms before auto-generating next message
59
- autoRunMode: 'automatic', // 'automatic', 'confirm', or 'manual'
60
- // Text-to-speech (ElevenLabs)
61
- enableTTS: false,
62
- ttsProxyUrl: null, // If set, uses Django proxy instead of direct API calls
63
- elevenLabsApiKey: null, // Only needed if not using proxy
64
- ttsVoices: {
65
- assistant: null, // ElevenLabs voice ID for assistant (not needed if using proxy)
66
- user: null, // ElevenLabs voice ID for simulated user (not needed if using proxy)
67
- },
68
- ttsModel: 'eleven_turbo_v2_5', // ElevenLabs model (not needed if using proxy)
69
- ttsSettings: {
70
- stability: 0.5,
71
- similarity_boost: 0.75,
72
- style: 0.0,
73
- use_speaker_boost: true,
74
- },
75
- availableVoices: [], // List of available voices for UI dropdown
76
- // UI visibility controls
77
- showClearButton: true,
78
- showDebugButton: true,
79
- showTTSButton: true,
80
- showVoiceSettings: true,
81
- showExpandButton: true,
82
- // Event callback
83
- onEvent: null, // Callback for SSE events: (eventType, payload) => void
84
- };
85
-
86
- // State
87
- let config = { ...DEFAULT_CONFIG };
88
- let state = {
89
- isOpen: false,
90
- isExpanded: false,
91
- isLoading: false,
92
- isSimulating: false,
93
- autoRunActive: false,
94
- autoRunPaused: false,
95
- debugMode: false,
96
- journeyType: 'general',
97
- messages: [],
98
- conversationId: null,
99
- sessionToken: null, // Deprecated: use authToken
100
- authToken: null, // Current auth token (for token/jwt/anonymous strategies)
101
- error: null,
102
- eventSource: null,
103
- currentAudio: null,
104
- isSpeaking: false,
105
- speechQueue: [],
106
- voiceSettingsOpen: false,
107
- };
108
-
109
- // DOM elements
110
- let container = null;
111
- let widgetEl = null;
112
- let fabEl = null;
113
-
114
- // ============================================================================
115
- // Utility Functions
116
- // ============================================================================
117
-
118
- function generateId() {
119
- return 'msg-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
120
- }
121
-
122
- function escapeHtml(text) {
123
- const div = document.createElement('div');
124
- div.textContent = text;
125
- return div.innerHTML;
126
- }
127
-
128
- function parseMarkdown(text) {
129
- // Check if enhanced markdown parser is available (from chat-widget-markdown.js)
130
- if (global.ChatWidget && global.ChatWidget._enhancedMarkdownParser) {
131
- return global.ChatWidget._enhancedMarkdownParser(text);
132
- }
133
-
134
- // Fallback: Simple markdown parsing for common patterns
135
- let html = escapeHtml(text);
136
-
137
- // Bold: **text** or __text__
138
- html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
139
- html = html.replace(/__(.+?)__/g, '<strong>$1</strong>');
140
-
141
- // Italic: *text* or _text_
142
- html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
143
- html = html.replace(/_(.+?)_/g, '<em>$1</em>');
144
-
145
- // Code: `code`
146
- html = html.replace(/`(.+?)`/g, '<code>$1</code>');
147
-
148
- // Links: [text](url)
149
- html = html.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
150
-
151
- // Line breaks
152
- html = html.replace(/\n/g, '<br>');
153
-
154
- // Lists: - item or * item
155
- html = html.replace(/^[\-\*]\s+(.+)$/gm, '<li>$1</li>');
156
- html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
157
-
158
- return html;
159
- }
160
-
161
- function getStoredValue(key) {
162
- try {
163
- return localStorage.getItem(key);
164
- } catch (e) {
165
- return null;
166
- }
167
- }
168
-
169
- function setStoredValue(key, value) {
170
- try {
171
- if (value === null) {
172
- localStorage.removeItem(key);
173
- } else {
174
- localStorage.setItem(key, value);
175
- }
176
- } catch (e) {
177
- // Ignore storage errors
178
- }
179
- }
180
-
181
- // ============================================================================
182
- // Text-to-Speech (ElevenLabs)
183
- // ============================================================================
184
-
185
- async function speakText(text, role) {
186
- if (!config.enableTTS) return;
187
-
188
- // Check if we have either proxy or direct API access
189
- if (!config.ttsProxyUrl && !config.elevenLabsApiKey) return;
190
-
191
- // If using direct API, check for voice ID
192
- if (!config.ttsProxyUrl) {
193
- const voiceId = role === 'assistant' ? config.ttsVoices.assistant : config.ttsVoices.user;
194
- if (!voiceId) return;
195
- }
196
-
197
- // Add to queue
198
- state.speechQueue.push({ text, role });
199
-
200
- // Process queue if not already speaking
201
- if (!state.isSpeaking) {
202
- processSpeechQueue();
203
- }
204
- }
205
-
206
- async function processSpeechQueue() {
207
- if (state.speechQueue.length === 0) {
208
- state.isSpeaking = false;
209
- render();
210
-
211
- // If auto-run is waiting for speech to finish, continue
212
- if (state.autoRunActive && state.autoRunPaused && config.autoRunMode === 'automatic') {
213
- setTimeout(() => {
214
- if (state.autoRunActive && !state.isSpeaking) {
215
- continueAutoRun();
216
- }
217
- }, config.autoRunDelay);
218
- }
219
- return;
220
- }
221
-
222
- state.isSpeaking = true;
223
- render();
224
-
225
- const { text, role } = state.speechQueue.shift();
226
-
227
- try {
228
- let response;
229
-
230
- if (config.ttsProxyUrl) {
231
- // Use Django proxy
232
- response = await fetch(config.ttsProxyUrl, getFetchOptions({
233
- method: 'POST',
234
- headers: {
235
- 'Content-Type': 'application/json',
236
- },
237
- body: JSON.stringify({
238
- text: text,
239
- role: role,
240
- }),
241
- }));
242
- } else {
243
- // Direct ElevenLabs API call
244
- const voiceId = role === 'assistant' ? config.ttsVoices.assistant : config.ttsVoices.user;
245
- response = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
246
- method: 'POST',
247
- headers: {
248
- 'Accept': 'audio/mpeg',
249
- 'Content-Type': 'application/json',
250
- 'xi-api-key': config.elevenLabsApiKey,
251
- },
252
- body: JSON.stringify({
253
- text: text,
254
- model_id: config.ttsModel,
255
- voice_settings: config.ttsSettings,
256
- }),
257
- });
258
- }
259
-
260
- if (!response.ok) {
261
- throw new Error(`TTS API error: ${response.status}`);
262
- }
263
-
264
- const audioBlob = await response.blob();
265
- const audioUrl = URL.createObjectURL(audioBlob);
266
- const audio = new Audio(audioUrl);
267
-
268
- state.currentAudio = audio;
269
-
270
- audio.onended = () => {
271
- URL.revokeObjectURL(audioUrl);
272
- state.currentAudio = null;
273
- processSpeechQueue();
274
- };
275
-
276
- audio.onerror = () => {
277
- console.error('[ChatWidget] Audio playback error');
278
- URL.revokeObjectURL(audioUrl);
279
- state.currentAudio = null;
280
- processSpeechQueue();
281
- };
282
-
283
- await audio.play();
284
- } catch (err) {
285
- console.error('[ChatWidget] TTS error:', err);
286
- state.currentAudio = null;
287
- processSpeechQueue();
288
- }
289
- }
290
-
291
- function stopSpeech() {
292
- if (state.currentAudio) {
293
- state.currentAudio.pause();
294
- state.currentAudio = null;
295
- }
296
- state.speechQueue = [];
297
- state.isSpeaking = false;
298
- render();
299
- }
300
-
301
- function toggleTTS() {
302
- config.enableTTS = !config.enableTTS;
303
- if (!config.enableTTS) {
304
- stopSpeech();
305
- }
306
- render();
307
- }
308
-
309
- function toggleVoiceSettings() {
310
- state.voiceSettingsOpen = !state.voiceSettingsOpen;
311
- render();
312
- }
313
-
314
- async function setVoice(role, voiceId) {
315
- config.ttsVoices[role] = voiceId;
316
-
317
- // If using proxy, notify backend of voice change
318
- if (config.ttsProxyUrl) {
319
- try {
320
- await getOrCreateSession();
321
-
322
- await fetch(`${config.backendUrl}${config.apiPaths.ttsSetVoice}`, getFetchOptions({
323
- method: 'POST',
324
- headers: {
325
- 'Content-Type': 'application/json',
326
- },
327
- body: JSON.stringify({ role, voice_id: voiceId }),
328
- }));
329
- } catch (err) {
330
- console.error('[ChatWidget] Failed to set voice on backend:', err);
331
- }
332
- }
333
-
334
- render();
335
- }
336
-
337
- async function fetchAvailableVoices() {
338
- try {
339
- let voices = [];
340
-
341
- if (config.ttsProxyUrl) {
342
- // Fetch voices from Django backend
343
- await getOrCreateSession();
344
-
345
- const response = await fetch(`${config.backendUrl}${config.apiPaths.ttsVoices}`, getFetchOptions());
346
-
347
- if (response.ok) {
348
- const data = await response.json();
349
- voices = data.voices || [];
350
- }
351
- } else if (config.elevenLabsApiKey) {
352
- // Fetch voices directly from ElevenLabs
353
- const response = await fetch('https://api.elevenlabs.io/v1/voices', {
354
- headers: {
355
- 'xi-api-key': config.elevenLabsApiKey,
356
- },
357
- });
358
-
359
- if (response.ok) {
360
- const data = await response.json();
361
- voices = data.voices || [];
362
- }
363
- }
364
-
365
- config.availableVoices = voices;
366
- render(); // Re-render to update dropdowns
367
- } catch (err) {
368
- console.error('[ChatWidget] Failed to fetch voices:', err);
369
- }
370
- }
371
-
372
- // ============================================================================
373
- // Authentication
374
- // ============================================================================
375
-
376
- /**
377
- * Determine the effective auth strategy based on config
378
- */
379
- function getAuthStrategy() {
380
- if (config.authStrategy) {
381
- return config.authStrategy;
382
- }
383
-
384
- // Auto-detect strategy based on config
385
- if (config.authToken) {
386
- return 'token'; // Default to token auth if token provided
387
- }
388
-
389
- // Check for legacy anonymous session config
390
- if (config.apiPaths.anonymousSession || config.anonymousSessionEndpoint) {
391
- return 'anonymous';
392
- }
393
-
394
- return 'none';
395
- }
396
-
397
- /**
398
- * Get auth headers based on current strategy
399
- */
400
- function getAuthHeaders() {
401
- const strategy = getAuthStrategy();
402
- const headers = {};
403
-
404
- switch (strategy) {
405
- case 'token': {
406
- const token = config.authToken || state.authToken;
407
- if (token) {
408
- const headerName = config.authHeader || 'Authorization';
409
- const prefix = config.authTokenPrefix !== undefined ? config.authTokenPrefix : 'Token';
410
- headers[headerName] = prefix ? `${prefix} ${token}` : token;
411
- }
412
- break;
413
- }
414
-
415
- case 'jwt': {
416
- const token = config.authToken || state.authToken;
417
- if (token) {
418
- const headerName = config.authHeader || 'Authorization';
419
- const prefix = config.authTokenPrefix !== undefined ? config.authTokenPrefix : 'Bearer';
420
- headers[headerName] = prefix ? `${prefix} ${token}` : token;
421
- }
422
- break;
423
- }
424
-
425
- case 'anonymous': {
426
- const token = state.authToken || state.sessionToken; // Support legacy sessionToken
427
- if (token) {
428
- const headerName = config.authHeader || config.anonymousTokenHeader || 'X-Anonymous-Token';
429
- headers[headerName] = token;
430
- }
431
- break;
432
- }
433
-
434
- case 'session':
435
- // Session auth uses cookies, no headers needed
436
- break;
437
-
438
- case 'none':
439
- // No auth
440
- break;
441
- }
442
-
443
- return headers;
444
- }
445
-
446
- /**
447
- * Get fetch options including auth credentials
448
- */
449
- function getFetchOptions(options = {}) {
450
- const strategy = getAuthStrategy();
451
- const fetchOptions = { ...options };
452
-
453
- // Add auth headers
454
- fetchOptions.headers = {
455
- ...fetchOptions.headers,
456
- ...getAuthHeaders(),
457
- };
458
-
459
- // For session auth, include credentials
460
- if (strategy === 'session') {
461
- fetchOptions.credentials = 'include';
462
- }
463
-
464
- return fetchOptions;
465
- }
466
-
467
- /**
468
- * Handle auth errors (401, 403)
469
- */
470
- function handleAuthError(error, response) {
471
- if (config.onAuthError && typeof config.onAuthError === 'function') {
472
- const authError = new Error(error.message || 'Authentication failed');
473
- authError.status = response?.status;
474
- authError.response = response;
475
- config.onAuthError(authError);
476
- }
477
- }
478
-
479
- // ============================================================================
480
- // Session Management
481
- // ============================================================================
482
-
483
- async function getOrCreateSession() {
484
- const strategy = getAuthStrategy();
485
-
486
- // For non-anonymous strategies, return the configured token
487
- if (strategy !== 'anonymous') {
488
- return config.authToken || state.authToken;
489
- }
490
-
491
- // Anonymous strategy: get or create anonymous token
492
- if (state.authToken) {
493
- return state.authToken;
494
- }
495
-
496
- // Support legacy sessionToken
497
- if (state.sessionToken) {
498
- state.authToken = state.sessionToken;
499
- return state.authToken;
500
- }
501
-
502
- // Try to restore from storage
503
- const storageKey = config.anonymousTokenKey || config.sessionTokenKey;
504
- const stored = getStoredValue(storageKey);
505
- if (stored) {
506
- state.authToken = stored;
507
- state.sessionToken = stored; // Keep legacy field in sync
508
- return stored;
509
- }
510
-
511
- // Create new anonymous session
512
- try {
513
- const endpoint = config.anonymousSessionEndpoint || config.apiPaths.anonymousSession;
514
- const response = await fetch(`${config.backendUrl}${endpoint}`, {
515
- method: 'POST',
516
- headers: { 'Content-Type': 'application/json' },
517
- });
518
-
519
- if (response.ok) {
520
- const data = await response.json();
521
- const token = data.token;
522
- state.authToken = token;
523
- state.sessionToken = token; // Keep legacy field in sync
524
- setStoredValue(storageKey, token);
525
- return token;
526
- } else if (response.status === 401 || response.status === 403) {
527
- handleAuthError(new Error('Failed to create anonymous session'), response);
528
- }
529
- } catch (e) {
530
- console.warn('[ChatWidget] Failed to create session:', e);
531
- }
532
-
533
- return null;
534
- }
535
-
536
- // ============================================================================
537
- // API Functions
538
- // ============================================================================
539
-
540
- async function sendMessage(content) {
541
- if (!content.trim() || state.isLoading) return;
542
-
543
- state.isLoading = true;
544
- state.error = null;
545
-
546
- // Add user message immediately
547
- const userMessage = {
548
- id: generateId(),
549
- role: 'user',
550
- content: content.trim(),
551
- timestamp: new Date(),
552
- type: 'message',
553
- };
554
- state.messages.push(userMessage);
555
- render();
556
-
557
- try {
558
- // Get auth token (if using anonymous strategy)
559
- const token = await getOrCreateSession();
560
-
561
- // Restore conversation ID from storage if not set
562
- if (!state.conversationId) {
563
- state.conversationId = getStoredValue(config.conversationIdKey);
564
- }
565
-
566
- const response = await fetch(`${config.backendUrl}${config.apiPaths.runs}`, getFetchOptions({
567
- method: 'POST',
568
- headers: { 'Content-Type': 'application/json' },
569
- body: JSON.stringify({
570
- agentKey: config.agentKey,
571
- conversationId: state.conversationId,
572
- messages: [{ role: 'user', content: content.trim() }],
573
- metadata: { journey_type: state.journeyType },
574
- }),
575
- }));
576
-
577
- if (!response.ok) {
578
- const errorData = await response.json().catch(() => ({}));
579
- const error = new Error(errorData.error || `HTTP ${response.status}`);
580
-
581
- // Handle auth errors
582
- if (response.status === 401 || response.status === 403) {
583
- handleAuthError(error, response);
584
- }
585
-
586
- throw error;
587
- }
588
-
589
- const run = await response.json();
590
-
591
- // Store conversation ID
592
- if (!state.conversationId && run.conversationId) {
593
- state.conversationId = run.conversationId;
594
- setStoredValue(config.conversationIdKey, run.conversationId);
595
- }
596
-
597
- // Subscribe to SSE events
598
- await subscribeToEvents(run.id, token);
599
-
600
- } catch (err) {
601
- state.error = err.message || 'Failed to send message';
602
- state.isLoading = false;
603
- render();
604
- }
605
- }
606
-
607
- async function subscribeToEvents(runId, token) {
608
- // Close existing connection
609
- if (state.eventSource) {
610
- state.eventSource.close();
611
- }
612
-
613
- const eventPath = config.apiPaths.runEvents.replace('{runId}', runId);
614
- let url = `${config.backendUrl}${eventPath}`;
615
- if (token) {
616
- url += `?anonymous_token=${encodeURIComponent(token)}`;
617
- }
618
-
619
- const eventSource = new EventSource(url);
620
- state.eventSource = eventSource;
621
-
622
- let assistantContent = '';
623
-
624
- // Handler for assistant messages
625
- eventSource.addEventListener('assistant.message', (event) => {
626
- try {
627
- const data = JSON.parse(event.data);
628
-
629
- // Call onEvent callback if provided
630
- if (config.onEvent && typeof config.onEvent === 'function') {
631
- config.onEvent('assistant.message', data.payload);
632
- }
633
-
634
- const content = data.payload.content;
635
- if (content) {
636
- assistantContent += content;
637
-
638
- // Update or add assistant message
639
- const lastMsg = state.messages[state.messages.length - 1];
640
- if (lastMsg?.role === 'assistant' && lastMsg.id.startsWith('assistant-stream-')) {
641
- lastMsg.content = assistantContent;
642
- } else {
643
- state.messages.push({
644
- id: 'assistant-stream-' + Date.now(),
645
- role: 'assistant',
646
- content: assistantContent,
647
- timestamp: new Date(),
648
- type: 'message',
649
- });
650
- }
651
- render();
652
- }
653
- } catch (err) {
654
- console.error('[ChatWidget] Failed to parse assistant.message:', err);
655
- }
656
- });
657
-
658
- // Handler for tool calls (debug mode)
659
- eventSource.addEventListener('tool.call', (event) => {
660
- try {
661
- const data = JSON.parse(event.data);
662
-
663
- // Call onEvent callback if provided
664
- if (config.onEvent && typeof config.onEvent === 'function') {
665
- config.onEvent('tool.call', data.payload);
666
- }
667
-
668
- if (state.debugMode) {
669
- state.messages.push({
670
- id: 'tool-call-' + Date.now(),
671
- role: 'system',
672
- content: `🔧 Tool: ${data.payload.name}`,
673
- timestamp: new Date(),
674
- type: 'tool_call',
675
- metadata: { name: data.payload.name, arguments: data.payload.arguments },
676
- });
677
- render();
678
- }
679
- } catch (err) {
680
- console.error('[ChatWidget] Failed to parse tool.call:', err);
681
- }
682
- });
683
-
684
- // Handler for tool results (debug mode)
685
- eventSource.addEventListener('tool.result', (event) => {
686
- try {
687
- const data = JSON.parse(event.data);
688
-
689
- // Call onEvent callback if provided
690
- if (config.onEvent && typeof config.onEvent === 'function') {
691
- config.onEvent('tool.result', data.payload);
692
- }
693
-
694
- if (state.debugMode) {
695
- const result = data.payload.result || '';
696
- state.messages.push({
697
- id: 'tool-result-' + Date.now(),
698
- role: 'system',
699
- content: `✅ Result: ${result.substring(0, 100)}${result.length > 100 ? '...' : ''}`,
700
- timestamp: new Date(),
701
- type: 'tool_result',
702
- metadata: { result },
703
- });
704
- render();
705
- }
706
- } catch (err) {
707
- console.error('[ChatWidget] Failed to parse tool.result:', err);
708
- }
709
- });
710
-
711
- // Terminal event handlers
712
- const handleTerminal = (event) => {
713
- try {
714
- const data = JSON.parse(event.data);
715
-
716
- // Call onEvent callback if provided
717
- if (config.onEvent && typeof config.onEvent === 'function') {
718
- config.onEvent(data.type, data.payload);
719
- }
720
-
721
- if (data.type === 'run.failed') {
722
- state.error = data.payload.error || 'Agent run failed';
723
- state.messages.push({
724
- id: 'error-' + Date.now(),
725
- role: 'system',
726
- content: `❌ Error: ${state.error}`,
727
- timestamp: new Date(),
728
- type: 'error',
729
- });
730
- }
731
- } catch (err) {
732
- console.error('[ChatWidget] Failed to parse terminal event:', err);
733
- }
734
- state.isLoading = false;
735
- eventSource.close();
736
- state.eventSource = null;
737
- render();
738
-
739
- // Speak assistant message if TTS enabled
740
- if (assistantContent && !state.error) {
741
- speakText(assistantContent, 'assistant');
742
- }
743
-
744
- // Trigger auto-run if enabled
745
- if (state.autoRunActive && !state.error) {
746
- if (config.autoRunMode === 'automatic') {
747
- // Wait for speech to finish before continuing
748
- if (config.enableTTS && assistantContent) {
749
- state.autoRunPaused = true;
750
- // processSpeechQueue will continue when done
751
- } else {
752
- setTimeout(() => triggerAutoRun(), config.autoRunDelay);
753
- }
754
- } else if (config.autoRunMode === 'confirm') {
755
- state.autoRunPaused = true;
756
- render();
757
- }
758
- }
759
- };
760
-
761
- eventSource.addEventListener('run.succeeded', handleTerminal);
762
- eventSource.addEventListener('run.failed', handleTerminal);
763
- eventSource.addEventListener('run.cancelled', handleTerminal);
764
- eventSource.addEventListener('run.timed_out', handleTerminal);
765
-
766
- // Generic handler for any other custom events
767
- eventSource.onmessage = (event) => {
768
- try {
769
- const data = JSON.parse(event.data);
770
-
771
- // Call onEvent callback for any unhandled events
772
- if (config.onEvent && typeof config.onEvent === 'function') {
773
- // Extract event type from data or use 'message' as default
774
- const eventType = data.type || 'message';
775
- config.onEvent(eventType, data.payload || data);
776
- }
777
- } catch (err) {
778
- console.debug('[ChatWidget] Received non-JSON SSE message:', event.data);
779
- }
780
- };
781
-
782
- eventSource.onerror = () => {
783
- if (eventSource.readyState !== EventSource.CLOSED) {
784
- console.debug('[ChatWidget] SSE connection closed');
785
- }
786
- state.isLoading = false;
787
- eventSource.close();
788
- state.eventSource = null;
789
- render();
790
- };
791
- }
792
-
793
- // ============================================================================
794
- // Auto-Run / Demo Mode
795
- // ============================================================================
796
-
797
- async function triggerAutoRun() {
798
- if (!state.autoRunActive || state.isLoading || state.isSimulating) return;
799
-
800
- const lastMessage = state.messages[state.messages.length - 1];
801
- if (lastMessage?.role !== 'assistant') return;
802
-
803
- state.isSimulating = true;
804
- state.autoRunPaused = false;
805
- render();
806
-
807
- try {
808
- const response = await fetch(`${config.backendUrl}${config.apiPaths.simulateCustomer}`, {
809
- method: 'POST',
810
- headers: { 'Content-Type': 'application/json' },
811
- body: JSON.stringify({
812
- messages: state.messages.map(m => ({ role: m.role, content: m.content })),
813
- journey_type: state.journeyType,
814
- }),
815
- });
816
-
817
- if (response.ok) {
818
- const data = await response.json();
819
- if (data.response) {
820
- state.isSimulating = false;
821
-
822
- // Speak simulated user message if TTS enabled
823
- if (config.enableTTS) {
824
- await speakText(data.response, 'user');
825
- }
826
-
827
- await sendMessage(data.response);
828
- return;
829
- }
830
- }
831
- } catch (err) {
832
- console.error('[ChatWidget] Failed to simulate customer:', err);
833
- }
834
-
835
- state.isSimulating = false;
836
- render();
837
- }
838
-
839
- async function startDemoFlow(journeyType) {
840
- clearMessages();
841
- state.journeyType = journeyType;
842
- state.autoRunActive = true;
843
- state.autoRunPaused = false;
844
- render();
845
-
846
- const journey = config.journeyTypes[journeyType];
847
- if (journey?.initialMessage) {
848
- setTimeout(async () => {
849
- state.isSimulating = true;
850
- render();
851
-
852
- // Speak initial message if TTS enabled
853
- if (config.enableTTS) {
854
- await speakText(journey.initialMessage, 'user');
855
- }
856
-
857
- sendMessage(journey.initialMessage).then(() => {
858
- state.isSimulating = false;
859
- render();
860
- });
861
- }, 100);
862
- }
863
- }
864
-
865
- function stopAutoRun() {
866
- state.autoRunActive = false;
867
- state.autoRunPaused = false;
868
- render();
869
- }
870
-
871
- function continueAutoRun() {
872
- if (state.autoRunActive && state.autoRunPaused) {
873
- triggerAutoRun();
874
- }
875
- }
876
-
877
- function setAutoRunMode(mode) {
878
- if (['automatic', 'confirm', 'manual'].includes(mode)) {
879
- config.autoRunMode = mode;
880
- render();
881
- }
882
- }
883
-
884
- function setAutoRunDelay(delay) {
885
- config.autoRunDelay = Math.max(0, parseInt(delay) || 1000);
886
- render();
887
- }
888
-
889
- // ============================================================================
890
- // UI Actions
891
- // ============================================================================
892
-
893
- function openWidget() {
894
- state.isOpen = true;
895
- getOrCreateSession();
896
- render();
897
- }
898
-
899
- function closeWidget() {
900
- state.isOpen = false;
901
- state.autoRunActive = false;
902
- state.autoRunPaused = false;
903
- render();
904
- }
905
-
906
- function toggleExpand() {
907
- state.isExpanded = !state.isExpanded;
908
- render();
909
- }
910
-
911
- function toggleDebugMode() {
912
- state.debugMode = !state.debugMode;
913
- render();
914
- }
915
-
916
- function clearMessages() {
917
- state.messages = [];
918
- state.conversationId = null;
919
- state.error = null;
920
- state.autoRunActive = false;
921
- state.autoRunPaused = false;
922
- setStoredValue(config.conversationIdKey, null);
923
- render();
924
- }
925
-
926
- // ============================================================================
927
- // Render Functions
928
- // ============================================================================
929
-
930
- function renderMessage(msg) {
931
- const isUser = msg.role === 'user';
932
- const isToolCall = msg.type === 'tool_call';
933
- const isToolResult = msg.type === 'tool_result';
934
- const isError = msg.type === 'error';
935
-
936
- // Hide debug messages if debug mode is off
937
- if ((isToolCall || isToolResult) && !state.debugMode) {
938
- return '';
939
- }
940
-
941
- let classes = 'cw-message';
942
- if (isUser) classes += ' cw-message-user';
943
- if (isToolCall) classes += ' cw-message-tool-call';
944
- if (isToolResult) classes += ' cw-message-tool-result';
945
- if (isError) classes += ' cw-message-error';
946
-
947
- let content = msg.role === 'assistant' ? parseMarkdown(msg.content) : escapeHtml(msg.content);
948
-
949
- // Add tool arguments for tool calls
950
- if (isToolCall && msg.metadata?.arguments) {
951
- content += `<pre class="cw-tool-args">${escapeHtml(JSON.stringify(msg.metadata.arguments, null, 2))}</pre>`;
952
- }
953
-
954
- return `
955
- <div class="cw-message-row ${isUser ? 'cw-message-row-user' : ''}">
956
- <div class="${classes}">${content}</div>
1
+ var ChatWidgetModule=(()=>{var he=Object.defineProperty;var wt=Object.getOwnPropertyDescriptor;var $t=Object.getOwnPropertyNames;var bt=Object.prototype.hasOwnProperty;var kt=(e,t)=>{for(var n in t)he(e,n,{get:t[n],enumerable:!0})},Ct=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of $t(t))!bt.call(e,a)&&a!==n&&he(e,a,{get:()=>t[a],enumerable:!(s=wt(t,a))||s.enumerable});return e};var St=e=>Ct(he({},"__esModule",{value:!0}),e);var Xt={};kt(Xt,{ChatWidget:()=>fe,default:()=>Gt});var re,M,De,Tt,G,xe,He,Ae,Ne,ve,me,ye,Mt,Z={},Oe=[],xt=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,le=Array.isArray;function q(e,t){for(var n in t)e[n]=t[n];return e}function we(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function ie(e,t,n){var s,a,o,l={};for(o in t)o=="key"?s=t[o]:o=="ref"?a=t[o]:l[o]=t[o];if(arguments.length>2&&(l.children=arguments.length>3?re.call(arguments,2):n),typeof e=="function"&&e.defaultProps!=null)for(o in e.defaultProps)l[o]===void 0&&(l[o]=e.defaultProps[o]);return se(e,l,s,a,null)}function se(e,t,n,s,a){var o={type:e,props:t,key:n,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:a??++De,__i:-1,__u:0};return a==null&&M.vnode!=null&&M.vnode(o),o}function ce(e){return e.children}function Y(e,t){this.props=e,this.context=t}function X(e,t){if(t==null)return e.__?X(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type=="function"?X(e):null}function Re(e){var t,n;if((e=e.__)!=null&&e.__c!=null){for(e.__e=e.__c.base=null,t=0;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null){e.__e=e.__c.base=n.__e;break}return Re(e)}}function Ee(e){(!e.__d&&(e.__d=!0)&&G.push(e)&&!ae.__r++||xe!=M.debounceRendering)&&((xe=M.debounceRendering)||He)(ae)}function ae(){for(var e,t,n,s,a,o,l,c=1;G.length;)G.length>c&&G.sort(Ae),e=G.shift(),c=G.length,e.__d&&(n=void 0,s=void 0,a=(s=(t=e).__v).__e,o=[],l=[],t.__P&&((n=q({},s)).__v=s.__v+1,M.vnode&&M.vnode(n),$e(t.__P,n,s,t.__n,t.__P.namespaceURI,32&s.__u?[a]:null,o,a??X(s),!!(32&s.__u),l),n.__v=s.__v,n.__.__k[n.__i]=n,Le(o,n,l),s.__e=s.__=null,n.__e!=a&&Re(n)));ae.__r=0}function Ue(e,t,n,s,a,o,l,c,_,i,u){var r,f,d,m,S,b,g,w=s&&s.__k||Oe,N=t.length;for(_=Et(n,t,w,_,N),r=0;r<N;r++)(d=n.__k[r])!=null&&(f=d.__i==-1?Z:w[d.__i]||Z,d.__i=r,b=$e(e,d,f,a,o,l,c,_,i,u),m=d.__e,d.ref&&f.ref!=d.ref&&(f.ref&&be(f.ref,null,d),u.push(d.ref,d.__c||m,d)),S==null&&m!=null&&(S=m),(g=!!(4&d.__u))||f.__k===d.__k?_=Fe(d,_,e,g):typeof d.type=="function"&&b!==void 0?_=b:m&&(_=m.nextSibling),d.__u&=-7);return n.__e=S,_}function Et(e,t,n,s,a){var o,l,c,_,i,u=n.length,r=u,f=0;for(e.__k=new Array(a),o=0;o<a;o++)(l=t[o])!=null&&typeof l!="boolean"&&typeof l!="function"?(typeof l=="string"||typeof l=="number"||typeof l=="bigint"||l.constructor==String?l=e.__k[o]=se(null,l,null,null,null):le(l)?l=e.__k[o]=se(ce,{children:l},null,null,null):l.constructor===void 0&&l.__b>0?l=e.__k[o]=se(l.type,l.props,l.key,l.ref?l.ref:null,l.__v):e.__k[o]=l,_=o+f,l.__=e,l.__b=e.__b+1,c=null,(i=l.__i=It(l,n,_,r))!=-1&&(r--,(c=n[i])&&(c.__u|=2)),c==null||c.__v==null?(i==-1&&(a>u?f--:a<u&&f++),typeof l.type!="function"&&(l.__u|=4)):i!=_&&(i==_-1?f--:i==_+1?f++:(i>_?f--:f++,l.__u|=4))):e.__k[o]=null;if(r)for(o=0;o<u;o++)(c=n[o])!=null&&!(2&c.__u)&&(c.__e==s&&(s=X(c)),Ke(c,c));return s}function Fe(e,t,n,s){var a,o;if(typeof e.type=="function"){for(a=e.__k,o=0;a&&o<a.length;o++)a[o]&&(a[o].__=e,t=Fe(a[o],t,n,s));return t}e.__e!=t&&(s&&(t&&e.type&&!t.parentNode&&(t=X(e)),n.insertBefore(e.__e,t||null)),t=e.__e);do t=t&&t.nextSibling;while(t!=null&&t.nodeType==8);return t}function It(e,t,n,s){var a,o,l,c=e.key,_=e.type,i=t[n],u=i!=null&&(2&i.__u)==0;if(i===null&&c==null||u&&c==i.key&&_==i.type)return n;if(s>(u?1:0)){for(a=n-1,o=n+1;a>=0||o<t.length;)if((i=t[l=a>=0?a--:o++])!=null&&!(2&i.__u)&&c==i.key&&_==i.type)return l}return-1}function Ie(e,t,n){t[0]=="-"?e.setProperty(t,n??""):e[t]=n==null?"":typeof n!="number"||xt.test(t)?n:n+"px"}function oe(e,t,n,s,a){var o,l;e:if(t=="style")if(typeof n=="string")e.style.cssText=n;else{if(typeof s=="string"&&(e.style.cssText=s=""),s)for(t in s)n&&t in n||Ie(e.style,t,"");if(n)for(t in n)s&&n[t]==s[t]||Ie(e.style,t,n[t])}else if(t[0]=="o"&&t[1]=="n")o=t!=(t=t.replace(Ne,"$1")),l=t.toLowerCase(),t=l in e||t=="onFocusOut"||t=="onFocusIn"?l.slice(2):t.slice(2),e.l||(e.l={}),e.l[t+o]=n,n?s?n.u=s.u:(n.u=ve,e.addEventListener(t,o?ye:me,o)):e.removeEventListener(t,o?ye:me,o);else{if(a=="http://www.w3.org/2000/svg")t=t.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(t!="width"&&t!="height"&&t!="href"&&t!="list"&&t!="form"&&t!="tabIndex"&&t!="download"&&t!="rowSpan"&&t!="colSpan"&&t!="role"&&t!="popover"&&t in e)try{e[t]=n??"";break e}catch{}typeof n=="function"||(n==null||n===!1&&t[4]!="-"?e.removeAttribute(t):e.setAttribute(t,t=="popover"&&n==1?"":n))}}function Pe(e){return function(t){if(this.l){var n=this.l[t.type+e];if(t.t==null)t.t=ve++;else if(t.t<n.u)return;return n(M.event?M.event(t):t)}}}function $e(e,t,n,s,a,o,l,c,_,i){var u,r,f,d,m,S,b,g,w,N,A,j,O,L,W,F,p,$=t.type;if(t.constructor!==void 0)return null;128&n.__u&&(_=!!(32&n.__u),o=[c=t.__e=n.__e]),(u=M.__b)&&u(t);e:if(typeof $=="function")try{if(g=t.props,w="prototype"in $&&$.prototype.render,N=(u=$.contextType)&&s[u.__c],A=u?N?N.props.value:u.__:s,n.__c?b=(r=t.__c=n.__c).__=r.__E:(w?t.__c=r=new $(g,A):(t.__c=r=new Y(g,A),r.constructor=$,r.render=Dt),N&&N.sub(r),r.state||(r.state={}),r.__n=s,f=r.__d=!0,r.__h=[],r._sb=[]),w&&r.__s==null&&(r.__s=r.state),w&&$.getDerivedStateFromProps!=null&&(r.__s==r.state&&(r.__s=q({},r.__s)),q(r.__s,$.getDerivedStateFromProps(g,r.__s))),d=r.props,m=r.state,r.__v=t,f)w&&$.getDerivedStateFromProps==null&&r.componentWillMount!=null&&r.componentWillMount(),w&&r.componentDidMount!=null&&r.__h.push(r.componentDidMount);else{if(w&&$.getDerivedStateFromProps==null&&g!==d&&r.componentWillReceiveProps!=null&&r.componentWillReceiveProps(g,A),t.__v==n.__v||!r.__e&&r.shouldComponentUpdate!=null&&r.shouldComponentUpdate(g,r.__s,A)===!1){for(t.__v!=n.__v&&(r.props=g,r.state=r.__s,r.__d=!1),t.__e=n.__e,t.__k=n.__k,t.__k.some(function(E){E&&(E.__=t)}),j=0;j<r._sb.length;j++)r.__h.push(r._sb[j]);r._sb=[],r.__h.length&&l.push(r);break e}r.componentWillUpdate!=null&&r.componentWillUpdate(g,r.__s,A),w&&r.componentDidUpdate!=null&&r.__h.push(function(){r.componentDidUpdate(d,m,S)})}if(r.context=A,r.props=g,r.__P=e,r.__e=!1,O=M.__r,L=0,w){for(r.state=r.__s,r.__d=!1,O&&O(t),u=r.render(r.props,r.state,r.context),W=0;W<r._sb.length;W++)r.__h.push(r._sb[W]);r._sb=[]}else do r.__d=!1,O&&O(t),u=r.render(r.props,r.state,r.context),r.state=r.__s;while(r.__d&&++L<25);r.state=r.__s,r.getChildContext!=null&&(s=q(q({},s),r.getChildContext())),w&&!f&&r.getSnapshotBeforeUpdate!=null&&(S=r.getSnapshotBeforeUpdate(d,m)),F=u,u!=null&&u.type===ce&&u.key==null&&(F=We(u.props.children)),c=Ue(e,le(F)?F:[F],t,n,s,a,o,l,c,_,i),r.base=t.__e,t.__u&=-161,r.__h.length&&l.push(r),b&&(r.__E=r.__=null)}catch(E){if(t.__v=null,_||o!=null)if(E.then){for(t.__u|=_?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;o[o.indexOf(c)]=null,t.__e=c}else{for(p=o.length;p--;)we(o[p]);ge(t)}else t.__e=n.__e,t.__k=n.__k,E.then||ge(t);M.__e(E,t,n)}else o==null&&t.__v==n.__v?(t.__k=n.__k,t.__e=n.__e):c=t.__e=Pt(n.__e,t,n,s,a,o,l,_,i);return(u=M.diffed)&&u(t),128&t.__u?void 0:c}function ge(e){e&&e.__c&&(e.__c.__e=!0),e&&e.__k&&e.__k.forEach(ge)}function Le(e,t,n){for(var s=0;s<n.length;s++)be(n[s],n[++s],n[++s]);M.__c&&M.__c(t,e),e.some(function(a){try{e=a.__h,a.__h=[],e.some(function(o){o.call(a)})}catch(o){M.__e(o,a.__v)}})}function We(e){return typeof e!="object"||e==null||e.__b&&e.__b>0?e:le(e)?e.map(We):q({},e)}function Pt(e,t,n,s,a,o,l,c,_){var i,u,r,f,d,m,S,b=n.props||Z,g=t.props,w=t.type;if(w=="svg"?a="http://www.w3.org/2000/svg":w=="math"?a="http://www.w3.org/1998/Math/MathML":a||(a="http://www.w3.org/1999/xhtml"),o!=null){for(i=0;i<o.length;i++)if((d=o[i])&&"setAttribute"in d==!!w&&(w?d.localName==w:d.nodeType==3)){e=d,o[i]=null;break}}if(e==null){if(w==null)return document.createTextNode(g);e=document.createElementNS(a,w,g.is&&g),c&&(M.__m&&M.__m(t,o),c=!1),o=null}if(w==null)b===g||c&&e.data==g||(e.data=g);else{if(o=o&&re.call(e.childNodes),!c&&o!=null)for(b={},i=0;i<e.attributes.length;i++)b[(d=e.attributes[i]).name]=d.value;for(i in b)if(d=b[i],i!="children"){if(i=="dangerouslySetInnerHTML")r=d;else if(!(i in g)){if(i=="value"&&"defaultValue"in g||i=="checked"&&"defaultChecked"in g)continue;oe(e,i,null,d,a)}}for(i in g)d=g[i],i=="children"?f=d:i=="dangerouslySetInnerHTML"?u=d:i=="value"?m=d:i=="checked"?S=d:c&&typeof d!="function"||b[i]===d||oe(e,i,d,b[i],a);if(u)c||r&&(u.__html==r.__html||u.__html==e.innerHTML)||(e.innerHTML=u.__html),t.__k=[];else if(r&&(e.innerHTML=""),Ue(t.type=="template"?e.content:e,le(f)?f:[f],t,n,s,w=="foreignObject"?"http://www.w3.org/1999/xhtml":a,o,l,o?o[0]:n.__k&&X(n,0),c,_),o!=null)for(i=o.length;i--;)we(o[i]);c||(i="value",w=="progress"&&m==null?e.removeAttribute("value"):m!=null&&(m!==e[i]||w=="progress"&&!m||w=="option"&&m!=b[i])&&oe(e,i,m,b[i],a),i="checked",S!=null&&S!=e[i]&&oe(e,i,S,b[i],a))}return e}function be(e,t,n){try{if(typeof e=="function"){var s=typeof e.__u=="function";s&&e.__u(),s&&t==null||(e.__u=e(t))}else e.current=t}catch(a){M.__e(a,n)}}function Ke(e,t,n){var s,a;if(M.unmount&&M.unmount(e),(s=e.ref)&&(s.current&&s.current!=e.__e||be(s,null,t)),(s=e.__c)!=null){if(s.componentWillUnmount)try{s.componentWillUnmount()}catch(o){M.__e(o,t)}s.base=s.__P=null}if(s=e.__k)for(a=0;a<s.length;a++)s[a]&&Ke(s[a],t,n||typeof e.type!="function");n||we(e.__e),e.__c=e.__=e.__e=void 0}function Dt(e,t,n){return this.constructor(e,n)}function ue(e,t,n){var s,a,o,l;t==document&&(t=document.documentElement),M.__&&M.__(e,t),a=(s=typeof n=="function")?null:n&&n.__k||t.__k,o=[],l=[],$e(t,e=(!s&&n||t).__k=ie(ce,null,[e]),a||Z,Z,t.namespaceURI,!s&&n?[n]:a?null:t.firstChild?re.call(t.childNodes):null,o,!s&&n?n:a?a.__e:t.firstChild,s,l),Le(o,e,l)}re=Oe.slice,M={__e:function(e,t,n,s){for(var a,o,l;t=t.__;)if((a=t.__c)&&!a.__)try{if((o=a.constructor)&&o.getDerivedStateFromError!=null&&(a.setState(o.getDerivedStateFromError(e)),l=a.__d),a.componentDidCatch!=null&&(a.componentDidCatch(e,s||{}),l=a.__d),l)return a.__E=a}catch(c){e=c}throw e}},De=0,Tt=function(e){return e!=null&&e.constructor===void 0},Y.prototype.setState=function(e,t){var n;n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=q({},this.state),typeof e=="function"&&(e=e(q({},n),this.props)),e&&q(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),Ee(this))},Y.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),Ee(this))},Y.prototype.render=ce,G=[],He=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,Ae=function(e,t){return e.__v.__b-t.__v.__b},ae.__r=0,Ne=/(PointerCapture)$|Capture$/i,ve=0,me=Pe(!1),ye=Pe(!0),Mt=0;var je=function(e,t,n,s){var a;t[0]=0;for(var o=1;o<t.length;o++){var l=t[o++],c=t[o]?(t[0]|=l?1:2,n[t[o++]]):t[++o];l===3?s[0]=c:l===4?s[1]=Object.assign(s[1]||{},c):l===5?(s[1]=s[1]||{})[t[++o]]=c:l===6?s[1][t[++o]]+=c+"":l?(a=e.apply(c,je(e,c,n,["",null])),s.push(a),c[0]?t[0]|=2:(t[o-2]=0,t[o]=a)):s.push(c)}return s},Be=new Map;function Je(e){var t=Be.get(this);return t||(t=new Map,Be.set(this,t)),(t=je(this,t.get(e)||(t.set(e,t=function(n){for(var s,a,o=1,l="",c="",_=[0],i=function(f){o===1&&(f||(l=l.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?_.push(0,f,l):o===3&&(f||l)?(_.push(3,f,l),o=2):o===2&&l==="..."&&f?_.push(4,f,0):o===2&&l&&!f?_.push(5,0,!0,l):o>=5&&((l||!f&&o===5)&&(_.push(o,0,l,a),o=6),f&&(_.push(o,f,0,a),o=6)),l=""},u=0;u<n.length;u++){u&&(o===1&&i(),i(u));for(var r=0;r<n[u].length;r++)s=n[u][r],o===1?s==="<"?(i(),_=[_],o=3):l+=s:o===4?l==="--"&&s===">"?(o=1,l=""):l=s+l[0]:c?s===c?c="":l+=s:s==='"'||s==="'"?c=s:s===">"?(i(),o=1):o&&(s==="="?(o=5,a=l,l=""):s==="/"&&(o<5||n[u][r+1]===">")?(i(),o===3&&(_=_[0]),o=_,(_=_[0]).push(2,0,o),o=0):s===" "||s===" "||s===`
2
+ `||s==="\r"?(i(),o=2):l+=s),o===3&&l==="!--"&&(o=4,_=_[0])}return i(),_}(e)),t),arguments,[])).length>1?t:t[0]}var y=Je.bind(ie);var ee,P,ke,Ve,te=0,et=[],D=M,qe=D.__b,ze=D.__r,Ge=D.diffed,Xe=D.__c,Qe=D.unmount,Ye=D.__;function Se(e,t){D.__h&&D.__h(P,e,te||t),te=0;var n=P.__H||(P.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function C(e){return te=1,Ht(nt,e)}function Ht(e,t,n){var s=Se(ee++,2);if(s.t=e,!s.__c&&(s.__=[n?n(t):nt(void 0,t),function(c){var _=s.__N?s.__N[0]:s.__[0],i=s.t(_,c);_!==i&&(s.__N=[i,s.__[1]],s.__c.setState({}))}],s.__c=P,!P.__f)){var a=function(c,_,i){if(!s.__c.__H)return!0;var u=s.__c.__H.__.filter(function(f){return!!f.__c});if(u.every(function(f){return!f.__N}))return!o||o.call(this,c,_,i);var r=s.__c.props!==c;return u.forEach(function(f){if(f.__N){var d=f.__[0];f.__=f.__N,f.__N=void 0,d!==f.__[0]&&(r=!0)}}),o&&o.call(this,c,_,i)||r};P.__f=!0;var o=P.shouldComponentUpdate,l=P.componentWillUpdate;P.componentWillUpdate=function(c,_,i){if(this.__e){var u=o;o=void 0,a(c,_,i),o=u}l&&l.call(this,c,_,i)},P.shouldComponentUpdate=a}return s.__N||s.__}function R(e,t){var n=Se(ee++,3);!D.__s&&tt(n.__H,t)&&(n.__=e,n.u=t,P.__H.__h.push(n))}function z(e){return te=5,ne(function(){return{current:e}},[])}function ne(e,t){var n=Se(ee++,7);return tt(n.__H,t)&&(n.__=e(),n.__H=t,n.__h=e),n.__}function U(e,t){return te=8,ne(function(){return e},t)}function At(){for(var e;e=et.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(_e),e.__H.__h.forEach(Ce),e.__H.__h=[]}catch(t){e.__H.__h=[],D.__e(t,e.__v)}}D.__b=function(e){P=null,qe&&qe(e)},D.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),Ye&&Ye(e,t)},D.__r=function(e){ze&&ze(e),ee=0;var t=(P=e.__c).__H;t&&(ke===P?(t.__h=[],P.__h=[],t.__.forEach(function(n){n.__N&&(n.__=n.__N),n.u=n.__N=void 0})):(t.__h.forEach(_e),t.__h.forEach(Ce),t.__h=[],ee=0)),ke=P},D.diffed=function(e){Ge&&Ge(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(et.push(t)!==1&&Ve===D.requestAnimationFrame||((Ve=D.requestAnimationFrame)||Nt)(At)),t.__H.__.forEach(function(n){n.u&&(n.__H=n.u),n.u=void 0})),ke=P=null},D.__c=function(e,t){t.some(function(n){try{n.__h.forEach(_e),n.__h=n.__h.filter(function(s){return!s.__||Ce(s)})}catch(s){t.some(function(a){a.__h&&(a.__h=[])}),t=[],D.__e(s,n.__v)}}),Xe&&Xe(e,t)},D.unmount=function(e){Qe&&Qe(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.forEach(function(s){try{_e(s)}catch(a){t=a}}),n.__H=void 0,t&&D.__e(t,n.__v))};var Ze=typeof requestAnimationFrame=="function";function Nt(e){var t,n=function(){clearTimeout(s),Ze&&cancelAnimationFrame(t),setTimeout(e)},s=setTimeout(n,35);Ze&&(t=requestAnimationFrame(n))}function _e(e){var t=P,n=e.__c;typeof n=="function"&&(e.__c=void 0,n()),P=t}function Ce(e){var t=P;e.__c=e.__(),P=t}function tt(e,t){return!e||e.length!==t.length||t.some(function(n,s){return n!==e[s]})}function nt(e,t){return typeof t=="function"?t(e):t}function de(){return"msg-"+Date.now()+"-"+Math.random().toString(36).substr(2,9)}function H(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}function ot(e){if(!e)return"";try{let t=new Date(e),s=new Date-t,a=Math.floor(s/6e4),o=Math.floor(s/36e5),l=Math.floor(s/864e5);return a<1?"Just now":a<60?`${a}m ago`:o<24?`${o}h ago`:l<7?`${l}d ago`:t.toLocaleDateString()}catch{return""}}function st(e,t=null){if(t)return t(e);let n=H(e);return n=n.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),n=n.replace(/__(.+?)__/g,"<strong>$1</strong>"),n=n.replace(/\*(.+?)\*/g,"<em>$1</em>"),n=n.replace(/_(.+?)_/g,"<em>$1</em>"),n=n.replace(/`(.+?)`/g,"<code>$1</code>"),n=n.replace(/\[(.+?)\]\((.+?)\)/g,'<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'),n=n.replace(/\n/g,"<br>"),n}function at(e=""){let t=n=>e?`${n}_${e}`:n;return{get(n){try{return localStorage.getItem(t(n))}catch{return null}},set(n,s){try{let a=t(n);s===null?localStorage.removeItem(a):localStorage.setItem(a,s)}catch{}}}}function rt(e="csrftoken"){let t=document.cookie.split(";");for(let s of t){let[a,o]=s.trim().split("=");if(a===e)return decodeURIComponent(o)}let n=document.querySelector('meta[name="csrf-token"]');return n?n.getAttribute("content"):null}function lt({config:e,debugMode:t,isExpanded:n,isSpeaking:s,messagesCount:a,isLoading:o,currentAgent:l,onClose:c,onToggleExpand:_,onToggleDebug:i,onToggleTTS:u,onClear:r,onToggleSidebar:f}){let{title:d,primaryColor:m,embedded:S,showConversationSidebar:b,showClearButton:g,showDebugButton:w,enableDebugMode:N,showTTSButton:A,showExpandButton:j,enableTTS:O,elevenLabsApiKey:L,ttsProxyUrl:W}=e,F=L||W;return y`
3
+ <div class="cw-header" style=${{backgroundColor:m}}>
4
+ ${b&&y`
5
+ <button
6
+ class="cw-header-btn cw-hamburger"
7
+ onClick=${f}
8
+ title="Conversations"
9
+ >
10
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16">
11
+ <line x1="3" y1="6" x2="21" y2="6"></line>
12
+ <line x1="3" y1="12" x2="21" y2="12"></line>
13
+ <line x1="3" y1="18" x2="21" y2="18"></line>
14
+ </svg>
15
+ </button>
16
+ `}
17
+
18
+ <div class="cw-title-container">
19
+ <span class="cw-title">${H(d)}</span>
20
+ ${l&&y`
21
+ <span class="cw-current-agent" title="Currently active agent">
22
+ <span class="cw-agent-indicator">🤖</span>
23
+ <span class="cw-agent-name">${H(l.name||l.key)}</span>
24
+ </span>
25
+ `}
957
26
  </div>
958
- `;
959
- }
960
-
961
- function renderVoiceSettings() {
962
- if (!state.voiceSettingsOpen) return '';
963
-
964
- const voiceOptions = (role) => {
965
- if (config.availableVoices.length === 0) {
966
- return '<option value="">Loading voices...</option>';
967
- }
968
- return config.availableVoices.map(voice => `
969
- <option value="${voice.voice_id}" ${config.ttsVoices[role] === voice.voice_id ? 'selected' : ''}>
970
- ${escapeHtml(voice.name)}
971
- </option>
972
- `).join('');
973
- };
974
-
975
- return `
976
- <div class="cw-voice-settings">
977
- <div class="cw-voice-settings-header">
978
- <span>🎙️ Voice Settings</span>
979
- <button class="cw-voice-settings-close" data-action="toggle-voice-settings">✕</button>
980
- </div>
981
- <div class="cw-voice-settings-content">
982
- <div class="cw-voice-setting">
983
- <label>Assistant Voice</label>
984
- <select class="cw-voice-select" data-role="assistant" onchange="ChatWidget.setVoice('assistant', this.value)">
985
- ${voiceOptions('assistant')}
986
- </select>
987
- </div>
988
- <div class="cw-voice-setting">
989
- <label>Customer Voice (Demo)</label>
990
- <select class="cw-voice-select" data-role="user" onchange="ChatWidget.setVoice('user', this.value)">
991
- ${voiceOptions('user')}
992
- </select>
993
- </div>
994
- </div>
27
+
28
+ <div class="cw-header-actions">
29
+ ${g&&y`
30
+ <button
31
+ class="cw-header-btn"
32
+ onClick=${r}
33
+ title="Clear"
34
+ disabled=${o||a===0}
35
+ >🗑️</button>
36
+ `}
37
+
38
+ ${w&&N&&y`
39
+ <button
40
+ class="cw-header-btn ${t?"cw-btn-active":""}"
41
+ onClick=${i}
42
+ title="Debug"
43
+ >🐛</button>
44
+ `}
45
+
46
+ ${A&&F&&y`
47
+ <button
48
+ class="cw-header-btn ${O?"cw-btn-active":""}"
49
+ onClick=${u}
50
+ title="TTS"
51
+ >${O?"\u{1F50A}":"\u{1F507}"}</button>
52
+ `}
53
+
54
+ ${j&&!S&&y`
55
+ <button
56
+ class="cw-header-btn"
57
+ onClick=${_}
58
+ title=${n?"Minimize":"Expand"}
59
+ >${n?"\u2296":"\u2295"}</button>
60
+ `}
61
+
62
+ ${!S&&y`
63
+ <button
64
+ class="cw-header-btn"
65
+ onClick=${c}
66
+ title="Close"
67
+ >✕</button>
68
+ `}
995
69
  </div>
996
- `;
997
- }
998
-
999
- function renderJourneyDropdown() {
1000
- if (!config.enableAutoRun || Object.keys(config.journeyTypes).length === 0) {
1001
- return '';
1002
- }
1003
-
1004
- const journeyItems = Object.entries(config.journeyTypes).map(([key, journey]) => `
1005
- <button class="cw-dropdown-item" data-journey="${key}">
1006
- ${escapeHtml(journey.label)}
1007
- </button>
1008
- `).join('');
1009
-
1010
- const controlsSection = state.autoRunActive ? `
1011
- <div class="cw-dropdown-separator"></div>
1012
- <div class="cw-dropdown-label">Demo Controls</div>
1013
- <div class="cw-autorun-controls">
1014
- <label class="cw-control-label">
1015
- <input type="radio" name="autorun-mode" value="automatic"
1016
- ${config.autoRunMode === 'automatic' ? 'checked' : ''}
1017
- onchange="ChatWidget.setAutoRunMode('automatic')">
1018
- <span>⚡ Automatic</span>
1019
- </label>
1020
- <label class="cw-control-label">
1021
- <input type="radio" name="autorun-mode" value="confirm"
1022
- ${config.autoRunMode === 'confirm' ? 'checked' : ''}
1023
- onchange="ChatWidget.setAutoRunMode('confirm')">
1024
- <span>👆 Confirm Next</span>
1025
- </label>
1026
- <label class="cw-control-label">
1027
- <input type="radio" name="autorun-mode" value="manual"
1028
- ${config.autoRunMode === 'manual' ? 'checked' : ''}
1029
- onchange="ChatWidget.setAutoRunMode('manual')">
1030
- <span>✋ Manual</span>
1031
- </label>
70
+ </div>
71
+ `}function Te({msg:e,show:t,onToggle:n}){return t?y`
72
+ <div class="cw-debug-payload">
73
+ <button class="cw-debug-payload-close" onClick=${n}>×</button>
74
+ <pre class="cw-debug-payload-content">${JSON.stringify(e,null,2)}</pre>
75
+ </div>
76
+ `:y`
77
+ <button
78
+ class="cw-debug-payload-btn"
79
+ onClick=${n}
80
+ title="Show message payload"
81
+ >{ }</button>
82
+ `}function it({msg:e,debugMode:t,markdownParser:n}){let[s,a]=C(!1),[o,l]=C(!1),c=e.role==="user",_=e.role==="system",i=e.type==="tool_call",u=e.type==="tool_result",r=e.type==="error",f=e.type==="sub_agent_start",d=e.type==="sub_agent_end",m=e.type==="agent_context";if(_&&!t)return null;if(f||d||m)return y`
83
+ <div class="cw-agent-context ${f?"cw-agent-delegating":""} ${d?"cw-agent-returned":""}" style="position: relative;">
84
+ <span class="cw-agent-context-icon">${f?"\u{1F517}":d?"\u2713":"\u{1F916}"}</span>
85
+ <span class="cw-agent-context-text">${e.content}</span>
86
+ ${e.metadata?.agentName&&y`
87
+ <span class="cw-agent-context-name">${e.metadata.agentName}</span>
88
+ `}
89
+ ${t&&y`<${Te} msg=${e} show=${o} onToggle=${()=>l(!o)} />`}
1032
90
  </div>
1033
- ${config.autoRunMode === 'automatic' ? `
1034
- <div class="cw-delay-control">
1035
- <label class="cw-control-label">
1036
- <span>Delay: ${config.autoRunDelay}ms</span>
1037
- <input type="range" min="0" max="5000" step="100"
1038
- value="${config.autoRunDelay}"
1039
- oninput="ChatWidget.setAutoRunDelay(this.value)">
1040
- </label>
1041
- </div>
1042
- ` : ''}
1043
- <div class="cw-dropdown-separator"></div>
1044
- <button class="cw-dropdown-item cw-dropdown-item-danger" data-action="stop-autorun">
1045
- ⏹️ Stop Demo
1046
- </button>
1047
- ` : '';
1048
-
1049
- return `
1050
- <div class="cw-dropdown">
1051
- <button class="cw-header-btn ${state.autoRunActive ? 'cw-btn-active' : ''}"
1052
- data-action="toggle-journey-dropdown"
1053
- title="Demo Flows"
1054
- ${state.isLoading || state.isSimulating ? 'disabled' : ''}>
1055
- ${state.isSimulating ? '<span class="cw-spinner"></span>' : '▶'}
1056
- </button>
1057
- <div class="cw-dropdown-menu cw-dropdown-hidden" id="cw-journey-dropdown">
1058
- <div class="cw-dropdown-label">Demo Flows</div>
1059
- <div class="cw-dropdown-separator"></div>
1060
- ${journeyItems}
1061
- ${controlsSection}
1062
- </div>
91
+ `;if(i||u){let w=e.metadata?.arguments||e.metadata?.result,N=A=>{if(typeof A=="string")try{return JSON.stringify(JSON.parse(A),null,2)}catch{return A}return JSON.stringify(A,null,2)};return y`
92
+ <div class="cw-tool-message ${u?"cw-tool-result":"cw-tool-call"}" style="position: relative;">
93
+ <span class="cw-tool-label" onClick=${()=>w&&a(!s)}>
94
+ ${e.content}
95
+ ${w&&y`<span class="cw-tool-expand">${s?"\u25BC":"\u25B6"}</span>`}
96
+ </span>
97
+ ${s&&w&&y`
98
+ <pre class="cw-tool-details">${H(N(i?e.metadata.arguments:e.metadata.result))}</pre>
99
+ `}
100
+ ${t&&y`<${Te} msg=${e} show=${o} onToggle=${()=>l(!o)} />`}
1063
101
  </div>
1064
- `;
1065
- }
1066
-
1067
- function render() {
1068
- if (!container) return;
1069
-
1070
- // Render FAB (floating action button)
1071
- if (!state.isOpen) {
1072
- container.innerHTML = `
1073
- <button class="cw-fab" style="background-color: ${config.primaryColor}">
1074
- <svg class="cw-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1075
- <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1076
- </svg>
1077
- </button>
1078
- `;
1079
- container.querySelector('.cw-fab').addEventListener('click', openWidget);
1080
- return;
1081
- }
1082
-
1083
- // Render chat widget
1084
- const messagesHtml = state.messages.length === 0
1085
- ? `
102
+ `}let S=["cw-message",c&&"cw-message-user",r&&"cw-message-error"].filter(Boolean).join(" "),b=`cw-message-row ${c?"cw-message-row-user":""}`,g=e.role==="assistant"?st(e.content,n):H(e.content);return y`
103
+ <div class=${b} style="position: relative;">
104
+ <div class=${S} dangerouslySetInnerHTML=${{__html:g}} />
105
+ ${t&&y`<${Te} msg=${e} show=${o} onToggle=${()=>l(!o)} />`}
106
+ </div>
107
+ `}function ct({messages:e,isLoading:t,hasMoreMessages:n,loadingMoreMessages:s,onLoadMore:a,debugMode:o,markdownParser:l,emptyStateTitle:c,emptyStateMessage:_}){let i=z(null),u=z(!0),r=d=>{let m=d.target,S=m.scrollHeight-m.scrollTop-m.clientHeight<100;if(u.current=S,m.scrollTop<50&&n&&!s){let b=m.scrollHeight;a().then(()=>{let g=m.scrollHeight;m.scrollTop=g-b+m.scrollTop})}};R(()=>{let d=i.current;d&&u.current&&requestAnimationFrame(()=>{d.scrollTop=d.scrollHeight})},[e,t]),R(()=>{let d=i.current;d&&e.length<=2&&(u.current=!0,requestAnimationFrame(()=>{d.scrollTop=d.scrollHeight}))},[e.length]);let f=e.length===0;return y`
108
+ <div class="cw-messages" ref=${i} onScroll=${r}>
109
+ ${f&&y`
1086
110
  <div class="cw-empty-state">
1087
111
  <svg class="cw-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1088
112
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
1089
113
  </svg>
1090
- <h3>${escapeHtml(config.emptyStateTitle)}</h3>
1091
- <p>${escapeHtml(config.emptyStateMessage)}</p>
114
+ <h3>${H(c)}</h3>
115
+ <p>${H(_)}</p>
1092
116
  </div>
1093
- `
1094
- : state.messages.map(renderMessage).join('');
1095
-
1096
- const typingIndicator = state.isLoading ? `
1097
- <div class="cw-message-row">
1098
- <div class="cw-typing">
1099
- <span class="cw-spinner"></span>
1100
- <span>Thinking...</span>
117
+ `}
118
+
119
+ ${!f&&n&&y`
120
+ <div class="cw-load-more" onClick=${a}>
121
+ ${s?y`<span class="cw-spinner"></span><span>Loading...</span>`:y`<span>↑ Scroll up or click to load older messages</span>`}
1101
122
  </div>
123
+ `}
124
+
125
+ ${e.map(d=>y`
126
+ <${it}
127
+ key=${d.id}
128
+ msg=${d}
129
+ debugMode=${o}
130
+ markdownParser=${l}
131
+ />
132
+ `)}
133
+
134
+ ${t&&y`
135
+ <div class="cw-message-row">
136
+ <div class="cw-typing">
137
+ <span class="cw-spinner"></span>
138
+ <span>Thinking...</span>
139
+ </div>
140
+ </div>
141
+ `}
142
+ </div>
143
+ `}function ut({onSend:e,onCancel:t,isLoading:n,placeholder:s,primaryColor:a}){let[o,l]=C(""),[c,_]=C(!1),i=z(null);R(()=>{!n&&i.current&&i.current.focus()},[n]),R(()=>{i.current&&(i.current.style.height="auto",i.current.style.height=Math.min(i.current.scrollHeight,150)+"px")},[o]);let u=m=>{m.preventDefault(),o.trim()&&!n&&(e(o),l(""),i.current&&(i.current.style.height="auto"))},r=m=>{m.key==="Enter"&&!m.shiftKey&&(m.preventDefault(),u(m))},f=m=>{n&&t&&(m.preventDefault(),t())},d=y`
144
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor">
145
+ <rect x="2" y="2" width="10" height="10" rx="1" />
146
+ </svg>
147
+ `;return y`
148
+ <form class="cw-input-form" onSubmit=${u}>
149
+ <textarea
150
+ ref=${i}
151
+ class="cw-input"
152
+ placeholder=${H(s)}
153
+ value=${o}
154
+ onInput=${m=>l(m.target.value)}
155
+ onKeyDown=${r}
156
+ disabled=${n}
157
+ rows="1"
158
+ />
159
+ <button
160
+ type=${n?"button":"submit"}
161
+ class=${`cw-send-btn ${n?"cw-send-btn-loading":""} ${n&&c?"cw-send-btn-stop":""}`}
162
+ style=${{backgroundColor:n&&c?"#dc2626":a}}
163
+ onClick=${f}
164
+ onMouseEnter=${()=>_(!0)}
165
+ onMouseLeave=${()=>_(!1)}
166
+ title=${n?"Stop":"Send"}
167
+ >
168
+ ${n?c?d:y`<span class="cw-spinner"></span>`:"\u27A4"}
169
+ </button>
170
+ </form>
171
+ `}function _t({isOpen:e,conversations:t,conversationsLoading:n,currentConversationId:s,onClose:a,onNewConversation:o,onSwitchConversation:l}){return y`
172
+ <div class="cw-sidebar ${e?"cw-sidebar-open":""}">
173
+ <div class="cw-sidebar-header">
174
+ <span>Conversations</span>
175
+ <button class="cw-sidebar-close" onClick=${a}>✕</button>
1102
176
  </div>
1103
- ` : '';
1104
-
1105
- const continueButton = (state.autoRunActive && state.autoRunPaused && config.autoRunMode === 'confirm') ? `
1106
- <div class="cw-continue-bar">
1107
- <button class="cw-continue-btn" data-action="continue-autorun" style="background-color: ${config.primaryColor}">
1108
- ▶️ Continue Demo
1109
- </button>
1110
- </div>
1111
- ` : '';
1112
-
1113
- const statusBar = (state.autoRunActive || state.debugMode) ? `
1114
- <div class="cw-status-bar">
1115
- ${state.autoRunActive ? `<span>🤖 Demo: ${config.journeyTypes[state.journeyType]?.label || state.journeyType} (${config.autoRunMode})</span>` : ''}
1116
- ${state.debugMode ? '<span>🐛 Debug</span>' : ''}
177
+
178
+ <button class="cw-new-conversation" onClick=${o}>
179
+ <span>+ New Conversation</span>
180
+ </button>
181
+
182
+ <div class="cw-conversation-list">
183
+ ${n&&y`
184
+ <div class="cw-sidebar-loading">
185
+ <span class="cw-spinner"></span>
186
+ </div>
187
+ `}
188
+
189
+ ${!n&&t.length===0&&y`
190
+ <div class="cw-sidebar-empty">No conversations yet</div>
191
+ `}
192
+
193
+ ${t.map(c=>y`
194
+ <div
195
+ key=${c.id}
196
+ class="cw-conversation-item ${c.id===s?"cw-conversation-active":""}"
197
+ onClick=${()=>l(c.id)}
198
+ >
199
+ <div class="cw-conversation-title">${H(c.title||"Untitled")}</div>
200
+ <div class="cw-conversation-date">${ot(c.updatedAt||c.createdAt)}</div>
201
+ </div>
202
+ `)}
1117
203
  </div>
1118
- ` : '';
1119
-
1120
- const errorBar = state.error ? `
1121
- <div class="cw-error-bar">${escapeHtml(state.error)}</div>
1122
- ` : '';
1123
-
1124
- container.innerHTML = `
1125
- <div class="cw-widget ${state.isExpanded ? 'cw-widget-expanded' : ''}" style="--cw-primary: ${config.primaryColor}">
1126
- <div class="cw-header" style="background-color: ${config.primaryColor}">
1127
- <span class="cw-title">${escapeHtml(config.title)}</span>
1128
- <div class="cw-header-actions">
1129
- ${config.showClearButton ? `
1130
- <button class="cw-header-btn" data-action="clear" title="Clear Conversation" ${state.isLoading || state.messages.length === 0 ? 'disabled' : ''}>
1131
- <svg class="cw-icon-sm" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1132
- <polyline points="3 6 5 6 21 6"></polyline>
1133
- <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>
1134
- </svg>
1135
- </button>
1136
- ` : ''}
1137
- ${config.showDebugButton && config.enableDebugMode ? `
1138
- <button class="cw-header-btn ${state.debugMode ? 'cw-btn-active' : ''}" data-action="toggle-debug" title="${state.debugMode ? 'Hide Debug Info' : 'Show Debug Info'}">
1139
- <svg class="cw-icon-sm ${state.debugMode ? 'cw-icon-warning' : ''}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
1140
- <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>
1141
- <circle cx="12" cy="12" r="3"></circle>
1142
- </svg>
1143
- </button>
1144
- ` : ''}
1145
- ${config.showTTSButton && (config.elevenLabsApiKey || config.ttsProxyUrl) ? `
1146
- <button class="cw-header-btn ${config.enableTTS ? 'cw-btn-active' : ''} ${state.isSpeaking ? 'cw-btn-speaking' : ''}"
1147
- data-action="toggle-tts"
1148
- title="${config.enableTTS ? (state.isSpeaking ? 'Speaking...' : 'TTS Enabled') : 'TTS Disabled'}">
1149
- ${state.isSpeaking ? '🔊' : (config.enableTTS ? '🔉' : '🔇')}
1150
- </button>
1151
- ` : ''}
1152
- ${config.showVoiceSettings && (config.elevenLabsApiKey || config.ttsProxyUrl) ? `
1153
- <button class="cw-header-btn ${state.voiceSettingsOpen ? 'cw-btn-active' : ''}" data-action="toggle-voice-settings" title="Voice Settings">
1154
- 🎙️
1155
- </button>
1156
- ` : ''}
1157
- ${renderJourneyDropdown()}
1158
- ${config.showExpandButton ? `
1159
- <button class="cw-header-btn" data-action="toggle-expand" title="${state.isExpanded ? 'Minimize' : 'Expand'}">
1160
- ${state.isExpanded ? '⊖' : '⊕'}
1161
- </button>
1162
- ` : ''}
1163
- <button class="cw-header-btn" data-action="close" title="Close">
1164
-
204
+ </div>
205
+
206
+ <div
207
+ class="cw-sidebar-overlay ${e?"cw-sidebar-overlay-visible":""}"
208
+ onClick=${a}
209
+ />
210
+ `}function dt({availableModels:e,selectedModel:t,onSelectModel:n,disabled:s}){let[a,o]=C(!1);if(!e||e.length===0)return null;let c=e.find(u=>u.id===t)?.name||"Select Model",_=()=>{s||o(!a)},i=u=>{n(u),o(!1)};return y`
211
+ <div class="cw-model-selector">
212
+ <button
213
+ class="cw-model-btn"
214
+ onClick=${_}
215
+ disabled=${s}
216
+ title="Select Model"
217
+ >
218
+ <span class="cw-model-icon">🤖</span>
219
+ <span class="cw-model-name">${H(c)}</span>
220
+ <span class="cw-model-chevron">${a?"\u25B2":"\u25BC"}</span>
221
+ </button>
222
+
223
+ ${a&&y`
224
+ <div class="cw-model-dropdown">
225
+ ${e.map(u=>y`
226
+ <button
227
+ key=${u.id}
228
+ class="cw-model-option ${u.id===t?"cw-model-option-selected":""}"
229
+ onClick=${()=>i(u.id)}
230
+ >
231
+ <span class="cw-model-option-name">${H(u.name)}</span>
232
+ <span class="cw-model-option-provider">${H(u.provider)}</span>
233
+ ${u.description&&y`
234
+ <span class="cw-model-option-desc">${H(u.description)}</span>
235
+ `}
1165
236
  </button>
1166
- </div>
1167
- </div>
1168
- ${renderVoiceSettings()}
1169
- ${statusBar}
1170
- <div class="cw-messages" id="cw-messages">
1171
- ${messagesHtml}
1172
- ${typingIndicator}
237
+ `)}
1173
238
  </div>
1174
- ${continueButton}
1175
- ${errorBar}
1176
- <form class="cw-input-form" id="cw-input-form">
1177
- <input type="text" class="cw-input" placeholder="${escapeHtml(config.placeholder)}" ${state.isLoading ? 'disabled' : ''}>
1178
- <button type="submit" class="cw-send-btn" style="background-color: ${config.primaryColor}" ${state.isLoading ? 'disabled' : ''}>
1179
- ${state.isLoading ? '<span class="cw-spinner"></span>' : '➤'}
1180
- </button>
1181
- </form>
1182
- </div>
1183
- `;
1184
-
1185
- // Attach event listeners
1186
- attachEventListeners();
1187
-
1188
- // Scroll to bottom
1189
- const messagesEl = document.getElementById('cw-messages');
1190
- if (messagesEl) {
1191
- messagesEl.scrollTop = messagesEl.scrollHeight;
1192
- }
1193
-
1194
- // Focus input field
1195
- const inputEl = container.querySelector('.cw-input');
1196
- if (inputEl && !state.isLoading) {
1197
- // Use setTimeout to ensure focus happens after render completes
1198
- setTimeout(() => inputEl.focus(), 0);
1199
- }
1200
- }
1201
-
1202
- function attachEventListeners() {
1203
- // Header buttons
1204
- container.querySelectorAll('[data-action]').forEach(btn => {
1205
- btn.addEventListener('click', (e) => {
1206
- e.preventDefault();
1207
- e.stopPropagation();
1208
- const action = btn.dataset.action;
1209
-
1210
- switch (action) {
1211
- case 'close': closeWidget(); break;
1212
- case 'toggle-expand': toggleExpand(); break;
1213
- case 'toggle-debug': toggleDebugMode(); break;
1214
- case 'toggle-tts': toggleTTS(); break;
1215
- case 'toggle-voice-settings': toggleVoiceSettings(); break;
1216
- case 'clear': clearMessages(); break;
1217
- case 'stop-autorun': stopAutoRun(); break;
1218
- case 'continue-autorun': continueAutoRun(); break;
1219
- case 'toggle-journey-dropdown':
1220
- const dropdown = document.getElementById('cw-journey-dropdown');
1221
- if (dropdown) {
1222
- dropdown.classList.toggle('cw-dropdown-hidden');
1223
- }
1224
- break;
1225
- }
1226
- });
1227
- });
1228
-
1229
- // Journey selection
1230
- container.querySelectorAll('[data-journey]').forEach(btn => {
1231
- btn.addEventListener('click', (e) => {
1232
- e.preventDefault();
1233
- const journeyType = btn.dataset.journey;
1234
- const dropdown = document.getElementById('cw-journey-dropdown');
1235
- if (dropdown) dropdown.classList.add('cw-dropdown-hidden');
1236
- startDemoFlow(journeyType);
1237
- });
1238
- });
1239
-
1240
- // Form submission
1241
- const form = document.getElementById('cw-input-form');
1242
- if (form) {
1243
- form.addEventListener('submit', (e) => {
1244
- e.preventDefault();
1245
- const input = form.querySelector('.cw-input');
1246
- if (input && input.value.trim()) {
1247
- sendMessage(input.value);
1248
- input.value = '';
1249
- // Keep focus on input after sending
1250
- input.focus();
1251
- }
1252
- });
1253
- }
1254
-
1255
- // Close dropdown when clicking outside
1256
- document.addEventListener('click', (e) => {
1257
- if (!e.target.closest('.cw-dropdown')) {
1258
- const dropdown = document.getElementById('cw-journey-dropdown');
1259
- if (dropdown) dropdown.classList.add('cw-dropdown-hidden');
1260
- }
1261
- });
1262
- }
1263
-
1264
- // ============================================================================
1265
- // Public API
1266
- // ============================================================================
1267
-
1268
- function init(userConfig = {}) {
1269
- // Deep merge apiPaths to allow partial overrides
1270
- const mergedApiPaths = {
1271
- ...DEFAULT_CONFIG.apiPaths,
1272
- ...(userConfig.apiPaths || {}),
1273
- };
1274
-
1275
- config = {
1276
- ...DEFAULT_CONFIG,
1277
- ...userConfig,
1278
- apiPaths: mergedApiPaths,
1279
- };
1280
- state.journeyType = config.defaultJourneyType;
1281
-
1282
- // Initialize auth token from config
1283
- if (config.authToken) {
1284
- state.authToken = config.authToken;
1285
- }
1286
-
1287
- // Restore conversation ID
1288
- state.conversationId = getStoredValue(config.conversationIdKey);
1289
-
1290
- // Create container
1291
- container = document.createElement('div');
1292
- container.id = 'chat-widget-container';
1293
- container.className = `cw-container cw-position-${config.position}`;
1294
- document.body.appendChild(container);
1295
-
1296
- // Initial render
1297
- render();
1298
-
1299
- // Fetch available voices if TTS is configured
1300
- if (config.elevenLabsApiKey || config.ttsProxyUrl) {
1301
- fetchAvailableVoices();
1302
- }
1303
-
1304
- console.log('[ChatWidget] Initialized with config:', config);
1305
- }
1306
-
1307
- function destroy() {
1308
- if (state.eventSource) {
1309
- state.eventSource.close();
1310
- }
1311
- if (container) {
1312
- container.remove();
1313
- container = null;
1314
- }
1315
- }
1316
-
1317
- function open() {
1318
- openWidget();
1319
- }
1320
-
1321
- function close() {
1322
- closeWidget();
1323
- }
1324
-
1325
- function send(message) {
1326
- if (!state.isOpen) {
1327
- openWidget();
1328
- }
1329
- sendMessage(message);
1330
- }
1331
-
1332
- /**
1333
- * Update authentication configuration
1334
- * @param {Object} authConfig - { strategy?: string, token?: string }
1335
- */
1336
- function setAuth(authConfig = {}) {
1337
- if (authConfig.strategy) {
1338
- config.authStrategy = authConfig.strategy;
1339
- }
1340
- if (authConfig.token !== undefined) {
1341
- config.authToken = authConfig.token;
1342
- state.authToken = authConfig.token;
1343
-
1344
- // For anonymous strategy, also persist to storage
1345
- if (getAuthStrategy() === 'anonymous' && authConfig.token) {
1346
- const storageKey = config.anonymousTokenKey || config.sessionTokenKey;
1347
- setStoredValue(storageKey, authConfig.token);
1348
- }
1349
- }
1350
- console.log('[ChatWidget] Auth updated:', { strategy: getAuthStrategy(), hasToken: !!state.authToken });
1351
- }
1352
-
1353
- /**
1354
- * Clear authentication
1355
- */
1356
- function clearAuth() {
1357
- config.authToken = null;
1358
- state.authToken = null;
1359
- state.sessionToken = null;
1360
-
1361
- // Clear from storage
1362
- const storageKey = config.anonymousTokenKey || config.sessionTokenKey;
1363
- setStoredValue(storageKey, null);
1364
-
1365
- console.log('[ChatWidget] Auth cleared');
1366
- }
1367
-
1368
- // Export public API
1369
- global.ChatWidget = {
1370
- init,
1371
- destroy,
1372
- open,
1373
- close,
1374
- send,
1375
- clearMessages,
1376
- startDemoFlow,
1377
- stopAutoRun,
1378
- continueAutoRun,
1379
- setAutoRunMode,
1380
- setAutoRunDelay,
1381
- toggleTTS,
1382
- stopSpeech,
1383
- setVoice,
1384
- setAuth,
1385
- clearAuth,
1386
- getState: () => ({ ...state }),
1387
- getConfig: () => ({ ...config }),
1388
- };
1389
-
1390
- })(typeof window !== 'undefined' ? window : this);
239
+ `}
240
+ </div>
241
+ `}function pt(e,t,n){let[s,a]=C([]),[o,l]=C(!1),[c,_]=C(null),[i,u]=C(()=>n?.get(e.conversationIdKey)||null),[r,f]=C(!1),[d,m]=C(!1),[S,b]=C(0),g=z(null),w=z(null);R(()=>{i&&n?.set(e.conversationIdKey,i)},[i,e.conversationIdKey,n]);let N=U(async(p,$,E)=>{g.current&&g.current.close();let I=e.apiPaths.runEvents.replace("{runId}",p),K=`${e.backendUrl}${I}`;$&&(K+=`?anonymous_token=${encodeURIComponent($)}`);let k=new EventSource(K);g.current=k;let B="";k.addEventListener("assistant.message",v=>{try{let h=JSON.parse(v.data);e.onEvent&&e.onEvent("assistant.message",h.payload);let x=h.payload.content;x&&(B+=x,a(V=>{let Q=V[V.length-1];return Q?.role==="assistant"&&Q.id.startsWith("assistant-stream-")?[...V.slice(0,-1),{...Q,content:B}]:[...V,{id:"assistant-stream-"+Date.now(),role:"assistant",content:B,timestamp:new Date,type:"message"}]}))}catch(h){console.error("[ChatWidget] Parse error:",h)}}),k.addEventListener("tool.call",v=>{try{let h=JSON.parse(v.data);e.onEvent&&e.onEvent("tool.call",h.payload),a(x=>[...x,{id:"tool-call-"+Date.now(),role:"assistant",content:`\u{1F527} ${h.payload.name}`,timestamp:new Date,type:"tool_call",metadata:{toolName:h.payload.name,arguments:h.payload.arguments,toolCallId:h.payload.id}}])}catch(h){console.error("[ChatWidget] Parse error:",h)}}),k.addEventListener("tool.result",v=>{try{let h=JSON.parse(v.data);e.onEvent&&e.onEvent("tool.result",h.payload);let x=h.payload.result,V=x?.error;a(Q=>[...Q,{id:"tool-result-"+Date.now(),role:"system",content:V?`\u274C ${x.error}`:"\u2713 Done",timestamp:new Date,type:"tool_result",metadata:{toolName:h.payload.name,result:x,toolCallId:h.payload.tool_call_id}}])}catch(h){console.error("[ChatWidget] Parse error:",h)}}),k.addEventListener("custom",v=>{try{let h=JSON.parse(v.data);e.onEvent&&e.onEvent("custom",h.payload),h.payload?.type==="ui_control"&&e.onUIControl&&e.onUIControl(h.payload),h.payload?.type==="agent_context"&&a(x=>[...x,{id:"agent-context-"+Date.now(),role:"system",content:`\u{1F517} ${h.payload.agent_name||"Sub-agent"} is now handling this request`,timestamp:new Date,type:"agent_context",metadata:{agentKey:h.payload.agent_key,agentName:h.payload.agent_name,action:h.payload.action}}])}catch(h){console.error("[ChatWidget] Parse error:",h)}}),k.addEventListener("sub_agent.start",v=>{try{let h=JSON.parse(v.data);e.onEvent&&e.onEvent("sub_agent.start",h.payload),a(x=>[...x,{id:"sub-agent-start-"+Date.now(),role:"system",content:`\u{1F517} Delegating to ${h.payload.agent_name||h.payload.sub_agent_key||"sub-agent"}...`,timestamp:new Date,type:"sub_agent_start",metadata:{subAgentKey:h.payload.sub_agent_key,agentName:h.payload.agent_name,invocationMode:h.payload.invocation_mode}}])}catch(h){console.error("[ChatWidget] Parse error:",h)}}),k.addEventListener("sub_agent.end",v=>{try{let h=JSON.parse(v.data);e.onEvent&&e.onEvent("sub_agent.end",h.payload),a(x=>[...x,{id:"sub-agent-end-"+Date.now(),role:"system",content:`\u2713 ${h.payload.agent_name||"Sub-agent"} completed`,timestamp:new Date,type:"sub_agent_end",metadata:{subAgentKey:h.payload.sub_agent_key,agentName:h.payload.agent_name}}])}catch(h){console.error("[ChatWidget] Parse error:",h)}});let J=v=>{try{let h=JSON.parse(v.data);if(e.onEvent&&e.onEvent(h.type,h.payload),h.type==="run.failed"){let x=h.payload.error||"Agent run failed";_(x),a(V=>[...V,{id:"error-"+Date.now(),role:"system",content:`\u274C Error: ${x}`,timestamp:new Date,type:"error"}])}}catch(h){console.error("[ChatWidget] Parse error:",h)}l(!1),k.close(),g.current=null,B&&E&&E(B)};k.addEventListener("run.succeeded",J),k.addEventListener("run.failed",J),k.addEventListener("run.cancelled",J),k.addEventListener("run.timed_out",J),k.onerror=()=>{l(!1),k.close(),g.current=null}},[e]),A=U(async(p,$={})=>{if(!p.trim()||o)return;let{model:E,onAssistantMessage:I}=typeof $=="function"?{onAssistantMessage:$}:$;l(!0),_(null);let K={id:de(),role:"user",content:p.trim(),timestamp:new Date,type:"message"};a(k=>[...k,K]);try{let k=await t.getOrCreateSession(),B={agentKey:e.agentKey,conversationId:i,messages:[{role:"user",content:p.trim()}],metadata:{...e.metadata,journeyType:e.defaultJourneyType}};E&&(B.model=E);let J=await fetch(`${e.backendUrl}${e.apiPaths.runs}`,t.getFetchOptions({method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(B)}));if(!J.ok){let x=await J.json().catch(()=>({}));throw new Error(x.error||`HTTP ${J.status}`)}let v=await J.json();w.current=v.id;let h=v.conversationId||v.conversation_id;!i&&h&&u(h),await N(v.id,k,I)}catch(k){_(k.message||"Failed to send message"),l(!1)}finally{w.current=null}},[e,t,i,o,N]),j=U(async()=>{let p=w.current;if(!(!p||!o))try{let $=e.apiPaths.cancelRun?e.apiPaths.cancelRun.replace("{runId}",p):`${e.apiPaths.runs}${p}/cancel/`;(await fetch(`${e.backendUrl}${$}`,t.getFetchOptions({method:"POST",headers:{"Content-Type":"application/json"}}))).ok&&(g.current&&(g.current.close(),g.current=null),l(!1),w.current=null,a(I=>[...I,{id:"cancelled-"+Date.now(),role:"system",content:"\u23F9 Run cancelled",timestamp:new Date,type:"cancelled"}]))}catch($){console.error("[ChatWidget] Failed to cancel run:",$)}},[e,t,o]),O=U(()=>{a([]),u(null),_(null),f(!1),b(0),n?.set(e.conversationIdKey,null)},[e.conversationIdKey,n]),L=p=>{let $={id:de(),role:p.role,timestamp:p.timestamp?new Date(p.timestamp):new Date};if(p.role==="tool")return{...$,role:"system",content:"\u2713 Done",type:"tool_result",metadata:{result:p.content,toolCallId:p.tool_call_id}};if(p.role==="assistant"&&p.tool_calls&&p.tool_calls.length>0)return p.tool_calls.map(I=>({id:de(),role:"assistant",content:`\u{1F527} ${I.function?.name||I.name||"tool"}`,timestamp:$.timestamp,type:"tool_call",metadata:{toolName:I.function?.name||I.name,arguments:I.function?.arguments||I.arguments,toolCallId:I.id}}));let E=typeof p.content=="string"?p.content:JSON.stringify(p.content);return p.role==="assistant"&&!E?.trim()?null:{...$,content:E,type:"message"}},W=U(async p=>{l(!0),a([]),u(p);try{let $=await t.getOrCreateSession(),I=`${e.backendUrl}${e.apiPaths.conversations}${p}/?limit=10&offset=0`,K=await fetch(I,t.getFetchOptions({method:"GET"}));if(K.ok){let k=await K.json();k.messages&&a(k.messages.flatMap(L).filter(Boolean)),f(k.has_more||k.hasMore||!1),b(k.messages?.length||0)}else K.status===404&&(u(null),n?.set(e.conversationIdKey,null))}catch($){console.error("[ChatWidget] Failed to load conversation:",$)}finally{l(!1)}},[e,t,n]),F=U(async()=>{if(!(!i||d||!r)){m(!0);try{let $=`${e.backendUrl}${e.apiPaths.conversations}${i}/?limit=10&offset=${S}`,E=await fetch($,t.getFetchOptions({method:"GET"}));if(E.ok){let I=await E.json();if(I.messages?.length>0){let K=I.messages.flatMap(L).filter(Boolean);a(k=>[...K,...k]),b(k=>k+I.messages.length),f(I.has_more||I.hasMore||!1)}else f(!1)}}catch(p){console.error("[ChatWidget] Failed to load more messages:",p)}finally{m(!1)}}},[e,t,i,S,d,r]);return R(()=>()=>{g.current&&g.current.close()},[]),{messages:s,isLoading:o,error:c,conversationId:i,hasMoreMessages:r,loadingMoreMessages:d,sendMessage:A,cancelRun:j,clearMessages:O,loadConversation:W,loadMoreMessages:F,setConversationId:u}}function ft(e,t,n){let[s,a]=C([]),[o,l]=C(null),[c,_]=C(null),[i,u]=C(!1);R(()=>{(async()=>{if(e.showModelSelector){u(!0);try{let m=await fetch(`${e.backendUrl}${e.apiPaths.models}`,t.getFetchOptions({method:"GET"}));if(m.ok){let S=await m.json(),b=S.models||[];a(b),_(S.default);let g=n?.get(e.modelKey);g&&b.some(w=>w.id===g)?l(g):l(S.default)}}catch(m){console.warn("[ChatWidget] Failed to load models:",m)}finally{u(!1)}}})()},[e.backendUrl,e.apiPaths.models,e.showModelSelector,e.modelKey,t,n]);let r=U(d=>{l(d),n?.set(e.modelKey,d)},[e.modelKey,n]),f=U(()=>s.find(d=>d.id===o)||null,[s,o]);return{availableModels:s,selectedModel:o,defaultModel:c,isLoading:i,selectModel:r,getSelectedModelInfo:f}}function ht(e,t,n){let s=()=>e.authStrategy?e.authStrategy:e.authToken?"token":e.apiPaths.anonymousSession||e.anonymousSessionEndpoint?"anonymous":"none",a=()=>{let c=s(),_={},i=e.authToken||t().authToken;if(c==="token"&&i){let u=e.authHeader||"Authorization",r=e.authTokenPrefix!==void 0?e.authTokenPrefix:"Token";_[u]=r?`${r} ${i}`:i}else if(c==="jwt"&&i){let u=e.authHeader||"Authorization",r=e.authTokenPrefix!==void 0?e.authTokenPrefix:"Bearer";_[u]=r?`${r} ${i}`:i}else if(c==="anonymous"&&i){let u=e.authHeader||e.anonymousTokenHeader||"X-Anonymous-Token";_[u]=i}if(c==="session"){let u=rt(e.csrfCookieName);u&&(_["X-CSRFToken"]=u)}return _};return{getAuthStrategy:s,getAuthHeaders:a,getFetchOptions:(c={})=>{let _=s(),i={...c};return i.headers={...i.headers,...a()},_==="session"&&(i.credentials="include"),i},getOrCreateSession:async()=>{let c=s(),_=t();if(c!=="anonymous")return e.authToken||_.authToken;if(_.authToken)return _.authToken;let i=e.anonymousTokenKey||e.sessionTokenKey,u=_.storage?.get(i);if(u)return n(r=>({...r,authToken:u})),u;try{let r=e.anonymousSessionEndpoint||e.apiPaths.anonymousSession,f=await fetch(`${e.backendUrl}${r}`,{method:"POST",headers:{"Content-Type":"application/json"}});if(f.ok){let d=await f.json();return n(m=>({...m,authToken:d.token})),_.storage?.set(i,d.token),d.token}}catch(r){console.warn("[ChatWidget] Failed to create session:",r)}return null}}}function mt({config:e,onStateChange:t,markdownParser:n,apiRef:s}){let[a,o]=C(e.embedded||e.forceOpen===!0),[l,c]=C(!1),[_,i]=C(!1),[u,r]=C(!1),[f,d]=C([]),[m,S]=C(!1),[b,g]=C(e.enableTTS),[w,N]=C(!1),[A,j]=C(null);R(()=>{e.forceOpen!==void 0&&o(e.forceOpen)},[e.forceOpen]);let O=ne(()=>at(e.containerId),[e.containerId]),[L,W]=C(e.authToken||null),F=ne(()=>ht(e,()=>({authToken:L,storage:O}),x=>{let V=x({authToken:L,storage:O});V.authToken!==L&&W(V.authToken)}),[e,L,O]),p=pt(e,F,O),$=ft(e,F,O);R(()=>{for(let v=p.messages.length-1;v>=0;v--){let h=p.messages[v];if(h.type==="sub_agent_start"){j({key:h.metadata?.subAgentKey,name:h.metadata?.agentName});return}if(h.type==="sub_agent_end"){j(null);return}}},[p.messages]),R(()=>{let v=O.get(e.conversationIdKey);v&&p.loadConversation(v)},[]),R(()=>{t&&t({isOpen:a,isExpanded:l,debugMode:_,messages:p.messages,conversationId:p.conversationId,isLoading:p.isLoading,error:p.error})},[a,l,_,p.messages,p.conversationId,p.isLoading,p.error]);let E=U(async()=>{if(e.showConversationSidebar){S(!0);try{let v=`${e.backendUrl}${e.apiPaths.conversations}?agent_key=${encodeURIComponent(e.agentKey)}`,h=await fetch(v,F.getFetchOptions({method:"GET"}));if(h.ok){let x=await h.json();d(x.results||x)}}catch(v){console.error("[ChatWidget] Failed to load conversations:",v),d([])}finally{S(!1)}}},[e,F]),I=U(()=>{let v=!u;r(v),v&&E()},[u,E]),K=U(v=>{v!==p.conversationId&&p.loadConversation(v),r(!1)},[p]),k=U(()=>{p.clearMessages(),r(!1)},[p]),B=U(v=>{p.sendMessage(v,{model:$.selectedModel,onAssistantMessage:h=>{}})},[p,b,$.selectedModel]);if(R(()=>{s&&(s.current={open:()=>o(!0),close:()=>o(!1),send:v=>B(v),clearMessages:()=>p.clearMessages(),toggleTTS:()=>g(v=>!v),stopSpeech:()=>N(!1),setAuth:v=>{v.token!==void 0&&W(v.token)},clearAuth:()=>W(null)})},[p,s,B]),!e.embedded&&!a)return y`
242
+ <button
243
+ class="cw-fab"
244
+ style=${{backgroundColor:e.primaryColor}}
245
+ onClick=${()=>o(!0)}
246
+ >
247
+ <svg class="cw-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
248
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
249
+ </svg>
250
+ </button>
251
+ `;let J=["cw-widget",l&&"cw-widget-expanded",e.embedded&&"cw-widget-embedded"].filter(Boolean).join(" ");return y`
252
+ <div class=${J} style=${{"--cw-primary":e.primaryColor}}>
253
+ ${e.showConversationSidebar&&y`
254
+ <${_t}
255
+ isOpen=${u}
256
+ conversations=${f}
257
+ conversationsLoading=${m}
258
+ currentConversationId=${p.conversationId}
259
+ onClose=${()=>r(!1)}
260
+ onNewConversation=${k}
261
+ onSwitchConversation=${K}
262
+ />
263
+ `}
264
+
265
+ <${lt}
266
+ config=${e}
267
+ debugMode=${_}
268
+ isExpanded=${l}
269
+ isSpeaking=${w}
270
+ messagesCount=${p.messages.length}
271
+ isLoading=${p.isLoading}
272
+ currentAgent=${A}
273
+ onClose=${()=>o(!1)}
274
+ onToggleExpand=${()=>c(!l)}
275
+ onToggleDebug=${()=>i(!_)}
276
+ onToggleTTS=${()=>g(!b)}
277
+ onClear=${p.clearMessages}
278
+ onToggleSidebar=${I}
279
+ />
280
+
281
+ ${_&&y`<div class="cw-status-bar"><span>🐛 Debug</span></div>`}
282
+
283
+ <${ct}
284
+ messages=${p.messages}
285
+ isLoading=${p.isLoading}
286
+ hasMoreMessages=${p.hasMoreMessages}
287
+ loadingMoreMessages=${p.loadingMoreMessages}
288
+ onLoadMore=${p.loadMoreMessages}
289
+ debugMode=${_}
290
+ markdownParser=${n}
291
+ emptyStateTitle=${e.emptyStateTitle}
292
+ emptyStateMessage=${e.emptyStateMessage}
293
+ />
294
+
295
+ ${p.error&&y`<div class="cw-error-bar">${p.error}</div>`}
296
+
297
+ ${e.showModelSelector&&$.availableModels.length>0&&y`
298
+ <${dt}
299
+ availableModels=${$.availableModels}
300
+ selectedModel=${$.selectedModel}
301
+ onSelectModel=${$.selectModel}
302
+ disabled=${p.isLoading}
303
+ />
304
+ `}
305
+
306
+ <${ut}
307
+ onSend=${B}
308
+ onCancel=${p.cancelRun}
309
+ isLoading=${p.isLoading}
310
+ placeholder=${e.placeholder}
311
+ primaryColor=${e.primaryColor}
312
+ />
313
+ </div>
314
+ `}var yt={backendUrl:"http://localhost:8000",agentKey:"default-agent",title:"Chat Assistant",subtitle:"How can we help you today?",primaryColor:"#0066cc",position:"bottom-right",defaultJourneyType:"general",enableDebugMode:!0,enableAutoRun:!0,journeyTypes:{},customerPrompts:{},placeholder:"Type your message...",emptyStateTitle:"Start a Conversation",emptyStateMessage:"Send a message to get started.",authStrategy:null,authToken:null,authHeader:null,authTokenPrefix:null,anonymousSessionEndpoint:null,anonymousTokenKey:"chat_widget_anonymous_token",onAuthError:null,anonymousTokenHeader:"X-Anonymous-Token",conversationIdKey:"chat_widget_conversation_id",sessionTokenKey:"chat_widget_session_token",apiPaths:{anonymousSession:"/api/accounts/anonymous-session/",conversations:"/api/agent-runtime/conversations/",runs:"/api/agent-runtime/runs/",runEvents:"/api/agent-runtime/runs/{runId}/events/",simulateCustomer:"/api/agent-runtime/simulate-customer/",ttsVoices:"/api/tts/voices/",ttsSetVoice:"/api/tts/set-voice/",models:"/api/agent-runtime/models/"},showConversationSidebar:!0,showClearButton:!0,showDebugButton:!0,showTTSButton:!0,showVoiceSettings:!0,showExpandButton:!0,showModelSelector:!1,modelKey:"chat_widget_selected_model",autoRunDelay:1e3,autoRunMode:"automatic",enableTTS:!1,ttsProxyUrl:null,elevenLabsApiKey:null,ttsVoices:{assistant:null,user:null},ttsModel:"eleven_turbo_v2_5",ttsSettings:{stability:.5,similarity_boost:.75,style:0,use_speaker_boost:!0},availableVoices:[],onEvent:null,containerId:null,embedded:!1,metadata:{}};function gt(e){let t={...yt.apiPaths,...e.apiPaths||{}};return{...yt,...e,apiPaths:t}}var pe=new Map,Ot=0,T=null,Me=class{constructor(t={}){this.instanceId=`cw-${++Ot}`,this.config=gt(t),this.container=null,this._state={},this._apiRef={current:null},pe.set(this.instanceId,this)}_handleStateChange=t=>{this._state=t};init(){if(this.config.containerId){if(this.container=document.getElementById(this.config.containerId),!this.container)return console.error(`[ChatWidget] Container not found: ${this.config.containerId}`),this;this.container.classList.add("cw-container-embedded")}else this.container=document.createElement("div"),this.container.id=this.instanceId,this.container.className=`cw-container cw-position-${this.config.position}`,document.body.appendChild(this.container);return this._render(),console.log(`[ChatWidget] Instance ${this.instanceId} initialized`),this}_render(t={}){this.container&&ue(y`<${mt}
315
+ config=${{...this.config,...t}}
316
+ onStateChange=${this._handleStateChange}
317
+ markdownParser=${fe._enhancedMarkdownParser}
318
+ apiRef=${this._apiRef}
319
+ />`,this.container)}destroy(){this.container&&(ue(null,this.container),this.config.containerId?this.container.classList.remove("cw-container-embedded"):this.container.remove(),this.container=null),pe.delete(this.instanceId),console.log(`[ChatWidget] Instance ${this.instanceId} destroyed`)}open(){this._apiRef.current?this._apiRef.current.open():this._render({forceOpen:!0})}close(){this._apiRef.current?this._apiRef.current.close():this._render({forceOpen:!1})}send(t){this._apiRef.current&&this._apiRef.current.send(t)}clearMessages(){this._apiRef.current&&this._apiRef.current.clearMessages()}toggleTTS(){this._apiRef.current&&this._apiRef.current.toggleTTS()}stopSpeech(){this._apiRef.current&&this._apiRef.current.stopSpeech()}setAuth(t){this._apiRef.current&&this._apiRef.current.setAuth(t)}clearAuth(){this._apiRef.current&&this._apiRef.current.clearAuth()}getState(){return{...this._state}}getConfig(){return{...this.config}}};function vt(e={}){return new Me(e).init()}function Rt(e={}){return T&&T.destroy(),T=vt(e),T}function Ut(){T&&(T.destroy(),T=null)}function Ft(){T&&T.open()}function Lt(){T&&T.close()}function Wt(e){T&&T.send(e)}function Kt(){T&&T.clearMessages()}function Bt(){T&&T.toggleTTS()}function jt(){T&&T.stopSpeech()}function Jt(e){T&&T.setAuth(e)}function Vt(){T&&T.clearAuth()}function qt(){return T?T.getState():null}function zt(){return T?T.getConfig():null}var fe={createInstance:vt,getInstance:e=>pe.get(e),getAllInstances:()=>Array.from(pe.values()),init:Rt,destroy:Ut,open:Ft,close:Lt,send:Wt,clearMessages:Kt,toggleTTS:Bt,stopSpeech:jt,setAuth:Jt,clearAuth:Vt,getState:qt,getConfig:zt,_enhancedMarkdownParser:null};var Gt=fe;typeof window<"u"&&(window.ChatWidget=fe);return St(Xt);})();