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

1,300 lines 44.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 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 // Function to show the chatbot widget (moved outside the Complianz logic)
740 function showChatWidget() {
741 setTimeout(function() {
742 $('#floating-chatbot-button').css('display', 'flex').fadeTo(500, 1);
743 }, 250);
744 }
745
746 // Function to hide the chatbot widget
747 function hideChatWidget() {
748 $('#floating-chatbot-button').css('display', 'none');
749 }
750
751 // Pre-chat dismissal check function (wrapped in a function for reuse)
752 function checkPreChatDismissal() {
753 $.ajax({
754 url: mxchatChat.ajax_url,
755 type: 'POST',
756 data: {
757 action: 'mxchat_check_pre_chat_message_status',
758 _ajax_nonce: mxchatChat.nonce
759 },
760 success: function(response) {
761 if (response.success && !response.data.dismissed) {
762 $('#pre-chat-message').fadeIn(250);
763 } else {
764 $('#pre-chat-message').hide();
765 }
766 },
767 error: function() {
768 console.error('Failed to check pre-chat message dismissal status.');
769 }
770 });
771 }
772
773 // Function to dismiss pre-chat message for 24 hours
774 function handlePreChatDismissal() {
775 $('#pre-chat-message').fadeOut(200);
776 $.ajax({
777 url: mxchatChat.ajax_url,
778 type: 'POST',
779 data: {
780 action: 'mxchat_dismiss_pre_chat_message',
781 _ajax_nonce: mxchatChat.nonce
782 },
783 success: function() {
784 $('#pre-chat-message').hide();
785 },
786 error: function() {
787 console.error('Failed to dismiss pre-chat message.');
788 }
789 });
790 }
791
792 // Handle pre-chat message dismissal on button click
793 $(document).on('click', '.close-pre-chat-message', function(e) {
794 e.stopPropagation();
795 handlePreChatDismissal();
796 });
797
798 // Function for Complianz logic
799 var applyComplianzLogic = mxchatChat.complianz_toggle;
800 if (applyComplianzLogic) {
801 function checkConsentAndShowChat() {
802 var consentStatus = typeof cmplz_has_consent === "function" && cmplz_has_consent('marketing');
803 var consentType = typeof complianz !== 'undefined' ? complianz.consenttype : null;
804
805 // Show the chatbot by default
806 showChatWidget();
807
808 if (consentType === 'optin' && !consentStatus) {
809 // For opt-in, hide only if user explicitly denies consent
810 hideChatWidget();
811 } else if (consentType === 'optout' && consentStatus === false) {
812 // For opt-out, hide only if user explicitly denies consent
813 hideChatWidget();
814 } else {
815 // Keep showing the chatbot
816 showChatWidget();
817 }
818
819 checkPreChatDismissal(); // Ensure we check dismissal after consent is handled
820 }
821
822 // Initial check when the page loads
823 checkConsentAndShowChat();
824
825 // Listen for changes in consent status
826 $(document).on('cmplz_status_change', function(event, category) {
827 if (category === 'marketing') {
828 checkConsentAndShowChat();
829 }
830 });
831 } else {
832 // If Complianz is not toggled on, always show the chatbot
833 showChatWidget();
834 checkPreChatDismissal(); // Always check pre-chat dismissal when consent logic is not applied
835 }
836
837 // Toggle chatbot visibility on floating button click
838 $(document).on('click', '#floating-chatbot-button', function() {
839 var chatbot = $('#floating-chatbot');
840 if (chatbot.hasClass('hidden')) {
841 chatbot.removeClass('hidden').addClass('visible');
842 $(this).addClass('hidden');
843 disableScroll();
844 // Hide the pre-chat message without dismissing it
845 $('#pre-chat-message').fadeOut(250);
846 } else {
847 chatbot.removeClass('visible').addClass('hidden');
848 $(this).removeClass('hidden');
849 enableScroll();
850 // Show the pre-chat message again if it hasn't been dismissed
851 checkPreChatDismissal();
852 }
853 });
854
855 $(document).on('click', '#exit-chat-button', function() {
856 $('#floating-chatbot').addClass('hidden').removeClass('visible');
857 $('#floating-chatbot-button').removeClass('hidden');
858 enableScroll();
859 });
860
861 // Close pre-chat message on click
862 $(document).on('click', '.close-pre-chat-message', function(e) {
863 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
864 $('#pre-chat-message').fadeOut(200, function() {
865 $(this).remove();
866 });
867 });
868
869 // Open chatbot when pre-chat message is clicked
870 $(document).on('click', '#pre-chat-message', function() {
871 var chatbot = $('#floating-chatbot');
872 if (chatbot.hasClass('hidden')) {
873 chatbot.removeClass('hidden').addClass('visible');
874 $('#floating-chatbot-button').addClass('hidden');
875 $('#pre-chat-message').fadeOut(250); // Hide pre-chat message
876 disableScroll(); // Disable scroll when chatbot opens
877 }
878 });
879
880 // If the chatbot is initially hidden, ensure the button is visible
881 if ($('#floating-chatbot').hasClass('hidden')) {
882 $('#floating-chatbot-button').removeClass('hidden');
883 }
884
885 function setFullHeight() {
886 var vh = $(window).innerHeight() * 0.01;
887 $(':root').css('--vh', vh + 'px');
888 }
889
890 // Set the height when the page loads
891 $(document).ready(function() {
892 setFullHeight();
893 });
894
895 // Set the height on resize and orientation change events
896 $(window).on('resize orientationchange', function() {
897 setFullHeight();
898 });
899
900
901 // Now handle the close button to dismiss the pre-chat message for 24 hours
902 var closeButton = document.querySelector('.close-pre-chat-message');
903 if (closeButton) {
904 closeButton.addEventListener('click', function() {
905 $('#pre-chat-message').fadeOut(200); // Hide the message
906
907 // Send an AJAX request to set the transient flag for 24 hours
908 $.ajax({
909 url: mxchatChat.ajax_url,
910 type: 'POST',
911 data: {
912 action: 'mxchat_dismiss_pre_chat_message',
913 _ajax_nonce: mxchatChat.nonce
914 },
915 success: function() {
916 //console.log('Pre-chat message dismissed for 24 hours.');
917
918 // Ensure the message is hidden after dismissal
919 $('#pre-chat-message').hide();
920 },
921 error: function() {
922 //console.error('Failed to dismiss pre-chat message.');
923 }
924 });
925 });
926 }
927
928
929
930
931 // Event listener for Add to Cart button
932 $(document).on('click', '.mxchat-add-to-cart-button', function() {
933 var productId = $(this).data('product-id');
934 // Add a special prefix to indicate this is from button
935 appendMessage("user", "add to cart");
936 sendMessageToChatbot("!addtocart"); // Special command to indicate button click
937 });
938
939
940
941 // PDF Upload button click handler
942 document.getElementById('pdf-upload-btn').addEventListener('click', function() {
943 document.getElementById('pdf-upload').click();
944 });
945
946 // Word Upload button click handler
947 document.getElementById('word-upload-btn').addEventListener('click', function() {
948 document.getElementById('word-upload').click();
949 });
950
951 // PDF file input change handler
952 document.getElementById('pdf-upload').addEventListener('change', async function(e) {
953 const file = e.target.files[0];
954
955 if (!file || file.type !== 'application/pdf') {
956 alert('Please select a valid PDF file.');
957 return;
958 }
959
960 if (!sessionId) {
961 console.error('No session ID found');
962 alert('Error: No session ID found');
963 return;
964 }
965
966 if (!mxchatChat || !mxchatChat.ajax_url || !mxchatChat.nonce) {
967 console.error('mxchatChat not properly configured:', mxchatChat);
968 alert('Error: Ajax configuration missing');
969 return;
970 }
971
972 // Disable buttons and show loading state
973 const uploadBtn = document.getElementById('pdf-upload-btn');
974 const sendBtn = document.getElementById('send-button');
975 const originalBtnContent = uploadBtn.innerHTML;
976
977 try {
978 const formData = new FormData();
979 formData.append('action', 'mxchat_upload_pdf');
980 formData.append('pdf_file', file);
981 formData.append('session_id', sessionId);
982 formData.append('nonce', mxchatChat.nonce);
983
984 uploadBtn.disabled = true;
985 sendBtn.disabled = true;
986 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
987 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
988 </svg>`;
989
990 const response = await fetch(mxchatChat.ajax_url, {
991 method: 'POST',
992 body: formData
993 });
994
995 const data = await response.json();
996
997 if (data.success) {
998 // Hide popular questions if they exist
999 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1000 if (popularQuestionsContainer) {
1001 popularQuestionsContainer.style.display = 'none';
1002 }
1003
1004 // Show the active PDF name
1005 showActivePdf(data.data.filename);
1006
1007 appendMessage('bot', data.data.message);
1008 scrollToBottom();
1009 activePdfFile = data.data.filename;
1010 } else {
1011 console.error('Upload failed:', data.data);
1012 alert('Failed to upload PDF. Please try again.');
1013 }
1014 } catch (error) {
1015 console.error('Upload error:', error);
1016 alert('Error uploading file. Please try again.');
1017 } finally {
1018 uploadBtn.disabled = false;
1019 sendBtn.disabled = false;
1020 uploadBtn.innerHTML = originalBtnContent;
1021 this.value = ''; // Reset file input
1022 }
1023 });
1024
1025 // Word file input change handler
1026 document.getElementById('word-upload').addEventListener('change', async function(e) {
1027 const file = e.target.files[0];
1028
1029 if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1030 alert('Please select a valid Word document (.docx).');
1031 return;
1032 }
1033
1034 if (!sessionId) {
1035 console.error('No session ID found');
1036 alert('Error: No session ID found');
1037 return;
1038 }
1039
1040 // Disable buttons and show loading state
1041 const uploadBtn = document.getElementById('word-upload-btn');
1042 const sendBtn = document.getElementById('send-button');
1043 const originalBtnContent = uploadBtn.innerHTML;
1044
1045 try {
1046 const formData = new FormData();
1047 formData.append('action', 'mxchat_upload_word');
1048 formData.append('word_file', file);
1049 formData.append('session_id', sessionId);
1050 formData.append('nonce', mxchatChat.nonce);
1051
1052 uploadBtn.disabled = true;
1053 sendBtn.disabled = true;
1054 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1055 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1056 </svg>`;
1057
1058 const response = await fetch(mxchatChat.ajax_url, {
1059 method: 'POST',
1060 body: formData
1061 });
1062
1063 const data = await response.json();
1064
1065 if (data.success) {
1066 // Hide popular questions if they exist
1067 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1068 if (popularQuestionsContainer) {
1069 popularQuestionsContainer.style.display = 'none';
1070 }
1071
1072 // Show the active Word document name
1073 showActiveWord(data.data.filename);
1074
1075 appendMessage('bot', data.data.message);
1076 scrollToBottom();
1077 activeWordFile = data.data.filename;
1078 } else {
1079 console.error('Upload failed:', data.data);
1080 alert('Failed to upload Word document. Please try again.');
1081 }
1082 } catch (error) {
1083 console.error('Upload error:', error);
1084 alert('Error uploading file. Please try again.');
1085 } finally {
1086 uploadBtn.disabled = false;
1087 sendBtn.disabled = false;
1088 uploadBtn.innerHTML = originalBtnContent;
1089 this.value = ''; // Reset file input
1090 }
1091 });
1092
1093 // Function to show active PDF name in toolbar
1094 function showActivePdf(filename) {
1095 const container = document.getElementById('active-pdf-container');
1096 const nameElement = document.getElementById('active-pdf-name');
1097
1098 if (!container || !nameElement) {
1099 console.error('PDF container elements not found');
1100 return;
1101 }
1102
1103 nameElement.textContent = filename;
1104 container.style.display = 'flex';
1105 }
1106
1107 // Function to show active Word document name in toolbar
1108 function showActiveWord(filename) {
1109 const container = document.getElementById('active-word-container');
1110 const nameElement = document.getElementById('active-word-name');
1111
1112 if (!container || !nameElement) {
1113 console.error('Word document container elements not found');
1114 return;
1115 }
1116
1117 nameElement.textContent = filename;
1118 container.style.display = 'flex';
1119 }
1120
1121 // Function to remove active PDF
1122 function removeActivePdf() {
1123 const container = document.getElementById('active-pdf-container');
1124 const nameElement = document.getElementById('active-pdf-name');
1125
1126 if (!container || !nameElement || !activePdfFile) return;
1127
1128 fetch(mxchatChat.ajax_url, {
1129 method: 'POST',
1130 headers: {
1131 'Content-Type': 'application/x-www-form-urlencoded',
1132 },
1133 body: new URLSearchParams({
1134 'action': 'mxchat_remove_pdf',
1135 'session_id': sessionId,
1136 'nonce': mxchatChat.nonce
1137 })
1138 })
1139 .then(response => response.json())
1140 .then(data => {
1141 if (data.success) {
1142 container.style.display = 'none';
1143 nameElement.textContent = '';
1144 activePdfFile = null;
1145 appendMessage('bot', 'PDF removed.');
1146 }
1147 })
1148 .catch(error => {
1149 console.error('Error removing PDF:', error);
1150 });
1151 }
1152
1153 // Function to remove active Word document
1154 function removeActiveWord() {
1155 const container = document.getElementById('active-word-container');
1156 const nameElement = document.getElementById('active-word-name');
1157
1158 if (!container || !nameElement || !activeWordFile) return;
1159
1160 fetch(mxchatChat.ajax_url, {
1161 method: 'POST',
1162 headers: {
1163 'Content-Type': 'application/x-www-form-urlencoded',
1164 },
1165 body: new URLSearchParams({
1166 'action': 'mxchat_remove_word',
1167 'session_id': sessionId,
1168 'nonce': mxchatChat.nonce
1169 })
1170 })
1171 .then(response => response.json())
1172 .then(data => {
1173 if (data.success) {
1174 container.style.display = 'none';
1175 nameElement.textContent = '';
1176 activeWordFile = null;
1177 appendMessage('bot', 'Word document removed.');
1178 }
1179 })
1180 .catch(error => {
1181 console.error('Error removing Word document:', error);
1182 });
1183 }
1184
1185 // Add remove button click handlers
1186 document.getElementById('remove-pdf-btn')?.addEventListener('click', function(e) {
1187 e.preventDefault();
1188 e.stopPropagation();
1189 removeActivePdf();
1190 });
1191
1192 document.getElementById('remove-word-btn')?.addEventListener('click', function(e) {
1193 e.preventDefault();
1194 e.stopPropagation();
1195 removeActiveWord();
1196 });
1197
1198 // Check initial document status
1199 function checkInitialDocumentStatus() {
1200 if (!sessionId) return;
1201
1202 // Check PDF status
1203 fetch(mxchatChat.ajax_url, {
1204 method: 'POST',
1205 headers: {
1206 'Content-Type': 'application/x-www-form-urlencoded',
1207 },
1208 body: new URLSearchParams({
1209 'action': 'mxchat_check_pdf_status',
1210 'session_id': sessionId,
1211 'nonce': mxchatChat.nonce
1212 })
1213 })
1214 .then(response => response.json())
1215 .then(data => {
1216 if (data.success && data.data.filename) {
1217 showActivePdf(data.data.filename);
1218 activePdfFile = data.data.filename;
1219 }
1220 })
1221 .catch(error => {
1222 console.error('Error checking PDF status:', error);
1223 });
1224
1225 // Check Word document status
1226 fetch(mxchatChat.ajax_url, {
1227 method: 'POST',
1228 headers: {
1229 'Content-Type': 'application/x-www-form-urlencoded',
1230 },
1231 body: new URLSearchParams({
1232 'action': 'mxchat_check_word_status',
1233 'session_id': sessionId,
1234 'nonce': mxchatChat.nonce
1235 })
1236 })
1237 .then(response => response.json())
1238 .then(data => {
1239 if (data.success && data.data.filename) {
1240 showActiveWord(data.data.filename);
1241 activeWordFile = data.data.filename;
1242 }
1243 })
1244 .catch(error => {
1245 console.error('Error checking Word document status:', error);
1246 });
1247 }
1248
1249 // Apply toolbar settings
1250 if (mxchatChat.chat_toolbar_toggle === 'on') {
1251 $('.chat-toolbar').show();
1252 } else {
1253 $('.chat-toolbar').hide();
1254 }
1255
1256 // Initialize on page load
1257 document.addEventListener('DOMContentLoaded', function() {
1258 checkInitialDocumentStatus();
1259 });
1260
1261 // Style all toolbar elements
1262 const toolbarElements = [
1263 '#mxchat-chatbot .toolbar-btn svg',
1264 '#mxchat-chatbot .active-pdf-name',
1265 '#mxchat-chatbot .active-word-name',
1266 '#mxchat-chatbot .remove-pdf-btn svg',
1267 '#mxchat-chatbot .remove-word-btn svg'
1268 ];
1269 $(toolbarElements.join(', ')).css({
1270 'fill': toolbarIconColor,
1271 'color': toolbarIconColor
1272 });
1273
1274
1275
1276 });
1277
1278 // Event listener for copy button
1279 document.addEventListener("click", (e) => {
1280 if (e.target.classList.contains("mxchat-copy-button")) {
1281 const copyButton = e.target;
1282 const codeBlock = copyButton
1283 .closest(".mxchat-code-block-container")
1284 .querySelector(".mxchat-code-block code");
1285
1286 if (codeBlock) {
1287 // Preserve formatting using innerText
1288 navigator.clipboard.writeText(codeBlock.innerText).then(() => {
1289 copyButton.textContent = "Copied!";
1290 copyButton.setAttribute("aria-label", "Copied to clipboard");
1291
1292 setTimeout(() => {
1293 copyButton.textContent = "Copy";
1294 copyButton.setAttribute("aria-label", "Copy to clipboard");
1295 }, 2000);
1296 });
1297 }
1298 }
1299 });
1300