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

1,304 lines 44.8 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'); // Get product ID from data attribute
934
935 // Simulate user message first for proper ordering
936 appendMessage("user", "add to cart"); // Display the user's "add to cart" message first
937
938
939 // Use existing function to send the "add to cart" command to the chatbot
940 sendMessageToChatbot("add to cart"); // Triggers the chatbot response as though user typed it
941 });
942
943
944
945 // PDF Upload button click handler
946 document.getElementById('pdf-upload-btn').addEventListener('click', function() {
947 document.getElementById('pdf-upload').click();
948 });
949
950 // Word Upload button click handler
951 document.getElementById('word-upload-btn').addEventListener('click', function() {
952 document.getElementById('word-upload').click();
953 });
954
955 // PDF file input change handler
956 document.getElementById('pdf-upload').addEventListener('change', async function(e) {
957 const file = e.target.files[0];
958
959 if (!file || file.type !== 'application/pdf') {
960 alert('Please select a valid PDF file.');
961 return;
962 }
963
964 if (!sessionId) {
965 console.error('No session ID found');
966 alert('Error: No session ID found');
967 return;
968 }
969
970 if (!mxchatChat || !mxchatChat.ajax_url || !mxchatChat.nonce) {
971 console.error('mxchatChat not properly configured:', mxchatChat);
972 alert('Error: Ajax configuration missing');
973 return;
974 }
975
976 // Disable buttons and show loading state
977 const uploadBtn = document.getElementById('pdf-upload-btn');
978 const sendBtn = document.getElementById('send-button');
979 const originalBtnContent = uploadBtn.innerHTML;
980
981 try {
982 const formData = new FormData();
983 formData.append('action', 'mxchat_upload_pdf');
984 formData.append('pdf_file', file);
985 formData.append('session_id', sessionId);
986 formData.append('nonce', mxchatChat.nonce);
987
988 uploadBtn.disabled = true;
989 sendBtn.disabled = true;
990 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
991 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
992 </svg>`;
993
994 const response = await fetch(mxchatChat.ajax_url, {
995 method: 'POST',
996 body: formData
997 });
998
999 const data = await response.json();
1000
1001 if (data.success) {
1002 // Hide popular questions if they exist
1003 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1004 if (popularQuestionsContainer) {
1005 popularQuestionsContainer.style.display = 'none';
1006 }
1007
1008 // Show the active PDF name
1009 showActivePdf(data.data.filename);
1010
1011 appendMessage('bot', data.data.message);
1012 scrollToBottom();
1013 activePdfFile = data.data.filename;
1014 } else {
1015 console.error('Upload failed:', data.data);
1016 alert('Failed to upload PDF. Please try again.');
1017 }
1018 } catch (error) {
1019 console.error('Upload error:', error);
1020 alert('Error uploading file. Please try again.');
1021 } finally {
1022 uploadBtn.disabled = false;
1023 sendBtn.disabled = false;
1024 uploadBtn.innerHTML = originalBtnContent;
1025 this.value = ''; // Reset file input
1026 }
1027 });
1028
1029 // Word file input change handler
1030 document.getElementById('word-upload').addEventListener('change', async function(e) {
1031 const file = e.target.files[0];
1032
1033 if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1034 alert('Please select a valid Word document (.docx).');
1035 return;
1036 }
1037
1038 if (!sessionId) {
1039 console.error('No session ID found');
1040 alert('Error: No session ID found');
1041 return;
1042 }
1043
1044 // Disable buttons and show loading state
1045 const uploadBtn = document.getElementById('word-upload-btn');
1046 const sendBtn = document.getElementById('send-button');
1047 const originalBtnContent = uploadBtn.innerHTML;
1048
1049 try {
1050 const formData = new FormData();
1051 formData.append('action', 'mxchat_upload_word');
1052 formData.append('word_file', file);
1053 formData.append('session_id', sessionId);
1054 formData.append('nonce', mxchatChat.nonce);
1055
1056 uploadBtn.disabled = true;
1057 sendBtn.disabled = true;
1058 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1059 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1060 </svg>`;
1061
1062 const response = await fetch(mxchatChat.ajax_url, {
1063 method: 'POST',
1064 body: formData
1065 });
1066
1067 const data = await response.json();
1068
1069 if (data.success) {
1070 // Hide popular questions if they exist
1071 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1072 if (popularQuestionsContainer) {
1073 popularQuestionsContainer.style.display = 'none';
1074 }
1075
1076 // Show the active Word document name
1077 showActiveWord(data.data.filename);
1078
1079 appendMessage('bot', data.data.message);
1080 scrollToBottom();
1081 activeWordFile = data.data.filename;
1082 } else {
1083 console.error('Upload failed:', data.data);
1084 alert('Failed to upload Word document. Please try again.');
1085 }
1086 } catch (error) {
1087 console.error('Upload error:', error);
1088 alert('Error uploading file. Please try again.');
1089 } finally {
1090 uploadBtn.disabled = false;
1091 sendBtn.disabled = false;
1092 uploadBtn.innerHTML = originalBtnContent;
1093 this.value = ''; // Reset file input
1094 }
1095 });
1096
1097 // Function to show active PDF name in toolbar
1098 function showActivePdf(filename) {
1099 const container = document.getElementById('active-pdf-container');
1100 const nameElement = document.getElementById('active-pdf-name');
1101
1102 if (!container || !nameElement) {
1103 console.error('PDF container elements not found');
1104 return;
1105 }
1106
1107 nameElement.textContent = filename;
1108 container.style.display = 'flex';
1109 }
1110
1111 // Function to show active Word document name in toolbar
1112 function showActiveWord(filename) {
1113 const container = document.getElementById('active-word-container');
1114 const nameElement = document.getElementById('active-word-name');
1115
1116 if (!container || !nameElement) {
1117 console.error('Word document container elements not found');
1118 return;
1119 }
1120
1121 nameElement.textContent = filename;
1122 container.style.display = 'flex';
1123 }
1124
1125 // Function to remove active PDF
1126 function removeActivePdf() {
1127 const container = document.getElementById('active-pdf-container');
1128 const nameElement = document.getElementById('active-pdf-name');
1129
1130 if (!container || !nameElement || !activePdfFile) return;
1131
1132 fetch(mxchatChat.ajax_url, {
1133 method: 'POST',
1134 headers: {
1135 'Content-Type': 'application/x-www-form-urlencoded',
1136 },
1137 body: new URLSearchParams({
1138 'action': 'mxchat_remove_pdf',
1139 'session_id': sessionId,
1140 'nonce': mxchatChat.nonce
1141 })
1142 })
1143 .then(response => response.json())
1144 .then(data => {
1145 if (data.success) {
1146 container.style.display = 'none';
1147 nameElement.textContent = '';
1148 activePdfFile = null;
1149 appendMessage('bot', 'PDF removed.');
1150 }
1151 })
1152 .catch(error => {
1153 console.error('Error removing PDF:', error);
1154 });
1155 }
1156
1157 // Function to remove active Word document
1158 function removeActiveWord() {
1159 const container = document.getElementById('active-word-container');
1160 const nameElement = document.getElementById('active-word-name');
1161
1162 if (!container || !nameElement || !activeWordFile) return;
1163
1164 fetch(mxchatChat.ajax_url, {
1165 method: 'POST',
1166 headers: {
1167 'Content-Type': 'application/x-www-form-urlencoded',
1168 },
1169 body: new URLSearchParams({
1170 'action': 'mxchat_remove_word',
1171 'session_id': sessionId,
1172 'nonce': mxchatChat.nonce
1173 })
1174 })
1175 .then(response => response.json())
1176 .then(data => {
1177 if (data.success) {
1178 container.style.display = 'none';
1179 nameElement.textContent = '';
1180 activeWordFile = null;
1181 appendMessage('bot', 'Word document removed.');
1182 }
1183 })
1184 .catch(error => {
1185 console.error('Error removing Word document:', error);
1186 });
1187 }
1188
1189 // Add remove button click handlers
1190 document.getElementById('remove-pdf-btn')?.addEventListener('click', function(e) {
1191 e.preventDefault();
1192 e.stopPropagation();
1193 removeActivePdf();
1194 });
1195
1196 document.getElementById('remove-word-btn')?.addEventListener('click', function(e) {
1197 e.preventDefault();
1198 e.stopPropagation();
1199 removeActiveWord();
1200 });
1201
1202 // Check initial document status
1203 function checkInitialDocumentStatus() {
1204 if (!sessionId) return;
1205
1206 // Check PDF status
1207 fetch(mxchatChat.ajax_url, {
1208 method: 'POST',
1209 headers: {
1210 'Content-Type': 'application/x-www-form-urlencoded',
1211 },
1212 body: new URLSearchParams({
1213 'action': 'mxchat_check_pdf_status',
1214 'session_id': sessionId,
1215 'nonce': mxchatChat.nonce
1216 })
1217 })
1218 .then(response => response.json())
1219 .then(data => {
1220 if (data.success && data.data.filename) {
1221 showActivePdf(data.data.filename);
1222 activePdfFile = data.data.filename;
1223 }
1224 })
1225 .catch(error => {
1226 console.error('Error checking PDF status:', error);
1227 });
1228
1229 // Check Word document status
1230 fetch(mxchatChat.ajax_url, {
1231 method: 'POST',
1232 headers: {
1233 'Content-Type': 'application/x-www-form-urlencoded',
1234 },
1235 body: new URLSearchParams({
1236 'action': 'mxchat_check_word_status',
1237 'session_id': sessionId,
1238 'nonce': mxchatChat.nonce
1239 })
1240 })
1241 .then(response => response.json())
1242 .then(data => {
1243 if (data.success && data.data.filename) {
1244 showActiveWord(data.data.filename);
1245 activeWordFile = data.data.filename;
1246 }
1247 })
1248 .catch(error => {
1249 console.error('Error checking Word document status:', error);
1250 });
1251 }
1252
1253 // Apply toolbar settings
1254 if (mxchatChat.chat_toolbar_toggle === 'on') {
1255 $('.chat-toolbar').show();
1256 } else {
1257 $('.chat-toolbar').hide();
1258 }
1259
1260 // Initialize on page load
1261 document.addEventListener('DOMContentLoaded', function() {
1262 checkInitialDocumentStatus();
1263 });
1264
1265 // Style all toolbar elements
1266 const toolbarElements = [
1267 '#mxchat-chatbot .toolbar-btn svg',
1268 '#mxchat-chatbot .active-pdf-name',
1269 '#mxchat-chatbot .active-word-name',
1270 '#mxchat-chatbot .remove-pdf-btn svg',
1271 '#mxchat-chatbot .remove-word-btn svg'
1272 ];
1273 $(toolbarElements.join(', ')).css({
1274 'fill': toolbarIconColor,
1275 'color': toolbarIconColor
1276 });
1277
1278
1279
1280 });
1281
1282 // Event listener for copy button
1283 document.addEventListener("click", (e) => {
1284 if (e.target.classList.contains("mxchat-copy-button")) {
1285 const copyButton = e.target;
1286 const codeBlock = copyButton
1287 .closest(".mxchat-code-block-container")
1288 .querySelector(".mxchat-code-block code");
1289
1290 if (codeBlock) {
1291 // Preserve formatting using innerText
1292 navigator.clipboard.writeText(codeBlock.innerText).then(() => {
1293 copyButton.textContent = "Copied!";
1294 copyButton.setAttribute("aria-label", "Copied to clipboard");
1295
1296 setTimeout(() => {
1297 copyButton.textContent = "Copy";
1298 copyButton.setAttribute("aria-label", "Copy to clipboard");
1299 }, 2000);
1300 });
1301 }
1302 }
1303 });
1304