PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.4.2
MxChat – AI Chatbot & Content Generation for WordPress v2.4.2
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/chat-script.js +1202 -1950 3.2.42.4.2 View file →
@@ -1,228 +1,12 @@
1 1 jQuery(document).ready(function($) {
2 -
3 - // Nonce refresh is deferred until first user interaction (ensureSession)
4 - // to avoid admin-ajax calls on passive page loads.
5 - var nonceRefreshed = false;
6 - function refreshNonceIfNeeded(callback) {
7 - if (nonceRefreshed || typeof mxchatChat === 'undefined' || !mxchatChat.ajax_url) {
8 - if (callback) callback();
9 - return;
10 - }
11 - nonceRefreshed = true;
12 - $.post(mxchatChat.ajax_url, { action: 'mxchat_refresh_nonce' }, function(res) {
13 - if (res && res.success && res.data && res.data.nonce) {
14 - mxchatChat.nonce = res.data.nonce;
15 - }
16 - if (callback) callback();
17 - });
18 - }
19 -
2 +
20 3 // ====================================
21 - // MULTI-INSTANCE MANAGEMENT SYSTEM
22 - // ====================================
23 -
24 - // Instance registry - tracks all chatbot instances on the page
25 - const MxChatInstances = {
26 - instances: {},
27 -
28 - // Initialize an instance for a bot
29 - init: function(botId) {
30 - if (!this.instances[botId]) {
31 - // When persistence is OFF, track when this session started
32 - // so the AI only sees messages from this page load
33 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
34 -
35 - this.instances[botId] = {
36 - botId: botId,
37 - sessionId: null,
38 - lastSeenMessageId: '',
39 - notificationCheckInterval: null,
40 - pollingInterval: null,
41 - processedMessageIds: new Set(),
42 - activePdfFile: null,
43 - activeWordFile: null,
44 - chatHistoryLoaded: false,
45 - isStreaming: false,
46 - // Fresh context timestamp - only used when persistence is OFF
47 - sessionStartTimestamp: chatPersistenceEnabled ? 0 : Date.now()
48 - };
49 - }
50 - return this.instances[botId];
51 - },
52 -
53 - // Get instance by botId
54 - get: function(botId) {
55 - return this.instances[botId] || this.init(botId);
56 - },
57 -
58 - // Get all active bot IDs
59 - getAllBotIds: function() {
60 - return Object.keys(this.instances);
61 - },
62 -
63 - // Session management per bot
64 - // Returns existing session ID from cookie or localStorage (with in-memory fallback),
65 - // or null if none exists. Does NOT create a new session — use ensureSession() for that.
66 - getChatSession: function(botId) {
67 - var cookieName = 'mxchat_session_id_' + botId;
68 - var storageKey = 'mxchat_session_id_' + botId;
69 - var sessionId = getCookie(cookieName);
70 -
71 - // Fallback to localStorage if cookie is missing (e.g. cleared by browser/consent)
72 - if (!sessionId) {
73 - try { sessionId = localStorage.getItem(storageKey); } catch (e) {}
74 - }
75 -
76 - // Fallback to in-memory instance when cookie AND localStorage are both blocked
77 - // (Safari ITP, strict tracking prevention, cross-origin iframes with partitioned
78 - // storage). Without this, ensureSession() can generate and store an ID that
79 - // getChatSession() then can't read back, causing null session_ids on send.
80 - if (!sessionId && this.instances[botId] && this.instances[botId].sessionId) {
81 - sessionId = this.instances[botId].sessionId;
82 - }
83 -
84 - // Guard against stored sentinel values that indicate earlier broken writes.
85 - if (sessionId === 'null' || sessionId === 'undefined') {
86 - sessionId = null;
87 - }
88 -
89 - // Re-sync cookie from localStorage if cookie was lost
90 - if (sessionId && !getCookie(cookieName)) {
91 - document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
92 - }
93 -
94 - return sessionId || null;
95 - },
96 -
97 - // Lazy session initializer — called on first user interaction
98 - ensureSession: function(botId) {
99 - botId = botId || 'default';
100 - var instance = this.instances[botId] || this.init(botId);
101 -
102 - if (instance.sessionId) {
103 - return instance.sessionId;
104 - }
105 -
106 - // Check for existing session from cookie or localStorage
107 - var existingSession = this.getChatSession(botId);
108 -
109 - if (existingSession) {
110 - instance.sessionId = existingSession;
111 - } else {
112 - // Brand new session
113 - var newId = generateSessionId();
114 - this.setChatSession(botId, newId);
115 - instance.sessionId = newId;
116 - }
117 -
118 - // Now that we have a session, do the deferred work
119 - refreshNonceIfNeeded();
120 - trackOriginatingPage();
121 -
122 - // Note: loadChatHistory is handled by showChatContainerForBot with loader UI,
123 - // so we do NOT call it here to avoid a race condition.
124 -
125 - return instance.sessionId;
126 - },
127 -
128 - setChatSession: function(botId, sessionId) {
129 - var cookieName = 'mxchat_session_id_' + botId;
130 - var storageKey = 'mxchat_session_id_' + botId;
131 - document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
132 - try { localStorage.setItem(storageKey, sessionId); } catch (e) {}
133 - if (this.instances[botId]) {
134 - this.instances[botId].sessionId = sessionId;
135 - }
136 - },
137 -
138 - resetChatSession: function(botId) {
139 - // Clear old session from localStorage before setting new one
140 - try { localStorage.removeItem('mxchat_session_id_' + botId); } catch (e) {}
141 - var newSessionId = generateSessionId();
142 - this.setChatSession(botId, newSessionId);
143 - var $chatBox = getElement(botId, 'chat-box');
144 - if ($chatBox.length) {
145 - $chatBox.find('.user-message, .bot-message:not(:first), .agent-message').remove();
146 - }
147 - if (this.instances[botId]) {
148 - this.instances[botId].chatHistoryLoaded = false;
149 - this.instances[botId].processedMessageIds = new Set();
150 - }
151 - },
152 -
153 - // Silent reset — new session ID without clearing the chat UI
154 - // Used when IP changes mid-conversation so the user doesn't see messages vanish
155 - silentResetSession: function(botId) {
156 - try { localStorage.removeItem('mxchat_session_id_' + botId); } catch (e) {}
157 - var newSessionId = generateSessionId();
158 - this.setChatSession(botId, newSessionId);
159 - if (this.instances[botId]) {
160 - this.instances[botId].sessionId = newSessionId;
161 - }
162 - return newSessionId;
163 - }
164 - };
165 -
166 - // ====================================
167 - // ELEMENT SELECTOR HELPERS
168 - // ====================================
169 -
170 - // Check if a specific bot has an AI theme assigned (skip inline colors)
171 - function shouldSkipInlineColors(botId) {
172 - // If global AI theme is active, skip inline colors for all bots
173 - if (mxchatChat.skip_inline_colors) {
174 - return true;
175 - }
176 - // Check if this specific bot has a theme assignment
177 - var botAssignments = mxchatChat.bot_theme_assignments || {};
178 - return botAssignments.hasOwnProperty(botId);
179 - }
180 -
181 - // Get element by ID with bot suffix - returns jQuery object
182 - function getElement(botId, elementName) {
183 - return $('#' + elementName + '-' + botId);
184 - }
185 -
186 - // Get element by ID with bot suffix - returns DOM element
187 - function getElementDOM(botId, elementName) {
188 - return document.getElementById(elementName + '-' + botId);
189 - }
190 -
191 - // Get bot ID from any element within a chatbot instance
192 - function getBotIdFromElement(element) {
193 - var $wrapper = $(element).closest('.mxchat-chatbot-wrapper');
194 - if ($wrapper.length) {
195 - return $wrapper.data('bot-id') || 'default';
196 - }
197 - // Fallback: try to find from floating container
198 - var $floating = $(element).closest('.floating-chatbot');
199 - if ($floating.length) {
200 - var id = $floating.attr('id') || '';
201 - var match = id.match(/floating-chatbot-(.+)/);
202 - if (match) return match[1];
203 - }
204 - // Fallback: check if element itself has an ID with bot suffix (e.g., floating-chatbot-button-{bot_id})
205 - var elementId = $(element).attr('id') || '';
206 - if (elementId) {
207 - // Match patterns like: floating-chatbot-button-{bot_id}, pre-chat-message-{bot_id}
208 - var idMatch = elementId.match(/^(?:floating-chatbot-button|pre-chat-message|chat-notification-badge)-(.+)$/);
209 - if (idMatch) return idMatch[1];
210 - }
211 - return 'default';
212 - }
213 -
214 - // Get wrapper element for a bot
215 - function getWrapper(botId) {
216 - return getElement(botId, 'mxchat-chatbot-wrapper');
217 - }
218 -
219 - // ====================================
220 4 // GLOBAL VARIABLES & CONFIGURATION
221 5 // ====================================
222 6 const toolbarIconColor = mxchatChat.toolbar_icon_color || '#212121';
223 -
224 - // Initialize color settings (these are global as they come from PHP)
7 +
8 + // Initialize color settings
225 9 var userMessageBgColor = mxchatChat.user_message_bg_color;
226 10 var userMessageFontColor = mxchatChat.user_message_font_color;
227 11 var botMessageBgColor = mxchatChat.bot_message_bg_color;
228 12 var botMessageFontColor = mxchatChat.bot_message_font_color;
@@ -227,64 +11,53 @@
227 11 var botMessageBgColor = mxchatChat.bot_message_bg_color;
228 12 var botMessageFontColor = mxchatChat.bot_message_font_color;
229 13 var liveAgentMessageBgColor = mxchatChat.live_agent_message_bg_color;
230 14 var liveAgentMessageFontColor = mxchatChat.live_agent_message_font_color;
15 +
16 + var linkTarget = mxchatChat.link_target_toggle === 'on' ? '_blank' : '_self';
17 + let lastSeenMessageId = '';
18 + let notificationCheckInterval;
19 + let notificationBadge;
20 + var sessionId = getChatSession();
21 + let pollingInterval;
22 + let processedMessageIds = new Set();
23 + let activePdfFile = null;
24 + let activeWordFile = null;
231 25
232 - var linkTarget = mxchatChat.link_target_toggle === 'on' ? '_blank' : '_self';
233 26
234 27 // ====================================
235 - // SESSION MANAGEMENT (Legacy compatibility)
28 + // SESSION MANAGEMENT
236 29 // ====================================
237 -
30 +
31 + function getChatSession() {
32 + var sessionId = getCookie('mxchat_session_id');
33 + //console.log("Session ID retrieved from cookie: ", sessionId);
34 +
35 + if (!sessionId) {
36 + sessionId = generateSessionId();
37 + //console.log("Generated new session ID: ", sessionId);
38 + setChatSession(sessionId);
39 + }
40 +
41 + //console.log("Final session ID: ", sessionId);
42 + return sessionId;
43 + }
44 +
45 + function setChatSession(sessionId) {
46 + // Set the cookie with a 24-hour expiration (86400 seconds)
47 + document.cookie = "mxchat_session_id=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
48 + }
49 +
238 50 function getCookie(name) {
239 51 let value = "; " + document.cookie;
240 52 let parts = value.split("; " + name + "=");
241 53 if (parts.length == 2) return parts.pop().split(";").shift();
242 54 }
243 -
55 +
244 56 function generateSessionId() {
245 57 return 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
246 58 }
247 59
248 - // Legacy function - now delegates to instance manager
249 - function getChatSession(botId) {
250 - botId = botId || 'default';
251 - return MxChatInstances.getChatSession(botId);
252 - }
253 -
254 - function setChatSession(sessionId, botId) {
255 - botId = botId || 'default';
256 - MxChatInstances.setChatSession(botId, sessionId);
257 - }
258 -
259 - function resetChatSession(botId) {
260 - botId = botId || 'default';
261 - MxChatInstances.resetChatSession(botId);
262 - }
263 -
264 - // ====================================
265 - // INITIALIZE ALL CHATBOT INSTANCES
266 - // ====================================
267 -
268 - function initializeAllInstances() {
269 - // Find all chatbot wrappers on the page
270 - $('.mxchat-chatbot-wrapper').each(function() {
271 - var botId = $(this).data('bot-id') || 'default';
272 - MxChatInstances.init(botId);
273 - initializeBotInstance(botId);
274 - });
275 - }
276 -
277 - function initializeBotInstance(botId) {
278 - var instance = MxChatInstances.get(botId);
279 -
280 - // Initialize quick questions state for this bot
281 - checkQuickQuestionsState(botId);
282 -
283 - // Note: Event handlers use event delegation with class selectors,
284 - // so they work automatically for all instances without per-bot setup
285 - }
286 -
287 60 // ====================================
288 61 // CONTEXTUAL AWARENESS FUNCTIONALITY
289 62 // ====================================
290 63
@@ -360,9 +133,9 @@
360 133 const elements = clone.querySelectorAll(selector);
361 134 elements.forEach(el => el.remove());
362 135 });
363 136
364 - // Extract MxChat context data attributes before getting text content
137 + // NEW: Extract MxChat context data attributes before getting text content
365 138 const contextData = [];
366 139 clone.querySelectorAll('[data-mxchat-context]').forEach(el => {
367 140 const contextValue = el.dataset.mxchatContext;
368 141 if (contextValue && contextValue.trim()) {
@@ -396,9 +169,9 @@
396 169 content: pageContent
397 170 };
398 171 }
399 172
400 -// Track originating page when chat starts
173 +// NEW: Track originating page when chat starts
401 174 function trackOriginatingPage() {
402 175 const sessionId = getChatSession();
403 176 const pageUrl = window.location.href;
404 177 const pageTitle = document.title || 'Untitled Page';
@@ -429,123 +202,70 @@
429 202
430 203 // ====================================
431 204 // CORE CHAT FUNCTIONALITY
432 205 // ====================================
433 -
434 -// Helper functions to disable/enable chat input while waiting for response
435 -function disableChatInput(botId) {
436 - botId = botId || 'default';
437 - var chatInput = getElementDOM(botId, 'chat-input');
438 - var sendButton = getElementDOM(botId, 'send-button');
439 - if (chatInput) {
440 - chatInput.disabled = true;
441 - chatInput.style.opacity = '0.6';
442 - }
443 - if (sendButton) {
444 - sendButton.disabled = true;
445 - sendButton.style.opacity = '0.5';
446 - sendButton.style.pointerEvents = 'none';
447 - }
448 -}
449 -
450 -function enableChatInput(botId) {
451 - botId = botId || 'default';
452 - var chatInput = getElementDOM(botId, 'chat-input');
453 - var sendButton = getElementDOM(botId, 'send-button');
454 - if (chatInput) {
455 - chatInput.disabled = false;
456 - chatInput.style.opacity = '1';
457 - chatInput.focus();
458 - }
459 - if (sendButton) {
460 - sendButton.disabled = false;
461 - sendButton.style.opacity = '1';
462 - sendButton.style.pointerEvents = 'auto';
463 - }
464 -}
465 -
466 206 // Update your existing sendMessage function
467 -function sendMessage(botId) {
468 - botId = botId || 'default';
469 - MxChatInstances.ensureSession(botId);
470 - var $chatInput = getElement(botId, 'chat-input');
471 - var message = $chatInput.val();
472 -
207 +function sendMessage() {
208 + var message = $('#chat-input').val();
209 +
473 210 // ADD PROMPT HOOK HERE
474 - if (typeof customMxChatFilter === 'function') {
475 - message = customMxChatFilter(message, "prompt");
211 + if (typeof customMxChatFilter === 'function') {
212 + message = customMxChatFilter(message, "prompt");
476 213 }
477 -
214 +
478 215 if (message) {
479 - // Don't disable input in live agent mode - let users chat freely
480 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
481 - var isAgentMode = modeIndicator && modeIndicator.textContent === 'Live Agent';
482 - if (!isAgentMode) {
483 - disableChatInput(botId);
484 - }
216 + appendMessage("user", message);
217 + $('#chat-input').val('');
218 + $('#chat-input').css('height', 'auto');
485 219
486 - appendMessage("user", message, '', [], false, botId);
487 - $chatInput.val('');
488 - $chatInput.css('height', 'auto');
489 -
490 - if (hasQuickQuestions(botId)) {
491 - collapseQuickQuestions(botId);
220 + if (hasQuickQuestions()) {
221 + collapseQuickQuestions();
492 222 }
493 - appendThinkingMessage(botId);
494 - scrollToBottom(botId);
223 + appendThinkingMessage();
224 + scrollToBottom();
495 225
496 - const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
226 + const currentModel = mxchatChat.model || 'gpt-4o';
497 227
498 228 // Check if streaming is enabled AND supported for this model
499 229 if (shouldUseStreaming(currentModel)) {
500 230 callMxChatStream(message, function(response) {
501 - getElement(botId, 'chat-box').find('.bot-message.temporary-message').removeClass('temporary-message');
502 - }, botId);
231 + $('.bot-message.temporary-message').removeClass('temporary-message');
232 + });
503 233 } else {
504 234 callMxChat(message, function(response) {
505 - replaceLastMessage("bot", response, '', [], botId);
506 - }, botId);
235 + replaceLastMessage("bot", response);
236 + });
507 237 }
508 238 }
509 239 }
510 240
511 241 // Update your existing sendMessageToChatbot function
512 -function sendMessageToChatbot(message, botId) {
513 - botId = botId || 'default';
514 - MxChatInstances.ensureSession(botId);
515 -
242 +function sendMessageToChatbot(message) {
516 243 // ADD PROMPT HOOK HERE
517 244 if (typeof customMxChatFilter === 'function') {
518 245 message = customMxChatFilter(message, "prompt");
519 246 }
247 +
248 + var sessionId = getChatSession();
520 249
521 - // Don't disable input in live agent mode - let users chat freely
522 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
523 - var isAgentMode = modeIndicator && modeIndicator.textContent === 'Live Agent';
524 - if (!isAgentMode) {
525 - disableChatInput(botId);
250 + if (hasQuickQuestions()) {
251 + collapseQuickQuestions();
526 252 }
253 + appendThinkingMessage();
254 + scrollToBottom();
527 255
528 - var sessionId = getChatSession(botId);
256 + const currentModel = mxchatChat.model || 'gpt-4o';
529 257
530 - if (hasQuickQuestions(botId)) {
531 - collapseQuickQuestions(botId);
532 - }
533 - appendThinkingMessage(botId);
534 - scrollToBottom(botId);
535 -
536 - const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
537 -
538 258 // Check if streaming is enabled AND supported for this model
539 259 if (shouldUseStreaming(currentModel)) {
540 260 callMxChatStream(message, function(response) {
541 - getElement(botId, 'chat-box').find('.bot-message.temporary-message').removeClass('temporary-message');
542 - }, botId);
261 + $('.bot-message.temporary-message').removeClass('temporary-message');
262 + });
543 263 } else {
544 264 callMxChat(message, function(response) {
545 - getElement(botId, 'chat-box').find('.temporary-message').remove();
546 - replaceLastMessage("bot", response, '', [], botId);
547 - }, botId);
265 + $('.temporary-message').remove();
266 + replaceLastMessage("bot", response);
267 + });
548 268 }
549 269 }
550 270
551 271 // Updated shouldUseStreaming function with debugging
@@ -588,53 +308,30 @@
588 308 updateChatModeIndicator('agent');
589 309 }
590 310 }
591 311
592 -// Function to get bot ID from any element or wrapper
593 -// If element is provided, finds the bot ID from its wrapper
594 -// If no element, returns 'default' (for backward compatibility)
595 -function getMxChatBotId(element) {
596 - if (element) {
597 - return getBotIdFromElement(element);
598 - }
599 - // Fallback: find first chatbot wrapper on page
600 - const chatbotWrapper = document.querySelector('.mxchat-chatbot-wrapper');
312 +//Function to get bot ID from the chatbot wrapper
313 +function getMxChatBotId() {
314 + const chatbotWrapper = document.getElementById('mxchat-chatbot-wrapper');
601 315 return chatbotWrapper ? chatbotWrapper.getAttribute('data-bot-id') || 'default' : 'default';
602 316 }
603 317
604 -function callMxChat(message, callback, botId) {
605 - botId = botId || getMxChatBotId();
606 -
607 - // Store the message in case we need to retry after session reset
608 - getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', message);
609 -
318 +function callMxChat(message, callback) {
610 319 // Get page context if contextual awareness is enabled
611 320 const pageContext = getPageContext();
612 -
613 - // Get instance for session start timestamp (used when persistence is OFF)
614 - var instance = MxChatInstances.get(botId);
615 -
616 - // Guarantee a non-null session_id before the AJAX leaves. ensureSession() is idempotent
617 - // and returns the guaranteed-present session id from the in-memory instance even when
618 - // cookie/localStorage writes are silently blocked by the browser.
619 - var sessionId = MxChatInstances.ensureSession(botId);
620 - if (!sessionId || sessionId === 'null' || sessionId === 'undefined') {
621 - // Last-resort generation to ensure we never POST a null marker.
622 - sessionId = generateSessionId();
623 - MxChatInstances.setChatSession(botId, sessionId);
624 - }
625 -
321 +
322 + // NEW: Get bot ID
323 + const botId = getMxChatBotId();
324 +
626 325 // Prepare AJAX data
627 326 const ajaxData = {
628 327 action: 'mxchat_handle_chat_request',
629 328 message: message,
630 - session_id: sessionId,
329 + session_id: getChatSession(),
631 330 nonce: mxchatChat.nonce,
632 331 current_page_url: window.location.href,
633 332 current_page_title: document.title,
634 - bot_id: botId,
635 - // Pass session start timestamp so AI context matches what user sees
636 - session_start_timestamp: instance.sessionStartTimestamp || 0
333 + bot_id: botId // NEW: Include bot ID
637 334 };
638 335
639 336 // Add page context if available
640 337 if (pageContext) {
@@ -657,78 +354,32 @@
657 354 type: 'POST',
658 355 dataType: 'json',
659 356 data: ajaxData,
660 357 success: function(response) {
661 - // IMMEDIATE CHAT MODE UPDATE - This should be FIRST
358 +
359 + // IMMEDIATE CHAT MODE UPDATE - This should be FIRST
360 + if (response.chat_mode) {
361 + //console.log("Updating chat mode to:", response.chat_mode);
362 + updateChatModeIndicator(response.chat_mode);
363 + }
364 +
365 + // Also check in data property if response is wrapped
366 + if (response.data && response.data.chat_mode) {
367 + //console.log("Updating chat mode from data to:", response.data.chat_mode);
368 + updateChatModeIndicator(response.data.chat_mode);
369 + }
370 + // Log the full response for debugging
371 + //console.log("API Response:", response);
372 +
373 + // IMMEDIATE CHAT MODE UPDATE - Handle this first, before any other processing
662 374 if (response.chat_mode) {
663 - updateChatModeIndicator(response.chat_mode, botId);
375 + //console.log("Updating chat mode immediately to:", response.chat_mode);
376 + updateChatModeIndicator(response.chat_mode);
664 377 }
665 378
666 - // Also check in data property if response is wrapped
667 - if (response.data && response.data.chat_mode) {
668 - updateChatModeIndicator(response.data.chat_mode, botId);
669 - }
670 -
671 - // SECURITY FIX: Check for errors FIRST before checking for success
672 - // This ensures API errors (quota exceeded, invalid key, rate limit) are properly displayed
673 - if (response.success === false || (response.data && response.data.error_message)) {
674 - let errorMessage = "";
675 - let errorCode = "";
676 -
677 - // Check various possible error locations in the response
678 - if (response.data && response.data.error_message) {
679 - errorMessage = response.data.error_message;
680 - errorCode = response.data.error_code || "";
681 - } else if (response.error_message) {
682 - errorMessage = response.error_message;
683 - errorCode = response.error_code || "";
684 - } else if (response.message) {
685 - errorMessage = response.message;
686 - } else if (typeof response.data === 'string') {
687 - errorMessage = response.data;
688 - } else {
689 - // Fallback for any other unexpected response format
690 - errorMessage = "An error occurred. Please try again or contact support.";
691 - }
692 -
693 - // Handle session reset action (IP changed, session expired, etc.)
694 - // Silent reset — keep chat UI intact, just get a new session and retry
695 - if (response.data && response.data.action === 'reset_session') {
696 - MxChatInstances.silentResetSession(botId);
697 - // Re-send the original message with the new session (user message is already displayed)
698 - var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
699 - if (originalMessage) {
700 - getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
701 - var currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
702 - if (shouldUseStreaming(currentModel)) {
703 - callMxChatStream(originalMessage, function(response) {
704 - getElement(botId, 'chat-box').find('.bot-message.temporary-message').removeClass('temporary-message');
705 - }, botId);
706 - } else {
707 - callMxChat(originalMessage, function(response) {
708 - replaceLastMessage("bot", response, '', [], botId);
709 - }, botId);
710 - }
711 - }
712 - return;
713 - }
714 -
715 - // Format user-friendly error message
716 - let displayMessage = errorMessage;
717 -
718 - // Customize message for admin users
719 - if (mxchatChat.is_admin) {
720 - // For admin users, show more technical details including error code
721 - displayMessage = errorMessage + (errorCode ? " (Error code: " + errorCode + ")" : "");
722 - }
723 -
724 - replaceLastMessage("bot", displayMessage, '', [], botId);
725 - return; // Exit early for errors
726 - }
727 -
728 - // NOW check if this is a successful response by looking for text, html, or message fields
379 + // First check if this is a successful response by looking for text, html, or message fields
729 380 // This preserves compatibility with your server response format
730 - if (response.text !== undefined || response.html !== undefined || response.message !== undefined ||
381 + if (response.text !== undefined || response.html !== undefined || response.message !== undefined ||
731 382 (response.success === true && response.data && response.data.status === 'waiting_for_agent')) {
732 383
733 384 // Handle successful response - this is your original success handling code
734 385
@@ -738,17 +389,16 @@
738 389 let responseMessage = response.message || '';
739 390
740 391 // Add PDF filename handling
741 392 if (response.data && response.data.filename) {
742 - showActivePdf(response.data.filename, botId);
743 - var instance = MxChatInstances.get(botId);
744 - instance.activePdfFile = response.data.filename;
393 + showActivePdf(response.data.filename);
394 + activePdfFile = response.data.filename;
745 395 }
746 396
747 397 // Add redirect check here
748 398 if (response.redirect_url) {
749 399 if (responseText) {
750 - replaceLastMessage("bot", responseText, '', [], botId);
400 + replaceLastMessage("bot", responseText);
751 401 }
752 402 setTimeout(() => {
753 403 window.location.href = response.redirect_url;
754 404 }, 1500);
@@ -756,17 +406,15 @@
756 406 }
757 407
758 408 // Check for live agent response
759 409 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
760 - removeThinkingDots(botId);
761 - updateChatModeIndicator('agent', botId);
762 - enableChatInput(botId);
410 + updateChatModeIndicator('agent');
763 411 return;
764 412 }
765 413
766 414 // Handle the message and show notification if chat is hidden
767 415 if (responseText || responseHtml || responseMessage) {
768 -
416 +
769 417 // ADD RESPONSE HOOKS HERE - BEFORE DISPLAYING
770 418 if (responseText && typeof customMxChatFilter === 'function') {
771 419 responseText = customMxChatFilter(responseText, "response");
772 420 }
@@ -772,53 +420,89 @@
772 420 }
773 421 if (responseMessage && typeof customMxChatFilter === 'function') {
774 422 responseMessage = customMxChatFilter(responseMessage, "response");
775 423 }
776 -
424 +
777 425 // Update the messages as before
778 426 if (responseText && responseHtml) {
779 - replaceLastMessage("bot", responseText, responseHtml, [], botId);
427 + replaceLastMessage("bot", responseText, responseHtml);
780 428 } else if (responseText) {
781 - replaceLastMessage("bot", responseText, '', [], botId);
429 + replaceLastMessage("bot", responseText);
782 430 } else if (responseHtml) {
783 - replaceLastMessage("bot", "", responseHtml, [], botId);
431 + replaceLastMessage("bot", "", responseHtml);
784 432 } else if (responseMessage) {
785 - replaceLastMessage("bot", responseMessage, '', [], botId);
433 + replaceLastMessage("bot", responseMessage);
786 434 }
787 435
788 436 // Check if chat is hidden and show notification
789 - var $floatingChatbot = getElement(botId, 'floating-chatbot');
790 - if ($floatingChatbot.hasClass('hidden')) {
791 - var $badge = getElement(botId, 'chat-notification-badge');
792 - if ($badge.length) {
793 - $badge.show();
437 + if ($('#floating-chatbot').hasClass('hidden')) {
438 + const badge = $('#chat-notification-badge');
439 + if (badge.length) {
440 + badge.show();
794 441 }
795 442 }
796 443 } else {
797 - var emptyMsg = "I received an empty response. Please try again or contact support if this persists.";
798 - if (response.vectorstore_error) {
799 - emptyMsg = "I received an empty response. Debug info: " + response.vectorstore_error;
800 - }
801 - replaceLastMessage("bot", emptyMsg, '', [], botId);
444 + ////console.error("Unexpected response format:", response);
445 + replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists.");
802 446 }
803 447
804 448 if (response.message_id) {
805 - var instance = MxChatInstances.get(botId);
806 - instance.lastSeenMessageId = response.message_id;
449 + lastSeenMessageId = response.message_id;
807 450 }
808 451
809 452 return;
810 453 }
811 454
812 - // Fallback for truly unexpected response formats
813 - replaceLastMessage("bot", "Unexpected response format. Please try again or contact support.", '', [], botId);
455 + // If we got here, it's likely an error response
456 + // Now we can check for error conditions with our robust error handling
457 +
458 + let errorMessage = "";
459 + let errorCode = "";
460 +
461 + // Check various possible error locations in the response
462 + if (response.data && response.data.error_message) {
463 + errorMessage = response.data.error_message;
464 + errorCode = response.data.error_code || "";
465 + } else if (response.error_message) {
466 + errorMessage = response.error_message;
467 + errorCode = response.error_code || "";
468 + } else if (response.message) {
469 + errorMessage = response.message;
470 + } else if (typeof response.data === 'string') {
471 + errorMessage = response.data;
472 + } else if (!response.success) {
473 + // Explicit check for success: false without other error info
474 + errorMessage = "An error occurred. Please try again or contact support.";
475 + } else {
476 + // Fallback for any other unexpected response format
477 + errorMessage = "Unexpected response received. Please try again or contact support.";
478 + }
479 +
480 + // Log the error with code for debugging
481 + //console.log("Response data:", response.data);
482 + ////console.error("API Error:", errorMessage, "Code:", errorCode);
483 +
484 + // Format user-friendly error message
485 + let displayMessage = errorMessage;
486 +
487 + // Customize message for admin users
488 + if (mxchatChat.is_admin) {
489 + // For admin users, show more technical details including error code
490 + displayMessage = errorMessage + (errorCode ? " (Error code: " + errorCode + ")" : "");
491 + }
492 +
493 + replaceLastMessage("bot", displayMessage);
814 494 },
815 495 error: function(xhr, status, error) {
496 + //console.error("AJAX Error:", status, error);
497 + //console.log("Response Text:", xhr.responseText);
498 +
816 499 let errorMessage = "An unexpected error occurred.";
817 500
818 501 // Try to parse the response if it's JSON
819 502 try {
820 503 const responseJson = JSON.parse(xhr.responseText);
504 + //console.log("Parsed error response:", responseJson);
821 505
822 506 if (responseJson.data && responseJson.data.error_message) {
823 507 errorMessage = responseJson.data.error_message;
824 508 } else if (responseJson.message) {
@@ -838,52 +522,38 @@
838 522 errorMessage = "Server error: The server encountered an issue. Please try again later.";
839 523 }
840 524 }
841 525
842 - replaceLastMessage("bot", errorMessage, '', [], botId);
526 + replaceLastMessage("bot", errorMessage);
843 527 }
844 528 });
845 529 }
846 530
847 -function callMxChatStream(message, callback, botId) {
848 - botId = botId || getMxChatBotId();
849 -
850 - // Store the message in case we need to retry after session reset
851 - getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', message);
852 -
853 - const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
531 +function callMxChatStream(message, callback) {
532 + //console.log("Using streaming for message:", message);
533 +
534 + const currentModel = mxchatChat.model || 'gpt-4o';
854 535 if (!isStreamingSupported(currentModel)) {
855 - callMxChat(message, callback, botId);
536 + //console.log("Streaming not supported, falling back to regular call");
537 + callMxChat(message, callback);
856 538 return;
857 539 }
858 540
859 541 // Get page context if contextual awareness is enabled
860 542 const pageContext = getPageContext();
543 +
544 + // NEW: Get bot ID
545 + const botId = getMxChatBotId();
861 546
862 - // Get instance for session start timestamp (used when persistence is OFF)
863 - var instance = MxChatInstances.get(botId);
864 -
865 - // Guarantee a non-null session_id before the fetch. FormData.append() stringifies any
866 - // non-string value via String(), so passing `null` would POST the literal string "null"
867 - // and land in the transcripts table as a ghost session. ensureSession() always returns
868 - // a real string even when cookies/localStorage are blocked.
869 - var streamSessionId = MxChatInstances.ensureSession(botId);
870 - if (!streamSessionId || streamSessionId === 'null' || streamSessionId === 'undefined') {
871 - streamSessionId = generateSessionId();
872 - MxChatInstances.setChatSession(botId, streamSessionId);
873 - }
874 -
875 547 const formData = new FormData();
876 548 formData.append('action', 'mxchat_stream_chat');
877 549 formData.append('message', message);
878 - formData.append('session_id', streamSessionId);
550 + formData.append('session_id', getChatSession());
879 551 formData.append('nonce', mxchatChat.nonce);
880 552 formData.append('current_page_url', window.location.href);
881 553 formData.append('current_page_title', document.title);
882 - formData.append('bot_id', botId);
883 - // Pass session start timestamp so AI context matches what user sees
884 - formData.append('session_start_timestamp', instance.sessionStartTimestamp || 0);
885 -
554 + formData.append('bot_id', botId); // NEW: Include bot ID
555 +
886 556 // Add page context if available
887 557 if (pageContext) {
888 558 formData.append('page_context', JSON.stringify(pageContext));
889 559 }
@@ -908,14 +578,17 @@
908 578 body: formData,
909 579 credentials: 'same-origin'
910 580 })
911 581 .then(response => {
582 + //console.log("Streaming response received:", response);
583 +
912 584 // Store the response for potential fallback handling
913 585 const responseClone = response.clone();
914 -
586 +
915 587 if (!response.ok) {
916 588 // Try to get error details from response
917 589 return responseClone.json().then(errorData => {
590 + //console.log("Server returned error response:", errorData);
918 591 throw { isServerError: true, data: errorData };
919 592 }).catch(() => {
920 593 throw new Error('Network response was not ok');
921 594 });
@@ -923,21 +596,26 @@
923 596
924 597 // Check if response is JSON instead of streaming
925 598 const contentType = response.headers.get('content-type');
926 599 if (contentType && contentType.includes('application/json')) {
600 + //console.log("Received JSON response instead of stream");
927 601 return responseClone.json().then(data => {
602 + //console.log("Processing JSON response:", data);
603 +
928 604 // IMMEDIATE CHAT MODE UPDATE for JSON response
929 605 if (data.chat_mode) {
930 - updateChatModeIndicator(data.chat_mode, botId);
606 + //console.log("JSON Response: Updating chat mode to:", data.chat_mode);
607 + updateChatModeIndicator(data.chat_mode);
931 608 }
932 -
609 +
933 610 // Check for testing panel
934 611 if (window.mxchatTestPanelInstance && data.testing_data) {
612 + //console.log('Testing data found in streaming JSON response:', data.testing_data);
935 613 window.mxchatTestPanelInstance.handleTestingData(data.testing_data);
936 614 }
937 -
615 +
938 616 // Handle the JSON response directly
939 - handleNonStreamResponse(data, callback, botId);
617 + handleNonStreamResponse(data, callback);
940 618 return Promise.resolve(); // Prevent further processing
941 619 });
942 620 }
943 621
@@ -948,44 +626,37 @@
948 626
949 627 function processStream() {
950 628 reader.read().then(({ done, value }) => {
951 629 if (done) {
630 + //console.log("Streaming completed, final content:", accumulatedContent);
631 +
952 632 // If streaming completed but no content was received, try to get response as fallback
953 633 if (!streamingStarted || !accumulatedContent) {
634 + //console.log("Stream completed with no content, checking for fallback data");
635 +
954 636 // Try to read the response as JSON
955 637 responseClone.text().then(text => {
956 638 try {
957 639 const data = JSON.parse(text);
958 - if (data.text || data.message || data.html) {
959 - handleNonStreamResponse(data, callback, botId);
640 + if (data.text || data.message) {
641 + handleNonStreamResponse(data, callback);
960 642 } else {
961 643 // No valid data, fall back to regular call
962 - getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
963 - callMxChat(message, callback, botId);
644 + $('.bot-message.temporary-message').remove();
645 + callMxChat(message, callback);
964 646 }
965 647 } catch (e) {
966 648 // Could not parse, fall back to regular call
967 - getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
968 - callMxChat(message, callback, botId);
649 + $('.bot-message.temporary-message').remove();
650 + callMxChat(message, callback);
969 651 }
970 652 }).catch(() => {
971 - getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
972 - callMxChat(message, callback, botId);
653 + $('.bot-message.temporary-message').remove();
654 + callMxChat(message, callback);
973 655 });
974 656 return;
975 657 }
976 -
977 - // Re-enable chat input when stream ends with content
978 - enableChatInput(botId);
979 -
980 - // Scroll the user's last message to the top now that the
981 - // bot's full reply has rendered (gives max reading room).
982 - var $chatBoxDone = getElement(botId, 'chat-box');
983 - var $lastUserMsgDone = $chatBoxDone.find('.user-message').last();
984 - if ($lastUserMsgDone.length) {
985 - scrollElementToTop($lastUserMsgDone, botId);
986 - }
987 -
658 +
988 659 if (callback) {
989 660 callback(accumulatedContent);
990 661 }
991 662 return;
@@ -999,25 +670,17 @@
999 670 if (line.startsWith('data: ')) {
1000 671 const data = line.substring(6);
1001 672
1002 673 if (data === '[DONE]') {
674 + //console.log("Received [DONE] signal");
675 +
1003 676 if (!accumulatedContent) {
1004 - getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
1005 - callMxChat(message, callback, botId);
677 + //console.log("No content received before [DONE]");
678 + $('.bot-message.temporary-message').remove();
679 + callMxChat(message, callback);
1006 680 return;
1007 681 }
1008 -
1009 - // Re-enable chat input after streaming completes
1010 - enableChatInput(botId);
1011 -
1012 - // Scroll the user's last message to the top now
1013 - // that the bot's full reply has rendered.
1014 - var $chatBoxStreamDone = getElement(botId, 'chat-box');
1015 - var $lastUserMsgStreamDone = $chatBoxStreamDone.find('.user-message').last();
1016 - if ($lastUserMsgStreamDone.length) {
1017 - scrollElementToTop($lastUserMsgStreamDone, botId);
1018 - }
1019 -
682 +
1020 683 if (callback) {
1021 684 callback(accumulatedContent);
1022 685 }
1023 686 return;
@@ -1024,16 +687,18 @@
1024 687 }
1025 688
1026 689 try {
1027 690 const json = JSON.parse(data);
1028 -
691 +
1029 692 // IMMEDIATE CHAT MODE UPDATE FOR STREAMING
1030 693 if (json.chat_mode) {
1031 - updateChatModeIndicator(json.chat_mode, botId);
694 + //console.log("Stream: Updating chat mode immediately to:", json.chat_mode);
695 + updateChatModeIndicator(json.chat_mode);
1032 696 }
1033 -
697 +
1034 698 // Handle testing data
1035 699 if (json.testing_data && !testingDataReceived) {
700 + //console.log('Testing data received in stream:', json.testing_data);
1036 701 if (window.mxchatTestPanelInstance) {
1037 702 window.mxchatTestPanelInstance.handleTestingData(json.testing_data);
1038 703 testingDataReceived = true;
1039 704 }
@@ -1041,35 +706,31 @@
1041 706 // Handle content streaming
1042 707 else if (json.content) {
1043 708 streamingStarted = true;
1044 709 accumulatedContent += json.content;
1045 - updateStreamingMessage(accumulatedContent, botId);
1046 - }
710 + updateStreamingMessage(accumulatedContent);
711 + }
1047 712 // Handle complete response in stream (fallback response)
1048 - else if (json.text || json.message || json.html) {
1049 - handleNonStreamResponse(json, callback, botId);
713 + else if (json.text || json.message) {
714 + //console.log("Received complete response in stream:", json);
715 + handleNonStreamResponse(json, callback);
1050 716 return;
1051 717 }
1052 718 // Handle errors
1053 719 else if (json.error) {
1054 -
1055 - // Get error message from various possible fields
1056 - let errorMessage = json.error_message || json.message || json.text ||
1057 - (typeof json.error === 'string' ? json.error : 'An error occurred. Please try again.');
1058 -
1059 - // Re-enable chat input on error
1060 - enableChatInput(botId);
1061 -
1062 - // Display the error directly in the chat
1063 - replaceLastMessage("bot", errorMessage, '', [], botId);
1064 -
1065 - if (callback) {
1066 - callback(errorMessage);
720 + console.error("Streaming error:", json.error);
721 +
722 + // Check if we have a fallback response
723 + if (json.fallback || json.text || json.message) {
724 + handleNonStreamResponse(json, callback);
725 + } else {
726 + $('.bot-message.temporary-message').remove();
727 + callMxChat(message, callback);
1067 728 }
1068 729 return;
1069 730 }
1070 731 } catch (e) {
1071 - // SSE data parsing error - silently continue
732 + console.error('Error parsing SSE data:', e, 'Data:', data);
1072 733 }
1073 734 }
1074 735 }
1075 736
@@ -1074,10 +735,11 @@
1074 735 }
1075 736
1076 737 processStream();
1077 738 }).catch(streamError => {
1078 - getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
1079 - callMxChat(message, callback, botId);
739 + console.error('Error reading stream:', streamError);
740 + $('.bot-message.temporary-message').remove();
741 + callMxChat(message, callback);
1080 742 });
1081 743 }
1082 744
1083 745 processStream();
@@ -1082,110 +744,52 @@
1082 744
1083 745 processStream();
1084 746 })
1085 747 .catch(error => {
748 + //console.log('Streaming failed:', error);
749 +
1086 750 // Check if we have server error data with chat mode
1087 751 if (error && error.isServerError && error.data) {
752 + //console.log('Using server error response data');
753 +
1088 754 // Check for chat mode in error data
1089 755 if (error.data.chat_mode) {
1090 - updateChatModeIndicator(error.data.chat_mode, botId);
756 + //console.log("Error Response: Updating chat mode to:", error.data.chat_mode);
757 + updateChatModeIndicator(error.data.chat_mode);
1091 758 }
1092 -
1093 - handleNonStreamResponse(error.data, callback, botId);
759 +
760 + handleNonStreamResponse(error.data, callback);
1094 761 } else {
1095 762 // Only fall back to regular call if we don't have any response data
1096 - getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
1097 - callMxChat(message, callback, botId);
763 + //console.log('No response data available, falling back to regular call');
764 + $('.bot-message.temporary-message').remove();
765 + callMxChat(message, callback);
1098 766 }
1099 767 });
1100 768 }
1101 769
1102 -// Helper function to handle non-streaming responses
1103 -function handleNonStreamResponse(data, callback, botId) {
1104 - botId = botId || 'default';
1105 -
770 +// Helper function to handle non-streaming responses (updated to include immediate chat mode handling)
771 +function handleNonStreamResponse(data, callback) {
772 + //console.log("Handling non-stream response:", data);
773 +
1106 774 // IMMEDIATE CHAT MODE UPDATE FOR NON-STREAMING RESPONSES
1107 775 if (data.chat_mode) {
1108 - updateChatModeIndicator(data.chat_mode, botId);
776 + //console.log("Non-Stream: Updating chat mode immediately to:", data.chat_mode);
777 + updateChatModeIndicator(data.chat_mode);
1109 778 }
1110 -
779 +
1111 780 // Also check in data property if response is wrapped
1112 781 if (data.data && data.data.chat_mode) {
1113 - updateChatModeIndicator(data.data.chat_mode, botId);
782 + //console.log("Non-Stream: Updating chat mode from data to:", data.data.chat_mode);
783 + updateChatModeIndicator(data.data.chat_mode);
1114 784 }
1115 -
1116 - // NOTE: Don't remove temporary message here - let replaceLastMessage handle it
1117 - // This prevents a visual gap between thinking dots disappearing and content appearing
1118 -
1119 - // SECURITY FIX: Check for errors FIRST
1120 - if (data.success === false || (data.data && data.data.error_message)) {
1121 - let errorMessage = "";
1122 - let errorCode = "";
1123 -
1124 - // Check various possible error locations
1125 - if (data.data && data.data.error_message) {
1126 - errorMessage = data.data.error_message;
1127 - errorCode = data.data.error_code || "";
1128 - } else if (data.error_message) {
1129 - errorMessage = data.error_message;
1130 - errorCode = data.error_code || "";
1131 - } else if (data.message) {
1132 - errorMessage = data.message;
1133 - } else if (typeof data.data === 'string') {
1134 - errorMessage = data.data;
1135 - } else {
1136 - errorMessage = "An error occurred. Please try again or contact support.";
1137 - }
1138 -
1139 - // Handle session reset action (IP changed, session expired, etc.)
1140 - // Silent reset — keep chat UI intact, just get a new session and retry
1141 - if (data.data && data.data.action === 'reset_session') {
1142 - MxChatInstances.silentResetSession(botId);
1143 - // Re-send the original message with the new session (user message is already displayed)
1144 - var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
1145 - if (originalMessage) {
1146 - getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
1147 - var currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
1148 - if (shouldUseStreaming(currentModel)) {
1149 - callMxChatStream(originalMessage, callback, botId);
1150 - } else {
1151 - callMxChat(originalMessage, callback, botId);
1152 - }
1153 - }
1154 - return;
1155 - }
1156 -
1157 - // Format user-friendly error message
1158 - let displayMessage = errorMessage;
1159 - if (mxchatChat.is_admin) {
1160 - displayMessage = errorMessage + (errorCode ? " (Error code: " + errorCode + ")" : "");
1161 - }
1162 -
1163 - replaceLastMessage("bot", displayMessage, '', [], botId);
1164 -
1165 - if (callback) {
1166 - callback('');
1167 - }
1168 - return; // Exit early for errors
1169 - }
1170 -
1171 - // Check for live agent response
1172 - if (data.success && data.data && data.data.status === 'waiting_for_agent') {
1173 - removeThinkingDots(botId);
1174 - // Also remove any leftover bot-message that lost its temporary-message class
1175 - var $chatBox = getElement(botId, 'chat-box');
1176 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1177 - updateChatModeIndicator('agent', botId);
1178 - enableChatInput(botId);
1179 - if (callback) {
1180 - callback('');
1181 - }
1182 - return;
1183 - }
1184 -
785 +
786 + // Remove temporary message
787 + $('.bot-message.temporary-message').remove();
788 +
1185 789 // Handle different response formats
1186 790 if (data.text || data.html || data.message) {
1187 -
791 +
1188 792 // Apply response hooks
1189 793 if (data.text && typeof customMxChatFilter === 'function') {
1190 794 data.text = customMxChatFilter(data.text, "response");
1191 795 }
@@ -1191,59 +795,56 @@
1191 795 }
1192 796 if (data.message && typeof customMxChatFilter === 'function') {
1193 797 data.message = customMxChatFilter(data.message, "response");
1194 798 }
1195 -
799 +
1196 800 // Display the response
1197 801 if (data.text && data.html) {
1198 - replaceLastMessage("bot", data.text, data.html, [], botId);
802 + replaceLastMessage("bot", data.text, data.html);
1199 803 } else if (data.text) {
1200 - replaceLastMessage("bot", data.text, '', [], botId);
804 + replaceLastMessage("bot", data.text);
1201 805 } else if (data.html) {
1202 - replaceLastMessage("bot", "", data.html, [], botId);
806 + replaceLastMessage("bot", "", data.html);
1203 807 } else if (data.message) {
1204 - replaceLastMessage("bot", data.message, '', [], botId);
808 + replaceLastMessage("bot", data.message);
1205 809 }
1206 810 }
1207 -
811 +
1208 812 // Handle other response properties
1209 813 if (data.data && data.data.filename) {
1210 - showActivePdf(data.data.filename, botId);
1211 - var instance = MxChatInstances.get(botId);
1212 - instance.activePdfFile = data.data.filename;
814 + showActivePdf(data.data.filename);
815 + activePdfFile = data.data.filename;
1213 816 }
1214 -
817 +
1215 818 if (data.redirect_url) {
1216 819 setTimeout(() => {
1217 820 window.location.href = data.redirect_url;
1218 821 }, 1500);
1219 822 }
1220 -
1221 - // Ensure chat input is re-enabled (safety net for edge cases)
1222 - enableChatInput(botId);
1223 -
823 +
1224 824 if (callback) {
1225 825 callback(data.text || data.message || '');
1226 826 }
1227 827 }
1228 -
1229 828 // Enhanced updateChatModeIndicator function for immediate DOM updates
1230 -function updateChatModeIndicator(mode, botId) {
1231 - botId = botId || 'default';
1232 - const indicator = getElementDOM(botId, 'chat-mode-indicator');
829 +function updateChatModeIndicator(mode) {
830 + //console.log("updateChatModeIndicator called with mode:", mode);
831 + const indicator = document.getElementById('chat-mode-indicator');
1233 832 if (indicator) {
1234 833 const oldText = indicator.textContent;
1235 -
834 +
1236 835 if (mode === 'agent') {
1237 836 indicator.textContent = 'Live Agent';
1238 - startPolling(botId);
837 + startPolling();
838 + //console.log("Set indicator to Live Agent");
1239 839 } else {
1240 840 // Everything else is AI mode
1241 841 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
1242 842 indicator.textContent = customAiText;
1243 - stopPolling(botId);
843 + stopPolling();
844 + //console.log("Set indicator to AI Agent:", customAiText);
1244 845 }
1245 -
846 +
1246 847 // Force immediate DOM update and reflow
1247 848 if (oldText !== indicator.textContent) {
1248 849 // Force a reflow to ensure the change is visible immediately
1249 850 indicator.style.display = 'none';
@@ -1248,88 +849,132 @@
1248 849 // Force a reflow to ensure the change is visible immediately
1249 850 indicator.style.display = 'none';
1250 851 indicator.offsetHeight; // Trigger reflow
1251 852 indicator.style.display = '';
1252 -
853 +
1253 854 // Double-check after a brief moment to ensure the change stuck
1254 855 setTimeout(() => {
856 + //console.log("Final indicator text:", indicator.textContent);
1255 857 if (mode === 'agent' && indicator.textContent !== 'Live Agent') {
858 + console.warn("Mode indicator update failed, forcing update");
1256 859 indicator.textContent = 'Live Agent';
1257 860 } else if (mode !== 'agent' && indicator.textContent === 'Live Agent') {
861 + console.warn("Mode indicator stuck on Live Agent, forcing AI update");
1258 862 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
1259 863 indicator.textContent = customAiText;
1260 864 }
1261 865 }, 50);
1262 866 }
867 + } else {
868 + console.error("Chat mode indicator element not found!");
1263 869 }
1264 870 }
1265 871
872 +// Helper function to handle non-streaming responses (updated to include chat mode handling)
873 +function handleNonStreamResponse(data, callback) {
874 + //console.log("Handling non-stream response:", data);
875 +
876 + // Remove temporary message
877 + $('.bot-message.temporary-message').remove();
878 +
879 + // Handle different response formats
880 + if (data.text || data.html || data.message) {
881 +
882 + // Apply response hooks
883 + if (data.text && typeof customMxChatFilter === 'function') {
884 + data.text = customMxChatFilter(data.text, "response");
885 + }
886 + if (data.message && typeof customMxChatFilter === 'function') {
887 + data.message = customMxChatFilter(data.message, "response");
888 + }
889 +
890 + // NEW: Handle chat mode updates for streaming responses too
891 + handleChatModeUpdates(data, data.text || data.message);
892 +
893 + // Display the response
894 + if (data.text && data.html) {
895 + replaceLastMessage("bot", data.text, data.html);
896 + } else if (data.text) {
897 + replaceLastMessage("bot", data.text);
898 + } else if (data.html) {
899 + replaceLastMessage("bot", "", data.html);
900 + } else if (data.message) {
901 + replaceLastMessage("bot", data.message);
902 + }
903 + }
904 +
905 + // Handle other response properties
906 + if (data.data && data.data.filename) {
907 + showActivePdf(data.data.filename);
908 + activePdfFile = data.data.filename;
909 + }
910 +
911 + if (data.redirect_url) {
912 + setTimeout(() => {
913 + window.location.href = data.redirect_url;
914 + }, 1500);
915 + }
916 +
917 + if (callback) {
918 + callback(data.text || data.message || '');
919 + }
920 +}
921 +
1266 922 // Function to update message during streaming
1267 -function updateStreamingMessage(content, botId) {
1268 - botId = botId || 'default';
1269 -
923 +function updateStreamingMessage(content) {
1270 924 // ADD RESPONSE HOOK FOR REAL-TIME STREAMING
1271 925 if (typeof customMxChatFilter === 'function') {
1272 926 content = customMxChatFilter(content, "response");
1273 927 }
928 +
929 + const formattedContent = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(content))));
1274 930
1275 - const formattedContent = linkify(content);
931 + // Find the temporary message
932 + const tempMessage = $('.bot-message.temporary-message').last();
1276 933
1277 - // Find the temporary message in this bot's chat box
1278 - var $chatBox = getElement(botId, 'chat-box');
1279 - const tempMessage = $chatBox.find('.bot-message.temporary-message').last();
1280 -
1281 934 if (tempMessage.length) {
1282 935 // Update existing message
1283 936 tempMessage.html(formattedContent);
1284 937 } else {
1285 938 // Create new temporary message if it doesn't exist
1286 - appendMessage("bot", content, '', [], true, botId);
939 + appendMessage("bot", content, '', [], true);
1287 940 }
1288 941 }
1289 942
943 +//Function to check if streaming is supported for the current model
1290 944 function isStreamingSupported(model) {
1291 945 if (!model) return false;
1292 946
947 + //console.log("Checking streaming support for model:", model); // Debug log
948 +
949 + // Get the model prefix
1293 950 const modelPrefix = model.split('-')[0].toLowerCase();
951 +
952 + //console.log("Model prefix:", modelPrefix); // Debug log
1294 953
1295 - // Support streaming for OpenAI, Claude, Grok, DeepSeek, and OpenRouter models
1296 - const isSupported = modelPrefix === 'gpt' ||
1297 - modelPrefix === 'o1' ||
1298 - modelPrefix === 'claude' ||
1299 - modelPrefix === 'grok' ||
1300 - modelPrefix === 'deepseek' ||
1301 - model === 'openrouter'; // Add this line - check full model name for OpenRouter
954 + // Support streaming for OpenAI, Claude, Grok, and DeepSeek models
955 + const isSupported = modelPrefix === 'gpt' || modelPrefix === 'o1' || modelPrefix === 'claude' || modelPrefix === 'grok' || modelPrefix === 'deepseek';
1302 956
957 + //console.log("Streaming supported:", isSupported); // Debug log
958 +
1303 959 return isSupported;
1304 960 }
1305 961
1306 -// Update the event handlers to use the correct function names (using event delegation)
1307 -// Use class-based selectors for multi-instance support
1308 -$(document).on('click', '.send-button', function() {
1309 - var botId = getBotIdFromElement(this);
1310 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1311 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1312 - disableChatInput(botId);
1313 - }
1314 - sendMessage(botId);
962 +// Update the event handlers to use the correct function names
963 +$('#send-button').off('click').on('click', function() {
964 + sendMessage(); // Use the updated sendMessage function
1315 965 });
1316 966
1317 -// Override enter key handler (using event delegation)
1318 -$(document).on('keypress', '.chat-input', function(e) {
967 +// Override enter key handler
968 +$('#chat-input').off('keypress').on('keypress', function(e) {
1319 969 if (e.which == 13 && !e.shiftKey) {
1320 970 e.preventDefault();
1321 - var botId = getBotIdFromElement(this);
1322 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1323 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1324 - disableChatInput(botId);
1325 - }
1326 - sendMessage(botId);
971 + sendMessage(); // Use the updated sendMessage function
1327 972 }
1328 973 });
1329 974
1330 975
1331 -function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false, botId = 'default') {
976 +function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
1332 977 try {
1333 978 // Determine styles based on sender type
1334 979 let messageClass, bgColor, fontColor;
1335 980
@@ -1350,29 +995,25 @@
1350 995 }
1351 996
1352 997 const messageDiv = $('<div>')
1353 998 .addClass(messageClass)
1354 - .attr('dir', 'auto');
1355 -
1356 - // Only apply inline colors if AI theme is not active (let CSS handle it)
1357 - var skipColors = shouldSkipInlineColors(botId);
1358 - if (skipColors) {
1359 - messageDiv.css({
1360 - 'margin-bottom': '1em'
1361 - });
1362 - } else {
1363 - messageDiv.css({
999 + .attr('dir', 'auto')
1000 + .css({
1364 1001 'background': bgColor,
1365 1002 'color': fontColor,
1366 1003 'margin-bottom': '1em'
1367 1004 });
1005 +
1006 + // Process the message content based on sender
1007 + let fullMessage;
1008 + if (sender === "user") {
1009 + // For user messages, apply linkify after sanitization
1010 + fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
1011 + } else {
1012 + // For bot/agent messages, preserve HTML
1013 + fullMessage = messageText;
1368 1014 }
1369 1015
1370 - // Process the message content - always run linkify to convert markdown
1371 - // links and format text. linkify() handles existing HTML safely via
1372 - // negative lookaheads that skip URLs already inside <a> tags.
1373 - let fullMessage = linkify(messageText);
1374 -
1375 1016 // Add images if provided
1376 1017 if (images && images.length > 0) {
1377 1018 fullMessage += '<div class="image-gallery" dir="auto">';
1378 1019 images.forEach(img => {
@@ -1378,9 +1019,9 @@
1378 1019 images.forEach(img => {
1379 1020 const safeTitle = sanitizeUserInput(img.title);
1380 1021 const safeUrl = encodeURI(img.image_url);
1381 1022 const safeThumbnail = encodeURI(img.thumbnail_url);
1382 -
1023 +
1383 1024 fullMessage += `
1384 1025 <div style="margin-bottom: 10px;">
1385 1026 <strong>${safeTitle}</strong><br>
1386 1027 <a href="${safeUrl}" target="_blank">
@@ -1392,14 +1033,9 @@
1392 1033 }
1393 1034
1394 1035 // Append HTML content if provided
1395 1036 if (messageHtml && sender !== "user") {
1396 - // Only add line breaks if there's actual text content before the HTML
1397 - if (fullMessage && fullMessage.trim()) {
1398 - fullMessage += '<br><br>' + messageHtml;
1399 - } else {
1400 - fullMessage = messageHtml;
1401 - }
1037 + fullMessage += '<br><br>' + messageHtml;
1402 1038 }
1403 1039
1404 1040 messageDiv.html(fullMessage);
1405 1041
@@ -1406,62 +1042,58 @@
1406 1042 if (isTemporary) {
1407 1043 messageDiv.addClass('temporary-message');
1408 1044 }
1409 1045
1410 - // Append to the correct chatbot instance's chat-box
1411 - var $chatBox = getElement(botId, 'chat-box');
1412 - messageDiv.hide().appendTo($chatBox).fadeIn(300, function() {
1046 + messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() {
1413 1047 // FIXED: Use event delegation for link tracking
1414 1048 if (sender === "bot" || sender === "agent") {
1415 - attachLinkTracking(messageDiv, messageText, botId);
1049 + attachLinkTracking(messageDiv, messageText);
1416 1050 }
1417 -
1051 +
1418 1052 if (sender === "bot") {
1419 - const lastUserMessage = $chatBox.find('.user-message').last();
1053 + const lastUserMessage = $('#chat-box').find('.user-message').last();
1420 1054 if (lastUserMessage.length) {
1421 - scrollElementToTop(lastUserMessage, botId);
1055 + scrollElementToTop(lastUserMessage);
1422 1056 }
1423 1057 }
1424 1058 });
1425 1059
1426 1060 if (messageText.id) {
1427 - var instance = MxChatInstances.get(botId);
1428 - instance.lastSeenMessageId = messageText.id;
1429 - hideNotification(botId);
1061 + lastSeenMessageId = messageText.id;
1062 + hideNotification();
1430 1063 }
1431 1064 } catch (error) {
1432 - // Error rendering message - silently continue
1065 + console.error("Error rendering message:", error);
1433 1066 }
1434 1067 }
1435 1068
1436 -// Helper function to attach link tracking with proper event handling
1437 -function attachLinkTracking(messageDiv, messageText, botId) {
1438 - botId = botId || 'default';
1069 +// NEW: Helper function to attach link tracking with proper event handling
1070 +function attachLinkTracking(messageDiv, messageText) {
1439 1071 // Use a slight delay to ensure DOM is ready
1440 1072 setTimeout(function() {
1441 1073 const links = messageDiv.find('a[href]').not('[data-tracked]');
1442 -
1074 +
1443 1075 links.each(function() {
1444 1076 const $link = $(this);
1445 1077 const originalHref = $link.attr('href');
1446 -
1078 +
1447 1079 // Mark as tracked to avoid duplicate handlers
1448 1080 $link.attr('data-tracked', 'true');
1449 -
1081 +
1450 1082 // Only track external URLs
1451 1083 if (originalHref && (originalHref.startsWith('http://') || originalHref.startsWith('https://'))) {
1452 1084 // Remove any existing click handlers first
1453 1085 $link.off('click.tracking');
1454 -
1086 +
1455 1087 // Add new click handler with namespace
1456 1088 $link.on('click.tracking', function(e) {
1457 1089 e.preventDefault();
1458 1090 e.stopPropagation();
1459 -
1460 - const messageContext = typeof messageText === 'string'
1461 - ? messageText.substring(0, 200)
1091 +
1092 + const messageContext = typeof messageText === 'string'
1093 + ? messageText.substring(0, 200)
1462 1094 : '';
1463 -
1095 +
1464 1096 // Track the click
1465 1097 $.ajax({
1466 1098 url: mxchatChat.ajax_url,
1467 1099 type: 'POST',
@@ -1466,9 +1098,9 @@
1466 1098 url: mxchatChat.ajax_url,
1467 1099 type: 'POST',
1468 1100 data: {
1469 1101 action: 'mxchat_track_url_click',
1470 - session_id: getChatSession(botId),
1102 + session_id: getChatSession(),
1471 1103 url: originalHref,
1472 1104 message_context: messageContext,
1473 1105 nonce: mxchatChat.nonce
1474 1106 },
@@ -1480,9 +1112,9 @@
1480 1112 window.location.href = originalHref;
1481 1113 }
1482 1114 }
1483 1115 });
1484 -
1116 +
1485 1117 return false; // Extra insurance to prevent default
1486 1118 });
1487 1119 }
1488 1120 });
@@ -1488,12 +1120,11 @@
1488 1120 });
1489 1121 }, 100); // Small delay to ensure DOM is ready
1490 1122 }
1491 1123
1492 -function replaceLastMessage(sender, responseText, responseHtml = '', images = [], botId = 'default') {
1124 +function replaceLastMessage(sender, responseText, responseHtml = '', images = []) {
1493 1125 var messageClass = sender === "user" ? "user-message" : sender === "agent" ? "agent-message" : "bot-message";
1494 - var $chatBox = getElement(botId, 'chat-box');
1495 - var lastMessageDiv = $chatBox.find('.bot-message.temporary-message, .agent-message.temporary-message').last();
1126 + var lastMessageDiv = $('#chat-box').find('.bot-message.temporary-message, .agent-message.temporary-message').last();
1496 1127
1497 1128 // Determine styles
1498 1129 let bgColor, fontColor;
1499 1130 if (sender === "user") {
@@ -1506,20 +1137,11 @@
1506 1137 bgColor = botMessageBgColor;
1507 1138 fontColor = botMessageFontColor;
1508 1139 }
1509 1140
1510 - // Always run linkify to convert markdown links and format text.
1511 - // linkify() already handles existing HTML (its URL patterns use negative lookaheads
1512 - // to avoid double-processing URLs that are already inside <a> tags).
1513 - var fullMessage = linkify(responseText);
1514 -
1141 + var fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(responseText))));
1515 1142 if (responseHtml) {
1516 - // Only add line breaks if there's actual text content before the HTML
1517 - if (fullMessage && fullMessage.trim()) {
1518 - fullMessage += '<br><br>' + responseHtml;
1519 - } else {
1520 - fullMessage = responseHtml;
1521 - }
1143 + fullMessage += '<br><br>' + responseHtml;
1522 1144 }
1523 1145
1524 1146 if (images.length > 0) {
1525 1147 fullMessage += '<div class="image-gallery" dir="auto">';
@@ -1535,423 +1157,158 @@
1535 1157 fullMessage += '</div>';
1536 1158 }
1537 1159
1538 1160 if (lastMessageDiv.length) {
1539 - // Replace content immediately to prevent visual gap between thinking dots and response
1540 - lastMessageDiv
1541 - .html(fullMessage)
1542 - .removeClass('bot-message user-message temporary-message')
1543 - .addClass(messageClass)
1544 - .attr('dir', 'auto');
1545 -
1546 - // Only apply inline colors if AI theme is not active (let CSS handle it)
1547 - var skipColors = mxchatChat.skip_inline_colors || shouldSkipInlineColors(botId);
1548 - if (!skipColors) {
1549 - lastMessageDiv.css({
1550 - 'background-color': bgColor,
1551 - 'color': fontColor,
1552 - });
1553 - }
1554 -
1555 - // Handle link tracking and scroll
1556 - if (sender === "bot" || sender === "agent") {
1557 - attachLinkTracking(lastMessageDiv, responseText, botId);
1558 -
1559 - const lastUserMessage = $chatBox.find('.user-message').last();
1560 - if (lastUserMessage.length) {
1561 - scrollElementToTop(lastUserMessage, botId);
1562 - }
1563 - // Show notification if chat is hidden
1564 - var $floatingChatbot = getElement(botId, 'floating-chatbot');
1565 - if ($floatingChatbot.hasClass('hidden')) {
1566 - showNotification(botId);
1567 - }
1568 - }
1569 -
1570 - // Re-enable chat input after response is displayed
1571 - enableChatInput(botId);
1161 + lastMessageDiv.fadeOut(200, function() {
1162 + $(this)
1163 + .html(fullMessage)
1164 + .removeClass('bot-message user-message')
1165 + .addClass(messageClass)
1166 + .attr('dir', 'auto')
1167 + .css({
1168 + 'background-color': bgColor,
1169 + 'color': fontColor,
1170 + })
1171 + .removeClass('temporary-message')
1172 + .fadeIn(200, function() {
1173 + // FIXED: Use the helper function for link tracking
1174 + if (sender === "bot" || sender === "agent") {
1175 + attachLinkTracking($(this), responseText);
1176 + }
1177 +
1178 + if (sender === "bot" || sender === "agent") {
1179 + const lastUserMessage = $('#chat-box').find('.user-message').last();
1180 + if (lastUserMessage.length) {
1181 + scrollElementToTop(lastUserMessage);
1182 + }
1183 + // Show notification if chat is hidden
1184 + if ($('#floating-chatbot').hasClass('hidden')) {
1185 + showNotification();
1186 + }
1187 + }
1188 + });
1189 + });
1572 1190 } else {
1573 - appendMessage(sender, responseText, responseHtml, images, false, botId);
1574 - // Re-enable chat input after response is displayed
1575 - enableChatInput(botId);
1191 + appendMessage(sender, responseText, responseHtml, images);
1576 1192 }
1577 1193 }
1578 1194
1579 1195
1580 - function appendThinkingMessage(botId) {
1581 - botId = botId || 'default';
1196 + function appendThinkingMessage() {
1197 + // Remove any existing thinking dots first
1198 + $('.thinking-dots').remove();
1582 1199
1583 - // Don't show thinking dots in live agent mode - message is just forwarded to a human
1584 - var indicator = getElementDOM(botId, 'chat-mode-indicator');
1585 - if (indicator && indicator.textContent === 'Live Agent') {
1586 - return;
1587 - }
1588 -
1589 - var $chatBox = getElement(botId, 'chat-box');
1590 -
1591 - // Remove any existing thinking dots in this bot's chat first
1592 - $chatBox.find('.thinking-dots').remove();
1593 -
1594 - // Check if we should skip inline colors (AI theme is active)
1595 - var skipColors = shouldSkipInlineColors(botId);
1596 -
1597 1200 // Retrieve the bot message font color and background color
1598 1201 var botMessageFontColor = mxchatChat.bot_message_font_color;
1599 1202 var botMessageBgColor = mxchatChat.bot_message_bg_color;
1600 1203
1601 - // Build thinking dots HTML - skip inline colors if AI theme is active
1602 - var dotStyle = skipColors ? '' : ' style="background-color: ' + botMessageFontColor + ';"';
1204 +
1603 1205 var thinkingHtml = '<div class="thinking-dots-container">' +
1604 1206 '<div class="thinking-dots">' +
1605 - '<span class="dot"' + dotStyle + '></span>' +
1606 - '<span class="dot"' + dotStyle + '></span>' +
1607 - '<span class="dot"' + dotStyle + '></span>' +
1207 + '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
1208 + '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
1209 + '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
1608 1210 '</div>' +
1609 1211 '</div>';
1610 1212
1611 - // Append the thinking dots to this bot's chat container - skip inline colors if AI theme is active
1612 - var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + '; color: ' + botMessageFontColor + ';"';
1613 - $chatBox.append('<div class="bot-message temporary-message"' + messageStyle + '>' + thinkingHtml + '</div>');
1614 - scrollToBottom(botId);
1213 + // Append the thinking dots to the chat container (or within the temporary message div)
1214 + $("#chat-box").append('<div class="bot-message temporary-message" style="background-color: ' + botMessageBgColor + ';">' + thinkingHtml + '</div>');
1215 + scrollToBottom();
1615 1216 }
1217 +
1218 + function removeThinkingDots() {
1219 + $('.thinking-dots').closest('.temporary-message').remove();
1220 + }
1616 1221
1617 - function removeThinkingDots(botId) {
1618 - botId = botId || 'default';
1619 - var $chatBox = getElement(botId, 'chat-box');
1620 - // Remove by temporary-message class first, then fall back to any bot-message containing thinking dots
1621 - $chatBox.find('.thinking-dots').closest('.temporary-message').remove();
1622 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1623 - }
1624 1222
1625 1223 // ====================================
1626 1224 // TEXT FORMATTING & PROCESSING
1627 1225 // ====================================
1628 1226
1227 +
1629 1228 function linkify(inputText) {
1630 - if (!inputText) {
1631 - return '';
1632 - }
1229 + if (!inputText) return '';
1230 +
1231 + // Process markdown headers
1232 + let processedText = formatMarkdownHeaders(inputText);
1233 +
1234 + // Process markdown links
1235 + const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
1236 + processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
1237 + const safeUrl = encodeURI(url);
1238 + const safeText = sanitizeUserInput(text);
1239 + return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
1240 + });
1633 1241
1634 - // Helper function to check if URL is already encoded
1635 - function isUrlEncoded(url) {
1636 - // Check for % followed by exactly 2 hex digits
1637 - return /%[0-9a-fA-F]{2}/.test(url);
1638 - }
1242 + // Process phone numbers (tel:)
1243 + const phonePattern = /\[([^\]]+)\]\((tel:[\d+]+)\)/g;
1244 + processedText = processedText.replace(phonePattern, (match, text, phone) => {
1245 + const safePhone = encodeURI(phone);
1246 + const safeText = sanitizeUserInput(text);
1247 + return `<a href="${safePhone}">${safeText}</a>`;
1248 + });
1639 1249
1640 - // Helper function to safely encode URLs only if needed
1641 - function safeEncodeUrl(url) {
1642 - // If URL already contains encoded characters, return as-is
1643 - if (isUrlEncoded(url)) {
1644 - return url;
1645 - }
1646 - // Otherwise, encode it
1647 - return encodeURI(url);
1648 - }
1649 -
1650 - // Process markdown headers FIRST
1651 - let processedText = formatMarkdownHeaders(inputText);
1652 -
1653 - // Process text styling (bold, italic, strikethrough)
1654 - processedText = formatTextStyling(processedText);
1655 -
1656 - // Process code blocks BEFORE processing links
1657 - processedText = formatCodeBlocks(processedText);
1658 -
1659 - // Process markdown tables BEFORE converting newlines to paragraphs
1660 - processedText = formatMarkdownTables(processedText);
1661 -
1662 - // NOW convert to paragraphs
1663 - processedText = convertNewlinesToBreaks(processedText);
1664 -
1665 - // IMPORTANT: Handle citation-style brackets FIRST [URL]
1666 - // This prevents them from being processed as markdown links
1667 - // Match [URL] where URL is a complete URL in square brackets (common in AI citations)
1668 - processedText = processedText.replace(/\[(https?:\/\/[^\]]+)\]/g, (match, url) => {
1669 - // Clean the URL of any trailing punctuation
1670 - let cleanUrl = url.replace(/[.,;!?]+$/, '');
1671 - const safeUrl = safeEncodeUrl(cleanUrl);
1672 - // Return as a proper link without the brackets
1673 - return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1674 - });
1675 -
1676 - // Process markdown links: [text](url) and [](url)
1677 - // Uses balanced parenthesis matching to handle URLs containing parens
1678 - // (e.g. PDF filenames with dates like (2025-08-28).pdf)
1679 - processedText = (function(input) {
1680 - var result = '';
1681 - var i = 0;
1682 - while (i < input.length) {
1683 - // Look for [ at current position
1684 - if (input[i] === '[') {
1685 - // Find closing ]
1686 - var closeBracket = input.indexOf(']', i + 1);
1687 - if (closeBracket === -1 || closeBracket + 1 >= input.length || input[closeBracket + 1] !== '(') {
1688 - result += input[i];
1689 - i++;
1690 - continue;
1691 - }
1692 - var linkText = input.substring(i + 1, closeBracket);
1693 - // Check if URL starts with http
1694 - var urlStart = closeBracket + 2;
1695 - if (!input.substring(urlStart).match(/^https?:\/\//)) {
1696 - result += input[i];
1697 - i++;
1698 - continue;
1699 - }
1700 - // Find balanced closing paren
1701 - var depth = 1;
1702 - var j = urlStart;
1703 - while (j < input.length && depth > 0) {
1704 - if (input[j] === '(') depth++;
1705 - else if (input[j] === ')') depth--;
1706 - if (depth > 0) j++;
1707 - }
1708 - if (depth !== 0) {
1709 - result += input[i];
1710 - i++;
1711 - continue;
1712 - }
1713 - var url = input.substring(urlStart, j);
1714 - var cleanUrl = url.replace(/[\].,;!?]+$/, '');
1715 - var encodedUrl = safeEncodeUrl(cleanUrl);
1716 - if (!linkText || !linkText.trim()) {
1717 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + cleanUrl + '</a>';
1718 - } else {
1719 - var safeText = sanitizeUserInput(linkText);
1720 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + safeText + '</a>';
1721 - }
1722 - i = j + 1; // Skip past the closing )
1723 - } else {
1724 - result += input[i];
1725 - i++;
1726 - }
1727 - }
1728 - return result;
1729 - })(processedText);
1730 -
1731 - // Process phone numbers: [text](tel:number)
1732 - const phonePattern = /\[([^\]]+)\]\((tel:[\d+\-\s()]+)\)/g;
1733 - processedText = processedText.replace(phonePattern, (match, text, phone) => {
1734 - const safePhone = safeEncodeUrl(phone);
1735 - const safeText = sanitizeUserInput(text);
1736 - return `<a href="${safePhone}">${safeText}</a>`;
1737 - });
1738 -
1739 - // Process mailto links: [text](mailto:email)
1740 - const mailtoPattern = /\[([^\]]+)\]\((mailto:[^\)]+)\)/g;
1741 - processedText = processedText.replace(mailtoPattern, (match, text, mailto) => {
1742 - const safeMailto = safeEncodeUrl(mailto);
1743 - const safeText = sanitizeUserInput(text);
1744 - return `<a href="${safeMailto}">${safeText}</a>`;
1745 - });
1746 -
1747 - // Process standalone URLs - but NOT if they're already in <a> tags or brackets
1748 - // Updated pattern to be more careful about what it matches
1749 - const urlPattern = /(^|[^">=\[\]])(https?:\/\/[^\s<"\[\]]+)(?![^<]*<\/a>)(?!\])/gim;
1750 - processedText = processedText.replace(urlPattern, (match, prefix, url) => {
1751 - // Extra check: make sure this isn't already linked
1752 - if (match.includes('href=') || match.includes('</a>')) {
1753 - return match;
1754 - }
1250 + // Process standalone URLs
1251 + const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
1252 + processedText = processedText.replace(urlPattern, (match, prefix, url) => {
1253 + const safeUrl = encodeURI(url);
1254 + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
1255 + });
1755 1256
1756 - // Clean trailing punctuation
1757 - let cleanUrl = url.replace(/[.,;!?)]+$/, '');
1758 - const safeUrl = safeEncodeUrl(cleanUrl);
1759 - return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1760 - });
1761 -
1762 - // Process www. URLs - but NOT if they're already in <a> tags or brackets
1763 - const wwwPattern = /(^|[^">/\[\]])(www\.[\S]+)(?![^<]*<\/a>)(?!\])/gim;
1764 - processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
1765 - // Extra check: make sure this isn't already linked
1766 - if (match.includes('href=') || match.includes('</a>')) {
1767 - return match;
1768 - }
1257 + // Process www. URLs
1258 + const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
1259 + processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
1260 + const safeUrl = encodeURI(`http://${url}`);
1261 + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
1262 + });
1769 1263
1770 - // Clean trailing punctuation
1771 - let cleanUrl = url.replace(/[.,;!?)]+$/, '');
1772 - const safeUrl = safeEncodeUrl(`http://${cleanUrl}`);
1773 - return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1774 - });
1264 + // Add this after your phone pattern
1265 + const mailtoPattern = /\[([^\]]+)\]\((mailto:[^\)]+)\)/g;
1266 + processedText = processedText.replace(mailtoPattern, (match, text, mailto) => {
1267 + const safeMailto = encodeURI(mailto);
1268 + const safeText = sanitizeUserInput(text);
1269 + return `<a href="${safeMailto}">${safeText}</a>`;
1270 + });
1775 1271
1776 - return processedText;
1777 -}
1272 + return processedText;
1273 + }
1778 1274
1779 1275 function formatMarkdownHeaders(text) {
1780 1276 // Handle h1 to h6 headers
1781 - return text.replace(/^(#{1,6})\s+(.+)$/gm, function(match, hashes, content) {
1277 + return text.replace(/^(#{1,6})\s(.+)$/gm, function(match, hashes, content) {
1782 1278 const level = hashes.length;
1783 - return `<h${level} class="chat-heading chat-heading-${level}">${content.trim()}</h${level}>`;
1279 + return `<h${level} class="chat-heading">${content}</h${level}>`;
1784 1280 });
1785 1281 }
1786 1282
1787 -function formatTextStyling(text) {
1788 - // IMPORTANT: Protect BOTH HTML href and Markdown URLs from formatting
1789 - const protectedSegments = [];
1790 - let protectedText = text;
1791 -
1792 - // Step 1a: Protect HTML href="..." attributes
1793 - protectedText = protectedText.replace(/href\s*=\s*["']([^"']+)["']/gi, function(match) {
1794 - const placeholder = `__PROTECTED_${protectedSegments.length}__`;
1795 - protectedSegments.push(match);
1796 - return placeholder;
1797 - });
1798 -
1799 - // Step 1b: Protect Markdown links [text](url)
1800 - // This is crucial - we need to protect the URLs in markdown format
1801 - protectedText = protectedText.replace(/\[([^\]]*)\]\(([^)]+)\)/g, function(match) {
1802 - const placeholder = `__PROTECTED_${protectedSegments.length}__`;
1803 - protectedSegments.push(match);
1804 - return placeholder;
1805 - });
1806 -
1807 - // Step 1c: Also protect bare URLs that might exist
1808 - protectedText = protectedText.replace(/(https?:\/\/[^\s<>"]+)/gi, function(match) {
1809 - const placeholder = `__PROTECTED_${protectedSegments.length}__`;
1810 - protectedSegments.push(match);
1811 - return placeholder;
1812 - });
1813 -
1814 - // Step 2: Now apply text styling to the protected text
1815 - // Handle bold text (**text**)
1816 - protectedText = protectedText.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
1817 -
1818 - // Handle italic text (*text* or _text_) - Safari-compatible (no lookbehind)
1819 - // Match single asterisks that aren't part of bold (**) by checking they're not followed/preceded by another *
1820 - protectedText = protectedText.replace(/(?!\*\*)\*([^*\n]+)\*(?!\*)/g, '<em>$1</em>');
1821 -
1822 - // Handle underscores for italic - Safari-compatible (no lookbehind)
1823 - // Exclude __PROTECTED_N__ placeholders by checking the content doesn't contain PROTECTED
1824 - protectedText = protectedText.replace(/(?!__)_((?!PROTECTED)[^_\n]+)_(?!_)/g, '<em>$1</em>');
1825 -
1826 - // Handle strikethrough (~~text~~)
1827 - protectedText = protectedText.replace(/~~(.*?)~~/g, '<del>$1</del>');
1828 -
1829 - // Step 3: Restore all protected segments
1830 - protectedSegments.forEach((original, index) => {
1831 - const placeholder = `__PROTECTED_${index}__`;
1832 - protectedText = protectedText.replace(placeholder, original);
1833 - });
1834 -
1835 - return protectedText;
1836 -}
1837 1283 function formatBoldText(text) {
1838 - // This function is kept for compatibility but now uses formatTextStyling
1839 - return formatTextStyling(text);
1284 + return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
1840 1285 }
1841 1286
1842 -function convertNewlinesToBreaks(text) {
1843 - // Split the text into paragraphs (marked by double newlines or multiple <br> tags)
1844 - const paragraphs = text.split(/(?:\n\n|\<br\>\s*\<br\>)/g);
1287 + function convertNewlinesToBreaks(text) {
1288 + // Split the text into paragraphs (marked by double newlines or multiple <br> tags)
1289 + const paragraphs = text.split(/(?:\n\n|\<br\>\s*\<br\>)/g);
1290 +
1291 + // Wrap each paragraph in <p> tags
1292 + return paragraphs
1293 + .map(para => `<p>${para.trim()}</p>`)
1294 + .join('');
1295 + }
1845 1296
1846 - // Filter out empty paragraphs and wrap each paragraph in <p> tags
1847 - return paragraphs
1848 - .map(para => para.trim())
1849 - .filter(para => para.length > 0) // Remove empty paragraphs
1850 - .map(para => `<p>${para}</p>`)
1851 - .join('');
1852 -}
1853 1297 function formatCodeBlocks(text) {
1854 - // Handle fenced code blocks with language specification (```language)
1855 - text = text.replace(/```(\w+)?\n?([\s\S]*?)```/g, (match, language, code) => {
1856 - const lang = language || 'text';
1857 - const escapedCode = escapeHtml(code.trim());
1858 - return `<div class="mxchat-code-block-container">
1859 - <div class="mxchat-code-header">
1860 - <span class="mxchat-code-language">${lang}</span>
1861 - <button class="mxchat-copy-button" aria-label="Copy to clipboard">Copy</button>
1862 - </div>
1863 - <pre class="mxchat-code-block"><code class="language-${lang}">${escapedCode}</code></pre>
1864 - </div>`;
1298 + // First handle raw PHP tags
1299 + text = text.replace(/(<\?php[\s\S]*?\?>)/g, (match) => {
1300 + return `<pre><code class="language-php">${escapeHtml(match)}</code></pre>`;
1865 1301 });
1866 -
1867 - // Handle inline code with single backticks
1868 - text = text.replace(/`([^`\n]+)`/g, '<code class="mxchat-inline-code">$1</code>');
1869 -
1870 - // Handle raw PHP tags (legacy support)
1871 - text = text.replace(/(<\?php[\s\S]*?\?>)/g, (match) => {
1872 - const escapedCode = escapeHtml(match);
1873 - return `<div class="mxchat-code-block-container">
1874 - <div class="mxchat-code-header">
1875 - <span class="mxchat-code-language">php</span>
1876 - <button class="mxchat-copy-button" aria-label="Copy to clipboard">Copy</button>
1877 - </div>
1878 - <pre class="mxchat-code-block"><code class="language-php">${escapedCode}</code></pre>
1879 - </div>`;
1302 +
1303 + // Then handle code blocks with backticks
1304 + text = text.replace(/```php5?\n([\s\S]+?)```/gi, (match, code) => {
1305 + return `<pre><code class="language-php">${escapeHtml(code)}</code></pre>`;
1880 1306 });
1881 -
1307 +
1882 1308 return text;
1883 1309 }
1884 -
1885 - function formatMarkdownTables(text) {
1886 - var lines = text.split('\n');
1887 - var result = [];
1888 - var i = 0;
1889 -
1890 - while (i < lines.length) {
1891 - // Check for a table: current line has pipes AND next line is a separator row
1892 - if (i + 1 < lines.length &&
1893 - lines[i].indexOf('|') !== -1 &&
1894 - /^\s*\|?[\s\-:]+(\|[\s\-:]+)+\|?\s*$/.test(lines[i + 1])) {
1895 -
1896 - var tableLines = [];
1897 - var headerLine = lines[i];
1898 - var separatorLine = lines[i + 1];
1899 - tableLines.push(headerLine);
1900 - tableLines.push(separatorLine);
1901 -
1902 - // Collect remaining table rows
1903 - var j = i + 2;
1904 - while (j < lines.length && lines[j].indexOf('|') !== -1 && lines[j].trim() !== '') {
1905 - tableLines.push(lines[j]);
1906 - j++;
1907 - }
1908 -
1909 - // Parse alignment from separator row
1910 - var sepCells = separatorLine.split('|').filter(function(c) { return c.trim() !== ''; });
1911 - var alignments = sepCells.map(function(cell) {
1912 - var trimmed = cell.trim();
1913 - if (trimmed.charAt(0) === ':' && trimmed.charAt(trimmed.length - 1) === ':') return 'center';
1914 - if (trimmed.charAt(trimmed.length - 1) === ':') return 'right';
1915 - return 'left';
1916 - });
1917 -
1918 - // Build HTML table
1919 - var html = '<div class="mxchat-table-wrapper"><table class="mxchat-table">';
1920 -
1921 - // Header row
1922 - var headerCells = tableLines[0].split('|').filter(function(c) { return c.trim() !== ''; });
1923 - html += '<thead><tr>';
1924 - headerCells.forEach(function(cell, idx) {
1925 - var align = alignments[idx] || 'left';
1926 - html += '<th style="text-align:' + align + '">' + cell.trim() + '</th>';
1927 - });
1928 - html += '</tr></thead>';
1929 -
1930 - // Body rows
1931 - html += '<tbody>';
1932 - for (var r = 2; r < tableLines.length; r++) {
1933 - var rowCells = tableLines[r].split('|').filter(function(c) { return c.trim() !== ''; });
1934 - html += '<tr>';
1935 - rowCells.forEach(function(cell, idx) {
1936 - var align = alignments[idx] || 'left';
1937 - html += '<td style="text-align:' + align + '">' + cell.trim() + '</td>';
1938 - });
1939 - html += '</tr>';
1940 - }
1941 - html += '</tbody></table></div>';
1942 -
1943 - result.push(html);
1944 - i = j;
1945 - } else {
1946 - result.push(lines[i]);
1947 - i++;
1948 - }
1949 - }
1950 -
1951 - return result.join('\n');
1952 - }
1953 -
1310 +
1954 1311 function sanitizeUserInput(text) {
1955 1312 const div = document.createElement('div');
1956 1313 div.textContent = text;
1957 1314 return div.innerHTML;
@@ -1956,12 +1313,12 @@
1956 1313 div.textContent = text;
1957 1314 return div.innerHTML;
1958 1315 }
1959 1316
1317 +
1960 1318 function escapeHtml(unsafe) {
1961 - // Skip escaping if it's already escaped or contains HTML code block markup
1962 - if (unsafe.includes('&lt;') || unsafe.includes('&gt;') ||
1963 - unsafe.includes('<pre><code') || unsafe.includes('</code></pre>')) {
1319 + // First check if it's already a code block
1320 + if (unsafe.includes('<pre><code') || unsafe.includes('</code></pre>')) {
1964 1321 return unsafe;
1965 1322 }
1966 1323
1967 1324 return unsafe
@@ -1977,25 +1334,15 @@
1977 1334 textArea.innerHTML = text;
1978 1335 return textArea.value;
1979 1336 }
1980 1337
1338 +
1981 1339 // ====================================
1982 1340 // UI & SCROLLING CONTROLS
1983 1341 // ====================================
1984 1342
1985 - function scrollToBottom(botIdOrInstant, instant) {
1986 - // Handle backward compatibility: scrollToBottom() or scrollToBottom(true/false)
1987 - var botId = 'default';
1988 - if (typeof botIdOrInstant === 'string') {
1989 - botId = botIdOrInstant;
1990 - instant = instant || false;
1991 - } else if (typeof botIdOrInstant === 'boolean') {
1992 - instant = botIdOrInstant;
1993 - } else {
1994 - instant = false;
1995 - }
1996 -
1997 - var chatBox = getElement(botId, 'chat-box');
1343 + function scrollToBottom(instant = false) {
1344 + var chatBox = $('#chat-box');
1998 1345 if (instant) {
1999 1346 // Instantly set the scroll position to the bottom
2000 1347 chatBox.scrollTop(chatBox.prop("scrollHeight"));
2001 1348 } else {
@@ -2004,15 +1351,15 @@
2004 1351 const scrollHeight = chatBox.prop("scrollHeight");
2005 1352 const initialScroll = chatBox.scrollTop();
2006 1353 const distance = scrollHeight - initialScroll;
2007 1354 const duration = 500; // Duration in ms
2008 -
1355 +
2009 1356 function smoothScroll(timestamp) {
2010 1357 if (!start) start = timestamp;
2011 1358 const progress = timestamp - start;
2012 1359 const currentScroll = initialScroll + (distance * (progress / duration));
2013 1360 chatBox.scrollTop(currentScroll);
2014 -
1361 +
2015 1362 if (progress < duration) {
2016 1363 requestAnimationFrame(smoothScroll);
2017 1364 } else {
2018 1365 chatBox.scrollTop(scrollHeight); // Ensure it's exactly at the bottom
@@ -2017,37 +1364,33 @@
2017 1364 } else {
2018 1365 chatBox.scrollTop(scrollHeight); // Ensure it's exactly at the bottom
2019 1366 }
2020 1367 }
2021 -
1368 +
2022 1369 requestAnimationFrame(smoothScroll);
2023 1370 }
2024 1371 }
2025 -
2026 - function scrollElementToTop(element, botId, topOffset) {
2027 - botId = botId || 'default';
2028 - topOffset = (typeof topOffset === 'number') ? topOffset : 2;
2029 - var chatBox = getElement(botId, 'chat-box');
1372 +
1373 + function scrollElementToTop(element) {
1374 + var chatBox = $('#chat-box');
2030 1375 var elementTop = element.position().top + chatBox.scrollTop();
2031 - chatBox.animate({ scrollTop: Math.max(0, elementTop - topOffset) }, 500);
1376 + chatBox.animate({ scrollTop: elementTop }, 500);
2032 1377 }
2033 -
2034 - function showChatWidget(botId) {
2035 - botId = botId || 'default';
2036 - var $button = getElement(botId, 'floating-chatbot-button');
1378 +
1379 + function showChatWidget() {
2037 1380 // First ensure display is set
2038 - $button.css('display', 'flex');
1381 + $('#floating-chatbot-button').css('display', 'flex');
2039 1382 // Then handle the fade
2040 - $button.fadeTo(500, 1);
1383 + $('#floating-chatbot-button').fadeTo(500, 1);
2041 1384 // Force visibility
2042 - $button.removeClass('hidden');
1385 + $('#floating-chatbot-button').removeClass('hidden');
1386 + //console.log('Showing widget');
2043 1387 }
2044 -
2045 - function hideChatWidget(botId) {
2046 - botId = botId || 'default';
2047 - var $button = getElement(botId, 'floating-chatbot-button');
2048 - $button.css('display', 'none');
2049 - $button.addClass('hidden');
1388 +
1389 + function hideChatWidget() {
1390 + $('#floating-chatbot-button').css('display', 'none');
1391 + $('#floating-chatbot-button').addClass('hidden');
1392 + //console.log('Hiding widget');
2050 1393 }
2051 1394
2052 1395 function disableScroll() {
2053 1396 if (isMobile()) {
@@ -2076,15 +1419,18 @@
2076 1419 // NOTIFICATION SYSTEM
2077 1420 // ====================================
2078 1421
2079 1422 function createNotificationBadge() {
1423 + //console.log("Creating notification badge...");
2080 1424 const chatButton = document.getElementById('floating-chatbot-button');
2081 -
1425 + //console.log("Chat button found:", !!chatButton);
1426 +
2082 1427 if (!chatButton) return;
2083 -
1428 +
2084 1429 // Remove any existing badge first
2085 1430 const existingBadge = chatButton.querySelector('.chat-notification-badge');
2086 1431 if (existingBadge) {
1432 + //console.log("Removing existing badge");
2087 1433 existingBadge.remove();
2088 1434 }
2089 1435
2090 1436 notificationBadge = document.createElement('div');
@@ -2106,43 +1452,34 @@
2106 1452 chatButton.appendChild(notificationBadge);
2107 1453
2108 1454 }
2109 1455
2110 - function showNotification(botId) {
2111 - botId = botId || 'default';
2112 - const badge = getElementDOM(botId, 'chat-notification-badge');
2113 - var $floatingChatbot = getElement(botId, 'floating-chatbot');
2114 - if (badge && $floatingChatbot.hasClass('hidden')) {
1456 + function showNotification() {
1457 + const badge = document.getElementById('chat-notification-badge');
1458 + if (badge && $('#floating-chatbot').hasClass('hidden')) {
2115 1459 badge.style.display = 'block';
2116 1460 badge.textContent = '1';
2117 1461 }
2118 1462 }
2119 -
2120 - function hideNotification(botId) {
2121 - botId = botId || 'default';
2122 - const badge = getElementDOM(botId, 'chat-notification-badge');
1463 +
1464 + function hideNotification() {
1465 + const badge = document.getElementById('chat-notification-badge');
2123 1466 if (badge) {
2124 1467 badge.style.display = 'none';
2125 1468 }
2126 1469 }
2127 -
2128 - function startNotificationChecking(botId) {
2129 - botId = botId || 'default';
1470 +
1471 + function startNotificationChecking() {
2130 1472 const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2131 1473 if (!chatPersistenceEnabled) return;
2132 -
2133 - createNotificationBadge(botId);
2134 - var instance = MxChatInstances.get(botId);
2135 - instance.notificationCheckInterval = setInterval(function() {
2136 - checkForNewMessages(botId);
2137 - }, 30000); // Check every 30 seconds
1474 +
1475 + createNotificationBadge();
1476 + notificationCheckInterval = setInterval(checkForNewMessages, 30000); // Check every 30 seconds
2138 1477 }
2139 -
2140 - function stopNotificationChecking(botId) {
2141 - botId = botId || 'default';
2142 - var instance = MxChatInstances.get(botId);
2143 - if (instance.notificationCheckInterval) {
2144 - clearInterval(instance.notificationCheckInterval);
1478 +
1479 + function stopNotificationChecking() {
1480 + if (notificationCheckInterval) {
1481 + clearInterval(notificationCheckInterval);
2145 1482 }
2146 1483 }
2147 1484
2148 1485 function checkForNewMessages() {
@@ -2168,35 +1505,63 @@
2168 1505 });
2169 1506 }
2170 1507
2171 1508
2172 -// ====================================
2173 -// LIVE AGENT FUNCTIONALITY
2174 -// ====================================
2175 -
2176 -function startPolling(botId) {
2177 - botId = botId || 'default';
2178 - var instance = MxChatInstances.get(botId);
2179 - // Clear any existing interval first
2180 - stopPolling(botId);
2181 - instance.pollingInterval = setInterval(function() {
2182 - checkForAgentMessages(botId);
2183 - }, 5000);
1509 + // ====================================
1510 + // LIVE AGENT FUNCTIONALITY
1511 + // ====================================
1512 +
1513 +function updateChatModeIndicator(mode) {
1514 + //console.log("updateChatModeIndicator called with mode:", mode);
1515 + const indicator = document.getElementById('chat-mode-indicator');
1516 + if (indicator) {
1517 + const oldText = indicator.textContent;
1518 +
1519 + if (mode === 'agent') {
1520 + indicator.textContent = 'Live Agent';
1521 + startPolling();
1522 + //console.log("Set indicator to Live Agent");
1523 + } else {
1524 + // Everything else is AI mode
1525 + const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
1526 + indicator.textContent = customAiText;
1527 + stopPolling();
1528 + //console.log("Set indicator to AI Agent:", customAiText);
1529 + }
1530 +
1531 + // Force immediate DOM update
1532 + if (oldText !== indicator.textContent) {
1533 + // Force a reflow to ensure the change is visible immediately
1534 + indicator.offsetHeight;
1535 +
1536 + // Double-check after a brief moment
1537 + setTimeout(() => {
1538 + //console.log("Final indicator text:", indicator.textContent);
1539 + }, 50);
1540 + }
1541 + } else {
1542 + console.error("Chat mode indicator element not found!");
1543 + }
2184 1544 }
2185 1545
2186 -function stopPolling(botId) {
2187 - botId = botId || 'default';
2188 - var instance = MxChatInstances.get(botId);
2189 - if (instance.pollingInterval) {
2190 - clearInterval(instance.pollingInterval);
2191 - instance.pollingInterval = null;
1546 + function startPolling() {
1547 + // Clear any existing interval first
1548 + stopPolling();
1549 + // Start new polling interval
1550 + pollingInterval = setInterval(checkForAgentMessages, 5000);
1551 + //console.log("Started agent message polling");
2192 1552 }
2193 -}
2194 -
2195 -function checkForAgentMessages(botId) {
2196 - botId = botId || 'default';
2197 - var instance = MxChatInstances.get(botId);
2198 - const sessionId = getChatSession(botId);
1553 +
1554 + function stopPolling() {
1555 + if (pollingInterval) {
1556 + clearInterval(pollingInterval);
1557 + pollingInterval = null;
1558 + //console.log("Stopped agent message polling");
1559 + }
1560 + }
1561 +
1562 +function checkForAgentMessages() {
1563 + const sessionId = getChatSession();
2199 1564 $.ajax({
2200 1565 url: mxchatChat.ajax_url,
2201 1566 type: 'POST',
2202 1567 dataType: 'json',
@@ -2202,44 +1567,35 @@
2202 1567 dataType: 'json',
2203 1568 data: {
2204 1569 action: 'mxchat_fetch_new_messages',
2205 1570 session_id: sessionId,
2206 - last_seen_id: instance.lastSeenMessageId,
2207 - persistence_enabled: 'true',
1571 + last_seen_id: lastSeenMessageId,
1572 + persistence_enabled: 'true', // Add this too
2208 1573 nonce: mxchatChat.nonce
2209 1574 },
2210 1575 success: function (response) {
2211 1576 if (response.success && response.data?.new_messages) {
2212 1577 let hasNewMessage = false;
2213 -
1578 +
2214 1579 response.data.new_messages.forEach(function (message) {
2215 - if (message.role === "agent" && !instance.processedMessageIds.has(message.id)) {
1580 + if (message.role === "agent" && !processedMessageIds.has(message.id)) {
2216 1581 hasNewMessage = true;
2217 - appendMessage("agent", message.content, '', [], false, botId);
2218 - instance.lastSeenMessageId = message.id;
2219 - instance.processedMessageIds.add(message.id);
1582 + // CHANGE THIS LINE:
1583 + appendMessage("agent", message.content); // Instead of replaceLastMessage
1584 + lastSeenMessageId = message.id;
1585 + processedMessageIds.add(message.id);
2220 1586 }
2221 1587 });
2222 1588
2223 - if (hasNewMessage) {
2224 - enableChatInput(botId);
1589 + if (hasNewMessage && $('#floating-chatbot').hasClass('hidden')) {
1590 + showNotification();
2225 1591 }
2226 -
2227 - var $floatingChatbot = getElement(botId, 'floating-chatbot');
2228 - if (hasNewMessage && $floatingChatbot.hasClass('hidden')) {
2229 - showNotification(botId);
2230 - }
2231 -
2232 - scrollToBottom(botId, true);
1592 +
1593 + scrollToBottom(true);
2233 1594 }
2234 -
2235 - // Handle chat mode transitions (e.g. agent ended chat via !endchat)
2236 - if (response.success && response.data?.chat_mode) {
2237 - updateChatModeIndicator(response.data.chat_mode, botId);
2238 - }
2239 1595 },
2240 1596 error: function (xhr, status, error) {
2241 - // Polling error - silently continue
1597 + //console.error("Polling error:", xhr, status, error);
2242 1598 }
2243 1599 });
2244 1600 }
2245 1601
@@ -2245,30 +1601,13 @@
2245 1601
2246 1602 // ====================================
2247 1603 // CHAT HISTORY & PERSISTENCE
2248 1604 // ====================================
2249 -
2250 -function loadChatHistory(botId, onComplete) {
2251 - botId = botId || 'default';
2252 - var instance = MxChatInstances.get(botId);
2253 -
2254 - // Prevent duplicate loading
2255 - if (instance.chatHistoryLoaded) {
2256 - if (onComplete) onComplete();
2257 - return;
2258 - }
2259 -
2260 - // Use getChatSession which returns null if no session exists (does NOT create one)
2261 - var sessionId = getChatSession(botId);
1605 +
1606 + function loadChatHistory() {
1607 + var sessionId = getChatSession();
2262 1608 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2263 1609
2264 - // No session yet — nothing to load. History will load after first message via ensureSession.
2265 - if (!sessionId) {
2266 - instance.chatHistoryLoaded = true;
2267 - if (onComplete) onComplete();
2268 - return;
2269 - }
2270 -
2271 1610 if (chatPersistenceEnabled && sessionId) {
2272 1611 $.ajax({
2273 1612 url: mxchatChat.ajax_url,
2274 1613 type: 'POST',
@@ -2277,35 +1616,23 @@
2277 1616 action: 'mxchat_fetch_conversation_history',
2278 1617 session_id: sessionId
2279 1618 },
2280 1619 success: function(response) {
2281 - // Handle session reset (IP changed while user was away)
2282 - if (response.success === false && response.data && response.data.action === 'reset_session') {
2283 - // Silent reset — new session but don't clear UI
2284 - MxChatInstances.silentResetSession(botId);
2285 - instance.chatHistoryLoaded = true; // Prevent retry loop
2286 - if (onComplete) onComplete();
2287 - return;
2288 - }
2289 -
2290 1620 // Check if the response indicates success
2291 1621 if (response.success) {
2292 1622 // Handle case where conversation data exists and is an array
2293 1623 if (response.data && Array.isArray(response.data.conversation)) {
2294 - var $chatBox = getElement(botId, 'chat-box');
1624 + var $chatBox = $('#chat-box');
2295 1625 var $fragment = $(document.createDocumentFragment());
2296 - let highestMessageId = instance.lastSeenMessageId;
1626 + let highestMessageId = lastSeenMessageId;
2297 1627
2298 1628 // Update chat mode if provided
2299 1629 if (response.data.chat_mode) {
2300 - updateChatModeIndicator(response.data.chat_mode, botId);
1630 + updateChatModeIndicator(response.data.chat_mode);
2301 1631 }
2302 1632
2303 1633 // Only process if there are actual messages
2304 1634 if (response.data.conversation.length > 0) {
2305 - // IMPORTANT: Clear existing messages before loading history
2306 - $chatBox.empty();
2307 -
2308 1635 $.each(response.data.conversation, function(index, message) {
2309 1636 // Skip agent messages if persistence is off
2310 1637 if (!chatPersistenceEnabled && message.role === 'agent') {
2311 1638 return;
@@ -2340,22 +1667,16 @@
2340 1667 var content = message.content;
2341 1668 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
2342 1669 content = decodeHTMLEntities(content);
2343 1670
2344 - // Skip linkify for messages containing structured HTML
2345 - // (forms, product cards, galleries, etc.) to avoid
2346 - // markdown formatting corrupting HTML attributes
2347 - // (e.g. underscores in name="field_name" becoming <em> tags)
2348 - if (content.includes("mxchat-product-card") ||
2349 - content.includes("mxchat-image-gallery") ||
2350 - content.includes("mxchat-featured-products") ||
2351 - content.includes("<form") ||
2352 - content.includes("<input") ||
2353 - content.includes("<select") ||
2354 - content.includes("<textarea")) {
1671 + if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
2355 1672 messageElement.html(content);
2356 1673 } else {
2357 - var formattedContent = linkify(content);
1674 + var formattedContent = linkify(
1675 + formatBoldText(
1676 + convertNewlinesToBreaks(formatCodeBlocks(content))
1677 + )
1678 + );
2358 1679 messageElement.html(formattedContent);
2359 1680 }
2360 1681
2361 1682 $fragment.append(messageElement);
@@ -2362,47 +1683,54 @@
2362 1683
2363 1684 // Track message IDs
2364 1685 if (message.id) {
2365 1686 highestMessageId = Math.max(highestMessageId, message.id);
2366 - instance.processedMessageIds.add(message.id);
1687 + processedMessageIds.add(message.id);
2367 1688 }
2368 1689 });
2369 1690
2370 1691 // Only append messages and scroll if we have content
2371 1692 $chatBox.append($fragment);
2372 - scrollToBottom(botId, true);
1693 + scrollToBottom(true);
2373 1694
2374 1695 // Collapse quick questions if we have conversation history
2375 - // BUT skip auto-collapse for embedded bots (they should stay expanded)
2376 - if (hasQuickQuestions(botId) && !isEmbeddedBot(botId)) {
2377 - collapseQuickQuestions(botId);
1696 + if (hasQuickQuestions()) {
1697 + collapseQuickQuestions();
2378 1698 }
2379 1699
2380 1700 // Update lastSeenMessageId after history loads
2381 - instance.lastSeenMessageId = highestMessageId;
1701 + lastSeenMessageId = highestMessageId;
2382 1702
2383 1703 // Only update chat mode if persistence is enabled and we have messages
2384 1704 if (chatPersistenceEnabled) {
2385 1705 var lastMessage = response.data.conversation[response.data.conversation.length - 1];
2386 1706 if (lastMessage.role === 'agent') {
2387 - updateChatModeIndicator('agent', botId);
1707 + updateChatModeIndicator('agent');
2388 1708 }
2389 1709 }
2390 -
2391 - // Mark as loaded ONLY after successful load
2392 - instance.chatHistoryLoaded = true;
1710 + } else {
1711 + // No conversation history - this is normal for new chats
1712 + //console.log("No conversation history found for this session - starting fresh chat.");
2393 1713 }
1714 + } else {
1715 + // Response successful but no conversation data - this is also normal
1716 + //console.log("No conversation data in response - starting fresh chat.");
2394 1717 }
1718 + } else {
1719 + // Only show error if the response explicitly indicates an error
1720 + console.warn("Failed to load chat history:", response.message || "Unknown error");
1721 + // Don't show error message to user for failed history loads
1722 + // Just start with a fresh chat instead
2395 1723 }
2396 - if (onComplete) onComplete();
2397 1724 },
2398 1725 error: function(xhr, status, error) {
2399 - // Error loading chat history - silently continue
2400 - if (onComplete) onComplete();
1726 + // Only log AJAX errors, don't show them to the user
1727 + console.error("AJAX error loading chat history:", status, error);
1728 + // Start with fresh chat - don't show error to user
2401 1729 }
2402 1730 });
2403 1731 } else {
2404 - if (onComplete) onComplete();
1732 + //console.log("Chat persistence is disabled or no session ID found. Starting fresh chat.");
2405 1733 }
2406 1734 }
2407 1735
2408 1736
@@ -2416,42 +1744,40 @@
2416 1744 element.addEventListener(eventType, handler);
2417 1745 }
2418 1746 }
2419 1747
2420 - function showActivePdf(filename, botId) {
2421 - botId = botId || 'default';
2422 - const container = getElementDOM(botId, 'active-pdf-container');
2423 - const nameElement = getElementDOM(botId, 'active-pdf-name');
2424 -
1748 + function showActivePdf(filename) {
1749 + const container = document.getElementById('active-pdf-container');
1750 + const nameElement = document.getElementById('active-pdf-name');
1751 +
2425 1752 if (!container || !nameElement) {
1753 + //console.error('PDF container elements not found');
2426 1754 return;
2427 1755 }
2428 -
1756 +
2429 1757 nameElement.textContent = filename;
2430 1758 container.style.display = 'flex';
2431 1759 }
2432 -
2433 - function showActiveWord(filename, botId) {
2434 - botId = botId || 'default';
2435 - const container = getElementDOM(botId, 'active-word-container');
2436 - const nameElement = getElementDOM(botId, 'active-word-name');
2437 -
1760 +
1761 + function showActiveWord(filename) {
1762 + const container = document.getElementById('active-word-container');
1763 + const nameElement = document.getElementById('active-word-name');
1764 +
2438 1765 if (!container || !nameElement) {
1766 + //console.error('Word document container elements not found');
2439 1767 return;
2440 1768 }
2441 -
1769 +
2442 1770 nameElement.textContent = filename;
2443 1771 container.style.display = 'flex';
2444 1772 }
2445 -
2446 - function removeActivePdf(botId) {
2447 - botId = botId || 'default';
2448 - var instance = MxChatInstances.get(botId);
2449 - const container = getElementDOM(botId, 'active-pdf-container');
2450 - const nameElement = getElementDOM(botId, 'active-pdf-name');
2451 -
2452 - if (!container || !nameElement || !instance.activePdfFile) return;
2453 -
1773 +
1774 + function removeActivePdf() {
1775 + const container = document.getElementById('active-pdf-container');
1776 + const nameElement = document.getElementById('active-pdf-name');
1777 +
1778 + if (!container || !nameElement || !activePdfFile) return;
1779 +
2454 1780 fetch(mxchatChat.ajax_url, {
2455 1781 method: 'POST',
2456 1782 headers: {
2457 1783 'Content-Type': 'application/x-www-form-urlencoded',
@@ -2457,9 +1783,9 @@
2457 1783 'Content-Type': 'application/x-www-form-urlencoded',
2458 1784 },
2459 1785 body: new URLSearchParams({
2460 1786 'action': 'mxchat_remove_pdf',
2461 - 'session_id': getChatSession(botId),
1787 + 'session_id': sessionId,
2462 1788 'nonce': mxchatChat.nonce
2463 1789 })
2464 1790 })
2465 1791 .then(response => response.json())
@@ -2471,12 +1797,12 @@
2471 1797 appendMessage('bot', 'PDF removed.');
2472 1798 }
2473 1799 })
2474 1800 .catch(error => {
2475 - // Error removing PDF - silently continue
1801 + //console.error('Error removing PDF:', error);
2476 1802 });
2477 1803 }
2478 -
1804 +
2479 1805 function removeActiveWord() {
2480 1806 const container = document.getElementById('active-word-container');
2481 1807 const nameElement = document.getElementById('active-word-name');
2482 1808
@@ -2502,53 +1828,56 @@
2502 1828 appendMessage('bot', 'Word document removed.');
2503 1829 }
2504 1830 })
2505 1831 .catch(error => {
2506 - // Error removing Word document - silently continue
1832 + //console.error('Error removing Word document:', error);
2507 1833 });
2508 1834 }
2509 -
1835 +
2510 1836 // ====================================
2511 1837 // CONSENT & COMPLIANCE (GDPR)
2512 1838 // ====================================
2513 -
2514 - function initializeChatVisibility(botId) {
2515 - botId = botId || 'default';
2516 - const complianzEnabled = mxchatChat.complianz_toggle === 'on' ||
2517 - mxchatChat.complianz_toggle === '1' ||
1839 +
1840 + function initializeChatVisibility() {
1841 + //console.log('Initializing chat visibility');
1842 + const complianzEnabled = mxchatChat.complianz_toggle === 'on' ||
1843 + mxchatChat.complianz_toggle === '1' ||
2518 1844 mxchatChat.complianz_toggle === 1;
2519 -
1845 +
2520 1846 if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') {
2521 1847 // Initial check
2522 - checkConsentAndShowChat(botId);
2523 -
1848 + checkConsentAndShowChat();
1849 +
2524 1850 // Listen for consent changes
2525 1851 $(document).on('cmplz_status_change', function(event) {
2526 - checkConsentAndShowChat(botId);
1852 + //console.log('Status change detected');
1853 + checkConsentAndShowChat();
2527 1854 });
2528 1855 } else {
2529 1856 // If Complianz is not enabled, always show
2530 - getElement(botId, 'floating-chatbot-button')
1857 + $('#floating-chatbot-button')
2531 1858 .css('display', 'flex')
2532 1859 .removeClass('hidden no-consent')
2533 1860 .fadeTo(500, 1);
2534 -
1861 +
2535 1862 // Also check pre-chat message when Complianz is not enabled
2536 - checkPreChatDismissal(botId);
1863 + checkPreChatDismissal();
2537 1864 }
2538 1865 }
2539 1866
2540 -
2541 - function checkConsentAndShowChat(botId) {
2542 - botId = botId || 'default';
1867 +
1868 + function checkConsentAndShowChat() {
2543 1869 var consentStatus = cmplz_has_consent('marketing');
2544 1870 var consentType = complianz.consenttype;
2545 -
2546 - let $widget = getElement(botId, 'floating-chatbot-button');
2547 - let $chatbot = getElement(botId, 'floating-chatbot');
2548 - let $preChat = getElement(botId, 'pre-chat-message');
2549 -
1871 +
1872 + //console.log('Checking consent:', {status: consentStatus,type: consentType});
1873 +
1874 + let $widget = $('#floating-chatbot-button');
1875 + let $chatbot = $('#floating-chatbot');
1876 + let $preChat = $('#pre-chat-message');
1877 +
2550 1878 if (consentStatus === true) {
1879 + //console.log('Consent granted - showing widget');
2551 1880 $widget
2552 1881 .removeClass('no-consent')
2553 1882 .css('display', 'flex')
2554 1883 .removeClass('hidden')
@@ -2553,12 +1882,13 @@
2553 1882 .css('display', 'flex')
2554 1883 .removeClass('hidden')
2555 1884 .fadeTo(500, 1);
2556 1885 $chatbot.removeClass('no-consent');
2557 -
1886 +
2558 1887 // Show pre-chat message if not dismissed
2559 - checkPreChatDismissal(botId);
1888 + checkPreChatDismissal();
2560 1889 } else {
1890 + //console.log('No consent - hiding widget');
2561 1891 $widget
2562 1892 .addClass('no-consent')
2563 1893 .fadeTo(500, 0, function() {
2564 1894 $(this)
@@ -2565,9 +1895,9 @@
2565 1895 .css('display', 'none')
2566 1896 .addClass('hidden');
2567 1897 });
2568 1898 $chatbot.addClass('no-consent');
2569 -
1899 +
2570 1900 // Hide pre-chat message when no consent
2571 1901 $preChat.hide();
2572 1902 }
2573 1903 }
@@ -2575,38 +1905,46 @@
2575 1905
2576 1906 // ====================================
2577 1907 // PRE-CHAT MESSAGE HANDLING
2578 1908 // ====================================
2579 -
2580 - function checkPreChatDismissal(botId) {
2581 - botId = botId || 'default';
2582 - try {
2583 - var dismissedAt = localStorage.getItem('mxchat_pre_chat_dismissed_' + botId);
2584 - if (dismissedAt) {
2585 - // Re-show after 24 hours
2586 - var elapsed = Date.now() - parseInt(dismissedAt, 10);
2587 - if (elapsed < 86400000) {
2588 - getElement(botId, 'pre-chat-message').hide();
2589 - return;
1909 +
1910 + function checkPreChatDismissal() {
1911 + $.ajax({
1912 + url: mxchatChat.ajax_url,
1913 + type: 'POST',
1914 + data: {
1915 + action: 'mxchat_check_pre_chat_message_status',
1916 + _ajax_nonce: mxchatChat.nonce
1917 + },
1918 + success: function(response) {
1919 + if (response.success && !response.data.dismissed) {
1920 + $('#pre-chat-message').fadeIn(250);
1921 + } else {
1922 + $('#pre-chat-message').hide();
2590 1923 }
2591 - // Expired — clear and show again
2592 - localStorage.removeItem('mxchat_pre_chat_dismissed_' + botId);
1924 + },
1925 + error: function() {
1926 + //console.error('Failed to check pre-chat message dismissal status.');
2593 1927 }
2594 - getElement(botId, 'pre-chat-message').fadeIn(250);
2595 - } catch (e) {
2596 - // localStorage unavailable — show the message
2597 - getElement(botId, 'pre-chat-message').fadeIn(250);
2598 - }
1928 + });
2599 1929 }
2600 -
2601 - function handlePreChatDismissal(botId) {
2602 - botId = botId || 'default';
2603 - getElement(botId, 'pre-chat-message').fadeOut(200);
2604 - try {
2605 - localStorage.setItem('mxchat_pre_chat_dismissed_' + botId, String(Date.now()));
2606 - } catch (e) {
2607 - // localStorage unavailable — dismissal won't persist
2608 - }
1930 +
1931 + function handlePreChatDismissal() {
1932 + $('#pre-chat-message').fadeOut(200);
1933 + $.ajax({
1934 + url: mxchatChat.ajax_url,
1935 + type: 'POST',
1936 + data: {
1937 + action: 'mxchat_dismiss_pre_chat_message',
1938 + _ajax_nonce: mxchatChat.nonce
1939 + },
1940 + success: function() {
1941 + $('#pre-chat-message').hide();
1942 + },
1943 + error: function() {
1944 + //console.error('Failed to dismiss pre-chat message.');
1945 + }
1946 + });
2609 1947 }
2610 1948
2611 1949
2612 1950 // ====================================
@@ -2632,104 +1970,78 @@
2632 1970 // ====================================
2633 1971
2634 1972 $(document).on('click', '.mxchat-popular-question', function () {
2635 1973 var question = $(this).text();
2636 - var botId = getBotIdFromElement(this);
2637 -
1974 +
2638 1975 // Append the question as if the user typed it
2639 - appendMessage("user", question, '', [], false, botId);
2640 -
1976 + appendMessage("user", question);
1977 +
2641 1978 // Only collapse if there are questions
2642 - if (hasQuickQuestions(botId)) {
2643 - collapseQuickQuestions(botId);
1979 + if (hasQuickQuestions()) {
1980 + collapseQuickQuestions();
2644 1981 }
2645 -
1982 +
2646 1983 // Send the question to the server
2647 - sendMessageToChatbot(question, botId);
1984 + sendMessageToChatbot(question);
2648 1985 });
2649 1986
2650 1987 $(document).on('click', '.questions-toggle-btn', function(e) {
2651 1988 e.preventDefault();
2652 1989 e.stopPropagation();
2653 - var botId = getBotIdFromElement(this);
2654 - expandQuickQuestions(botId);
1990 + expandQuickQuestions();
2655 1991 });
2656 1992
2657 1993 $(document).on('click', '.questions-collapse-btn', function(e) {
2658 1994 e.preventDefault();
2659 1995 e.stopPropagation();
2660 - var botId = getBotIdFromElement(this);
2661 - collapseQuickQuestions(botId);
1996 + collapseQuickQuestions();
2662 1997 });
2663 1998
2664 - // Chatbot visibility toggle handlers - use class selector for multi-instance support
2665 - $(document).on('click', '.floating-chatbot-button', function() {
2666 - var botId = getBotIdFromElement(this);
2667 - var $chatbot = getElement(botId, 'floating-chatbot');
2668 - var $badge = getElement(botId, 'chat-notification-badge');
2669 - var $preChat = getElement(botId, 'pre-chat-message');
2670 -
2671 - if ($chatbot.hasClass('hidden')) {
2672 - $chatbot.removeClass('hidden').addClass('visible');
1999 + // Chatbot visibility toggle handlers
2000 + $(document).on('click', '#floating-chatbot-button', function() {
2001 + var chatbot = $('#floating-chatbot');
2002 + if (chatbot.hasClass('hidden')) {
2003 + chatbot.removeClass('hidden').addClass('visible');
2673 2004 $(this).addClass('hidden');
2674 - $badge.hide(); // Hide notification when opening chat
2005 + $('#chat-notification-badge').hide(); // Hide notification when opening chat
2675 2006 disableScroll();
2676 - $preChat.fadeOut(250);
2677 -
2678 - // Load chat history for returning visitors (persistence)
2679 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
2680 - if (chatPersistenceEnabled) {
2681 - MxChatInstances.ensureSession(botId);
2682 - }
2683 -
2684 - // Deferred email check — only on first widget open
2685 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2686 - var instance = MxChatInstances.get(botId);
2687 - if (emailBlocker && !instance.emailCheckDone) {
2688 - instance.emailCheckDone = true;
2689 - resolveEmailState(botId);
2690 - } else if (!emailBlocker) {
2691 - // No email collection — still route through showChatContainerForBot
2692 - // so the loader is shown while chat history loads
2693 - showChatContainerForBot(botId);
2694 - }
2007 + $('#pre-chat-message').fadeOut(250);
2695 2008 } else {
2696 - $chatbot.removeClass('visible').addClass('hidden');
2009 + chatbot.removeClass('visible').addClass('hidden');
2697 2010 $(this).removeClass('hidden');
2698 2011 enableScroll();
2699 - checkPreChatDismissal(botId);
2012 + checkPreChatDismissal();
2700 2013 }
2701 2014 });
2702 -
2703 - // Allow clicking anywhere on the title bar to close the chatbot
2704 - $(document).on('click', '.chatbot-top-bar', function() {
2705 - var botId = getBotIdFromElement(this);
2706 - getElement(botId, 'floating-chatbot').addClass('hidden').removeClass('visible');
2707 - getElement(botId, 'floating-chatbot-button').removeClass('hidden');
2015 +
2016 + $(document).on('click', '#exit-chat-button', function() {
2017 + $('#floating-chatbot').addClass('hidden').removeClass('visible');
2018 + $('#floating-chatbot-button').removeClass('hidden');
2708 2019 enableScroll();
2709 2020 });
2710 -
2021 +
2711 2022 $(document).on('click', '.close-pre-chat-message', function(e) {
2712 2023 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
2713 - var botId = getBotIdFromElement(this);
2714 - handlePreChatDismissal(botId);
2024 + $('#pre-chat-message').fadeOut(200, function() {
2025 + $(this).remove();
2026 + });
2715 2027 });
2028 +
2716 2029
2717 -
2718 - // PDF upload button handlers - use class selector
2719 - $(document).on('click', '.pdf-upload-btn', function() {
2720 - var botId = getBotIdFromElement(this);
2721 - var pdfInput = getElementDOM(botId, 'pdf-upload');
2722 - if (pdfInput) pdfInput.click();
2723 - });
2724 -
2725 - // Word upload button handlers - use class selector
2726 - $(document).on('click', '.word-upload-btn', function() {
2727 - var botId = getBotIdFromElement(this);
2728 - var wordInput = getElementDOM(botId, 'word-upload');
2729 - if (wordInput) wordInput.click();
2730 - });
2030 + // PDF upload button handlers
2031 + if (document.getElementById('pdf-upload-btn')) {
2032 + document.getElementById('pdf-upload-btn').addEventListener('click', function() {
2033 + document.getElementById('pdf-upload').click();
2034 + });
2035 + }
2731 2036
2037 + // Word upload button handlers
2038 + if (document.getElementById('word-upload-btn')) {
2039 + document.getElementById('word-upload-btn').addEventListener('click', function() {
2040 + document.getElementById('word-upload').click();
2041 + });
2042 + }
2043 +
2732 2044 // PDF file input change handler
2733 2045 addSafeEventListener('pdf-upload', 'change', async function(e) {
2734 2046 const file = e.target.files[0];
2735 2047
@@ -2738,13 +2050,15 @@
2738 2050 return;
2739 2051 }
2740 2052
2741 2053 if (!sessionId) {
2054 + //console.error('No session ID found');
2742 2055 alert('Error: No session ID found');
2743 2056 return;
2744 2057 }
2745 -
2058 +
2746 2059 if (!mxchatChat || !mxchatChat.ajax_url || !mxchatChat.nonce) {
2060 + //console.error('mxchatChat not properly configured:', mxchatChat);
2747 2061 alert('Error: Ajax configuration missing');
2748 2062 return;
2749 2063 }
2750 2064
@@ -2786,11 +2100,13 @@
2786 2100 appendMessage('bot', data.data.message);
2787 2101 scrollToBottom();
2788 2102 activePdfFile = data.data.filename;
2789 2103 } else {
2104 + //console.error('Upload failed:', data.data);
2790 2105 alert('Failed to upload PDF. Please try again.');
2791 2106 }
2792 2107 } catch (error) {
2108 + //console.error('Upload error:', error);
2793 2109 alert('Error uploading file. Please try again.');
2794 2110 } finally {
2795 2111 uploadBtn.disabled = false;
2796 2112 sendBtn.disabled = false;
@@ -2808,12 +2124,13 @@
2808 2124 return;
2809 2125 }
2810 2126
2811 2127 if (!sessionId) {
2128 + //console.error('No session ID found');
2812 2129 alert('Error: No session ID found');
2813 2130 return;
2814 2131 }
2815 -
2132 +
2816 2133 // Disable buttons and show loading state
2817 2134 const uploadBtn = document.getElementById('word-upload-btn');
2818 2135 const sendBtn = document.getElementById('send-button');
2819 2136 const originalBtnContent = uploadBtn.innerHTML;
@@ -2851,11 +2168,13 @@
2851 2168 appendMessage('bot', data.data.message);
2852 2169 scrollToBottom();
2853 2170 activeWordFile = data.data.filename;
2854 2171 } else {
2172 + //console.error('Upload failed:', data.data);
2855 2173 alert('Failed to upload Word document. Please try again.');
2856 2174 }
2857 2175 } catch (error) {
2176 + //console.error('Upload error:', error);
2858 2177 alert('Error uploading file. Please try again.');
2859 2178 } finally {
2860 2179 uploadBtn.disabled = false;
2861 2180 sendBtn.disabled = false;
@@ -2913,497 +2232,454 @@
2913 2232 });
2914 2233
2915 2234
2916 2235 // ====================================
2917 -// INIT LOADER & CHAT CONTAINER HELPERS
2236 +// EMAIL COLLECTION SETUP - FIXED VERSION
2918 2237 // ====================================
2919 -// These must be outside the email collection block so they're always available
2920 -// (used by persistence loading even when email collection is off)
2921 -
2922 -function showInitLoader(botId) {
2923 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2924 - if (loader) loader.style.display = 'flex';
2925 -}
2926 -
2927 -function hideInitLoader(botId) {
2928 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2929 - if (loader) loader.style.display = 'none';
2930 -}
2931 -
2932 -function showEmailFormForBot(botId) {
2933 - hideInitLoader(botId);
2934 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2935 - var chatContainer = getElementDOM(botId, 'chat-container');
2936 - if (emailBlocker) emailBlocker.style.display = 'flex';
2937 - if (chatContainer) chatContainer.style.display = 'none';
2938 -}
2939 -
2940 -function showChatContainerForBot(botId) {
2941 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2942 - var chatContainer = getElementDOM(botId, 'chat-container');
2943 - if (emailBlocker) emailBlocker.style.display = 'none';
2944 -
2945 - var instance = MxChatInstances.get(botId);
2946 - var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2947 -
2948 - // If persistence is on and history hasn't loaded yet, show loader
2949 - // while history loads to prevent flash of empty chat
2950 - if (chatPersistenceEnabled && !instance.chatHistoryLoaded) {
2951 - if (chatContainer) chatContainer.style.display = 'none';
2952 - showInitLoader(botId);
2953 - loadChatHistory(botId, function() {
2954 - hideInitLoader(botId);
2955 - if (chatContainer) chatContainer.style.display = 'flex';
2956 - scrollToBottom(botId, true);
2957 - });
2958 - } else {
2959 - hideInitLoader(botId);
2960 - if (chatContainer) chatContainer.style.display = 'flex';
2961 - if (typeof loadChatHistory === 'function') {
2962 - loadChatHistory(botId);
2963 - }
2964 - }
2965 -}
2966 -
2967 -// ====================================
2968 -// EMAIL COLLECTION SETUP - MULTI-INSTANCE VERSION
2969 -// ====================================
2970 2238 // Only run email collection setup if it's enabled
2971 2239 if (mxchatChat && mxchatChat.email_collection_enabled === 'on') {
2240 + //console.log('Email collection is enabled, setting up handlers...');
2241 +
2242 + // Email collection form setup and handlers
2243 + const emailForm = document.getElementById('email-collection-form');
2244 + const emailBlocker = document.getElementById('email-blocker');
2245 + const chatbotWrapper = document.getElementById('chat-container');
2972 2246
2973 - // Track submitting state per bot
2974 - const emailSubmittingState = {};
2247 + if (emailForm && emailBlocker && chatbotWrapper) {
2248 +
2249 + // Add loading state management
2250 + let isSubmitting = false;
2251 +
2252 + // Optimized UI transition functions
2253 + function showEmailForm() {
2254 + emailBlocker.style.display = 'flex';
2255 + chatbotWrapper.style.display = 'none';
2256 + }
2975 2257
2976 - // Add CSS animations for email form (once globally)
2977 - if (!document.getElementById('email-error-styles')) {
2978 - const style = document.createElement('style');
2979 - style.id = 'email-error-styles';
2980 - style.textContent = `
2981 - @keyframes fadeInError {
2982 - from { opacity: 0; transform: translateY(-5px); }
2983 - to { opacity: 1; transform: translateY(0); }
2258 + function showChatContainer() {
2259 + // Show chat immediately without delay
2260 + emailBlocker.style.display = 'none';
2261 + chatbotWrapper.style.display = 'flex';
2262 +
2263 + // Load chat history only after showing chat container
2264 + if (typeof loadChatHistory === 'function') {
2265 + loadChatHistory();
2984 2266 }
2985 - .email-input-shake {
2986 - animation: shake 0.5s ease-in-out;
2987 - }
2988 - @keyframes shake {
2989 - 0%, 100% { transform: translateX(0); }
2990 - 25% { transform: translateX(-5px); }
2991 - 75% { transform: translateX(5px); }
2992 - }
2993 - @keyframes spin {
2994 - from { transform: rotate(0deg); }
2995 - to { transform: rotate(360deg); }
2996 - }
2997 - .email-spinner {
2998 - display: inline-block;
2999 - vertical-align: middle;
3000 - }
3001 - `;
3002 - document.head.appendChild(style);
3003 - }
2267 + }
3004 2268
3005 - function isValidEmailAddress(email) {
3006 - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3007 - return emailRegex.test(email.trim()) && email.length <= 254;
3008 - }
2269 + // Enhanced email validation
2270 + function isValidEmail(email) {
2271 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2272 + return emailRegex.test(email.trim()) && email.length <= 254; // RFC 5321 limit
2273 + }
3009 2274
3010 - function isValidNameInput(name) {
3011 - return name && name.trim().length >= 2 && name.trim().length <= 100;
3012 - }
2275 + // Enhanced name validation
2276 + function isValidName(name) {
2277 + return name && name.trim().length >= 2 && name.trim().length <= 100;
2278 + }
3013 2279
3014 - /**
3015 - * Replace {visitor_name} placeholder in intro message with actual visitor name
3016 - * @param {string} botId - The bot instance ID
3017 - * @param {string} visitorName - The visitor's name to insert
3018 - */
3019 - function replaceVisitorNamePlaceholder(botId, visitorName) {
3020 - var chatBox = getElementDOM(botId, 'chat-box');
3021 - if (!chatBox) return;
3022 -
3023 - // Find the first bot message (intro message)
3024 - var introMessage = chatBox.querySelector('.bot-message');
3025 - if (!introMessage) return;
3026 -
3027 - var messageContent = introMessage.querySelector('div[dir="auto"]');
3028 - if (!messageContent) return;
3029 -
3030 - var html = messageContent.innerHTML;
3031 -
3032 - // Replace {visitor_name} placeholder (case-insensitive)
3033 - if (visitorName && visitorName.trim()) {
3034 - // Escape HTML to prevent XSS
3035 - var safeName = $('<div>').text(visitorName.trim()).html();
3036 - html = html.replace(/\{visitor_name\}/gi, safeName);
3037 - } else {
3038 - // Remove placeholder and clean up spacing if no name provided
3039 - html = html.replace(/\{visitor_name\}/gi, '');
3040 - // Clean up any double spaces that might result
3041 - html = html.replace(/\s{2,}/g, ' ').trim();
2280 + // Show loading state with spinner
2281 + function setSubmissionState(loading) {
2282 + const submitButton = document.getElementById('email-submit-button');
2283 + const emailInput = document.getElementById('user-email');
2284 + const nameInput = document.getElementById('user-name');
2285 +
2286 + if (loading) {
2287 + isSubmitting = true;
2288 + if (submitButton) submitButton.disabled = true;
2289 + if (emailInput) emailInput.disabled = true;
2290 + if (nameInput) nameInput.disabled = true;
2291 +
2292 + // Store original content and add spinner
2293 + if (submitButton && !submitButton.getAttribute('data-original-html')) {
2294 + submitButton.setAttribute('data-original-html', submitButton.innerHTML);
2295 +
2296 + // Add loading spinner while keeping original text
2297 + const originalText = submitButton.textContent;
2298 + submitButton.innerHTML = `
2299 + <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
2300 + <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
2301 + <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
2302 + <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
2303 + </circle>
2304 + </svg>
2305 + ${originalText}
2306 + `;
2307 +
2308 + submitButton.style.opacity = '0.8';
2309 + }
2310 + } else {
2311 + isSubmitting = false;
2312 + if (submitButton) submitButton.disabled = false;
2313 + if (emailInput) emailInput.disabled = false;
2314 + if (nameInput) nameInput.disabled = false;
2315 +
2316 + // Restore original content
2317 + if (submitButton) {
2318 + const originalHtml = submitButton.getAttribute('data-original-html');
2319 + if (originalHtml) {
2320 + submitButton.innerHTML = originalHtml;
2321 + }
2322 + submitButton.style.opacity = '1';
2323 + }
2324 + }
3042 2325 }
3043 2326
3044 - messageContent.innerHTML = html;
3045 - }
3046 -
3047 - function setEmailSubmissionState(botId, loading) {
3048 - var submitButton = getElementDOM(botId, 'email-submit-button');
3049 - var emailInput = getElementDOM(botId, 'user-email');
3050 - var nameInput = getElementDOM(botId, 'user-name');
3051 -
3052 - if (loading) {
3053 - emailSubmittingState[botId] = true;
3054 - if (submitButton) submitButton.disabled = true;
3055 - if (emailInput) emailInput.disabled = true;
3056 - if (nameInput) nameInput.disabled = true;
3057 -
3058 - if (submitButton && !submitButton.getAttribute('data-original-html')) {
3059 - submitButton.setAttribute('data-original-html', submitButton.innerHTML);
3060 - const originalText = submitButton.textContent;
3061 - submitButton.innerHTML = `
3062 - <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
3063 - <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
3064 - <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
3065 - <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
3066 - </circle>
3067 - </svg>
3068 - ${originalText}
2327 + // Error display functions
2328 + function showEmailError(message) {
2329 + clearEmailError();
2330 +
2331 + const errorDiv = document.createElement('div');
2332 + errorDiv.className = 'email-error';
2333 + errorDiv.style.cssText = `
2334 + color: #e74c3c;
2335 + font-size: 12px;
2336 + margin-top: 8px;
2337 + padding: 4px 0;
2338 + animation: fadeInError 0.3s ease;
2339 + `;
2340 + errorDiv.textContent = message;
2341 +
2342 + // Add CSS animation if not already present
2343 + if (!document.getElementById('email-error-styles')) {
2344 + const style = document.createElement('style');
2345 + style.id = 'email-error-styles';
2346 + style.textContent = `
2347 + @keyframes fadeInError {
2348 + from { opacity: 0; transform: translateY(-5px); }
2349 + to { opacity: 1; transform: translateY(0); }
2350 + }
2351 + .email-input-shake {
2352 + animation: shake 0.5s ease-in-out;
2353 + }
2354 + @keyframes shake {
2355 + 0%, 100% { transform: translateX(0); }
2356 + 25% { transform: translateX(-5px); }
2357 + 75% { transform: translateX(5px); }
2358 + }
2359 + @keyframes spin {
2360 + from { transform: rotate(0deg); }
2361 + to { transform: rotate(360deg); }
2362 + }
2363 + .email-spinner {
2364 + display: inline-block;
2365 + vertical-align: middle;
2366 + }
3069 2367 `;
3070 - submitButton.style.opacity = '0.8';
2368 + document.head.appendChild(style);
3071 2369 }
3072 - } else {
3073 - emailSubmittingState[botId] = false;
3074 - if (submitButton) submitButton.disabled = false;
3075 - if (emailInput) emailInput.disabled = false;
3076 - if (nameInput) nameInput.disabled = false;
3077 -
3078 - if (submitButton) {
3079 - const originalHtml = submitButton.getAttribute('data-original-html');
3080 - if (originalHtml) {
3081 - submitButton.innerHTML = originalHtml;
3082 - }
3083 - submitButton.style.opacity = '1';
2370 +
2371 + emailForm.appendChild(errorDiv);
2372 +
2373 + // Add shake animation to inputs
2374 + const emailInput = document.getElementById('user-email');
2375 + const nameInput = document.getElementById('user-name');
2376 +
2377 + if (emailInput) {
2378 + emailInput.classList.add('email-input-shake');
2379 + setTimeout(() => {
2380 + emailInput.classList.remove('email-input-shake');
2381 + }, 500);
3084 2382 }
2383 +
2384 + if (nameInput) {
2385 + nameInput.classList.add('email-input-shake');
2386 + setTimeout(() => {
2387 + nameInput.classList.remove('email-input-shake');
2388 + }, 500);
2389 + }
3085 2390 }
3086 - }
3087 2391
3088 - function showEmailError(botId, message) {
3089 - clearEmailError(botId);
3090 -
3091 - var emailForm = getElementDOM(botId, 'email-collection-form');
3092 - if (!emailForm) return;
3093 -
3094 - const errorDiv = document.createElement('div');
3095 - errorDiv.className = 'email-error';
3096 - errorDiv.style.cssText = `
3097 - color: #e74c3c;
3098 - font-size: 12px;
3099 - margin-top: 8px;
3100 - padding: 4px 0;
3101 - animation: fadeInError 0.3s ease;
3102 - `;
3103 - errorDiv.textContent = message;
3104 - emailForm.appendChild(errorDiv);
3105 -
3106 - // Add shake animation to inputs
3107 - var emailInput = getElementDOM(botId, 'user-email');
3108 - var nameInput = getElementDOM(botId, 'user-name');
3109 -
3110 - if (emailInput) {
3111 - emailInput.classList.add('email-input-shake');
3112 - setTimeout(() => emailInput.classList.remove('email-input-shake'), 500);
3113 - }
3114 - if (nameInput) {
3115 - nameInput.classList.add('email-input-shake');
3116 - setTimeout(() => nameInput.classList.remove('email-input-shake'), 500);
3117 - }
3118 - }
3119 -
3120 - function clearEmailError(botId) {
3121 - var emailForm = getElementDOM(botId, 'email-collection-form');
3122 - if (emailForm) {
2392 + function clearEmailError() {
3123 2393 const existingErrors = emailForm.querySelectorAll('.email-error');
3124 2394 existingErrors.forEach(error => error.remove());
3125 2395 }
3126 - }
3127 2396
3128 - // Resolve email state using server-side data when available, AJAX fallback otherwise
3129 - function resolveEmailState(botId) {
3130 - if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
3131 - if (mxchatChat.initial_email_state.show_email_form) {
3132 - showEmailFormForBot(botId);
3133 - } else {
3134 - showChatContainerForBot(botId);
2397 + // MAIN FORM SUBMIT HANDLER - This is the critical fix
2398 + //console.log('Setting up form submit handler...');
2399 +
2400 + // Remove any existing event listeners first
2401 + emailForm.removeEventListener('submit', handleFormSubmit);
2402 +
2403 + // Add the form submit handler
2404 + emailForm.addEventListener('submit', handleFormSubmit);
2405 +
2406 + function handleFormSubmit(event) {
2407 + //console.log('Form submit handler triggered!');
2408 + event.preventDefault();
2409 + event.stopPropagation();
2410 +
2411 + // Prevent double submission
2412 + if (isSubmitting) {
2413 + //console.log('Already submitting, ignoring...');
2414 + return false;
3135 2415 }
3136 - } else {
3137 - checkSessionAndEmailForBot(botId);
3138 - }
3139 - }
3140 2416
3141 - function checkSessionAndEmailForBot(botId) {
3142 - const sessionId = MxChatInstances.ensureSession(botId);
2417 + const userEmail = document.getElementById('user-email').value.trim();
2418 + const nameInput = document.getElementById('user-name');
2419 + const userName = nameInput ? nameInput.value.trim() : '';
2420 + const sessionId = getChatSession();
3143 2421
3144 - // Hide both panels while we check — show loader instead
3145 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3146 - var chatContainer = getElementDOM(botId, 'chat-container');
3147 - if (emailBlocker) emailBlocker.style.display = 'none';
3148 - if (chatContainer) chatContainer.style.display = 'none';
3149 - showInitLoader(botId);
2422 + //console.log('Form data:', { userEmail, userName, sessionId });
3150 2423
3151 - fetch(mxchatChat.ajax_url, {
3152 - method: 'POST',
3153 - headers: {
3154 - 'Content-Type': 'application/x-www-form-urlencoded',
3155 - },
3156 - body: new URLSearchParams({
3157 - action: 'mxchat_check_email_provided',
3158 - session_id: sessionId,
3159 - nonce: mxchatChat.nonce,
3160 - })
3161 - })
3162 - .then((response) => {
3163 - if (!response.ok) {
3164 - throw new Error(`HTTP error! status: ${response.status}`);
2424 + // Validate email before submission
2425 + if (!userEmail) {
2426 + showEmailError('Please enter your email address.');
2427 + return false;
3165 2428 }
3166 - return response.json();
3167 - })
3168 - .then((data) => {
3169 - if (data.success) {
3170 - if (data.data.logged_in || data.data.email) {
3171 - showChatContainerForBot(botId);
3172 - } else {
3173 - showEmailFormForBot(botId);
3174 - }
3175 - } else {
3176 - showEmailFormForBot(botId);
2429 +
2430 + if (!isValidEmail(userEmail)) {
2431 + showEmailError('Please enter a valid email address.');
2432 + return false;
3177 2433 }
3178 - })
3179 - .catch((error) => {
3180 - showEmailFormForBot(botId);
3181 - });
3182 - }
3183 2434
3184 - // Event delegation for email form submission
3185 - $(document).on('submit', '.email-collection-form', function(e) {
3186 - e.preventDefault();
3187 - e.stopPropagation();
2435 + // Validate name if field exists
2436 + if (nameInput && !isValidName(userName)) {
2437 + showEmailError('Please enter a valid name (2-100 characters).');
2438 + return false;
2439 + }
3188 2440
3189 - var botId = getBotIdFromElement(this);
2441 + // Clear any existing errors
2442 + clearEmailError();
2443 + setSubmissionState(true);
3190 2444
3191 - // Prevent double submission
3192 - if (emailSubmittingState[botId]) {
3193 - return false;
3194 - }
2445 + //console.log('Sending AJAX request...');
3195 2446
3196 - var emailInput = getElementDOM(botId, 'user-email');
3197 - var nameInput = getElementDOM(botId, 'user-name');
3198 - var userEmail = emailInput ? emailInput.value.trim() : '';
3199 - var userName = nameInput ? nameInput.value.trim() : '';
3200 - var sessionId = MxChatInstances.ensureSession(botId);
2447 + // Prepare form data with optional name
2448 + const formData = new URLSearchParams({
2449 + action: 'mxchat_handle_save_email_and_response',
2450 + email: userEmail,
2451 + session_id: sessionId,
2452 + nonce: mxchatChat.nonce,
2453 + });
3201 2454
3202 - // Validate email
3203 - if (!userEmail) {
3204 - showEmailError(botId, 'Please enter your email address.');
3205 - return false;
3206 - }
2455 + // Add name to form data if provided
2456 + if (userName) {
2457 + formData.append('name', userName);
2458 + }
3207 2459
3208 - if (!isValidEmailAddress(userEmail)) {
3209 - showEmailError(botId, 'Please enter a valid email address.');
3210 - return false;
3211 - }
2460 + fetch(mxchatChat.ajax_url, {
2461 + method: 'POST',
2462 + headers: {
2463 + 'Content-Type': 'application/x-www-form-urlencoded',
2464 + },
2465 + body: formData
2466 + })
2467 + .then((response) => {
2468 + //console.log('Response received:', response);
2469 + if (!response.ok) {
2470 + throw new Error(`HTTP error! status: ${response.status}`);
2471 + }
2472 + return response.json();
2473 + })
2474 + .then((data) => {
2475 + //console.log('Response data:', data);
2476 + setSubmissionState(false);
2477 +
2478 + if (data.success) {
2479 + // Show chat immediately
2480 + showChatContainer();
3212 2481
3213 - // Validate name if field exists and has content
3214 - if (nameInput && userName && !isValidNameInput(userName)) {
3215 - showEmailError(botId, 'Please enter a valid name (2-100 characters).');
3216 - return false;
2482 + // Handle bot response if provided
2483 + if (data.message && typeof appendMessage === 'function') {
2484 + setTimeout(() => {
2485 + appendMessage('bot', data.message);
2486 + if (typeof scrollToBottom === 'function') {
2487 + scrollToBottom();
2488 + }
2489 + }, 100);
2490 + }
2491 + } else {
2492 + showEmailError(data.message || 'Failed to save email. Please try again.');
2493 + }
2494 + })
2495 + .catch((error) => {
2496 + console.error('Email submission error:', error);
2497 + setSubmissionState(false);
2498 + showEmailError('An error occurred. Please try again.');
2499 + });
2500 +
2501 + return false; // Extra prevention
3217 2502 }
3218 2503
3219 - clearEmailError(botId);
3220 - setEmailSubmissionState(botId, true);
2504 + // Real-time email validation
2505 + const emailInput = document.getElementById('user-email');
2506 + if (emailInput) {
2507 + let validationTimeout;
2508 +
2509 + emailInput.addEventListener('input', function() {
2510 + // Clear previous validation timeout
2511 + if (validationTimeout) {
2512 + clearTimeout(validationTimeout);
2513 + }
2514 +
2515 + // Debounce validation
2516 + validationTimeout = setTimeout(() => {
2517 + const email = this.value.trim();
2518 + clearEmailError();
2519 +
2520 + if (email && !isValidEmail(email)) {
2521 + showEmailError('Please enter a valid email address.');
2522 + }
2523 + }, 500);
2524 + });
3221 2525
3222 - // Prepare form data
3223 - const formData = new URLSearchParams({
3224 - action: 'mxchat_handle_save_email_and_response',
3225 - email: userEmail,
3226 - session_id: sessionId,
3227 - nonce: mxchatChat.nonce,
3228 - });
3229 -
3230 - if (userName) {
3231 - formData.append('name', userName);
2526 + // Handle Enter key
2527 + emailInput.addEventListener('keypress', function(e) {
2528 + if (e.key === 'Enter' && !isSubmitting) {
2529 + e.preventDefault();
2530 + emailForm.dispatchEvent(new Event('submit'));
2531 + }
2532 + });
3232 2533 }
3233 2534
3234 - fetch(mxchatChat.ajax_url, {
3235 - method: 'POST',
3236 - headers: {
3237 - 'Content-Type': 'application/x-www-form-urlencoded',
3238 - },
3239 - body: formData
3240 - })
3241 - .then((response) => {
3242 - if (!response.ok) {
3243 - throw new Error(`HTTP error! status: ${response.status}`);
3244 - }
3245 - return response.json();
3246 - })
3247 - .then((data) => {
3248 - setEmailSubmissionState(botId, false);
3249 -
3250 - if (data.success) {
3251 - showChatContainerForBot(botId);
3252 -
3253 - // Replace {visitor_name} placeholder in intro message with actual name
3254 - if (userName) {
3255 - replaceVisitorNamePlaceholder(botId, userName);
3256 - } else {
3257 - // Remove placeholder if no name provided
3258 - replaceVisitorNamePlaceholder(botId, '');
2535 + // Real-time name validation
2536 + const nameInput = document.getElementById('user-name');
2537 + if (nameInput) {
2538 + let nameValidationTimeout;
2539 +
2540 + nameInput.addEventListener('input', function() {
2541 + // Clear previous validation timeout
2542 + if (nameValidationTimeout) {
2543 + clearTimeout(nameValidationTimeout);
3259 2544 }
2545 +
2546 + // Debounce validation
2547 + nameValidationTimeout = setTimeout(() => {
2548 + const name = this.value.trim();
2549 + clearEmailError();
2550 +
2551 + if (name && !isValidName(name)) {
2552 + showEmailError('Name must be between 2 and 100 characters.');
2553 + }
2554 + }, 500);
2555 + });
3260 2556
3261 - if (data.message && typeof appendMessage === 'function') {
3262 - setTimeout(() => {
3263 - appendMessage('bot', data.message, '', [], false, botId);
3264 - if (typeof scrollToBottom === 'function') {
3265 - scrollToBottom(botId);
3266 - }
3267 - }, 100);
2557 + // Handle Enter key
2558 + nameInput.addEventListener('keypress', function(e) {
2559 + if (e.key === 'Enter' && !isSubmitting) {
2560 + e.preventDefault();
2561 + emailForm.dispatchEvent(new Event('submit'));
3268 2562 }
3269 - } else {
3270 - showEmailError(botId, data.message || 'Failed to save email. Please try again.');
3271 - }
3272 - })
3273 - .catch((error) => {
3274 - setEmailSubmissionState(botId, false);
3275 - showEmailError(botId, 'An error occurred. Please try again.');
3276 - });
3277 -
3278 - return false;
3279 - });
3280 -
3281 - // Real-time email validation using event delegation
3282 - $(document).on('input', '.mxchat-email-input', function() {
3283 - var botId = getBotIdFromElement(this);
3284 - var $input = $(this);
3285 -
3286 - // Clear previous timeout
3287 - clearTimeout($input.data('validationTimeout'));
3288 -
3289 - // Debounce validation
3290 - var timeout = setTimeout(() => {
3291 - var email = this.value.trim();
3292 - clearEmailError(botId);
3293 -
3294 - if (email && !isValidEmailAddress(email)) {
3295 - showEmailError(botId, 'Please enter a valid email address.');
3296 - }
3297 - }, 500);
3298 -
3299 - $input.data('validationTimeout', timeout);
3300 - });
3301 -
3302 - // Handle Enter key in email input
3303 - $(document).on('keypress', '.mxchat-email-input', function(e) {
3304 - if (e.key === 'Enter') {
3305 - e.preventDefault();
3306 - var botId = getBotIdFromElement(this);
3307 - if (!emailSubmittingState[botId]) {
3308 - $(this).closest('.email-collection-form').submit();
3309 - }
2563 + });
3310 2564 }
3311 - });
3312 2565
3313 - // Handle Enter key in name input
3314 - $(document).on('keypress', '.mxchat-name-input', function(e) {
3315 - if (e.key === 'Enter') {
3316 - e.preventDefault();
3317 - var botId = getBotIdFromElement(this);
3318 - if (!emailSubmittingState[botId]) {
3319 - $(this).closest('.email-collection-form').submit();
2566 + // Initial state check
2567 + if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
2568 + //console.log('Using server-provided email state:', mxchatChat.initial_email_state);
2569 +
2570 + const emailState = mxchatChat.initial_email_state;
2571 + if (emailState.show_email_form) {
2572 + showEmailForm();
2573 + } else {
2574 + showChatContainer();
3320 2575 }
2576 + } else {
2577 + // Check email status via AJAX
2578 + setTimeout(checkSessionAndEmail, 100);
3321 2579 }
3322 - });
3323 2580
3324 - // Initialize email check for all bot instances
3325 - // For floating bots: defer until widget is opened (zero passive AJAX)
3326 - // For embedded bots: check immediately since the form is visible
3327 - $('.mxchat-chatbot-wrapper').each(function() {
3328 - var botId = $(this).data('bot-id') || 'default';
3329 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3330 -
3331 - if (emailBlocker) {
3332 - if (isEmbeddedBot(botId)) {
3333 - // Embedded bots are always visible — check now
3334 - resolveEmailState(botId);
3335 - }
3336 - // Floating bots: handled in the widget open handler
3337 - } else if (isEmbeddedBot(botId)) {
3338 - // Embedded bot, no email collection — load history with loader
3339 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3340 - if (chatPersistenceEnabled) {
3341 - MxChatInstances.ensureSession(botId);
3342 - showChatContainerForBot(botId);
3343 - }
2581 + // Check if email exists for the current session
2582 + function checkSessionAndEmail() {
2583 + const sessionId = getChatSession();
2584 +
2585 + fetch(mxchatChat.ajax_url, {
2586 + method: 'POST',
2587 + headers: {
2588 + 'Content-Type': 'application/x-www-form-urlencoded',
2589 + },
2590 + body: new URLSearchParams({
2591 + action: 'mxchat_check_email_provided',
2592 + session_id: sessionId,
2593 + nonce: mxchatChat.nonce,
2594 + })
2595 + })
2596 + .then((response) => {
2597 + if (!response.ok) {
2598 + throw new Error(`HTTP error! status: ${response.status}`);
2599 + }
2600 + return response.json();
2601 + })
2602 + .then((data) => {
2603 + if (data.success) {
2604 + if (data.data.logged_in || data.data.email) {
2605 + showChatContainer();
2606 + } else {
2607 + showEmailForm();
2608 + }
2609 + } else {
2610 + // On error, default to showing email form
2611 + showEmailForm();
2612 + }
2613 + })
2614 + .catch((error) => {
2615 + console.warn('Email check failed, defaulting to email form:', error);
2616 + showEmailForm();
2617 + });
3344 2618 }
3345 - });
2619 +
2620 + } else {
2621 + console.error('Email collection is enabled but essential elements are missing:', {
2622 + emailForm: !!emailForm,
2623 + emailBlocker: !!emailBlocker,
2624 + chatbotWrapper: !!chatbotWrapper
2625 + });
2626 + }
2627 +} else {
2628 + //console.log('Email collection is disabled');
3346 2629 }
3347 2630
3348 - // Open chatbot when pre-chat message is clicked - use class selector for multi-instance
3349 - $(document).on('click', '.pre-chat-message', function() {
3350 - var botId = getBotIdFromElement(this);
3351 - var $chatbot = getElement(botId, 'floating-chatbot');
3352 - if ($chatbot.hasClass('hidden')) {
3353 - $chatbot.removeClass('hidden').addClass('visible');
3354 - getElement(botId, 'floating-chatbot-button').addClass('hidden');
3355 - handlePreChatDismissal(botId);
2631 + // Open chatbot when pre-chat message is clicked
2632 + $(document).on('click', '#pre-chat-message', function() {
2633 + var chatbot = $('#floating-chatbot');
2634 + if (chatbot.hasClass('hidden')) {
2635 + chatbot.removeClass('hidden').addClass('visible');
2636 + $('#floating-chatbot-button').addClass('hidden');
2637 + $('#pre-chat-message').fadeOut(250); // Hide pre-chat message
3356 2638 disableScroll(); // Disable scroll when chatbot opens
3357 -
3358 - // Load chat history for returning visitors (persistence)
3359 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3360 - if (chatPersistenceEnabled) {
3361 - MxChatInstances.ensureSession(botId);
3362 - }
3363 -
3364 - // Deferred email check — only on first widget open
3365 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3366 - var instance = MxChatInstances.get(botId);
3367 - if (emailBlocker && !instance.emailCheckDone) {
3368 - instance.emailCheckDone = true;
3369 - resolveEmailState(botId);
3370 - } else if (!emailBlocker) {
3371 - showChatContainerForBot(botId);
3372 - }
3373 2639 }
3374 2640 });
3375 2641
3376 - // Legacy duplicate close handler removed — handled by single event delegation above
2642 + var closeButton = document.querySelector('.close-pre-chat-message');
2643 + if (closeButton) {
2644 + closeButton.addEventListener('click', function() {
2645 + $('#pre-chat-message').fadeOut(200); // Hide the message
3377 2646
2647 + // Send an AJAX request to set the transient flag for 24 hours
2648 + $.ajax({
2649 + url: mxchatChat.ajax_url,
2650 + type: 'POST',
2651 + data: {
2652 + action: 'mxchat_dismiss_pre_chat_message',
2653 + _ajax_nonce: mxchatChat.nonce
2654 + },
2655 + success: function() {
2656 + //console.log('Pre-chat message dismissed for 24 hours.');
3378 2657
3379 -function hasQuickQuestions(botId) {
3380 - botId = botId || 'default';
3381 - var questionsContainer = getElementDOM(botId, 'mxchat-popular-questions');
3382 - if (!questionsContainer) return false;
3383 - const questionButtons = questionsContainer.querySelectorAll('.mxchat-popular-question');
2658 + // Ensure the message is hidden after dismissal
2659 + $('#pre-chat-message').hide();
2660 + },
2661 + error: function() {
2662 + ////console.error('Failed to dismiss pre-chat message.');
2663 + }
2664 + });
2665 + });
2666 + }
2667 +
2668 +
2669 +function hasQuickQuestions() {
2670 + const questionButtons = document.querySelectorAll('#mxchat-popular-questions .mxchat-popular-question');
3384 2671 return questionButtons.length > 0;
3385 2672 }
3386 2673
3387 -/**
3388 - * Check if a bot is embedded (not floating)
3389 - * Embedded bots don't have a .floating-chatbot wrapper
3390 - */
3391 -function isEmbeddedBot(botId) {
3392 - botId = botId || 'default';
3393 - var floatingWrapper = document.getElementById('floating-chatbot-' + botId);
3394 - return !floatingWrapper;
3395 -}
3396 -
3397 -function collapseQuickQuestions(botId) {
3398 - botId = botId || 'default';
3399 - const questionsContainer = getElementDOM(botId, 'mxchat-popular-questions');
3400 - if (questionsContainer && hasQuickQuestions(botId)) {
2674 +function collapseQuickQuestions() {
2675 + const questionsContainer = document.getElementById('mxchat-popular-questions');
2676 + if (questionsContainer && hasQuickQuestions()) {
3401 2677 questionsContainer.classList.add('collapsed');
3402 2678 questionsContainer.classList.add('has-been-collapsed');
3403 2679 try {
3404 - sessionStorage.setItem('mxchat_questions_collapsed_' + botId, 'true');
3405 - sessionStorage.setItem('mxchat_questions_has_been_collapsed_' + botId, 'true');
2680 + sessionStorage.setItem('mxchat_questions_collapsed', 'true');
2681 + sessionStorage.setItem('mxchat_questions_has_been_collapsed', 'true');
3406 2682 } catch (e) {
3407 2683 // Ignore if sessionStorage is not available
3408 2684 }
3409 2685 }
@@ -3408,15 +2684,14 @@
3408 2684 }
3409 2685 }
3410 2686 }
3411 2687
3412 -function expandQuickQuestions(botId) {
3413 - botId = botId || 'default';
3414 - const questionsContainer = getElementDOM(botId, 'mxchat-popular-questions');
3415 - if (questionsContainer && hasQuickQuestions(botId)) {
2688 +function expandQuickQuestions() {
2689 + const questionsContainer = document.getElementById('mxchat-popular-questions');
2690 + if (questionsContainer && hasQuickQuestions()) {
3416 2691 questionsContainer.classList.remove('collapsed');
3417 2692 try {
3418 - sessionStorage.setItem('mxchat_questions_collapsed_' + botId, 'false');
2693 + sessionStorage.setItem('mxchat_questions_collapsed', 'false');
3419 2694 } catch (e) {
3420 2695 // Ignore if sessionStorage is not available
3421 2696 }
3422 2697 }
@@ -3421,24 +2696,18 @@
3421 2696 }
3422 2697 }
3423 2698 }
3424 2699
3425 -function checkQuickQuestionsState(botId) {
3426 - botId = botId || 'default';
3427 - if (!hasQuickQuestions(botId)) {
2700 +function checkQuickQuestionsState() {
2701 + if (!hasQuickQuestions()) {
3428 2702 return; // Don't do anything if no questions exist
3429 2703 }
3430 -
3431 - // Skip restoring collapsed state for embedded bots - they should always start expanded
3432 - if (isEmbeddedBot(botId)) {
3433 - return;
3434 - }
3435 -
2704 +
3436 2705 try {
3437 - const isCollapsed = sessionStorage.getItem('mxchat_questions_collapsed_' + botId);
3438 - const hasBeenCollapsed = sessionStorage.getItem('mxchat_questions_has_been_collapsed_' + botId);
3439 -
3440 - const questionsContainer = getElementDOM(botId, 'mxchat-popular-questions');
2706 + const isCollapsed = sessionStorage.getItem('mxchat_questions_collapsed');
2707 + const hasBeenCollapsed = sessionStorage.getItem('mxchat_questions_has_been_collapsed');
2708 +
2709 + const questionsContainer = document.getElementById('mxchat-popular-questions');
3441 2710 if (questionsContainer) {
3442 2711 if (hasBeenCollapsed === 'true') {
3443 2712 questionsContainer.classList.add('has-been-collapsed');
3444 2713 }
@@ -3450,37 +2719,33 @@
3450 2719 // Ignore if sessionStorage is not available
3451 2720 }
3452 2721 }
3453 2722
3454 -// Global delegation for dynamically added links as fallback
3455 -// Use class selector for multi-instance support
3456 -$(document).on('click', '.chat-box a[href]:not([data-tracked])', function(e) {
2723 +// NEW: Global delegation for dynamically added links as fallback
2724 +$(document).on('click', '#chat-box a[href]:not([data-tracked])', function(e) {
3457 2725 const $link = $(this);
3458 2726 const messageDiv = $link.closest('.bot-message, .agent-message');
3459 -
2727 +
3460 2728 // Only process bot/agent message links
3461 2729 if (messageDiv.length > 0) {
3462 2730 const originalHref = $link.attr('href');
3463 -
2731 +
3464 2732 if (originalHref && (originalHref.startsWith('http://') || originalHref.startsWith('https://'))) {
3465 2733 e.preventDefault();
3466 2734 e.stopPropagation();
3467 -
2735 +
3468 2736 // Mark as tracked
3469 2737 $link.attr('data-tracked', 'true');
3470 -
3471 - // Get bot ID from the chat box context
3472 - var botId = getBotIdFromElement(this);
3473 -
2738 +
3474 2739 // Get message context from the message div
3475 2740 const messageText = messageDiv.text().substring(0, 200);
3476 -
2741 +
3477 2742 $.ajax({
3478 2743 url: mxchatChat.ajax_url,
3479 2744 type: 'POST',
3480 2745 data: {
3481 2746 action: 'mxchat_track_url_click',
3482 - session_id: getChatSession(botId),
2747 + session_id: getChatSession(),
3483 2748 url: originalHref,
3484 2749 message_context: messageText,
3485 2750 nonce: mxchatChat.nonce
3486 2751 },
@@ -3491,57 +2756,44 @@
3491 2756 window.location.href = originalHref;
3492 2757 }
3493 2758 }
3494 2759 });
3495 -
2760 +
3496 2761 return false;
3497 2762 }
3498 2763 }
3499 2764 });
3500 2765
3501 - // ====================================
3502 - // MAIN INITIALIZATION
3503 - // ====================================
2766 +
2767 +
2768 +
2769 +// ====================================
2770 +// MAIN INITIALIZATION
2771 +// ====================================
3504 2772
3505 - // Initialize all chatbot instances on the page
3506 - initializeAllInstances();
2773 +if ($('#floating-chatbot').hasClass('hidden')) {
2774 + $('#floating-chatbot-button').removeClass('hidden');
2775 +}
2776 +// Initialize when document is ready
2777 +setFullHeight();
2778 +initializeChatVisibility();
2779 +loadChatHistory();
2780 +trackOriginatingPage();
3507 2781
3508 - // Legacy initialization for single bot compatibility
3509 - $('.floating-chatbot.hidden').each(function() {
3510 - var botId = getBotIdFromElement(this);
3511 - getElement(botId, 'floating-chatbot-button').removeClass('hidden');
3512 - });
3513 -
3514 - // Initialize when document is ready
3515 - setFullHeight();
3516 -
3517 - // Note: trackOriginatingPage() and loadChatHistory() are now deferred
3518 - // until the user's first interaction via MxChatInstances.ensureSession()
3519 -
3520 - // Initialize chat visibility for all instances
3521 - $('.mxchat-chatbot-wrapper').each(function() {
3522 - var botId = $(this).data('bot-id') || 'default';
3523 - initializeChatVisibility(botId);
3524 - });
3525 -
3526 - // Make functions globally available for add-ons
3527 - window.hasQuickQuestions = hasQuickQuestions;
3528 - window.collapseQuickQuestions = collapseQuickQuestions;
3529 - window.appendMessage = appendMessage;
3530 - window.appendThinkingMessage = appendThinkingMessage;
3531 - window.scrollToBottom = scrollToBottom;
3532 - window.scrollElementToTop = scrollElementToTop;
3533 - window.replaceLastMessage = replaceLastMessage;
3534 - window.callMxChat = callMxChat;
3535 - window.callMxChatStream = callMxChatStream;
3536 - window.shouldUseStreaming = shouldUseStreaming;
3537 - window.getChatSession = getChatSession;
3538 - window.getPageContext = getPageContext;
3539 - window.updateStreamingMessage = updateStreamingMessage;
3540 - window.MxChatInstances = MxChatInstances;
3541 - window.getElement = getElement;
3542 - window.getElementDOM = getElementDOM;
3543 - window.getBotIdFromElement = getBotIdFromElement;
2782 +// Make functions globally available for add-ons
2783 +window.hasQuickQuestions = hasQuickQuestions;
2784 +window.collapseQuickQuestions = collapseQuickQuestions;
2785 +window.appendMessage = appendMessage;
2786 +window.appendThinkingMessage = appendThinkingMessage;
2787 +window.scrollToBottom = scrollToBottom;
2788 +window.scrollElementToTop = scrollElementToTop;
2789 +window.replaceLastMessage = replaceLastMessage;
2790 +window.callMxChat = callMxChat;
2791 +window.callMxChatStream = callMxChatStream;
2792 +window.shouldUseStreaming = shouldUseStreaming;
2793 +window.getChatSession = getChatSession;
2794 +window.getPageContext = getPageContext;
2795 +window.updateStreamingMessage = updateStreamingMessage;
3544 2796
3545 2797 }); // End of jQuery ready
3546 2798
3547 2799