mini-chat-bot-widget 0.6.0 → 0.9.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.
@@ -4,19 +4,34 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ChatWidget = factory());
5
5
  })(this, (function () { 'use strict';
6
6
 
7
- function _classCallCheck(a, n) {
8
- if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
7
+ function _defineProperty(e, r, t) {
8
+ return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
9
+ value: t,
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true
13
+ }) : e[r] = t, e;
9
14
  }
10
- function _defineProperties(e, r) {
11
- for (var t = 0; t < r.length; t++) {
12
- var o = r[t];
13
- o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o);
15
+ function ownKeys(e, r) {
16
+ var t = Object.keys(e);
17
+ if (Object.getOwnPropertySymbols) {
18
+ var o = Object.getOwnPropertySymbols(e);
19
+ r && (o = o.filter(function (r) {
20
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
21
+ })), t.push.apply(t, o);
14
22
  }
23
+ return t;
15
24
  }
16
- function _createClass(e, r, t) {
17
- return r && _defineProperties(e.prototype, r), Object.defineProperty(e, "prototype", {
18
- writable: false
19
- }), e;
25
+ function _objectSpread2(e) {
26
+ for (var r = 1; r < arguments.length; r++) {
27
+ var t = null != arguments[r] ? arguments[r] : {};
28
+ r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
29
+ _defineProperty(e, r, t[r]);
30
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
31
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
32
+ });
33
+ }
34
+ return e;
20
35
  }
21
36
  function _toPrimitive(t, r) {
22
37
  if ("object" != typeof t || !t) return t;
@@ -26,134 +41,3051 @@
26
41
  if ("object" != typeof i) return i;
27
42
  throw new TypeError("@@toPrimitive must return a primitive value.");
28
43
  }
29
- return (String )(t);
44
+ return ("string" === r ? String : Number)(t);
30
45
  }
31
46
  function _toPropertyKey(t) {
32
47
  var i = _toPrimitive(t, "string");
33
48
  return "symbol" == typeof i ? i : i + "";
34
49
  }
35
50
 
36
- function getDefaultExportFromCjs (x) {
37
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
51
+ // text-chat-screen.js - Text chat screen component
52
+
53
+ class TextChatScreen {
54
+ constructor() {
55
+ let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
56
+ this.placeholder = options.placeholder || "Type your message...";
57
+ this.primaryColor = options.primaryColor || "#1a5c4b";
58
+ this.title = options.title || "Chat Assistant";
59
+ this.onMessage = options.onMessage || (() => {});
60
+ this.onBack = options.onBack || (() => {});
61
+ this.onOpenDrawer = options.onOpenDrawer || (() => {});
62
+ this.onClose = options.onClose || (() => {});
63
+ this.container = null;
64
+ this.messages = options.messages;
65
+ this.sendMessage = options.sendMessage || null;
66
+ this.contentBlocks = options.contentBlocks || [];
67
+ this.onContentBlocksChange = options.onContentBlocksChange || (() => {});
68
+ this.navigateToAudioScreen = options.navigateToAudioScreen;
69
+ // Supported file types
70
+ this.SUPPORTED_FILE_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp"];
71
+ }
72
+ render(container) {
73
+ this.container = container;
74
+ this._applyStyles();
75
+ container.innerHTML = "\n <div class=\"text-chat-screen\">\n <div class=\"chat-header\">\n <div class=\"chat-header-content\">\n <button class=\"chat-back\" id=\"text-chat-menu\">\n <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-menu-icon lucide-menu\"><path d=\"M4 5h16\"/><path d=\"M4 12h16\"/><path d=\"M4 19h16\"/></svg>\n </button>\n \n <div class=\"chat-header-text\">\n <div class=\"chat-title\">".concat(this.title, "</div>\n </div>\n </div>\n <button class=\"chat-close\" id=\"navigate-to-audio-screen\">\n <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>\n </button>\n &#8203; &#8203; &#8203;\n <button class=\"chat-close\" id=\"text-chat-close\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n <div class=\"chat-messages\" id=\"chat-messages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-text\">\uD83D\uDC4B Hello! I'm your AI assistant. How can I help you today?</div>\n </div>\n </div>\n <div class=\"file-attachments-container\" id=\"file-attachments-container\" style=\"display: none;\"></div>\n <div class=\"chat-input-wrapper\">\n <div class=\"file-upload-controls\">\n <input\n type=\"file\"\n id=\"file-upload-input\"\n accept=\"image/jpeg,image/png,image/gif,image/webp\"\n style=\"display: none;\"\n />\n <button id=\"file-upload-button\" class=\"file-upload-button\" title=\"Attach files\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48\"></path>\n </svg>\n </button>\n </div>\n <input type=\"text\" id=\"chat-input\" placeholder=\"").concat(this.placeholder, "\" autocomplete=\"off\" />\n <button id=\"chat-send\" disabled>\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line>\n <polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon>\n </svg>\n </button>\n </div>\n </div>\n ");
76
+
77
+ // Bind header events
78
+ const menuBtn = container.querySelector("#text-chat-menu");
79
+ const closeBtn = container.querySelector("#text-chat-close");
80
+ const navigateToAudioBtn = container.querySelector("#navigate-to-audio-screen");
81
+ if (menuBtn) {
82
+ menuBtn.addEventListener("click", () => {
83
+ if (this.onOpenDrawer) {
84
+ this.onOpenDrawer();
85
+ } else if (this.onBack) {
86
+ this.onBack();
87
+ }
88
+ });
89
+ }
90
+ if (closeBtn) {
91
+ closeBtn.addEventListener("click", () => {
92
+ if (this.onClose) this.onClose();
93
+ });
94
+ }
95
+ if (navigateToAudioBtn) {
96
+ navigateToAudioBtn.addEventListener("click", () => {
97
+ if (this.navigateToAudioScreen) this.navigateToAudioScreen();
98
+ });
99
+ }
100
+
101
+ // Bind text chat events
102
+ const input = container.querySelector("#chat-input");
103
+ const sendBtn = container.querySelector("#chat-send");
104
+ const fileInput = container.querySelector("#file-upload-input");
105
+ const fileUploadBtn = container.querySelector("#file-upload-button");
106
+
107
+ // File upload handler
108
+ fileUploadBtn.addEventListener("click", () => {
109
+ fileInput.click();
110
+ });
111
+ fileInput.addEventListener("change", e => {
112
+ this.handleFileUpload(e);
113
+ });
114
+
115
+ // Update send button state based on input and contentBlocks
116
+ const updateSendButton = () => {
117
+ const hasText = input.value.trim().length > 0;
118
+ const hasFiles = this.contentBlocks.length > 0;
119
+ sendBtn.disabled = !(hasText || hasFiles);
120
+ };
121
+ input.addEventListener("input", () => {
122
+ updateSendButton();
123
+ });
124
+ const send = async () => {
125
+ const text = input.value.trim();
126
+ if (!text && this.contentBlocks.length === 0) return;
127
+
128
+ // Add user message to UI immediately
129
+ this.addMessage(text || "📎 File attachments", true);
130
+ input.value = "";
131
+ sendBtn.disabled = true;
132
+ this.hideTypingIndicator();
133
+
134
+ // Use the sendMessage function from widget if available
135
+ if (this.sendMessage) {
136
+ try {
137
+ await this.sendMessage(text, this.contentBlocks);
138
+ // Content blocks are cleared in widget's sendMessage, but ensure UI is updated
139
+ this.contentBlocks = [];
140
+ this.onContentBlocksChange(this.contentBlocks);
141
+ this.renderFilePreview(); // Update preview
142
+ this.updateSendButton();
143
+ } catch (error) {
144
+ console.error("Error sending message:", error);
145
+ this.addMessage("Sorry, I encountered an error. Please try again.", false);
146
+ } finally {
147
+ this.hideTypingIndicator();
148
+ }
149
+ } else {
150
+ // Fallback to old behavior
151
+ setTimeout(() => {
152
+ this.hideTypingIndicator();
153
+ this.handleUserMessage(text);
154
+ }, 1000 + Math.random() * 1000);
155
+ }
156
+ };
157
+ sendBtn.addEventListener("click", send);
158
+ input.addEventListener("keypress", e => {
159
+ if (e.key === "Enter" && !sendBtn.disabled) send();
160
+ });
161
+
162
+ // Focus input when screen loads
163
+ setTimeout(() => input.focus(), 100);
164
+
165
+ // Render file preview if contentBlocks exist
166
+ this.renderFilePreview();
167
+
168
+ // Sync existing messages on render
169
+ if (this.messages && this.messages.length > 0) {
170
+ setTimeout(() => {
171
+ this._syncMessages(this.messages);
172
+ }, 50);
173
+ }
174
+ }
175
+
176
+ // Convert file to content block
177
+ fileToContentBlock(file) {
178
+ return new Promise((resolve, reject) => {
179
+ const reader = new FileReader();
180
+ reader.onload = () => {
181
+ const base64Data = reader.result.split(",")[1]; // Remove data:mime;base64, prefix
182
+
183
+ if (this.SUPPORTED_FILE_TYPES.includes(file.type)) {
184
+ resolve({
185
+ type: "image",
186
+ source_type: "base64",
187
+ mime_type: file.type,
188
+ data: base64Data,
189
+ metadata: {
190
+ name: file.name,
191
+ size: file.size
192
+ }
193
+ });
194
+ } else {
195
+ reject(new Error("Unsupported file type: ".concat(file.type)));
196
+ }
197
+ };
198
+ reader.onerror = () => {
199
+ reject(new Error("Failed to read file: ".concat(file.name)));
200
+ };
201
+ reader.readAsDataURL(file);
202
+ });
203
+ }
204
+
205
+ // Check if file is duplicate
206
+ isDuplicateFile(file, existingBlocks) {
207
+ if (this.SUPPORTED_FILE_TYPES.includes(file.type)) {
208
+ return existingBlocks.some(block => {
209
+ var _block$metadata;
210
+ return block.type === "image" && ((_block$metadata = block.metadata) === null || _block$metadata === void 0 ? void 0 : _block$metadata.name) === file.name && block.mime_type === file.type;
211
+ });
212
+ }
213
+ return false;
214
+ }
215
+
216
+ // Format file size
217
+ formatFileSize(bytes) {
218
+ if (bytes === 0) return "0 Bytes";
219
+ const k = 1024;
220
+ const sizes = ["Bytes", "KB", "MB", "GB"];
221
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
222
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
223
+ }
224
+
225
+ // Get file type display name
226
+ getFileTypeDisplay(mimeType) {
227
+ switch (mimeType) {
228
+ case "image/jpeg":
229
+ case "image/jpg":
230
+ return "JPEG Image";
231
+ case "image/png":
232
+ return "PNG Image";
233
+ case "image/gif":
234
+ return "GIF Image";
235
+ case "image/webp":
236
+ return "WebP Image";
237
+ default:
238
+ return "Unknown File";
239
+ }
240
+ }
241
+
242
+ // Handle file upload
243
+ async handleFileUpload(e) {
244
+ const files = e.target.files;
245
+ if (!files) return;
246
+ const fileArray = Array.from(files);
247
+ const validFiles = fileArray.filter(file => this.SUPPORTED_FILE_TYPES.includes(file.type));
248
+ const invalidFiles = fileArray.filter(file => !this.SUPPORTED_FILE_TYPES.includes(file.type));
249
+ if (invalidFiles.length > 0) {
250
+ console.warn("Invalid file type. Please upload a JPEG, PNG, GIF, or WEBP image.");
251
+ // Could show a toast/alert here
252
+ }
253
+
254
+ // Since we only allow 1 file, we take the first valid one and replace existing
255
+ const fileToProcess = validFiles[0];
256
+ if (fileToProcess) {
257
+ try {
258
+ const newBlocks = await this.fileToContentBlock(fileToProcess);
259
+ // Replace existing blocks with new one
260
+ this.contentBlocks = [newBlocks];
261
+ this.onContentBlocksChange(this.contentBlocks);
262
+ this.renderFilePreview();
263
+ this.updateSendButton();
264
+ } catch (error) {
265
+ console.error("Failed to process files: ".concat(error.message));
266
+ }
267
+ }
268
+
269
+ // Clear the input
270
+ e.target.value = "";
271
+ }
272
+
273
+ // Remove content block
274
+ removeBlock(index) {
275
+ this.contentBlocks = this.contentBlocks.filter((_, i) => i !== index);
276
+ this.onContentBlocksChange(this.contentBlocks);
277
+ this.renderFilePreview();
278
+ this.updateSendButton();
279
+ }
280
+
281
+ // Update send button state
282
+ updateSendButton() {
283
+ if (!this.container) return;
284
+ const input = this.container.querySelector("#chat-input");
285
+ const sendBtn = this.container.querySelector("#chat-send");
286
+ if (!input || !sendBtn) return;
287
+ const hasText = input.value.trim().length > 0;
288
+ const hasFiles = this.contentBlocks.length > 0;
289
+ sendBtn.disabled = !(hasText || hasFiles);
290
+ }
291
+
292
+ // Render file preview
293
+ renderFilePreview() {
294
+ if (!this.container) return;
295
+ const container = this.container.querySelector("#file-attachments-container");
296
+ if (!container) return;
297
+ if (!this.contentBlocks || this.contentBlocks.length === 0) {
298
+ container.innerHTML = "";
299
+ container.style.display = "none";
300
+ return;
301
+ }
302
+ container.style.display = "flex";
303
+ container.innerHTML = this.contentBlocks.map((block, index) => {
304
+ var _block$metadata2, _block$metadata3, _block$metadata4;
305
+ const displayName = ((_block$metadata2 = block.metadata) === null || _block$metadata2 === void 0 ? void 0 : _block$metadata2.filename) || ((_block$metadata3 = block.metadata) === null || _block$metadata3 === void 0 ? void 0 : _block$metadata3.name) || "file";
306
+ const size = this.formatFileSize(((_block$metadata4 = block.metadata) === null || _block$metadata4 === void 0 ? void 0 : _block$metadata4.size) || 0);
307
+ const isImage = block.type === "image";
308
+ const src = isImage ? "data:".concat(block.mime_type, ";base64,").concat(block.data) : null;
309
+ return "\n <div class=\"file-attachment ".concat(isImage ? "has-thumbnail" : "", "\" data-index=\"").concat(index, "\">\n ").concat(isImage ? "\n <div class=\"file-thumbnail\">\n <img src=\"".concat(src, "\" alt=\"").concat(displayName, "\" class=\"file-thumbnail-image\" />\n </div>\n ") : '<div class="file-attachment-icon">📄</div>', "\n <div class=\"file-attachment-info\">\n <div class=\"file-attachment-name\" title=\"").concat(displayName, "\">\n ").concat(displayName, "\n </div>\n <div class=\"file-attachment-size\">").concat(size, "</div>\n </div>\n <button\n class=\"file-attachment-remove\"\n data-index=\"").concat(index, "\"\n title=\"Remove file\"\n >\n \u2715\n </button>\n </div>\n ");
310
+ }).join("");
311
+
312
+ // Bind remove buttons
313
+ container.querySelectorAll(".file-attachment-remove").forEach(btn => {
314
+ btn.addEventListener("click", e => {
315
+ e.stopPropagation();
316
+ const index = parseInt(btn.getAttribute("data-index"));
317
+ this.removeBlock(index);
318
+ });
319
+ });
320
+
321
+ // Bind image click for expansion (optional - can add modal later)
322
+ container.querySelectorAll(".file-thumbnail-image").forEach(img => {
323
+ img.addEventListener("click", e => {
324
+ e.stopPropagation();
325
+ // Could add image expansion modal here
326
+ });
327
+ });
328
+ }
329
+ _formatTime(date) {
330
+ let hours = date.getHours();
331
+ const minutes = date.getMinutes();
332
+ const ampm = hours >= 12 ? 'PM' : 'AM';
333
+ hours = hours % 12;
334
+ hours = hours ? hours : 12;
335
+ const strTime = hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0') + ' ' + ampm;
336
+ return strTime;
337
+ }
338
+ addMessage(text, isUser) {
339
+ let messageData = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
340
+ console.log("Is user", isUser);
341
+ if (!this.container) return;
342
+ const messagesContainer = this.container.querySelector("#chat-messages");
343
+ if (!messagesContainer) return;
344
+ const welcome = messagesContainer.querySelector(".chat-welcome");
345
+ if (welcome) welcome.remove();
346
+
347
+ // Check if message already exists (for syncing)
348
+ const messageId = (messageData === null || messageData === void 0 ? void 0 : messageData.id) || Date.now();
349
+ console.log("Message ID", messageId);
350
+ const existingMessage = messagesContainer.querySelector("[data-message-id=\"".concat(messageId, "\"]"));
351
+ if (existingMessage && messageData) {
352
+ // Update existing message
353
+ const bubble = existingMessage.querySelector(".message-bubble");
354
+ if (bubble) {
355
+ // Parse markdown for assistant messages, escape for user messages
356
+ const content = messageData.content || text;
357
+ bubble.innerHTML = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
358
+ }
359
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
360
+ return;
361
+ }
362
+ const messageEl = document.createElement("div");
363
+ messageEl.className = "chat-message ".concat(isUser ? "user" : "bot");
364
+ messageEl.setAttribute("data-message-id", messageId);
365
+
366
+ // Handle different message types
367
+ let content = text;
368
+ if (messageData) {
369
+ if (messageData.isStreaming) {
370
+ content = messageData.content || "Thinking...";
371
+ } else if (messageData.isError) {
372
+ content = messageData.content || "Error occurred";
373
+ messageEl.classList.add("error");
374
+ } else {
375
+ content = messageData.content || text;
376
+ }
377
+ }
378
+
379
+ // Build attachments HTML if present
380
+ let attachmentsHTML = "";
381
+ if (messageData && messageData.attachments && messageData.attachments.length > 0) {
382
+ attachmentsHTML = "\n <div class=\"message-attachments\">\n ".concat(messageData.attachments.map((attachment, index) => {
383
+ var _attachment$metadata, _attachment$metadata2, _attachment$metadata3;
384
+ const displayName = ((_attachment$metadata = attachment.metadata) === null || _attachment$metadata === void 0 ? void 0 : _attachment$metadata.name) || ((_attachment$metadata2 = attachment.metadata) === null || _attachment$metadata2 === void 0 ? void 0 : _attachment$metadata2.filename) || "Unknown file";
385
+ const isImage = attachment.type === "image";
386
+ const imageSrc = isImage && attachment.data ? "data:".concat(attachment.mime_type, ";base64,").concat(attachment.data) : null;
387
+ return "\n <div class=\"message-attachment ".concat(isImage ? "image" : "pdf", "\" data-attachment-index=\"").concat(index, "\">\n <span class=\"message-attachment-icon\">\n ").concat(isImage ? "🖼️" : "📄", "\n </span>\n <div class=\"message-attachment-info\">\n <div \n class=\"message-attachment-name ").concat(isImage ? "clickable" : "", "\"\n ").concat(isImage && imageSrc ? "data-image-src=\"".concat(imageSrc, "\" data-image-alt=\"").concat(displayName, "\"") : "", "\n style=\"cursor: ").concat(isImage ? "pointer" : "default", ";\"\n >\n ").concat(this._escapeHtml(displayName), "\n </div>\n <div class=\"message-attachment-size\">\n ").concat(this.formatFileSize(((_attachment$metadata3 = attachment.metadata) === null || _attachment$metadata3 === void 0 ? void 0 : _attachment$metadata3.size) || 0), " \u2022 ").concat(this.getFileTypeDisplay(attachment.mime_type), "\n </div>\n </div>\n </div>\n ");
388
+ }).join(""), "\n </div>\n ");
389
+ }
390
+ const timestamp = messageData !== null && messageData !== void 0 && messageData.timestamp ? new Date(messageData.timestamp) : new Date();
391
+ const formattedTime = this._formatTime(timestamp);
392
+
393
+ // Parse markdown for assistant messages, escape for user messages
394
+ const renderedContent = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
395
+ messageEl.innerHTML = "\n <div class=\"message-content\">\n ".concat(attachmentsHTML, "\n <div class=\"message-bubble\">\n ").concat(renderedContent, "\n </div>\n <div class=\"message-time\">").concat(formattedTime, "</div>\n </div>\n ");
396
+
397
+ // Bind image click handlers for expansion
398
+ if (messageData && messageData.attachments) {
399
+ messageEl.querySelectorAll(".message-attachment-name.clickable").forEach(el => {
400
+ el.addEventListener("click", e => {
401
+ e.stopPropagation();
402
+ const imageSrc = el.getAttribute("data-image-src");
403
+ const imageAlt = el.getAttribute("data-image-alt");
404
+ if (imageSrc) {
405
+ this.showExpandedImage(imageSrc, imageAlt);
406
+ }
407
+ });
408
+ });
409
+ }
410
+ messagesContainer.appendChild(messageEl);
411
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
412
+ }
413
+
414
+ // Sync messages from shared array
415
+ _syncMessages(messages) {
416
+ if (!this.container) return;
417
+ const messagesContainer = this.container.querySelector("#chat-messages");
418
+ if (!messagesContainer) return;
419
+
420
+ // Remove welcome message if there are any messages
421
+ if (messages.length > 0) {
422
+ const welcome = messagesContainer.querySelector(".chat-welcome");
423
+ if (welcome) welcome.remove();
424
+ }
425
+
426
+ // Get existing message elements
427
+ const existingMessages = messagesContainer.querySelectorAll("[data-message-id]");
428
+ const existingIds = new Set(Array.from(existingMessages).map(el => el.getAttribute("data-message-id")));
429
+
430
+ // Add or update messages
431
+ messages.forEach(msg => {
432
+ const msgId = String(msg.id);
433
+ if (existingIds.has(msgId)) {
434
+ // Update existing message
435
+ const existingEl = messagesContainer.querySelector("[data-message-id=\"".concat(msgId, "\"]"));
436
+ if (existingEl) {
437
+ const bubble = existingEl.querySelector(".message-bubble");
438
+ if (bubble) {
439
+ // Parse markdown for assistant messages, escape for user messages
440
+ const content = msg.content || "";
441
+ const isUserMessage = msg.sender === "user";
442
+ bubble.innerHTML = !isUserMessage ? this._parseMarkdown(content) : this._escapeHtml(content);
443
+ }
444
+
445
+ // Update attachments if they exist
446
+ const messageContent = existingEl.querySelector(".message-content");
447
+ if (messageContent && msg.attachments && msg.attachments.length > 0) {
448
+ let attachmentsHTML = "\n <div class=\"message-attachments\">\n ".concat(msg.attachments.map((attachment, index) => {
449
+ var _attachment$metadata4, _attachment$metadata5, _attachment$metadata6;
450
+ const displayName = ((_attachment$metadata4 = attachment.metadata) === null || _attachment$metadata4 === void 0 ? void 0 : _attachment$metadata4.name) || ((_attachment$metadata5 = attachment.metadata) === null || _attachment$metadata5 === void 0 ? void 0 : _attachment$metadata5.filename) || "Unknown file";
451
+ const isImage = attachment.type === "image";
452
+ const imageSrc = isImage && attachment.data ? "data:".concat(attachment.mime_type, ";base64,").concat(attachment.data) : null;
453
+ return "\n <div class=\"message-attachment ".concat(isImage ? "image" : "pdf", "\" data-attachment-index=\"").concat(index, "\">\n <span class=\"message-attachment-icon\">\n ").concat(isImage ? "🖼️" : "📄", "\n </span>\n <div class=\"message-attachment-info\">\n <div \n class=\"message-attachment-name ").concat(isImage ? "clickable" : "", "\"\n ").concat(isImage && imageSrc ? "data-image-src=\"".concat(imageSrc, "\" data-image-alt=\"").concat(displayName, "\"") : "", "\n style=\"cursor: ").concat(isImage ? "pointer" : "default", ";\"\n >\n ").concat(this._escapeHtml(displayName), "\n </div>\n <div class=\"message-attachment-size\">\n ").concat(this.formatFileSize(((_attachment$metadata6 = attachment.metadata) === null || _attachment$metadata6 === void 0 ? void 0 : _attachment$metadata6.size) || 0), " \u2022 ").concat(this.getFileTypeDisplay(attachment.mime_type), "\n </div>\n </div>\n </div>\n ");
454
+ }).join(""), "\n </div>\n ");
455
+
456
+ // Insert or update attachments
457
+ const existingAttachments = messageContent.querySelector(".message-attachments");
458
+ if (existingAttachments) {
459
+ existingAttachments.outerHTML = attachmentsHTML;
460
+ } else {
461
+ messageContent.insertAdjacentHTML("afterbegin", attachmentsHTML);
462
+ }
463
+
464
+ // Re-bind image click handlers
465
+ messageContent.querySelectorAll(".message-attachment-name.clickable").forEach(el => {
466
+ // Remove existing listeners by cloning
467
+ const newEl = el.cloneNode(true);
468
+ el.parentNode.replaceChild(newEl, el);
469
+ newEl.addEventListener("click", e => {
470
+ e.stopPropagation();
471
+ const imageSrc = newEl.getAttribute("data-image-src");
472
+ const imageAlt = newEl.getAttribute("data-image-alt");
473
+ if (imageSrc) {
474
+ this.showExpandedImage(imageSrc, imageAlt);
475
+ }
476
+ });
477
+ });
478
+ }
479
+
480
+ // Update classes based on message state
481
+ if (msg.isStreaming) {
482
+ existingEl.classList.add("streaming");
483
+ } else {
484
+ existingEl.classList.remove("streaming");
485
+ }
486
+ if (msg.isError) {
487
+ existingEl.classList.add("error");
488
+ }
489
+ }
490
+ } else {
491
+ // Add new message
492
+ this.addMessage(msg.content || "", msg.sender === "user", msg);
493
+ }
494
+ });
495
+
496
+ // Remove messages that are no longer in the array
497
+ existingMessages.forEach(el => {
498
+ const msgId = el.getAttribute("data-message-id");
499
+ const exists = messages.some(msg => String(msg.id) === msgId);
500
+ if (!exists) {
501
+ el.remove();
502
+ }
503
+ });
504
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
505
+ }
506
+ showTypingIndicator() {
507
+ if (!this.container) return;
508
+ const messagesContainer = this.container.querySelector("#chat-messages");
509
+ if (!messagesContainer) return;
510
+ const indicator = document.createElement("div");
511
+ indicator.className = "chat-message bot";
512
+ indicator.id = "typing-indicator";
513
+ indicator.innerHTML = "\n <div class=\"typing-indicator\">\n <div class=\"typing-dot\"></div>\n <div class=\"typing-dot\"></div>\n <div class=\"typing-dot\"></div>\n </div>\n ";
514
+ messagesContainer.appendChild(indicator);
515
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
516
+ }
517
+ hideTypingIndicator() {
518
+ if (!this.container) return;
519
+ const indicator = this.container.querySelector("#typing-indicator");
520
+ if (indicator) indicator.remove();
521
+ }
522
+ handleUserMessage(text) {
523
+ // Call the callback if provided, otherwise use default echo
524
+ if (this.onMessage) {
525
+ this.onMessage(text, response => {
526
+ this.addMessage(response, false);
527
+ });
528
+ } else {
529
+ // Default echo response
530
+ this.addMessage("You said: \"".concat(text, "\""), false);
531
+ }
532
+ }
533
+ _escapeHtml(text) {
534
+ const div = document.createElement("div");
535
+ div.textContent = text;
536
+ return div.innerHTML;
537
+ }
538
+
539
+ // Parse markdown to HTML
540
+ _parseMarkdown(text) {
541
+ if (!text || typeof text !== 'string') return '';
542
+ let html = text;
543
+
544
+ // Escape HTML first to prevent XSS
545
+ html = html.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
546
+
547
+ // Split into lines for processing
548
+ const lines = html.split('\n');
549
+ const processedLines = [];
550
+ let inList = false;
551
+ for (let i = 0; i < lines.length; i++) {
552
+ let line = lines[i];
553
+
554
+ // Check for headers first (#, ##, ###, etc.)
555
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
556
+ if (headerMatch) {
557
+ if (inList) {
558
+ processedLines.push('</ul>');
559
+ inList = false;
560
+ }
561
+ const level = headerMatch[1].length;
562
+ const headerText = headerMatch[2];
563
+ // Process markdown inside header
564
+ let processedHeader = headerText;
565
+ processedHeader = processedHeader.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
566
+ processedHeader = processedHeader.replace(/__([^_]+)__/g, '<strong>$1</strong>');
567
+ processedHeader = processedHeader.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
568
+ processedHeader = processedHeader.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
569
+ processedLines.push("<h".concat(level, ">").concat(processedHeader, "</h").concat(level, ">"));
570
+ continue;
571
+ }
572
+
573
+ // Check if this is a list item (before processing bold/italic)
574
+ const listMatch = line.match(/^[\s]*[-*]\s+(.+)$/);
575
+ if (listMatch) {
576
+ // Process markdown inside list item
577
+ let listContent = listMatch[1];
578
+
579
+ // Bold: **text** (must be processed before italic)
580
+ listContent = listContent.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
581
+ listContent = listContent.replace(/__([^_]+)__/g, '<strong>$1</strong>');
582
+
583
+ // Italic: *text* (single asterisk, avoid matching **text**)
584
+ listContent = listContent.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
585
+ listContent = listContent.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
586
+ if (!inList) {
587
+ processedLines.push('<ul>');
588
+ inList = true;
589
+ }
590
+ processedLines.push("<li>".concat(listContent, "</li>"));
591
+ } else {
592
+ if (inList) {
593
+ processedLines.push('</ul>');
594
+ inList = false;
595
+ }
596
+
597
+ // Skip empty lines (they'll become <br> later)
598
+ if (line.trim() === '') {
599
+ processedLines.push('');
600
+ continue;
601
+ }
602
+
603
+ // Process markdown in non-list lines
604
+ // Bold: **text** (must be processed before italic)
605
+ line = line.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
606
+ line = line.replace(/__([^_]+)__/g, '<strong>$1</strong>');
607
+
608
+ // Italic: *text* (single asterisk, avoid matching **text**)
609
+ line = line.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
610
+ line = line.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
611
+
612
+ // Inline code: `code`
613
+ line = line.replace(/`([^`]+)`/g, '<code>$1</code>');
614
+
615
+ // Links: [text](url)
616
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
617
+ processedLines.push(line);
618
+ }
619
+ }
620
+ if (inList) {
621
+ processedLines.push('</ul>');
622
+ }
623
+
624
+ // Join lines with <br> and return
625
+ html = processedLines.join('<br>');
626
+ return html;
627
+ }
628
+
629
+ // Show expanded image modal
630
+ showExpandedImage(src, alt) {
631
+ // Remove existing modal if any
632
+ const existingModal = document.querySelector(".expanded-image-modal");
633
+ if (existingModal) {
634
+ existingModal.remove();
635
+ }
636
+ const modal = document.createElement("div");
637
+ modal.className = "expanded-image-modal";
638
+ modal.innerHTML = "\n <div class=\"expanded-image-container\">\n <button class=\"expanded-image-close\" title=\"Close\">\u2715</button>\n <img src=\"".concat(src, "\" alt=\"").concat(alt || "Image", "\" class=\"expanded-image\" />\n <div class=\"expanded-image-caption\">").concat(this._escapeHtml(alt || "Image preview"), "</div>\n </div>\n ");
639
+
640
+ // Close handlers
641
+ const closeModal = e => {
642
+ e === null || e === void 0 || e.stopPropagation();
643
+ modal.remove();
644
+ document.removeEventListener("mousedown", handleClickOutside);
645
+ };
646
+ const handleClickOutside = e => {
647
+ if (!e.target.closest(".expanded-image-container")) {
648
+ closeModal(e);
649
+ }
650
+ };
651
+ modal.querySelector(".expanded-image-close").addEventListener("click", closeModal);
652
+ modal.addEventListener("click", e => {
653
+ if (e.target === modal) {
654
+ closeModal(e);
655
+ }
656
+ });
657
+ document.addEventListener("mousedown", handleClickOutside);
658
+ document.body.appendChild(modal);
659
+ }
660
+ _applyStyles() {
661
+ // Check if styles already applied
662
+ if (document.getElementById('text-chat-screen-styles')) return;
663
+ const style = document.createElement("style");
664
+ style.id = 'text-chat-screen-styles';
665
+ style.textContent = "\n .text-chat-screen {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n \n }\n\n .text-chat-screen .chat-header {\n background: transparent;\n color: ".concat(this.primaryColor, ";\n padding: 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .text-chat-screen .chat-header-content {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n }\n\n .text-chat-screen .chat-back {\n background: ").concat(this.primaryColor, ";\n border: none;\n color: white;\n cursor: pointer;\n padding: 8px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n margin-right: 8px;\n }\n\n \n\n .text-chat-screen .chat-avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.2);\n backdrop-filter: blur(10px);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .text-chat-screen .chat-header-text {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .text-chat-screen .chat-title {\n font-weight: 600;\n font-size: 20px;\n }\n\n .text-chat-screen .chat-status {\n font-size: 12px;\n opacity: 0.9;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .text-chat-screen .chat-close {\n background: ").concat(this.primaryColor, ";\n border: none;\n color: white;\n cursor: pointer;\n padding: 8px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n }\n\n \n\n .text-chat-screen .chat-messages {\n flex: 1;\n padding: 20px;\n overflow-y: auto;\n background: linear-gradient(180deg, white 10%, #E1EFCC );\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .text-chat-screen .chat-messages::-webkit-scrollbar {\n width: 6px;\n }\n\n .text-chat-screen .chat-messages::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .text-chat-screen .chat-messages::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 3px;\n }\n\n .text-chat-screen .chat-welcome {\n text-align: center;\n padding: 100px 20px;\n color: #64748b;\n \n }\n\n .text-chat-screen .chat-welcome .welcome-icon {\n font-size: 48px;\n margin-bottom: 12px;\n }\n\n .text-chat-screen .welcome-text {\n font-size: 15px;\n font-weight: 500;\n color: #475569;\n }\n\n .text-chat-screen .chat-message {\n display: flex;\n gap: 8px;\n animation: messageSlide 0.3s ease-out;\n }\n\n @keyframes messageSlide {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .text-chat-screen .chat-message.user {\n flex-direction: row-reverse;\n }\n\n .text-chat-screen .message-content {\n max-width: 75%;\n }\n\n .text-chat-screen .message-bubble {\n padding: 12px 16px;\n border-radius: 16px;\n font-size: 14px;\n line-height: 1.5;\n word-wrap: break-word;\n }\n\n .text-chat-screen .chat-message.user .message-bubble {\n background: #e9f5d7;\n color: ").concat(this.primaryColor, ";\n border-bottom-right-radius: 4px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n\n .text-chat-screen .chat-message.bot .message-bubble {\n background: white;\n color: #1e293b;\n border-bottom-left-radius: 4px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n\n /* ============================================\n Text Chat Message Bubble Styles\n ============================================ */\n\n/* Typography - Bold */\n.text-chat-screen .message-bubble strong {\n font-weight: 600;\n color: inherit;\n}\n\n/* Typography - Italic */\n.text-chat-screen .message-bubble em {\n font-style: italic;\n}\n\n/* Lists */\n.text-chat-screen .message-bubble ul {\n margin: 0px 0;\n padding-left: 20px;\n list-style-type: disc;\n margin-bottom:0px;\n margin-top:5px;\n}\n\n.text-chat-screen .message-bubble li {\n margin: 0;\n line-height: 1.1;\n padding-left: 3px;\n}\n\n/* Headings - Shared Styles */\n.text-chat-screen .message-bubble h1,\n.text-chat-screen .message-bubble h2,\n.text-chat-screen .message-bubble h3,\n.text-chat-screen .message-bubble h4,\n.text-chat-screen .message-bubble h5,\n.text-chat-screen .message-bubble h6 {\n margin: 0px 0 0px 0;\n font-weight: 600;\n color: inherit;\n line-height: 1.3;\n}\n\n/* Heading Sizes */\n.text-chat-screen .message-bubble h1 { font-size: 1.5em; }\n.text-chat-screen .message-bubble h2 { font-size: 1.3em; }\n.text-chat-screen .message-bubble h3 { font-size: 1.15em; }\n.text-chat-screen .message-bubble h4 { font-size: 1.05em; }\n.text-chat-screen .message-bubble h5 { font-size: 1em; }\n.text-chat-screen .message-bubble h6 { font-size: 0.95em; }\n\n/* Inline Code */\n.text-chat-screen .message-bubble code {\n background: rgba(0, 0, 0, 0.05);\n padding: 2px 6px;\n border-radius: 4px;\n font-family: 'Courier New', Courier, monospace;\n font-size: 0.9em;\n}\n\n/* Links */\n.text-chat-screen .message-bubble a {\n color: ").concat(this.primaryColor, ";\n text-decoration: underline;\n}\n\n.text-chat-screen .message-bubble a:hover {\n opacity: 0.8;\n}\n.text-chat-screen .message-bubble br {\n line-height: 0.1; /* Adjust between 0 and 1 */\n}\n\n .text-chat-screen .message-time {\n font-size: 10px;\n color: #94a3b8;\n margin-top: 4px;\n text-align: right;\n }\n\n .text-chat-screen .typing-indicator {\n display: flex;\n gap: 4px;\n padding: 12px 16px;\n background: white;\n border-radius: 16px;\n border-bottom-left-radius: 4px;\n max-width: 60px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n\n .text-chat-screen .typing-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #94a3b8;\n animation: typing 1.4s infinite;\n }\n\n .text-chat-screen .typing-dot:nth-child(2) {\n animation-delay: 0.2s;\n }\n\n .text-chat-screen .typing-dot:nth-child(3) {\n animation-delay: 0.4s;\n }\n\n @keyframes typing {\n 0%, 60%, 100% {\n transform: translateY(0);\n opacity: 0.7;\n }\n 30% {\n transform: translateY(-10px);\n opacity: 1;\n }\n }\n\n .text-chat-screen .file-attachments-container {\n padding: 8px 12px;\n background: white;\n border-top: 1px solid #e2e8f0;\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n max-height: 120px;\n overflow-y: auto;\n }\n\n .text-chat-screen .file-attachments-container::-webkit-scrollbar {\n width: 4px;\n height: 4px;\n }\n\n .text-chat-screen .file-attachments-container::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .text-chat-screen .file-attachments-container::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 2px;\n }\n\n .text-chat-screen .file-attachment {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 10px;\n background: #f1f5f9;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n font-size: 12px;\n max-width: 200px;\n position: relative;\n }\n\n .text-chat-screen .file-attachment.has-thumbnail {\n padding: 4px;\n }\n\n .text-chat-screen .file-thumbnail {\n width: 40px;\n height: 40px;\n border-radius: 6px;\n overflow: hidden;\n flex-shrink: 0;\n background: #e2e8f0;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .text-chat-screen .file-thumbnail-image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n cursor: pointer;\n }\n\n .text-chat-screen .file-attachment-icon {\n font-size: 24px;\n flex-shrink: 0;\n }\n\n .text-chat-screen .file-attachment-info {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .text-chat-screen .file-attachment-name {\n font-weight: 500;\n color: #1e293b;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 120px;\n }\n\n .text-chat-screen .file-attachment-size {\n font-size: 11px;\n color: #64748b;\n }\n\n .text-chat-screen .file-attachment-remove {\n background: transparent;\n border: none;\n color: #64748b;\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n font-size: 16px;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n flex-shrink: 0;\n }\n\n .text-chat-screen .file-attachment-remove:hover {\n background: #fee2e2;\n color: #dc2626;\n }\n\n .text-chat-screen .chat-input-wrapper {\n display: flex;\n padding: 10px;\n gap: 8px;\n background: white;\n border-top: 1px solid #e2e8f0;\n align-items: center;\n }\n\n .text-chat-screen .file-upload-controls {\n display: flex;\n align-items: center;\n }\n\n .text-chat-screen .file-upload-button {\n background: transparent;\n border: none;\n color: ").concat(this.primaryColor, ";\n cursor: pointer;\n padding: 8px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n }\n\n .text-chat-screen .file-upload-button:hover {\n background: rgba(26, 92, 75, 0.1);\n }\n\n .text-chat-screen .file-upload-button:active {\n transform: scale(0.95);\n }\n\n .text-chat-screen #chat-input {\n flex: 1;\n border: 2px solid #e2e8f0;\n border-radius: 12px;\n padding: 12px 16px;\n font-size: 14px;\n outline: none;\n transition: all 0.2s;\n font-family: inherit;\n }\n\n .text-chat-screen #chat-input:focus {\n border-color: ").concat(this.primaryColor, ";\n box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);\n }\n\n .text-chat-screen #chat-send {\n background: ").concat(this.primaryColor, ";\n color: white;\n border: none;\n border-radius: 12px;\n padding: 12px 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n }\n\n .text-chat-screen #chat-send:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .text-chat-screen #chat-send:not(:disabled):hover {\n transform: scale(1.05);\n box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);\n }\n\n .text-chat-screen #chat-send:not(:disabled):active {\n transform: scale(0.95);\n }\n\n /* Message Attachments Styles */\n .text-chat-screen .message-attachments {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 8px;\n }\n\n .text-chat-screen .message-attachment {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: #f1f5f9;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n font-size: 12px;\n max-width: 100%;\n }\n\n .text-chat-screen .message-attachment.image {\n background: #f8fafc;\n }\n\n .text-chat-screen .message-attachment.pdf {\n background: #fef2f2;\n }\n\n .text-chat-screen .message-attachment-icon {\n font-size: 20px;\n flex-shrink: 0;\n }\n\n .text-chat-screen .message-attachment-info {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .text-chat-screen .message-attachment-name {\n font-weight: 500;\n color: #1e293b;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .text-chat-screen .message-attachment-name.clickable:hover {\n color: ").concat(this.primaryColor, ";\n text-decoration: underline;\n }\n\n .text-chat-screen .message-attachment-size {\n font-size: 11px;\n color: #64748b;\n }\n\n /* Expanded Image Modal Styles */\n .expanded-image-modal {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.8);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n animation: fadeIn 0.2s ease;\n }\n\n @keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }\n\n .expanded-image-container {\n position: relative;\n max-width: 90%;\n max-height: 90vh;\n background: #fff;\n border-radius: 8px;\n overflow: hidden;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n animation: scaleIn 0.2s ease;\n display: flex;\n flex-direction: column;\n }\n\n @keyframes scaleIn {\n from {\n transform: scale(0.9);\n opacity: 0;\n }\n to {\n transform: scale(1);\n opacity: 1;\n }\n }\n\n .expanded-image-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: rgba(0, 0, 0, 0.6);\n border: none;\n color: white;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n z-index: 1;\n transition: all 0.2s;\n }\n\n .expanded-image-close:hover {\n background: rgba(0, 0, 0, 0.8);\n transform: scale(1.1);\n }\n\n .expanded-image {\n max-width: 100%;\n max-height: calc(90vh - 60px);\n object-fit: contain;\n display: block;\n }\n\n .expanded-image-caption {\n padding: 12px 16px;\n background: #fff;\n color: #1e293b;\n font-size: 14px;\n text-align: center;\n border-top: 1px solid #e2e8f0;\n }\n ");
666
+ document.head.appendChild(style);
667
+ }
38
668
  }
39
669
 
40
- var chatWidget = {exports: {}};
41
-
42
- (function (module) {
43
- // chat-widget.js - Modern vanilla JS chat bot widget
44
- var ChatWidget = /*#__PURE__*/function () {
45
- function ChatWidget() {
46
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
47
- _classCallCheck(this, ChatWidget);
48
- this.title = options.title || "Chat Assistant";
49
- this.placeholder = options.placeholder || "Type your message...";
50
- this.primaryColor = options.primaryColor || "#6366f1";
51
- this.container = null;
52
- this.messages = [];
53
- this.isMinimized = false;
54
- this._init();
55
- }
56
- return _createClass(ChatWidget, [{
57
- key: "_init",
58
- value: function _init() {
59
- // Create container
60
- this.container = document.createElement("div");
61
- this.container.id = "chat-widget-container";
62
- this.container.innerHTML = "\n <div class=\"chat-widget-minimized\" id=\"chat-toggle\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <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>\n </svg>\n </div>\n <div class=\"chat-widget-expanded\" id=\"chat-expanded\">\n <div class=\"chat-header\">\n <div class=\"chat-header-content\">\n <div class=\"chat-avatar\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z\"></path>\n <path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"></path>\n </svg>\n </div>\n <div class=\"chat-header-text\">\n <div class=\"chat-title\">".concat(this.title, "</div>\n <div class=\"chat-status\">\u25CF Online</div>\n </div>\n </div>\n <button class=\"chat-close\" id=\"chat-close\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n <div class=\"chat-messages\" id=\"chat-messages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-icon\">\uD83D\uDC4B</div>\n <div class=\"welcome-text\">Hello! How can I help you today?</div>\n </div>\n </div>\n <div class=\"chat-input-wrapper\">\n <input type=\"text\" id=\"chat-input\" placeholder=\"").concat(this.placeholder, "\" autocomplete=\"off\" />\n <button id=\"chat-send\" disabled>\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line>\n <polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon>\n </svg>\n </button>\n </div>\n </div>\n ");
63
- document.body.appendChild(this.container);
64
- this._applyStyles();
65
- this._bindEvents();
66
- }
67
- }, {
68
- key: "_applyStyles",
69
- value: function _applyStyles() {
70
- var style = document.createElement("style");
71
- style.textContent = "\n @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');\n \n #chat-widget-container {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 10000;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n }\n\n .chat-widget-minimized {\n width: 60px;\n height: 60px;\n border-radius: 50%;\n background: linear-gradient(135deg, ".concat(this.primaryColor, " 0%, #8b5cf6 100%);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n animation: pulse 2s infinite;\n }\n\n .chat-widget-minimized:hover {\n transform: scale(1.1);\n box-shadow: 0 12px 32px rgba(99, 102, 241, 0.5);\n }\n\n @keyframes pulse {\n 0%, 100% { box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4); }\n 50% { box-shadow: 0 8px 32px rgba(99, 102, 241, 0.6); }\n }\n\n .chat-widget-expanded {\n width: 380px;\n height: 600px;\n background: white;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);\n display: none;\n flex-direction: column;\n overflow: hidden;\n animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n @keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(20px) scale(0.95);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n .chat-widget-expanded.visible {\n display: flex;\n }\n\n .chat-header {\n background: linear-gradient(135deg, ").concat(this.primaryColor, " 0%, #8b5cf6 100%);\n color: white;\n padding: 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .chat-header-content {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .chat-avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.2);\n backdrop-filter: blur(10px);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .chat-header-text {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .chat-title {\n font-weight: 600;\n font-size: 16px;\n }\n\n .chat-status {\n font-size: 12px;\n opacity: 0.9;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .chat-close {\n background: transparent;\n border: none;\n color: white;\n cursor: pointer;\n padding: 8px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n }\n\n .chat-close:hover {\n background: rgba(255, 255, 255, 0.15);\n }\n\n .chat-messages {\n flex: 1;\n padding: 20px;\n overflow-y: auto;\n background: #f9fafb;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .chat-messages::-webkit-scrollbar {\n width: 6px;\n }\n\n .chat-messages::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .chat-messages::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 3px;\n }\n\n .chat-welcome {\n text-align: center;\n padding: 40px 20px;\n color: #64748b;\n }\n\n .welcome-icon {\n font-size: 48px;\n margin-bottom: 12px;\n }\n\n .welcome-text {\n font-size: 15px;\n font-weight: 500;\n color: #475569;\n }\n\n .chat-message {\n display: flex;\n gap: 8px;\n animation: messageSlide 0.3s ease-out;\n }\n\n @keyframes messageSlide {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .chat-message.user {\n flex-direction: row-reverse;\n }\n\n .message-bubble {\n max-width: 75%;\n padding: 12px 16px;\n border-radius: 16px;\n font-size: 14px;\n line-height: 1.5;\n word-wrap: break-word;\n }\n\n .chat-message.user .message-bubble {\n background: linear-gradient(135deg, ").concat(this.primaryColor, " 0%, #8b5cf6 100%);\n color: white;\n border-bottom-right-radius: 4px;\n }\n\n .chat-message.bot .message-bubble {\n background: white;\n color: #1e293b;\n border-bottom-left-radius: 4px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n\n .typing-indicator {\n display: flex;\n gap: 4px;\n padding: 12px 16px;\n background: white;\n border-radius: 16px;\n border-bottom-left-radius: 4px;\n max-width: 60px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n\n .typing-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #94a3b8;\n animation: typing 1.4s infinite;\n }\n\n .typing-dot:nth-child(2) {\n animation-delay: 0.2s;\n }\n\n .typing-dot:nth-child(3) {\n animation-delay: 0.4s;\n }\n\n @keyframes typing {\n 0%, 60%, 100% {\n transform: translateY(0);\n opacity: 0.7;\n }\n 30% {\n transform: translateY(-10px);\n opacity: 1;\n }\n }\n\n .chat-input-wrapper {\n display: flex;\n padding: 16px;\n gap: 8px;\n background: white;\n border-top: 1px solid #e2e8f0;\n }\n\n #chat-input {\n flex: 1;\n border: 2px solid #e2e8f0;\n border-radius: 12px;\n padding: 12px 16px;\n font-size: 14px;\n outline: none;\n transition: all 0.2s;\n font-family: inherit;\n }\n\n #chat-input:focus {\n border-color: ").concat(this.primaryColor, ";\n box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);\n }\n\n #chat-send {\n background: linear-gradient(135deg, ").concat(this.primaryColor, " 0%, #8b5cf6 100%);\n color: white;\n border: none;\n border-radius: 12px;\n padding: 12px 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n }\n\n #chat-send:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n #chat-send:not(:disabled):hover {\n transform: scale(1.05);\n box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);\n }\n\n #chat-send:not(:disabled):active {\n transform: scale(0.95);\n }\n ");
72
- document.head.appendChild(style);
73
- }
74
- }, {
75
- key: "_bindEvents",
76
- value: function _bindEvents() {
77
- var _this = this;
78
- var toggle = this.container.querySelector("#chat-toggle");
79
- var closeBtn = this.container.querySelector("#chat-close");
80
- var expanded = this.container.querySelector("#chat-expanded");
81
- var input = this.container.querySelector("#chat-input");
82
- var sendBtn = this.container.querySelector("#chat-send");
83
- toggle.addEventListener("click", function () {
84
- toggle.style.display = "none";
85
- expanded.classList.add("visible");
86
- input.focus();
670
+ class BhashiniFrontend {
671
+ constructor() {
672
+ this.apiKey = '23588533c1-d990-4a7d-b052-d970c886147e';
673
+ this.userId = '749bd0b0c65e4d17a372be0ad92af981';
674
+ this.callbackUrl = 'https://meity-auth.ulcacontrib.org/ulca/apis/v0/model/getModelsPipeline';
675
+ this.inferenceUrl = null;
676
+ this.inferenceHeaders = {};
677
+ this.asrServiceId = {
678
+ 'hi': 'ai4bharat/conformer-hi-gpu--t4',
679
+ 'en': 'ai4bharat/whisper-medium-en--gpu--t4'
680
+ };
681
+ this.ttsServiceId = {
682
+ 'hi': 'ai4bharat/indic-tts-coqui-indo_aryan-gpu--t4',
683
+ 'en': 'ai4bharat/indic-tts-coqui-misc-gpu--t4'
684
+ };
685
+ this.translationServiceId = 'ai4bharat/indictrans-v2-all-gpu--t4';
686
+ }
687
+ async initialize() {
688
+ try {
689
+ const response = await fetch(this.callbackUrl, {
690
+ method: 'POST',
691
+ headers: {
692
+ 'Content-Type': 'application/json',
693
+ 'userID': this.userId,
694
+ 'ulcaApiKey': this.apiKey
695
+ },
696
+ body: JSON.stringify({
697
+ pipelineTasks: [{
698
+ taskType: 'asr'
699
+ }],
700
+ pipelineRequestConfig: {
701
+ pipelineId: '64392f96daac500b55c543cd'
702
+ }
703
+ })
704
+ });
705
+ const data = await response.json();
706
+ if (data.pipelineInferenceAPIEndPoint) {
707
+ this.inferenceUrl = data.pipelineInferenceAPIEndPoint.callbackUrl;
708
+ const keyName = data.pipelineInferenceAPIEndPoint.inferenceApiKey.name;
709
+ const keyValue = data.pipelineInferenceAPIEndPoint.inferenceApiKey.value;
710
+ this.inferenceHeaders = {
711
+ 'Content-Type': 'application/json',
712
+ [keyName]: keyValue
713
+ };
714
+ } else {
715
+ // Fallback to direct endpoint
716
+ this.inferenceUrl = 'https://dhruva-api.bhashini.gov.in/services/inference/pipeline';
717
+ this.inferenceHeaders = {
718
+ 'Content-Type': 'application/json',
719
+ 'userID': this.userId,
720
+ 'ulcaApiKey': this.apiKey
721
+ };
722
+ }
723
+ return true;
724
+ } catch (error) {
725
+ console.error('Failed to initialize Bhashini:', error);
726
+ // Use fallback endpoint
727
+ this.inferenceUrl = 'https://dhruva-api.bhashini.gov.in/services/inference/pipeline';
728
+ this.inferenceHeaders = {
729
+ 'Content-Type': 'application/json',
730
+ 'userID': this.userId,
731
+ 'ulcaApiKey': this.apiKey
732
+ };
733
+ return false;
734
+ }
735
+ }
736
+ async convertToWav(audioBlob) {
737
+ return new Promise((resolve, reject) => {
738
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
739
+ const reader = new FileReader();
740
+ reader.onload = async e => {
741
+ try {
742
+ const arrayBuffer = e.target.result;
743
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
744
+
745
+ // Convert to mono 16kHz
746
+ const offlineContext = new OfflineAudioContext(1, audioBuffer.duration * 16000, 16000);
747
+ const source = offlineContext.createBufferSource();
748
+ source.buffer = audioBuffer;
749
+ source.connect(offlineContext.destination);
750
+ source.start();
751
+ const renderedBuffer = await offlineContext.startRendering();
752
+
753
+ // Convert to WAV
754
+ const wav = this.audioBufferToWav(renderedBuffer);
755
+ const wavBlob = new Blob([wav], {
756
+ type: 'audio/wav'
757
+ });
758
+
759
+ // Convert to base64
760
+ const wavReader = new FileReader();
761
+ wavReader.onloadend = () => {
762
+ const base64 = wavReader.result.split(',')[1];
763
+ resolve(base64);
764
+ };
765
+ wavReader.readAsDataURL(wavBlob);
766
+ } catch (error) {
767
+ reject(error);
768
+ }
769
+ };
770
+ reader.onerror = reject;
771
+ reader.readAsArrayBuffer(audioBlob);
772
+ });
773
+ }
774
+ audioBufferToWav(buffer) {
775
+ const length = buffer.length * buffer.numberOfChannels * 2 + 44;
776
+ const arrayBuffer = new ArrayBuffer(length);
777
+ const view = new DataView(arrayBuffer);
778
+ const channels = [];
779
+ let offset = 0;
780
+ let pos = 0;
781
+
782
+ // Write WAV header
783
+ const setUint16 = data => {
784
+ view.setUint16(pos, data, true);
785
+ pos += 2;
786
+ };
787
+ const setUint32 = data => {
788
+ view.setUint32(pos, data, true);
789
+ pos += 4;
790
+ };
791
+
792
+ // "RIFF" chunk descriptor
793
+ setUint32(0x46464952); // "RIFF"
794
+ setUint32(length - 8); // file length - 8
795
+ setUint32(0x45564157); // "WAVE"
796
+
797
+ // "fmt " sub-chunk
798
+ setUint32(0x20746d66); // "fmt "
799
+ setUint32(16); // SubChunk1Size = 16
800
+ setUint16(1); // AudioFormat = 1 (PCM)
801
+ setUint16(buffer.numberOfChannels);
802
+ setUint32(buffer.sampleRate);
803
+ setUint32(buffer.sampleRate * 2 * buffer.numberOfChannels); // byte rate
804
+ setUint16(buffer.numberOfChannels * 2); // block align
805
+ setUint16(16); // bits per sample
806
+
807
+ // "data" sub-chunk
808
+ setUint32(0x61746164); // "data"
809
+ setUint32(length - pos - 4); // SubChunk2Size
810
+
811
+ // Write interleaved data
812
+ for (let i = 0; i < buffer.numberOfChannels; i++) {
813
+ channels.push(buffer.getChannelData(i));
814
+ }
815
+ while (pos < length) {
816
+ for (let i = 0; i < buffer.numberOfChannels; i++) {
817
+ let sample = Math.max(-1, Math.min(1, channels[i][offset]));
818
+ sample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
819
+ view.setInt16(pos, sample, true);
820
+ pos += 2;
821
+ }
822
+ offset++;
823
+ }
824
+ return arrayBuffer;
825
+ }
826
+ async speechToText(base64Audio) {
827
+ let sourceLanguage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'auto';
828
+ let targetLanguage = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'hi';
829
+ if (!this.inferenceUrl) {
830
+ await this.initialize();
831
+ }
832
+ const pipelineTasks = [{
833
+ taskType: 'asr',
834
+ config: {
835
+ language: {
836
+ sourceLanguage
837
+ },
838
+ serviceId: this.asrServiceId[sourceLanguage] || this.asrServiceId['hi'],
839
+ audioFormat: 'wav',
840
+ samplingRate: 16000
841
+ }
842
+ }];
843
+
844
+ // Only translate if languages differ
845
+ if (sourceLanguage !== targetLanguage) {
846
+ pipelineTasks.push({
847
+ taskType: 'translation',
848
+ config: {
849
+ sourceLanguage: sourceLanguage === 'auto' ? undefined : sourceLanguage,
850
+ targetLanguage
851
+ }
852
+ });
853
+ }
854
+ const response = await fetch(this.inferenceUrl, {
855
+ method: 'POST',
856
+ headers: this.inferenceHeaders,
857
+ body: JSON.stringify({
858
+ pipelineTasks,
859
+ inputData: {
860
+ audio: [{
861
+ audioContent: base64Audio
862
+ }]
863
+ }
864
+ })
865
+ });
866
+ if (!response.ok) {
867
+ throw new Error("API error: ".concat(response.status, " ").concat(response.statusText));
868
+ }
869
+ const data = await response.json();
870
+
871
+ // If translated, return translation
872
+ if (pipelineTasks.length > 1) {
873
+ return data.pipelineResponse[1].output[0].target;
874
+ }
875
+
876
+ // Otherwise return raw transcription
877
+ return data.pipelineResponse[0].output[0].source;
878
+ }
879
+ async textToSpeech(text) {
880
+ let language = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en';
881
+ let gender = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'female';
882
+ if (!this.inferenceUrl) {
883
+ await this.initialize();
884
+ }
885
+
886
+ // ✅ Clean the text first
887
+ const cleanedText = text.replace(/!/g, ".");
888
+ const response = await fetch(this.inferenceUrl, {
889
+ method: 'POST',
890
+ headers: this.inferenceHeaders,
891
+ body: JSON.stringify({
892
+ pipelineTasks: [{
893
+ taskType: "tts",
894
+ config: {
895
+ language: {
896
+ sourceLanguage: language
897
+ },
898
+ serviceId: this.ttsServiceId[language] || this.ttsServiceId["en"],
899
+ samplingRate: 8000,
900
+ gender: gender
901
+ }
902
+ }],
903
+ inputData: {
904
+ input: [{
905
+ source: cleanedText
906
+ }]
907
+ }
908
+ })
909
+ });
910
+ if (!response.ok) {
911
+ throw new Error("TTS API error: ".concat(response.status, " ").concat(response.statusText));
912
+ }
913
+ const data = await response.json();
914
+ return data.pipelineResponse[0].audio[0].audioContent;
915
+ }
916
+ async translateText(text, sourceLanguage, targetLanguage) {
917
+ if (!this.inferenceUrl) {
918
+ await this.initialize();
919
+ }
920
+ const response = await fetch(this.inferenceUrl, {
921
+ method: 'POST',
922
+ headers: this.inferenceHeaders,
923
+ body: JSON.stringify({
924
+ pipelineTasks: [{
925
+ taskType: 'translation',
926
+ config: {
927
+ language: {
928
+ sourceLanguage: sourceLanguage,
929
+ targetLanguage: targetLanguage
930
+ },
931
+ serviceId: this.translationServiceId
932
+ }
933
+ }],
934
+ inputData: {
935
+ input: [{
936
+ source: text
937
+ }]
938
+ }
939
+ })
940
+ });
941
+ if (!response.ok) {
942
+ throw new Error("Translation API error: ".concat(response.status, " ").concat(response.statusText));
943
+ }
944
+ const data = await response.json();
945
+ return data.pipelineResponse[0].output[0].target;
946
+ }
947
+ async speechToEnglish(base64Audio) {
948
+ let sourceLanguage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'hi';
949
+ // Step 1: Transcribe audio to source language text
950
+ const transcribedText = await this.speechToText(base64Audio, sourceLanguage);
951
+
952
+ // Step 2: If already English, return as is
953
+ if (sourceLanguage === 'en') {
954
+ return {
955
+ transcription: transcribedText,
956
+ translation: transcribedText
957
+ };
958
+ }
959
+
960
+ // Step 3: Translate to English
961
+ const translatedText = await this.translateText(transcribedText, sourceLanguage, 'en');
962
+ return {
963
+ transcription: transcribedText,
964
+ translation: translatedText
965
+ };
966
+ }
967
+ async speechToHindi(base64Audio) {
968
+ let sourceLanguage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'en';
969
+ // Step 1: Transcribe audio to source language text
970
+ const transcribedText = await this.speechToText(base64Audio, sourceLanguage);
971
+
972
+ // Step 2: If already Hindi, return as is
973
+ if (sourceLanguage === 'hi') {
974
+ return {
975
+ transcription: transcribedText,
976
+ translation: transcribedText
977
+ };
978
+ }
979
+
980
+ // Step 3: Translate to Hindi
981
+ const translatedText = await this.translateText(transcribedText, sourceLanguage, 'hi');
982
+ return {
983
+ transcription: transcribedText,
984
+ translation: translatedText
985
+ };
986
+ }
987
+ async transcribeSpeechToEnglish(base64Audio) {
988
+ let sourceLanguage = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "hi";
989
+ // Step 1: Transcribe audio to source language text
990
+ const transcribedText = await this.speechToText(base64Audio, sourceLanguage);
991
+
992
+ // Step 2: If already English, return as is
993
+ if (sourceLanguage === "en") {
994
+ return {
995
+ transcription: transcribedText,
996
+ translation: transcribedText
997
+ };
998
+ }
999
+
1000
+ // Step 3: Translate to English
1001
+ const translatedText = await this.translateText(transcribedText, sourceLanguage, "en");
1002
+ return {
1003
+ transcription: transcribedText,
1004
+ translation: translatedText
1005
+ };
1006
+ }
1007
+ }
1008
+
1009
+ // audio-chat-screen.js - Audio chat screen component
1010
+ class AudioChatScreen {
1011
+ constructor() {
1012
+ let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1013
+ this.primaryColor = options.primaryColor || "#1a5c4b";
1014
+ this.title = options.title || "Chat Assistant";
1015
+ this.onRecordStart = options.onRecordStart || (() => {});
1016
+ this.onRecordStop = options.onRecordStop || (() => {});
1017
+ this.onBack = options.onBack || (() => {});
1018
+ this.onOpenDrawer = options.onOpenDrawer || (() => {});
1019
+ this.onClose = options.onClose || (() => {});
1020
+ this.navigateToTextScreen = options.navigateToTextScreen;
1021
+ this.sendMessage = options.sendMessage || null;
1022
+ this.selectedLanguage = options.selectedLanguage || "en";
1023
+ this.container = null;
1024
+ this.messages = options.messages || [];
1025
+
1026
+ // Bhashini Integration
1027
+ this.bhashini = new BhashiniFrontend();
1028
+ this.mediaRecorder = null;
1029
+ this.audioChunks = [];
1030
+ this.isRecording = false;
1031
+
1032
+ // TTS tracking
1033
+ this.playedMessageIds = new Set();
1034
+ this.audioQueue = [];
1035
+ this.isPlaying = false;
1036
+
1037
+ // File attachments
1038
+ this.contentBlocks = [];
1039
+ this.SUPPORTED_FILE_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp"];
1040
+ }
1041
+ render(container) {
1042
+ this.container = container;
1043
+ this._applyStyles();
1044
+ container.innerHTML = "\n <div class=\"audio-chat-screen\">\n <div class=\"chat-header\">\n <div class=\"chat-header-content\">\n <button class=\"chat-back\" id=\"audio-chat-menu\">\n <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-menu-icon lucide-menu\"><path d=\"M4 5h16\"/><path d=\"M4 12h16\"/><path d=\"M4 19h16\"/></svg>\n </button>\n \n <div class=\"chat-header-text\">\n <div class=\"chat-title\">".concat(this.title, "</div>\n </div>\n </div>\n <button class=\"chat-close\" id=\"navigate-to-text-screen\">\n <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-message-square-text-icon lucide-message-square-text\"><path d=\"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z\"/><path d=\"M7 11h10\"/><path d=\"M7 15h6\"/><path d=\"M7 7h8\"/></svg>\n </button>\n &#8203; &#8203; &#8203;\n <button class=\"chat-close\" id=\"audio-chat-close\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n \n \n <div class=\"chat-messages\" id=\"chat-messages\">\n <div class=\"chat-welcome\">\n <div class=\"welcome-text\">\uD83D\uDC4B Hello! I'm your AI assistant. How can I help you today?</div>\n </div>\n </div> \n \n \n <div class=\"file-attachments-container\" id=\"file-attachments-container\" style=\"display: none;\"></div>\n <div class=\"bottom-inputs\">\n <button id=\"record-button\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" 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>\n </button>\n &#8203; &#8203; &#8203;&#8203; &#8203; &#8203;\n <input\n type=\"file\"\n id=\"file-upload-input\"\n accept=\"image/jpeg,image/png,image/gif,image/webp\"\n style=\"display: none;\"\n />\n <button id=\"attachment-button\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-paperclip-icon lucide-paperclip\"><path d=\"m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551\"/></svg>\n </button>\n </div>\n \n \n </div>\n ");
1045
+
1046
+ // Bind header events
1047
+ const menuBtn = container.querySelector("#audio-chat-menu");
1048
+ const closeBtn = container.querySelector("#audio-chat-close");
1049
+ const navigateToTextScreen = container.querySelector("#navigate-to-text-screen");
1050
+ if (menuBtn) {
1051
+ menuBtn.addEventListener("click", () => {
1052
+ if (this.onOpenDrawer) {
1053
+ this.onOpenDrawer();
1054
+ } else if (this.onBack) {
1055
+ this.onBack();
1056
+ }
1057
+ });
1058
+ }
1059
+ if (closeBtn) {
1060
+ closeBtn.addEventListener("click", () => {
1061
+ if (this.onClose) this.onClose();
1062
+ });
1063
+ }
1064
+ if (navigateToTextScreen) {
1065
+ navigateToTextScreen.addEventListener("click", () => {
1066
+ if (this.navigateToTextScreen) this.navigateToTextScreen();
1067
+ });
1068
+ }
1069
+
1070
+ // Bind audio chat events
1071
+ const recordBtn = container.querySelector("#record-button");
1072
+ const attachmentBtn = container.querySelector("#attachment-button");
1073
+ if (recordBtn) {
1074
+ recordBtn.addEventListener("click", () => {
1075
+ this.toggleRecording(recordBtn);
1076
+ });
1077
+ }
1078
+ const fileInput = container.querySelector("#file-upload-input");
1079
+ if (attachmentBtn && fileInput) {
1080
+ attachmentBtn.addEventListener("click", () => {
1081
+ fileInput.click();
1082
+ });
1083
+ fileInput.addEventListener("change", e => {
1084
+ this.handleFileUpload(e);
1085
+ });
1086
+ }
1087
+
1088
+ // Sync existing messages on render
1089
+ if (this.messages && this.messages.length > 0) {
1090
+ setTimeout(() => {
1091
+ this._syncMessages(this.messages);
1092
+ }, 50);
1093
+ }
1094
+ }
1095
+
1096
+ // Convert file to content block
1097
+ fileToContentBlock(file) {
1098
+ return new Promise((resolve, reject) => {
1099
+ const reader = new FileReader();
1100
+ reader.onload = () => {
1101
+ const base64Data = reader.result.split(",")[1];
1102
+ if (this.SUPPORTED_FILE_TYPES.includes(file.type)) {
1103
+ resolve({
1104
+ type: "image",
1105
+ source_type: "base64",
1106
+ mime_type: file.type,
1107
+ data: base64Data,
1108
+ metadata: {
1109
+ name: file.name,
1110
+ size: file.size
1111
+ }
1112
+ });
1113
+ } else {
1114
+ reject(new Error("Unsupported file type: ".concat(file.type)));
1115
+ }
1116
+ };
1117
+ reader.onerror = () => {
1118
+ reject(new Error("Failed to read file: ".concat(file.name)));
1119
+ };
1120
+ reader.readAsDataURL(file);
1121
+ });
1122
+ }
1123
+
1124
+ // Handle file upload
1125
+ async handleFileUpload(e) {
1126
+ const files = e.target.files;
1127
+ if (!files) return;
1128
+ const fileArray = Array.from(files);
1129
+ const validFiles = fileArray.filter(file => this.SUPPORTED_FILE_TYPES.includes(file.type));
1130
+ if (validFiles.length === 0) {
1131
+ if (fileArray.length > 0) {
1132
+ console.warn("Invalid file type.");
1133
+ }
1134
+ return;
1135
+ }
1136
+
1137
+ // Replace existing with the new one
1138
+ const file = validFiles[0];
1139
+ try {
1140
+ const block = await this.fileToContentBlock(file);
1141
+ this.contentBlocks = [block];
1142
+ this.renderFilePreview();
1143
+ } catch (error) {
1144
+ console.error("Error processing file:", error);
1145
+ }
1146
+
1147
+ // Clear input
1148
+ e.target.value = "";
1149
+ }
1150
+ removeBlock(index) {
1151
+ this.contentBlocks = []; // Since we only have max 1, removing index 0 means clearing all
1152
+ this.renderFilePreview();
1153
+ }
1154
+ renderFilePreview() {
1155
+ if (!this.container) return;
1156
+ const container = this.container.querySelector("#file-attachments-container");
1157
+ if (!container) return;
1158
+ if (!this.contentBlocks || this.contentBlocks.length === 0) {
1159
+ container.innerHTML = "";
1160
+ container.style.display = "none";
1161
+ return;
1162
+ }
1163
+ container.style.display = "flex";
1164
+ container.innerHTML = this.contentBlocks.map((block, index) => {
1165
+ var _block$metadata, _block$metadata2, _block$metadata3;
1166
+ const displayName = ((_block$metadata = block.metadata) === null || _block$metadata === void 0 ? void 0 : _block$metadata.filename) || ((_block$metadata2 = block.metadata) === null || _block$metadata2 === void 0 ? void 0 : _block$metadata2.name) || "file";
1167
+ const size = this.formatFileSize(((_block$metadata3 = block.metadata) === null || _block$metadata3 === void 0 ? void 0 : _block$metadata3.size) || 0);
1168
+ const isImage = block.type === "image";
1169
+ const src = isImage ? "data:".concat(block.mime_type, ";base64,").concat(block.data) : null;
1170
+ return "\n <div class=\"file-attachment ".concat(isImage ? "has-thumbnail" : "", "\" data-index=\"").concat(index, "\">\n <div class=\"file-thumbnail\">\n <img src=\"").concat(src, "\" alt=\"").concat(displayName, "\" class=\"file-thumbnail-image\" />\n </div>\n <div class=\"file-attachment-info\">\n <div class=\"file-attachment-name\" title=\"").concat(displayName, "\">\n ").concat(displayName, "\n </div>\n <div class=\"file-attachment-size\">").concat(size, "</div>\n </div>\n <button\n class=\"file-attachment-remove\"\n data-index=\"").concat(index, "\"\n title=\"Remove file\"\n >\n \u2715\n </button>\n </div>\n ");
1171
+ }).join("");
1172
+
1173
+ // Bind remove buttons
1174
+ container.querySelectorAll(".file-attachment-remove").forEach(btn => {
1175
+ btn.addEventListener("click", e => {
1176
+ e.stopPropagation();
1177
+ const index = parseInt(btn.getAttribute("data-index"));
1178
+ this.removeBlock(index);
1179
+ });
1180
+ });
1181
+ }
1182
+ async toggleRecording(btn) {
1183
+ if (this.isRecording) {
1184
+ this.stopRecording(btn);
1185
+ } else {
1186
+ await this.startRecording(btn);
1187
+ }
1188
+ }
1189
+ async startRecording(btn) {
1190
+ try {
1191
+ const stream = await navigator.mediaDevices.getUserMedia({
1192
+ audio: true
1193
+ });
1194
+ this.mediaRecorder = new MediaRecorder(stream);
1195
+ this.audioChunks = [];
1196
+ this.mediaRecorder.ondataavailable = event => {
1197
+ this.audioChunks.push(event.data);
1198
+ };
1199
+ this.mediaRecorder.onstop = async () => {
1200
+ const audioBlob = new Blob(this.audioChunks, {
1201
+ type: "audio/wav"
87
1202
  });
88
- closeBtn.addEventListener("click", function () {
89
- expanded.classList.remove("visible");
90
- toggle.style.display = "flex";
1203
+ this.audioChunks = [];
1204
+
1205
+ // Stop all tracks
1206
+ stream.getTracks().forEach(track => track.stop());
1207
+
1208
+ // Process audio
1209
+ await this.processAudioInput(audioBlob);
1210
+ };
1211
+ this.mediaRecorder.start();
1212
+ this.isRecording = true;
1213
+ this.onRecordStart();
1214
+
1215
+ // Update UI
1216
+ btn.classList.add("recording");
1217
+ btn.innerHTML = "\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-square-icon\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"></rect></svg>\n ";
1218
+
1219
+ // Stop current TTS if any
1220
+ this.stopTTS();
1221
+ } catch (error) {
1222
+ console.error("Error accessing microphone:", error);
1223
+ alert("Could not access microphone. Please check permissions.");
1224
+ }
1225
+ }
1226
+ stopRecording(btn) {
1227
+ if (this.mediaRecorder && this.isRecording) {
1228
+ this.mediaRecorder.stop();
1229
+ this.isRecording = false;
1230
+ this.onRecordStop();
1231
+
1232
+ // Update UI
1233
+ btn.classList.remove("recording");
1234
+ btn.innerHTML = "\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" 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>\n ";
1235
+ }
1236
+ }
1237
+ async processAudioInput(audioBlob) {
1238
+ try {
1239
+ // Show processing state?
1240
+
1241
+ // 1. Convert to WAV Base64 (using Bhashini helper)
1242
+ const base64Audio = await this.bhashini.convertToWav(audioBlob);
1243
+
1244
+ // 2. STT
1245
+ const text = await this.bhashini.speechToText(base64Audio, this.selectedLanguage, this.selectedLanguage);
1246
+ if (text && text.trim()) {
1247
+ // 3. Send Message
1248
+ if (this.sendMessage) {
1249
+ await this.sendMessage(text, this.contentBlocks);
1250
+ // Clear attachments after sending
1251
+ this.contentBlocks = [];
1252
+ this.renderFilePreview(); // Update UI to remove preview
1253
+ }
1254
+ }
1255
+ } catch (error) {
1256
+ console.error("Error processing audio:", error);
1257
+ this.addMessage("Sorry, I couldn't understand that.", false, {
1258
+ isError: true
1259
+ });
1260
+ }
1261
+ }
1262
+ // 9113770648
1263
+ stopTTS() {
1264
+ // Cancel any active speech synthesis (if using browser API) or stop audio element
1265
+ if (this.currentAudio) {
1266
+ this.currentAudio.pause();
1267
+ this.currentAudio = null;
1268
+ }
1269
+ this.audioQueue = [];
1270
+ this.isPlaying = false;
1271
+ }
1272
+ async playNextInQueue() {
1273
+ // Check if audio screen is active
1274
+ const isVisible = this.container && this.container.querySelector(".audio-chat-screen");
1275
+ if (!isVisible) return;
1276
+ console.log("Playing next in queue", this.audioQueue);
1277
+ if (this.isPlaying || this.audioQueue.length === 0) return;
1278
+ this.isPlaying = true;
1279
+ const text = this.audioQueue.shift();
1280
+ try {
1281
+ const audioContent = await this.bhashini.textToSpeech(text, this.selectedLanguage);
1282
+ if (audioContent) {
1283
+ const audioSrc = "data:audio/wav;base64,".concat(audioContent);
1284
+ this.currentAudio = new Audio(audioSrc);
1285
+ this.currentAudio.onended = () => {
1286
+ this.isPlaying = false;
1287
+ this.playNextInQueue();
1288
+ };
1289
+ this.currentAudio.onerror = () => {
1290
+ this.isPlaying = false;
1291
+ this.playNextInQueue();
1292
+ };
1293
+ await this.currentAudio.play();
1294
+ } else {
1295
+ this.isPlaying = false;
1296
+ this.playNextInQueue();
1297
+ }
1298
+ } catch (error) {
1299
+ console.error("TTS Error:", error);
1300
+ this.isPlaying = false;
1301
+ this.playNextInQueue();
1302
+ }
1303
+ }
1304
+
1305
+ // Format file size (mirrors TextChatScreen)
1306
+ formatFileSize(bytes) {
1307
+ if (bytes === 0) return "0 Bytes";
1308
+ const k = 1024;
1309
+ const sizes = ["Bytes", "KB", "MB", "GB"];
1310
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1311
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
1312
+ }
1313
+
1314
+ // Get file type display name (mirrors TextChatScreen)
1315
+ getFileTypeDisplay(mimeType) {
1316
+ switch (mimeType) {
1317
+ case "image/jpeg":
1318
+ case "image/jpg":
1319
+ return "JPEG Image";
1320
+ case "image/png":
1321
+ return "PNG Image";
1322
+ case "image/gif":
1323
+ return "GIF Image";
1324
+ case "image/webp":
1325
+ return "WebP Image";
1326
+ case "application/pdf":
1327
+ return "PDF Document";
1328
+ default:
1329
+ return "Unknown File";
1330
+ }
1331
+ }
1332
+ _formatTime(date) {
1333
+ let hours = date.getHours();
1334
+ const minutes = date.getMinutes();
1335
+ const ampm = hours >= 12 ? 'PM' : 'AM';
1336
+ hours = hours % 12;
1337
+ hours = hours ? hours : 12;
1338
+ const strTime = hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0') + ' ' + ampm;
1339
+ return strTime;
1340
+ }
1341
+ addMessage(text, isUser) {
1342
+ let messageData = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1343
+ if (!this.container) return;
1344
+ const messagesContainer = this.container.querySelector("#chat-messages");
1345
+ if (!messagesContainer) return;
1346
+ const welcome = messagesContainer.querySelector(".chat-welcome");
1347
+ if (welcome) welcome.remove();
1348
+
1349
+ // Check if message already exists (for syncing)
1350
+ const messageId = (messageData === null || messageData === void 0 ? void 0 : messageData.id) || Date.now();
1351
+ const existingMessage = messagesContainer.querySelector("[data-message-id=\"".concat(messageId, "\"]"));
1352
+ if (existingMessage && messageData) {
1353
+ // Update existing message
1354
+ const bubble = existingMessage.querySelector(".message-bubble");
1355
+ if (bubble) {
1356
+ // Parse markdown for assistant messages, escape for user messages
1357
+ const content = messageData.content || text;
1358
+ bubble.innerHTML = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
1359
+ }
1360
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
1361
+ return;
1362
+ }
1363
+ const messageEl = document.createElement("div");
1364
+ messageEl.className = "chat-message ".concat(isUser ? "user" : "bot");
1365
+ messageEl.setAttribute("data-message-id", messageId);
1366
+
1367
+ // Handle different message types
1368
+ let content = text;
1369
+ if (messageData) {
1370
+ if (messageData.isStreaming) {
1371
+ content = messageData.content || "Thinking...";
1372
+ } else if (messageData.isError) {
1373
+ content = messageData.content || "Error occurred";
1374
+ messageEl.classList.add("error");
1375
+ } else {
1376
+ content = messageData.content || text;
1377
+ }
1378
+ }
1379
+
1380
+ // Build attachments HTML if present
1381
+ let attachmentsHTML = "";
1382
+ if (messageData && messageData.attachments && messageData.attachments.length > 0) {
1383
+ attachmentsHTML = "\n <div class=\"message-attachments\">\n ".concat(messageData.attachments.map((attachment, index) => {
1384
+ var _attachment$metadata, _attachment$metadata2, _attachment$metadata3;
1385
+ const displayName = ((_attachment$metadata = attachment.metadata) === null || _attachment$metadata === void 0 ? void 0 : _attachment$metadata.name) || ((_attachment$metadata2 = attachment.metadata) === null || _attachment$metadata2 === void 0 ? void 0 : _attachment$metadata2.filename) || "Unknown file";
1386
+ const isImage = attachment.type === "image";
1387
+ const imageSrc = isImage && attachment.data ? "data:".concat(attachment.mime_type, ";base64,").concat(attachment.data) : null;
1388
+ return "\n <div class=\"message-attachment ".concat(isImage ? "image" : "pdf", "\" data-attachment-index=\"").concat(index, "\">\n <span class=\"message-attachment-icon\">\n ").concat(isImage ? "🖼️" : "📄", "\n </span>\n <div class=\"message-attachment-info\">\n <div \n class=\"message-attachment-name ").concat(isImage ? "clickable" : "", "\"\n ").concat(isImage && imageSrc ? "data-image-src=\"".concat(imageSrc, "\" data-image-alt=\"").concat(displayName, "\"") : "", "\n style=\"cursor: ").concat(isImage ? "pointer" : "default", ";\"\n >\n ").concat(this._escapeHtml(displayName), "\n </div>\n <div class=\"message-attachment-size\">\n ").concat(this.formatFileSize(((_attachment$metadata3 = attachment.metadata) === null || _attachment$metadata3 === void 0 ? void 0 : _attachment$metadata3.size) || 0), " \u2022 ").concat(this.getFileTypeDisplay(attachment.mime_type), "\n </div>\n </div>\n </div>\n ");
1389
+ }).join(""), "\n </div>\n ");
1390
+ }
1391
+ const timestamp = messageData !== null && messageData !== void 0 && messageData.timestamp ? new Date(messageData.timestamp) : new Date();
1392
+ const formattedTime = this._formatTime(timestamp);
1393
+
1394
+ // Parse markdown for assistant messages, escape for user messages
1395
+ const renderedContent = !isUser ? this._parseMarkdown(content) : this._escapeHtml(content);
1396
+ messageEl.innerHTML = "\n <div class=\"message-content\">\n ".concat(attachmentsHTML, "\n <div class=\"message-bubble\">").concat(renderedContent, "</div>\n <div class=\"message-time\">").concat(formattedTime, "</div>\n </div>\n ");
1397
+
1398
+ // Bind image click handlers for expansion
1399
+ if (messageData && messageData.attachments) {
1400
+ messageEl.querySelectorAll(".message-attachment-name.clickable").forEach(el => {
1401
+ el.addEventListener("click", e => {
1402
+ e.stopPropagation();
1403
+ const imageSrc = el.getAttribute("data-image-src");
1404
+ const imageAlt = el.getAttribute("data-image-alt");
1405
+ if (imageSrc) {
1406
+ this.showExpandedImage(imageSrc, imageAlt);
1407
+ }
91
1408
  });
92
- input.addEventListener("input", function () {
93
- sendBtn.disabled = !input.value.trim();
1409
+ });
1410
+ }
1411
+ messagesContainer.appendChild(messageEl);
1412
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
1413
+ }
1414
+
1415
+ // Sync messages from shared array, using same DOM structure as TextChatScreen
1416
+ _syncMessages(messages) {
1417
+ if (!this.container) return;
1418
+ const messagesContainer = this.container.querySelector("#chat-messages");
1419
+ if (!messagesContainer) return;
1420
+
1421
+ // Remove welcome message if there are any messages
1422
+ if (messages.length > 0) {
1423
+ const welcome = messagesContainer.querySelector(".chat-welcome");
1424
+ if (welcome) welcome.remove();
1425
+ }
1426
+
1427
+ // Get existing message elements
1428
+ const existingMessages = messagesContainer.querySelectorAll("[data-message-id]");
1429
+ const existingIds = new Set(Array.from(existingMessages).map(el => el.getAttribute("data-message-id")));
1430
+
1431
+ // Add or update messages
1432
+ messages.forEach(msg => {
1433
+ const msgId = String(msg.id);
1434
+
1435
+ // Handle TTS for new bot messages
1436
+ if (msg.sender !== "user" && !this.playedMessageIds.has(msgId) && !msg.isStreaming && !msg.isProcessing && msg.content) {
1437
+ this.playedMessageIds.add(msgId);
1438
+ this.audioQueue.push(msg.content);
1439
+ this.playNextInQueue();
1440
+ }
1441
+ if (existingIds.has(msgId)) {
1442
+ // Update existing message
1443
+ const existingEl = messagesContainer.querySelector("[data-message-id=\"".concat(msgId, "\"]"));
1444
+ if (existingEl) {
1445
+ const bubble = existingEl.querySelector(".message-bubble");
1446
+ if (bubble) {
1447
+ // Parse markdown for assistant messages, escape for user messages
1448
+ const content = msg.content || "";
1449
+ const isUserMessage = msg.sender === "user";
1450
+ bubble.innerHTML = !isUserMessage ? this._parseMarkdown(content) : this._escapeHtml(content);
1451
+ }
1452
+
1453
+ // Update attachments if they exist
1454
+ const messageContent = existingEl.querySelector(".message-content");
1455
+ if (messageContent && msg.attachments && msg.attachments.length > 0) {
1456
+ let attachmentsHTML = "\n <div class=\"message-attachments\">\n ".concat(msg.attachments.map((attachment, index) => {
1457
+ var _attachment$metadata4, _attachment$metadata5, _attachment$metadata6;
1458
+ const displayName = ((_attachment$metadata4 = attachment.metadata) === null || _attachment$metadata4 === void 0 ? void 0 : _attachment$metadata4.name) || ((_attachment$metadata5 = attachment.metadata) === null || _attachment$metadata5 === void 0 ? void 0 : _attachment$metadata5.filename) || "Unknown file";
1459
+ const isImage = attachment.type === "image";
1460
+ const imageSrc = isImage && attachment.data ? "data:".concat(attachment.mime_type, ";base64,").concat(attachment.data) : null;
1461
+ return "\n <div class=\"message-attachment ".concat(isImage ? "image" : "pdf", "\" data-attachment-index=\"").concat(index, "\">\n <span class=\"message-attachment-icon\">\n ").concat(isImage ? "🖼️" : "📄", "\n </span>\n <div class=\"message-attachment-info\">\n <div \n class=\"message-attachment-name ").concat(isImage ? "clickable" : "", "\"\n ").concat(isImage && imageSrc ? "data-image-src=\"".concat(imageSrc, "\" data-image-alt=\"").concat(displayName, "\"") : "", "\n style=\"cursor: ").concat(isImage ? "pointer" : "default", ";\"\n >\n ").concat(this._escapeHtml(displayName), "\n </div>\n <div class=\"message-attachment-size\">\n ").concat(this.formatFileSize(((_attachment$metadata6 = attachment.metadata) === null || _attachment$metadata6 === void 0 ? void 0 : _attachment$metadata6.size) || 0), " \u2022 ").concat(this.getFileTypeDisplay(attachment.mime_type), "\n </div>\n </div>\n </div>\n ");
1462
+ }).join(""), "\n </div>\n ");
1463
+
1464
+ // Insert or update attachments
1465
+ const existingAttachments = messageContent.querySelector(".message-attachments");
1466
+ if (existingAttachments) {
1467
+ existingAttachments.outerHTML = attachmentsHTML;
1468
+ } else {
1469
+ messageContent.insertAdjacentHTML("afterbegin", attachmentsHTML);
1470
+ }
1471
+
1472
+ // Re-bind image click handlers
1473
+ messageContent.querySelectorAll(".message-attachment-name.clickable").forEach(el => {
1474
+ // Remove existing listeners by cloning
1475
+ const newEl = el.cloneNode(true);
1476
+ el.parentNode.replaceChild(newEl, el);
1477
+ newEl.addEventListener("click", e => {
1478
+ e.stopPropagation();
1479
+ const imageSrc = newEl.getAttribute("data-image-src");
1480
+ const imageAlt = newEl.getAttribute("data-image-alt");
1481
+ if (imageSrc) {
1482
+ this.showExpandedImage(imageSrc, imageAlt);
1483
+ }
1484
+ });
1485
+ });
1486
+ }
1487
+
1488
+ // Update classes based on message state
1489
+ if (msg.isStreaming) {
1490
+ existingEl.classList.add("streaming");
1491
+ } else {
1492
+ existingEl.classList.remove("streaming");
1493
+ }
1494
+ if (msg.isError) {
1495
+ existingEl.classList.add("error");
1496
+ }
1497
+ }
1498
+ } else {
1499
+ // Add new message
1500
+ this.addMessage(msg.content || "", msg.sender === "user", msg);
1501
+ }
1502
+ });
1503
+
1504
+ // Remove messages that are no longer in the array
1505
+ existingMessages.forEach(el => {
1506
+ const msgId = el.getAttribute("data-message-id");
1507
+ const exists = messages.some(msg => String(msg.id) === msgId);
1508
+ if (!exists) {
1509
+ el.remove();
1510
+ }
1511
+ });
1512
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
1513
+ }
1514
+ _escapeHtml(text) {
1515
+ const div = document.createElement("div");
1516
+ div.textContent = text;
1517
+ return div.innerHTML;
1518
+ }
1519
+
1520
+ // Parse markdown to HTML
1521
+ _parseMarkdown(text) {
1522
+ if (!text || typeof text !== 'string') return '';
1523
+ let html = text;
1524
+
1525
+ // Escape HTML first to prevent XSS
1526
+ html = html.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1527
+
1528
+ // Split into lines for processing
1529
+ const lines = html.split('\n');
1530
+ const processedLines = [];
1531
+ let inList = false;
1532
+ for (let i = 0; i < lines.length; i++) {
1533
+ let line = lines[i];
1534
+
1535
+ // Check for headers first (#, ##, ###, etc.)
1536
+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
1537
+ if (headerMatch) {
1538
+ if (inList) {
1539
+ processedLines.push('</ul>');
1540
+ inList = false;
1541
+ }
1542
+ const level = headerMatch[1].length;
1543
+ const headerText = headerMatch[2];
1544
+ // Process markdown inside header
1545
+ let processedHeader = headerText;
1546
+ processedHeader = processedHeader.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
1547
+ processedHeader = processedHeader.replace(/__([^_]+)__/g, '<strong>$1</strong>');
1548
+ processedHeader = processedHeader.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
1549
+ processedHeader = processedHeader.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
1550
+ processedLines.push("<h".concat(level, ">").concat(processedHeader, "</h").concat(level, ">"));
1551
+ continue;
1552
+ }
1553
+
1554
+ // Check if this is a list item (before processing bold/italic)
1555
+ const listMatch = line.match(/^[\s]*[-*]\s+(.+)$/);
1556
+ if (listMatch) {
1557
+ // Process markdown inside list item
1558
+ let listContent = listMatch[1];
1559
+
1560
+ // Bold: **text** (must be processed before italic)
1561
+ listContent = listContent.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
1562
+ listContent = listContent.replace(/__([^_]+)__/g, '<strong>$1</strong>');
1563
+
1564
+ // Italic: *text* (single asterisk, avoid matching **text**)
1565
+ listContent = listContent.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
1566
+ listContent = listContent.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
1567
+ if (!inList) {
1568
+ processedLines.push('<ul>');
1569
+ inList = true;
1570
+ }
1571
+ processedLines.push("<li>".concat(listContent, "</li>"));
1572
+ } else {
1573
+ if (inList) {
1574
+ processedLines.push('</ul>');
1575
+ inList = false;
1576
+ }
1577
+
1578
+ // Skip empty lines (they'll become <br> later)
1579
+ if (line.trim() === '') {
1580
+ processedLines.push('');
1581
+ continue;
1582
+ }
1583
+
1584
+ // Process markdown in non-list lines
1585
+ // Bold: **text** (must be processed before italic)
1586
+ line = line.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
1587
+ line = line.replace(/__([^_]+)__/g, '<strong>$1</strong>');
1588
+
1589
+ // Italic: *text* (single asterisk, avoid matching **text**)
1590
+ line = line.replace(/(^|[^*])\*([^*]+)\*([^*]|$)/g, '$1<em>$2</em>$3');
1591
+ line = line.replace(/(^|[^_])_([^_]+)_([^_]|$)/g, '$1<em>$2</em>$3');
1592
+
1593
+ // Inline code: `code`
1594
+ line = line.replace(/`([^`]+)`/g, '<code>$1</code>');
1595
+
1596
+ // Links: [text](url)
1597
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>');
1598
+ processedLines.push(line);
1599
+ }
1600
+ }
1601
+ if (inList) {
1602
+ processedLines.push('</ul>');
1603
+ }
1604
+
1605
+ // Join lines with <br> and return
1606
+ html = processedLines.join('<br>');
1607
+ return html;
1608
+ }
1609
+
1610
+ // Show expanded image modal (mirrors TextChatScreen)
1611
+ showExpandedImage(src, alt) {
1612
+ const existingModal = document.querySelector(".expanded-image-modal");
1613
+ if (existingModal) {
1614
+ existingModal.remove();
1615
+ }
1616
+ const modal = document.createElement("div");
1617
+ modal.className = "expanded-image-modal";
1618
+ modal.innerHTML = "\n <div class=\"expanded-image-container\">\n <button class=\"expanded-image-close\" title=\"Close\">\u2715</button>\n <img src=\"".concat(src, "\" alt=\"").concat(alt || "Image", "\" class=\"expanded-image\" />\n <div class=\"expanded-image-caption\">").concat(this._escapeHtml(alt || "Image preview"), "</div>\n </div>\n ");
1619
+ const closeModal = e => {
1620
+ if (e) e.stopPropagation();
1621
+ modal.remove();
1622
+ document.removeEventListener("mousedown", handleClickOutside);
1623
+ };
1624
+ const handleClickOutside = e => {
1625
+ if (!e.target.closest(".expanded-image-container")) {
1626
+ closeModal(e);
1627
+ }
1628
+ };
1629
+ modal.querySelector(".expanded-image-close").addEventListener("click", closeModal);
1630
+ modal.addEventListener("click", e => {
1631
+ if (e.target === modal) {
1632
+ closeModal(e);
1633
+ }
1634
+ });
1635
+ document.addEventListener("mousedown", handleClickOutside);
1636
+ document.body.appendChild(modal);
1637
+ }
1638
+ _applyStyles() {
1639
+ // Check if styles already applied
1640
+ if (document.getElementById('audio-chat-screen-styles')) return;
1641
+ const style = document.createElement("style");
1642
+ style.id = 'audio-chat-screen-styles';
1643
+ style.textContent = "\n .audio-chat-screen {\n flex: 1;\n height: 100%;\n display: flex;\n flex-direction: column;\n position: relative;\n overflow: hidden;\n background: linear-gradient(180deg, white 10%, #E1EFCC );\n }\n\n .audio-chat-screen .chat-header {\n color: ".concat(this.primaryColor, ";\n padding: 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n flex-shrink: 0;\n background: transparent;\n z-index: 20;\n }\n\n .audio-chat-screen .chat-header-content {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n }\n\n .audio-chat-screen .chat-back {\n background: ").concat(this.primaryColor, ";\n border: none;\n color: white;\n cursor: pointer;\n padding: 8px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n margin-right: 8px;\n }\n\n .bottom-inputs {\n width: 100%;\n padding: 20px;\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 5px;\n z-index: 10;\n flex-shrink: 0;\n }\n\n .bottom-inputs button {\n border: none;\n border-radius: 9999px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s ease;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n }\n\n #record-button {\n width: 50px;\n height: 50px;\n background: ").concat(this.primaryColor, ";\n color: #ffffff;\n }\n\n #record-button.recording {\n background: #ef4444;\n animation: pulse-ring 2s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;\n }\n\n @keyframes pulse-ring {\n 0% {\n box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);\n }\n 70% {\n box-shadow: 0 0 0 10px rgba(239, 68, 68, 0);\n }\n 100% {\n box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);\n }\n }\n\n #record-button:hover {\n transform: translateY(-2px) scale(1.03);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n }\n\n #record-button:active {\n transform: translateY(0) scale(0.97);\n box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);\n }\n\n #attachment-button {\n width: 50px;\n height: 50px;\n background: #ffffff;\n color: ").concat(this.primaryColor, ";\n border: 1px solid rgba(148, 163, 184, 0.5);\n }\n\n #attachment-button:hover {\n transform: translateY(-2px) scale(1.03);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n }\n\n #attachment-button:active {\n transform: translateY(1px);\n box-shadow: 0 3px 8px rgba(15, 23, 42, 0.15);\n }\n .audio-chat-screen .chat-avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.2);\n backdrop-filter: blur(10px);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .audio-chat-screen .chat-header-text {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .audio-chat-screen .chat-title {\n font-weight: 600;\n font-size: 20px;\n }\n\n .audio-chat-screen .chat-status {\n font-size: 12px;\n opacity: 0.9;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .audio-chat-screen .chat-close {\n background: ").concat(this.primaryColor, ";\n border: none;\n color: white;\n cursor: pointer;\n padding: 8px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n }\n\n\n .audio-chat-screen .audio-chat-content {\n flex: 1;\n height:100%;\n display: flex;\n flex-direction: column;\n padding: 20px;\n gap: 20px;\n }\n\n .audio-chat-screen .audio-status {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 40px 20px;\n background: white;\n border-radius: 16px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n\n .audio-chat-screen .audio-status-icon {\n font-size: 48px;\n margin-bottom: 12px;\n transition: transform 0.3s;\n }\n\n .audio-chat-screen .audio-status-text {\n font-size: 16px;\n font-weight: 500;\n color: #475569;\n }\n\n /* Message list styles mirrored from TextChatScreen */\n .audio-chat-screen .chat-messages {\n flex: 1;\n padding: 20px;\n overflow-y: auto;\n overflow-x: hidden; /* Prevent horizontal scroll */\n min-height: 0; /* Crucial for nested flex scrolling */\n background: transparent;\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .audio-chat-screen .chat-messages::-webkit-scrollbar {\n width: 6px;\n }\n\n .audio-chat-screen .chat-messages::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .audio-chat-screen .chat-messages::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 3px;\n }\n\n .audio-chat-screen .file-attachments-container {\n padding: 8px 12px;\n background: transparent;\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n max-height: 120px;\n overflow-y: auto;\n position: relative;\n z-index: 5;\n }\n\n .audio-chat-screen .file-attachment {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 10px;\n background: #f1f5f9;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n font-size: 12px;\n max-width: 200px;\n }\n\n .audio-chat-screen .file-thumbnail {\n width: 40px;\n height: 40px;\n border-radius: 6px;\n overflow: hidden;\n flex-shrink: 0;\n background: #e2e8f0;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .audio-chat-screen .file-thumbnail-image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n .audio-chat-screen .file-attachment-info {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .audio-chat-screen .file-attachment-name {\n font-weight: 500;\n color: #1e293b;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 120px;\n }\n\n .audio-chat-screen .file-attachment-size {\n font-size: 11px;\n color: #64748b;\n }\n\n .audio-chat-screen .file-attachment-remove {\n background: transparent;\n border: none;\n color: #64748b;\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n font-size: 16px;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n flex-shrink: 0;\n }\n\n .audio-chat-screen .file-attachment-remove:hover {\n background: #fee2e2;\n color: #dc2626;\n }\n\n .audio-chat-screen .chat-welcome {\n text-align: center;\n padding: 100px 20px;\n color: #64748b;\n }\n\n .audio-chat-screen .chat-welcome .welcome-icon {\n font-size: 48px;\n margin-bottom: 12px;\n }\n\n .audio-chat-screen .welcome-text {\n font-size: 15px;\n font-weight: 500;\n color: #475569;\n }\n\n .audio-chat-screen .chat-message {\n display: flex;\n gap: 8px;\n animation: messageSlide 0.3s ease-out;\n }\n\n @keyframes messageSlide {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .audio-chat-screen .chat-message.user {\n flex-direction: row-reverse;\n }\n\n .audio-chat-screen .message-content {\n max-width: 75%;\n }\n\n .audio-chat-screen .message-bubble {\n padding: 12px 16px;\n border-radius: 16px;\n font-size: 14px;\n line-height: 1.5;\n word-wrap: break-word;\n }\n\n .audio-chat-screen .chat-message.user .message-bubble {\n background: #e9f5d7;\n color: ").concat(this.primaryColor, ";\n border-bottom-right-radius: 4px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n\n .audio-chat-screen .chat-message.bot .message-bubble {\n background: white;\n color: #1e293b;\n border-bottom-left-radius: 4px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n\n /* Markdown styles */\n .audio-chat-screen .message-bubble strong {\n font-weight: 600;\n color: inherit;\n }\n\n .audio-chat-screen .message-bubble em {\n font-style: italic;\n }\n\n .audio-chat-screen .message-bubble ul {\n margin: 0px 0;\n padding-left: 20px;\n list-style-type: disc;\n margin-bottom:0px;\n margin-top:5px;\n }\n\n .audio-chat-screen .message-bubble li {\n margin: 0;\n line-height: 1.1;\n padding-left: 3px;\n }\n\n .audio-chat-screen .message-bubble h1,\n .audio-chat-screen .message-bubble h2,\n .audio-chat-screen .message-bubble h3,\n .audio-chat-screen .message-bubble h4,\n .audio-chat-screen .message-bubble h5,\n .audio-chat-screen .message-bubble h6 {\n margin: 0px 0 0px 0;\n font-weight: 600;\n color: inherit;\n line-height: 1.3;\n }\n\n .audio-chat-screen .message-bubble h1 {\n font-size: 1.5em;\n }\n\n .audio-chat-screen .message-bubble h2 {\n font-size: 1.3em;\n }\n\n .audio-chat-screen .message-bubble h3 {\n font-size: 1.15em;\n }\n\n .audio-chat-screen .message-bubble h4 {\n font-size: 1.05em;\n }\n\n .audio-chat-screen .message-bubble h5 {\n font-size: 1em;\n }\n\n .audio-chat-screen .message-bubble h6 {\n font-size: 0.95em;\n }\n\n .audio-chat-screen .message-bubble code {\n background: rgba(0, 0, 0, 0.05);\n padding: 2px 6px;\n border-radius: 4px;\n font-family: 'Courier New', Courier, monospace;\n font-size: 0.9em;\n }\n\n .audio-chat-screen .message-bubble a {\n color: ").concat(this.primaryColor, ";\n text-decoration: underline;\n }\n\n .audio-chat-screen .message-bubble a:hover {\n opacity: 0.8;\n }\n \n .audio-chat-screen .message-bubble br {\n line-height: 0.1; /* Adjust between 0 and 1 */\n}\n\n\n .audio-chat-screen .message-time {\n font-size: 10px;\n color: #94a3b8;\n margin-top: 4px;\n text-align: right;\n }\n\n /* Message Attachments Styles (mirrored from TextChatScreen) */\n .audio-chat-screen .message-attachments {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 8px;\n }\n\n .audio-chat-screen .message-attachment {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: #f1f5f9;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n font-size: 12px;\n max-width: 100%;\n }\n\n .audio-chat-screen .message-attachment.image {\n background: #f8fafc;\n }\n\n .audio-chat-screen .message-attachment.pdf {\n background: #fef2f2;\n }\n\n .audio-chat-screen .message-attachment-icon {\n font-size: 20px;\n flex-shrink: 0;\n }\n\n .audio-chat-screen .message-attachment-info {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n\n .audio-chat-screen .message-attachment-name {\n font-weight: 500;\n color: #1e293b;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .audio-chat-screen .message-attachment-name.clickable:hover {\n color: ").concat(this.primaryColor, ";\n text-decoration: underline;\n }\n\n .audio-chat-screen .message-attachment-size {\n font-size: 11px;\n color: #64748b;\n }\n\n .audio-chat-screen .audio-messages {\n flex: 1;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-height: 200px;\n }\n\n .audio-chat-screen .audio-message {\n display: flex;\n gap: 8px;\n animation: messageSlide 0.3s ease-out;\n }\n\n .audio-chat-screen .audio-message.user {\n flex-direction: row-reverse;\n }\n\n .audio-chat-screen .audio-message-content {\n max-width: 75%;\n }\n\n .audio-chat-screen .audio-message-bubble {\n padding: 12px 16px;\n border-radius: 16px;\n font-size: 14px;\n line-height: 1.5;\n word-wrap: break-word;\n }\n\n .audio-chat-screen .audio-message.user .audio-message-bubble {\n background: ").concat(this.primaryColor, ";\n color: white;\n border-bottom-right-radius: 4px;\n }\n\n .audio-chat-screen .audio-message.assistant .audio-message-bubble {\n background: white;\n color: #1e293b;\n border-bottom-left-radius: 4px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n\n @keyframes messageSlide {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .audio-chat-screen .audio-messages::-webkit-scrollbar {\n width: 6px;\n }\n\n .audio-chat-screen .audio-messages::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .audio-chat-screen .audio-messages::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 3px;\n }\n\n .audio-chat-screen .audio-controls {\n position: absolute;\n left: 50%;\n bottom: 20px;\n transform: translateX(-50%);\n display: flex;\n justify-content: center;\n align-items: center;\n pointer-events: none; /* let only the button receive events */\n }\n\n .audio-chat-screen .audio-controls .audio-record-btn {\n pointer-events: auto;\n }\n\n .audio-chat-screen .audio-record-btn {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: ").concat(this.primaryColor, ";\n color: white;\n border: none;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n transition: all 0.3s;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n }\n\n .audio-chat-screen .audio-record-btn:hover {\n transform: scale(1.05);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n }\n\n .audio-chat-screen .audio-record-btn:active {\n transform: scale(0.95);\n }\n\n .audio-chat-screen .audio-record-btn.recording {\n background: #ef4444;\n animation: pulse-record 1.5s infinite;\n }\n\n @keyframes pulse-record {\n 0%, 100% {\n box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);\n }\n 50% {\n box-shadow: 0 4px 24px rgba(239, 68, 68, 0.6);\n }\n }\n\n .audio-chat-screen .audio-record-btn span {\n font-size: 11px;\n font-weight: 500;\n margin-top: 4px;\n }\n\n .audio-chat-screen .audio-record-btn svg {\n width: 32px;\n height: 32px;\n }\n\n @media (max-width: 768px) {\n .audio-chat-screen .audio-record-btn {\n width: 100px;\n height: 100px;\n }\n }\n\n /* Expanded Image Modal Styles */\n .expanded-image-modal {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.8);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10000;\n animation: fadeIn 0.2s ease;\n }\n\n @keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }\n\n .expanded-image-container {\n position: relative;\n max-width: 90%;\n max-height: 90vh;\n background: #fff;\n border-radius: 8px;\n overflow: hidden;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n animation: scaleIn 0.2s ease;\n display: flex;\n flex-direction: column;\n }\n\n @keyframes scaleIn {\n from {\n transform: scale(0.9);\n opacity: 0;\n }\n to {\n transform: scale(1);\n opacity: 1;\n }\n }\n\n .expanded-image-close {\n position: absolute;\n top: 12px;\n right: 12px;\n background: rgba(0, 0, 0, 0.6);\n border: none;\n color: white;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n z-index: 1;\n transition: all 0.2s;\n }\n\n .expanded-image-close:hover {\n background: rgba(0, 0, 0, 0.8);\n transform: scale(1.1);\n }\n\n .expanded-image {\n max-width: 100%;\n max-height: calc(90vh - 60px);\n object-fit: contain;\n display: block;\n }\n\n .expanded-image-caption {\n padding: 12px 16px;\n background: #fff;\n color: #1e293b;\n font-size: 14px;\n text-align: center;\n border-top: 1px solid #e2e8f0;\n }\n ");
1644
+ document.head.appendChild(style);
1645
+ }
1646
+ }
1647
+
1648
+ class ChatWidget {
1649
+ constructor() {
1650
+ let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
1651
+ this.title = options.title || "Chat Assistant";
1652
+ this.placeholder = options.placeholder || "Type your message...";
1653
+ this.primaryColor = options.primaryColor || "#1a5c4b";
1654
+ this.container = null;
1655
+ this.messages = [];
1656
+ this.isMinimized = false;
1657
+ this.currentScreen = "welcome"; // welcome, text, audio
1658
+ this.textChatScreen = null;
1659
+ this.audioChatScreen = null;
1660
+
1661
+ // Configuration options for message sending
1662
+ this.langgraphUrl = options.langgraphUrl || "http://localhost:8080";
1663
+ this.authToken = options.authToken || null;
1664
+ this.threadId = options.threadId || null;
1665
+ this.assistantId = options.assistantId || null;
1666
+ this.selectedLanguage = options.selectedLanguage || "en";
1667
+ this.accessToken = options.accessToken || "";
1668
+ this.supabaseToken = options.supabaseToken || "";
1669
+ this.userInfo = options.userInfo || {};
1670
+ this.mcpServerUrl = options.mcpServerUrl || "http://localhost:8010/mcp";
1671
+ // Store the custom getHeaders function or use default
1672
+ this._customGetHeaders = options.getHeaders;
1673
+
1674
+ // Default getHeaders implementation
1675
+ this.getHeaders = () => {
1676
+ if (this._customGetHeaders) {
1677
+ return this._customGetHeaders();
1678
+ }
1679
+ const authToken = this.authToken || this.accessToken;
1680
+ const supabaseToken = this.supabaseToken || authToken;
1681
+ return _objectSpread2(_objectSpread2(_objectSpread2({
1682
+ "Content-Type": "application/json"
1683
+ }, authToken && {
1684
+ Authorization: "Bearer ".concat(authToken)
1685
+ }), supabaseToken && {
1686
+ "x-supabase-access-token": supabaseToken
1687
+ }), {}, {
1688
+ Origin: (typeof window !== 'undefined' ? window.location.origin : "*") || "*"
1689
+ });
1690
+ };
1691
+ this.getLatestCheckpoint = options.getLatestCheckpoint || (async () => null);
1692
+ this.updateThread = options.updateThread || (async () => {});
1693
+ this.getUserThreads = options.getUserThreads || (async _ref => {
1694
+ let {
1695
+ userId,
1696
+ userUuid
1697
+ } = _ref;
1698
+ try {
1699
+ const params = new URLSearchParams();
1700
+ if (userUuid) params.append("user_uuid", userUuid);
1701
+ const url = "".concat(this.langgraphUrl, "/history?").concat(params.toString());
1702
+ const response = await fetch(url, {
1703
+ method: "GET",
1704
+ headers: this.getHeaders()
94
1705
  });
95
- var send = function send() {
96
- var text = input.value.trim();
97
- if (!text) return;
98
- _this._addMessage(text, true);
99
- input.value = "";
100
- sendBtn.disabled = true;
101
- _this._showTypingIndicator();
102
- setTimeout(function () {
103
- _this._hideTypingIndicator();
104
- _this._addMessage("Thanks for your message! You said: \"".concat(text, "\""), false);
105
- }, 1000 + Math.random() * 1000);
1706
+ if (!response.ok) return {
1707
+ threads: []
1708
+ };
1709
+ return await response.json();
1710
+ } catch (e) {
1711
+ console.error("Error fetching threads", e);
1712
+ return {
1713
+ threads: []
106
1714
  };
107
- sendBtn.addEventListener("click", send);
108
- input.addEventListener("keypress", function (e) {
109
- if (e.key === "Enter" && !sendBtn.disabled) send();
110
- });
111
1715
  }
1716
+ });
1717
+ this.setUserInfoFromDirectChatLogin = options.setUserInfoFromDirectChatLogin || (() => {});
1718
+ this.setUserThreads = options.setUserThreads || (() => {});
1719
+ this.setUserThreadsMetaData = options.setUserThreadsMetaData || (() => {});
1720
+ this.languageOptions = [{
1721
+ label: "Assamese",
1722
+ value: "as"
112
1723
  }, {
113
- key: "_addMessage",
114
- value: function _addMessage(text, isUser) {
115
- var messagesContainer = this.container.querySelector("#chat-messages");
116
- var welcome = messagesContainer.querySelector(".chat-welcome");
117
- if (welcome) welcome.remove();
118
- var messageEl = document.createElement("div");
119
- messageEl.className = "chat-message ".concat(isUser ? "user" : "bot");
120
- messageEl.innerHTML = "<div class=\"message-bubble\">".concat(this._escapeHtml(text), "</div>");
121
- messagesContainer.appendChild(messageEl);
122
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
123
- }
1724
+ label: "Bengali",
1725
+ value: "bn"
124
1726
  }, {
125
- key: "_showTypingIndicator",
126
- value: function _showTypingIndicator() {
127
- var messagesContainer = this.container.querySelector("#chat-messages");
128
- var indicator = document.createElement("div");
129
- indicator.className = "chat-message bot";
130
- indicator.id = "typing-indicator";
131
- indicator.innerHTML = "\n <div class=\"typing-indicator\">\n <div class=\"typing-dot\"></div>\n <div class=\"typing-dot\"></div>\n <div class=\"typing-dot\"></div>\n </div>\n ";
132
- messagesContainer.appendChild(indicator);
133
- messagesContainer.scrollTop = messagesContainer.scrollHeight;
134
- }
1727
+ label: "Dogri",
1728
+ value: "doi"
135
1729
  }, {
136
- key: "_hideTypingIndicator",
137
- value: function _hideTypingIndicator() {
138
- var indicator = this.container.querySelector("#typing-indicator");
139
- if (indicator) indicator.remove();
140
- }
1730
+ label: "English",
1731
+ value: "en"
1732
+ }, {
1733
+ label: "Gujarati",
1734
+ value: "gu"
141
1735
  }, {
142
- key: "_escapeHtml",
143
- value: function _escapeHtml(text) {
144
- var div = document.createElement("div");
145
- div.textContent = text;
146
- return div.innerHTML;
1736
+ label: "Hindi",
1737
+ value: "hi"
1738
+ }, {
1739
+ label: "Kannada",
1740
+ value: "kn"
1741
+ }, {
1742
+ label: "Konkani",
1743
+ value: "gom"
1744
+ }, {
1745
+ label: "Maithili",
1746
+ value: "mai"
1747
+ }, {
1748
+ label: "Malayalam",
1749
+ value: "ml"
1750
+ }, {
1751
+ label: "Marathi",
1752
+ value: "mr"
1753
+ }, {
1754
+ label: "Oriya",
1755
+ value: "or"
1756
+ }, {
1757
+ label: "Punjabi",
1758
+ value: "pa"
1759
+ }, {
1760
+ label: "Sanskrit",
1761
+ value: "sa"
1762
+ }, {
1763
+ label: "Sindhi",
1764
+ value: "sd"
1765
+ }, {
1766
+ label: "Tamil",
1767
+ value: "ta"
1768
+ }, {
1769
+ label: "Telugu",
1770
+ value: "te"
1771
+ }, {
1772
+ label: "Urdu",
1773
+ value: "ur"
1774
+ }];
1775
+
1776
+ // State management
1777
+ this.isLoading = false;
1778
+ this.isConnected = false;
1779
+ this.initialized = false;
1780
+ this.contentBlocks = [];
1781
+ this.playedAudioIds = new Set();
1782
+ this.currentInputAudio = null;
1783
+ this.submissionInProgress = false;
1784
+ this.processingAudioId = null;
1785
+ this._init();
1786
+
1787
+ // Initialize chat after DOM is ready
1788
+ if (typeof window !== 'undefined') {
1789
+ // Use setTimeout to ensure DOM is ready
1790
+ setTimeout(() => {
1791
+ this.initializeChat();
1792
+ }, 100);
1793
+ }
1794
+ }
1795
+ _init() {
1796
+ // Create container
1797
+ this.container = document.createElement("div");
1798
+ this.container.id = "chat-widget-container";
1799
+ this.container.innerHTML = "\n <div class=\"chat-widget-minimized\" id=\"chat-toggle\">\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <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>\n </svg>\n </div>\n <div class=\"chat-widget-expanded\" id=\"chat-expanded\">\n <div class=\"chat-screen-container\" id=\"chat-screen-container\">\n <!-- Screens will be rendered here -->\n </div>\n <div class=\"chat-drawer-overlay\" id=\"chat-drawer-overlay\">\n <div class=\"chat-drawer\">\n <div class=\"drawer-header\">\n <div class=\"drawer-title\">Menu</div>\n </div>\n <div class=\"drawer-content\">\n <button class=\"drawer-item\" id=\"drawer-new-chat\">\n New Chat\n </button> \n <div class=\"language-selector-container\">\n <div class=\"custom-select\" id=\"language-selector\">\n <div class=\"select-trigger\" id=\"language-trigger\">\n <span id=\"selected-language-text\">English</span>\n <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>\n </div>\n <div class=\"select-options\" id=\"language-options\"></div>\n </div>\n </div>\n <div class=\"drawer-divider\" style=\"height: 1px; background: #e2e8f0; margin: 8px 0;\"></div>\n \n <div id=\"drawer-threads\" class=\"threads-container\">\n <!-- Threads will be rendered here -->\n <div class=\"threads-loading\">Loading history...</div>\n </div>\n </div>\n \n </div>\n <div class=\"drawer-backdrop\" id=\"drawer-backdrop\"></div>\n </div>\n </div>\n ";
1800
+ document.body.appendChild(this.container);
1801
+ this._applyStyles();
1802
+ this._initScreens();
1803
+ this._renderScreen();
1804
+ this._populateLanguageOptions();
1805
+ this._bindEvents();
1806
+ }
1807
+ _applyStyles() {
1808
+ const style = document.createElement("style");
1809
+ style.textContent = "\n @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');\n \n #chat-widget-container {\n position: fixed;\n bottom: 24px;\n right: 24px;\n z-index: 10000;\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n }\n.text-chat-screen {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n .chat-header {\n color: ".concat(this.primaryColor, ";\n padding: 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .chat-header-content {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n }\n\n .chat-back {\n background: transparent;\n border: none;\n color: white;\n cursor: pointer;\n padding: 8px;\n border-radius: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n margin-right: 8px;\n }\n\n .chat-back:hover {\n background: rgba(255, 255, 255, 0.15);\n }\n\n .chat-avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.2);\n backdrop-filter: blur(10px);\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n .chat-header-text {\n display: flex;\n flex-direction: column;\n gap: 2px;\n }\n \n .chat-title {\n font-weight: 600;\n font-size: 20px;\n }\n\n .chat-status {\n font-size: 12px;\n opacity: 0.9;\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .chat-close {\n background: ").concat(this.primaryColor, ";\n border: none;\n color: white;\n cursor: pointer;\n padding: 8px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n }\n\n .chat-widget-minimized {\n width: 60px;\n height: 60px;\n border-radius: 50%;\n background: ").concat(this.primaryColor, ";\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .chat-widget-minimized:hover {\n transform: scale(1.1);\n }\n\n @keyframes pulse {\n 0%, 100% { box-shadow: 0 8px 24px rgba(99, 102, 241, 0.4); }\n 50% { box-shadow: 0 8px 32px rgba(99, 102, 241, 0.6); }\n }\n\n .chat-widget-expanded {\n position: relative;\n width: 480px;\n height: 640px;\n background: white;\n border-radius: 16px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);\n display: none;\n flex-direction: column;\n overflow: hidden;\n animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n @keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(20px) scale(0.95);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n }\n\n .chat-widget-expanded.visible {\n display: flex;\n }\n\n .chat-screen-container {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n\n /* Welcome Screen Styles */\n .welcome-screen {\n flex: 1;\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n justify-content: start;\n background: linear-gradient(180deg, white 10%, #E1EFCC );\n padding: 0px;\n }\n\n .welcome-content {\n width: 80%;\n margin: auto;\n text-align: center;\n padding-top: 20px;\n }\n\n .welcome-icon {\n font-size: 64px;\n margin-bottom: 16px;\n animation: bounce 2s infinite;\n }\n\n @keyframes bounce {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-10px); }\n }\n\n .welcome-title {\n font-size: 24px;\n font-weight: 600;\n color: #1e293b;\n margin: 0 0 8px 0;\n }\n\n .welcome-subtitle {\n font-size: 14px;\n color: #64748b;\n margin: 0 0 32px 0;\n }\n\n .welcome-options {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n\n .welcome-option-btn {\n background: white;\n border: 2px solid #e2e8f0;\n border-radius: 12px;\n padding: 16px;\n display: flex;\n align-items: center;\n gap: 12px;\n cursor: pointer;\n transition: all 0.2s;\n text-align: left;\n width: 100%;\n }\n\n .welcome-option-btn:hover {\n border-color: ").concat(this.primaryColor, ";\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n transform: translateY(-2px);\n }\n\n .option-icon {\n font-size: 32px;\n flex-shrink: 0;\n }\n\n .option-content {\n flex: 1;\n }\n\n .option-title {\n font-size: 16px;\n font-weight: 600;\n color: #1e293b;\n margin-bottom: 4px;\n }\n\n .option-description {\n font-size: 13px;\n color: #64748b;\n }\n\n .welcome-option-btn svg {\n color: #94a3b8;\n flex-shrink: 0;\n }\n\n .welcome-option-btn:hover svg {\n color: ").concat(this.primaryColor, ";\n }\n.gradient-sphere-welcome-screen {\n width: 200px !important;\n height: 200px !important;\n border-radius: 50%;\n background: linear-gradient(135deg, #0a0d120f 0%, #85bc31 50%, #d0f19e 100%)\n !important;\n position: relative;\n margin: 20px auto 30px auto;\n box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);\n animation: float 3s ease-in-out infinite;\n flex-shrink: 0;\n}\n\n.sphere-highlight {\n position: absolute;\n top: 20%;\n right: 20%;\n width: 35px;\n height: 35px;\n background: radial-gradient(\n circle,\n rgba(255, 255, 255, 0.8) 0%,\n transparent 70%\n );\n border-radius: 50%;\n filter: blur(1px);\n}\n\n@keyframes float {\n 0%,\n 100% {\n transform: translateY(0px);\n }\n 50% {\n transform: translateY(-10px);\n }\n}\n\n.welcome-text {\n max-width: 400px;\n margin-bottom: 30px;\n flex-shrink: 0;\n text-align: left;\n}\n\n.greeting {\n font-size: 24px;\n font-weight: 600;\n color: #1a5c4b;\n margin: 0 0 16px 0;\n}\n\n.intro {\n font-size: 18px;\n font-weight: 500;\n color: #1a5c4b;\n margin: 0 0 12px 0;\n line-height: 1.4;\n}\n\n\n.action-buttons {\n display: flex;\n align-items: center;\n gap: 16px;\n flex-shrink: 0;\n margin-top: auto;\n /* padding-top: 10px; */\n padding-bottom: 20px;\n justify-content: center;\n width: 100%;\n align-self: center;\n}\n\n.primary-button {\n background: #1a5c4b;\n display:flex;\n color: white;\n border: none;\n padding: 16px 32px;\n border-radius: 25px;\n font-size: 16px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.3s ease;\n box-shadow: 0 4px 12px rgba(26, 92, 75, 0.3);\n}\n\n.primary-button:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 16px rgba(26, 92, 75, 0.4);\n}\n\n @media (max-width: 768px) {\n #chat-widget-container {\n bottom: 16px;\n right: 16px;\n }\n\n .chat-widget-expanded {\n width: 100vw;\n height: 100vh;\n border-radius: 0;\n max-width: 100vw;\n max-height: 100vh;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n }\n\n .chat-widget-expanded.visible {\n display: flex;\n }\n\n .welcome-content {\n max-width: 100%;\n padding-top: 40px;\n }\n\n .chat-drawer {\n width: 85% !important; /* Wider drawer on mobile */\n }\n }\n\n /* Drawer Styles */\n .chat-drawer-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 2000;\n display: flex;\n pointer-events: none;\n visibility: hidden;\n }\n\n .chat-drawer-overlay.visible {\n pointer-events: auto;\n visibility: visible;\n }\n\n .chat-drawer {\n width: 65%;\n background: white;\n height: 100%;\n transform: translateX(-100%);\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n flex-direction: column;\n z-index: 2002;\n box-shadow: 4px 0 24px rgba(0,0,0,0.1);\n }\n\n .chat-drawer-overlay.visible .chat-drawer {\n transform: translateX(0);\n }\n\n .drawer-backdrop {\n flex: 1;\n background: rgba(0, 0, 0, 0.5);\n opacity: 0;\n transition: opacity 0.3s ease;\n backdrop-filter: blur(2px);\n cursor: pointer;\n }\n\n .chat-drawer-overlay.visible .drawer-backdrop {\n opacity: 1;\n }\n\n .drawer-header {\n padding: 24px;\n border-bottom: 1px solid #f1f5f9;\n }\n\n .drawer-title {\n font-size: 20px;\n font-weight: 600;\n color: ").concat(this.primaryColor, ";\n }\n\n .drawer-content {\n flex: 1;\n padding: 16px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n overflow-y: auto;\n }\n\n .drawer-item {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n background: transparent;\n border: none;\n border-radius: 8px;\n color: #475569;\n font-size: 15px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n text-align: left;\n }\n\n .drawer-item:hover {\n background: #f1f5f9;\n color: ").concat(this.primaryColor, ";\n }\n\n .drawer-item svg {\n opacity: 0.7;\n }\n\n .drawer-item:hover svg {\n opacity: 1;\n color: ").concat(this.primaryColor, ";\n }\n\n .drawer-footer {\n padding: 16px 24px;\n border-top: 1px solid #f1f5f9;\n }\n\n .drawer-version {\n font-size: 12px;\n color: #94a3b8;\n text-align: center;\n }\n\n /* Language Selector Styles */\n .language-selector-container {\n margin-top: 8px;\n margin-bottom: 8px;\n }\n\n .language-label {\n font-size: 11px;\n font-weight: 600;\n color: #94a3b8;\n margin-bottom: 6px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n padding-left: 4px;\n }\n\n .custom-select {\n position: relative;\n width: 100%;\n user-select: none;\n }\n\n .select-trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 12px;\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s;\n font-size: 14px;\n color: #334155;\n }\n\n .select-trigger:hover {\n border-color: ").concat(this.primaryColor, ";\n background: white;\n }\n\n .select-trigger.active {\n border-color: ").concat(this.primaryColor, ";\n background: white;\n box-shadow: 0 0 0 2px rgba(26, 92, 75, 0.1);\n }\n\n .select-options {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n background: white;\n border: 1px solid #e2e8f0;\n border-radius: 8px;\n box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);\n max-height: 200px;\n overflow-y: auto;\n z-index: 50;\n display: none;\n opacity: 0;\n transform: translateY(-10px);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .select-options.open {\n display: block;\n opacity: 1;\n transform: translateY(0);\n }\n\n .select-option {\n padding: 10px 12px;\n font-size: 14px;\n color: #334155;\n cursor: pointer;\n transition: all 0.1s;\n }\n\n .select-option:hover {\n background: #f1f5f9;\n color: ").concat(this.primaryColor, ";\n }\n\n .select-option.selected {\n background: rgba(26, 92, 75, 0.08);\n color: ").concat(this.primaryColor, ";\n font-weight: 500;\n }\n \n .chevron {\n transition: transform 0.2s ease;\n color: #94a3b8;\n }\n \n .select-trigger.active .chevron {\n transform: rotate(180deg);\n color: ").concat(this.primaryColor, ";\n }\n \n /* Thread List Styles */\n .threads-container {\n flex: 1;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding-top: 8px;\n }\n \n .threads-loading {\n padding: 16px;\n text-align: center;\n color: #94a3b8;\n font-size: 13px;\n }\n \n .thread-item {\n display: flex;\n flex-direction: column;\n padding: 10px 12px;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s;\n text-align: left;\n border: none;\n background: transparent;\n width: 100%;\n color: #475569;\n }\n \n .thread-item:hover {\n background-color: #f1f5f9; /* action.hover */\n color: ").concat(this.primaryColor, ";\n }\n \n .thread-item.selected {\n background-color: rgba(26, 92, 75, 0.08); /* action.selected */\n }\n \n .thread-content {\n font-size: 0.9rem;\n color: #334155;\n line-height: 1.3;\n margin-bottom: 4px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n font-weight: 500;\n }\n \n .thread-date {\n font-size: 0.75rem;\n font-weight: 600;\n color: #94a3b8;\n }\n \n .no-history {\n padding: 16px;\n text-align: center;\n color: #94a3b8;\n font-size: 14px;\n }\n ");
1810
+ document.head.appendChild(style);
1811
+ }
1812
+ _renderScreen() {
1813
+ const screenContainer = this.container.querySelector("#chat-screen-container");
1814
+ switch (this.currentScreen) {
1815
+ case "welcome":
1816
+ this._renderWelcomeScreen(screenContainer);
1817
+ break;
1818
+ case "text":
1819
+ this._renderTextChatScreen(screenContainer);
1820
+ break;
1821
+ case "audio":
1822
+ this._renderAudioChatScreen(screenContainer);
1823
+ break;
1824
+ }
1825
+ }
1826
+ _initScreens() {
1827
+ // Initialize text chat screen
1828
+ this.textChatScreen = new TextChatScreen({
1829
+ title: this.title,
1830
+ placeholder: this.placeholder,
1831
+ primaryColor: this.primaryColor,
1832
+ onMessage: (text, respond) => {
1833
+ this._handleUserMessage(text, respond);
1834
+ },
1835
+ onBack: () => {
1836
+ this.currentScreen = "welcome";
1837
+ this._renderScreen();
1838
+ },
1839
+ onOpenDrawer: () => {
1840
+ this._toggleDrawer(true);
1841
+ },
1842
+ onClose: () => {
1843
+ const expanded = this.container.querySelector("#chat-expanded");
1844
+ const toggle = this.container.querySelector("#chat-toggle");
1845
+ expanded.classList.remove("visible");
1846
+ toggle.style.display = "flex";
1847
+ this.currentScreen = "welcome";
1848
+ this.messages = [];
1849
+ this.contentBlocks = [];
1850
+ this._initScreens();
1851
+ },
1852
+ messages: this.messages,
1853
+ sendMessage: (text, contentBlocks) => {
1854
+ return this.sendMessage(text, contentBlocks);
1855
+ },
1856
+ contentBlocks: this.contentBlocks,
1857
+ onContentBlocksChange: contentBlocks => {
1858
+ this.contentBlocks = contentBlocks;
1859
+ },
1860
+ navigateToAudioScreen: () => {
1861
+ this._navigateToScreen("audio");
147
1862
  }
148
- }]);
149
- }(); // Export as UMD and ESM compatible
150
- {
151
- module.exports = ChatWidget;
1863
+ });
1864
+
1865
+ // Initialize audio chat screen
1866
+ this.audioChatScreen = new AudioChatScreen({
1867
+ title: this.title,
1868
+ primaryColor: this.primaryColor,
1869
+ onRecordStart: () => {
1870
+ // Handle recording start
1871
+ },
1872
+ onRecordStop: () => {
1873
+ // Handle recording stop
1874
+ },
1875
+ onBack: () => {
1876
+ this.currentScreen = "welcome";
1877
+ this._renderScreen();
1878
+ },
1879
+ onOpenDrawer: () => {
1880
+ this._toggleDrawer(true);
1881
+ },
1882
+ onClose: () => {
1883
+ const expanded = this.container.querySelector("#chat-expanded");
1884
+ const toggle = this.container.querySelector("#chat-toggle");
1885
+ expanded.classList.remove("visible");
1886
+ toggle.style.display = "flex";
1887
+ this.currentScreen = "welcome";
1888
+ this.messages = [];
1889
+ this._initScreens();
1890
+ },
1891
+ messages: this.messages,
1892
+ sendMessage: (text, contentBlocks) => {
1893
+ return this.sendMessage(text, contentBlocks);
1894
+ },
1895
+ selectedLanguage: this.selectedLanguage,
1896
+ navigateToTextScreen: () => {
1897
+ this._navigateToScreen("text");
1898
+ }
1899
+ });
1900
+ }
1901
+ _renderWelcomeScreen(container) {
1902
+ container.innerHTML = "\n <main class=\"welcome-screen\">\n <div class=\"chat-header\">\n <div class=\"chat-header-content visible\">\n <div class=\"chat-header-text\">\n <div class=\"chat-title\">".concat(this.title, "</div>\n </div>\n </div>\n <button class=\"chat-close\" id=\"text-chat-close\">\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n <div >\n <div class=\"welcome-content\">\n <div class=\"gradient-sphere-welcome-screen\">\n <div class=\"sphere-highlight\">\n </div>\n </div>\n \n <div class=\"welcome-text\">\n <p class=\"greeting\">Hello!</p>\n <p class=\"intro\">\n We are your Krishi Vigyan Sahayak\u2014KVS, a companion to help with every farming question! Tell us, what do you want to know today?\n </p>\n </div>\n \n <div class=\"action-buttons\">\n <button\n class=\"primary-button !rounded-full\"\n id=\"welcome-audio-btn\"\n >\n 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>\n </button>\n </div>\n \n \n\n </div>\n </div>\n </main>\n ");
1903
+
1904
+ // Bind welcome screen events
1905
+ // const textBtn = container.querySelector("#welcome-text-btn");
1906
+ const audioBtn = container.querySelector("#welcome-audio-btn");
1907
+ const closeBtn = container.querySelector("#text-chat-close");
1908
+
1909
+ // textBtn.addEventListener("click", () => this._navigateToScreen("text"));
1910
+ audioBtn.addEventListener("click", () => this._navigateToScreen("audio"));
1911
+ if (closeBtn) {
1912
+ closeBtn.addEventListener("click", () => {
1913
+ const expanded = this.container.querySelector("#chat-expanded");
1914
+ const toggle = this.container.querySelector("#chat-toggle");
1915
+ expanded.classList.remove("visible");
1916
+ toggle.style.display = "flex";
1917
+ this.currentScreen = "welcome";
1918
+ this.messages = [];
1919
+ this._initScreens();
1920
+ });
1921
+ }
152
1922
  }
153
- })(chatWidget);
154
- var chatWidgetExports = chatWidget.exports;
155
- var chatWidget_default = /*@__PURE__*/getDefaultExportFromCjs(chatWidgetExports);
1923
+ _renderTextChatScreen(container) {
1924
+ if (this.textChatScreen) {
1925
+ // Sync contentBlocks before rendering
1926
+ this.textChatScreen.contentBlocks = this.contentBlocks;
1927
+ this.textChatScreen.messages = this.messages;
1928
+ this.textChatScreen.render(container);
1929
+ }
1930
+ }
1931
+ _renderAudioChatScreen(container) {
1932
+ if (this.audioChatScreen) {
1933
+ this.audioChatScreen.messages = this.messages;
1934
+ this.audioChatScreen.render(container);
1935
+ }
1936
+ }
1937
+ _navigateToScreen(screen) {
1938
+ this.currentScreen = screen;
1939
+ this._renderScreen();
1940
+ }
1941
+ _populateLanguageOptions() {
1942
+ const optionsContainer = this.container.querySelector("#language-options");
1943
+ if (!optionsContainer) return;
1944
+ optionsContainer.innerHTML = this.languageOptions.map(opt => "\n <div class=\"select-option ".concat(opt.value === this.selectedLanguage ? 'selected' : '', "\" data-value=\"").concat(opt.value, "\">\n ").concat(opt.label, "\n </div>\n ")).join('');
1945
+ this._updateSelectedLanguageDisplay();
1946
+ }
1947
+ _updateSelectedLanguageDisplay() {
1948
+ const textSpan = this.container.querySelector("#selected-language-text");
1949
+ if (textSpan) {
1950
+ const option = this.languageOptions.find(opt => opt.value === this.selectedLanguage);
1951
+ textSpan.textContent = option ? option.label : "English";
1952
+ }
1953
+ }
1954
+ _bindLanguageSelectorEvents() {
1955
+ const selector = this.container.querySelector("#language-selector");
1956
+ const trigger = this.container.querySelector("#language-trigger");
1957
+ const optionsFn = this.container.querySelector("#language-options");
1958
+ if (trigger && optionsFn) {
1959
+ trigger.addEventListener("click", e => {
1960
+ e.stopPropagation();
1961
+ const isOpen = optionsFn.classList.contains("open");
1962
+ if (isOpen) {
1963
+ optionsFn.classList.remove("open");
1964
+ trigger.classList.remove("active");
1965
+ } else {
1966
+ optionsFn.classList.add("open");
1967
+ trigger.classList.add("active");
1968
+ }
1969
+ });
1970
+ const options = optionsFn.querySelectorAll(".select-option");
1971
+ options.forEach(opt => {
1972
+ opt.addEventListener("click", e => {
1973
+ const value = e.currentTarget.dataset.value;
1974
+ this.selectedLanguage = value;
1975
+
1976
+ // Update UI
1977
+ this._updateSelectedLanguageDisplay();
1978
+ optionsFn.querySelectorAll(".select-option").forEach(o => o.classList.remove("selected"));
1979
+ e.currentTarget.classList.add("selected");
1980
+
1981
+ // Close dropdown
1982
+ optionsFn.classList.remove("open");
1983
+ trigger.classList.remove("active");
1984
+ console.log("Language selected:", this.selectedLanguage);
1985
+ });
1986
+ });
1987
+
1988
+ // Close on click outside
1989
+ document.addEventListener("click", e => {
1990
+ if (selector && !selector.contains(e.target)) {
1991
+ optionsFn.classList.remove("open");
1992
+ trigger.classList.remove("active");
1993
+ }
1994
+ });
1995
+ }
1996
+ }
1997
+ async _fetchThreads() {
1998
+ const threadsContainer = this.container.querySelector("#drawer-threads");
1999
+ if (!threadsContainer) return;
2000
+ threadsContainer.innerHTML = '<div class="threads-loading">Loading history...</div>';
2001
+ try {
2002
+ const storedUser = localStorage.getItem("DfsWeb.user-info");
2003
+ const user = storedUser ? JSON.parse(storedUser) : null;
2004
+ if (!(user !== null && user !== void 0 && user.uuid)) {
2005
+ threadsContainer.innerHTML = '<div class="no-history">Please log in to view history</div>';
2006
+ return;
2007
+ }
2008
+ console.log("USER ID", user.id, "USER UUID", user.uuid);
2009
+ const data = await this.getUserThreads({
2010
+ userId: user.id,
2011
+ userUuid: user.uuid
2012
+ });
2013
+ console.log("THREAD DATA", data);
2014
+ const threads = data.threads || [];
2015
+ const threadsWithHistory = threads.filter(thread => thread.history && thread.history.length > 0);
2016
+ this._renderThreadList(threadsWithHistory);
2017
+ } catch (error) {
2018
+ console.error("UNEXPECTED ERROR IN FETCHING THREADS", error.message);
2019
+ threadsContainer.innerHTML = '<div class="no-history">Failed to load history</div>';
2020
+ }
2021
+ }
2022
+ _renderThreadList(threads) {
2023
+ const threadsContainer = this.container.querySelector("#drawer-threads");
2024
+ if (!threadsContainer) return;
2025
+ if (threads.length === 0) {
2026
+ threadsContainer.innerHTML = '<div class="no-history">No history available</div>';
2027
+ return;
2028
+ }
2029
+ threadsContainer.innerHTML = '';
2030
+ threads.forEach(thread => {
2031
+ var _thread$history$;
2032
+ const isSelected = thread.thread_id === this.threadId; // Use current threadId
2033
+
2034
+ const threadItem = document.createElement('div');
2035
+ threadItem.className = "thread-item ".concat(isSelected ? 'selected' : '');
2036
+
2037
+ // Get primary text (first user message)
2038
+ const primaryText = ((_thread$history$ = thread.history[0]) === null || _thread$history$ === void 0 || (_thread$history$ = _thread$history$.values) === null || _thread$history$ === void 0 || (_thread$history$ = _thread$history$.messages[0]) === null || _thread$history$ === void 0 ? void 0 : _thread$history$.content) || "New Chat";
2039
+ // Handle array content (if content is array of blocks)
2040
+ let displayText = primaryText;
2041
+ if (Array.isArray(primaryText)) {
2042
+ const textBlock = primaryText.find(b => b.type === 'text');
2043
+ displayText = textBlock ? textBlock.text : "Multimedia Message";
2044
+ } else if (typeof primaryText === 'object') {
2045
+ displayText = "Message";
2046
+ }
2047
+ const date = new Date(thread.created_at).toLocaleString();
2048
+ threadItem.innerHTML = "\n <div class=\"thread-content\">".concat(displayText, "</div>\n <div class=\"thread-date\">").concat(date, "</div>\n ");
2049
+ threadItem.addEventListener('click', () => {
2050
+ this._handleThreadSelect(thread.thread_id);
2051
+ });
2052
+ threadsContainer.appendChild(threadItem);
2053
+ });
2054
+ }
2055
+ _handleThreadSelect(threadId) {
2056
+ console.log("Select thread:", threadId);
2057
+ this.threadId = threadId;
2058
+ this._toggleDrawer(false);
2059
+
2060
+ // Switch to text screen to show history
2061
+ this.currentScreen = "text";
2062
+
2063
+ // Clear current messages
2064
+ this.messages = [];
2065
+ this.setIsLoading = true; // Set loading state if you have setter, otherwise this.isLoading = true
2066
+ this.isLoading = true;
2067
+
2068
+ // Re-initialize screens (clears them)
2069
+ // this._initScreens(); // Can't fully re-init as it might break event listeners
2070
+ // Better to just load history and render
2071
+
2072
+ this._renderScreen();
2073
+
2074
+ // Load history for this thread
2075
+ this.loadHistory(threadId).then(() => {
2076
+ this.isLoading = false;
2077
+ this._renderScreen(); // Re-render with new messages
2078
+ });
2079
+ }
2080
+ _bindEvents() {
2081
+ const toggle = this.container.querySelector("#chat-toggle");
2082
+ const expanded = this.container.querySelector("#chat-expanded");
2083
+
2084
+ // Ensure toggle button is visible
2085
+ if (toggle) {
2086
+ toggle.style.display = "flex";
2087
+ }
2088
+ toggle.addEventListener("click", () => {
2089
+ console.log("Clicked");
2090
+ toggle.style.display = "none";
2091
+ expanded.classList.add("visible");
2092
+ this.currentScreen = "welcome";
2093
+ this._renderScreen();
2094
+ });
2095
+
2096
+ // Drawer events
2097
+ const drawerBackdrop = this.container.querySelector("#drawer-backdrop");
2098
+ if (drawerBackdrop) {
2099
+ drawerBackdrop.addEventListener("click", () => {
2100
+ this._toggleDrawer(false);
2101
+ });
2102
+ }
2103
+ const drawerItems = this.container.querySelectorAll(".drawer-item");
2104
+ drawerItems.forEach(item => {
2105
+ item.addEventListener("click", e => {
2106
+ // Handle drawer item click
2107
+ this._toggleDrawer(false);
2108
+ const targetId = e.currentTarget.id;
2109
+ if (targetId === "drawer-new-chat") {
2110
+ this._resetChat();
2111
+ }
2112
+ });
2113
+ });
2114
+ this._bindLanguageSelectorEvents();
2115
+ }
2116
+ _toggleDrawer(show) {
2117
+ const overlay = this.container.querySelector("#chat-drawer-overlay");
2118
+ if (!overlay) return;
2119
+ if (show) {
2120
+ overlay.classList.add("visible");
2121
+ // Fetch threads when drawer is opened
2122
+ this._fetchThreads();
2123
+ } else {
2124
+ overlay.classList.remove("visible");
2125
+ }
2126
+ }
2127
+ _resetChat() {
2128
+ this.messages = [];
2129
+ this.threadId = null;
2130
+ this.contentBlocks = [];
2131
+ // this.currentScreen = "welcome";
2132
+ this._initScreens();
2133
+ this._renderScreen();
2134
+ }
2135
+ _handleUserMessage(text, respond) {
2136
+ // Simple echo response - just returns what the user typed
2137
+ // The respond callback is provided by TextChatScreen
2138
+ if (respond) {
2139
+ respond("You said: \"".concat(text, "\""));
2140
+ }
2141
+ }
2142
+
2143
+ // Add message to shared messages array and update UI
2144
+ _addMessageToArray(message) {
2145
+ this.messages.push(message);
2146
+ this._notifyScreensUpdate();
2147
+ }
2148
+
2149
+ // Update message in shared messages array
2150
+ _updateMessageInArray(messageId, updates) {
2151
+ const index = this.messages.findIndex(msg => msg.id === messageId);
2152
+ if (index !== -1) {
2153
+ this.messages[index] = _objectSpread2(_objectSpread2({}, this.messages[index]), updates);
2154
+ this._notifyScreensUpdate();
2155
+ }
2156
+ }
2157
+
2158
+ // Notify all screens to update their UI
2159
+ _notifyScreensUpdate() {
2160
+ if (this.textChatScreen && this.textChatScreen.container) {
2161
+ this.textChatScreen._syncMessages(this.messages);
2162
+ }
2163
+ if (this.audioChatScreen && this.audioChatScreen.container) {
2164
+ this.audioChatScreen._syncMessages(this.messages);
2165
+ }
2166
+ }
2167
+
2168
+ // Send message function adapted from React version
2169
+ async sendMessage(inputValue) {
2170
+ let contentBlocks = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
2171
+ if (!inputValue.trim() && contentBlocks.length === 0 || this.isLoading) {
2172
+ return;
2173
+ }
2174
+ console.log("📤 Sending text message - Language:", this.selectedLanguage, "Assistant ID:", this.assistantId || "Not available", "Thread ID:", this.threadId || "Not available", "Access token:", this.accessToken ? "Present" : "Empty - backend will handle");
2175
+
2176
+ // Prepare content blocks for the message
2177
+ const messageContent = [];
2178
+
2179
+ // Add text content if present
2180
+ if (inputValue.trim()) {
2181
+ const textContent = inputValue;
2182
+ messageContent.push({
2183
+ type: "text",
2184
+ text: textContent
2185
+ });
2186
+ }
2187
+
2188
+ // Add file attachments
2189
+ contentBlocks.forEach(block => {
2190
+ messageContent.push(block);
2191
+ });
2192
+
2193
+ // Create user message for display
2194
+ const userMessage = {
2195
+ id: Date.now(),
2196
+ content: inputValue.trim() || "📎 File attachments",
2197
+ sender: "user",
2198
+ timestamp: new Date().toISOString(),
2199
+ attachments: contentBlocks.length > 0 ? contentBlocks : null
2200
+ };
2201
+ console.log("USER MESSAGE", userMessage);
2202
+ console.log("USER MESSAGE CONTENTBLOCKS", contentBlocks);
2203
+ console.log("USER MESSAGE CONTENT", messageContent);
2204
+ this._addMessageToArray(userMessage);
2205
+ this.contentBlocks = [];
2206
+
2207
+ // Clear contentBlocks in text chat screen
2208
+ if (this.textChatScreen) {
2209
+ this.textChatScreen.contentBlocks = [];
2210
+ this.textChatScreen.renderFilePreview();
2211
+ this.textChatScreen.updateSendButton();
2212
+ }
2213
+
2214
+ // Reset states for text messages
2215
+ this.playedAudioIds.clear();
2216
+ this.currentInputAudio = null;
2217
+ this.submissionInProgress = false;
2218
+ this.processingAudioId = null;
2219
+ this.isLoading = true;
2220
+ try {
2221
+ // Always attempt to send message, let backend handle auth
2222
+ await this.sendMessageWithStreaming(messageContent);
2223
+ } catch (error) {
2224
+ console.error("Error sending message:", error);
2225
+ const errorMessage = {
2226
+ id: Date.now() + 1,
2227
+ content: "Sorry, I encountered an error. Please try again.",
2228
+ sender: "assistant",
2229
+ timestamp: new Date().toISOString(),
2230
+ isError: true
2231
+ };
2232
+ this._addMessageToArray(errorMessage);
2233
+ } finally {
2234
+ this.isLoading = false;
2235
+ }
2236
+ }
2237
+
2238
+ // Send message with streaming function adapted from React version
2239
+ async sendMessageWithStreaming(messageContent) {
2240
+ console.log("USER MESSAGE STREAMING", messageContent);
2241
+ console.log("📤 Sending text message - Language:", this.selectedLanguage, "Access token:", this.accessToken ? "Present" : "Not found");
2242
+ const assistantMessage = {
2243
+ id: Date.now() + 1,
2244
+ content: "",
2245
+ sender: "assistant",
2246
+ timestamp: new Date().toISOString(),
2247
+ isStreaming: true,
2248
+ isProcessing: true,
2249
+ textSessionId: Date.now() + 1,
2250
+ hasAudioResponse: false,
2251
+ inputType: "text"
2252
+ };
2253
+ this._addMessageToArray(assistantMessage);
2254
+ let latestRunId = null;
2255
+ try {
2256
+ var _currentAssistantMess;
2257
+ // Get the latest checkpoint to resume from the latest state
2258
+ const latestCheckpoint = await this.getLatestCheckpoint(this.threadId);
2259
+ console.log("📤 Sending message to backend:", _objectSpread2({
2260
+ input: {
2261
+ messages: [{
2262
+ id: "msg-".concat(Date.now()),
2263
+ type: "human",
2264
+ content: messageContent
2265
+ }],
2266
+ user_language: this.selectedLanguage,
2267
+ access_token: "[redacted]",
2268
+ user_info: JSON.stringify(this.userInfo)
2269
+ },
2270
+ config: {
2271
+ configurable: {}
2272
+ },
2273
+ metadata: {
2274
+ supabaseAccessToken: "[redacted]",
2275
+ user_language: this.selectedLanguage,
2276
+ input_type: "text",
2277
+ audio_content: null
2278
+ },
2279
+ stream_mode: ["values", "messages-tuple", "custom"],
2280
+ stream_subgraphs: true,
2281
+ assistant_id: this.assistantId,
2282
+ on_disconnect: "cancel"
2283
+ }, latestCheckpoint && {
2284
+ checkpoint: latestCheckpoint
2285
+ }));
2286
+ const response = await fetch("".concat(this.langgraphUrl, "/threads/").concat(this.threadId, "/runs/stream"), {
2287
+ method: "POST",
2288
+ headers: this.getHeaders(),
2289
+ body: JSON.stringify(_objectSpread2({
2290
+ input: {
2291
+ messages: [{
2292
+ id: "msg-".concat(Date.now()),
2293
+ type: "human",
2294
+ content: messageContent
2295
+ }],
2296
+ user_language: this.selectedLanguage,
2297
+ access_token: this.accessToken,
2298
+ user_info: JSON.stringify(this.userInfo)
2299
+ },
2300
+ config: {
2301
+ configurable: {}
2302
+ },
2303
+ metadata: {
2304
+ supabaseAccessToken: this.supabaseToken,
2305
+ user_language: this.selectedLanguage
2306
+ },
2307
+ stream_mode: ["values", "messages-tuple", "custom"],
2308
+ stream_subgraphs: true,
2309
+ assistant_id: this.assistantId,
2310
+ on_disconnect: "cancel"
2311
+ }, latestCheckpoint && {
2312
+ checkpoint: latestCheckpoint
2313
+ }))
2314
+ });
2315
+ if (!response.ok) {
2316
+ throw new Error("HTTP error! status: ".concat(response.status));
2317
+ }
2318
+
2319
+ // Handle streaming response
2320
+ const reader = response.body.getReader();
2321
+ const decoder = new TextDecoder();
2322
+ let buffer = "";
2323
+ let currentMessage = ""; // Local to this function scope
2324
+ let bestMessageText = "";
2325
+ let bestMessagePriority = -1;
2326
+ let lastUpdateTime = 0;
2327
+ const UPDATE_THROTTLE = 150; // Throttle updates to max ~7 per second
2328
+ const requestId = Date.now(); // Unique ID for this request
2329
+ let currentEvent = null; // Track current event type
2330
+ let currentData = null; // Track current data
2331
+ let hasReceivedMeaningfulResponse = false; // Track if we've received a meaningful response
2332
+ let finalResponse = ""; // Store the final meaningful response
2333
+ let latestUiAction = null; // Track the last ui_action from the last ui_action_mapper
2334
+ let endEventFinalized = false; // Track if we finalized on 'end' event
2335
+ let endFinalContent = ""; // Content used at 'end' event
2336
+ let currentAdditionalKwargs = null; // Chart-related additional kwargs captured from stream
2337
+ let chartUrlFromViz = null; // If stream provides a chart image URL, store it here and use it for the assistant message
2338
+
2339
+ console.log("🔍 Starting to process streaming response for requestId:", requestId);
2340
+
2341
+ // Prioritize human-readable assistant text over tool/JSON outputs
2342
+ const getNodePriority = nodeKey => {
2343
+ if (!nodeKey) return 0;
2344
+ const key = String(nodeKey).toLowerCase();
2345
+ if (key.includes("mdms_assistant") || key.includes("grievance_assistant") || key.includes("mandiprice_assistant")) return 0;
2346
+ if (key.includes("assistant") && !key.includes("tools")) return 3;
2347
+ if (key.includes("response_processor")) return 2;
2348
+ if (key.includes("post_assistant")) return 2;
2349
+ if (key.includes("ui_action_mapper")) return 1;
2350
+ return 0;
2351
+ };
2352
+ while (true) {
2353
+ const {
2354
+ done,
2355
+ value
2356
+ } = await reader.read();
2357
+ if (done) break;
2358
+ buffer += decoder.decode(value, {
2359
+ stream: true
2360
+ });
2361
+ const lines = buffer.split("\n");
2362
+ buffer = lines.pop() || "";
2363
+ for (const line of lines) {
2364
+ if (line.trim() === "") continue;
2365
+ console.log("📝 Processing SSE line:", {
2366
+ line: line.substring(0, 100) + (line.length > 100 ? "..." : ""),
2367
+ requestId: requestId
2368
+ });
2369
+ if (line.startsWith("data:")) {
2370
+ try {
2371
+ const data = JSON.parse(line.slice(5).trim());
2372
+ if (data.run_id) {
2373
+ console.log("🟢 Run ID:", data.run_id);
2374
+ latestRunId = data.run_id;
2375
+ }
2376
+ } catch (err) {
2377
+ console.error("Error parsing JSON:", err, line);
2378
+ }
2379
+ }
2380
+ if (line.startsWith("event: ")) {
2381
+ currentEvent = line.slice(7).trim();
2382
+ console.log("🎯 Event type:", currentEvent);
2383
+ } else if (line.startsWith("data: ")) {
2384
+ const jsonStr = line.slice(6).trim();
2385
+ if (jsonStr === "[DONE]") {
2386
+ console.log("🏁 Stream completed");
2387
+ break;
2388
+ }
2389
+ try {
2390
+ const data = JSON.parse(jsonStr);
2391
+ currentData = data;
2392
+ if (data.run_id) {
2393
+ console.log("🟢 Capturing run_id from parsed data:", data.run_id);
2394
+ latestRunId = data.run_id;
2395
+ }
2396
+
2397
+ // Handle end event
2398
+ if (currentEvent === "end") {
2399
+ console.log("🏁 Received event: end - finalizing message with run_id:", latestRunId);
2400
+ if (data.run_id) {
2401
+ latestRunId = data.run_id;
2402
+ console.log("🟢 Updated run_id from end event:", latestRunId);
2403
+ }
2404
+ if (currentMessage && currentMessage.length > 0) {
2405
+ var _this$messages$find;
2406
+ this._updateMessageInArray(assistantMessage.id, {
2407
+ content: currentMessage,
2408
+ additional_kwargs: currentAdditionalKwargs,
2409
+ isStreaming: false,
2410
+ isProcessing: false,
2411
+ hasAudioResponse: ((_this$messages$find = this.messages.find(m => m.id === assistantMessage.id)) === null || _this$messages$find === void 0 ? void 0 : _this$messages$find.hasAudioResponse) || false,
2412
+ latestRunId: latestRunId
2413
+ });
2414
+ endEventFinalized = true;
2415
+ endFinalContent = currentMessage;
2416
+ } else if (latestRunId) {
2417
+ this._updateMessageInArray(assistantMessage.id, {
2418
+ latestRunId: latestRunId,
2419
+ isStreaming: false,
2420
+ isProcessing: false
2421
+ });
2422
+ }
2423
+ }
2424
+ console.log("🔍 Parsed SSE data:", {
2425
+ event: currentEvent,
2426
+ hasData: !!data,
2427
+ dataKeys: data ? Object.keys(data) : "no data",
2428
+ topLevelKeys: Object.keys(data),
2429
+ hasAudioContent: data !== null && data !== void 0 && data.audio_content ? "YES" : "NO",
2430
+ hasResponseProcessor: data !== null && data !== void 0 && data.response_processor ? "YES" : "NO",
2431
+ hasRunId: data !== null && data !== void 0 && data.run_id ? "YES" : "NO",
2432
+ runId: (data === null || data === void 0 ? void 0 : data.run_id) || "N/A",
2433
+ requestId: requestId
2434
+ });
2435
+
2436
+ // Handle Authentication from smart_router
2437
+ if (data.smart_router && data.smart_router.access_token && data.smart_router.farmer_profile && data.smart_router.farmer_profile_fetched === true) {
2438
+ var _data$smart_router$fa, _data$smart_router$fa2;
2439
+ console.log("🔐 Authentication data detected in smart_router:", {
2440
+ hasAccessToken: !!data.smart_router.access_token,
2441
+ mobileNumber: data.smart_router.mobile_number,
2442
+ userUuid: data.smart_router.userUuid,
2443
+ hasFarmerProfile: !!data.smart_router.farmer_profile,
2444
+ farmerId: (_data$smart_router$fa = data.smart_router.farmer_profile) === null || _data$smart_router$fa === void 0 ? void 0 : _data$smart_router$fa.individualId,
2445
+ farmerName: (_data$smart_router$fa2 = data.smart_router.farmer_profile) === null || _data$smart_router$fa2 === void 0 ? void 0 : _data$smart_router$fa2.name,
2446
+ requestId: requestId
2447
+ });
2448
+ try {
2449
+ const smartRouter = data.smart_router;
2450
+ const farmerProfile = smartRouter.farmer_profile;
2451
+ const accessToken = smartRouter.access_token;
2452
+ localStorage.setItem("DfsWeb.access-token", accessToken);
2453
+ let userInfo = {};
2454
+ if (farmerProfile.user_details) {
2455
+ userInfo = _objectSpread2({}, farmerProfile.user_details);
2456
+ userInfo.name = farmerProfile.name || userInfo.name || "";
2457
+ if (!userInfo.uuid) {
2458
+ userInfo.uuid = smartRouter.userUuid;
2459
+ }
2460
+ if (!userInfo.mobileNumber) {
2461
+ userInfo.mobileNumber = smartRouter.mobile_number || farmerProfile.mobileNumber;
2462
+ }
2463
+ } else {
2464
+ userInfo = {
2465
+ id: null,
2466
+ uuid: smartRouter.userUuid,
2467
+ userName: smartRouter.mobile_number,
2468
+ name: farmerProfile.name || "",
2469
+ mobileNumber: smartRouter.mobile_number || farmerProfile.mobileNumber,
2470
+ emailId: farmerProfile.email || null,
2471
+ locale: null,
2472
+ type: "CITIZEN",
2473
+ roles: [{
2474
+ name: "Citizen",
2475
+ code: "CITIZEN",
2476
+ tenantId: farmerProfile.tenantId || "br"
2477
+ }],
2478
+ active: true,
2479
+ tenantId: farmerProfile.tenantId || "br",
2480
+ permanentCity: null
2481
+ };
2482
+ this.setUserInfoFromDirectChatLogin(userInfo);
2483
+ }
2484
+ this.userInfo = userInfo;
2485
+ this.accessToken = accessToken;
2486
+ if (userInfo.uuid) {
2487
+ console.log("USER INFO FOR UPDATING THREAD", userInfo);
2488
+ if (this.threadId) {
2489
+ this.updateThread(this.threadId, userInfo.uuid).then(updatedThread => {
2490
+ console.log("✅ Thread updated after authentication:", updatedThread);
2491
+ }).catch(error => {
2492
+ console.error("❌ Error updating thread after authentication:", error.message);
2493
+ });
2494
+ }
2495
+ this.getUserThreads({
2496
+ userId: userInfo.id,
2497
+ userUuid: userInfo.uuid
2498
+ }).then(data => {
2499
+ console.log("✅ Thread data fetched after authentication:", data);
2500
+ this.setUserThreads(data.threads);
2501
+ this.setUserThreadsMetaData({
2502
+ threads_processed: data.threads_processed,
2503
+ total_history_items: data.total_history_items,
2504
+ total_threads: data.total_threads
2505
+ });
2506
+ }).catch(error => {
2507
+ console.error("❌ Error fetching threads after authentication:", error.message);
2508
+ });
2509
+ }
2510
+ } catch (authError) {
2511
+ console.error("❌ Error processing authentication data:", authError);
2512
+ }
2513
+ }
2514
+
2515
+ // Handle UI Actions
2516
+ let uiActionSource = null;
2517
+ let actions = null;
2518
+ if (data.response_processor && Array.isArray(data.response_processor.ui_actions)) {
2519
+ actions = data.response_processor.ui_actions;
2520
+ } else if (data.response_processor && data.response_processor.messages && Array.isArray(data.response_processor.messages) && data.response_processor.messages.length > 0 && data.response_processor.messages[0].additional_kwargs && Array.isArray(data.response_processor.messages[0].additional_kwargs.ui_actions)) {
2521
+ actions = data.response_processor.messages[0].additional_kwargs.ui_actions;
2522
+ } else if (data.ui_action_mapper && Array.isArray(data.ui_action_mapper.ui_actions)) {
2523
+ uiActionSource = data.ui_action_mapper;
2524
+ actions = uiActionSource.ui_actions;
2525
+ } else if (data.mdms_ui_action_mapper && Array.isArray(data.mdms_ui_action_mapper.ui_actions)) {
2526
+ uiActionSource = data.mdms_ui_action_mapper;
2527
+ actions = uiActionSource.ui_actions;
2528
+ }
2529
+ if (actions && actions.length > 0) {
2530
+ console.log("UI ACTIONS", actions);
2531
+ const action = actions[actions.length - 1];
2532
+ if (action.web && action.web.link) {
2533
+ var _action$web$parameter, _action$web$parameter2, _action$web$parameter3, _action$web$parameter4, _action$web$parameter5;
2534
+ let scrollToId = null;
2535
+ let navigationParams = {};
2536
+ if (action.web.parameters) {
2537
+ if (action.web.parameters.scrollTo) {
2538
+ scrollToId = action.web.parameters.scrollTo;
2539
+ }
2540
+ navigationParams = _objectSpread2({}, action.web.parameters);
2541
+ } else if (action.web.link && action.web.link.includes("scrollTo=")) {
2542
+ const urlParams = new URLSearchParams(action.web.link.split("?")[1]);
2543
+ scrollToId = urlParams.get("scrollTo");
2544
+ }
2545
+ let finalLink = action.web.link;
2546
+ let finalMessage = action.message || "";
2547
+ const hasSchemeId = ((_action$web$parameter = action.web.parameters) === null || _action$web$parameter === void 0 ? void 0 : _action$web$parameter.schemeId) && action.web.parameters.schemeId !== "";
2548
+ let hasHelpName = null;
2549
+ if ((_action$web$parameter2 = action.web.parameters) !== null && _action$web$parameter2 !== void 0 && _action$web$parameter2.button_title) {
2550
+ hasHelpName = true;
2551
+ } else if ((_action$web$parameter3 = action.web.parameters) !== null && _action$web$parameter3 !== void 0 && _action$web$parameter3.name) {
2552
+ hasHelpName = true;
2553
+ } else {
2554
+ hasHelpName = false;
2555
+ }
2556
+ if (!hasSchemeId && !hasHelpName) {
2557
+ finalLink = "/help";
2558
+ finalMessage = "Please find more information about available schemes and services";
2559
+ }
2560
+ latestUiAction = {
2561
+ content: "",
2562
+ url: finalLink && !finalLink.startsWith("http") ? window.location.origin + finalLink : finalLink,
2563
+ originalUrl: finalLink,
2564
+ uiActionType: action.ui_action,
2565
+ scrollToId: scrollToId,
2566
+ navigationParams: _objectSpread2(_objectSpread2({}, navigationParams), hasHelpName && {
2567
+ name: ((_action$web$parameter4 = action.web.parameters) === null || _action$web$parameter4 === void 0 ? void 0 : _action$web$parameter4.button_title) || ((_action$web$parameter5 = action.web.parameters) === null || _action$web$parameter5 === void 0 ? void 0 : _action$web$parameter5.name)
2568
+ })
2569
+ };
2570
+ }
2571
+ }
2572
+
2573
+ // Chart Image Extraction
2574
+ if (data.analytics_visualization_node && data.analytics_visualization_node.messages) {
2575
+ const vizMessages = data.analytics_visualization_node.messages;
2576
+ if (vizMessages.length > 0) {
2577
+ const vizMessage = vizMessages[vizMessages.length - 1];
2578
+ if (typeof vizMessage === "string" && vizMessage.includes("additional_kwargs")) {
2579
+ const kwargsStart = vizMessage.indexOf("additional_kwargs=") + 18;
2580
+ const kwargsEnd = vizMessage.indexOf("} response_metadata");
2581
+ if (kwargsStart < kwargsEnd) {
2582
+ const kwargsString = vizMessage.substring(kwargsStart, kwargsEnd + 1);
2583
+ try {
2584
+ let cleanedKwargsString = kwargsString.replace(/'/g, '"').replace(/None/g, "null").replace(/True/g, "true").replace(/False/g, "false");
2585
+ const additionalKwargs = JSON.parse(cleanedKwargsString);
2586
+ if (additionalKwargs.chart_image_url) {
2587
+ console.log("🎯 Found chart image URL:", additionalKwargs.chart_image_url);
2588
+ currentAdditionalKwargs = additionalKwargs;
2589
+ try {
2590
+ const chartUrl = String(additionalKwargs.chart_image_url).trim();
2591
+ if (chartUrl.startsWith("http")) {
2592
+ chartUrlFromViz = chartUrl;
2593
+ console.log("🔖 Stored chartUrlFromViz:", chartUrlFromViz);
2594
+ }
2595
+ } catch (e) {
2596
+ console.warn("Failed to store chart image URL:", e);
2597
+ }
2598
+ } else if (additionalKwargs.antv_chart_data) {
2599
+ console.log("🎯 Found chart data (fallback):", additionalKwargs.antv_chart_data);
2600
+ currentAdditionalKwargs = additionalKwargs;
2601
+ }
2602
+ } catch (e) {
2603
+ console.warn("Failed to parse chart image additional_kwargs:", e);
2604
+ }
2605
+ }
2606
+ }
2607
+ if (typeof vizMessage === "string") {
2608
+ const contentMatch = vizMessage.match(/content=['"]([^'"]+)['"]/);
2609
+ if (contentMatch && contentMatch[1]) {
2610
+ const extractedUrl = contentMatch[1].trim();
2611
+ if (extractedUrl.startsWith("http")) {
2612
+ chartUrlFromViz = extractedUrl;
2613
+ console.log("🔖 Stored extracted chartUrlFromViz:", chartUrlFromViz);
2614
+ }
2615
+ }
2616
+ }
2617
+ }
2618
+ }
2619
+
2620
+ // Generic Content Extraction
2621
+ let lastContent = null;
2622
+ let lastNodeKey = null;
2623
+ let lastNodeMessages = null;
2624
+ Object.keys(data).forEach(nodeKey => {
2625
+ if (nodeKey !== "audio_content" && nodeKey !== "audio_processor" && nodeKey !== "ui_action_mapper") {
2626
+ const nodeData = data[nodeKey];
2627
+ if (nodeData && Array.isArray(nodeData.messages) && nodeData.messages.length > 0) {
2628
+ lastNodeKey = nodeKey;
2629
+ lastNodeMessages = nodeData.messages;
2630
+ }
2631
+ }
2632
+ });
2633
+ if (lastNodeMessages && lastNodeMessages.length > 0) {
2634
+ let lastMessage = lastNodeMessages[lastNodeMessages.length - 1];
2635
+ let content = "";
2636
+ if (typeof lastMessage === "string") {
2637
+ if (lastMessage.startsWith("content='") && lastMessage.includes("' additional_kwargs")) {
2638
+ const start = lastMessage.indexOf("content='") + 9;
2639
+ const end = lastMessage.indexOf("' additional_kwargs");
2640
+ content = lastMessage.substring(start, end).replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\"/g, '"').replace(/\\'/g, "'");
2641
+ } else if (lastMessage.startsWith('content="') && lastMessage.includes('" additional_kwargs')) {
2642
+ const start = lastMessage.indexOf('content="') + 9;
2643
+ const end = lastMessage.indexOf('" additional_kwargs');
2644
+ content = lastMessage.substring(start, end).replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\"/g, '"').replace(/\\'/g, "'");
2645
+ } else {
2646
+ const singleQuoteMatch = lastMessage.match(/content='([^']+)'/);
2647
+ const doubleQuoteMatch = lastMessage.match(/content="([^"]+)"/);
2648
+ if (singleQuoteMatch) {
2649
+ content = singleQuoteMatch[1];
2650
+ } else if (doubleQuoteMatch) {
2651
+ content = doubleQuoteMatch[1];
2652
+ } else {
2653
+ content = lastMessage;
2654
+ }
2655
+ }
2656
+ } else if (lastMessage && typeof lastMessage === "object") {
2657
+ if (typeof lastMessage.content === "string") {
2658
+ content = lastMessage.content;
2659
+ } else if (Array.isArray(lastMessage.content) && lastMessage.content.length > 0) {
2660
+ const textParts = lastMessage.content.map(part => typeof part === "string" ? part : typeof (part === null || part === void 0 ? void 0 : part.text) === "string" ? part.text : typeof (part === null || part === void 0 ? void 0 : part.value) === "string" ? part.value : "").filter(Boolean);
2661
+ content = textParts.join(" ").trim();
2662
+ } else {
2663
+ content = "";
2664
+ }
2665
+ }
2666
+ if (typeof content === "string" && content.trim().length > 0) {
2667
+ const looksLikeJson = content.trim().startsWith("{") || content.trim().startsWith("[");
2668
+ const priority = getNodePriority(lastNodeKey);
2669
+ if (looksLikeJson && priority < 2) {
2670
+ console.log("\u23ED\uFE0F Skipping JSON-like content from ".concat(lastNodeKey));
2671
+ } else {
2672
+ const MAX_LEN = 8000;
2673
+ const safeContent = content.length > MAX_LEN ? content.slice(0, MAX_LEN) + "\n\n… (truncated)" : content;
2674
+ currentMessage = safeContent;
2675
+ if (priority > bestMessagePriority || priority === bestMessagePriority && safeContent.length > bestMessageText.length) {
2676
+ bestMessageText = safeContent;
2677
+ bestMessagePriority = priority;
2678
+ }
2679
+ hasReceivedMeaningfulResponse = true;
2680
+ console.log("\uD83D\uDFE2 Found content from ".concat(lastNodeKey, ":"), safeContent.substring(0, 100) + "...");
2681
+ }
2682
+ }
2683
+ }
2684
+
2685
+ // Process based on event type
2686
+ if (data) {
2687
+ // Set default event type for data messages without explicit event
2688
+ const eventType = currentEvent || "values";
2689
+ console.log("🎵 Processing data message:", {
2690
+ eventType: eventType,
2691
+ currentEvent: currentEvent,
2692
+ dataKeys: Object.keys(data),
2693
+ requestId: requestId
2694
+ });
2695
+
2696
+ // Handle audio content in response
2697
+ if (data.audio_content && typeof data.audio_content === "string") {
2698
+ console.log("🎵 AUDIO CONTENT FOUND in text stream:", {
2699
+ audioContentLength: data.audio_content.length,
2700
+ audioContentPreview: data.audio_content.substring(0, 20) + "..."
2701
+ });
2702
+ const audioResponseId = "text_response_".concat(requestId, "_").concat(data.audio_content.substring(0, 30));
2703
+ const alreadyPlayed = this.playedAudioIds.has(audioResponseId);
2704
+ console.log("🔊 Text stream audio content received:", {
2705
+ audioResponseId: audioResponseId,
2706
+ requestId: requestId,
2707
+ alreadyPlayed: alreadyPlayed,
2708
+ audioContentLength: data.audio_content.length
2709
+ });
2710
+ if (!alreadyPlayed) {
2711
+ this.playedAudioIds.add(audioResponseId);
2712
+ // Mark the current assistant message as having audio response
2713
+ console.log("🔍 Setting audio content for message:", {
2714
+ targetMessageId: assistantMessage.id,
2715
+ audioContentLength: data.audio_content.length,
2716
+ audioContentPreview: data.audio_content.substring(0, 20) + "..."
2717
+ });
2718
+ this._updateMessageInArray(assistantMessage.id, {
2719
+ hasAudioResponse: true,
2720
+ audioContent: data.audio_content,
2721
+ latestRunId: latestRunId
2722
+ });
2723
+ console.log("✅ Stored audio response (text stream)");
2724
+
2725
+ // Clean up old audio IDs to prevent memory issues
2726
+ if (this.playedAudioIds.size > 10) {
2727
+ const audioIds = Array.from(this.playedAudioIds);
2728
+ this.playedAudioIds.clear();
2729
+ audioIds.slice(-5).forEach(id => this.playedAudioIds.add(id));
2730
+ }
2731
+ } else {
2732
+ console.log("⏭️ Skipping audio: already played");
2733
+ }
2734
+ } else {
2735
+ console.log("❌ No audio_content found in text stream data");
2736
+ }
2737
+
2738
+ // Handle audio content in response_processor
2739
+ if (data.response_processor && data.response_processor.audio_content && typeof data.response_processor.audio_content === "string") {
2740
+ console.log("🔍 Found audio_content in response_processor:", {
2741
+ audioContentLength: data.response_processor.audio_content.length,
2742
+ audioContentPreview: data.response_processor.audio_content.substring(0, 50) + "..."
2743
+ });
2744
+ const audioResponseId = "text_response_".concat(requestId, "_").concat(data.response_processor.audio_content.substring(0, 30));
2745
+ const alreadyPlayed = this.playedAudioIds.has(audioResponseId);
2746
+ console.log("🔊 Response processor audio content received:", {
2747
+ audioResponseId: audioResponseId,
2748
+ requestId: requestId,
2749
+ alreadyPlayed: alreadyPlayed,
2750
+ audioContentLength: data.response_processor.audio_content.length
2751
+ });
2752
+ if (!alreadyPlayed) {
2753
+ this.playedAudioIds.add(audioResponseId);
2754
+
2755
+ // Mark the current assistant message as having audio response
2756
+ console.log("🔍 Setting response processor audio content for message:", {
2757
+ targetMessageId: assistantMessage.id,
2758
+ audioContentLength: data.response_processor.audio_content.length,
2759
+ audioContentPreview: data.response_processor.audio_content.substring(0, 20) + "..."
2760
+ });
2761
+ this._updateMessageInArray(assistantMessage.id, {
2762
+ latestRunId: latestRunId,
2763
+ hasAudioResponse: true,
2764
+ audioContent: data.response_processor.audio_content
2765
+ });
2766
+ console.log("✅ Found matching message, setting response processor audio content");
2767
+
2768
+ // Clean up old audio IDs to prevent memory issues
2769
+ if (this.playedAudioIds.size > 10) {
2770
+ const audioIds = Array.from(this.playedAudioIds);
2771
+ this.playedAudioIds.clear();
2772
+ audioIds.slice(-5).forEach(id => this.playedAudioIds.add(id));
2773
+ }
2774
+ } else {
2775
+ console.log("⏭️ Skipping response processor audio: already played");
2776
+ }
2777
+ }
2778
+ }
2779
+ } catch (parseError) {
2780
+ console.log("⚠️ Failed to parse streaming data:", parseError);
2781
+ }
2782
+ }
2783
+ }
2784
+ }
2785
+
2786
+ // Ensure streaming is marked as complete for this specific assistant message
2787
+ console.log("🏁 Finalizing assistant message:", assistantMessage.id);
2788
+
2789
+ // Add a small delay to ensure audio content is properly set
2790
+ await new Promise(resolve => setTimeout(resolve, 100));
2791
+
2792
+ // Get the current state to ensure we have the latest audio content
2793
+ const currentAssistantMessage = this.messages.find(msg => msg.id === assistantMessage.id);
2794
+ console.log("🔍 Current assistant message before finalization:", {
2795
+ messageId: assistantMessage.id,
2796
+ hasAudioContent: !!(currentAssistantMessage !== null && currentAssistantMessage !== void 0 && currentAssistantMessage.audioContent),
2797
+ hasAudioResponse: currentAssistantMessage === null || currentAssistantMessage === void 0 ? void 0 : currentAssistantMessage.hasAudioResponse,
2798
+ audioContentLength: (currentAssistantMessage === null || currentAssistantMessage === void 0 || (_currentAssistantMess = currentAssistantMessage.audioContent) === null || _currentAssistantMess === void 0 ? void 0 : _currentAssistantMess.length) || 0
2799
+ });
2800
+
2801
+ // If message was already finalized at 'end' with content, do not override
2802
+ if (endEventFinalized && endFinalContent && endFinalContent.length > 10) {
2803
+ console.log("✅ Message already finalized on end event; skipping overwrite");
2804
+ } else {
2805
+ // Check if we have a meaningful response to display
2806
+ const finalText = bestMessageText && bestMessageText.length > 5 ? bestMessageText : currentMessage;
2807
+ // If a chart URL was captured from the viz node, prefer it and show only the image
2808
+ const contentToSet = chartUrlFromViz && String(chartUrlFromViz).startsWith("http") ? chartUrlFromViz : finalText;
2809
+ if (contentToSet && contentToSet.length > 10) {
2810
+ var _currentMsg$audioCont;
2811
+ console.log("🔍 Finalizing message with content:", {
2812
+ messageId: assistantMessage.id,
2813
+ currentMessageLength: String(contentToSet).length,
2814
+ currentMessagePreview: String(contentToSet).substring(0, 50) + "..."
2815
+ });
2816
+ const currentMsg = this.messages.find(m => m.id === assistantMessage.id);
2817
+ console.log("🔍 Finalizing message:", {
2818
+ latestRunId,
2819
+ messageId: assistantMessage.id,
2820
+ existingAudioContent: !!(currentMsg !== null && currentMsg !== void 0 && currentMsg.audioContent),
2821
+ existingAudioResponse: currentMsg === null || currentMsg === void 0 ? void 0 : currentMsg.hasAudioResponse,
2822
+ audioContentLength: (currentMsg === null || currentMsg === void 0 || (_currentMsg$audioCont = currentMsg.audioContent) === null || _currentMsg$audioCont === void 0 ? void 0 : _currentMsg$audioCont.length) || 0
2823
+ });
2824
+ this._updateMessageInArray(assistantMessage.id, {
2825
+ latestRunId: latestRunId,
2826
+ content: contentToSet,
2827
+ additional_kwargs: currentAdditionalKwargs,
2828
+ isStreaming: false,
2829
+ isProcessing: false,
2830
+ hasAudioResponse: (currentMsg === null || currentMsg === void 0 ? void 0 : currentMsg.hasAudioResponse) || false,
2831
+ // Preserve existing audio response state
2832
+ audioContent: (currentMsg === null || currentMsg === void 0 ? void 0 : currentMsg.audioContent) || null // Preserve existing audio content
2833
+ });
2834
+ } else {
2835
+ // If no meaningful response was found, show a clear, user-friendly fallback
2836
+ console.log("🔍 Finalizing message with friendly fallback");
2837
+ const currentMsg = this.messages.find(m => m.id === assistantMessage.id);
2838
+ this._updateMessageInArray(assistantMessage.id, {
2839
+ content: "Processing your request...",
2840
+ latestRunId: latestRunId,
2841
+ additional_kwargs: currentAdditionalKwargs,
2842
+ isStreaming: false,
2843
+ isProcessing: false,
2844
+ hasAudioResponse: (currentMsg === null || currentMsg === void 0 ? void 0 : currentMsg.hasAudioResponse) || false,
2845
+ audioContent: (currentMsg === null || currentMsg === void 0 ? void 0 : currentMsg.audioContent) || null
2846
+ });
2847
+ }
2848
+ }
2849
+
2850
+ // Handle UI actions if present
2851
+ if (latestUiAction) {
2852
+ console.log("🔗 Adding UI action message:", latestUiAction);
2853
+ const uiActionMessage = {
2854
+ id: Date.now() + 2,
2855
+ content: "",
2856
+ sender: "assistant",
2857
+ timestamp: new Date().toISOString(),
2858
+ isUiAction: true,
2859
+ url: latestUiAction.url,
2860
+ uiActionType: latestUiAction.uiActionType,
2861
+ scrollToId: latestUiAction.scrollToId,
2862
+ navigationParams: latestUiAction.navigationParams,
2863
+ originalUrl: latestUiAction.originalUrl,
2864
+ isScrollAction: latestUiAction.uiActionType === "scroll",
2865
+ isNavigateStateAction: latestUiAction.uiActionType === "navigate-state"
2866
+ };
2867
+ const lastMessage = this.messages[this.messages.length - 1];
2868
+ if (lastMessage) {
2869
+ const updatedLastMessage = _objectSpread2(_objectSpread2({}, lastMessage), {}, {
2870
+ uiActionMessage,
2871
+ latestRunId
2872
+ });
2873
+ console.log("UPDATED LAST MESSAGE", updatedLastMessage);
2874
+ this._updateMessageInArray(lastMessage.id, {
2875
+ uiActionMessage,
2876
+ latestRunId
2877
+ });
2878
+ }
2879
+ }
2880
+ } catch (error) {
2881
+ console.error("❌ Streaming error:", error);
2882
+ const currentMsg = this.messages.find(m => m.id === assistantMessage.id);
2883
+ this._updateMessageInArray(assistantMessage.id, {
2884
+ isStreaming: false,
2885
+ isProcessing: false,
2886
+ isError: true,
2887
+ content: "Error occurred while processing request",
2888
+ latestRunId: latestRunId || (currentMsg === null || currentMsg === void 0 ? void 0 : currentMsg.latestRunId) // Preserve or set latestRunId
2889
+ });
2890
+ }
2891
+ }
2892
+
2893
+ // Fetch assistant ID from API
2894
+ async fetchAssistantId() {
2895
+ try {
2896
+ console.log("🔍 Fetching assistant ID from API...");
2897
+
2898
+ // Use provided authToken or fallback to accessToken
2899
+ const authToken = this.authToken || this.accessToken;
2900
+ const supabaseToken = this.supabaseToken || authToken;
2901
+ const response = await fetch("".concat(this.langgraphUrl, "/assistants/search"), {
2902
+ method: "POST",
2903
+ headers: {
2904
+ "Content-Type": "application/json",
2905
+ Authorization: "Bearer ".concat(authToken),
2906
+ "x-supabase-access-token": supabaseToken,
2907
+ Origin: window.location.origin || "*"
2908
+ },
2909
+ body: JSON.stringify({
2910
+ limit: 100,
2911
+ offset: 0
2912
+ })
2913
+ });
2914
+ if (!response.ok) {
2915
+ console.log("\u274C API call failed with status: ".concat(response.status, " - backend may have rejected due to auth"));
2916
+ return null; // Return null instead of throwing to let backend handle auth
2917
+ }
2918
+ const assistants = await response.json();
2919
+ console.log("📋 Available assistants:", assistants);
2920
+
2921
+ // Log assistant details for debugging
2922
+ assistants.forEach((assistant, index) => {
2923
+ console.log("Assistant ".concat(index, ":"), {
2924
+ id: assistant.assistant_id,
2925
+ name: assistant.name,
2926
+ hasName: !!assistant.name,
2927
+ type: typeof assistant.name
2928
+ });
2929
+ });
2930
+
2931
+ // Find the assistant with name "Default Assistant"
2932
+ const defaultAssistant = assistants.filter(assistant => assistant.name && assistant.name.includes("Default"))[0];
2933
+ if (defaultAssistant) {
2934
+ console.log("✅ Found Default Assistant:", defaultAssistant.assistant_id);
2935
+ this.assistantId = defaultAssistant.assistant_id;
2936
+ return defaultAssistant.assistant_id;
2937
+ } else {
2938
+ console.log("⚠️ Default Assistant not found in the list");
2939
+
2940
+ // Find first assistant with a valid name
2941
+ const firstValidAssistant = assistants.find(assistant => assistant.name && assistant.name.trim() !== "");
2942
+ if (firstValidAssistant) {
2943
+ console.log("⚠️ Using first valid assistant as fallback:", firstValidAssistant.assistant_id, "Name:", firstValidAssistant.name);
2944
+ this.assistantId = firstValidAssistant.assistant_id;
2945
+ return firstValidAssistant.assistant_id;
2946
+ } else {
2947
+ console.log("❌ No assistants with valid names available");
2948
+ return null;
2949
+ }
2950
+ }
2951
+ } catch (error) {
2952
+ console.error("❌ Error fetching assistant ID:", error);
2953
+ console.log("🔍 Error details:", {
2954
+ name: error.name,
2955
+ message: error.message,
2956
+ isNetworkError: error.name === "TypeError"
2957
+ });
2958
+ return null; // Return null instead of throwing to let backend handle auth
2959
+ }
2960
+ }
2961
+
2962
+ // Load conversation history for a thread
2963
+ async loadHistory(threadId) {
2964
+ try {
2965
+ const response = await fetch("".concat(this.langgraphUrl, "/threads/").concat(threadId, "/history"), {
2966
+ method: "POST",
2967
+ headers: this.getHeaders(),
2968
+ body: JSON.stringify({
2969
+ limit: 10
2970
+ })
2971
+ });
2972
+ if (response.ok) {
2973
+ var _latestState$values;
2974
+ const history = await response.json();
2975
+ console.log("📜 Loaded history:", history);
2976
+
2977
+ // Get only the LATEST state (first item) which contains the complete conversation
2978
+ const latestState = history[0];
2979
+ if (!(latestState !== null && latestState !== void 0 && (_latestState$values = latestState.values) !== null && _latestState$values !== void 0 && _latestState$values.messages)) {
2980
+ console.log("No messages found in history");
2981
+ return;
2982
+ }
2983
+
2984
+ // Convert messages from the latest state only
2985
+ const historyMessages = [];
2986
+ latestState.values.messages.forEach((msg, msgIndex) => {
2987
+ if (msg.type === "human") {
2988
+ // Handle both text and audio content
2989
+ let content = "";
2990
+ let isAudio = false;
2991
+ if (Array.isArray(msg.content)) {
2992
+ const textContent = msg.content.find(c => c.type === "text");
2993
+ const audioContent = msg.content.find(c => c.type === "audio");
2994
+ if (audioContent) {
2995
+ content = "🎤 Audio message";
2996
+ isAudio = true;
2997
+ } else if (textContent) {
2998
+ content = textContent.text;
2999
+ }
3000
+ } else {
3001
+ content = msg.content;
3002
+ }
3003
+ historyMessages.push({
3004
+ id: "history-".concat(msgIndex, "-human"),
3005
+ content: content,
3006
+ sender: "user",
3007
+ timestamp: new Date().toISOString(),
3008
+ isAudio: isAudio
3009
+ });
3010
+ } else if (msg.type === "ai") {
3011
+ historyMessages.push({
3012
+ id: "history-".concat(msgIndex, "-ai"),
3013
+ content: msg.content,
3014
+ sender: "assistant",
3015
+ timestamp: new Date().toISOString()
3016
+ });
3017
+ }
3018
+ });
3019
+ if (historyMessages && historyMessages.length > 0) {
3020
+ console.log("\uD83D\uDCDD Loading ".concat(historyMessages.length, " messages from history"));
3021
+ // Add history messages to the messages array
3022
+ historyMessages.forEach(msg => {
3023
+ this._addMessageToArray(msg);
3024
+ });
3025
+ }
3026
+ }
3027
+ } catch (error) {
3028
+ console.error("❌ Failed to load history:", error);
3029
+ }
3030
+ }
3031
+
3032
+ // Initialize chat - fetch assistant ID and create thread
3033
+ async initializeChat() {
3034
+ try {
3035
+ console.log("🔄 Initializing chat...");
3036
+
3037
+ // Always attempt API calls, let backend handle authentication
3038
+ const currentToken = localStorage.getItem("DfsWeb.access-token");
3039
+ console.log("🔑 Access token status:", currentToken ? "Present" : "Empty - backend will handle");
3040
+
3041
+ // Always attempt to fetch assistant ID
3042
+ console.log("📞 Making API call to /assistants/search...");
3043
+ const fetchedAssistantId = await this.fetchAssistantId();
3044
+ if (!fetchedAssistantId) {
3045
+ console.log("❌ Failed to fetch assistant ID - backend may have rejected due to auth");
3046
+ this.isConnected = false;
3047
+ this.initialized = true;
3048
+ return;
3049
+ }
3050
+ console.log("✅ Assistant ID fetched successfully:", fetchedAssistantId);
3051
+ const storedUser = localStorage.getItem("DfsWeb.user-info");
3052
+ const user = storedUser ? JSON.parse(storedUser) : null;
3053
+
3054
+ // Always attempt to create thread
3055
+ console.log("📞 Making API call to /threads...");
3056
+ const response = await fetch("".concat(this.langgraphUrl, "/threads"), {
3057
+ method: "POST",
3058
+ headers: this.getHeaders(),
3059
+ body: JSON.stringify({
3060
+ metadata: {
3061
+ user_id: (user === null || user === void 0 ? void 0 : user.id) || null,
3062
+ user_uuid: (user === null || user === void 0 ? void 0 : user.uuid) || null
3063
+ }
3064
+ })
3065
+ });
3066
+ if (response.ok) {
3067
+ const thread = await response.json();
3068
+ this.threadId = thread.thread_id;
3069
+ this.isConnected = true;
3070
+ console.log("✅ Thread created:", thread.thread_id, "\n", thread);
3071
+
3072
+ // Load conversation history
3073
+ await this.loadHistory(thread.thread_id);
3074
+ } else {
3075
+ console.log("Thread created NOT");
3076
+ const errorText = await response.text();
3077
+ console.error("❌ Failed to create thread:", response.status, errorText);
3078
+ this.isConnected = false;
3079
+ }
3080
+ this.initialized = true;
3081
+ } catch (error) {
3082
+ console.error("❌ Thread creation error:", error);
3083
+ this.isConnected = false;
3084
+ this.initialized = true;
3085
+ }
3086
+ }
3087
+ }
156
3088
 
157
- return chatWidget_default;
3089
+ return ChatWidget;
158
3090
 
159
3091
  }));