PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 1.6.1
MxChat – AI Chatbot & Content Generation for WordPress v1.6.1
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 1.6.1, at js/chat-script.js

1,450 lines 49.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 //console.log('mxchatChat object:', mxchatChat);
3 //console.log('Link Target Toggle Value:', mxchatChat.link_target_toggle);
4
5 const toolbarIconColor = mxchatChat.toolbar_icon_color || '#212121';
6
7 // Initialize color settings
8 var userMessageBgColor = mxchatChat.user_message_bg_color;
9 var userMessageFontColor = mxchatChat.user_message_font_color;
10 var botMessageBgColor = mxchatChat.bot_message_bg_color;
11 var botMessageFontColor = mxchatChat.bot_message_font_color;
12 // Add live agent message colors
13 var liveAgentMessageBgColor = mxchatChat.live_agent_message_bg_color;
14 var liveAgentMessageFontColor = mxchatChat.live_agent_message_font_color;
15
16
17 var linkTarget = mxchatChat.link_target_toggle === 'on' ? '_blank' : '_self';
18 var lastSeenMessageId = '';
19 // Initialize session ID
20 var sessionId = getChatSession();
21
22 let pollingInterval; // Variable to store the interval ID
23 let processedMessageIds = new Set(); // Add this at the top with your other variables
24 //console.log('Live Agent BG Color:', liveAgentMessageBgColor);
25 //console.log('Live Agent Font Color:', liveAgentMessageFontColor);
26 let activePdfFile = null;
27 let activeWordFile = null;
28
29
30
31
32 function getChatSession() {
33 var sessionId = getCookie('mxchat_session_id');
34 //console.log("Session ID retrieved from cookie: ", sessionId);
35
36 if (!sessionId) {
37 sessionId = generateSessionId();
38 //console.log("Generated new session ID: ", sessionId);
39 setChatSession(sessionId);
40 }
41
42 //console.log("Final session ID: ", sessionId);
43 return sessionId;
44 }
45
46 function setChatSession(sessionId) {
47 // Set the cookie with a 24-hour expiration (86400 seconds)
48 document.cookie = "mxchat_session_id=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
49 }
50
51 // Get cookie value by name
52 function getCookie(name) {
53 let value = "; " + document.cookie;
54 let parts = value.split("; " + name + "=");
55 if (parts.length == 2) return parts.pop().split(";").shift();
56 }
57
58 // Generate a new session ID
59 function generateSessionId() {
60 return 'mxchat_chat_' + Math.random().toString(36).substr(2, 9);
61 }
62
63 // Function to send the message to the chatbot (backend)
64 function sendMessageToChatbot(message) {
65 var sessionId = getChatSession(); // Reuse the session ID logic
66
67 // Hide the popular questions section
68 $('#mxchat-popular-questions').hide();
69
70 // Show thinking indicator (no need to append the user's message again)
71 appendThinkingMessage();
72 scrollToBottom();
73
74 //console.log("Sending message to chatbot:", message); // Log the message
75 //console.log("Session ID:", sessionId); // Log the session ID
76
77 // Call the chatbot using the same call logic as sendMessage
78 callMxChat(message, function(response) {
79 // ** Ensure temporary thinking message is removed before adding new response **
80 $('.temporary-message').remove();
81
82 // Replace thinking indicator with actual response
83 replaceLastMessage("bot", response);
84 });
85 }
86
87
88
89
90 function sendMessage() {
91 var message = $('#chat-input').val(); // Get value from textarea
92 if (message) {
93 appendMessage("user", message); // Append user's message
94 $('#chat-input').val(''); // Clear the textarea
95 $('#chat-input').css('height', 'auto'); // Reset height after clearing content
96
97 // Hide the popular questions section
98 $('#mxchat-popular-questions').hide();
99
100 // Show typing indicator
101 appendThinkingMessage();
102 scrollToBottom();
103
104 callMxChat(message, function(response) {
105 // Replace typing indicator with actual response
106 replaceLastMessage("bot", response);
107 });
108 }
109 }
110
111
112
113 // Function to append a thinking message with animation
114 function appendThinkingMessage() {
115 // Remove any existing thinking dots first
116 $('.thinking-dots').remove();
117
118 // Retrieve the bot message font color and background color
119 var botMessageFontColor = mxchatChat.bot_message_font_color;
120 var botMessageBgColor = mxchatChat.bot_message_bg_color;
121
122
123 var thinkingHtml = '<div class="thinking-dots-container">' +
124 '<div class="thinking-dots">' +
125 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
126 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
127 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
128 '</div>' +
129 '</div>';
130
131 // Append the thinking dots to the chat container (or within the temporary message div)
132 $("#chat-box").append('<div class="bot-message temporary-message" style="background-color: ' + botMessageBgColor + ';">' + thinkingHtml + '</div>');
133 scrollToBottom();
134 }
135
136 // Trigger send button click when "Enter" key is pressed in the textarea
137 $('#chat-input').keypress(function(e) {
138 if (e.which == 13 && !e.shiftKey) { // Check if "Enter" is pressed without Shift
139 e.preventDefault(); // Prevent default "Enter" behavior
140 $('#send-button').click(); // Trigger send button click
141 }
142 });
143
144 // Handle send button click
145 $('#send-button').click(function() {
146 sendMessage();
147 });
148
149 // Handle click on popular questions
150 $('.mxchat-popular-question').on('click', function () {
151 var question = $(this).text(); // Get the text of the clicked question
152
153 // Append the question as if the user typed it
154 appendMessage("user", question);
155
156 // Send the question to the server (backend)
157 sendMessageToChatbot(question);
158 });
159
160
161 // Use the linkTarget in your linkify function
162 function linkify(inputText) {
163 // Check for already linked URLs and skip them
164 // We use negative lookaheads to skip anything already in an <a> tag
165 var markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
166 var replacedText = inputText.replace(markdownLinkPattern, '<a href="$2" target="' + linkTarget + '">$1</a>');
167
168 // Replace standalone URLs not already in an <a> tag
169 var urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
170 replacedText = replacedText.replace(urlPattern, '$1<a href="$2" target="' + linkTarget + '">$2</a>');
171
172 // Replace "www." prefixed URLs not already in an <a> tag
173 var wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
174 replacedText = replacedText.replace(wwwPattern, '$1<a href="http://$2" target="' + linkTarget + '">$2</a>');
175
176 return replacedText;
177 }
178
179
180 function scrollElementToTop(element) {
181 var chatBox = $('#chat-box');
182 var elementTop = element.position().top + chatBox.scrollTop();
183 chatBox.animate({ scrollTop: elementTop }, 500);
184 }
185
186
187 // Optimized scrollToBottom function for instant scrolling
188 function scrollToBottom(instant = false) {
189 var chatBox = $('#chat-box');
190 if (instant) {
191 // Instantly set the scroll position to the bottom
192 chatBox.scrollTop(chatBox.prop("scrollHeight"));
193 } else {
194 // Use requestAnimationFrame for smoother scrolling if needed
195 let start = null;
196 const scrollHeight = chatBox.prop("scrollHeight");
197 const initialScroll = chatBox.scrollTop();
198 const distance = scrollHeight - initialScroll;
199 const duration = 500; // Duration in ms
200
201 function smoothScroll(timestamp) {
202 if (!start) start = timestamp;
203 const progress = timestamp - start;
204 const currentScroll = initialScroll + (distance * (progress / duration));
205 chatBox.scrollTop(currentScroll);
206
207 if (progress < duration) {
208 requestAnimationFrame(smoothScroll);
209 } else {
210 chatBox.scrollTop(scrollHeight); // Ensure it's exactly at the bottom
211 }
212 }
213
214 requestAnimationFrame(smoothScroll);
215 }
216 }
217
218
219 // Function to format text with **bold** inside double asterisks
220 function formatBoldText(text) {
221 return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
222 }
223
224 // Function to convert newline characters to HTML line breaks and handle paragraph spacing
225 function convertNewlinesToBreaks(text) {
226 // Split the text into paragraphs (marked by double newlines or multiple <br> tags)
227 const paragraphs = text.split(/(?:\n\n|\<br\>\s*\<br\>)/g);
228
229 // Wrap each paragraph in <p> tags
230 return paragraphs
231 .map(para => `<p>${para.trim()}</p>`)
232 .join('');
233 }
234 // Copy to clipboard function
235 // Function to copy text to clipboard
236 function copyToClipboard(text) {
237 var tempInput = $('<input>');
238 $('body').append(tempInput);
239 tempInput.val(text).select();
240 document.execCommand('copy');
241 tempInput.remove();
242 }
243
244
245 function updateChatModeIndicator(mode) {
246 const indicator = document.getElementById('chat-mode-indicator');
247 if (indicator) {
248 indicator.textContent = mode === 'agent' ? 'Live Agent' : 'AI Agent';
249 }
250
251 // Start or stop polling based on mode
252 if (mode === 'agent') {
253 startPolling();
254 } else {
255 stopPolling();
256 }
257 }
258
259 function callMxChat(message, callback) {
260 //console.log("Sending message to chatbot:", message);
261
262 $.ajax({
263 url: mxchatChat.ajax_url,
264 type: 'POST',
265 dataType: 'json',
266 data: {
267 action: 'mxchat_handle_chat_request',
268 message: message,
269 session_id: getChatSession(),
270 nonce: mxchatChat.nonce
271 },
272 success: function(response) {
273 //console.log("callMxChat response:", response);
274
275 // Check for chat_mode in the response
276 if (response.chat_mode) {
277 updateChatModeIndicator(response.chat_mode);
278 }
279 // Also check in fallbackResponse if exists
280 else if (response.fallbackResponse && response.fallbackResponse.chat_mode) {
281 updateChatModeIndicator(response.fallbackResponse.chat_mode);
282 }
283 // Add PDF filename handling
284 if (response.data && response.data.filename) {
285 showActivePdf(response.data.filename);
286 activePdfFile = response.data.filename;
287 }
288 // Add redirect check here
289 if (response.redirect_url) {
290 // Show the message first
291 let responseText = response.text || '';
292 if (responseText) {
293 replaceLastMessage("bot", responseText);
294 }
295 // Then redirect after a short delay
296 setTimeout(() => {
297 window.location.href = response.redirect_url;
298 }, 1500);
299 return; // Exit early since we're redirecting
300 }
301
302 // Check for live agent response
303 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
304 updateChatModeIndicator('agent');
305 // Do not replace the thinking dots; just wait for the agent's actual response
306 return;
307 }
308
309 // Handle other responses
310 let responseText = response.text || '';
311 let responseHtml = response.html || '';
312 let responseMessage = response.message || '';
313
314 // Check for mode change in text response
315 if (responseText === 'You are now chatting with the AI chatbot.') {
316 updateChatModeIndicator('ai');
317 }
318
319 // For product card or chatbot responses with HTML
320 if (responseText && responseHtml) {
321 replaceLastMessage("bot", responseText, responseHtml);
322 }
323 // For regular chatbot responses with just text
324 else if (responseText) {
325 replaceLastMessage("bot", responseText);
326 }
327 // For responses with only HTML (like product cards)
328 else if (responseHtml) {
329 replaceLastMessage("bot", "", responseHtml);
330 }
331 // For legacy message format
332 else if (responseMessage) {
333 replaceLastMessage("bot", responseMessage);
334 }
335 // Fallback error case
336 else {
337 console.error("Unexpected response format:", response);
338 replaceLastMessage("bot", "I'm sorry, something went wrong.");
339 }
340
341 if (response.message_id) {
342 lastSeenMessageId = response.message_id;
343 }
344 },
345 error: function(xhr, status, error) {
346 //console.log("Error communicating with the server:", xhr.status, error);
347 replaceLastMessage("bot", "An unexpected error occurred.");
348 }
349 });
350 }
351
352 function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
353 //console.log("Appending message. Sender:", sender, "Content:", messageText);
354
355 try {
356 // Determine styles based on sender type
357 let messageClass, bgColor, fontColor;
358
359 if (sender === "user") {
360 messageClass = "user-message";
361 bgColor = userMessageBgColor;
362 fontColor = userMessageFontColor;
363 } else if (sender === "agent") {
364 messageClass = "agent-message";
365 bgColor = liveAgentMessageBgColor;
366 fontColor = liveAgentMessageFontColor;
367 } else {
368 messageClass = "bot-message";
369 bgColor = botMessageBgColor;
370 fontColor = botMessageFontColor;
371 }
372
373 const messageDiv = $('<div>')
374 .addClass(messageClass)
375 .css({
376 'background': bgColor,
377 'color': fontColor,
378 });
379
380 // Add CSS for paragraphs
381 messageDiv.css({
382 'margin-bottom': '1em'
383 });
384
385 // Format and process the message content
386 let fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
387
388 // Add images if provided
389 if (images && images.length > 0) {
390 fullMessage += '<div class="image-gallery">';
391 images.forEach(img => {
392 fullMessage += `
393 <div style="margin-bottom: 10px;">
394 <strong>${img.title}</strong><br>
395 <a href="${img.image_url}" target="_blank">
396 <img src="${img.thumbnail_url}" alt="${img.title}" style="max-width: 100px; height: auto; margin: 5px;" />
397 </a>
398 </div>`;
399 });
400 fullMessage += '</div>';
401 }
402
403 // Append HTML content if provided
404 if (messageHtml) {
405 fullMessage += '<br><br>' + messageHtml;
406 }
407
408 messageDiv.html(fullMessage);
409
410 // Add a class for temporary messages if needed
411 if (isTemporary) {
412 messageDiv.addClass('temporary-message');
413 }
414
415 // Append the message to the chat box
416 messageDiv.hide().appendTo('#chat-box').fadeIn(300, function () {
417 if (sender === "bot") {
418 // After bot's message is displayed, scroll the last user message to the top
419 const lastUserMessage = $('#chat-box').find('.user-message').last();
420 if (lastUserMessage.length) {
421 scrollElementToTop(lastUserMessage);
422 }
423 }
424 });
425
426 // Update the last seen message ID if applicable
427 if (messageText.id) {
428 lastSeenMessageId = messageText.id;
429 }
430 } catch (error) {
431 console.error("Error rendering message with images:", error);
432 }
433 }
434
435
436 function replaceLastMessage(sender, responseText, responseHtml = '', images = []) {
437 var messageClass = sender === "user" ? "user-message" : sender === "agent" ? "agent-message" : "bot-message";
438 var lastMessageDiv = $('#chat-box').find('.bot-message.temporary-message, .agent-message.temporary-message').last();
439
440 // Determine styles
441 let bgColor, fontColor;
442 if (sender === "user") {
443 bgColor = userMessageBgColor;
444 fontColor = userMessageFontColor;
445 } else if (sender === "agent") {
446 bgColor = liveAgentMessageBgColor;
447 fontColor = liveAgentMessageFontColor;
448 } else {
449 bgColor = botMessageBgColor;
450 fontColor = botMessageFontColor;
451 }
452
453 var fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(responseText))));
454 if (responseHtml) {
455 fullMessage += '<br><br>' + responseHtml;
456 }
457
458 if (images.length > 0) {
459 fullMessage += '<div class="image-gallery">';
460 images.forEach(img => {
461 fullMessage += `
462 <div style="margin-bottom: 10px;">
463 <strong>${img.title}</strong><br>
464 <a href="${img.image_url}" target="_blank">
465 <img src="${img.thumbnail_url}" alt="${img.title}" style="max-width: 100px; height: auto; margin: 5px;" />
466 </a>
467 </div>`;
468 });
469 fullMessage += '</div>';
470 }
471
472 if (lastMessageDiv.length) {
473 lastMessageDiv.fadeOut(200, function() {
474 $(this)
475 .html(fullMessage)
476 .removeClass('bot-message user-message')
477 .addClass(messageClass)
478 .css({
479 'background-color': bgColor,
480 'color': fontColor,
481 })
482 .removeClass('temporary-message')
483 .fadeIn(200, function() {
484 // After message is displayed, scroll last user message to top
485 if (sender === "bot" || sender === "agent") {
486 const lastUserMessage = $('#chat-box').find('.user-message').last();
487 if (lastUserMessage.length) {
488 scrollElementToTop(lastUserMessage);
489 }
490 }
491 });
492 });
493 } else {
494 appendMessage(sender, responseText, responseHtml, images);
495 }
496 }
497
498
499 function startPolling() {
500 // Clear any existing interval first
501 stopPolling();
502 // Start new polling interval
503 pollingInterval = setInterval(checkForAgentMessages, 5000);
504 //console.log("Started agent message polling");
505 }
506
507 function stopPolling() {
508 if (pollingInterval) {
509 clearInterval(pollingInterval);
510 pollingInterval = null;
511 //console.log("Stopped agent message polling");
512 }
513 }
514
515
516 function checkForAgentMessages() {
517 const sessionId = getChatSession();
518
519 $.ajax({
520 url: mxchatChat.ajax_url,
521 type: 'POST',
522 dataType: 'json',
523 data: {
524 action: 'mxchat_fetch_new_messages',
525 session_id: sessionId,
526 last_seen_id: lastSeenMessageId,
527 nonce: mxchatChat.nonce
528 },
529 success: function (response) {
530 //console.log("Agent messages polling response:", response);
531 if (response.success && response.data?.new_messages) {
532 response.data.new_messages.forEach(function (message) {
533 if (message.role === "agent" && !processedMessageIds.has(message.id)) {
534 replaceLastMessage("agent", message.content);
535 lastSeenMessageId = message.id;
536 processedMessageIds.add(message.id);
537 }
538 });
539 scrollToBottom(true);
540 }
541 },
542 error: function (xhr, status, error) {
543 console.error("Polling error:", xhr, status, error);
544 }
545 });
546 }
547 function loadChatHistory() {
548 var sessionId = getChatSession();
549 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
550
551 if (chatPersistenceEnabled && sessionId) {
552 $.ajax({
553 url: mxchatChat.ajax_url,
554 type: 'POST',
555 dataType: 'json',
556 data: {
557 action: 'mxchat_fetch_conversation_history',
558 session_id: sessionId
559 },
560 success: function(response) {
561 if (response.success && response.data && Array.isArray(response.data.conversation)) {
562
563
564 var $chatBox = $('#chat-box');
565 var $fragment = $(document.createDocumentFragment());
566 let highestMessageId = lastSeenMessageId;
567
568 if (response.data.chat_mode) {
569 updateChatModeIndicator(response.data.chat_mode);
570 }
571
572 $.each(response.data.conversation, function(index, message) {
573 // Skip agent messages if persistence is off
574 if (!chatPersistenceEnabled && message.role === 'agent') {
575 return;
576 }
577
578 var messageClass, messageBgColor, messageFontColor;
579
580 switch (message.role) {
581 case 'user':
582 messageClass = 'user-message';
583 messageBgColor = userMessageBgColor;
584 messageFontColor = userMessageFontColor;
585 break;
586 case 'agent':
587 messageClass = 'agent-message';
588 messageBgColor = liveAgentMessageBgColor;
589 messageFontColor = liveAgentMessageFontColor;
590 break;
591 default:
592 messageClass = 'bot-message';
593 messageBgColor = botMessageBgColor;
594 messageFontColor = botMessageFontColor;
595 break;
596 }
597
598 var messageElement = $('<div>').addClass(messageClass)
599 .css({
600 'background': messageBgColor,
601 'color': messageFontColor
602 });
603
604 var content = message.content;
605 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
606 content = decodeHTMLEntities(content);
607
608 if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
609 messageElement.html(content);
610 } else {
611 var formattedContent = linkify(
612 formatBoldText(
613 convertNewlinesToBreaks(formatCodeBlocks(content))
614 )
615 );
616 messageElement.html(formattedContent);
617 }
618
619 $fragment.append(messageElement);
620
621 // In loadChatHistory, change this part:
622 if (message.id) {
623 highestMessageId = Math.max(highestMessageId, message.id);
624 processedMessageIds.add(message.id); // Add all message IDs to processed set
625 }
626 });
627
628 $chatBox.append($fragment);
629 scrollToBottom(true);
630
631 if (response.data.conversation.length > 0) {
632 $('#mxchat-popular-questions').hide();
633 }
634
635 // Update lastSeenMessageId after history loads
636 lastSeenMessageId = highestMessageId;
637
638 // Only update chat mode if persistence is enabled
639 if (chatPersistenceEnabled && response.data.conversation.length > 0) {
640 var lastMessage = response.data.conversation[response.data.conversation.length - 1];
641 if (lastMessage.role === 'agent') {
642 updateChatModeIndicator('agent');
643 }
644 }
645 } else {
646 console.warn("No conversation history found.");
647 }
648 },
649 error: function(xhr, status, error) {
650 console.error("Error loading chat history:", status, error);
651 appendMessage("bot", "Unable to load chat history.");
652 }
653 });
654 } else {
655 console.warn("Chat persistence is disabled or no session ID found. Not loading history.");
656 }
657 }
658
659 // Function to decode HTML entities
660 function decodeHTMLEntities(text) {
661 var textArea = document.createElement('textarea');
662 textArea.innerHTML = text;
663 return textArea.value;
664 }
665
666
667 function formatCodeBlocks(text) {
668 // Ensure the input is a string; otherwise, convert or return empty
669 if (typeof text !== 'string') {
670 console.error("formatCodeBlocks: Input is not a string:", text);
671 return typeof text === 'object' && text.text ? text.text : ""; // Use .text if available, else empty
672 }
673
674 const codeBlockPattern = /```(\w+)?\n?([\s\S]+?)```/g;
675
676 return text.replace(codeBlockPattern, (_, language, codeContent) => {
677 language = language || 'plaintext';
678
679 return `
680 <div class="mxchat-code-block-container">
681 <button class="mxchat-copy-button" aria-label="Copy to clipboard">Copy</button>
682 <pre class="mxchat-code-block"><code class="mxchat-language-${language}">${escapeHtml(codeContent)}</code></pre>
683 </div>`;
684 });
685 }
686
687
688 // Utility function to escape HTML
689 function escapeHtml(unsafe) {
690 return unsafe
691 .replace(/&/g, "&amp;")
692 .replace(/</g, "&lt;")
693 .replace(/>/g, "&gt;")
694 .replace(/"/g, "&quot;")
695 .replace(/'/g, "&#039;");
696 }
697
698
699
700
701 // Function to convert newlines, skipping preformatted text
702 function convertNewlinesToBreaks(text) {
703 // Regex to exclude <pre> and <code> tags from adding <br> tags
704 return text.replace(/(^|[^>])\n/g, '$1<br>');
705 }
706
707
708
709 $(document).ready(function() {
710 loadChatHistory();
711 });
712
713
714
715 // Helper function to check if a string is an image HTML
716 function isImageHtml(str) {
717 return str.startsWith('<img') && str.endsWith('>');
718 }
719
720 // Function to remove thinking dots
721 function removeThinkingDots() {
722 $('.thinking-dots').closest('.temporary-message').remove();
723 }
724
725 function isMobile() {
726 // This can be a simple check, or more sophisticated detection of mobile devices
727 return window.innerWidth <= 768; // Example threshold for mobile devices
728 }
729
730 function disableScroll() {
731 if (isMobile()) {
732 $('body').css('overflow', 'hidden');
733 }
734 }
735
736 function enableScroll() {
737 if (isMobile()) {
738 $('body').css('overflow', '');
739 }
740 }
741
742 // Pre-chat dismissal check function (wrapped in a function for reuse)
743 function checkPreChatDismissal() {
744 $.ajax({
745 url: mxchatChat.ajax_url,
746 type: 'POST',
747 data: {
748 action: 'mxchat_check_pre_chat_message_status',
749 _ajax_nonce: mxchatChat.nonce
750 },
751 success: function(response) {
752 if (response.success && !response.data.dismissed) {
753 $('#pre-chat-message').fadeIn(250);
754 } else {
755 $('#pre-chat-message').hide();
756 }
757 },
758 error: function() {
759 console.error('Failed to check pre-chat message dismissal status.');
760 }
761 });
762 }
763
764 // Function to show the chatbot widget
765 function showChatWidget() {
766 // First ensure display is set
767 $('#floating-chatbot-button').css('display', 'flex');
768 // Then handle the fade
769 $('#floating-chatbot-button').fadeTo(500, 1);
770 // Force visibility
771 $('#floating-chatbot-button').removeClass('hidden');
772 //console.log('Showing widget');
773 }
774
775 // Function to hide the chatbot widget
776 function hideChatWidget() {
777 $('#floating-chatbot-button').css('display', 'none');
778 $('#floating-chatbot-button').addClass('hidden');
779 //console.log('Hiding widget');
780 }
781
782 function initializeChatVisibility() {
783 //console.log('Initializing chat visibility');
784 const complianzEnabled = mxchatChat.complianz_toggle === 'on' ||
785 mxchatChat.complianz_toggle === '1' ||
786 mxchatChat.complianz_toggle === 1;
787
788 if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') {
789 // Initial check
790 checkConsentAndShowChat();
791
792 // Listen for consent changes
793 $(document).on('cmplz_status_change', function(event) {
794 //console.log('Status change detected');
795 checkConsentAndShowChat();
796 });
797 } else {
798 // If Complianz is not enabled, always show
799 $('#floating-chatbot-button')
800 .css('display', 'flex')
801 .removeClass('hidden no-consent')
802 .fadeTo(500, 1);
803
804 // Also check pre-chat message when Complianz is not enabled
805 checkPreChatDismissal();
806 }
807 }
808 // Initialize when document is ready
809 $(document).ready(function() {
810 //console.log('Document ready - starting initialization');
811 initializeChatVisibility();
812 });
813
814
815 function checkConsentAndShowChat() {
816 var consentStatus = cmplz_has_consent('marketing');
817 var consentType = complianz.consenttype;
818
819 //console.log('Checking consent:', {status: consentStatus,type: consentType});
820
821 let $widget = $('#floating-chatbot-button');
822 let $chatbot = $('#floating-chatbot');
823 let $preChat = $('#pre-chat-message');
824
825 if (consentStatus === true) {
826 //console.log('Consent granted - showing widget');
827 $widget
828 .removeClass('no-consent')
829 .css('display', 'flex')
830 .removeClass('hidden')
831 .fadeTo(500, 1);
832 $chatbot.removeClass('no-consent');
833
834 // Show pre-chat message if not dismissed
835 checkPreChatDismissal();
836 } else {
837 //console.log('No consent - hiding widget');
838 $widget
839 .addClass('no-consent')
840 .fadeTo(500, 0, function() {
841 $(this)
842 .css('display', 'none')
843 .addClass('hidden');
844 });
845 $chatbot.addClass('no-consent');
846
847 // Hide pre-chat message when no consent
848 $preChat.hide();
849 }
850 }
851
852 // Function to dismiss pre-chat message for 24 hours
853 function handlePreChatDismissal() {
854 $('#pre-chat-message').fadeOut(200);
855 $.ajax({
856 url: mxchatChat.ajax_url,
857 type: 'POST',
858 data: {
859 action: 'mxchat_dismiss_pre_chat_message',
860 _ajax_nonce: mxchatChat.nonce
861 },
862 success: function() {
863 $('#pre-chat-message').hide();
864 },
865 error: function() {
866 console.error('Failed to dismiss pre-chat message.');
867 }
868 });
869 }
870
871 // Handle pre-chat message dismissal on button click
872 $(document).on('click', '.close-pre-chat-message', function(e) {
873 e.stopPropagation();
874 handlePreChatDismissal();
875 });
876
877 // Toggle chatbot visibility on floating button click
878 $(document).on('click', '#floating-chatbot-button', function() {
879 var chatbot = $('#floating-chatbot');
880 if (chatbot.hasClass('hidden')) {
881 chatbot.removeClass('hidden').addClass('visible');
882 $(this).addClass('hidden');
883 disableScroll();
884 // Hide the pre-chat message without dismissing it
885 $('#pre-chat-message').fadeOut(250);
886 } else {
887 chatbot.removeClass('visible').addClass('hidden');
888 $(this).removeClass('hidden');
889 enableScroll();
890 // Show the pre-chat message again if it hasn't been dismissed
891 checkPreChatDismissal();
892 }
893 });
894
895 $(document).on('click', '#exit-chat-button', function() {
896 $('#floating-chatbot').addClass('hidden').removeClass('visible');
897 $('#floating-chatbot-button').removeClass('hidden');
898 enableScroll();
899 });
900
901 // Close pre-chat message on click
902 $(document).on('click', '.close-pre-chat-message', function(e) {
903 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
904 $('#pre-chat-message').fadeOut(200, function() {
905 $(this).remove();
906 });
907 });
908
909 // Open chatbot when pre-chat message is clicked
910 $(document).on('click', '#pre-chat-message', function() {
911 var chatbot = $('#floating-chatbot');
912 if (chatbot.hasClass('hidden')) {
913 chatbot.removeClass('hidden').addClass('visible');
914 $('#floating-chatbot-button').addClass('hidden');
915 $('#pre-chat-message').fadeOut(250); // Hide pre-chat message
916 disableScroll(); // Disable scroll when chatbot opens
917 }
918 });
919
920 // If the chatbot is initially hidden, ensure the button is visible
921 if ($('#floating-chatbot').hasClass('hidden')) {
922 $('#floating-chatbot-button').removeClass('hidden');
923 }
924
925 function setFullHeight() {
926 var vh = $(window).innerHeight() * 0.01;
927 $(':root').css('--vh', vh + 'px');
928 }
929
930 // Set the height when the page loads
931 $(document).ready(function() {
932 setFullHeight();
933 });
934
935 // Set the height on resize and orientation change events
936 $(window).on('resize orientationchange', function() {
937 setFullHeight();
938 });
939
940
941 // Now handle the close button to dismiss the pre-chat message for 24 hours
942 var closeButton = document.querySelector('.close-pre-chat-message');
943 if (closeButton) {
944 closeButton.addEventListener('click', function() {
945 $('#pre-chat-message').fadeOut(200); // Hide the message
946
947 // Send an AJAX request to set the transient flag for 24 hours
948 $.ajax({
949 url: mxchatChat.ajax_url,
950 type: 'POST',
951 data: {
952 action: 'mxchat_dismiss_pre_chat_message',
953 _ajax_nonce: mxchatChat.nonce
954 },
955 success: function() {
956 //console.log('Pre-chat message dismissed for 24 hours.');
957
958 // Ensure the message is hidden after dismissal
959 $('#pre-chat-message').hide();
960 },
961 error: function() {
962 //console.error('Failed to dismiss pre-chat message.');
963 }
964 });
965 });
966 }
967
968
969
970
971 // Event listener for Add to Cart button
972 $(document).on('click', '.mxchat-add-to-cart-button', function() {
973 var productId = $(this).data('product-id');
974 // Add a special prefix to indicate this is from button
975 appendMessage("user", "add to cart");
976 sendMessageToChatbot("!addtocart"); // Special command to indicate button click
977 });
978
979
980 if (document.getElementById('pdf-upload-btn')) {
981 document.getElementById('pdf-upload-btn').addEventListener('click', function() {
982 document.getElementById('pdf-upload').click();
983 });
984 }
985
986 if (document.getElementById('word-upload-btn')) {
987 document.getElementById('word-upload-btn').addEventListener('click', function() {
988 document.getElementById('word-upload').click();
989 });
990 }
991
992 // PDF file input change handler
993 document.getElementById('pdf-upload').addEventListener('change', async function(e) {
994 const file = e.target.files[0];
995
996 if (!file || file.type !== 'application/pdf') {
997 alert('Please select a valid PDF file.');
998 return;
999 }
1000
1001 if (!sessionId) {
1002 console.error('No session ID found');
1003 alert('Error: No session ID found');
1004 return;
1005 }
1006
1007 if (!mxchatChat || !mxchatChat.ajax_url || !mxchatChat.nonce) {
1008 console.error('mxchatChat not properly configured:', mxchatChat);
1009 alert('Error: Ajax configuration missing');
1010 return;
1011 }
1012
1013 // Disable buttons and show loading state
1014 const uploadBtn = document.getElementById('pdf-upload-btn');
1015 const sendBtn = document.getElementById('send-button');
1016 const originalBtnContent = uploadBtn.innerHTML;
1017
1018 try {
1019 const formData = new FormData();
1020 formData.append('action', 'mxchat_upload_pdf');
1021 formData.append('pdf_file', file);
1022 formData.append('session_id', sessionId);
1023 formData.append('nonce', mxchatChat.nonce);
1024
1025 uploadBtn.disabled = true;
1026 sendBtn.disabled = true;
1027 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1028 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1029 </svg>`;
1030
1031 const response = await fetch(mxchatChat.ajax_url, {
1032 method: 'POST',
1033 body: formData
1034 });
1035
1036 const data = await response.json();
1037
1038 if (data.success) {
1039 // Hide popular questions if they exist
1040 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1041 if (popularQuestionsContainer) {
1042 popularQuestionsContainer.style.display = 'none';
1043 }
1044
1045 // Show the active PDF name
1046 showActivePdf(data.data.filename);
1047
1048 appendMessage('bot', data.data.message);
1049 scrollToBottom();
1050 activePdfFile = data.data.filename;
1051 } else {
1052 console.error('Upload failed:', data.data);
1053 alert('Failed to upload PDF. Please try again.');
1054 }
1055 } catch (error) {
1056 console.error('Upload error:', error);
1057 alert('Error uploading file. Please try again.');
1058 } finally {
1059 uploadBtn.disabled = false;
1060 sendBtn.disabled = false;
1061 uploadBtn.innerHTML = originalBtnContent;
1062 this.value = ''; // Reset file input
1063 }
1064 });
1065
1066 // Word file input change handler
1067 document.getElementById('word-upload').addEventListener('change', async function(e) {
1068 const file = e.target.files[0];
1069
1070 if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1071 alert('Please select a valid Word document (.docx).');
1072 return;
1073 }
1074
1075 if (!sessionId) {
1076 console.error('No session ID found');
1077 alert('Error: No session ID found');
1078 return;
1079 }
1080
1081 // Disable buttons and show loading state
1082 const uploadBtn = document.getElementById('word-upload-btn');
1083 const sendBtn = document.getElementById('send-button');
1084 const originalBtnContent = uploadBtn.innerHTML;
1085
1086 try {
1087 const formData = new FormData();
1088 formData.append('action', 'mxchat_upload_word');
1089 formData.append('word_file', file);
1090 formData.append('session_id', sessionId);
1091 formData.append('nonce', mxchatChat.nonce);
1092
1093 uploadBtn.disabled = true;
1094 sendBtn.disabled = true;
1095 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1096 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1097 </svg>`;
1098
1099 const response = await fetch(mxchatChat.ajax_url, {
1100 method: 'POST',
1101 body: formData
1102 });
1103
1104 const data = await response.json();
1105
1106 if (data.success) {
1107 // Hide popular questions if they exist
1108 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1109 if (popularQuestionsContainer) {
1110 popularQuestionsContainer.style.display = 'none';
1111 }
1112
1113 // Show the active Word document name
1114 showActiveWord(data.data.filename);
1115
1116 appendMessage('bot', data.data.message);
1117 scrollToBottom();
1118 activeWordFile = data.data.filename;
1119 } else {
1120 console.error('Upload failed:', data.data);
1121 alert('Failed to upload Word document. Please try again.');
1122 }
1123 } catch (error) {
1124 console.error('Upload error:', error);
1125 alert('Error uploading file. Please try again.');
1126 } finally {
1127 uploadBtn.disabled = false;
1128 sendBtn.disabled = false;
1129 uploadBtn.innerHTML = originalBtnContent;
1130 this.value = ''; // Reset file input
1131 }
1132 });
1133
1134 // Function to show active PDF name in toolbar
1135 function showActivePdf(filename) {
1136 const container = document.getElementById('active-pdf-container');
1137 const nameElement = document.getElementById('active-pdf-name');
1138
1139 if (!container || !nameElement) {
1140 console.error('PDF container elements not found');
1141 return;
1142 }
1143
1144 nameElement.textContent = filename;
1145 container.style.display = 'flex';
1146 }
1147
1148 // Function to show active Word document name in toolbar
1149 function showActiveWord(filename) {
1150 const container = document.getElementById('active-word-container');
1151 const nameElement = document.getElementById('active-word-name');
1152
1153 if (!container || !nameElement) {
1154 console.error('Word document container elements not found');
1155 return;
1156 }
1157
1158 nameElement.textContent = filename;
1159 container.style.display = 'flex';
1160 }
1161
1162 // Function to remove active PDF
1163 function removeActivePdf() {
1164 const container = document.getElementById('active-pdf-container');
1165 const nameElement = document.getElementById('active-pdf-name');
1166
1167 if (!container || !nameElement || !activePdfFile) return;
1168
1169 fetch(mxchatChat.ajax_url, {
1170 method: 'POST',
1171 headers: {
1172 'Content-Type': 'application/x-www-form-urlencoded',
1173 },
1174 body: new URLSearchParams({
1175 'action': 'mxchat_remove_pdf',
1176 'session_id': sessionId,
1177 'nonce': mxchatChat.nonce
1178 })
1179 })
1180 .then(response => response.json())
1181 .then(data => {
1182 if (data.success) {
1183 container.style.display = 'none';
1184 nameElement.textContent = '';
1185 activePdfFile = null;
1186 appendMessage('bot', 'PDF removed.');
1187 }
1188 })
1189 .catch(error => {
1190 console.error('Error removing PDF:', error);
1191 });
1192 }
1193
1194 // Function to remove active Word document
1195 function removeActiveWord() {
1196 const container = document.getElementById('active-word-container');
1197 const nameElement = document.getElementById('active-word-name');
1198
1199 if (!container || !nameElement || !activeWordFile) return;
1200
1201 fetch(mxchatChat.ajax_url, {
1202 method: 'POST',
1203 headers: {
1204 'Content-Type': 'application/x-www-form-urlencoded',
1205 },
1206 body: new URLSearchParams({
1207 'action': 'mxchat_remove_word',
1208 'session_id': sessionId,
1209 'nonce': mxchatChat.nonce
1210 })
1211 })
1212 .then(response => response.json())
1213 .then(data => {
1214 if (data.success) {
1215 container.style.display = 'none';
1216 nameElement.textContent = '';
1217 activeWordFile = null;
1218 appendMessage('bot', 'Word document removed.');
1219 }
1220 })
1221 .catch(error => {
1222 console.error('Error removing Word document:', error);
1223 });
1224 }
1225
1226 // Add remove button click handlers
1227 document.getElementById('remove-pdf-btn')?.addEventListener('click', function(e) {
1228 e.preventDefault();
1229 e.stopPropagation();
1230 removeActivePdf();
1231 });
1232
1233 document.getElementById('remove-word-btn')?.addEventListener('click', function(e) {
1234 e.preventDefault();
1235 e.stopPropagation();
1236 removeActiveWord();
1237 });
1238
1239 // Check initial document status
1240 function checkInitialDocumentStatus() {
1241 if (!sessionId) return;
1242
1243 // Check PDF status
1244 fetch(mxchatChat.ajax_url, {
1245 method: 'POST',
1246 headers: {
1247 'Content-Type': 'application/x-www-form-urlencoded',
1248 },
1249 body: new URLSearchParams({
1250 'action': 'mxchat_check_pdf_status',
1251 'session_id': sessionId,
1252 'nonce': mxchatChat.nonce
1253 })
1254 })
1255 .then(response => response.json())
1256 .then(data => {
1257 if (data.success && data.data.filename) {
1258 showActivePdf(data.data.filename);
1259 activePdfFile = data.data.filename;
1260 }
1261 })
1262 .catch(error => {
1263 console.error('Error checking PDF status:', error);
1264 });
1265
1266 // Check Word document status
1267 fetch(mxchatChat.ajax_url, {
1268 method: 'POST',
1269 headers: {
1270 'Content-Type': 'application/x-www-form-urlencoded',
1271 },
1272 body: new URLSearchParams({
1273 'action': 'mxchat_check_word_status',
1274 'session_id': sessionId,
1275 'nonce': mxchatChat.nonce
1276 })
1277 })
1278 .then(response => response.json())
1279 .then(data => {
1280 if (data.success && data.data.filename) {
1281 showActiveWord(data.data.filename);
1282 activeWordFile = data.data.filename;
1283 }
1284 })
1285 .catch(error => {
1286 console.error('Error checking Word document status:', error);
1287 });
1288 }
1289
1290 // Apply toolbar settings
1291 if (mxchatChat.chat_toolbar_toggle === 'on') {
1292 $('.chat-toolbar').show();
1293 } else {
1294 $('.chat-toolbar').hide();
1295 }
1296
1297 // Initialize on page load
1298 document.addEventListener('DOMContentLoaded', function() {
1299 checkInitialDocumentStatus();
1300 });
1301
1302 // Style all toolbar elements
1303 const toolbarElements = [
1304 '#mxchat-chatbot .toolbar-btn svg',
1305 '#mxchat-chatbot .active-pdf-name',
1306 '#mxchat-chatbot .active-word-name',
1307 '#mxchat-chatbot .remove-pdf-btn svg',
1308 '#mxchat-chatbot .remove-word-btn svg'
1309 ];
1310 $(toolbarElements.join(', ')).css({
1311 'fill': toolbarIconColor,
1312 'color': toolbarIconColor
1313 });
1314
1315
1316
1317 // Ensure essential elements are defined
1318 const emailForm = document.getElementById('email-collection-form');
1319 const emailBlocker = document.getElementById('email-blocker');
1320 const chatbotWrapper = document.getElementById('chat-container');
1321
1322 if (emailForm && emailBlocker && chatbotWrapper) {
1323 // Check if email exists for the current session
1324 function checkSessionAndEmail() {
1325 const sessionId = getChatSession();
1326 //console.log("[DEBUG JS] checkSessionAndEmail -> sessionId:", sessionId);
1327
1328 fetch(mxchatChat.ajax_url, {
1329 method: 'POST',
1330 headers: {
1331 'Content-Type': 'application/x-www-form-urlencoded',
1332 },
1333 body: new URLSearchParams({
1334 action: 'mxchat_check_email_provided',
1335 session_id: sessionId,
1336 nonce: mxchatChat.nonce,
1337 }),
1338 })
1339 .then((response) => response.json())
1340 .then((data) => {
1341 //console.log("[DEBUG JS] mxchat_check_email_provided response:", data);
1342
1343 if (data.success) {
1344 if (data.data.logged_in) {
1345 //console.log("[DEBUG JS] User is logged in. Hiding email form.");
1346 emailBlocker.style.display = 'none';
1347 chatbotWrapper.style.display = 'flex';
1348 } else if (data.data.email) {
1349 //console.log("[DEBUG JS] Email found for session. Hiding email form.");
1350 emailBlocker.style.display = 'none';
1351 chatbotWrapper.style.display = 'flex';
1352 } else {
1353 //console.log("[DEBUG JS] No email provided. Showing email form.");
1354 emailBlocker.style.display = 'flex';
1355 chatbotWrapper.style.display = 'none';
1356 }
1357 } else {
1358 //console.log("[DEBUG JS] Error or no data received. Showing email form.");
1359 emailBlocker.style.display = 'flex';
1360 chatbotWrapper.style.display = 'none';
1361 }
1362 })
1363 .catch((error) => {
1364 // console.error("[DEBUG JS] Fetch error -> forcing email form visible:", error);
1365 emailBlocker.style.display = 'flex';
1366 chatbotWrapper.style.display = 'none';
1367 });
1368 }
1369
1370
1371
1372 // Handle email form submission
1373 emailForm.addEventListener('submit', function (event) {
1374 event.preventDefault();
1375 const userEmail = document.getElementById('user-email').value;
1376 const sessionId = getChatSession();
1377
1378 if (userEmail) {
1379 fetch(mxchatChat.ajax_url, {
1380 method: 'POST',
1381 headers: {
1382 'Content-Type': 'application/x-www-form-urlencoded',
1383 },
1384 body: new URLSearchParams({
1385 action: 'mxchat_handle_save_email_and_response',
1386 email: userEmail,
1387 session_id: sessionId,
1388 nonce: mxchatChat.nonce,
1389 }),
1390 })
1391 .then((response) => response.json())
1392 .then((data) => {
1393 //console.log('Backend response:', data);
1394 if (data.success) {
1395 //console.log('Email saved successfully:', userEmail);
1396 emailBlocker.style.display = 'none';
1397 chatbotWrapper.style.display = 'flex';
1398
1399 // Optionally handle bot response
1400 if (data.message) {
1401 appendMessage('bot', data.message);
1402 scrollToBottom();
1403 }
1404 } else {
1405 console.error('Error saving email:', data.message || 'Unknown error');
1406 }
1407 })
1408 .catch((error) => {
1409 console.error('AJAX error:', error);
1410 });
1411 }
1412 });
1413
1414 // Check session and email status on page load
1415 checkSessionAndEmail();
1416 } else {
1417 console.error('Essential elements for email handling are missing.');
1418 }
1419
1420
1421
1422
1423 });
1424
1425 // Event listener for copy button
1426 document.addEventListener("click", (e) => {
1427 if (e.target.classList.contains("mxchat-copy-button")) {
1428 const copyButton = e.target;
1429 const codeBlock = copyButton
1430 .closest(".mxchat-code-block-container")
1431 .querySelector(".mxchat-code-block code");
1432
1433 if (codeBlock) {
1434 // Preserve formatting using innerText
1435 navigator.clipboard.writeText(codeBlock.innerText).then(() => {
1436 copyButton.textContent = "Copied!";
1437 copyButton.setAttribute("aria-label", "Copied to clipboard");
1438
1439 setTimeout(() => {
1440 copyButton.textContent = "Copy";
1441 copyButton.setAttribute("aria-label", "Copy to clipboard");
1442 }, 2000);
1443 });
1444 }
1445 }
1446 });
1447
1448
1449
1450