mini-chat-bot-widget 0.6.0 → 0.8.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,14 +1,109 @@
1
- // chat-widget.js - Modern vanilla JS chat bot widget
1
+ // chat-widget.js - Basic vanilla JS chat widget skeleton
2
+
3
+ // Import screen components
4
+ import TextChatScreen from "./text-chat-screen.js";
5
+ import AudioChatScreen from "./audio-chat-screen.js";
2
6
 
3
7
  class ChatWidget {
4
8
  constructor(options = {}) {
5
9
  this.title = options.title || "Chat Assistant";
6
10
  this.placeholder = options.placeholder || "Type your message...";
7
- this.primaryColor = options.primaryColor || "#6366f1";
11
+ this.primaryColor = options.primaryColor || "#1a5c4b";
8
12
  this.container = null;
9
13
  this.messages = [];
10
14
  this.isMinimized = false;
15
+ this.currentScreen = "welcome"; // welcome, text, audio
16
+ this.textChatScreen = null;
17
+ this.audioChatScreen = null;
18
+
19
+ // Configuration options for message sending
20
+ this.langgraphUrl = options.langgraphUrl || "http://localhost:8080";
21
+ this.authToken = options.authToken || null;
22
+ this.threadId = options.threadId || null;
23
+ this.assistantId = options.assistantId || null;
24
+ this.selectedLanguage = options.selectedLanguage || "en";
25
+ this.accessToken = options.accessToken || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhpZHdnbHl5emRqc2dyaW92bWdtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDczOTE5NjEsImV4cCI6MjA2Mjk2Nzk2MX0.jAdwoGNbwK";
26
+ this.supabaseToken = options.supabaseToken || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhpZHdnbHl5emRqc2dyaW92bWdtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDczOTE5NjEsImV4cCI6MjA2Mjk2Nzk2MX0.jAdwoGNbwK";
27
+ this.userInfo = options.userInfo || {};
28
+ // Store the custom getHeaders function or use default
29
+ this._customGetHeaders = options.getHeaders;
30
+
31
+ // Default getHeaders implementation
32
+ this.getHeaders = () => {
33
+ if (this._customGetHeaders) {
34
+ return this._customGetHeaders();
35
+ }
36
+ const authToken = this.authToken || this.accessToken;
37
+ const supabaseToken = this.supabaseToken || authToken;
38
+ return {
39
+ "Content-Type": "application/json",
40
+ ...(authToken && { Authorization: `Bearer ${authToken}` }),
41
+ ...(supabaseToken && { "x-supabase-access-token": supabaseToken }),
42
+ Origin: (typeof window !== 'undefined' ? window.location.origin : "*") || "*",
43
+ };
44
+ };
45
+ this.getLatestCheckpoint = options.getLatestCheckpoint || (async () => null);
46
+ this.updateThread = options.updateThread || (async () => { });
47
+ this.getUserThreads = options.getUserThreads || (async ({ userId, userUuid }) => {
48
+ try {
49
+ const params = new URLSearchParams();
50
+ if (userUuid) params.append("user_uuid", userUuid);
51
+ const url = `${this.langgraphUrl}/history?${params.toString()}`;
52
+ const response = await fetch(url, {
53
+ method: "GET",
54
+ headers: this.getHeaders(),
55
+ });
56
+ if (!response.ok) return { threads: [] };
57
+ return await response.json();
58
+ } catch (e) {
59
+ console.error("Error fetching threads", e);
60
+ return { threads: [] };
61
+ }
62
+ });
63
+ this.setUserInfoFromDirectChatLogin = options.setUserInfoFromDirectChatLogin || (() => { });
64
+ this.setUserThreads = options.setUserThreads || (() => { });
65
+ this.setUserThreadsMetaData = options.setUserThreadsMetaData || (() => { });
66
+
67
+ this.languageOptions = [
68
+ { label: "Assamese", value: "as" },
69
+ { label: "Bengali", value: "bn" },
70
+ { label: "Dogri", value: "doi" },
71
+ { label: "English", value: "en" },
72
+ { label: "Gujarati", value: "gu" },
73
+ { label: "Hindi", value: "hi" },
74
+ { label: "Kannada", value: "kn" },
75
+ { label: "Konkani", value: "gom" },
76
+ { label: "Maithili", value: "mai" },
77
+ { label: "Malayalam", value: "ml" },
78
+ { label: "Marathi", value: "mr" },
79
+ { label: "Oriya", value: "or" },
80
+ { label: "Punjabi", value: "pa" },
81
+ { label: "Sanskrit", value: "sa" },
82
+ { label: "Sindhi", value: "sd" },
83
+ { label: "Tamil", value: "ta" },
84
+ { label: "Telugu", value: "te" },
85
+ { label: "Urdu", value: "ur" },
86
+ ];
87
+
88
+ // State management
89
+ this.isLoading = false;
90
+ this.isConnected = false;
91
+ this.initialized = false;
92
+ this.contentBlocks = [];
93
+ this.playedAudioIds = new Set();
94
+ this.currentInputAudio = null;
95
+ this.submissionInProgress = false;
96
+ this.processingAudioId = null;
97
+
11
98
  this._init();
99
+
100
+ // Initialize chat after DOM is ready
101
+ if (typeof window !== 'undefined') {
102
+ // Use setTimeout to ensure DOM is ready
103
+ setTimeout(() => {
104
+ this.initializeChat();
105
+ }, 100);
106
+ }
12
107
  }
13
108
 
14
109
  _init() {
@@ -22,45 +117,45 @@ class ChatWidget {
22
117
  </svg>
23
118
  </div>
24
119
  <div class="chat-widget-expanded" id="chat-expanded">
25
- <div class="chat-header">
26
- <div class="chat-header-content">
27
- <div class="chat-avatar">
28
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
29
- <path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path>
30
- <path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
31
- </svg>
120
+ <div class="chat-screen-container" id="chat-screen-container">
121
+ <!-- Screens will be rendered here -->
122
+ </div>
123
+ <div class="chat-drawer-overlay" id="chat-drawer-overlay">
124
+ <div class="chat-drawer">
125
+ <div class="drawer-header">
126
+ <div class="drawer-title">Menu</div>
32
127
  </div>
33
- <div class="chat-header-text">
34
- <div class="chat-title">${this.title}</div>
35
- <div class="chat-status">● Online</div>
128
+ <div class="drawer-content">
129
+ <button class="drawer-item" id="drawer-new-chat">
130
+ New Chat
131
+ </button>
132
+ <div class="language-selector-container">
133
+ <div class="custom-select" id="language-selector">
134
+ <div class="select-trigger" id="language-trigger">
135
+ <span id="selected-language-text">English</span>
136
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="chevron"><path d="m6 9 6 6 6-6"/></svg>
137
+ </div>
138
+ <div class="select-options" id="language-options"></div>
139
+ </div>
140
+ </div>
141
+ <div class="drawer-divider" style="height: 1px; background: #e2e8f0; margin: 8px 0;"></div>
142
+
143
+ <div id="drawer-threads" class="threads-container">
144
+ <!-- Threads will be rendered here -->
145
+ <div class="threads-loading">Loading history...</div>
146
+ </div>
36
147
  </div>
148
+
37
149
  </div>
38
- <button class="chat-close" id="chat-close">
39
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
40
- <line x1="18" y1="6" x2="6" y2="18"></line>
41
- <line x1="6" y1="6" x2="18" y2="18"></line>
42
- </svg>
43
- </button>
44
- </div>
45
- <div class="chat-messages" id="chat-messages">
46
- <div class="chat-welcome">
47
- <div class="welcome-icon">👋</div>
48
- <div class="welcome-text">Hello! How can I help you today?</div>
49
- </div>
50
- </div>
51
- <div class="chat-input-wrapper">
52
- <input type="text" id="chat-input" placeholder="${this.placeholder}" autocomplete="off" />
53
- <button id="chat-send" disabled>
54
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
55
- <line x1="22" y1="2" x2="11" y2="13"></line>
56
- <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
57
- </svg>
58
- </button>
150
+ <div class="drawer-backdrop" id="drawer-backdrop"></div>
59
151
  </div>
60
152
  </div>
61
153
  `;
62
154
  document.body.appendChild(this.container);
63
155
  this._applyStyles();
156
+ this._initScreens();
157
+ this._renderScreen();
158
+ this._populateLanguageOptions();
64
159
  this._bindEvents();
65
160
  }
66
161
 
@@ -76,62 +171,15 @@ class ChatWidget {
76
171
  z-index: 10000;
77
172
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
78
173
  }
79
-
80
- .chat-widget-minimized {
81
- width: 60px;
82
- height: 60px;
83
- border-radius: 50%;
84
- background: linear-gradient(135deg, ${this.primaryColor} 0%, #8b5cf6 100%);
85
- color: white;
174
+ .text-chat-screen {
175
+ flex: 1;
86
176
  display: flex;
87
- align-items: center;
88
- justify-content: center;
89
- cursor: pointer;
90
- box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4);
91
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
92
- animation: pulse 2s infinite;
93
- }
94
-
95
- .chat-widget-minimized:hover {
96
- transform: scale(1.1);
97
- box-shadow: 0 12px 32px rgba(99, 102, 241, 0.5);
98
- }
99
-
100
- @keyframes pulse {
101
- 0%, 100% { box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4); }
102
- 50% { box-shadow: 0 8px 32px rgba(99, 102, 241, 0.6); }
103
- }
104
-
105
- .chat-widget-expanded {
106
- width: 380px;
107
- height: 600px;
108
- background: white;
109
- border-radius: 16px;
110
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
111
- display: none;
112
177
  flex-direction: column;
113
178
  overflow: hidden;
114
- animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
115
- }
116
-
117
- @keyframes slideUp {
118
- from {
119
- opacity: 0;
120
- transform: translateY(20px) scale(0.95);
121
- }
122
- to {
123
- opacity: 1;
124
- transform: translateY(0) scale(1);
125
- }
126
- }
127
-
128
- .chat-widget-expanded.visible {
129
- display: flex;
130
179
  }
131
180
 
132
181
  .chat-header {
133
- background: linear-gradient(135deg, ${this.primaryColor} 0%, #8b5cf6 100%);
134
- color: white;
182
+ color: ${this.primaryColor};
135
183
  padding: 20px;
136
184
  display: flex;
137
185
  align-items: center;
@@ -142,6 +190,25 @@ class ChatWidget {
142
190
  display: flex;
143
191
  align-items: center;
144
192
  gap: 12px;
193
+ flex: 1;
194
+ }
195
+
196
+ .chat-back {
197
+ background: transparent;
198
+ border: none;
199
+ color: white;
200
+ cursor: pointer;
201
+ padding: 8px;
202
+ border-radius: 8px;
203
+ display: flex;
204
+ align-items: center;
205
+ justify-content: center;
206
+ transition: all 0.2s;
207
+ margin-right: 8px;
208
+ }
209
+
210
+ .chat-back:hover {
211
+ background: rgba(255, 255, 255, 0.15);
145
212
  }
146
213
 
147
214
  .chat-avatar {
@@ -160,10 +227,10 @@ class ChatWidget {
160
227
  flex-direction: column;
161
228
  gap: 2px;
162
229
  }
163
-
230
+
164
231
  .chat-title {
165
232
  font-weight: 600;
166
- font-size: 16px;
233
+ font-size: 20px;
167
234
  }
168
235
 
169
236
  .chat-status {
@@ -175,284 +242,2282 @@ class ChatWidget {
175
242
  }
176
243
 
177
244
  .chat-close {
178
- background: transparent;
245
+ background: ${this.primaryColor};
179
246
  border: none;
180
247
  color: white;
181
248
  cursor: pointer;
182
249
  padding: 8px;
183
- border-radius: 8px;
250
+ border-radius: 50%;
184
251
  display: flex;
185
252
  align-items: center;
186
253
  justify-content: center;
187
254
  transition: all 0.2s;
188
255
  }
189
256
 
190
- .chat-close:hover {
191
- background: rgba(255, 255, 255, 0.15);
192
- }
193
-
194
- .chat-messages {
195
- flex: 1;
196
- padding: 20px;
197
- overflow-y: auto;
198
- background: #f9fafb;
257
+ .chat-widget-minimized {
258
+ width: 60px;
259
+ height: 60px;
260
+ border-radius: 50%;
261
+ background: ${this.primaryColor};
262
+ color: white;
199
263
  display: flex;
200
- flex-direction: column;
201
- gap: 12px;
202
- }
203
-
204
- .chat-messages::-webkit-scrollbar {
205
- width: 6px;
206
- }
207
-
208
- .chat-messages::-webkit-scrollbar-track {
209
- background: transparent;
210
- }
211
-
212
- .chat-messages::-webkit-scrollbar-thumb {
213
- background: #cbd5e1;
214
- border-radius: 3px;
215
- }
216
-
217
- .chat-welcome {
218
- text-align: center;
219
- padding: 40px 20px;
220
- color: #64748b;
264
+ align-items: center;
265
+ justify-content: center;
266
+ cursor: pointer;
267
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
221
268
  }
222
269
 
223
- .welcome-icon {
224
- font-size: 48px;
225
- margin-bottom: 12px;
270
+ .chat-widget-minimized:hover {
271
+ transform: scale(1.1);
226
272
  }
227
273
 
228
- .welcome-text {
229
- font-size: 15px;
230
- font-weight: 500;
231
- color: #475569;
274
+ @keyframes pulse {
275
+ 0%, 100% { box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4); }
276
+ 50% { box-shadow: 0 8px 32px rgba(99, 102, 241, 0.6); }
232
277
  }
233
278
 
234
- .chat-message {
235
- display: flex;
236
- gap: 8px;
237
- animation: messageSlide 0.3s ease-out;
279
+ .chat-widget-expanded {
280
+ position: relative;
281
+ width: 480px;
282
+ height: 640px;
283
+ background: white;
284
+ border-radius: 16px;
285
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
286
+ display: none;
287
+ flex-direction: column;
288
+ overflow: hidden;
289
+ animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
238
290
  }
239
291
 
240
- @keyframes messageSlide {
292
+ @keyframes slideUp {
241
293
  from {
242
294
  opacity: 0;
243
- transform: translateY(10px);
295
+ transform: translateY(20px) scale(0.95);
244
296
  }
245
297
  to {
246
298
  opacity: 1;
247
- transform: translateY(0);
299
+ transform: translateY(0) scale(1);
248
300
  }
249
301
  }
250
302
 
251
- .chat-message.user {
252
- flex-direction: row-reverse;
253
- }
254
-
255
- .message-bubble {
256
- max-width: 75%;
257
- padding: 12px 16px;
258
- border-radius: 16px;
259
- font-size: 14px;
260
- line-height: 1.5;
261
- word-wrap: break-word;
303
+ .chat-widget-expanded.visible {
304
+ display: flex;
262
305
  }
263
306
 
264
- .chat-message.user .message-bubble {
265
- background: linear-gradient(135deg, ${this.primaryColor} 0%, #8b5cf6 100%);
266
- color: white;
267
- border-bottom-right-radius: 4px;
307
+ .chat-screen-container {
308
+ flex: 1;
309
+ display: flex;
310
+ flex-direction: column;
311
+ overflow: hidden;
268
312
  }
269
313
 
270
- .chat-message.bot .message-bubble {
271
- background: white;
272
- color: #1e293b;
273
- border-bottom-left-radius: 4px;
274
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
314
+ /* Welcome Screen Styles */
315
+ .welcome-screen {
316
+ flex: 1;
317
+ height: 100%;
318
+ display: flex;
319
+ flex-direction: column;
320
+ align-items: stretch;
321
+ justify-content: start;
322
+ background: linear-gradient(180deg, white 10%, #E1EFCC );
323
+ padding: 0px;
275
324
  }
276
325
 
277
- .typing-indicator {
278
- display: flex;
279
- gap: 4px;
280
- padding: 12px 16px;
281
- background: white;
282
- border-radius: 16px;
283
- border-bottom-left-radius: 4px;
284
- max-width: 60px;
285
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
326
+ .welcome-content {
327
+ width: 80%;
328
+ margin: auto;
329
+ text-align: center;
330
+ padding-top: 20px;
286
331
  }
287
332
 
288
- .typing-dot {
289
- width: 8px;
290
- height: 8px;
291
- border-radius: 50%;
292
- background: #94a3b8;
293
- animation: typing 1.4s infinite;
333
+ .welcome-icon {
334
+ font-size: 64px;
335
+ margin-bottom: 16px;
336
+ animation: bounce 2s infinite;
294
337
  }
295
338
 
296
- .typing-dot:nth-child(2) {
297
- animation-delay: 0.2s;
339
+ @keyframes bounce {
340
+ 0%, 100% { transform: translateY(0); }
341
+ 50% { transform: translateY(-10px); }
298
342
  }
299
343
 
300
- .typing-dot:nth-child(3) {
301
- animation-delay: 0.4s;
344
+ .welcome-title {
345
+ font-size: 24px;
346
+ font-weight: 600;
347
+ color: #1e293b;
348
+ margin: 0 0 8px 0;
302
349
  }
303
350
 
304
- @keyframes typing {
305
- 0%, 60%, 100% {
306
- transform: translateY(0);
307
- opacity: 0.7;
308
- }
309
- 30% {
310
- transform: translateY(-10px);
311
- opacity: 1;
312
- }
351
+ .welcome-subtitle {
352
+ font-size: 14px;
353
+ color: #64748b;
354
+ margin: 0 0 32px 0;
313
355
  }
314
356
 
315
- .chat-input-wrapper {
357
+ .welcome-options {
316
358
  display: flex;
317
- padding: 16px;
318
- gap: 8px;
319
- background: white;
320
- border-top: 1px solid #e2e8f0;
359
+ flex-direction: column;
360
+ gap: 12px;
321
361
  }
322
362
 
323
- #chat-input {
324
- flex: 1;
363
+ .welcome-option-btn {
364
+ background: white;
325
365
  border: 2px solid #e2e8f0;
326
366
  border-radius: 12px;
327
- padding: 12px 16px;
328
- font-size: 14px;
329
- outline: none;
367
+ padding: 16px;
368
+ display: flex;
369
+ align-items: center;
370
+ gap: 12px;
371
+ cursor: pointer;
330
372
  transition: all 0.2s;
331
- font-family: inherit;
373
+ text-align: left;
374
+ width: 100%;
332
375
  }
333
376
 
334
- #chat-input:focus {
377
+ .welcome-option-btn:hover {
335
378
  border-color: ${this.primaryColor};
336
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
379
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
380
+ transform: translateY(-2px);
337
381
  }
338
382
 
339
- #chat-send {
340
- background: linear-gradient(135deg, ${this.primaryColor} 0%, #8b5cf6 100%);
341
- color: white;
342
- border: none;
343
- border-radius: 12px;
344
- padding: 12px 16px;
345
- cursor: pointer;
346
- display: flex;
347
- align-items: center;
348
- justify-content: center;
349
- transition: all 0.2s;
383
+ .option-icon {
384
+ font-size: 32px;
385
+ flex-shrink: 0;
386
+ }
387
+
388
+ .option-content {
389
+ flex: 1;
350
390
  }
351
391
 
352
- #chat-send:disabled {
353
- opacity: 0.5;
354
- cursor: not-allowed;
392
+ .option-title {
393
+ font-size: 16px;
394
+ font-weight: 600;
395
+ color: #1e293b;
396
+ margin-bottom: 4px;
397
+ }
398
+
399
+ .option-description {
400
+ font-size: 13px;
401
+ color: #64748b;
355
402
  }
356
403
 
357
- #chat-send:not(:disabled):hover {
358
- transform: scale(1.05);
359
- box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
404
+ .welcome-option-btn svg {
405
+ color: #94a3b8;
406
+ flex-shrink: 0;
360
407
  }
361
408
 
362
- #chat-send:not(:disabled):active {
363
- transform: scale(0.95);
409
+ .welcome-option-btn:hover svg {
410
+ color: ${this.primaryColor};
364
411
  }
365
- `;
366
- document.head.appendChild(style);
412
+ .gradient-sphere-welcome-screen {
413
+ width: 200px !important;
414
+ height: 200px !important;
415
+ border-radius: 50%;
416
+ background: linear-gradient(135deg, #0a0d120f 0%, #85bc31 50%, #d0f19e 100%)
417
+ !important;
418
+ position: relative;
419
+ margin: 20px auto 30px auto;
420
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
421
+ animation: float 3s ease-in-out infinite;
422
+ flex-shrink: 0;
423
+ }
424
+
425
+ .sphere-highlight {
426
+ position: absolute;
427
+ top: 20%;
428
+ right: 20%;
429
+ width: 35px;
430
+ height: 35px;
431
+ background: radial-gradient(
432
+ circle,
433
+ rgba(255, 255, 255, 0.8) 0%,
434
+ transparent 70%
435
+ );
436
+ border-radius: 50%;
437
+ filter: blur(1px);
438
+ }
439
+
440
+ @keyframes float {
441
+ 0%,
442
+ 100% {
443
+ transform: translateY(0px);
367
444
  }
445
+ 50% {
446
+ transform: translateY(-10px);
447
+ }
448
+ }
368
449
 
369
- _bindEvents() {
370
- const toggle = this.container.querySelector("#chat-toggle");
371
- const closeBtn = this.container.querySelector("#chat-close");
372
- const expanded = this.container.querySelector("#chat-expanded");
373
- const input = this.container.querySelector("#chat-input");
374
- const sendBtn = this.container.querySelector("#chat-send");
450
+ .welcome-text {
451
+ max-width: 400px;
452
+ margin-bottom: 30px;
453
+ flex-shrink: 0;
454
+ text-align: left;
455
+ }
375
456
 
376
- toggle.addEventListener("click", () => {
377
- toggle.style.display = "none";
378
- expanded.classList.add("visible");
379
- input.focus();
380
- });
457
+ .greeting {
458
+ font-size: 24px;
459
+ font-weight: 600;
460
+ color: #1a5c4b;
461
+ margin: 0 0 16px 0;
462
+ }
381
463
 
382
- closeBtn.addEventListener("click", () => {
383
- expanded.classList.remove("visible");
384
- toggle.style.display = "flex";
385
- });
464
+ .intro {
465
+ font-size: 18px;
466
+ font-weight: 500;
467
+ color: #1a5c4b;
468
+ margin: 0 0 12px 0;
469
+ line-height: 1.4;
470
+ }
386
471
 
387
- input.addEventListener("input", () => {
388
- sendBtn.disabled = !input.value.trim();
389
- });
390
472
 
391
- const send = () => {
392
- const text = input.value.trim();
393
- if (!text) return;
473
+ .action-buttons {
474
+ display: flex;
475
+ align-items: center;
476
+ gap: 16px;
477
+ flex-shrink: 0;
478
+ margin-top: auto;
479
+ /* padding-top: 10px; */
480
+ padding-bottom: 20px;
481
+ justify-content: center;
482
+ width: 100%;
483
+ align-self: center;
484
+ }
394
485
 
395
- this._addMessage(text, true);
396
- input.value = "";
397
- sendBtn.disabled = true;
486
+ .primary-button {
487
+ background: #1a5c4b;
488
+ display:flex;
489
+ color: white;
490
+ border: none;
491
+ padding: 16px 32px;
492
+ border-radius: 25px;
493
+ font-size: 16px;
494
+ font-weight: 600;
495
+ cursor: pointer;
496
+ transition: all 0.3s ease;
497
+ box-shadow: 0 4px 12px rgba(26, 92, 75, 0.3);
498
+ }
398
499
 
399
- this._showTypingIndicator();
500
+ .primary-button:hover {
501
+ transform: translateY(-2px);
502
+ box-shadow: 0 6px 16px rgba(26, 92, 75, 0.4);
503
+ }
400
504
 
401
- setTimeout(() => {
402
- this._hideTypingIndicator();
403
- this._addMessage(`Thanks for your message! You said: "${text}"`, false);
404
- }, 1000 + Math.random() * 1000);
405
- };
505
+ @media (max-width: 768px) {
506
+ #chat-widget-container {
507
+ bottom: 16px;
508
+ right: 16px;
509
+ }
406
510
 
407
- sendBtn.addEventListener("click", send);
408
- input.addEventListener("keypress", (e) => {
409
- if (e.key === "Enter" && !sendBtn.disabled) send();
410
- });
411
- }
511
+ .chat-widget-expanded {
512
+ width: 100vw;
513
+ height: 100vh;
514
+ border-radius: 0;
515
+ max-width: 100vw;
516
+ max-height: 100vh;
517
+ position: fixed;
518
+ top: 0;
519
+ left: 0;
520
+ right: 0;
521
+ bottom: 0;
522
+ }
412
523
 
413
- _addMessage(text, isUser) {
414
- const messagesContainer = this.container.querySelector("#chat-messages");
415
- const welcome = messagesContainer.querySelector(".chat-welcome");
416
- if (welcome) welcome.remove();
524
+ .chat-widget-expanded.visible {
525
+ display: flex;
526
+ }
417
527
 
418
- const messageEl = document.createElement("div");
419
- messageEl.className = `chat-message ${isUser ? "user" : "bot"}`;
420
- messageEl.innerHTML = `<div class="message-bubble">${this._escapeHtml(text)}</div>`;
421
- messagesContainer.appendChild(messageEl);
422
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
423
- }
528
+ .welcome-content {
529
+ max-width: 100%;
530
+ padding-top: 40px;
531
+ }
424
532
 
425
- _showTypingIndicator() {
426
- const messagesContainer = this.container.querySelector("#chat-messages");
427
- const indicator = document.createElement("div");
428
- indicator.className = "chat-message bot";
429
- indicator.id = "typing-indicator";
430
- indicator.innerHTML = `
431
- <div class="typing-indicator">
432
- <div class="typing-dot"></div>
433
- <div class="typing-dot"></div>
434
- <div class="typing-dot"></div>
435
- </div>
436
- `;
437
- messagesContainer.appendChild(indicator);
438
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
439
- }
533
+ .chat-drawer {
534
+ width: 85% !important; /* Wider drawer on mobile */
535
+ }
536
+ }
440
537
 
441
- _hideTypingIndicator() {
442
- const indicator = this.container.querySelector("#typing-indicator");
443
- if (indicator) indicator.remove();
444
- }
538
+ /* Drawer Styles */
539
+ .chat-drawer-overlay {
540
+ position: absolute;
541
+ top: 0;
542
+ left: 0;
543
+ right: 0;
544
+ bottom: 0;
545
+ z-index: 2000;
546
+ display: flex;
547
+ pointer-events: none;
548
+ visibility: hidden;
549
+ }
550
+
551
+ .chat-drawer-overlay.visible {
552
+ pointer-events: auto;
553
+ visibility: visible;
554
+ }
555
+
556
+ .chat-drawer {
557
+ width: 65%;
558
+ background: white;
559
+ height: 100%;
560
+ transform: translateX(-100%);
561
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
562
+ display: flex;
563
+ flex-direction: column;
564
+ z-index: 2002;
565
+ box-shadow: 4px 0 24px rgba(0,0,0,0.1);
566
+ }
567
+
568
+ .chat-drawer-overlay.visible .chat-drawer {
569
+ transform: translateX(0);
570
+ }
571
+
572
+ .drawer-backdrop {
573
+ flex: 1;
574
+ background: rgba(0, 0, 0, 0.5);
575
+ opacity: 0;
576
+ transition: opacity 0.3s ease;
577
+ backdrop-filter: blur(2px);
578
+ cursor: pointer;
579
+ }
580
+
581
+ .chat-drawer-overlay.visible .drawer-backdrop {
582
+ opacity: 1;
583
+ }
584
+
585
+ .drawer-header {
586
+ padding: 24px;
587
+ border-bottom: 1px solid #f1f5f9;
588
+ }
589
+
590
+ .drawer-title {
591
+ font-size: 20px;
592
+ font-weight: 600;
593
+ color: ${this.primaryColor};
594
+ }
595
+
596
+ .drawer-content {
597
+ flex: 1;
598
+ padding: 16px;
599
+ display: flex;
600
+ flex-direction: column;
601
+ gap: 8px;
602
+ overflow-y: auto;
603
+ }
604
+
605
+ .drawer-item {
606
+ display: flex;
607
+ align-items: center;
608
+ gap: 12px;
609
+ padding: 12px 16px;
610
+ background: transparent;
611
+ border: none;
612
+ border-radius: 8px;
613
+ color: #475569;
614
+ font-size: 15px;
615
+ font-weight: 500;
616
+ cursor: pointer;
617
+ transition: all 0.2s;
618
+ text-align: left;
619
+ }
620
+
621
+ .drawer-item:hover {
622
+ background: #f1f5f9;
623
+ color: ${this.primaryColor};
624
+ }
625
+
626
+ .drawer-item svg {
627
+ opacity: 0.7;
628
+ }
629
+
630
+ .drawer-item:hover svg {
631
+ opacity: 1;
632
+ color: ${this.primaryColor};
633
+ }
634
+
635
+ .drawer-footer {
636
+ padding: 16px 24px;
637
+ border-top: 1px solid #f1f5f9;
638
+ }
639
+
640
+ .drawer-version {
641
+ font-size: 12px;
642
+ color: #94a3b8;
643
+ text-align: center;
644
+ }
645
+
646
+ /* Language Selector Styles */
647
+ .language-selector-container {
648
+ margin-top: 8px;
649
+ margin-bottom: 8px;
650
+ }
651
+
652
+ .language-label {
653
+ font-size: 11px;
654
+ font-weight: 600;
655
+ color: #94a3b8;
656
+ margin-bottom: 6px;
657
+ text-transform: uppercase;
658
+ letter-spacing: 0.5px;
659
+ padding-left: 4px;
660
+ }
661
+
662
+ .custom-select {
663
+ position: relative;
664
+ width: 100%;
665
+ user-select: none;
666
+ }
667
+
668
+ .select-trigger {
669
+ display: flex;
670
+ align-items: center;
671
+ justify-content: space-between;
672
+ padding: 10px 12px;
673
+ background: #f8fafc;
674
+ border: 1px solid #e2e8f0;
675
+ border-radius: 8px;
676
+ cursor: pointer;
677
+ transition: all 0.2s;
678
+ font-size: 14px;
679
+ color: #334155;
680
+ }
681
+
682
+ .select-trigger:hover {
683
+ border-color: ${this.primaryColor};
684
+ background: white;
685
+ }
686
+
687
+ .select-trigger.active {
688
+ border-color: ${this.primaryColor};
689
+ background: white;
690
+ box-shadow: 0 0 0 2px rgba(26, 92, 75, 0.1);
691
+ }
692
+
693
+ .select-options {
694
+ position: absolute;
695
+ top: calc(100% + 4px);
696
+ left: 0;
697
+ right: 0;
698
+ background: white;
699
+ border: 1px solid #e2e8f0;
700
+ border-radius: 8px;
701
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
702
+ max-height: 200px;
703
+ overflow-y: auto;
704
+ z-index: 50;
705
+ display: none;
706
+ opacity: 0;
707
+ transform: translateY(-10px);
708
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
709
+ }
710
+
711
+ .select-options.open {
712
+ display: block;
713
+ opacity: 1;
714
+ transform: translateY(0);
715
+ }
716
+
717
+ .select-option {
718
+ padding: 10px 12px;
719
+ font-size: 14px;
720
+ color: #334155;
721
+ cursor: pointer;
722
+ transition: all 0.1s;
723
+ }
724
+
725
+ .select-option:hover {
726
+ background: #f1f5f9;
727
+ color: ${this.primaryColor};
728
+ }
729
+
730
+ .select-option.selected {
731
+ background: rgba(26, 92, 75, 0.08);
732
+ color: ${this.primaryColor};
733
+ font-weight: 500;
734
+ }
735
+
736
+ .chevron {
737
+ transition: transform 0.2s ease;
738
+ color: #94a3b8;
739
+ }
740
+
741
+ .select-trigger.active .chevron {
742
+ transform: rotate(180deg);
743
+ color: ${this.primaryColor};
744
+ }
745
+
746
+ /* Thread List Styles */
747
+ .threads-container {
748
+ flex: 1;
749
+ overflow-y: auto;
750
+ display: flex;
751
+ flex-direction: column;
752
+ gap: 4px;
753
+ padding-top: 8px;
754
+ }
755
+
756
+ .threads-loading {
757
+ padding: 16px;
758
+ text-align: center;
759
+ color: #94a3b8;
760
+ font-size: 13px;
761
+ }
762
+
763
+ .thread-item {
764
+ display: flex;
765
+ flex-direction: column;
766
+ padding: 10px 12px;
767
+ border-radius: 8px;
768
+ cursor: pointer;
769
+ transition: all 0.2s;
770
+ text-align: left;
771
+ border: none;
772
+ background: transparent;
773
+ width: 100%;
774
+ color: #475569;
775
+ }
776
+
777
+ .thread-item:hover {
778
+ background-color: #f1f5f9; /* action.hover */
779
+ color: ${this.primaryColor};
780
+ }
781
+
782
+ .thread-item.selected {
783
+ background-color: rgba(26, 92, 75, 0.08); /* action.selected */
784
+ }
785
+
786
+ .thread-content {
787
+ font-size: 0.9rem;
788
+ color: #334155;
789
+ line-height: 1.3;
790
+ margin-bottom: 4px;
791
+ display: -webkit-box;
792
+ -webkit-line-clamp: 2;
793
+ -webkit-box-orient: vertical;
794
+ overflow: hidden;
795
+ font-weight: 500;
796
+ }
797
+
798
+ .thread-date {
799
+ font-size: 0.75rem;
800
+ font-weight: 600;
801
+ color: #94a3b8;
802
+ }
803
+
804
+ .no-history {
805
+ padding: 16px;
806
+ text-align: center;
807
+ color: #94a3b8;
808
+ font-size: 14px;
809
+ }
810
+ `;
811
+ document.head.appendChild(style);
812
+ }
813
+
814
+ _renderScreen() {
815
+ const screenContainer = this.container.querySelector("#chat-screen-container");
816
+
817
+ switch (this.currentScreen) {
818
+ case "welcome":
819
+ this._renderWelcomeScreen(screenContainer);
820
+ break;
821
+ case "text":
822
+ this._renderTextChatScreen(screenContainer);
823
+ break;
824
+ case "audio":
825
+ this._renderAudioChatScreen(screenContainer);
826
+ break;
827
+ }
828
+ }
829
+
830
+ _initScreens() {
831
+ // Initialize text chat screen
832
+ this.textChatScreen = new TextChatScreen({
833
+ title: this.title,
834
+ placeholder: this.placeholder,
835
+ primaryColor: this.primaryColor,
836
+ onMessage: (text, respond) => {
837
+ this._handleUserMessage(text, respond);
838
+ },
839
+ onBack: () => {
840
+ this.currentScreen = "welcome";
841
+ this._renderScreen();
842
+ },
843
+ onOpenDrawer: () => {
844
+ this._toggleDrawer(true);
845
+ },
846
+ onClose: () => {
847
+ const expanded = this.container.querySelector("#chat-expanded");
848
+ const toggle = this.container.querySelector("#chat-toggle");
849
+ expanded.classList.remove("visible");
850
+ toggle.style.display = "flex";
851
+ this.currentScreen = "welcome";
852
+ this.messages = [];
853
+ this.contentBlocks = [];
854
+ this._initScreens();
855
+ },
856
+ messages: this.messages,
857
+ sendMessage: (text, contentBlocks) => {
858
+ return this.sendMessage(text, contentBlocks);
859
+ },
860
+ contentBlocks: this.contentBlocks,
861
+ onContentBlocksChange: (contentBlocks) => {
862
+ this.contentBlocks = contentBlocks;
863
+ },
864
+ navigateToAudioScreen: () => {
865
+ this._navigateToScreen("audio")
866
+ }
867
+ });
868
+
869
+ // Initialize audio chat screen
870
+ this.audioChatScreen = new AudioChatScreen({
871
+ title: this.title,
872
+ primaryColor: this.primaryColor,
873
+ onRecordStart: () => {
874
+ // Handle recording start
875
+ },
876
+ onRecordStop: () => {
877
+ // Handle recording stop
878
+ },
879
+ onBack: () => {
880
+ this.currentScreen = "welcome";
881
+ this._renderScreen();
882
+ },
883
+ onOpenDrawer: () => {
884
+ this._toggleDrawer(true);
885
+ },
886
+ onClose: () => {
887
+ const expanded = this.container.querySelector("#chat-expanded");
888
+ const toggle = this.container.querySelector("#chat-toggle");
889
+ expanded.classList.remove("visible");
890
+ toggle.style.display = "flex";
891
+ this.currentScreen = "welcome";
892
+ this.messages = [];
893
+ this._initScreens();
894
+ },
895
+ messages: this.messages,
896
+ sendMessage: (text, contentBlocks) => {
897
+ return this.sendMessage(text, contentBlocks);
898
+ },
899
+ selectedLanguage: this.selectedLanguage,
900
+ navigateToTextScreen: () => {
901
+ this._navigateToScreen("text")
902
+ }
903
+ });
904
+ }
905
+
906
+ _renderWelcomeScreen(container) {
907
+ container.innerHTML = `
908
+ <main class="welcome-screen">
909
+ <div class="chat-header">
910
+ <div class="chat-header-content visible">
911
+ <div class="chat-header-text">
912
+ <div class="chat-title">${this.title}</div>
913
+ </div>
914
+ </div>
915
+ <button class="chat-close" id="text-chat-close">
916
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
917
+ <line x1="18" y1="6" x2="6" y2="18"></line>
918
+ <line x1="6" y1="6" x2="18" y2="18"></line>
919
+ </svg>
920
+ </button>
921
+ </div>
922
+ <div >
923
+ <div class="welcome-content">
924
+ <div class="gradient-sphere-welcome-screen">
925
+ <div class="sphere-highlight">
926
+ </div>
927
+ </div>
928
+
929
+ <div class="welcome-text">
930
+ <p class="greeting">Hello!</p>
931
+ <p class="intro">
932
+ We are your Krishi Vigyan Sahayak—KVS, a companion to help with every farming question! Tell us, what do you want to know today?
933
+ </p>
934
+ </div>
935
+
936
+ <div class="action-buttons">
937
+ <button
938
+ class="primary-button !rounded-full"
939
+ id="welcome-audio-btn"
940
+ >
941
+ Let's talk &#8203; &#8203; <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-mic-icon lucide-mic"><path d="M12 19v3"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><rect x="9" y="2" width="6" height="13" rx="3"/></svg>
942
+ </button>
943
+ </div>
944
+
945
+
946
+
947
+ </div>
948
+ </div>
949
+ </main>
950
+ `;
951
+
952
+ // Bind welcome screen events
953
+ // const textBtn = container.querySelector("#welcome-text-btn");
954
+ const audioBtn = container.querySelector("#welcome-audio-btn");
955
+ const closeBtn = container.querySelector("#text-chat-close");
956
+
957
+ // textBtn.addEventListener("click", () => this._navigateToScreen("text"));
958
+ audioBtn.addEventListener("click", () => this._navigateToScreen("audio"));
959
+
960
+ if (closeBtn) {
961
+ closeBtn.addEventListener("click", () => {
962
+ const expanded = this.container.querySelector("#chat-expanded");
963
+ const toggle = this.container.querySelector("#chat-toggle");
964
+ expanded.classList.remove("visible");
965
+ toggle.style.display = "flex";
966
+ this.currentScreen = "welcome";
967
+ this.messages = [];
968
+ this._initScreens();
969
+ });
970
+ }
971
+ }
972
+
973
+ _renderTextChatScreen(container) {
974
+ if (this.textChatScreen) {
975
+ // Sync contentBlocks before rendering
976
+ this.textChatScreen.contentBlocks = this.contentBlocks;
977
+ this.textChatScreen.messages = this.messages;
978
+ this.textChatScreen.render(container);
979
+ }
980
+ }
981
+
982
+ _renderAudioChatScreen(container) {
983
+ if (this.audioChatScreen) {
984
+ this.audioChatScreen.messages = this.messages;
985
+ this.audioChatScreen.render(container);
986
+ }
987
+ }
988
+
989
+
990
+
991
+ _navigateToScreen(screen) {
992
+ this.currentScreen = screen;
993
+ this._renderScreen();
994
+ }
995
+
996
+ _populateLanguageOptions() {
997
+ const optionsContainer = this.container.querySelector("#language-options");
998
+ if (!optionsContainer) return;
999
+
1000
+ optionsContainer.innerHTML = this.languageOptions.map(opt => `
1001
+ <div class="select-option ${opt.value === this.selectedLanguage ? 'selected' : ''}" data-value="${opt.value}">
1002
+ ${opt.label}
1003
+ </div>
1004
+ `).join('');
1005
+
1006
+ this._updateSelectedLanguageDisplay();
1007
+ }
1008
+
1009
+ _updateSelectedLanguageDisplay() {
1010
+ const textSpan = this.container.querySelector("#selected-language-text");
1011
+ if (textSpan) {
1012
+ const option = this.languageOptions.find(opt => opt.value === this.selectedLanguage);
1013
+ textSpan.textContent = option ? option.label : "English";
1014
+ }
1015
+ }
1016
+
1017
+ _bindLanguageSelectorEvents() {
1018
+ const selector = this.container.querySelector("#language-selector");
1019
+ const trigger = this.container.querySelector("#language-trigger");
1020
+ const optionsFn = this.container.querySelector("#language-options");
1021
+
1022
+ if (trigger && optionsFn) {
1023
+ trigger.addEventListener("click", (e) => {
1024
+ e.stopPropagation();
1025
+ const isOpen = optionsFn.classList.contains("open");
1026
+ if (isOpen) {
1027
+ optionsFn.classList.remove("open");
1028
+ trigger.classList.remove("active");
1029
+ } else {
1030
+ optionsFn.classList.add("open");
1031
+ trigger.classList.add("active");
1032
+ }
1033
+ });
1034
+
1035
+ const options = optionsFn.querySelectorAll(".select-option");
1036
+ options.forEach(opt => {
1037
+ opt.addEventListener("click", (e) => {
1038
+ const value = e.currentTarget.dataset.value;
1039
+ this.selectedLanguage = value;
1040
+
1041
+ // Update UI
1042
+ this._updateSelectedLanguageDisplay();
1043
+ optionsFn.querySelectorAll(".select-option").forEach(o => o.classList.remove("selected"));
1044
+ e.currentTarget.classList.add("selected");
1045
+
1046
+ // Close dropdown
1047
+ optionsFn.classList.remove("open");
1048
+ trigger.classList.remove("active");
1049
+
1050
+ console.log("Language selected:", this.selectedLanguage);
1051
+ });
1052
+ });
1053
+
1054
+ // Close on click outside
1055
+ document.addEventListener("click", (e) => {
1056
+ if (selector && !selector.contains(e.target)) {
1057
+ optionsFn.classList.remove("open");
1058
+ trigger.classList.remove("active");
1059
+ }
1060
+ });
1061
+ }
1062
+ }
1063
+
1064
+ async _fetchThreads() {
1065
+ const threadsContainer = this.container.querySelector("#drawer-threads");
1066
+ if (!threadsContainer) return;
1067
+
1068
+ threadsContainer.innerHTML = '<div class="threads-loading">Loading history...</div>';
1069
+
1070
+ try {
1071
+ const storedUser = localStorage.getItem("DfsWeb.user-info");
1072
+ const user = storedUser ? JSON.parse(storedUser) : null;
1073
+
1074
+ if (!user?.uuid) {
1075
+ threadsContainer.innerHTML = '<div class="no-history">Please log in to view history</div>';
1076
+ return;
1077
+ }
1078
+
1079
+ console.log("USER ID", user.id, "USER UUID", user.uuid);
1080
+
1081
+ const data = await this.getUserThreads({ userId: user.id, userUuid: user.uuid });
1082
+ console.log("THREAD DATA", data);
1083
+
1084
+ const threads = data.threads || [];
1085
+ const threadsWithHistory = threads.filter((thread) => thread.history && thread.history.length > 0);
1086
+
1087
+ this._renderThreadList(threadsWithHistory);
1088
+
1089
+ } catch (error) {
1090
+ console.error("UNEXPECTED ERROR IN FETCHING THREADS", error.message);
1091
+ threadsContainer.innerHTML = '<div class="no-history">Failed to load history</div>';
1092
+ }
1093
+ }
1094
+
1095
+ _renderThreadList(threads) {
1096
+ const threadsContainer = this.container.querySelector("#drawer-threads");
1097
+ if (!threadsContainer) return;
1098
+
1099
+ if (threads.length === 0) {
1100
+ threadsContainer.innerHTML = '<div class="no-history">No history available</div>';
1101
+ return;
1102
+ }
1103
+
1104
+ threadsContainer.innerHTML = '';
1105
+
1106
+ threads.forEach(thread => {
1107
+ const isSelected = thread.thread_id === this.threadId; // Use current threadId
1108
+
1109
+ const threadItem = document.createElement('div');
1110
+ threadItem.className = `thread-item ${isSelected ? 'selected' : ''}`;
1111
+
1112
+ // Get primary text (first user message)
1113
+ const primaryText = thread.history[0]?.values?.messages[0]?.content || "New Chat";
1114
+ // Handle array content (if content is array of blocks)
1115
+ let displayText = primaryText;
1116
+ if (Array.isArray(primaryText)) {
1117
+ const textBlock = primaryText.find(b => b.type === 'text');
1118
+ displayText = textBlock ? textBlock.text : "Multimedia Message";
1119
+ } else if (typeof primaryText === 'object') {
1120
+ displayText = "Message";
1121
+ }
1122
+
1123
+ const date = new Date(thread.created_at).toLocaleString();
1124
+
1125
+ threadItem.innerHTML = `
1126
+ <div class="thread-content">${displayText}</div>
1127
+ <div class="thread-date">${date}</div>
1128
+ `;
1129
+
1130
+ threadItem.addEventListener('click', () => {
1131
+ this._handleThreadSelect(thread.thread_id);
1132
+ });
1133
+
1134
+ threadsContainer.appendChild(threadItem);
1135
+ });
1136
+ }
1137
+
1138
+ _handleThreadSelect(threadId) {
1139
+ console.log("Select thread:", threadId);
1140
+ this.threadId = threadId;
1141
+ this._toggleDrawer(false);
1142
+
1143
+ // Switch to text screen to show history
1144
+ this.currentScreen = "text";
1145
+
1146
+ // Clear current messages
1147
+ this.messages = [];
1148
+ this.setIsLoading = true; // Set loading state if you have setter, otherwise this.isLoading = true
1149
+ this.isLoading = true;
1150
+
1151
+ // Re-initialize screens (clears them)
1152
+ // this._initScreens(); // Can't fully re-init as it might break event listeners
1153
+ // Better to just load history and render
1154
+
1155
+ this._renderScreen();
1156
+
1157
+ // Load history for this thread
1158
+ this.loadHistory(threadId).then(() => {
1159
+ this.isLoading = false;
1160
+ this._renderScreen(); // Re-render with new messages
1161
+ });
1162
+ }
1163
+
1164
+ _bindEvents() {
1165
+ const toggle = this.container.querySelector("#chat-toggle");
1166
+ const expanded = this.container.querySelector("#chat-expanded");
1167
+
1168
+ // Ensure toggle button is visible
1169
+ if (toggle) {
1170
+ toggle.style.display = "flex";
1171
+ }
1172
+
1173
+ toggle.addEventListener("click", () => {
1174
+ console.log("Clicked")
1175
+ toggle.style.display = "none";
1176
+ expanded.classList.add("visible");
1177
+ this.currentScreen = "welcome";
1178
+ this._renderScreen();
1179
+ });
1180
+
1181
+ // Drawer events
1182
+ const drawerBackdrop = this.container.querySelector("#drawer-backdrop");
1183
+ if (drawerBackdrop) {
1184
+ drawerBackdrop.addEventListener("click", () => {
1185
+ this._toggleDrawer(false);
1186
+ });
1187
+ }
1188
+
1189
+ const drawerItems = this.container.querySelectorAll(".drawer-item");
1190
+ drawerItems.forEach(item => {
1191
+ item.addEventListener("click", (e) => {
1192
+ // Handle drawer item click
1193
+ this._toggleDrawer(false);
1194
+ const targetId = e.currentTarget.id;
1195
+ if (targetId === "drawer-new-chat") {
1196
+ this._resetChat();
1197
+ }
1198
+ });
1199
+ });
1200
+
1201
+ this._bindLanguageSelectorEvents();
1202
+ }
1203
+
1204
+ _toggleDrawer(show) {
1205
+ const overlay = this.container.querySelector("#chat-drawer-overlay");
1206
+ if (!overlay) return;
1207
+
1208
+ if (show) {
1209
+ overlay.classList.add("visible");
1210
+ // Fetch threads when drawer is opened
1211
+ this._fetchThreads();
1212
+ } else {
1213
+ overlay.classList.remove("visible");
1214
+ }
1215
+ }
1216
+
1217
+ _resetChat() {
1218
+ this.messages = [];
1219
+ this.threadId = null;
1220
+ this.contentBlocks = [];
1221
+ // this.currentScreen = "welcome";
1222
+ this._initScreens();
1223
+ this._renderScreen();
1224
+ }
1225
+
1226
+ _handleUserMessage(text, respond) {
1227
+ // Simple echo response - just returns what the user typed
1228
+ // The respond callback is provided by TextChatScreen
1229
+ if (respond) {
1230
+ respond(`You said: "${text}"`);
1231
+ }
1232
+ }
1233
+
1234
+ // Add message to shared messages array and update UI
1235
+ _addMessageToArray(message) {
1236
+ this.messages.push(message);
1237
+ this._notifyScreensUpdate();
1238
+ }
1239
+
1240
+ // Update message in shared messages array
1241
+ _updateMessageInArray(messageId, updates) {
1242
+ const index = this.messages.findIndex(msg => msg.id === messageId);
1243
+ if (index !== -1) {
1244
+ this.messages[index] = { ...this.messages[index], ...updates };
1245
+ this._notifyScreensUpdate();
1246
+ }
1247
+ }
1248
+
1249
+ // Notify all screens to update their UI
1250
+ _notifyScreensUpdate() {
1251
+ if (this.textChatScreen && this.textChatScreen.container) {
1252
+ this.textChatScreen._syncMessages(this.messages);
1253
+ }
1254
+ if (this.audioChatScreen && this.audioChatScreen.container) {
1255
+ this.audioChatScreen._syncMessages(this.messages);
1256
+ }
1257
+ }
1258
+
1259
+ // Send message function adapted from React version
1260
+ async sendMessage(inputValue, contentBlocks = []) {
1261
+ if ((!inputValue.trim() && contentBlocks.length === 0) || this.isLoading) {
1262
+ return;
1263
+ }
1264
+
1265
+ console.log(
1266
+ "📤 Sending text message - Language:",
1267
+ this.selectedLanguage,
1268
+ "Assistant ID:",
1269
+ this.assistantId || "Not available",
1270
+ "Thread ID:",
1271
+ this.threadId || "Not available",
1272
+ "Access token:",
1273
+ this.accessToken ? "Present" : "Empty - backend will handle",
1274
+ );
1275
+
1276
+ // Prepare content blocks for the message
1277
+ const messageContent = [];
1278
+
1279
+ // Add text content if present
1280
+ if (inputValue.trim()) {
1281
+ const textContent = inputValue;
1282
+ messageContent.push({ type: "text", text: textContent });
1283
+ }
1284
+
1285
+ // Add file attachments
1286
+ contentBlocks.forEach((block) => {
1287
+ messageContent.push(block);
1288
+ });
1289
+
1290
+ // Create user message for display
1291
+ const userMessage = {
1292
+ id: Date.now(),
1293
+ content: inputValue.trim() || "📎 File attachments",
1294
+ sender: "user",
1295
+ timestamp: new Date().toISOString(),
1296
+ attachments: contentBlocks.length > 0 ? contentBlocks : null,
1297
+ };
1298
+
1299
+ console.log("USER MESSAGE", userMessage);
1300
+ console.log("USER MESSAGE CONTENTBLOCKS", contentBlocks);
1301
+ console.log("USER MESSAGE CONTENT", messageContent);
1302
+
1303
+ this._addMessageToArray(userMessage);
1304
+ this.contentBlocks = [];
1305
+
1306
+ // Clear contentBlocks in text chat screen
1307
+ if (this.textChatScreen) {
1308
+ this.textChatScreen.contentBlocks = [];
1309
+ this.textChatScreen.renderFilePreview();
1310
+ this.textChatScreen.updateSendButton();
1311
+ }
1312
+
1313
+ // Reset states for text messages
1314
+ this.playedAudioIds.clear();
1315
+ this.currentInputAudio = null;
1316
+ this.submissionInProgress = false;
1317
+ this.processingAudioId = null;
1318
+
1319
+ this.isLoading = true;
1320
+
1321
+ try {
1322
+ // Always attempt to send message, let backend handle auth
1323
+ await this.sendMessageWithStreaming(messageContent);
1324
+ } catch (error) {
1325
+ console.error("Error sending message:", error);
1326
+ const errorMessage = {
1327
+ id: Date.now() + 1,
1328
+ content: "Sorry, I encountered an error. Please try again.",
1329
+ sender: "assistant",
1330
+ timestamp: new Date().toISOString(),
1331
+ isError: true,
1332
+ };
1333
+ this._addMessageToArray(errorMessage);
1334
+ } finally {
1335
+ this.isLoading = false;
1336
+ }
1337
+ }
1338
+
1339
+ // Send message with streaming function adapted from React version
1340
+ async sendMessageWithStreaming(messageContent) {
1341
+ console.log("USER MESSAGE STREAMING", messageContent);
1342
+ console.log(
1343
+ "📤 Sending text message - Language:",
1344
+ this.selectedLanguage,
1345
+ "Access token:",
1346
+ this.accessToken ? "Present" : "Not found",
1347
+ );
1348
+
1349
+ const assistantMessage = {
1350
+ id: Date.now() + 1,
1351
+ content: "",
1352
+ sender: "assistant",
1353
+ timestamp: new Date().toISOString(),
1354
+ isStreaming: true,
1355
+ isProcessing: true,
1356
+ textSessionId: Date.now() + 1,
1357
+ hasAudioResponse: false,
1358
+ inputType: "text",
1359
+ };
1360
+
1361
+ this._addMessageToArray(assistantMessage);
1362
+
1363
+ let latestRunId = null;
1364
+
1365
+ try {
1366
+ // Get the latest checkpoint to resume from the latest state
1367
+ const latestCheckpoint = await this.getLatestCheckpoint(this.threadId);
1368
+
1369
+ console.log("📤 Sending message to backend:", {
1370
+ input: {
1371
+ messages: [
1372
+ {
1373
+ id: `msg-${Date.now()}`,
1374
+ type: "human",
1375
+ content: messageContent,
1376
+ },
1377
+ ],
1378
+ user_language: this.selectedLanguage,
1379
+ access_token: "[redacted]",
1380
+ user_info: JSON.stringify(this.userInfo),
1381
+ },
1382
+ config: { configurable: {} },
1383
+ metadata: {
1384
+ supabaseAccessToken: "[redacted]",
1385
+ user_language: this.selectedLanguage,
1386
+ input_type: "text",
1387
+ audio_content: null,
1388
+ },
1389
+ stream_mode: ["values", "messages-tuple", "custom"],
1390
+ stream_subgraphs: true,
1391
+ assistant_id: this.assistantId,
1392
+ on_disconnect: "cancel",
1393
+ ...(latestCheckpoint && { checkpoint: latestCheckpoint }),
1394
+ });
1395
+
1396
+ const response = await fetch(
1397
+ `${this.langgraphUrl}/threads/${this.threadId}/runs/stream`,
1398
+ {
1399
+ method: "POST",
1400
+ headers: this.getHeaders(),
1401
+ body: JSON.stringify({
1402
+ input: {
1403
+ messages: [
1404
+ {
1405
+ id: `msg-${Date.now()}`,
1406
+ type: "human",
1407
+ content: messageContent,
1408
+ },
1409
+ ],
1410
+ user_language: this.selectedLanguage,
1411
+ access_token: this.accessToken,
1412
+ user_info: JSON.stringify(this.userInfo),
1413
+ },
1414
+ config: { configurable: {} },
1415
+ metadata: {
1416
+ supabaseAccessToken: this.supabaseToken,
1417
+ user_language: this.selectedLanguage,
1418
+ },
1419
+ stream_mode: ["values", "messages-tuple", "custom"],
1420
+ stream_subgraphs: true,
1421
+ assistant_id: this.assistantId,
1422
+ on_disconnect: "cancel",
1423
+ ...(latestCheckpoint && { checkpoint: latestCheckpoint }),
1424
+ }),
1425
+ },
1426
+ );
1427
+
1428
+ if (!response.ok) {
1429
+ throw new Error(`HTTP error! status: ${response.status}`);
1430
+ }
1431
+
1432
+ // Handle streaming response
1433
+ const reader = response.body.getReader();
1434
+ const decoder = new TextDecoder();
1435
+ let buffer = "";
1436
+ let currentMessage = ""; // Local to this function scope
1437
+ let bestMessageText = "";
1438
+ let bestMessagePriority = -1;
1439
+ let lastUpdateTime = 0;
1440
+ const UPDATE_THROTTLE = 150; // Throttle updates to max ~7 per second
1441
+ const requestId = Date.now(); // Unique ID for this request
1442
+ let currentEvent = null; // Track current event type
1443
+ let currentData = null; // Track current data
1444
+ let hasReceivedMeaningfulResponse = false; // Track if we've received a meaningful response
1445
+ let finalResponse = ""; // Store the final meaningful response
1446
+ let latestUiAction = null; // Track the last ui_action from the last ui_action_mapper
1447
+ let endEventFinalized = false; // Track if we finalized on 'end' event
1448
+ let endFinalContent = ""; // Content used at 'end' event
1449
+ let currentAdditionalKwargs = null; // Chart-related additional kwargs captured from stream
1450
+ let chartUrlFromViz = null; // If stream provides a chart image URL, store it here and use it for the assistant message
1451
+
1452
+ console.log(
1453
+ "🔍 Starting to process streaming response for requestId:",
1454
+ requestId,
1455
+ );
1456
+
1457
+ // Prioritize human-readable assistant text over tool/JSON outputs
1458
+ const getNodePriority = (nodeKey) => {
1459
+ if (!nodeKey) return 0;
1460
+ const key = String(nodeKey).toLowerCase();
1461
+ if (
1462
+ key.includes("mdms_assistant") ||
1463
+ key.includes("grievance_assistant") ||
1464
+ key.includes("mandiprice_assistant")
1465
+ ) return 0;
1466
+ if (key.includes("assistant") && !key.includes("tools")) return 3;
1467
+ if (key.includes("response_processor")) return 2;
1468
+ if (key.includes("post_assistant")) return 2;
1469
+ if (key.includes("ui_action_mapper")) return 1;
1470
+ return 0;
1471
+ };
1472
+
1473
+ while (true) {
1474
+ const { done, value } = await reader.read();
1475
+ if (done) break;
1476
+
1477
+ buffer += decoder.decode(value, { stream: true });
1478
+ const lines = buffer.split("\n");
1479
+
1480
+ buffer = lines.pop() || "";
1481
+
1482
+ for (const line of lines) {
1483
+ if (line.trim() === "") continue;
1484
+
1485
+ console.log("📝 Processing SSE line:", {
1486
+ line: line.substring(0, 100) + (line.length > 100 ? "..." : ""),
1487
+ requestId: requestId,
1488
+ });
1489
+
1490
+ if (line.startsWith("data:")) {
1491
+ try {
1492
+ const data = JSON.parse(line.slice(5).trim());
1493
+ if (data.run_id) {
1494
+ console.log("🟢 Run ID:", data.run_id);
1495
+ latestRunId = data.run_id;
1496
+ }
1497
+ } catch (err) {
1498
+ console.error("Error parsing JSON:", err, line);
1499
+ }
1500
+ }
1501
+
1502
+ if (line.startsWith("event: ")) {
1503
+ currentEvent = line.slice(7).trim();
1504
+ console.log("🎯 Event type:", currentEvent);
1505
+ } else if (line.startsWith("data: ")) {
1506
+ const jsonStr = line.slice(6).trim();
1507
+
1508
+ if (jsonStr === "[DONE]") {
1509
+ console.log("🏁 Stream completed");
1510
+ break;
1511
+ }
1512
+
1513
+ try {
1514
+ const data = JSON.parse(jsonStr);
1515
+ currentData = data;
1516
+
1517
+ if (data.run_id) {
1518
+ console.log(
1519
+ "🟢 Capturing run_id from parsed data:",
1520
+ data.run_id,
1521
+ );
1522
+ latestRunId = data.run_id;
1523
+ }
1524
+
1525
+ // Handle end event
1526
+ if (currentEvent === "end") {
1527
+ console.log(
1528
+ "🏁 Received event: end - finalizing message with run_id:",
1529
+ latestRunId,
1530
+ );
1531
+ if (data.run_id) {
1532
+ latestRunId = data.run_id;
1533
+ console.log(
1534
+ "🟢 Updated run_id from end event:",
1535
+ latestRunId,
1536
+ );
1537
+ }
1538
+
1539
+ if (currentMessage && currentMessage.length > 0) {
1540
+ this._updateMessageInArray(assistantMessage.id, {
1541
+ content: currentMessage,
1542
+ additional_kwargs: currentAdditionalKwargs,
1543
+ isStreaming: false,
1544
+ isProcessing: false,
1545
+ hasAudioResponse: this.messages.find(m => m.id === assistantMessage.id)?.hasAudioResponse || false,
1546
+ latestRunId: latestRunId,
1547
+ });
1548
+ endEventFinalized = true;
1549
+ endFinalContent = currentMessage;
1550
+ } else if (latestRunId) {
1551
+ this._updateMessageInArray(assistantMessage.id, {
1552
+ latestRunId: latestRunId,
1553
+ isStreaming: false,
1554
+ isProcessing: false,
1555
+ });
1556
+ }
1557
+ }
1558
+
1559
+ console.log("🔍 Parsed SSE data:", {
1560
+ event: currentEvent,
1561
+ hasData: !!data,
1562
+ dataKeys: data ? Object.keys(data) : "no data",
1563
+ topLevelKeys: Object.keys(data),
1564
+ hasAudioContent: data?.audio_content ? "YES" : "NO",
1565
+ hasResponseProcessor: data?.response_processor ? "YES" : "NO",
1566
+ hasRunId: data?.run_id ? "YES" : "NO",
1567
+ runId: data?.run_id || "N/A",
1568
+ requestId: requestId,
1569
+ });
1570
+
1571
+ // Handle Authentication from smart_router
1572
+ if (
1573
+ data.smart_router &&
1574
+ data.smart_router.access_token &&
1575
+ data.smart_router.farmer_profile &&
1576
+ data.smart_router.farmer_profile_fetched === true
1577
+ ) {
1578
+ console.log(
1579
+ "🔐 Authentication data detected in smart_router:",
1580
+ {
1581
+ hasAccessToken: !!data.smart_router.access_token,
1582
+ mobileNumber: data.smart_router.mobile_number,
1583
+ userUuid: data.smart_router.userUuid,
1584
+ hasFarmerProfile: !!data.smart_router.farmer_profile,
1585
+ farmerId: data.smart_router.farmer_profile?.individualId,
1586
+ farmerName: data.smart_router.farmer_profile?.name,
1587
+ requestId: requestId,
1588
+ },
1589
+ );
1590
+
1591
+ try {
1592
+ const smartRouter = data.smart_router;
1593
+ const farmerProfile = smartRouter.farmer_profile;
1594
+ const accessToken = smartRouter.access_token;
1595
+
1596
+ localStorage.setItem("DfsWeb.access-token", accessToken);
1597
+
1598
+ let userInfo = {};
1599
+ if (farmerProfile.user_details) {
1600
+ userInfo = { ...farmerProfile.user_details };
1601
+ userInfo.name = farmerProfile.name || userInfo.name || "";
1602
+ if (!userInfo.uuid) {
1603
+ userInfo.uuid = smartRouter.userUuid;
1604
+ }
1605
+ if (!userInfo.mobileNumber) {
1606
+ userInfo.mobileNumber = smartRouter.mobile_number ||
1607
+ farmerProfile.mobileNumber;
1608
+ }
1609
+ } else {
1610
+ userInfo = {
1611
+ id: null,
1612
+ uuid: smartRouter.userUuid,
1613
+ userName: smartRouter.mobile_number,
1614
+ name: farmerProfile.name || "",
1615
+ mobileNumber: smartRouter.mobile_number ||
1616
+ farmerProfile.mobileNumber,
1617
+ emailId: farmerProfile.email || null,
1618
+ locale: null,
1619
+ type: "CITIZEN",
1620
+ roles: [
1621
+ {
1622
+ name: "Citizen",
1623
+ code: "CITIZEN",
1624
+ tenantId: farmerProfile.tenantId || "br",
1625
+ },
1626
+ ],
1627
+ active: true,
1628
+ tenantId: farmerProfile.tenantId || "br",
1629
+ permanentCity: null,
1630
+ };
1631
+ this.setUserInfoFromDirectChatLogin(userInfo);
1632
+ }
1633
+
1634
+ this.userInfo = userInfo;
1635
+ this.accessToken = accessToken;
1636
+
1637
+ if (userInfo.uuid) {
1638
+ console.log("USER INFO FOR UPDATING THREAD", userInfo);
1639
+ if (this.threadId) {
1640
+ this.updateThread(this.threadId, userInfo.uuid)
1641
+ .then((updatedThread) => {
1642
+ console.log(
1643
+ "✅ Thread updated after authentication:",
1644
+ updatedThread,
1645
+ );
1646
+ })
1647
+ .catch((error) => {
1648
+ console.error(
1649
+ "❌ Error updating thread after authentication:",
1650
+ error.message,
1651
+ );
1652
+ });
1653
+ }
1654
+
1655
+ this.getUserThreads({
1656
+ userId: userInfo.id,
1657
+ userUuid: userInfo.uuid,
1658
+ })
1659
+ .then((data) => {
1660
+ console.log(
1661
+ "✅ Thread data fetched after authentication:",
1662
+ data,
1663
+ );
1664
+ this.setUserThreads(data.threads);
1665
+ this.setUserThreadsMetaData({
1666
+ threads_processed: data.threads_processed,
1667
+ total_history_items: data.total_history_items,
1668
+ total_threads: data.total_threads,
1669
+ });
1670
+ })
1671
+ .catch((error) => {
1672
+ console.error(
1673
+ "❌ Error fetching threads after authentication:",
1674
+ error.message,
1675
+ );
1676
+ });
1677
+ }
1678
+ } catch (authError) {
1679
+ console.error(
1680
+ "❌ Error processing authentication data:",
1681
+ authError,
1682
+ );
1683
+ }
1684
+ }
1685
+
1686
+ // Handle UI Actions
1687
+ let uiActionSource = null;
1688
+ let actions = null;
1689
+
1690
+ if (
1691
+ data.response_processor &&
1692
+ Array.isArray(data.response_processor.ui_actions)
1693
+ ) {
1694
+ actions = data.response_processor.ui_actions;
1695
+ } else if (
1696
+ data.response_processor && data.response_processor.messages &&
1697
+ Array.isArray(data.response_processor.messages) &&
1698
+ data.response_processor.messages.length > 0 &&
1699
+ data.response_processor.messages[0].additional_kwargs &&
1700
+ Array.isArray(
1701
+ data.response_processor.messages[0].additional_kwargs
1702
+ .ui_actions,
1703
+ )
1704
+ ) {
1705
+ actions =
1706
+ data.response_processor.messages[0].additional_kwargs
1707
+ .ui_actions;
1708
+ } else if (
1709
+ data.ui_action_mapper &&
1710
+ Array.isArray(data.ui_action_mapper.ui_actions)
1711
+ ) {
1712
+ uiActionSource = data.ui_action_mapper;
1713
+ actions = uiActionSource.ui_actions;
1714
+ } else if (
1715
+ data.mdms_ui_action_mapper &&
1716
+ Array.isArray(data.mdms_ui_action_mapper.ui_actions)
1717
+ ) {
1718
+ uiActionSource = data.mdms_ui_action_mapper;
1719
+ actions = uiActionSource.ui_actions;
1720
+ }
1721
+
1722
+ if (actions && actions.length > 0) {
1723
+ console.log("UI ACTIONS", actions);
1724
+ const action = actions[actions.length - 1];
1725
+ if (action.web && action.web.link) {
1726
+ let scrollToId = null;
1727
+ let navigationParams = {};
1728
+
1729
+ if (action.web.parameters) {
1730
+ if (action.web.parameters.scrollTo) {
1731
+ scrollToId = action.web.parameters.scrollTo;
1732
+ }
1733
+ navigationParams = { ...action.web.parameters };
1734
+ } else if (
1735
+ action.web.link &&
1736
+ action.web.link.includes("scrollTo=")
1737
+ ) {
1738
+ const urlParams = new URLSearchParams(
1739
+ action.web.link.split("?")[1],
1740
+ );
1741
+ scrollToId = urlParams.get("scrollTo");
1742
+ }
1743
+
1744
+ let finalLink = action.web.link;
1745
+ let finalMessage = action.message || "";
1746
+
1747
+ const hasSchemeId = action.web.parameters?.schemeId &&
1748
+ action.web.parameters.schemeId !== "";
1749
+ let hasHelpName = null;
1750
+
1751
+ if (action.web.parameters?.button_title) {
1752
+ hasHelpName = true;
1753
+ } else if (action.web.parameters?.name) {
1754
+ hasHelpName = true;
1755
+ } else {
1756
+ hasHelpName = false;
1757
+ }
1758
+
1759
+ if (!hasSchemeId && !hasHelpName) {
1760
+ finalLink = "/help";
1761
+ finalMessage =
1762
+ "Please find more information about available schemes and services";
1763
+ }
1764
+ latestUiAction = {
1765
+ content: "",
1766
+ url: finalLink && !finalLink.startsWith("http")
1767
+ ? window.location.origin + finalLink
1768
+ : finalLink,
1769
+ originalUrl: finalLink,
1770
+ uiActionType: action.ui_action,
1771
+ scrollToId: scrollToId,
1772
+ navigationParams: {
1773
+ ...navigationParams,
1774
+ ...(hasHelpName && {
1775
+ name: action.web.parameters?.button_title ||
1776
+ action.web.parameters?.name,
1777
+ }),
1778
+ },
1779
+ };
1780
+ }
1781
+ }
1782
+
1783
+ // Chart Image Extraction
1784
+ if (
1785
+ data.analytics_visualization_node &&
1786
+ data.analytics_visualization_node.messages
1787
+ ) {
1788
+ const vizMessages =
1789
+ data.analytics_visualization_node.messages;
1790
+ if (vizMessages.length > 0) {
1791
+ const vizMessage = vizMessages[vizMessages.length - 1];
1792
+
1793
+ if (
1794
+ typeof vizMessage === "string" &&
1795
+ vizMessage.includes("additional_kwargs")
1796
+ ) {
1797
+ const kwargsStart =
1798
+ vizMessage.indexOf("additional_kwargs=") + 18;
1799
+ const kwargsEnd = vizMessage.indexOf(
1800
+ "} response_metadata",
1801
+ );
1802
+
1803
+ if (kwargsStart < kwargsEnd) {
1804
+ const kwargsString = vizMessage.substring(
1805
+ kwargsStart,
1806
+ kwargsEnd + 1,
1807
+ );
1808
+ try {
1809
+ let cleanedKwargsString = kwargsString
1810
+ .replace(/'/g, '"')
1811
+ .replace(/None/g, "null")
1812
+ .replace(/True/g, "true")
1813
+ .replace(/False/g, "false");
1814
+
1815
+ const additionalKwargs = JSON.parse(
1816
+ cleanedKwargsString,
1817
+ );
1818
+
1819
+ if (additionalKwargs.chart_image_url) {
1820
+ console.log(
1821
+ "🎯 Found chart image URL:",
1822
+ additionalKwargs.chart_image_url,
1823
+ );
1824
+ currentAdditionalKwargs = additionalKwargs;
1825
+ try {
1826
+ const chartUrl = String(
1827
+ additionalKwargs.chart_image_url,
1828
+ ).trim();
1829
+ if (chartUrl.startsWith("http")) {
1830
+ chartUrlFromViz = chartUrl;
1831
+ console.log(
1832
+ "🔖 Stored chartUrlFromViz:",
1833
+ chartUrlFromViz,
1834
+ );
1835
+ }
1836
+ } catch (e) {
1837
+ console.warn(
1838
+ "Failed to store chart image URL:",
1839
+ e,
1840
+ );
1841
+ }
1842
+ } else if (additionalKwargs.antv_chart_data) {
1843
+ console.log(
1844
+ "🎯 Found chart data (fallback):",
1845
+ additionalKwargs.antv_chart_data,
1846
+ );
1847
+ currentAdditionalKwargs = additionalKwargs;
1848
+ }
1849
+ } catch (e) {
1850
+ console.warn(
1851
+ "Failed to parse chart image additional_kwargs:",
1852
+ e,
1853
+ );
1854
+ }
1855
+ }
1856
+ }
1857
+ if (typeof vizMessage === "string") {
1858
+ const contentMatch = vizMessage.match(
1859
+ /content=['"]([^'"]+)['"]/,
1860
+ );
1861
+ if (contentMatch && contentMatch[1]) {
1862
+ const extractedUrl = contentMatch[1].trim();
1863
+ if (extractedUrl.startsWith("http")) {
1864
+ chartUrlFromViz = extractedUrl;
1865
+ console.log(
1866
+ "🔖 Stored extracted chartUrlFromViz:",
1867
+ chartUrlFromViz,
1868
+ );
1869
+ }
1870
+ }
1871
+ }
1872
+ }
1873
+ }
1874
+
1875
+ // Generic Content Extraction
1876
+ let lastContent = null;
1877
+ let lastNodeKey = null;
1878
+ let lastNodeMessages = null;
1879
+ Object.keys(data).forEach((nodeKey) => {
1880
+ if (
1881
+ nodeKey !== "audio_content" &&
1882
+ nodeKey !== "audio_processor" &&
1883
+ nodeKey !== "ui_action_mapper"
1884
+ ) {
1885
+ const nodeData = data[nodeKey];
1886
+ if (
1887
+ nodeData &&
1888
+ Array.isArray(nodeData.messages) &&
1889
+ nodeData.messages.length > 0
1890
+ ) {
1891
+ lastNodeKey = nodeKey;
1892
+ lastNodeMessages = nodeData.messages;
1893
+ }
1894
+ }
1895
+ });
1896
+ if (lastNodeMessages && lastNodeMessages.length > 0) {
1897
+ let lastMessage =
1898
+ lastNodeMessages[lastNodeMessages.length - 1];
1899
+ let content = "";
1900
+ if (typeof lastMessage === "string") {
1901
+ if (
1902
+ lastMessage.startsWith("content='") &&
1903
+ lastMessage.includes("' additional_kwargs")
1904
+ ) {
1905
+ const start = lastMessage.indexOf("content='") + 9;
1906
+ const end = lastMessage.indexOf("' additional_kwargs");
1907
+ content = lastMessage
1908
+ .substring(start, end)
1909
+ .replace(/\\n/g, "\n")
1910
+ .replace(/\\t/g, "\t")
1911
+ .replace(/\\"/g, '"')
1912
+ .replace(/\\'/g, "'");
1913
+ } else if (
1914
+ lastMessage.startsWith('content="') &&
1915
+ lastMessage.includes('" additional_kwargs')
1916
+ ) {
1917
+ const start = lastMessage.indexOf('content="') + 9;
1918
+ const end = lastMessage.indexOf('" additional_kwargs');
1919
+ content = lastMessage
1920
+ .substring(start, end)
1921
+ .replace(/\\n/g, "\n")
1922
+ .replace(/\\t/g, "\t")
1923
+ .replace(/\\"/g, '"')
1924
+ .replace(/\\'/g, "'");
1925
+ } else {
1926
+ const singleQuoteMatch = lastMessage.match(
1927
+ /content='([^']+)'/,
1928
+ );
1929
+ const doubleQuoteMatch = lastMessage.match(
1930
+ /content="([^"]+)"/,
1931
+ );
1932
+ if (singleQuoteMatch) {
1933
+ content = singleQuoteMatch[1];
1934
+ } else if (doubleQuoteMatch) {
1935
+ content = doubleQuoteMatch[1];
1936
+ } else {
1937
+ content = lastMessage;
1938
+ }
1939
+ }
1940
+ } else if (lastMessage && typeof lastMessage === "object") {
1941
+ if (typeof lastMessage.content === "string") {
1942
+ content = lastMessage.content;
1943
+ } else if (
1944
+ Array.isArray(lastMessage.content) &&
1945
+ lastMessage.content.length > 0
1946
+ ) {
1947
+ const textParts = lastMessage.content
1948
+ .map((part) =>
1949
+ typeof part === "string"
1950
+ ? part
1951
+ : typeof part?.text === "string"
1952
+ ? part.text
1953
+ : typeof part?.value === "string"
1954
+ ? part.value
1955
+ : ""
1956
+ )
1957
+ .filter(Boolean);
1958
+ content = textParts.join(" ").trim();
1959
+ } else {
1960
+ content = "";
1961
+ }
1962
+ }
1963
+ if (
1964
+ typeof content === "string" &&
1965
+ content.trim().length > 0
1966
+ ) {
1967
+ const looksLikeJson = content.trim().startsWith("{") ||
1968
+ content.trim().startsWith("[");
1969
+ const priority = getNodePriority(lastNodeKey);
1970
+ if (looksLikeJson && priority < 2) {
1971
+ console.log(
1972
+ `⏭️ Skipping JSON-like content from ${lastNodeKey}`,
1973
+ );
1974
+ } else {
1975
+ const MAX_LEN = 8000;
1976
+ const safeContent = content.length > MAX_LEN
1977
+ ? content.slice(0, MAX_LEN) + "\n\n… (truncated)"
1978
+ : content;
1979
+ currentMessage = safeContent;
1980
+ if (
1981
+ priority > bestMessagePriority ||
1982
+ (priority === bestMessagePriority &&
1983
+ safeContent.length > bestMessageText.length)
1984
+ ) {
1985
+ bestMessageText = safeContent;
1986
+ bestMessagePriority = priority;
1987
+ }
1988
+ hasReceivedMeaningfulResponse = true;
1989
+ console.log(
1990
+ `🟢 Found content from ${lastNodeKey}:`,
1991
+ safeContent.substring(0, 100) + "...",
1992
+ );
1993
+ }
1994
+ }
1995
+ }
1996
+
1997
+ // Process based on event type
1998
+ if (data) {
1999
+ // Set default event type for data messages without explicit event
2000
+ const eventType = currentEvent || "values";
2001
+
2002
+ console.log("🎵 Processing data message:", {
2003
+ eventType: eventType,
2004
+ currentEvent: currentEvent,
2005
+ dataKeys: Object.keys(data),
2006
+ requestId: requestId,
2007
+ });
2008
+
2009
+ // Handle audio content in response
2010
+ if (
2011
+ data.audio_content &&
2012
+ typeof data.audio_content === "string"
2013
+ ) {
2014
+ console.log("🎵 AUDIO CONTENT FOUND in text stream:", {
2015
+ audioContentLength: data.audio_content.length,
2016
+ audioContentPreview: data.audio_content.substring(0, 20) +
2017
+ "...",
2018
+ });
2019
+
2020
+ const audioResponseId = `text_response_${requestId}_${data.audio_content.substring(
2021
+ 0,
2022
+ 30,
2023
+ )
2024
+ }`;
2025
+ const alreadyPlayed = this.playedAudioIds.has(
2026
+ audioResponseId,
2027
+ );
2028
+
2029
+ console.log("🔊 Text stream audio content received:", {
2030
+ audioResponseId: audioResponseId,
2031
+ requestId: requestId,
2032
+ alreadyPlayed: alreadyPlayed,
2033
+ audioContentLength: data.audio_content.length,
2034
+ });
2035
+
2036
+ if (!alreadyPlayed) {
2037
+ this.playedAudioIds.add(audioResponseId);
2038
+ // Mark the current assistant message as having audio response
2039
+ console.log("🔍 Setting audio content for message:", {
2040
+ targetMessageId: assistantMessage.id,
2041
+ audioContentLength: data.audio_content.length,
2042
+ audioContentPreview:
2043
+ data.audio_content.substring(0, 20) + "...",
2044
+ });
2045
+ this._updateMessageInArray(assistantMessage.id, {
2046
+ hasAudioResponse: true,
2047
+ audioContent: data.audio_content,
2048
+ latestRunId: latestRunId,
2049
+ });
2050
+ console.log("✅ Stored audio response (text stream)");
2051
+
2052
+ // Clean up old audio IDs to prevent memory issues
2053
+ if (this.playedAudioIds.size > 10) {
2054
+ const audioIds = Array.from(this.playedAudioIds);
2055
+ this.playedAudioIds.clear();
2056
+ audioIds
2057
+ .slice(-5)
2058
+ .forEach((id) => this.playedAudioIds.add(id));
2059
+ }
2060
+ } else {
2061
+ console.log("⏭️ Skipping audio: already played");
2062
+ }
2063
+ } else {
2064
+ console.log(
2065
+ "❌ No audio_content found in text stream data",
2066
+ );
2067
+ }
2068
+
2069
+ // Handle audio content in response_processor
2070
+ if (
2071
+ data.response_processor &&
2072
+ data.response_processor.audio_content &&
2073
+ typeof data.response_processor.audio_content === "string"
2074
+ ) {
2075
+ console.log(
2076
+ "🔍 Found audio_content in response_processor:",
2077
+ {
2078
+ audioContentLength:
2079
+ data.response_processor.audio_content.length,
2080
+ audioContentPreview:
2081
+ data.response_processor.audio_content.substring(
2082
+ 0,
2083
+ 50,
2084
+ ) + "...",
2085
+ },
2086
+ );
2087
+
2088
+ const audioResponseId = `text_response_${requestId}_${data.response_processor.audio_content.substring(
2089
+ 0,
2090
+ 30,
2091
+ )
2092
+ }`;
2093
+ const alreadyPlayed = this.playedAudioIds.has(
2094
+ audioResponseId,
2095
+ );
2096
+
2097
+ console.log(
2098
+ "🔊 Response processor audio content received:",
2099
+ {
2100
+ audioResponseId: audioResponseId,
2101
+ requestId: requestId,
2102
+ alreadyPlayed: alreadyPlayed,
2103
+ audioContentLength:
2104
+ data.response_processor.audio_content.length,
2105
+ },
2106
+ );
2107
+
2108
+ if (!alreadyPlayed) {
2109
+ this.playedAudioIds.add(audioResponseId);
2110
+
2111
+ // Mark the current assistant message as having audio response
2112
+ console.log(
2113
+ "🔍 Setting response processor audio content for message:",
2114
+ {
2115
+ targetMessageId: assistantMessage.id,
2116
+ audioContentLength:
2117
+ data.response_processor.audio_content.length,
2118
+ audioContentPreview:
2119
+ data.response_processor.audio_content.substring(
2120
+ 0,
2121
+ 20,
2122
+ ) + "...",
2123
+ },
2124
+ );
2125
+ this._updateMessageInArray(assistantMessage.id, {
2126
+ latestRunId: latestRunId,
2127
+ hasAudioResponse: true,
2128
+ audioContent:
2129
+ data.response_processor.audio_content,
2130
+ });
2131
+ console.log(
2132
+ "✅ Found matching message, setting response processor audio content",
2133
+ );
2134
+
2135
+ // Clean up old audio IDs to prevent memory issues
2136
+ if (this.playedAudioIds.size > 10) {
2137
+ const audioIds = Array.from(this.playedAudioIds);
2138
+ this.playedAudioIds.clear();
2139
+ audioIds
2140
+ .slice(-5)
2141
+ .forEach((id) => this.playedAudioIds.add(id));
2142
+ }
2143
+ } else {
2144
+ console.log(
2145
+ "⏭️ Skipping response processor audio: already played",
2146
+ );
2147
+ }
2148
+ }
2149
+ }
2150
+ } catch (parseError) {
2151
+ console.log("⚠️ Failed to parse streaming data:", parseError);
2152
+ }
2153
+ }
2154
+ }
2155
+ }
2156
+
2157
+ // Ensure streaming is marked as complete for this specific assistant message
2158
+ console.log("🏁 Finalizing assistant message:", assistantMessage.id);
2159
+
2160
+ // Add a small delay to ensure audio content is properly set
2161
+ await new Promise((resolve) => setTimeout(resolve, 100));
2162
+
2163
+ // Get the current state to ensure we have the latest audio content
2164
+ const currentAssistantMessage = this.messages.find(
2165
+ (msg) => msg.id === assistantMessage.id,
2166
+ );
2167
+ console.log("🔍 Current assistant message before finalization:", {
2168
+ messageId: assistantMessage.id,
2169
+ hasAudioContent: !!currentAssistantMessage?.audioContent,
2170
+ hasAudioResponse: currentAssistantMessage?.hasAudioResponse,
2171
+ audioContentLength: currentAssistantMessage?.audioContent?.length || 0,
2172
+ });
2173
+
2174
+ // If message was already finalized at 'end' with content, do not override
2175
+ if (
2176
+ endEventFinalized &&
2177
+ endFinalContent &&
2178
+ endFinalContent.length > 10
2179
+ ) {
2180
+ console.log(
2181
+ "✅ Message already finalized on end event; skipping overwrite",
2182
+ );
2183
+ } else {
2184
+ // Check if we have a meaningful response to display
2185
+ const finalText = bestMessageText && bestMessageText.length > 5
2186
+ ? bestMessageText
2187
+ : currentMessage;
2188
+ // If a chart URL was captured from the viz node, prefer it and show only the image
2189
+ const contentToSet =
2190
+ chartUrlFromViz && String(chartUrlFromViz).startsWith("http")
2191
+ ? chartUrlFromViz
2192
+ : finalText;
2193
+
2194
+ if (contentToSet && contentToSet.length > 10) {
2195
+ console.log("🔍 Finalizing message with content:", {
2196
+ messageId: assistantMessage.id,
2197
+ currentMessageLength: String(contentToSet).length,
2198
+ currentMessagePreview: String(contentToSet).substring(0, 50) +
2199
+ "...",
2200
+ });
2201
+ const currentMsg = this.messages.find(m => m.id === assistantMessage.id);
2202
+ console.log("🔍 Finalizing message:", {
2203
+ latestRunId,
2204
+ messageId: assistantMessage.id,
2205
+ existingAudioContent: !!currentMsg?.audioContent,
2206
+ existingAudioResponse: currentMsg?.hasAudioResponse,
2207
+ audioContentLength: currentMsg?.audioContent?.length || 0,
2208
+ });
2209
+ this._updateMessageInArray(assistantMessage.id, {
2210
+ latestRunId: latestRunId,
2211
+ content: contentToSet,
2212
+ additional_kwargs: currentAdditionalKwargs,
2213
+ isStreaming: false,
2214
+ isProcessing: false,
2215
+ hasAudioResponse: currentMsg?.hasAudioResponse || false, // Preserve existing audio response state
2216
+ audioContent: currentMsg?.audioContent || null, // Preserve existing audio content
2217
+ });
2218
+ } else {
2219
+ // If no meaningful response was found, show a clear, user-friendly fallback
2220
+ console.log("🔍 Finalizing message with friendly fallback");
2221
+ const currentMsg = this.messages.find(m => m.id === assistantMessage.id);
2222
+ this._updateMessageInArray(assistantMessage.id, {
2223
+ content: "Processing your request...",
2224
+ latestRunId: latestRunId,
2225
+ additional_kwargs: currentAdditionalKwargs,
2226
+ isStreaming: false,
2227
+ isProcessing: false,
2228
+ hasAudioResponse: currentMsg?.hasAudioResponse || false,
2229
+ audioContent: currentMsg?.audioContent || null,
2230
+ });
2231
+ }
2232
+ }
2233
+
2234
+ // Handle UI actions if present
2235
+ if (latestUiAction) {
2236
+ console.log("🔗 Adding UI action message:", latestUiAction);
2237
+ const uiActionMessage = {
2238
+ id: Date.now() + 2,
2239
+ content: "",
2240
+ sender: "assistant",
2241
+ timestamp: new Date().toISOString(),
2242
+ isUiAction: true,
2243
+ url: latestUiAction.url,
2244
+ uiActionType: latestUiAction.uiActionType,
2245
+ scrollToId: latestUiAction.scrollToId,
2246
+ navigationParams: latestUiAction.navigationParams,
2247
+ originalUrl: latestUiAction.originalUrl,
2248
+ isScrollAction: latestUiAction.uiActionType === "scroll",
2249
+ isNavigateStateAction:
2250
+ latestUiAction.uiActionType === "navigate-state",
2251
+ };
2252
+
2253
+ const lastMessage = this.messages[this.messages.length - 1];
2254
+ if (lastMessage) {
2255
+ const updatedLastMessage = {
2256
+ ...lastMessage,
2257
+ uiActionMessage,
2258
+ latestRunId,
2259
+ };
2260
+ console.log("UPDATED LAST MESSAGE", updatedLastMessage);
2261
+ this._updateMessageInArray(lastMessage.id, {
2262
+ uiActionMessage,
2263
+ latestRunId,
2264
+ });
2265
+ }
2266
+ }
2267
+ } catch (error) {
2268
+ console.error("❌ Streaming error:", error);
2269
+ const currentMsg = this.messages.find(m => m.id === assistantMessage.id);
2270
+ this._updateMessageInArray(assistantMessage.id, {
2271
+ isStreaming: false,
2272
+ isProcessing: false,
2273
+ isError: true,
2274
+ content: "Error occurred while processing request",
2275
+ latestRunId: latestRunId || currentMsg?.latestRunId, // Preserve or set latestRunId
2276
+ });
2277
+ }
2278
+ }
2279
+
2280
+ // Fetch assistant ID from API
2281
+ async fetchAssistantId() {
2282
+ try {
2283
+ console.log("🔍 Fetching assistant ID from API...");
2284
+
2285
+ // Use provided authToken or fallback to accessToken
2286
+ const authToken = this.authToken || this.accessToken;
2287
+ const supabaseToken = this.supabaseToken || authToken;
2288
+
2289
+ const response = await fetch(`${this.langgraphUrl}/assistants/search`, {
2290
+ method: "POST",
2291
+ headers: {
2292
+ "Content-Type": "application/json",
2293
+ Authorization: `Bearer ${authToken}`,
2294
+ "x-supabase-access-token": supabaseToken,
2295
+ Origin: window.location.origin || "*",
2296
+ },
2297
+ body: JSON.stringify({
2298
+ limit: 100,
2299
+ offset: 0,
2300
+ }),
2301
+ });
2302
+
2303
+ if (!response.ok) {
2304
+ console.log(
2305
+ `❌ API call failed with status: ${response.status} - backend may have rejected due to auth`,
2306
+ );
2307
+ return null; // Return null instead of throwing to let backend handle auth
2308
+ }
2309
+
2310
+ const assistants = await response.json();
2311
+ console.log("📋 Available assistants:", assistants);
2312
+
2313
+ // Log assistant details for debugging
2314
+ assistants.forEach((assistant, index) => {
2315
+ console.log(`Assistant ${index}:`, {
2316
+ id: assistant.assistant_id,
2317
+ name: assistant.name,
2318
+ hasName: !!assistant.name,
2319
+ type: typeof assistant.name,
2320
+ });
2321
+ });
2322
+
2323
+ // Find the assistant with name "Default Assistant"
2324
+ const defaultAssistant = assistants.filter(
2325
+ (assistant) => assistant.name && assistant.name.includes("Default"),
2326
+ )[0];
2327
+
2328
+ if (defaultAssistant) {
2329
+ console.log(
2330
+ "✅ Found Default Assistant:",
2331
+ defaultAssistant.assistant_id,
2332
+ );
2333
+ this.assistantId = defaultAssistant.assistant_id;
2334
+ return defaultAssistant.assistant_id;
2335
+ } else {
2336
+ console.log("⚠️ Default Assistant not found in the list");
2337
+
2338
+ // Find first assistant with a valid name
2339
+ const firstValidAssistant = assistants.find(
2340
+ (assistant) => assistant.name && assistant.name.trim() !== "",
2341
+ );
2342
+
2343
+ if (firstValidAssistant) {
2344
+ console.log(
2345
+ "⚠️ Using first valid assistant as fallback:",
2346
+ firstValidAssistant.assistant_id,
2347
+ "Name:",
2348
+ firstValidAssistant.name,
2349
+ );
2350
+ this.assistantId = firstValidAssistant.assistant_id;
2351
+ return firstValidAssistant.assistant_id;
2352
+ } else {
2353
+ console.log("❌ No assistants with valid names available");
2354
+ return null;
2355
+ }
2356
+ }
2357
+ } catch (error) {
2358
+ console.error("❌ Error fetching assistant ID:", error);
2359
+ console.log("🔍 Error details:", {
2360
+ name: error.name,
2361
+ message: error.message,
2362
+ isNetworkError: error.name === "TypeError",
2363
+ });
2364
+ return null; // Return null instead of throwing to let backend handle auth
2365
+ }
2366
+ }
2367
+
2368
+ // Load conversation history for a thread
2369
+ async loadHistory(threadId) {
2370
+ try {
2371
+ const response = await fetch(
2372
+ `${this.langgraphUrl}/threads/${threadId}/history`,
2373
+ {
2374
+ method: "POST",
2375
+ headers: this.getHeaders(),
2376
+ body: JSON.stringify({ limit: 10 }),
2377
+ },
2378
+ );
2379
+
2380
+ if (response.ok) {
2381
+ const history = await response.json();
2382
+ console.log("📜 Loaded history:", history);
2383
+
2384
+ // Get only the LATEST state (first item) which contains the complete conversation
2385
+ const latestState = history[0];
2386
+
2387
+ if (!latestState?.values?.messages) {
2388
+ console.log("No messages found in history");
2389
+ return;
2390
+ }
2391
+
2392
+ // Convert messages from the latest state only
2393
+ const historyMessages = [];
2394
+ latestState.values.messages.forEach((msg, msgIndex) => {
2395
+ if (msg.type === "human") {
2396
+ // Handle both text and audio content
2397
+ let content = "";
2398
+ let isAudio = false;
2399
+
2400
+ if (Array.isArray(msg.content)) {
2401
+ const textContent = msg.content.find(
2402
+ (c) => c.type === "text",
2403
+ );
2404
+ const audioContent = msg.content.find(
2405
+ (c) => c.type === "audio",
2406
+ );
2407
+
2408
+ if (audioContent) {
2409
+ content = "🎤 Audio message";
2410
+ isAudio = true;
2411
+ } else if (textContent) {
2412
+ content = textContent.text;
2413
+ }
2414
+ } else {
2415
+ content = msg.content;
2416
+ }
2417
+
2418
+ historyMessages.push({
2419
+ id: `history-${msgIndex}-human`,
2420
+ content: content,
2421
+ sender: "user",
2422
+ timestamp: new Date().toISOString(),
2423
+ isAudio: isAudio,
2424
+ });
2425
+ } else if (msg.type === "ai") {
2426
+ historyMessages.push({
2427
+ id: `history-${msgIndex}-ai`,
2428
+ content: msg.content,
2429
+ sender: "assistant",
2430
+ timestamp: new Date().toISOString(),
2431
+ });
2432
+ }
2433
+ });
2434
+
2435
+ if (historyMessages && historyMessages.length > 0) {
2436
+ console.log(`📝 Loading ${historyMessages.length} messages from history`);
2437
+ // Add history messages to the messages array
2438
+ historyMessages.forEach(msg => {
2439
+ this._addMessageToArray(msg);
2440
+ });
2441
+ }
2442
+ }
2443
+ } catch (error) {
2444
+ console.error("❌ Failed to load history:", error);
2445
+ }
2446
+ }
2447
+
2448
+ // Initialize chat - fetch assistant ID and create thread
2449
+ async initializeChat() {
2450
+ try {
2451
+ console.log("🔄 Initializing chat...");
2452
+
2453
+ // Always attempt API calls, let backend handle authentication
2454
+ const currentToken = localStorage.getItem("DfsWeb.access-token");
2455
+ console.log(
2456
+ "🔑 Access token status:",
2457
+ currentToken ? "Present" : "Empty - backend will handle",
2458
+ );
2459
+
2460
+ // Always attempt to fetch assistant ID
2461
+ console.log("📞 Making API call to /assistants/search...");
2462
+ const fetchedAssistantId = await this.fetchAssistantId();
2463
+
2464
+ if (!fetchedAssistantId) {
2465
+ console.log(
2466
+ "❌ Failed to fetch assistant ID - backend may have rejected due to auth",
2467
+ );
2468
+ this.isConnected = false;
2469
+ this.initialized = true;
2470
+ return;
2471
+ }
2472
+
2473
+ console.log(
2474
+ "✅ Assistant ID fetched successfully:",
2475
+ fetchedAssistantId,
2476
+ );
2477
+
2478
+ const storedUser = localStorage.getItem("DfsWeb.user-info");
2479
+ const user = storedUser ? JSON.parse(storedUser) : null;
2480
+
2481
+ // Always attempt to create thread
2482
+ console.log("📞 Making API call to /threads...");
2483
+ const response = await fetch(`${this.langgraphUrl}/threads`, {
2484
+ method: "POST",
2485
+ headers: this.getHeaders(),
2486
+ body: JSON.stringify({
2487
+ metadata: {
2488
+ user_id: user?.id || null,
2489
+ user_uuid: user?.uuid || null,
2490
+ },
2491
+ }),
2492
+ });
2493
+
2494
+ if (response.ok) {
2495
+ const thread = await response.json();
2496
+ this.threadId = thread.thread_id;
2497
+ this.isConnected = true;
2498
+ console.log("✅ Thread created:", thread.thread_id, "\n", thread);
2499
+
2500
+ // Load conversation history
2501
+ await this.loadHistory(thread.thread_id);
2502
+ } else {
2503
+ console.log("Thread created NOT");
2504
+ const errorText = await response.text();
2505
+ console.error(
2506
+ "❌ Failed to create thread:",
2507
+ response.status,
2508
+ errorText,
2509
+ );
2510
+ this.isConnected = false;
2511
+ }
445
2512
 
446
- _escapeHtml(text) {
447
- const div = document.createElement("div");
448
- div.textContent = text;
449
- return div.innerHTML;
2513
+ this.initialized = true;
2514
+ } catch (error) {
2515
+ console.error("❌ Thread creation error:", error);
2516
+ this.isConnected = false;
2517
+ this.initialized = true;
2518
+ }
450
2519
  }
451
2520
  }
452
2521
 
453
- // Export as UMD and ESM compatible
454
- if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
455
- module.exports = ChatWidget;
456
- } else {
457
- window.ChatWidget = ChatWidget;
458
- }
2522
+ // Export as ESM (Rollup will handle UMD conversion)
2523
+ export default ChatWidget;