PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.3
MxChat – AI Chatbot & Content Generation for WordPress v2.2.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.2.3, at js/chat-script.js

1,680 lines 65.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 // ====================================
62 // CORE CHAT FUNCTIONALITY
63 // ====================================
64
65 function sendMessage() {
66 var message = $('#chat-input').val(); // Get value from textarea
67 if (message) {
68 appendMessage("user", message); // Append user's message
69 $('#chat-input').val(''); // Clear the textarea
70 $('#chat-input').css('height', 'auto'); // Reset height after clearing content
71
72 // Hide the popular questions section
73 $('#mxchat-popular-questions').hide();
74
75 // Show typing indicator
76 appendThinkingMessage();
77 scrollToBottom();
78
79 callMxChat(message, function(response) {
80 // Replace typing indicator with actual response
81 replaceLastMessage("bot", response);
82 });
83 }
84 }
85
86 function sendMessageToChatbot(message) {
87 var sessionId = getChatSession(); // Reuse the session ID logic
88
89 // Hide the popular questions section
90 $('#mxchat-popular-questions').hide();
91
92 // Show thinking indicator (no need to append the user's message again)
93 appendThinkingMessage();
94 scrollToBottom();
95
96 //console.log("Sending message to chatbot:", message); // Log the message
97 //console.log("Session ID:", sessionId); // Log the session ID
98
99 // Call the chatbot using the same call logic as sendMessage
100 callMxChat(message, function(response) {
101 // ** Ensure temporary thinking message is removed before adding new response **
102 $('.temporary-message').remove();
103
104 // Replace thinking indicator with actual response
105 replaceLastMessage("bot", response);
106 });
107 }
108
109 function callMxChat(message, callback) {
110 $.ajax({
111 url: mxchatChat.ajax_url,
112 type: 'POST',
113 dataType: 'json',
114 data: {
115 action: 'mxchat_handle_chat_request',
116 message: message,
117 session_id: getChatSession(),
118 nonce: mxchatChat.nonce
119 },
120 success: function(response) {
121 // Log the full response for debugging
122 //console.log("API Response:", response);
123
124 // First check if this is a successful response by looking for text, html, or message fields
125 // This preserves compatibility with your server response format
126 if (response.text !== undefined || response.html !== undefined || response.message !== undefined ||
127 (response.success === true && response.data && response.data.status === 'waiting_for_agent')) {
128
129 // Handle successful response - this is your original success handling code
130
131 // Existing chat mode check
132 if (response.chat_mode) {
133 updateChatModeIndicator(response.chat_mode);
134 }
135 else if (response.fallbackResponse && response.fallbackResponse.chat_mode) {
136 updateChatModeIndicator(response.fallbackResponse.chat_mode);
137 }
138
139 // Add PDF filename handling
140 if (response.data && response.data.filename) {
141 showActivePdf(response.data.filename);
142 activePdfFile = response.data.filename;
143 }
144
145 // Add redirect check here
146 if (response.redirect_url) {
147 let responseText = response.text || '';
148 if (responseText) {
149 replaceLastMessage("bot", responseText);
150 }
151 setTimeout(() => {
152 window.location.href = response.redirect_url;
153 }, 1500);
154 return;
155 }
156
157 // Check for live agent response
158 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
159 updateChatModeIndicator('agent');
160 return;
161 }
162
163 // Handle other responses
164 let responseText = response.text || '';
165 let responseHtml = response.html || '';
166 let responseMessage = response.message || '';
167
168 if (responseText === 'You are now chatting with the AI chatbot.') {
169 updateChatModeIndicator('ai');
170 }
171
172 // Handle the message and show notification if chat is hidden
173 if (responseText || responseHtml || responseMessage) {
174 // Update the messages as before
175 if (responseText && responseHtml) {
176 replaceLastMessage("bot", responseText, responseHtml);
177 } else if (responseText) {
178 replaceLastMessage("bot", responseText);
179 } else if (responseHtml) {
180 replaceLastMessage("bot", "", responseHtml);
181 } else if (responseMessage) {
182 replaceLastMessage("bot", responseMessage);
183 }
184
185 // Check if chat is hidden and show notification
186 if ($('#floating-chatbot').hasClass('hidden')) {
187 const badge = $('#chat-notification-badge');
188 if (badge.length) {
189 badge.show();
190 }
191 }
192 } else {
193 //console.error("Unexpected response format:", response);
194 replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists.");
195 }
196
197 if (response.message_id) {
198 lastSeenMessageId = response.message_id;
199 }
200
201 return;
202 }
203
204 // If we got here, it's likely an error response
205 // Now we can check for error conditions with our robust error handling
206
207 let errorMessage = "";
208 let errorCode = "";
209
210 // Check various possible error locations in the response
211 if (response.data && response.data.error_message) {
212 errorMessage = response.data.error_message;
213 errorCode = response.data.error_code || "";
214 } else if (response.error_message) {
215 errorMessage = response.error_message;
216 errorCode = response.error_code || "";
217 } else if (response.message) {
218 errorMessage = response.message;
219 } else if (typeof response.data === 'string') {
220 errorMessage = response.data;
221 } else if (!response.success) {
222 // Explicit check for success: false without other error info
223 errorMessage = "An error occurred. Please try again or contact support.";
224 } else {
225 // Fallback for any other unexpected response format
226 errorMessage = "Unexpected response received. Please try again or contact support.";
227 }
228
229 // Log the error with code for debugging
230 //console.log("Response data:", response.data);
231 //console.error("API Error:", errorMessage, "Code:", errorCode);
232
233 // Format user-friendly error message
234 let displayMessage = errorMessage;
235
236 // Customize message for admin users
237 if (mxchatChat.is_admin) {
238 // For admin users, show more technical details including error code
239 displayMessage = errorMessage + (errorCode ? " (Error code: " + errorCode + ")" : "");
240 }
241
242 replaceLastMessage("bot", displayMessage);
243 },
244 error: function(xhr, status, error) {
245 console.error("AJAX Error:", status, error);
246 //console.log("Response Text:", xhr.responseText);
247
248 let errorMessage = "An unexpected error occurred.";
249
250 // Try to parse the response if it's JSON
251 try {
252 const responseJson = JSON.parse(xhr.responseText);
253 //console.log("Parsed error response:", responseJson);
254
255 if (responseJson.data && responseJson.data.error_message) {
256 errorMessage = responseJson.data.error_message;
257 } else if (responseJson.message) {
258 errorMessage = responseJson.message;
259 }
260 } catch (e) {
261 // Not JSON or parsing failed, use HTTP status based messages
262 if (xhr.status === 0) {
263 errorMessage = "Network error: Please check your internet connection.";
264 } else if (xhr.status === 403) {
265 errorMessage = "Access denied: Your session may have expired. Please refresh the page.";
266 } else if (xhr.status === 404) {
267 errorMessage = "API endpoint not found. Please contact support.";
268 } else if (xhr.status === 429) {
269 errorMessage = "Too many requests. Please try again in a moment.";
270 } else if (xhr.status >= 500) {
271 errorMessage = "Server error: The server encountered an issue. Please try again later.";
272 }
273 }
274
275 replaceLastMessage("bot", errorMessage);
276 }
277 });
278 }
279
280 function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
281 try {
282 // Determine styles based on sender type
283 let messageClass, bgColor, fontColor;
284
285 if (sender === "user") {
286 messageClass = "user-message";
287 bgColor = userMessageBgColor;
288 fontColor = userMessageFontColor;
289 // Only sanitize user input
290 messageText = sanitizeUserInput(messageText);
291 } else if (sender === "agent") {
292 messageClass = "agent-message";
293 bgColor = liveAgentMessageBgColor;
294 fontColor = liveAgentMessageFontColor;
295 } else {
296 messageClass = "bot-message";
297 bgColor = botMessageBgColor;
298 fontColor = botMessageFontColor;
299 }
300
301 const messageDiv = $('<div>')
302 .addClass(messageClass)
303 .attr('dir', 'auto') // Add dir="auto" for automatic text direction
304 .css({
305 'background': bgColor,
306 'color': fontColor,
307 'margin-bottom': '1em'
308 });
309
310 // Process the message content based on sender
311 let fullMessage;
312 if (sender === "user") {
313 // For user messages, apply linkify after sanitization
314 fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
315 } else {
316 // For bot/agent messages, preserve HTML
317 fullMessage = messageText;
318 }
319
320 // Add images if provided
321 if (images && images.length > 0) {
322 fullMessage += '<div class="image-gallery" dir="auto">'; // Add dir="auto" to image gallery
323 images.forEach(img => {
324 // Ensure image URLs and titles are properly escaped
325 const safeTitle = sanitizeUserInput(img.title);
326 const safeUrl = encodeURI(img.image_url);
327 const safeThumbnail = encodeURI(img.thumbnail_url);
328
329 fullMessage += `
330 <div style="margin-bottom: 10px;">
331 <strong>${safeTitle}</strong><br>
332 <a href="${safeUrl}" target="_blank">
333 <img src="${safeThumbnail}" alt="${safeTitle}" style="max-width: 100px; height: auto; margin: 5px;" />
334 </a>
335 </div>`;
336 });
337 fullMessage += '</div>';
338 }
339
340 // Append HTML content if provided
341 if (messageHtml && sender !== "user") {
342 fullMessage += '<br><br>' + messageHtml;
343 }
344
345 messageDiv.html(fullMessage);
346
347 if (isTemporary) {
348 messageDiv.addClass('temporary-message');
349 }
350
351 messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() {
352 if (sender === "bot") {
353 const lastUserMessage = $('#chat-box').find('.user-message').last();
354 if (lastUserMessage.length) {
355 scrollElementToTop(lastUserMessage);
356 }
357 }
358 });
359
360 if (messageText.id) {
361 lastSeenMessageId = messageText.id;
362 hideNotification();
363 }
364 } catch (error) {
365 console.error("Error rendering message:", error);
366 }
367 }
368
369 function replaceLastMessage(sender, responseText, responseHtml = '', images = []) {
370 var messageClass = sender === "user" ? "user-message" : sender === "agent" ? "agent-message" : "bot-message";
371 var lastMessageDiv = $('#chat-box').find('.bot-message.temporary-message, .agent-message.temporary-message').last();
372
373 // Determine styles
374 let bgColor, fontColor;
375 if (sender === "user") {
376 bgColor = userMessageBgColor;
377 fontColor = userMessageFontColor;
378 } else if (sender === "agent") {
379 bgColor = liveAgentMessageBgColor;
380 fontColor = liveAgentMessageFontColor;
381 } else {
382 bgColor = botMessageBgColor;
383 fontColor = botMessageFontColor;
384 }
385
386 var fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(responseText))));
387 if (responseHtml) {
388 fullMessage += '<br><br>' + responseHtml;
389 }
390
391 if (images.length > 0) {
392 fullMessage += '<div class="image-gallery" dir="auto">'; // Add dir="auto" to image gallery
393 images.forEach(img => {
394 fullMessage += `
395 <div style="margin-bottom: 10px;">
396 <strong>${img.title}</strong><br>
397 <a href="${img.image_url}" target="_blank">
398 <img src="${img.thumbnail_url}" alt="${img.title}" style="max-width: 100px; height: auto; margin: 5px;" />
399 </a>
400 </div>`;
401 });
402 fullMessage += '</div>';
403 }
404
405 if (lastMessageDiv.length) {
406 lastMessageDiv.fadeOut(200, function() {
407 $(this)
408 .html(fullMessage)
409 .removeClass('bot-message user-message')
410 .addClass(messageClass)
411 .attr('dir', 'auto') // Add dir="auto" for automatic text direction
412 .css({
413 'background-color': bgColor,
414 'color': fontColor,
415 })
416 .removeClass('temporary-message')
417 .fadeIn(200, function() {
418 if (sender === "bot" || sender === "agent") {
419 const lastUserMessage = $('#chat-box').find('.user-message').last();
420 if (lastUserMessage.length) {
421 scrollElementToTop(lastUserMessage);
422 }
423 // Show notification if chat is hidden
424 if ($('#floating-chatbot').hasClass('hidden')) {
425 showNotification();
426 }
427 }
428 });
429 });
430 } else {
431 appendMessage(sender, responseText, responseHtml, images);
432 }
433 }
434
435 function appendThinkingMessage() {
436 // Remove any existing thinking dots first
437 $('.thinking-dots').remove();
438
439 // Retrieve the bot message font color and background color
440 var botMessageFontColor = mxchatChat.bot_message_font_color;
441 var botMessageBgColor = mxchatChat.bot_message_bg_color;
442
443
444 var thinkingHtml = '<div class="thinking-dots-container">' +
445 '<div class="thinking-dots">' +
446 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
447 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
448 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
449 '</div>' +
450 '</div>';
451
452 // Append the thinking dots to the chat container (or within the temporary message div)
453 $("#chat-box").append('<div class="bot-message temporary-message" style="background-color: ' + botMessageBgColor + ';">' + thinkingHtml + '</div>');
454 scrollToBottom();
455 }
456
457 function removeThinkingDots() {
458 $('.thinking-dots').closest('.temporary-message').remove();
459 }
460
461
462 // ====================================
463 // TEXT FORMATTING & PROCESSING
464 // ====================================
465
466 function linkify(inputText) {
467 if (!inputText) return '';
468
469 // Process markdown headers
470 let processedText = formatMarkdownHeaders(inputText);
471
472 // Process markdown links
473 const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
474 processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
475 const safeUrl = encodeURI(url);
476 const safeText = sanitizeUserInput(text);
477 return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
478 });
479
480 // Process phone numbers (tel:)
481 const phonePattern = /\[([^\]]+)\]\((tel:[\d+]+)\)/g;
482 processedText = processedText.replace(phonePattern, (match, text, phone) => {
483 const safePhone = encodeURI(phone);
484 const safeText = sanitizeUserInput(text);
485 return `<a href="${safePhone}">${safeText}</a>`;
486 });
487
488 // Process standalone URLs
489 const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
490 processedText = processedText.replace(urlPattern, (match, prefix, url) => {
491 const safeUrl = encodeURI(url);
492 return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
493 });
494
495 // Process www. URLs
496 const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
497 processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
498 const safeUrl = encodeURI(`http://${url}`);
499 return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
500 });
501
502 return processedText;
503 }
504
505 function formatMarkdownHeaders(text) {
506 // Handle h1 to h6 headers
507 return text.replace(/^(#{1,6})\s(.+)$/gm, function(match, hashes, content) {
508 const level = hashes.length;
509 return `<h${level} class="chat-heading">${content}</h${level}>`;
510 });
511 }
512
513 function formatBoldText(text) {
514 return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
515 }
516
517 function convertNewlinesToBreaks(text) {
518 // Split the text into paragraphs (marked by double newlines or multiple <br> tags)
519 const paragraphs = text.split(/(?:\n\n|\<br\>\s*\<br\>)/g);
520
521 // Wrap each paragraph in <p> tags
522 return paragraphs
523 .map(para => `<p>${para.trim()}</p>`)
524 .join('');
525 }
526
527 function formatCodeBlocks(text) {
528 // First handle raw PHP tags
529 text = text.replace(/(<\?php[\s\S]*?\?>)/g, (match) => {
530 return `<pre><code class="language-php">${escapeHtml(match)}</code></pre>`;
531 });
532
533 // Then handle code blocks with backticks
534 text = text.replace(/```php5?\n([\s\S]+?)```/gi, (match, code) => {
535 return `<pre><code class="language-php">${escapeHtml(code)}</code></pre>`;
536 });
537
538 return text;
539 }
540
541 function sanitizeUserInput(text) {
542 const div = document.createElement('div');
543 div.textContent = text;
544 return div.innerHTML;
545 }
546
547
548 function escapeHtml(unsafe) {
549 // First check if it's already a code block
550 if (unsafe.includes('<pre><code') || unsafe.includes('</code></pre>')) {
551 return unsafe;
552 }
553
554 return unsafe
555 .replace(/&/g, "&amp;")
556 .replace(/</g, "&lt;")
557 .replace(/>/g, "&gt;")
558 .replace(/"/g, "&quot;")
559 .replace(/'/g, "&#039;");
560 }
561
562 function decodeHTMLEntities(text) {
563 var textArea = document.createElement('textarea');
564 textArea.innerHTML = text;
565 return textArea.value;
566 }
567
568
569 // ====================================
570 // UI & SCROLLING CONTROLS
571 // ====================================
572
573 function scrollToBottom(instant = false) {
574 var chatBox = $('#chat-box');
575 if (instant) {
576 // Instantly set the scroll position to the bottom
577 chatBox.scrollTop(chatBox.prop("scrollHeight"));
578 } else {
579 // Use requestAnimationFrame for smoother scrolling if needed
580 let start = null;
581 const scrollHeight = chatBox.prop("scrollHeight");
582 const initialScroll = chatBox.scrollTop();
583 const distance = scrollHeight - initialScroll;
584 const duration = 500; // Duration in ms
585
586 function smoothScroll(timestamp) {
587 if (!start) start = timestamp;
588 const progress = timestamp - start;
589 const currentScroll = initialScroll + (distance * (progress / duration));
590 chatBox.scrollTop(currentScroll);
591
592 if (progress < duration) {
593 requestAnimationFrame(smoothScroll);
594 } else {
595 chatBox.scrollTop(scrollHeight); // Ensure it's exactly at the bottom
596 }
597 }
598
599 requestAnimationFrame(smoothScroll);
600 }
601 }
602
603 function scrollElementToTop(element) {
604 var chatBox = $('#chat-box');
605 var elementTop = element.position().top + chatBox.scrollTop();
606 chatBox.animate({ scrollTop: elementTop }, 500);
607 }
608
609 function showChatWidget() {
610 // First ensure display is set
611 $('#floating-chatbot-button').css('display', 'flex');
612 // Then handle the fade
613 $('#floating-chatbot-button').fadeTo(500, 1);
614 // Force visibility
615 $('#floating-chatbot-button').removeClass('hidden');
616 //console.log('Showing widget');
617 }
618
619 function hideChatWidget() {
620 $('#floating-chatbot-button').css('display', 'none');
621 $('#floating-chatbot-button').addClass('hidden');
622 //console.log('Hiding widget');
623 }
624
625 function disableScroll() {
626 if (isMobile()) {
627 $('body').css('overflow', 'hidden');
628 }
629 }
630
631 function enableScroll() {
632 if (isMobile()) {
633 $('body').css('overflow', '');
634 }
635 }
636
637 function isMobile() {
638 // This can be a simple check, or more sophisticated detection of mobile devices
639 return window.innerWidth <= 768; // Example threshold for mobile devices
640 }
641
642 function setFullHeight() {
643 var vh = $(window).innerHeight() * 0.01;
644 $(':root').css('--vh', vh + 'px');
645 }
646
647
648 // ====================================
649 // NOTIFICATION SYSTEM
650 // ====================================
651
652 function createNotificationBadge() {
653 //console.log("Creating notification badge...");
654 const chatButton = document.getElementById('floating-chatbot-button');
655 //console.log("Chat button found:", !!chatButton);
656
657 if (!chatButton) return;
658
659 // Remove any existing badge first
660 const existingBadge = chatButton.querySelector('.chat-notification-badge');
661 if (existingBadge) {
662 //console.log("Removing existing badge");
663 existingBadge.remove();
664 }
665
666 notificationBadge = document.createElement('div');
667 notificationBadge.className = 'chat-notification-badge';
668 notificationBadge.style.cssText = `
669 display: none;
670 position: absolute;
671 top: -5px;
672 right: -5px;
673 background-color: red;
674 color: white;
675 border-radius: 50%;
676 padding: 4px 8px;
677 font-size: 12px;
678 font-weight: bold;
679 z-index: 10001;
680 `;
681 chatButton.style.position = 'relative';
682 chatButton.appendChild(notificationBadge);
683
684 }
685
686 function showNotification() {
687 const badge = document.getElementById('chat-notification-badge');
688 if (badge && $('#floating-chatbot').hasClass('hidden')) {
689 badge.style.display = 'block';
690 badge.textContent = '1';
691 }
692 }
693
694 function hideNotification() {
695 const badge = document.getElementById('chat-notification-badge');
696 if (badge) {
697 badge.style.display = 'none';
698 }
699 }
700
701 function startNotificationChecking() {
702 const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
703 if (!chatPersistenceEnabled) return;
704
705 createNotificationBadge();
706 notificationCheckInterval = setInterval(checkForNewMessages, 30000); // Check every 30 seconds
707 }
708
709 function stopNotificationChecking() {
710 if (notificationCheckInterval) {
711 clearInterval(notificationCheckInterval);
712 }
713 }
714
715 function checkForNewMessages() {
716 const sessionId = getChatSession();
717 const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
718
719 if (!chatPersistenceEnabled) return;
720
721 $.ajax({
722 url: mxchatChat.ajax_url,
723 type: 'POST',
724 data: {
725 action: 'mxchat_check_new_messages',
726 session_id: sessionId,
727 last_seen_id: lastSeenMessageId,
728 nonce: mxchatChat.nonce
729 },
730 success: function(response) {
731 if (response.success && response.data.hasNewMessages) {
732 showNotification();
733 }
734 }
735 });
736 }
737
738
739 // ====================================
740 // LIVE AGENT FUNCTIONALITY
741 // ====================================
742
743 function updateChatModeIndicator(mode) {
744 const indicator = document.getElementById('chat-mode-indicator');
745 if (indicator) {
746 // For Live Agent, keep as is; for AI mode, use the customized text
747 if (mode === 'agent') {
748 indicator.textContent = 'Live Agent';
749 } else {
750 // Get the custom AI agent text from a data attribute we'll add to the element
751 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
752 indicator.textContent = customAiText;
753 }
754 }
755 // Start or stop polling based on mode
756 if (mode === 'agent') {
757 startPolling();
758 } else {
759 stopPolling();
760 }
761 }
762
763 function startPolling() {
764 // Clear any existing interval first
765 stopPolling();
766 // Start new polling interval
767 pollingInterval = setInterval(checkForAgentMessages, 5000);
768 //console.log("Started agent message polling");
769 }
770
771 function stopPolling() {
772 if (pollingInterval) {
773 clearInterval(pollingInterval);
774 pollingInterval = null;
775 //console.log("Stopped agent message polling");
776 }
777 }
778
779 function checkForAgentMessages() {
780 const sessionId = getChatSession();
781 $.ajax({
782 url: mxchatChat.ajax_url,
783 type: 'POST',
784 dataType: 'json',
785 data: {
786 action: 'mxchat_fetch_new_messages',
787 session_id: sessionId,
788 last_seen_id: lastSeenMessageId,
789 nonce: mxchatChat.nonce
790 },
791 success: function (response) {
792 if (response.success && response.data?.new_messages) {
793 let hasNewMessage = false;
794
795 response.data.new_messages.forEach(function (message) {
796 if (message.role === "agent" && !processedMessageIds.has(message.id)) {
797 hasNewMessage = true;
798 replaceLastMessage("agent", message.content);
799 lastSeenMessageId = message.id;
800 processedMessageIds.add(message.id);
801 }
802 });
803
804 if (hasNewMessage && $('#floating-chatbot').hasClass('hidden')) {
805 showNotification();
806 }
807
808 scrollToBottom(true);
809 }
810 },
811 error: function (xhr, status, error) {
812 console.error("Polling error:", xhr, status, error);
813 }
814 });
815 }
816
817
818 // ====================================
819 // CHAT HISTORY & PERSISTENCE
820 // ====================================
821
822 function loadChatHistory() {
823 var sessionId = getChatSession();
824 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
825
826 if (chatPersistenceEnabled && sessionId) {
827 $.ajax({
828 url: mxchatChat.ajax_url,
829 type: 'POST',
830 dataType: 'json',
831 data: {
832 action: 'mxchat_fetch_conversation_history',
833 session_id: sessionId
834 },
835 success: function(response) {
836 if (response.success && response.data && Array.isArray(response.data.conversation)) {
837
838
839 var $chatBox = $('#chat-box');
840 var $fragment = $(document.createDocumentFragment());
841 let highestMessageId = lastSeenMessageId;
842
843 if (response.data.chat_mode) {
844 updateChatModeIndicator(response.data.chat_mode);
845 }
846
847 $.each(response.data.conversation, function(index, message) {
848 // Skip agent messages if persistence is off
849 if (!chatPersistenceEnabled && message.role === 'agent') {
850 return;
851 }
852
853 var messageClass, messageBgColor, messageFontColor;
854
855 switch (message.role) {
856 case 'user':
857 messageClass = 'user-message';
858 messageBgColor = userMessageBgColor;
859 messageFontColor = userMessageFontColor;
860 break;
861 case 'agent':
862 messageClass = 'agent-message';
863 messageBgColor = liveAgentMessageBgColor;
864 messageFontColor = liveAgentMessageFontColor;
865 break;
866 default:
867 messageClass = 'bot-message';
868 messageBgColor = botMessageBgColor;
869 messageFontColor = botMessageFontColor;
870 break;
871 }
872
873 var messageElement = $('<div>').addClass(messageClass)
874 .css({
875 'background': messageBgColor,
876 'color': messageFontColor
877 });
878
879 var content = message.content;
880 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
881 content = decodeHTMLEntities(content);
882
883 if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
884 messageElement.html(content);
885 } else {
886 var formattedContent = linkify(
887 formatBoldText(
888 convertNewlinesToBreaks(formatCodeBlocks(content))
889 )
890 );
891 messageElement.html(formattedContent);
892 }
893
894 $fragment.append(messageElement);
895
896 // In loadChatHistory, change this part:
897 if (message.id) {
898 highestMessageId = Math.max(highestMessageId, message.id);
899 processedMessageIds.add(message.id); // Add all message IDs to processed set
900 }
901 });
902
903 $chatBox.append($fragment);
904 scrollToBottom(true);
905
906 if (response.data.conversation.length > 0) {
907 $('#mxchat-popular-questions').hide();
908 }
909
910 // Update lastSeenMessageId after history loads
911 lastSeenMessageId = highestMessageId;
912
913 // Only update chat mode if persistence is enabled
914 if (chatPersistenceEnabled && response.data.conversation.length > 0) {
915 var lastMessage = response.data.conversation[response.data.conversation.length - 1];
916 if (lastMessage.role === 'agent') {
917 updateChatModeIndicator('agent');
918 }
919 }
920 } else {
921 console.warn("No conversation history found.");
922 }
923 },
924 error: function(xhr, status, error) {
925 console.error("Error loading chat history:", status, error);
926 appendMessage("bot", "Unable to load chat history.");
927 }
928 });
929 } else {
930 console.warn("Chat persistence is disabled or no session ID found. Not loading history.");
931 }
932 }
933
934
935 // ====================================
936 // FILE UPLOAD FUNCTIONALITY
937 // ====================================
938
939 function addSafeEventListener(elementId, eventType, handler) {
940 const element = document.getElementById(elementId);
941 if (element) {
942 element.addEventListener(eventType, handler);
943 }
944 }
945
946 function showActivePdf(filename) {
947 const container = document.getElementById('active-pdf-container');
948 const nameElement = document.getElementById('active-pdf-name');
949
950 if (!container || !nameElement) {
951 console.error('PDF container elements not found');
952 return;
953 }
954
955 nameElement.textContent = filename;
956 container.style.display = 'flex';
957 }
958
959 function showActiveWord(filename) {
960 const container = document.getElementById('active-word-container');
961 const nameElement = document.getElementById('active-word-name');
962
963 if (!container || !nameElement) {
964 console.error('Word document container elements not found');
965 return;
966 }
967
968 nameElement.textContent = filename;
969 container.style.display = 'flex';
970 }
971
972 function removeActivePdf() {
973 const container = document.getElementById('active-pdf-container');
974 const nameElement = document.getElementById('active-pdf-name');
975
976 if (!container || !nameElement || !activePdfFile) return;
977
978 fetch(mxchatChat.ajax_url, {
979 method: 'POST',
980 headers: {
981 'Content-Type': 'application/x-www-form-urlencoded',
982 },
983 body: new URLSearchParams({
984 'action': 'mxchat_remove_pdf',
985 'session_id': sessionId,
986 'nonce': mxchatChat.nonce
987 })
988 })
989 .then(response => response.json())
990 .then(data => {
991 if (data.success) {
992 container.style.display = 'none';
993 nameElement.textContent = '';
994 activePdfFile = null;
995 appendMessage('bot', 'PDF removed.');
996 }
997 })
998 .catch(error => {
999 console.error('Error removing PDF:', error);
1000 });
1001 }
1002
1003 function removeActiveWord() {
1004 const container = document.getElementById('active-word-container');
1005 const nameElement = document.getElementById('active-word-name');
1006
1007 if (!container || !nameElement || !activeWordFile) return;
1008
1009 fetch(mxchatChat.ajax_url, {
1010 method: 'POST',
1011 headers: {
1012 'Content-Type': 'application/x-www-form-urlencoded',
1013 },
1014 body: new URLSearchParams({
1015 'action': 'mxchat_remove_word',
1016 'session_id': sessionId,
1017 'nonce': mxchatChat.nonce
1018 })
1019 })
1020 .then(response => response.json())
1021 .then(data => {
1022 if (data.success) {
1023 container.style.display = 'none';
1024 nameElement.textContent = '';
1025 activeWordFile = null;
1026 appendMessage('bot', 'Word document removed.');
1027 }
1028 })
1029 .catch(error => {
1030 console.error('Error removing Word document:', error);
1031 });
1032 }
1033
1034 function checkInitialDocumentStatus() {
1035 if (!sessionId) return;
1036
1037 // Check PDF status
1038 fetch(mxchatChat.ajax_url, {
1039 method: 'POST',
1040 headers: {
1041 'Content-Type': 'application/x-www-form-urlencoded',
1042 },
1043 body: new URLSearchParams({
1044 'action': 'mxchat_check_pdf_status',
1045 'session_id': sessionId,
1046 'nonce': mxchatChat.nonce
1047 })
1048 })
1049 .then(response => response.json())
1050 .then(data => {
1051 if (data.success && data.data.filename) {
1052 showActivePdf(data.data.filename);
1053 activePdfFile = data.data.filename;
1054 }
1055 })
1056 .catch(error => {
1057 console.error('Error checking PDF status:', error);
1058 });
1059
1060 // Check Word document status
1061 fetch(mxchatChat.ajax_url, {
1062 method: 'POST',
1063 headers: {
1064 'Content-Type': 'application/x-www-form-urlencoded',
1065 },
1066 body: new URLSearchParams({
1067 'action': 'mxchat_check_word_status',
1068 'session_id': sessionId,
1069 'nonce': mxchatChat.nonce
1070 })
1071 })
1072 .then(response => response.json())
1073 .then(data => {
1074 if (data.success && data.data.filename) {
1075 showActiveWord(data.data.filename);
1076 activeWordFile = data.data.filename;
1077 }
1078 })
1079 .catch(error => {
1080 console.error('Error checking Word document status:', error);
1081 });
1082 }
1083
1084
1085 // ====================================
1086 // CONSENT & COMPLIANCE (GDPR)
1087 // ====================================
1088
1089 function initializeChatVisibility() {
1090 //console.log('Initializing chat visibility');
1091 const complianzEnabled = mxchatChat.complianz_toggle === 'on' ||
1092 mxchatChat.complianz_toggle === '1' ||
1093 mxchatChat.complianz_toggle === 1;
1094
1095 if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') {
1096 // Initial check
1097 checkConsentAndShowChat();
1098
1099 // Listen for consent changes
1100 $(document).on('cmplz_status_change', function(event) {
1101 //console.log('Status change detected');
1102 checkConsentAndShowChat();
1103 });
1104 } else {
1105 // If Complianz is not enabled, always show
1106 $('#floating-chatbot-button')
1107 .css('display', 'flex')
1108 .removeClass('hidden no-consent')
1109 .fadeTo(500, 1);
1110
1111 // Also check pre-chat message when Complianz is not enabled
1112 checkPreChatDismissal();
1113 }
1114 }
1115
1116
1117 function checkConsentAndShowChat() {
1118 var consentStatus = cmplz_has_consent('marketing');
1119 var consentType = complianz.consenttype;
1120
1121 //console.log('Checking consent:', {status: consentStatus,type: consentType});
1122
1123 let $widget = $('#floating-chatbot-button');
1124 let $chatbot = $('#floating-chatbot');
1125 let $preChat = $('#pre-chat-message');
1126
1127 if (consentStatus === true) {
1128 //console.log('Consent granted - showing widget');
1129 $widget
1130 .removeClass('no-consent')
1131 .css('display', 'flex')
1132 .removeClass('hidden')
1133 .fadeTo(500, 1);
1134 $chatbot.removeClass('no-consent');
1135
1136 // Show pre-chat message if not dismissed
1137 checkPreChatDismissal();
1138 } else {
1139 //console.log('No consent - hiding widget');
1140 $widget
1141 .addClass('no-consent')
1142 .fadeTo(500, 0, function() {
1143 $(this)
1144 .css('display', 'none')
1145 .addClass('hidden');
1146 });
1147 $chatbot.addClass('no-consent');
1148
1149 // Hide pre-chat message when no consent
1150 $preChat.hide();
1151 }
1152 }
1153
1154
1155 // ====================================
1156 // PRE-CHAT MESSAGE HANDLING
1157 // ====================================
1158
1159 function checkPreChatDismissal() {
1160 $.ajax({
1161 url: mxchatChat.ajax_url,
1162 type: 'POST',
1163 data: {
1164 action: 'mxchat_check_pre_chat_message_status',
1165 _ajax_nonce: mxchatChat.nonce
1166 },
1167 success: function(response) {
1168 if (response.success && !response.data.dismissed) {
1169 $('#pre-chat-message').fadeIn(250);
1170 } else {
1171 $('#pre-chat-message').hide();
1172 }
1173 },
1174 error: function() {
1175 console.error('Failed to check pre-chat message dismissal status.');
1176 }
1177 });
1178 }
1179
1180 function handlePreChatDismissal() {
1181 $('#pre-chat-message').fadeOut(200);
1182 $.ajax({
1183 url: mxchatChat.ajax_url,
1184 type: 'POST',
1185 data: {
1186 action: 'mxchat_dismiss_pre_chat_message',
1187 _ajax_nonce: mxchatChat.nonce
1188 },
1189 success: function() {
1190 $('#pre-chat-message').hide();
1191 },
1192 error: function() {
1193 console.error('Failed to dismiss pre-chat message.');
1194 }
1195 });
1196 }
1197
1198
1199 // ====================================
1200 // UTILITY FUNCTIONS
1201 // ====================================
1202
1203 function copyToClipboard(text) {
1204 var tempInput = $('<input>');
1205 $('body').append(tempInput);
1206 tempInput.val(text).select();
1207 document.execCommand('copy');
1208 tempInput.remove();
1209 }
1210
1211
1212 function isImageHtml(str) {
1213 return str.startsWith('<img') && str.endsWith('>');
1214 }
1215
1216
1217 // ====================================
1218 // EVENT HANDLERS & INITIALIZATION
1219 // ====================================
1220
1221 // Enter key handler for chat input
1222 $('#chat-input').keypress(function(e) {
1223 if (e.which == 13 && !e.shiftKey) { // Check if "Enter" is pressed without Shift
1224 e.preventDefault(); // Prevent default "Enter" behavior
1225 $('#send-button').click(); // Trigger send button click
1226 }
1227 });
1228
1229 // Send button click handler
1230 $('#send-button').click(function() {
1231 sendMessage();
1232 });
1233
1234 // Popular questions click handler
1235 $('.mxchat-popular-question').on('click', function () {
1236 var question = $(this).text(); // Get the text of the clicked question
1237
1238 // Append the question as if the user typed it
1239 appendMessage("user", question);
1240
1241 // Send the question to the server (backend)
1242 sendMessageToChatbot(question);
1243 });
1244
1245 // Pre-chat message dismissal handlers
1246 $(document).on('click', '.close-pre-chat-message', function(e) {
1247 e.stopPropagation();
1248 handlePreChatDismissal();
1249 });
1250
1251 // Chatbot visibility toggle handlers
1252 $(document).on('click', '#floating-chatbot-button', function() {
1253 var chatbot = $('#floating-chatbot');
1254 if (chatbot.hasClass('hidden')) {
1255 chatbot.removeClass('hidden').addClass('visible');
1256 $(this).addClass('hidden');
1257 $('#chat-notification-badge').hide(); // Hide notification when opening chat
1258 disableScroll();
1259 $('#pre-chat-message').fadeOut(250);
1260 } else {
1261 chatbot.removeClass('visible').addClass('hidden');
1262 $(this).removeClass('hidden');
1263 enableScroll();
1264 checkPreChatDismissal();
1265 }
1266 });
1267
1268 $(document).on('click', '#exit-chat-button', function() {
1269 $('#floating-chatbot').addClass('hidden').removeClass('visible');
1270 $('#floating-chatbot-button').removeClass('hidden');
1271 enableScroll();
1272 });
1273
1274 $(document).on('click', '.close-pre-chat-message', function(e) {
1275 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
1276 $('#pre-chat-message').fadeOut(200, function() {
1277 $(this).remove();
1278 });
1279 });
1280
1281 // Add to Cart button handler
1282 $(document).on('click', '.mxchat-add-to-cart-button', function() {
1283 var productId = $(this).data('product-id');
1284 // Add a special prefix to indicate this is from button
1285 appendMessage("user", "add to cart");
1286 sendMessageToChatbot("!addtocart"); // Special command to indicate button click
1287 });
1288
1289 // PDF upload button handlers
1290 if (document.getElementById('pdf-upload-btn')) {
1291 document.getElementById('pdf-upload-btn').addEventListener('click', function() {
1292 document.getElementById('pdf-upload').click();
1293 });
1294 }
1295
1296 // Word upload button handlers
1297 if (document.getElementById('word-upload-btn')) {
1298 document.getElementById('word-upload-btn').addEventListener('click', function() {
1299 document.getElementById('word-upload').click();
1300 });
1301 }
1302
1303 // PDF file input change handler
1304 addSafeEventListener('pdf-upload', 'change', async function(e) {
1305 const file = e.target.files[0];
1306
1307 if (!file || file.type !== 'application/pdf') {
1308 alert('Please select a valid PDF file.');
1309 return;
1310 }
1311
1312 if (!sessionId) {
1313 console.error('No session ID found');
1314 alert('Error: No session ID found');
1315 return;
1316 }
1317
1318 if (!mxchatChat || !mxchatChat.ajax_url || !mxchatChat.nonce) {
1319 console.error('mxchatChat not properly configured:', mxchatChat);
1320 alert('Error: Ajax configuration missing');
1321 return;
1322 }
1323
1324 // Disable buttons and show loading state
1325 const uploadBtn = document.getElementById('pdf-upload-btn');
1326 const sendBtn = document.getElementById('send-button');
1327 const originalBtnContent = uploadBtn.innerHTML;
1328
1329 try {
1330 const formData = new FormData();
1331 formData.append('action', 'mxchat_upload_pdf');
1332 formData.append('pdf_file', file);
1333 formData.append('session_id', sessionId);
1334 formData.append('nonce', mxchatChat.nonce);
1335
1336 uploadBtn.disabled = true;
1337 sendBtn.disabled = true;
1338 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1339 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1340 </svg>`;
1341
1342 const response = await fetch(mxchatChat.ajax_url, {
1343 method: 'POST',
1344 body: formData
1345 });
1346
1347 const data = await response.json();
1348
1349 if (data.success) {
1350 // Hide popular questions if they exist
1351 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1352 if (popularQuestionsContainer) {
1353 popularQuestionsContainer.style.display = 'none';
1354 }
1355
1356 // Show the active PDF name
1357 showActivePdf(data.data.filename);
1358
1359 appendMessage('bot', data.data.message);
1360 scrollToBottom();
1361 activePdfFile = data.data.filename;
1362 } else {
1363 console.error('Upload failed:', data.data);
1364 alert('Failed to upload PDF. Please try again.');
1365 }
1366 } catch (error) {
1367 console.error('Upload error:', error);
1368 alert('Error uploading file. Please try again.');
1369 } finally {
1370 uploadBtn.disabled = false;
1371 sendBtn.disabled = false;
1372 uploadBtn.innerHTML = originalBtnContent;
1373 this.value = ''; // Reset file input
1374 }
1375 });
1376
1377 // Word file input change handler
1378 addSafeEventListener('word-upload', 'change', async function(e) {
1379 const file = e.target.files[0];
1380
1381 if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1382 alert('Please select a valid Word document (.docx).');
1383 return;
1384 }
1385
1386 if (!sessionId) {
1387 console.error('No session ID found');
1388 alert('Error: No session ID found');
1389 return;
1390 }
1391
1392 // Disable buttons and show loading state
1393 const uploadBtn = document.getElementById('word-upload-btn');
1394 const sendBtn = document.getElementById('send-button');
1395 const originalBtnContent = uploadBtn.innerHTML;
1396
1397 try {
1398 const formData = new FormData();
1399 formData.append('action', 'mxchat_upload_word');
1400 formData.append('word_file', file);
1401 formData.append('session_id', sessionId);
1402 formData.append('nonce', mxchatChat.nonce);
1403
1404 uploadBtn.disabled = true;
1405 sendBtn.disabled = true;
1406 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1407 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1408 </svg>`;
1409
1410 const response = await fetch(mxchatChat.ajax_url, {
1411 method: 'POST',
1412 body: formData
1413 });
1414
1415 const data = await response.json();
1416
1417 if (data.success) {
1418 // Hide popular questions if they exist
1419 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1420 if (popularQuestionsContainer) {
1421 popularQuestionsContainer.style.display = 'none';
1422 }
1423
1424 // Show the active Word document name
1425 showActiveWord(data.data.filename);
1426
1427 appendMessage('bot', data.data.message);
1428 scrollToBottom();
1429 activeWordFile = data.data.filename;
1430 } else {
1431 console.error('Upload failed:', data.data);
1432 alert('Failed to upload Word document. Please try again.');
1433 }
1434 } catch (error) {
1435 console.error('Upload error:', error);
1436 alert('Error uploading file. Please try again.');
1437 } finally {
1438 uploadBtn.disabled = false;
1439 sendBtn.disabled = false;
1440 uploadBtn.innerHTML = originalBtnContent;
1441 this.value = ''; // Reset file input
1442 }
1443 });
1444
1445 // Remove button click handlers
1446 document.getElementById('remove-pdf-btn')?.addEventListener('click', function(e) {
1447 e.preventDefault();
1448 e.stopPropagation();
1449 removeActivePdf();
1450 });
1451
1452 document.getElementById('remove-word-btn')?.addEventListener('click', function(e) {
1453 e.preventDefault();
1454 e.stopPropagation();
1455 removeActiveWord();
1456 });
1457
1458 // Window resize handlers
1459 $(window).on('resize orientationchange', function() {
1460 setFullHeight();
1461 });
1462
1463
1464 // ====================================
1465 // TOOLBAR & STYLING SETUP
1466 // ====================================
1467
1468 // Apply toolbar settings
1469 if (mxchatChat.chat_toolbar_toggle === 'on') {
1470 $('.chat-toolbar').show();
1471 } else {
1472 $('.chat-toolbar').hide();
1473 }
1474
1475 // Apply toolbar icon colors
1476 const toolbarElements = [
1477 '#mxchat-chatbot .toolbar-btn svg',
1478 '#mxchat-chatbot .active-pdf-name',
1479 '#mxchat-chatbot .active-word-name',
1480 '#mxchat-chatbot .remove-pdf-btn svg',
1481 '#mxchat-chatbot .remove-word-btn svg',
1482 '#mxchat-chatbot .toolbar-perplexity svg'
1483 ];
1484
1485 toolbarElements.forEach(selector => {
1486 $(selector).css({
1487 'fill': toolbarIconColor,
1488 'stroke': toolbarIconColor,
1489 'color': toolbarIconColor
1490 });
1491 });
1492
1493
1494 // ====================================
1495 // EMAIL COLLECTION SETUP
1496 // ====================================
1497
1498 // Email collection form setup and handlers
1499 const emailForm = document.getElementById('email-collection-form');
1500 const emailBlocker = document.getElementById('email-blocker');
1501 const chatbotWrapper = document.getElementById('chat-container');
1502
1503 if (emailForm && emailBlocker && chatbotWrapper) {
1504 // Check if email exists for the current session
1505 function checkSessionAndEmail() {
1506 const sessionId = getChatSession();
1507 //console.log("[DEBUG JS] checkSessionAndEmail -> sessionId:", sessionId);
1508
1509 fetch(mxchatChat.ajax_url, {
1510 method: 'POST',
1511 headers: {
1512 'Content-Type': 'application/x-www-form-urlencoded',
1513 },
1514 body: new URLSearchParams({
1515 action: 'mxchat_check_email_provided',
1516 session_id: sessionId,
1517 nonce: mxchatChat.nonce,
1518 }),
1519 })
1520 .then((response) => response.json())
1521 .then((data) => {
1522 //console.log("[DEBUG JS] mxchat_check_email_provided response:", data);
1523
1524 if (data.success) {
1525 if (data.data.logged_in) {
1526 //console.log("[DEBUG JS] User is logged in. Hiding email form.");
1527 emailBlocker.style.display = 'none';
1528 chatbotWrapper.style.display = 'flex';
1529 } else if (data.data.email) {
1530 //console.log("[DEBUG JS] Email found for session. Hiding email form.");
1531 emailBlocker.style.display = 'none';
1532 chatbotWrapper.style.display = 'flex';
1533 } else {
1534 //console.log("[DEBUG JS] No email provided. Showing email form.");
1535 emailBlocker.style.display = 'flex';
1536 chatbotWrapper.style.display = 'none';
1537 }
1538 } else {
1539 //console.log("[DEBUG JS] Error or no data received. Showing email form.");
1540 emailBlocker.style.display = 'flex';
1541 chatbotWrapper.style.display = 'none';
1542 }
1543 })
1544 .catch((error) => {
1545 // console.error("[DEBUG JS] Fetch error -> forcing email form visible:", error);
1546 emailBlocker.style.display = 'flex';
1547 chatbotWrapper.style.display = 'none';
1548 });
1549 }
1550
1551 // Handle email form submission
1552 emailForm.addEventListener('submit', function (event) {
1553 event.preventDefault();
1554 const userEmail = document.getElementById('user-email').value;
1555 const sessionId = getChatSession();
1556
1557 if (userEmail) {
1558 fetch(mxchatChat.ajax_url, {
1559 method: 'POST',
1560 headers: {
1561 'Content-Type': 'application/x-www-form-urlencoded',
1562 },
1563 body: new URLSearchParams({
1564 action: 'mxchat_handle_save_email_and_response',
1565 email: userEmail,
1566 session_id: sessionId,
1567 nonce: mxchatChat.nonce,
1568 }),
1569 })
1570 .then((response) => response.json())
1571 .then((data) => {
1572 //console.log('Backend response:', data);
1573 if (data.success) {
1574 //console.log('Email saved successfully:', userEmail);
1575 emailBlocker.style.display = 'none';
1576 chatbotWrapper.style.display = 'flex';
1577
1578 // Optionally handle bot response
1579 if (data.message) {
1580 appendMessage('bot', data.message);
1581 scrollToBottom();
1582 }
1583 } else {
1584 console.error('Error saving email:', data.message || 'Unknown error');
1585 }
1586 })
1587 .catch((error) => {
1588 console.error('AJAX error:', error);
1589 });
1590 }
1591 });
1592
1593 // Check session and email status on page load
1594 checkSessionAndEmail();
1595 } else if (mxchatChat.email_collection_enabled) {
1596 // Only show error if email collection is enabled but elements are missing
1597 console.error('Essential elements for email handling are missing.');
1598 }
1599
1600 // Open chatbot when pre-chat message is clicked
1601 $(document).on('click', '#pre-chat-message', function() {
1602 var chatbot = $('#floating-chatbot');
1603 if (chatbot.hasClass('hidden')) {
1604 chatbot.removeClass('hidden').addClass('visible');
1605 $('#floating-chatbot-button').addClass('hidden');
1606 $('#pre-chat-message').fadeOut(250); // Hide pre-chat message
1607 disableScroll(); // Disable scroll when chatbot opens
1608 }
1609 });
1610
1611 var closeButton = document.querySelector('.close-pre-chat-message');
1612 if (closeButton) {
1613 closeButton.addEventListener('click', function() {
1614 $('#pre-chat-message').fadeOut(200); // Hide the message
1615
1616 // Send an AJAX request to set the transient flag for 24 hours
1617 $.ajax({
1618 url: mxchatChat.ajax_url,
1619 type: 'POST',
1620 data: {
1621 action: 'mxchat_dismiss_pre_chat_message',
1622 _ajax_nonce: mxchatChat.nonce
1623 },
1624 success: function() {
1625 //console.log('Pre-chat message dismissed for 24 hours.');
1626
1627 // Ensure the message is hidden after dismissal
1628 $('#pre-chat-message').hide();
1629 },
1630 error: function() {
1631 //console.error('Failed to dismiss pre-chat message.');
1632 }
1633 });
1634 });
1635 }
1636 // ====================================
1637 // MAIN INITIALIZATION
1638 // ====================================
1639
1640 if ($('#floating-chatbot').hasClass('hidden')) {
1641 $('#floating-chatbot-button').removeClass('hidden');
1642 }
1643 // Initialize when document is ready
1644 setFullHeight();
1645 initializeChatVisibility();
1646 loadChatHistory();
1647
1648 }); // End of jQuery ready
1649
1650 // ====================================
1651 // GLOBAL EVENT LISTENERS (Outside jQuery)
1652 // ====================================
1653
1654 // Event listener for copy button (code blocks)
1655 document.addEventListener("click", (e) => {
1656 if (e.target.classList.contains("mxchat-copy-button")) {
1657 const copyButton = e.target;
1658 const codeBlock = copyButton
1659 .closest(".mxchat-code-block-container")
1660 .querySelector(".mxchat-code-block code");
1661
1662 if (codeBlock) {
1663 // Preserve formatting using innerText
1664 navigator.clipboard.writeText(codeBlock.innerText).then(() => {
1665 copyButton.textContent = "Copied!";
1666 copyButton.setAttribute("aria-label", "Copied to clipboard");
1667
1668 setTimeout(() => {
1669 copyButton.textContent = "Copy";
1670 copyButton.setAttribute("aria-label", "Copy to clipboard");
1671 }, 2000);
1672 });
1673 }
1674 }
1675 });
1676
1677 // Initialize on page load
1678 document.addEventListener('DOMContentLoaded', function() {
1679 checkInitialDocumentStatus();
1680 });