PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.3.3
MxChat – AI Chatbot & Content Generation for WordPress v2.3.3
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
mxchat-basic / js / chat-script.js

chat-script.js in MxChat – AI Chatbot & Content Generation for WordPress 2.3.3, at js/chat-script.js

2,339 lines 86.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 jQuery(document).ready(function($) {
2
3 // ====================================
4 // GLOBAL VARIABLES & CONFIGURATION
5 // ====================================
6 const toolbarIconColor = mxchatChat.toolbar_icon_color || '#212121';
7
8 // Initialize color settings
9 var userMessageBgColor = mxchatChat.user_message_bg_color;
10 var userMessageFontColor = mxchatChat.user_message_font_color;
11 var botMessageBgColor = mxchatChat.bot_message_bg_color;
12 var botMessageFontColor = mxchatChat.bot_message_font_color;
13 var liveAgentMessageBgColor = mxchatChat.live_agent_message_bg_color;
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;
25
26
27 // ====================================
28 // SESSION MANAGEMENT
29 // ====================================
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
50 function getCookie(name) {
51 let value = "; " + document.cookie;
52 let parts = value.split("; " + name + "=");
53 if (parts.length == 2) return parts.pop().split(";").shift();
54 }
55
56 function generateSessionId() {
57 return 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
58 }
59
60 // ====================================
61 // CONTEXTUAL AWARENESS FUNCTIONALITY
62 // ====================================
63
64 function getPageContext() {
65 // Check if contextual awareness is enabled
66 if (mxchatChat.contextual_awareness_toggle !== 'on') {
67 return null;
68 }
69
70 // Get page URL
71 const pageUrl = window.location.href;
72
73 // Get page title
74 const pageTitle = document.title || '';
75
76 // Get main content from the page
77 let pageContent = '';
78
79 // Try to get content from common content areas
80 const contentSelectors = [
81 'main',
82 '[role="main"]',
83 '.content',
84 '.main-content',
85 '.post-content',
86 '.entry-content',
87 '.page-content',
88 'article',
89 '#content',
90 '#main'
91 ];
92
93 let contentElement = null;
94 for (const selector of contentSelectors) {
95 contentElement = document.querySelector(selector);
96 if (contentElement) {
97 break;
98 }
99 }
100
101 // If no specific content area found, use body but exclude header, footer, nav, sidebar
102 if (!contentElement) {
103 contentElement = document.body;
104 }
105
106 if (contentElement) {
107 // Clone the element to avoid modifying the original
108 const clone = contentElement.cloneNode(true);
109
110 // Remove unwanted elements
111 const unwantedSelectors = [
112 'header',
113 'footer',
114 'nav',
115 '.navigation',
116 '.sidebar',
117 '.widget',
118 '.menu',
119 'script',
120 'style',
121 '.comments',
122 '#comments',
123 '.breadcrumb',
124 '.breadcrumbs',
125 '#floating-chatbot',
126 '#floating-chatbot-button',
127 '.mxchat',
128 '[class*="chat"]',
129 '[id*="chat"]'
130 ];
131
132 unwantedSelectors.forEach(selector => {
133 const elements = clone.querySelectorAll(selector);
134 elements.forEach(el => el.remove());
135 });
136
137 // NEW: Extract MxChat context data attributes before getting text content
138 const contextData = [];
139 clone.querySelectorAll('[data-mxchat-context]').forEach(el => {
140 const contextValue = el.dataset.mxchatContext;
141 if (contextValue && contextValue.trim()) {
142 contextData.push(contextValue);
143 }
144 });
145
146 // Get text content and clean it up
147 pageContent = clone.textContent || clone.innerText || '';
148
149 // Add context data to page content if any were found
150 if (contextData.length > 0) {
151 pageContent += '\n\nAdditional Context:\n' + contextData.join('\n');
152 }
153
154 // Clean up whitespace and limit length
155 pageContent = pageContent
156 .replace(/\s+/g, ' ')
157 .trim()
158 .substring(0, 3000); // Limit to 3000 characters to avoid token limits
159 }
160
161 // Only return context if we have meaningful content
162 if (!pageContent || pageContent.length < 50) {
163 return null;
164 }
165
166 return {
167 url: pageUrl,
168 title: pageTitle,
169 content: pageContent
170 };
171 }
172
173 // ====================================
174 // CORE CHAT FUNCTIONALITY
175 // ====================================
176 // Update your existing sendMessage function
177 function sendMessage() {
178 var message = $('#chat-input').val();
179
180 // ADD PROMPT HOOK HERE
181 if (typeof customMxChatFilter === 'function') {
182 message = customMxChatFilter(message, "prompt");
183 }
184
185 if (message) {
186 appendMessage("user", message);
187 $('#chat-input').val('');
188 $('#chat-input').css('height', 'auto');
189
190 if (hasQuickQuestions()) {
191 collapseQuickQuestions();
192 }
193 appendThinkingMessage();
194 scrollToBottom();
195
196 const currentModel = mxchatChat.model || 'gpt-4o';
197
198 // Check if streaming is enabled AND supported for this model
199 if (shouldUseStreaming(currentModel)) {
200 callMxChatStream(message, function(response) {
201 $('.bot-message.temporary-message').removeClass('temporary-message');
202 });
203 } else {
204 callMxChat(message, function(response) {
205 replaceLastMessage("bot", response);
206 });
207 }
208 }
209 }
210
211 // Update your existing sendMessageToChatbot function
212 function sendMessageToChatbot(message) {
213 // ADD PROMPT HOOK HERE
214 if (typeof customMxChatFilter === 'function') {
215 message = customMxChatFilter(message, "prompt");
216 }
217
218 var sessionId = getChatSession();
219
220 if (hasQuickQuestions()) {
221 collapseQuickQuestions();
222 }
223 appendThinkingMessage();
224 scrollToBottom();
225
226 const currentModel = mxchatChat.model || 'gpt-4o';
227
228 // Check if streaming is enabled AND supported for this model
229 if (shouldUseStreaming(currentModel)) {
230 callMxChatStream(message, function(response) {
231 $('.bot-message.temporary-message').removeClass('temporary-message');
232 });
233 } else {
234 callMxChat(message, function(response) {
235 $('.temporary-message').remove();
236 replaceLastMessage("bot", response);
237 });
238 }
239 }
240
241 // Updated shouldUseStreaming function with debugging
242 function shouldUseStreaming(model) {
243 // Check if streaming is enabled in settings (using your toggle naming pattern)
244 const streamingEnabled = mxchatChat.enable_streaming_toggle === 'on';
245
246 // Check if model supports streaming
247 const streamingSupported = isStreamingSupported(model);
248
249
250 // Only use streaming if both enabled and supported
251 return streamingEnabled && streamingSupported;
252 }
253
254 function callMxChat(message, callback) {
255 // Get page context if contextual awareness is enabled
256 const pageContext = getPageContext();
257
258 // Prepare AJAX data
259 const ajaxData = {
260 action: 'mxchat_handle_chat_request',
261 message: message,
262 session_id: getChatSession(),
263 nonce: mxchatChat.nonce
264 };
265
266 // Add page context if available
267 if (pageContext) {
268 ajaxData.page_context = JSON.stringify(pageContext);
269 }
270
271 // CHECK FOR VISION FLAGS AND ADD THEM
272 if (window.mxchatVisionProcessed) {
273 ajaxData.vision_processed = true;
274 ajaxData.original_user_message = window.mxchatOriginalMessage || message;
275 ajaxData.vision_images_count = window.mxchatVisionImagesCount || 0;
276 // Clear the flags after use
277 window.mxchatVisionProcessed = false;
278 window.mxchatOriginalMessage = null;
279 window.mxchatVisionImagesCount = 0;
280 }
281
282 $.ajax({
283 url: mxchatChat.ajax_url,
284 type: 'POST',
285 dataType: 'json',
286 data: ajaxData,
287 success: function(response) {
288 // Log the full response for debugging
289 //console.log("API Response:", response);
290
291 // First check if this is a successful response by looking for text, html, or message fields
292 // This preserves compatibility with your server response format
293 if (response.text !== undefined || response.html !== undefined || response.message !== undefined ||
294 (response.success === true && response.data && response.data.status === 'waiting_for_agent')) {
295
296 // Handle successful response - this is your original success handling code
297
298 // Existing chat mode check
299 if (response.chat_mode) {
300 updateChatModeIndicator(response.chat_mode);
301 }
302 else if (response.fallbackResponse && response.fallbackResponse.chat_mode) {
303 updateChatModeIndicator(response.fallbackResponse.chat_mode);
304 }
305
306 // Add PDF filename handling
307 if (response.data && response.data.filename) {
308 showActivePdf(response.data.filename);
309 activePdfFile = response.data.filename;
310 }
311
312 // Add redirect check here
313 if (response.redirect_url) {
314 let responseText = response.text || '';
315 if (responseText) {
316 replaceLastMessage("bot", responseText);
317 }
318 setTimeout(() => {
319 window.location.href = response.redirect_url;
320 }, 1500);
321 return;
322 }
323
324 // Check for live agent response
325 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
326 updateChatModeIndicator('agent');
327 return;
328 }
329
330 // Handle other responses
331 let responseText = response.text || '';
332 let responseHtml = response.html || '';
333 let responseMessage = response.message || '';
334
335 if (responseText === 'You are now chatting with the AI chatbot.') {
336 updateChatModeIndicator('ai');
337 }
338
339 // Handle the message and show notification if chat is hidden
340 if (responseText || responseHtml || responseMessage) {
341
342 // ADD RESPONSE HOOKS HERE - BEFORE DISPLAYING
343 if (responseText && typeof customMxChatFilter === 'function') {
344 responseText = customMxChatFilter(responseText, "response");
345 }
346 if (responseMessage && typeof customMxChatFilter === 'function') {
347 responseMessage = customMxChatFilter(responseMessage, "response");
348 }
349
350 // Update the messages as before
351 if (responseText && responseHtml) {
352 replaceLastMessage("bot", responseText, responseHtml);
353 } else if (responseText) {
354 replaceLastMessage("bot", responseText);
355 } else if (responseHtml) {
356 replaceLastMessage("bot", "", responseHtml);
357 } else if (responseMessage) {
358 replaceLastMessage("bot", responseMessage);
359 }
360
361 // Check if chat is hidden and show notification
362 if ($('#floating-chatbot').hasClass('hidden')) {
363 const badge = $('#chat-notification-badge');
364 if (badge.length) {
365 badge.show();
366 }
367 }
368 } else {
369 ////console.error("Unexpected response format:", response);
370 replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists.");
371 }
372
373 if (response.message_id) {
374 lastSeenMessageId = response.message_id;
375 }
376
377 return;
378 }
379
380 // If we got here, it's likely an error response
381 // Now we can check for error conditions with our robust error handling
382
383 let errorMessage = "";
384 let errorCode = "";
385
386 // Check various possible error locations in the response
387 if (response.data && response.data.error_message) {
388 errorMessage = response.data.error_message;
389 errorCode = response.data.error_code || "";
390 } else if (response.error_message) {
391 errorMessage = response.error_message;
392 errorCode = response.error_code || "";
393 } else if (response.message) {
394 errorMessage = response.message;
395 } else if (typeof response.data === 'string') {
396 errorMessage = response.data;
397 } else if (!response.success) {
398 // Explicit check for success: false without other error info
399 errorMessage = "An error occurred. Please try again or contact support.";
400 } else {
401 // Fallback for any other unexpected response format
402 errorMessage = "Unexpected response received. Please try again or contact support.";
403 }
404
405 // Log the error with code for debugging
406 //console.log("Response data:", response.data);
407 ////console.error("API Error:", errorMessage, "Code:", errorCode);
408
409 // Format user-friendly error message
410 let displayMessage = errorMessage;
411
412 // Customize message for admin users
413 if (mxchatChat.is_admin) {
414 // For admin users, show more technical details including error code
415 displayMessage = errorMessage + (errorCode ? " (Error code: " + errorCode + ")" : "");
416 }
417
418 replaceLastMessage("bot", displayMessage);
419 },
420 error: function(xhr, status, error) {
421 //console.error("AJAX Error:", status, error);
422 //console.log("Response Text:", xhr.responseText);
423
424 let errorMessage = "An unexpected error occurred.";
425
426 // Try to parse the response if it's JSON
427 try {
428 const responseJson = JSON.parse(xhr.responseText);
429 //console.log("Parsed error response:", responseJson);
430
431 if (responseJson.data && responseJson.data.error_message) {
432 errorMessage = responseJson.data.error_message;
433 } else if (responseJson.message) {
434 errorMessage = responseJson.message;
435 }
436 } catch (e) {
437 // Not JSON or parsing failed, use HTTP status based messages
438 if (xhr.status === 0) {
439 errorMessage = "Network error: Please check your internet connection.";
440 } else if (xhr.status === 403) {
441 errorMessage = "Access denied: Your session may have expired. Please refresh the page.";
442 } else if (xhr.status === 404) {
443 errorMessage = "API endpoint not found. Please contact support.";
444 } else if (xhr.status === 429) {
445 errorMessage = "Too many requests. Please try again in a moment.";
446 } else if (xhr.status >= 500) {
447 errorMessage = "Server error: The server encountered an issue. Please try again later.";
448 }
449 }
450
451 replaceLastMessage("bot", errorMessage);
452 }
453 });
454 }
455
456 function callMxChatStream(message, callback) {
457 //console.log("Using streaming for message:", message);
458
459 const currentModel = mxchatChat.model || 'gpt-4o';
460 if (!isStreamingSupported(currentModel)) {
461 //console.log("Streaming not supported, falling back to regular call");
462 callMxChat(message, callback);
463 return;
464 }
465
466 // Get page context if contextual awareness is enabled
467 const pageContext = getPageContext();
468
469 const formData = new FormData();
470 formData.append('action', 'mxchat_stream_chat');
471 formData.append('message', message);
472 formData.append('session_id', getChatSession());
473 formData.append('nonce', mxchatChat.nonce);
474
475 // Add page context if available
476 if (pageContext) {
477 formData.append('page_context', JSON.stringify(pageContext));
478 }
479
480 // CHECK FOR VISION FLAGS AND ADD THEM
481 if (window.mxchatVisionProcessed) {
482 formData.append('vision_processed', 'true');
483 formData.append('original_user_message', window.mxchatOriginalMessage || message);
484 formData.append('vision_images_count', window.mxchatVisionImagesCount || '0');
485 // Clear the flags after use
486 window.mxchatVisionProcessed = false;
487 window.mxchatOriginalMessage = null;
488 window.mxchatVisionImagesCount = 0;
489 }
490
491 let accumulatedContent = '';
492 let testingDataReceived = false;
493
494 fetch(mxchatChat.ajax_url, {
495 method: 'POST',
496 body: formData,
497 credentials: 'same-origin'
498 })
499 .then(response => {
500 //console.log("Streaming response received:", response);
501
502 if (!response.ok) {
503 throw new Error('Network response was not ok');
504 }
505
506 // Check if response is JSON instead of streaming
507 const contentType = response.headers.get('content-type');
508 if (contentType && contentType.includes('application/json')) {
509 //console.log("Received JSON response instead of stream, handling as regular response");
510 return response.json().then(data => {
511
512 // FIXED: Always check for testing panel, not just in testing mode
513 if (window.mxchatTestPanelInstance && data.testing_data) {
514 //console.log('Testing data found in streaming JSON response:', data.testing_data);
515 window.mxchatTestPanelInstance.handleTestingData(data.testing_data);
516 }
517
518 // Handle as regular JSON response
519 $('.bot-message.temporary-message').remove();
520
521 // Handle different response formats (including intent responses)
522 if (data.text || data.html || data.message) {
523
524 // ADD RESPONSE HOOKS HERE - FOR STREAMING JSON RESPONSES
525 if (data.text && typeof customMxChatFilter === 'function') {
526 data.text = customMxChatFilter(data.text, "response");
527 }
528 if (data.message && typeof customMxChatFilter === 'function') {
529 data.message = customMxChatFilter(data.message, "response");
530 }
531
532 if (data.text && data.html) {
533 replaceLastMessage("bot", data.text, data.html);
534 } else if (data.text) {
535 replaceLastMessage("bot", data.text);
536 } else if (data.html) {
537 replaceLastMessage("bot", "", data.html);
538 } else if (data.message) {
539 replaceLastMessage("bot", data.message);
540 }
541 }
542
543 // Handle other response properties
544 if (data.chat_mode) {
545 updateChatModeIndicator(data.chat_mode);
546 }
547
548 if (data.data && data.data.filename) {
549 showActivePdf(data.data.filename);
550 activePdfFile = data.data.filename;
551 }
552
553 if (callback) {
554 callback(data.text || data.message || '');
555 }
556 });
557 }
558
559 // Continue with streaming processing
560 const reader = response.body.getReader();
561 const decoder = new TextDecoder();
562 let buffer = '';
563
564 function processStream() {
565 reader.read().then(({ done, value }) => {
566 if (done) {
567 //console.log("Streaming completed, final content:", accumulatedContent);
568 if (callback) {
569 callback(accumulatedContent);
570 }
571 return;
572 }
573
574 buffer += decoder.decode(value, { stream: true });
575 const lines = buffer.split('\n');
576 buffer = lines.pop() || '';
577
578 for (const line of lines) {
579 if (line.startsWith('data: ')) {
580 const data = line.substring(6);
581
582 if (data === '[DONE]') {
583 //console.log("Received [DONE] signal");
584 if (callback) {
585 callback(accumulatedContent);
586 }
587 return;
588 }
589
590 try {
591 const json = JSON.parse(data);
592
593 // FIXED: Always check for testing panel and handle testing data properly
594 if (json.testing_data && !testingDataReceived) {
595 //console.log('Testing data received in stream:', json.testing_data);
596 if (window.mxchatTestPanelInstance) {
597 window.mxchatTestPanelInstance.handleTestingData(json.testing_data);
598 testingDataReceived = true;
599 }
600 }
601 // Handle content streaming
602 else if (json.content) {
603 accumulatedContent += json.content;
604 updateStreamingMessage(accumulatedContent);
605 }
606 // Handle errors
607 else if (json.error) {
608 //console.error("Streaming error:", json.error);
609 replaceLastMessage("bot", "Error: " + json.error);
610 return;
611 }
612 } catch (e) {
613 //console.error('Error parsing SSE data:', e, 'Data:', data);
614 }
615 }
616 }
617
618 processStream();
619 });
620 }
621
622 processStream();
623 })
624 .catch(error => {
625 //console.error('Streaming error:', error);
626 callMxChat(message, callback);
627 });
628 }
629
630 // Function to update message during streaming
631 function updateStreamingMessage(content) {
632 // ADD RESPONSE HOOK FOR REAL-TIME STREAMING
633 if (typeof customMxChatFilter === 'function') {
634 content = customMxChatFilter(content, "response");
635 }
636
637 const formattedContent = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(content))));
638
639 // Find the temporary message
640 const tempMessage = $('.bot-message.temporary-message').last();
641
642 if (tempMessage.length) {
643 // Update existing message
644 tempMessage.html(formattedContent);
645 } else {
646 // Create new temporary message if it doesn't exist
647 appendMessage("bot", content, '', [], true);
648 }
649 }
650
651 // UPGRADE: Function to check if streaming is supported for the current model
652 function isStreamingSupported(model) {
653 if (!model) return false;
654
655 //console.log("Checking streaming support for model:", model); // Debug log
656
657 // Get the model prefix
658 const modelPrefix = model.split('-')[0].toLowerCase();
659
660 //console.log("Model prefix:", modelPrefix); // Debug log
661
662 // Support streaming for OpenAI, Claude, and Grok models
663 const isSupported = modelPrefix === 'gpt' || modelPrefix === 'o1' || modelPrefix === 'claude' || modelPrefix === 'grok';
664
665 //console.log("Streaming supported:", isSupported); // Debug log
666
667 return isSupported;
668 }
669
670 // Update the event handlers to use the correct function names
671 $('#send-button').off('click').on('click', function() {
672 sendMessage(); // Use the updated sendMessage function
673 });
674
675 // Override enter key handler
676 $('#chat-input').off('keypress').on('keypress', function(e) {
677 if (e.which == 13 && !e.shiftKey) {
678 e.preventDefault();
679 sendMessage(); // Use the updated sendMessage function
680 }
681 });
682
683
684 function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
685 try {
686 // Determine styles based on sender type
687 let messageClass, bgColor, fontColor;
688
689 if (sender === "user") {
690 messageClass = "user-message";
691 bgColor = userMessageBgColor;
692 fontColor = userMessageFontColor;
693 // Only sanitize user input
694 messageText = sanitizeUserInput(messageText);
695 } else if (sender === "agent") {
696 messageClass = "agent-message";
697 bgColor = liveAgentMessageBgColor;
698 fontColor = liveAgentMessageFontColor;
699 } else {
700 messageClass = "bot-message";
701 bgColor = botMessageBgColor;
702 fontColor = botMessageFontColor;
703 }
704
705 const messageDiv = $('<div>')
706 .addClass(messageClass)
707 .attr('dir', 'auto') // Add dir="auto" for automatic text direction
708 .css({
709 'background': bgColor,
710 'color': fontColor,
711 'margin-bottom': '1em'
712 });
713
714 // Process the message content based on sender
715 let fullMessage;
716 if (sender === "user") {
717 // For user messages, apply linkify after sanitization
718 fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
719 } else {
720 // For bot/agent messages, preserve HTML
721 fullMessage = messageText;
722 }
723
724 // Add images if provided
725 if (images && images.length > 0) {
726 fullMessage += '<div class="image-gallery" dir="auto">'; // Add dir="auto" to image gallery
727 images.forEach(img => {
728 // Ensure image URLs and titles are properly escaped
729 const safeTitle = sanitizeUserInput(img.title);
730 const safeUrl = encodeURI(img.image_url);
731 const safeThumbnail = encodeURI(img.thumbnail_url);
732
733 fullMessage += `
734 <div style="margin-bottom: 10px;">
735 <strong>${safeTitle}</strong><br>
736 <a href="${safeUrl}" target="_blank">
737 <img src="${safeThumbnail}" alt="${safeTitle}" style="max-width: 100px; height: auto; margin: 5px;" />
738 </a>
739 </div>`;
740 });
741 fullMessage += '</div>';
742 }
743
744 // Append HTML content if provided
745 if (messageHtml && sender !== "user") {
746 fullMessage += '<br><br>' + messageHtml;
747 }
748
749 messageDiv.html(fullMessage);
750
751 if (isTemporary) {
752 messageDiv.addClass('temporary-message');
753 }
754
755 messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() {
756 if (sender === "bot") {
757 const lastUserMessage = $('#chat-box').find('.user-message').last();
758 if (lastUserMessage.length) {
759 scrollElementToTop(lastUserMessage);
760 }
761 }
762 });
763
764 if (messageText.id) {
765 lastSeenMessageId = messageText.id;
766 hideNotification();
767 }
768 } catch (error) {
769 //console.error("Error rendering message:", error);
770 }
771 }
772
773 function replaceLastMessage(sender, responseText, responseHtml = '', images = []) {
774 var messageClass = sender === "user" ? "user-message" : sender === "agent" ? "agent-message" : "bot-message";
775 var lastMessageDiv = $('#chat-box').find('.bot-message.temporary-message, .agent-message.temporary-message').last();
776
777 // Determine styles
778 let bgColor, fontColor;
779 if (sender === "user") {
780 bgColor = userMessageBgColor;
781 fontColor = userMessageFontColor;
782 } else if (sender === "agent") {
783 bgColor = liveAgentMessageBgColor;
784 fontColor = liveAgentMessageFontColor;
785 } else {
786 bgColor = botMessageBgColor;
787 fontColor = botMessageFontColor;
788 }
789
790 var fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(responseText))));
791 if (responseHtml) {
792 fullMessage += '<br><br>' + responseHtml;
793 }
794
795 if (images.length > 0) {
796 fullMessage += '<div class="image-gallery" dir="auto">'; // Add dir="auto" to image gallery
797 images.forEach(img => {
798 fullMessage += `
799 <div style="margin-bottom: 10px;">
800 <strong>${img.title}</strong><br>
801 <a href="${img.image_url}" target="_blank">
802 <img src="${img.thumbnail_url}" alt="${img.title}" style="max-width: 100px; height: auto; margin: 5px;" />
803 </a>
804 </div>`;
805 });
806 fullMessage += '</div>';
807 }
808
809 if (lastMessageDiv.length) {
810 lastMessageDiv.fadeOut(200, function() {
811 $(this)
812 .html(fullMessage)
813 .removeClass('bot-message user-message')
814 .addClass(messageClass)
815 .attr('dir', 'auto') // Add dir="auto" for automatic text direction
816 .css({
817 'background-color': bgColor,
818 'color': fontColor,
819 })
820 .removeClass('temporary-message')
821 .fadeIn(200, function() {
822 if (sender === "bot" || sender === "agent") {
823 const lastUserMessage = $('#chat-box').find('.user-message').last();
824 if (lastUserMessage.length) {
825 scrollElementToTop(lastUserMessage);
826 }
827 // Show notification if chat is hidden
828 if ($('#floating-chatbot').hasClass('hidden')) {
829 showNotification();
830 }
831 }
832 });
833 });
834 } else {
835 appendMessage(sender, responseText, responseHtml, images);
836 }
837 }
838
839 function appendThinkingMessage() {
840 // Remove any existing thinking dots first
841 $('.thinking-dots').remove();
842
843 // Retrieve the bot message font color and background color
844 var botMessageFontColor = mxchatChat.bot_message_font_color;
845 var botMessageBgColor = mxchatChat.bot_message_bg_color;
846
847
848 var thinkingHtml = '<div class="thinking-dots-container">' +
849 '<div class="thinking-dots">' +
850 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
851 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
852 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
853 '</div>' +
854 '</div>';
855
856 // Append the thinking dots to the chat container (or within the temporary message div)
857 $("#chat-box").append('<div class="bot-message temporary-message" style="background-color: ' + botMessageBgColor + ';">' + thinkingHtml + '</div>');
858 scrollToBottom();
859 }
860
861 function removeThinkingDots() {
862 $('.thinking-dots').closest('.temporary-message').remove();
863 }
864
865
866 // ====================================
867 // TEXT FORMATTING & PROCESSING
868 // ====================================
869
870
871 function linkify(inputText) {
872 if (!inputText) return '';
873
874 // Process markdown headers
875 let processedText = formatMarkdownHeaders(inputText);
876
877 // Process markdown links
878 const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
879 processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
880 const safeUrl = encodeURI(url);
881 const safeText = sanitizeUserInput(text);
882 return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
883 });
884
885 // Process phone numbers (tel:)
886 const phonePattern = /\[([^\]]+)\]\((tel:[\d+]+)\)/g;
887 processedText = processedText.replace(phonePattern, (match, text, phone) => {
888 const safePhone = encodeURI(phone);
889 const safeText = sanitizeUserInput(text);
890 return `<a href="${safePhone}">${safeText}</a>`;
891 });
892
893 // Process standalone URLs
894 const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
895 processedText = processedText.replace(urlPattern, (match, prefix, url) => {
896 const safeUrl = encodeURI(url);
897 return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
898 });
899
900 // Process www. URLs
901 const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
902 processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
903 const safeUrl = encodeURI(`http://${url}`);
904 return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
905 });
906
907 // Add this after your phone pattern
908 const mailtoPattern = /\[([^\]]+)\]\((mailto:[^\)]+)\)/g;
909 processedText = processedText.replace(mailtoPattern, (match, text, mailto) => {
910 const safeMailto = encodeURI(mailto);
911 const safeText = sanitizeUserInput(text);
912 return `<a href="${safeMailto}">${safeText}</a>`;
913 });
914
915 return processedText;
916 }
917
918 function formatMarkdownHeaders(text) {
919 // Handle h1 to h6 headers
920 return text.replace(/^(#{1,6})\s(.+)$/gm, function(match, hashes, content) {
921 const level = hashes.length;
922 return `<h${level} class="chat-heading">${content}</h${level}>`;
923 });
924 }
925
926 function formatBoldText(text) {
927 return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
928 }
929
930 function convertNewlinesToBreaks(text) {
931 // Split the text into paragraphs (marked by double newlines or multiple <br> tags)
932 const paragraphs = text.split(/(?:\n\n|\<br\>\s*\<br\>)/g);
933
934 // Wrap each paragraph in <p> tags
935 return paragraphs
936 .map(para => `<p>${para.trim()}</p>`)
937 .join('');
938 }
939
940 function formatCodeBlocks(text) {
941 // First handle raw PHP tags
942 text = text.replace(/(<\?php[\s\S]*?\?>)/g, (match) => {
943 return `<pre><code class="language-php">${escapeHtml(match)}</code></pre>`;
944 });
945
946 // Then handle code blocks with backticks
947 text = text.replace(/```php5?\n([\s\S]+?)```/gi, (match, code) => {
948 return `<pre><code class="language-php">${escapeHtml(code)}</code></pre>`;
949 });
950
951 return text;
952 }
953
954 function sanitizeUserInput(text) {
955 const div = document.createElement('div');
956 div.textContent = text;
957 return div.innerHTML;
958 }
959
960
961 function escapeHtml(unsafe) {
962 // First check if it's already a code block
963 if (unsafe.includes('<pre><code') || unsafe.includes('</code></pre>')) {
964 return unsafe;
965 }
966
967 return unsafe
968 .replace(/&/g, "&amp;")
969 .replace(/</g, "&lt;")
970 .replace(/>/g, "&gt;")
971 .replace(/"/g, "&quot;")
972 .replace(/'/g, "&#039;");
973 }
974
975 function decodeHTMLEntities(text) {
976 var textArea = document.createElement('textarea');
977 textArea.innerHTML = text;
978 return textArea.value;
979 }
980
981
982 // ====================================
983 // UI & SCROLLING CONTROLS
984 // ====================================
985
986 function scrollToBottom(instant = false) {
987 var chatBox = $('#chat-box');
988 if (instant) {
989 // Instantly set the scroll position to the bottom
990 chatBox.scrollTop(chatBox.prop("scrollHeight"));
991 } else {
992 // Use requestAnimationFrame for smoother scrolling if needed
993 let start = null;
994 const scrollHeight = chatBox.prop("scrollHeight");
995 const initialScroll = chatBox.scrollTop();
996 const distance = scrollHeight - initialScroll;
997 const duration = 500; // Duration in ms
998
999 function smoothScroll(timestamp) {
1000 if (!start) start = timestamp;
1001 const progress = timestamp - start;
1002 const currentScroll = initialScroll + (distance * (progress / duration));
1003 chatBox.scrollTop(currentScroll);
1004
1005 if (progress < duration) {
1006 requestAnimationFrame(smoothScroll);
1007 } else {
1008 chatBox.scrollTop(scrollHeight); // Ensure it's exactly at the bottom
1009 }
1010 }
1011
1012 requestAnimationFrame(smoothScroll);
1013 }
1014 }
1015
1016 function scrollElementToTop(element) {
1017 var chatBox = $('#chat-box');
1018 var elementTop = element.position().top + chatBox.scrollTop();
1019 chatBox.animate({ scrollTop: elementTop }, 500);
1020 }
1021
1022 function showChatWidget() {
1023 // First ensure display is set
1024 $('#floating-chatbot-button').css('display', 'flex');
1025 // Then handle the fade
1026 $('#floating-chatbot-button').fadeTo(500, 1);
1027 // Force visibility
1028 $('#floating-chatbot-button').removeClass('hidden');
1029 //console.log('Showing widget');
1030 }
1031
1032 function hideChatWidget() {
1033 $('#floating-chatbot-button').css('display', 'none');
1034 $('#floating-chatbot-button').addClass('hidden');
1035 //console.log('Hiding widget');
1036 }
1037
1038 function disableScroll() {
1039 if (isMobile()) {
1040 $('body').css('overflow', 'hidden');
1041 }
1042 }
1043
1044 function enableScroll() {
1045 if (isMobile()) {
1046 $('body').css('overflow', '');
1047 }
1048 }
1049
1050 function isMobile() {
1051 // This can be a simple check, or more sophisticated detection of mobile devices
1052 return window.innerWidth <= 768; // Example threshold for mobile devices
1053 }
1054
1055 function setFullHeight() {
1056 var vh = $(window).innerHeight() * 0.01;
1057 $(':root').css('--vh', vh + 'px');
1058 }
1059
1060
1061 // ====================================
1062 // NOTIFICATION SYSTEM
1063 // ====================================
1064
1065 function createNotificationBadge() {
1066 //console.log("Creating notification badge...");
1067 const chatButton = document.getElementById('floating-chatbot-button');
1068 //console.log("Chat button found:", !!chatButton);
1069
1070 if (!chatButton) return;
1071
1072 // Remove any existing badge first
1073 const existingBadge = chatButton.querySelector('.chat-notification-badge');
1074 if (existingBadge) {
1075 //console.log("Removing existing badge");
1076 existingBadge.remove();
1077 }
1078
1079 notificationBadge = document.createElement('div');
1080 notificationBadge.className = 'chat-notification-badge';
1081 notificationBadge.style.cssText = `
1082 display: none;
1083 position: absolute;
1084 top: -5px;
1085 right: -5px;
1086 background-color: red;
1087 color: white;
1088 border-radius: 50%;
1089 padding: 4px 8px;
1090 font-size: 12px;
1091 font-weight: bold;
1092 z-index: 10001;
1093 `;
1094 chatButton.style.position = 'relative';
1095 chatButton.appendChild(notificationBadge);
1096
1097 }
1098
1099 function showNotification() {
1100 const badge = document.getElementById('chat-notification-badge');
1101 if (badge && $('#floating-chatbot').hasClass('hidden')) {
1102 badge.style.display = 'block';
1103 badge.textContent = '1';
1104 }
1105 }
1106
1107 function hideNotification() {
1108 const badge = document.getElementById('chat-notification-badge');
1109 if (badge) {
1110 badge.style.display = 'none';
1111 }
1112 }
1113
1114 function startNotificationChecking() {
1115 const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
1116 if (!chatPersistenceEnabled) return;
1117
1118 createNotificationBadge();
1119 notificationCheckInterval = setInterval(checkForNewMessages, 30000); // Check every 30 seconds
1120 }
1121
1122 function stopNotificationChecking() {
1123 if (notificationCheckInterval) {
1124 clearInterval(notificationCheckInterval);
1125 }
1126 }
1127
1128 function checkForNewMessages() {
1129 const sessionId = getChatSession();
1130 const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
1131
1132 if (!chatPersistenceEnabled) return;
1133
1134 $.ajax({
1135 url: mxchatChat.ajax_url,
1136 type: 'POST',
1137 data: {
1138 action: 'mxchat_check_new_messages',
1139 session_id: sessionId,
1140 last_seen_id: lastSeenMessageId,
1141 nonce: mxchatChat.nonce
1142 },
1143 success: function(response) {
1144 if (response.success && response.data.hasNewMessages) {
1145 showNotification();
1146 }
1147 }
1148 });
1149 }
1150
1151
1152 // ====================================
1153 // LIVE AGENT FUNCTIONALITY
1154 // ====================================
1155
1156 function updateChatModeIndicator(mode) {
1157 const indicator = document.getElementById('chat-mode-indicator');
1158 if (indicator) {
1159 // For Live Agent, keep as is; for AI mode, use the customized text
1160 if (mode === 'agent') {
1161 indicator.textContent = 'Live Agent';
1162 } else {
1163 // Get the custom AI agent text from a data attribute we'll add to the element
1164 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
1165 indicator.textContent = customAiText;
1166 }
1167 }
1168 // Start or stop polling based on mode
1169 if (mode === 'agent') {
1170 startPolling();
1171 } else {
1172 stopPolling();
1173 }
1174 }
1175
1176 function startPolling() {
1177 // Clear any existing interval first
1178 stopPolling();
1179 // Start new polling interval
1180 pollingInterval = setInterval(checkForAgentMessages, 5000);
1181 //console.log("Started agent message polling");
1182 }
1183
1184 function stopPolling() {
1185 if (pollingInterval) {
1186 clearInterval(pollingInterval);
1187 pollingInterval = null;
1188 //console.log("Stopped agent message polling");
1189 }
1190 }
1191
1192 function checkForAgentMessages() {
1193 const sessionId = getChatSession();
1194 $.ajax({
1195 url: mxchatChat.ajax_url,
1196 type: 'POST',
1197 dataType: 'json',
1198 data: {
1199 action: 'mxchat_fetch_new_messages',
1200 session_id: sessionId,
1201 last_seen_id: lastSeenMessageId,
1202 persistence_enabled: 'true', // Add this too
1203 nonce: mxchatChat.nonce
1204 },
1205 success: function (response) {
1206 if (response.success && response.data?.new_messages) {
1207 let hasNewMessage = false;
1208
1209 response.data.new_messages.forEach(function (message) {
1210 if (message.role === "agent" && !processedMessageIds.has(message.id)) {
1211 hasNewMessage = true;
1212 // CHANGE THIS LINE:
1213 appendMessage("agent", message.content); // Instead of replaceLastMessage
1214 lastSeenMessageId = message.id;
1215 processedMessageIds.add(message.id);
1216 }
1217 });
1218
1219 if (hasNewMessage && $('#floating-chatbot').hasClass('hidden')) {
1220 showNotification();
1221 }
1222
1223 scrollToBottom(true);
1224 }
1225 },
1226 error: function (xhr, status, error) {
1227 //console.error("Polling error:", xhr, status, error);
1228 }
1229 });
1230 }
1231
1232 // ====================================
1233 // CHAT HISTORY & PERSISTENCE
1234 // ====================================
1235
1236 function loadChatHistory() {
1237 var sessionId = getChatSession();
1238 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
1239
1240 if (chatPersistenceEnabled && sessionId) {
1241 $.ajax({
1242 url: mxchatChat.ajax_url,
1243 type: 'POST',
1244 dataType: 'json',
1245 data: {
1246 action: 'mxchat_fetch_conversation_history',
1247 session_id: sessionId
1248 },
1249 success: function(response) {
1250 if (response.success && response.data && Array.isArray(response.data.conversation)) {
1251
1252
1253 var $chatBox = $('#chat-box');
1254 var $fragment = $(document.createDocumentFragment());
1255 let highestMessageId = lastSeenMessageId;
1256
1257 if (response.data.chat_mode) {
1258 updateChatModeIndicator(response.data.chat_mode);
1259 }
1260
1261 $.each(response.data.conversation, function(index, message) {
1262 // Skip agent messages if persistence is off
1263 if (!chatPersistenceEnabled && message.role === 'agent') {
1264 return;
1265 }
1266
1267 var messageClass, messageBgColor, messageFontColor;
1268
1269 switch (message.role) {
1270 case 'user':
1271 messageClass = 'user-message';
1272 messageBgColor = userMessageBgColor;
1273 messageFontColor = userMessageFontColor;
1274 break;
1275 case 'agent':
1276 messageClass = 'agent-message';
1277 messageBgColor = liveAgentMessageBgColor;
1278 messageFontColor = liveAgentMessageFontColor;
1279 break;
1280 default:
1281 messageClass = 'bot-message';
1282 messageBgColor = botMessageBgColor;
1283 messageFontColor = botMessageFontColor;
1284 break;
1285 }
1286
1287 var messageElement = $('<div>').addClass(messageClass)
1288 .css({
1289 'background': messageBgColor,
1290 'color': messageFontColor
1291 });
1292
1293 var content = message.content;
1294 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
1295 content = decodeHTMLEntities(content);
1296
1297 if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
1298 messageElement.html(content);
1299 } else {
1300 var formattedContent = linkify(
1301 formatBoldText(
1302 convertNewlinesToBreaks(formatCodeBlocks(content))
1303 )
1304 );
1305 messageElement.html(formattedContent);
1306 }
1307
1308 $fragment.append(messageElement);
1309
1310 // In loadChatHistory, change this part:
1311 if (message.id) {
1312 highestMessageId = Math.max(highestMessageId, message.id);
1313 processedMessageIds.add(message.id); // Add all message IDs to processed set
1314 }
1315 });
1316
1317 $chatBox.append($fragment);
1318 scrollToBottom(true);
1319
1320 if (response.data.conversation.length > 0 && hasQuickQuestions()) {
1321 collapseQuickQuestions();
1322 }
1323
1324 // Update lastSeenMessageId after history loads
1325 lastSeenMessageId = highestMessageId;
1326
1327 // Only update chat mode if persistence is enabled
1328 if (chatPersistenceEnabled && response.data.conversation.length > 0) {
1329 var lastMessage = response.data.conversation[response.data.conversation.length - 1];
1330 if (lastMessage.role === 'agent') {
1331 updateChatModeIndicator('agent');
1332 }
1333 }
1334 } else {
1335 console.warn("No conversation history found.");
1336 }
1337 },
1338 error: function(xhr, status, error) {
1339 //console.error("Error loading chat history:", status, error);
1340 appendMessage("bot", "Unable to load chat history.");
1341 }
1342 });
1343 } else {
1344 console.warn("Chat persistence is disabled or no session ID found. Not loading history.");
1345 }
1346 }
1347
1348
1349 // ====================================
1350 // FILE UPLOAD FUNCTIONALITY
1351 // ====================================
1352
1353 function addSafeEventListener(elementId, eventType, handler) {
1354 const element = document.getElementById(elementId);
1355 if (element) {
1356 element.addEventListener(eventType, handler);
1357 }
1358 }
1359
1360 function showActivePdf(filename) {
1361 const container = document.getElementById('active-pdf-container');
1362 const nameElement = document.getElementById('active-pdf-name');
1363
1364 if (!container || !nameElement) {
1365 //console.error('PDF container elements not found');
1366 return;
1367 }
1368
1369 nameElement.textContent = filename;
1370 container.style.display = 'flex';
1371 }
1372
1373 function showActiveWord(filename) {
1374 const container = document.getElementById('active-word-container');
1375 const nameElement = document.getElementById('active-word-name');
1376
1377 if (!container || !nameElement) {
1378 //console.error('Word document container elements not found');
1379 return;
1380 }
1381
1382 nameElement.textContent = filename;
1383 container.style.display = 'flex';
1384 }
1385
1386 function removeActivePdf() {
1387 const container = document.getElementById('active-pdf-container');
1388 const nameElement = document.getElementById('active-pdf-name');
1389
1390 if (!container || !nameElement || !activePdfFile) return;
1391
1392 fetch(mxchatChat.ajax_url, {
1393 method: 'POST',
1394 headers: {
1395 'Content-Type': 'application/x-www-form-urlencoded',
1396 },
1397 body: new URLSearchParams({
1398 'action': 'mxchat_remove_pdf',
1399 'session_id': sessionId,
1400 'nonce': mxchatChat.nonce
1401 })
1402 })
1403 .then(response => response.json())
1404 .then(data => {
1405 if (data.success) {
1406 container.style.display = 'none';
1407 nameElement.textContent = '';
1408 activePdfFile = null;
1409 appendMessage('bot', 'PDF removed.');
1410 }
1411 })
1412 .catch(error => {
1413 //console.error('Error removing PDF:', error);
1414 });
1415 }
1416
1417 function removeActiveWord() {
1418 const container = document.getElementById('active-word-container');
1419 const nameElement = document.getElementById('active-word-name');
1420
1421 if (!container || !nameElement || !activeWordFile) return;
1422
1423 fetch(mxchatChat.ajax_url, {
1424 method: 'POST',
1425 headers: {
1426 'Content-Type': 'application/x-www-form-urlencoded',
1427 },
1428 body: new URLSearchParams({
1429 'action': 'mxchat_remove_word',
1430 'session_id': sessionId,
1431 'nonce': mxchatChat.nonce
1432 })
1433 })
1434 .then(response => response.json())
1435 .then(data => {
1436 if (data.success) {
1437 container.style.display = 'none';
1438 nameElement.textContent = '';
1439 activeWordFile = null;
1440 appendMessage('bot', 'Word document removed.');
1441 }
1442 })
1443 .catch(error => {
1444 //console.error('Error removing Word document:', error);
1445 });
1446 }
1447
1448 // ====================================
1449 // CONSENT & COMPLIANCE (GDPR)
1450 // ====================================
1451
1452 function initializeChatVisibility() {
1453 //console.log('Initializing chat visibility');
1454 const complianzEnabled = mxchatChat.complianz_toggle === 'on' ||
1455 mxchatChat.complianz_toggle === '1' ||
1456 mxchatChat.complianz_toggle === 1;
1457
1458 if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') {
1459 // Initial check
1460 checkConsentAndShowChat();
1461
1462 // Listen for consent changes
1463 $(document).on('cmplz_status_change', function(event) {
1464 //console.log('Status change detected');
1465 checkConsentAndShowChat();
1466 });
1467 } else {
1468 // If Complianz is not enabled, always show
1469 $('#floating-chatbot-button')
1470 .css('display', 'flex')
1471 .removeClass('hidden no-consent')
1472 .fadeTo(500, 1);
1473
1474 // Also check pre-chat message when Complianz is not enabled
1475 checkPreChatDismissal();
1476 }
1477 }
1478
1479
1480 function checkConsentAndShowChat() {
1481 var consentStatus = cmplz_has_consent('marketing');
1482 var consentType = complianz.consenttype;
1483
1484 //console.log('Checking consent:', {status: consentStatus,type: consentType});
1485
1486 let $widget = $('#floating-chatbot-button');
1487 let $chatbot = $('#floating-chatbot');
1488 let $preChat = $('#pre-chat-message');
1489
1490 if (consentStatus === true) {
1491 //console.log('Consent granted - showing widget');
1492 $widget
1493 .removeClass('no-consent')
1494 .css('display', 'flex')
1495 .removeClass('hidden')
1496 .fadeTo(500, 1);
1497 $chatbot.removeClass('no-consent');
1498
1499 // Show pre-chat message if not dismissed
1500 checkPreChatDismissal();
1501 } else {
1502 //console.log('No consent - hiding widget');
1503 $widget
1504 .addClass('no-consent')
1505 .fadeTo(500, 0, function() {
1506 $(this)
1507 .css('display', 'none')
1508 .addClass('hidden');
1509 });
1510 $chatbot.addClass('no-consent');
1511
1512 // Hide pre-chat message when no consent
1513 $preChat.hide();
1514 }
1515 }
1516
1517
1518 // ====================================
1519 // PRE-CHAT MESSAGE HANDLING
1520 // ====================================
1521
1522 function checkPreChatDismissal() {
1523 $.ajax({
1524 url: mxchatChat.ajax_url,
1525 type: 'POST',
1526 data: {
1527 action: 'mxchat_check_pre_chat_message_status',
1528 _ajax_nonce: mxchatChat.nonce
1529 },
1530 success: function(response) {
1531 if (response.success && !response.data.dismissed) {
1532 $('#pre-chat-message').fadeIn(250);
1533 } else {
1534 $('#pre-chat-message').hide();
1535 }
1536 },
1537 error: function() {
1538 //console.error('Failed to check pre-chat message dismissal status.');
1539 }
1540 });
1541 }
1542
1543 function handlePreChatDismissal() {
1544 $('#pre-chat-message').fadeOut(200);
1545 $.ajax({
1546 url: mxchatChat.ajax_url,
1547 type: 'POST',
1548 data: {
1549 action: 'mxchat_dismiss_pre_chat_message',
1550 _ajax_nonce: mxchatChat.nonce
1551 },
1552 success: function() {
1553 $('#pre-chat-message').hide();
1554 },
1555 error: function() {
1556 //console.error('Failed to dismiss pre-chat message.');
1557 }
1558 });
1559 }
1560
1561
1562 // ====================================
1563 // UTILITY FUNCTIONS
1564 // ====================================
1565
1566 function copyToClipboard(text) {
1567 var tempInput = $('<input>');
1568 $('body').append(tempInput);
1569 tempInput.val(text).select();
1570 document.execCommand('copy');
1571 tempInput.remove();
1572 }
1573
1574
1575 function isImageHtml(str) {
1576 return str.startsWith('<img') && str.endsWith('>');
1577 }
1578
1579
1580 // ====================================
1581 // EVENT HANDLERS & INITIALIZATION
1582 // ====================================
1583
1584 $(document).on('click', '.mxchat-popular-question', function () {
1585 var question = $(this).text();
1586
1587 // Append the question as if the user typed it
1588 appendMessage("user", question);
1589
1590 // Only collapse if there are questions
1591 if (hasQuickQuestions()) {
1592 collapseQuickQuestions();
1593 }
1594
1595 // Send the question to the server
1596 sendMessageToChatbot(question);
1597 });
1598
1599 $(document).on('click', '.questions-toggle-btn', function(e) {
1600 e.preventDefault();
1601 e.stopPropagation();
1602 expandQuickQuestions();
1603 });
1604
1605 $(document).on('click', '.questions-collapse-btn', function(e) {
1606 e.preventDefault();
1607 e.stopPropagation();
1608 collapseQuickQuestions();
1609 });
1610
1611 // Chatbot visibility toggle handlers
1612 $(document).on('click', '#floating-chatbot-button', function() {
1613 var chatbot = $('#floating-chatbot');
1614 if (chatbot.hasClass('hidden')) {
1615 chatbot.removeClass('hidden').addClass('visible');
1616 $(this).addClass('hidden');
1617 $('#chat-notification-badge').hide(); // Hide notification when opening chat
1618 disableScroll();
1619 $('#pre-chat-message').fadeOut(250);
1620 } else {
1621 chatbot.removeClass('visible').addClass('hidden');
1622 $(this).removeClass('hidden');
1623 enableScroll();
1624 checkPreChatDismissal();
1625 }
1626 });
1627
1628 $(document).on('click', '#exit-chat-button', function() {
1629 $('#floating-chatbot').addClass('hidden').removeClass('visible');
1630 $('#floating-chatbot-button').removeClass('hidden');
1631 enableScroll();
1632 });
1633
1634 $(document).on('click', '.close-pre-chat-message', function(e) {
1635 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
1636 $('#pre-chat-message').fadeOut(200, function() {
1637 $(this).remove();
1638 });
1639 });
1640
1641 // Add to Cart button handler
1642 $(document).on('click', '.mxchat-add-to-cart-button', function() {
1643 var productId = $(this).data('product-id');
1644
1645 // Get the button text instead of hardcoded "add to cart"
1646 var buttonText = $(this).text() || "add to cart";
1647
1648 // Add a special prefix to indicate this is from button
1649 appendMessage("user", buttonText);
1650 sendMessageToChatbot("!addtocart"); // Special command to indicate button click
1651 });
1652
1653 // PDF upload button handlers
1654 if (document.getElementById('pdf-upload-btn')) {
1655 document.getElementById('pdf-upload-btn').addEventListener('click', function() {
1656 document.getElementById('pdf-upload').click();
1657 });
1658 }
1659
1660 // Word upload button handlers
1661 if (document.getElementById('word-upload-btn')) {
1662 document.getElementById('word-upload-btn').addEventListener('click', function() {
1663 document.getElementById('word-upload').click();
1664 });
1665 }
1666
1667 // PDF file input change handler
1668 addSafeEventListener('pdf-upload', 'change', async function(e) {
1669 const file = e.target.files[0];
1670
1671 if (!file || file.type !== 'application/pdf') {
1672 alert('Please select a valid PDF file.');
1673 return;
1674 }
1675
1676 if (!sessionId) {
1677 //console.error('No session ID found');
1678 alert('Error: No session ID found');
1679 return;
1680 }
1681
1682 if (!mxchatChat || !mxchatChat.ajax_url || !mxchatChat.nonce) {
1683 //console.error('mxchatChat not properly configured:', mxchatChat);
1684 alert('Error: Ajax configuration missing');
1685 return;
1686 }
1687
1688 // Disable buttons and show loading state
1689 const uploadBtn = document.getElementById('pdf-upload-btn');
1690 const sendBtn = document.getElementById('send-button');
1691 const originalBtnContent = uploadBtn.innerHTML;
1692
1693 try {
1694 const formData = new FormData();
1695 formData.append('action', 'mxchat_upload_pdf');
1696 formData.append('pdf_file', file);
1697 formData.append('session_id', sessionId);
1698 formData.append('nonce', mxchatChat.nonce);
1699
1700 uploadBtn.disabled = true;
1701 sendBtn.disabled = true;
1702 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1703 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1704 </svg>`;
1705
1706 const response = await fetch(mxchatChat.ajax_url, {
1707 method: 'POST',
1708 body: formData
1709 });
1710
1711 const data = await response.json();
1712
1713 if (data.success) {
1714 // Hide popular questions if they exist
1715 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1716 if (hasQuickQuestions()) {
1717 collapseQuickQuestions();
1718 }
1719
1720 // Show the active PDF name
1721 showActivePdf(data.data.filename);
1722
1723 appendMessage('bot', data.data.message);
1724 scrollToBottom();
1725 activePdfFile = data.data.filename;
1726 } else {
1727 //console.error('Upload failed:', data.data);
1728 alert('Failed to upload PDF. Please try again.');
1729 }
1730 } catch (error) {
1731 //console.error('Upload error:', error);
1732 alert('Error uploading file. Please try again.');
1733 } finally {
1734 uploadBtn.disabled = false;
1735 sendBtn.disabled = false;
1736 uploadBtn.innerHTML = originalBtnContent;
1737 this.value = ''; // Reset file input
1738 }
1739 });
1740
1741 // Word file input change handler
1742 addSafeEventListener('word-upload', 'change', async function(e) {
1743 const file = e.target.files[0];
1744
1745 if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1746 alert('Please select a valid Word document (.docx).');
1747 return;
1748 }
1749
1750 if (!sessionId) {
1751 //console.error('No session ID found');
1752 alert('Error: No session ID found');
1753 return;
1754 }
1755
1756 // Disable buttons and show loading state
1757 const uploadBtn = document.getElementById('word-upload-btn');
1758 const sendBtn = document.getElementById('send-button');
1759 const originalBtnContent = uploadBtn.innerHTML;
1760
1761 try {
1762 const formData = new FormData();
1763 formData.append('action', 'mxchat_upload_word');
1764 formData.append('word_file', file);
1765 formData.append('session_id', sessionId);
1766 formData.append('nonce', mxchatChat.nonce);
1767
1768 uploadBtn.disabled = true;
1769 sendBtn.disabled = true;
1770 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1771 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1772 </svg>`;
1773
1774 const response = await fetch(mxchatChat.ajax_url, {
1775 method: 'POST',
1776 body: formData
1777 });
1778
1779 const data = await response.json();
1780
1781 if (data.success) {
1782 // Hide popular questions if they exist
1783 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1784 if (hasQuickQuestions()) {
1785 collapseQuickQuestions();
1786 }
1787
1788 // Show the active Word document name
1789 showActiveWord(data.data.filename);
1790
1791 appendMessage('bot', data.data.message);
1792 scrollToBottom();
1793 activeWordFile = data.data.filename;
1794 } else {
1795 //console.error('Upload failed:', data.data);
1796 alert('Failed to upload Word document. Please try again.');
1797 }
1798 } catch (error) {
1799 //console.error('Upload error:', error);
1800 alert('Error uploading file. Please try again.');
1801 } finally {
1802 uploadBtn.disabled = false;
1803 sendBtn.disabled = false;
1804 uploadBtn.innerHTML = originalBtnContent;
1805 this.value = ''; // Reset file input
1806 }
1807 });
1808
1809 // Remove button click handlers
1810 document.getElementById('remove-pdf-btn')?.addEventListener('click', function(e) {
1811 e.preventDefault();
1812 e.stopPropagation();
1813 removeActivePdf();
1814 });
1815
1816 document.getElementById('remove-word-btn')?.addEventListener('click', function(e) {
1817 e.preventDefault();
1818 e.stopPropagation();
1819 removeActiveWord();
1820 });
1821
1822 // Window resize handlers
1823 $(window).on('resize orientationchange', function() {
1824 setFullHeight();
1825 });
1826
1827
1828 // ====================================
1829 // TOOLBAR & STYLING SETUP
1830 // ====================================
1831
1832 // Apply toolbar settings
1833 if (mxchatChat.chat_toolbar_toggle === 'on') {
1834 $('.chat-toolbar').show();
1835 } else {
1836 $('.chat-toolbar').hide();
1837 }
1838
1839 // Apply toolbar icon colors
1840 const toolbarElements = [
1841 '#mxchat-chatbot .toolbar-btn svg',
1842 '#mxchat-chatbot .active-pdf-name',
1843 '#mxchat-chatbot .active-word-name',
1844 '#mxchat-chatbot .remove-pdf-btn svg',
1845 '#mxchat-chatbot .remove-word-btn svg',
1846 '#mxchat-chatbot .toolbar-perplexity svg'
1847 ];
1848
1849 toolbarElements.forEach(selector => {
1850 $(selector).css({
1851 'fill': toolbarIconColor,
1852 'stroke': toolbarIconColor,
1853 'color': toolbarIconColor
1854 });
1855 });
1856
1857
1858 // ====================================
1859 // IMPROVED EMAIL COLLECTION SETUP
1860 // ====================================
1861
1862 // Email collection form setup and handlers
1863 const emailForm = document.getElementById('email-collection-form');
1864 const emailBlocker = document.getElementById('email-blocker');
1865 const chatbotWrapper = document.getElementById('chat-container');
1866
1867 if (emailForm && emailBlocker && chatbotWrapper) {
1868 // Add loading state management
1869 let isSubmitting = false;
1870
1871 // Check if email exists for the current session
1872 function checkSessionAndEmail() {
1873 const sessionId = getChatSession();
1874
1875 // Add timeout to prevent hanging
1876 const controller = new AbortController();
1877 const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
1878
1879 fetch(mxchatChat.ajax_url, {
1880 method: 'POST',
1881 headers: {
1882 'Content-Type': 'application/x-www-form-urlencoded',
1883 },
1884 body: new URLSearchParams({
1885 action: 'mxchat_check_email_provided',
1886 session_id: sessionId,
1887 nonce: mxchatChat.nonce,
1888 }),
1889 signal: controller.signal
1890 })
1891 .then((response) => {
1892 clearTimeout(timeoutId);
1893 if (!response.ok) {
1894 throw new Error(`HTTP error! status: ${response.status}`);
1895 }
1896 return response.json();
1897 })
1898 .then((data) => {
1899 if (data.success) {
1900 if (data.data.logged_in || data.data.email) {
1901 showChatContainer();
1902 } else {
1903 showEmailForm();
1904 }
1905 } else {
1906 // On error, default to showing email form
1907 showEmailForm();
1908 }
1909 })
1910 .catch((error) => {
1911 clearTimeout(timeoutId);
1912 console.warn('Email check failed, defaulting to email form:', error);
1913 showEmailForm();
1914 });
1915 }
1916
1917 // Optimized UI transition functions
1918 function showEmailForm() {
1919 emailBlocker.style.display = 'flex';
1920 chatbotWrapper.style.display = 'none';
1921 }
1922
1923 function showChatContainer() {
1924 // Show chat immediately without delay
1925 emailBlocker.style.display = 'none';
1926 chatbotWrapper.style.display = 'flex';
1927
1928 // Load chat history only after showing chat container
1929 if (typeof loadChatHistory === 'function') {
1930 loadChatHistory();
1931 }
1932 }
1933
1934 // Enhanced email validation
1935 function isValidEmail(email) {
1936 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1937 return emailRegex.test(email.trim()) && email.length <= 254; // RFC 5321 limit
1938 }
1939
1940 // Show loading state with spinner
1941 function setSubmissionState(loading) {
1942 const submitButton = document.getElementById('email-submit-button');
1943 const emailInput = document.getElementById('user-email');
1944
1945 if (loading) {
1946 isSubmitting = true;
1947 submitButton.disabled = true;
1948 emailInput.disabled = true;
1949
1950 // Store original content and add spinner
1951 if (!submitButton.getAttribute('data-original-html')) {
1952 submitButton.setAttribute('data-original-html', submitButton.innerHTML);
1953 }
1954
1955 // Add loading spinner while keeping original text
1956 const originalText = submitButton.textContent;
1957 submitButton.innerHTML = `
1958 <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
1959 <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
1960 <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
1961 <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
1962 </circle>
1963 </svg>
1964 ${originalText}
1965 `;
1966
1967 submitButton.style.opacity = '0.8';
1968 } else {
1969 isSubmitting = false;
1970 submitButton.disabled = false;
1971 emailInput.disabled = false;
1972
1973 // Restore original content
1974 const originalHtml = submitButton.getAttribute('data-original-html');
1975 if (originalHtml) {
1976 submitButton.innerHTML = originalHtml;
1977 }
1978
1979 submitButton.style.opacity = '1';
1980 }
1981 }
1982
1983 // Add CSS for spinner animation if not already present
1984 if (!document.getElementById('email-spinner-styles')) {
1985 const style = document.createElement('style');
1986 style.id = 'email-spinner-styles';
1987 style.textContent = `
1988 @keyframes spin {
1989 from { transform: rotate(0deg); }
1990 to { transform: rotate(360deg); }
1991 }
1992 .email-spinner {
1993 display: inline-block;
1994 vertical-align: middle;
1995 }
1996 `;
1997 document.head.appendChild(style);
1998 }
1999
2000 // Handle email form submission with improved error handling
2001 emailForm.addEventListener('submit', function (event) {
2002 event.preventDefault();
2003
2004 // Prevent double submission
2005 if (isSubmitting) {
2006 return;
2007 }
2008
2009 const userEmail = document.getElementById('user-email').value.trim();
2010 const sessionId = getChatSession();
2011
2012 // Validate email before submission
2013 if (!userEmail) {
2014 showEmailError('Please enter your email address.');
2015 return;
2016 }
2017
2018 if (!isValidEmail(userEmail)) {
2019 showEmailError('Please enter a valid email address.');
2020 return;
2021 }
2022
2023 // Clear any existing errors
2024 clearEmailError();
2025 setSubmissionState(true);
2026
2027 // Add timeout for submission
2028 const controller = new AbortController();
2029 const timeoutId = setTimeout(() => {
2030 controller.abort();
2031 setSubmissionState(false);
2032 showEmailError('Request timed out. Please try again.');
2033 }, 15000); // 15 second timeout
2034
2035 fetch(mxchatChat.ajax_url, {
2036 method: 'POST',
2037 headers: {
2038 'Content-Type': 'application/x-www-form-urlencoded',
2039 },
2040 body: new URLSearchParams({
2041 action: 'mxchat_handle_save_email_and_response',
2042 email: userEmail,
2043 session_id: sessionId,
2044 nonce: mxchatChat.nonce,
2045 }),
2046 signal: controller.signal
2047 })
2048 .then((response) => {
2049 clearTimeout(timeoutId);
2050 if (!response.ok) {
2051 throw new Error(`HTTP error! status: ${response.status}`);
2052 }
2053 return response.json();
2054 })
2055 .then((data) => {
2056 setSubmissionState(false);
2057
2058 if (data.success) {
2059 // Show chat immediately
2060 showChatContainer();
2061
2062 // Handle bot response if provided
2063 if (data.message && typeof appendMessage === 'function') {
2064 setTimeout(() => {
2065 appendMessage('bot', data.message);
2066 if (typeof scrollToBottom === 'function') {
2067 scrollToBottom();
2068 }
2069 }, 100);
2070 }
2071 } else {
2072 showEmailError(data.message || 'Failed to save email. Please try again.');
2073 }
2074 })
2075 .catch((error) => {
2076 clearTimeout(timeoutId);
2077 setSubmissionState(false);
2078
2079 if (error.name === 'AbortError') {
2080 showEmailError('Request timed out. Please try again.');
2081 } else {
2082 console.error('Email submission error:', error);
2083 showEmailError('An error occurred. Please try again.');
2084 }
2085 });
2086 });
2087
2088 // Real-time email validation
2089 const emailInput = document.getElementById('user-email');
2090 if (emailInput) {
2091 let validationTimeout;
2092
2093 emailInput.addEventListener('input', function() {
2094 // Clear previous validation timeout
2095 if (validationTimeout) {
2096 clearTimeout(validationTimeout);
2097 }
2098
2099 // Debounce validation
2100 validationTimeout = setTimeout(() => {
2101 const email = this.value.trim();
2102 clearEmailError();
2103
2104 if (email && !isValidEmail(email)) {
2105 showEmailError('Please enter a valid email address.');
2106 }
2107 }, 500);
2108 });
2109
2110 // Handle Enter key
2111 emailInput.addEventListener('keypress', function(e) {
2112 if (e.key === 'Enter' && !isSubmitting) {
2113 emailForm.dispatchEvent(new Event('submit'));
2114 }
2115 });
2116 }
2117
2118 // Error display functions
2119 function showEmailError(message) {
2120 clearEmailError();
2121
2122 const errorDiv = document.createElement('div');
2123 errorDiv.className = 'email-error';
2124 errorDiv.style.cssText = `
2125 color: #e74c3c;
2126 font-size: 12px;
2127 margin-top: 8px;
2128 padding: 4px 0;
2129 animation: fadeInError 0.3s ease;
2130 `;
2131 errorDiv.textContent = message;
2132
2133 // Add CSS animation if not already present
2134 if (!document.getElementById('email-error-styles')) {
2135 const style = document.createElement('style');
2136 style.id = 'email-error-styles';
2137 style.textContent = `
2138 @keyframes fadeInError {
2139 from { opacity: 0; transform: translateY(-5px); }
2140 to { opacity: 1; transform: translateY(0); }
2141 }
2142 .email-input-shake {
2143 animation: shake 0.5s ease-in-out;
2144 }
2145 @keyframes shake {
2146 0%, 100% { transform: translateX(0); }
2147 25% { transform: translateX(-5px); }
2148 75% { transform: translateX(5px); }
2149 }
2150 `;
2151 document.head.appendChild(style);
2152 }
2153
2154 emailForm.appendChild(errorDiv);
2155
2156 // Add shake animation to input
2157 if (emailInput) {
2158 emailInput.classList.add('email-input-shake');
2159 setTimeout(() => {
2160 emailInput.classList.remove('email-input-shake');
2161 }, 500);
2162 }
2163 }
2164
2165 function clearEmailError() {
2166 const existingErrors = emailForm.querySelectorAll('.email-error');
2167 existingErrors.forEach(error => error.remove());
2168 }
2169
2170 // Initialize email check with delay to prevent race conditions
2171 setTimeout(checkSessionAndEmail, 100);
2172
2173 } else if (mxchatChat.email_collection_enabled) {
2174 console.error('Essential elements for email handling are missing:', {
2175 emailForm: !!emailForm,
2176 emailBlocker: !!emailBlocker,
2177 chatbotWrapper: !!chatbotWrapper
2178 });
2179 }
2180
2181
2182 // Open chatbot when pre-chat message is clicked
2183 $(document).on('click', '#pre-chat-message', function() {
2184 var chatbot = $('#floating-chatbot');
2185 if (chatbot.hasClass('hidden')) {
2186 chatbot.removeClass('hidden').addClass('visible');
2187 $('#floating-chatbot-button').addClass('hidden');
2188 $('#pre-chat-message').fadeOut(250); // Hide pre-chat message
2189 disableScroll(); // Disable scroll when chatbot opens
2190 }
2191 });
2192
2193 var closeButton = document.querySelector('.close-pre-chat-message');
2194 if (closeButton) {
2195 closeButton.addEventListener('click', function() {
2196 $('#pre-chat-message').fadeOut(200); // Hide the message
2197
2198 // Send an AJAX request to set the transient flag for 24 hours
2199 $.ajax({
2200 url: mxchatChat.ajax_url,
2201 type: 'POST',
2202 data: {
2203 action: 'mxchat_dismiss_pre_chat_message',
2204 _ajax_nonce: mxchatChat.nonce
2205 },
2206 success: function() {
2207 //console.log('Pre-chat message dismissed for 24 hours.');
2208
2209 // Ensure the message is hidden after dismissal
2210 $('#pre-chat-message').hide();
2211 },
2212 error: function() {
2213 ////console.error('Failed to dismiss pre-chat message.');
2214 }
2215 });
2216 });
2217 }
2218
2219
2220 function hasQuickQuestions() {
2221 const questionButtons = document.querySelectorAll('#mxchat-popular-questions .mxchat-popular-question');
2222 return questionButtons.length > 0;
2223 }
2224
2225 function collapseQuickQuestions() {
2226 const questionsContainer = document.getElementById('mxchat-popular-questions');
2227 if (questionsContainer && hasQuickQuestions()) {
2228 questionsContainer.classList.add('collapsed');
2229 questionsContainer.classList.add('has-been-collapsed');
2230 try {
2231 sessionStorage.setItem('mxchat_questions_collapsed', 'true');
2232 sessionStorage.setItem('mxchat_questions_has_been_collapsed', 'true');
2233 } catch (e) {
2234 // Ignore if sessionStorage is not available
2235 }
2236 }
2237 }
2238
2239 function expandQuickQuestions() {
2240 const questionsContainer = document.getElementById('mxchat-popular-questions');
2241 if (questionsContainer && hasQuickQuestions()) {
2242 questionsContainer.classList.remove('collapsed');
2243 try {
2244 sessionStorage.setItem('mxchat_questions_collapsed', 'false');
2245 } catch (e) {
2246 // Ignore if sessionStorage is not available
2247 }
2248 }
2249 }
2250
2251 function checkQuickQuestionsState() {
2252 if (!hasQuickQuestions()) {
2253 return; // Don't do anything if no questions exist
2254 }
2255
2256 try {
2257 const isCollapsed = sessionStorage.getItem('mxchat_questions_collapsed');
2258 const hasBeenCollapsed = sessionStorage.getItem('mxchat_questions_has_been_collapsed');
2259
2260 const questionsContainer = document.getElementById('mxchat-popular-questions');
2261 if (questionsContainer) {
2262 if (hasBeenCollapsed === 'true') {
2263 questionsContainer.classList.add('has-been-collapsed');
2264 }
2265 if (isCollapsed === 'true') {
2266 questionsContainer.classList.add('collapsed');
2267 }
2268 }
2269 } catch (e) {
2270 // Ignore if sessionStorage is not available
2271 }
2272 }
2273
2274
2275
2276
2277 // ====================================
2278 // MAIN INITIALIZATION
2279 // ====================================
2280
2281 if ($('#floating-chatbot').hasClass('hidden')) {
2282 $('#floating-chatbot-button').removeClass('hidden');
2283 }
2284 // Initialize when document is ready
2285 setFullHeight();
2286 initializeChatVisibility();
2287 loadChatHistory();
2288
2289 // Make functions globally available for add-ons
2290 window.hasQuickQuestions = hasQuickQuestions;
2291 window.collapseQuickQuestions = collapseQuickQuestions;
2292 window.appendMessage = appendMessage;
2293 window.appendThinkingMessage = appendThinkingMessage;
2294 window.scrollToBottom = scrollToBottom;
2295 window.scrollElementToTop = scrollElementToTop;
2296 window.replaceLastMessage = replaceLastMessage;
2297 window.callMxChat = callMxChat;
2298 window.callMxChatStream = callMxChatStream;
2299 window.shouldUseStreaming = shouldUseStreaming;
2300 window.getChatSession = getChatSession;
2301 window.getPageContext = getPageContext;
2302 window.updateStreamingMessage = updateStreamingMessage;
2303
2304 }); // End of jQuery ready
2305
2306
2307 // ====================================
2308 // GLOBAL EVENT LISTENERS (Outside jQuery)
2309 // ====================================
2310
2311 // Event listener for copy button (code blocks)
2312 document.addEventListener("click", (e) => {
2313 if (e.target.classList.contains("mxchat-copy-button")) {
2314 const copyButton = e.target;
2315 const codeBlock = copyButton
2316 .closest(".mxchat-code-block-container")
2317 .querySelector(".mxchat-code-block code");
2318
2319 if (codeBlock) {
2320 // Preserve formatting using innerText
2321 navigator.clipboard.writeText(codeBlock.innerText).then(() => {
2322 copyButton.textContent = "Copied!";
2323 copyButton.setAttribute("aria-label", "Copied to clipboard");
2324
2325 setTimeout(() => {
2326 copyButton.textContent = "Copy";
2327 copyButton.setAttribute("aria-label", "Copy to clipboard");
2328 }, 2000);
2329 });
2330 }
2331 }
2332 });
2333
2334
2335
2336
2337
2338
2339