PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.2.8
MxChat – AI Chatbot & Content Generation for WordPress v2.2.8
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / js / chat-script.js

chat-script.js in MxChat – AI Chatbot & Content Generation for WordPress 2.2.8, at js/chat-script.js

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