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

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