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

2,392 lines 89.9 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 // NEW: Extract MxChat context data attributes before getting text content
138 const contextData = [];
139 clone.querySelectorAll('[data-mxchat-context]').forEach(el => {
140 const contextValue = el.dataset.mxchatContext;
141 if (contextValue && contextValue.trim()) {
142 contextData.push(contextValue);
143 }
144 });
145
146 // Get text content and clean it up
147 pageContent = clone.textContent || clone.innerText || '';
148
149 // Add context data to page content if any were found
150 if (contextData.length > 0) {
151 pageContent += '\n\nAdditional Context:\n' + contextData.join('\n');
152 }
153
154 // Clean up whitespace and limit length
155 pageContent = pageContent
156 .replace(/\s+/g, ' ')
157 .trim()
158 .substring(0, 3000); // Limit to 3000 characters to avoid token limits
159 }
160
161 // Only return context if we have meaningful content
162 if (!pageContent || pageContent.length < 50) {
163 return null;
164 }
165
166 return {
167 url: pageUrl,
168 title: pageTitle,
169 content: pageContent
170 };
171 }
172
173 // ====================================
174 // CORE CHAT FUNCTIONALITY
175 // ====================================
176 // Update your existing sendMessage function
177 function sendMessage() {
178 var message = $('#chat-input').val();
179
180 // ADD PROMPT HOOK HERE
181 if (typeof customMxChatFilter === 'function') {
182 message = customMxChatFilter(message, "prompt");
183 }
184
185 if (message) {
186 appendMessage("user", message);
187 $('#chat-input').val('');
188 $('#chat-input').css('height', 'auto');
189
190 if (hasQuickQuestions()) {
191 collapseQuickQuestions();
192 }
193 appendThinkingMessage();
194 scrollToBottom();
195
196 const currentModel = mxchatChat.model || 'gpt-4o';
197
198 // Check if streaming is enabled AND supported for this model
199 if (shouldUseStreaming(currentModel)) {
200 callMxChatStream(message, function(response) {
201 $('.bot-message.temporary-message').removeClass('temporary-message');
202 });
203 } else {
204 callMxChat(message, function(response) {
205 replaceLastMessage("bot", response);
206 });
207 }
208 }
209 }
210
211 // Update your existing sendMessageToChatbot function
212 function sendMessageToChatbot(message) {
213 // ADD PROMPT HOOK HERE
214 if (typeof customMxChatFilter === 'function') {
215 message = customMxChatFilter(message, "prompt");
216 }
217
218 var sessionId = getChatSession();
219
220 if (hasQuickQuestions()) {
221 collapseQuickQuestions();
222 }
223 appendThinkingMessage();
224 scrollToBottom();
225
226 const currentModel = mxchatChat.model || 'gpt-4o';
227
228 // Check if streaming is enabled AND supported for this model
229 if (shouldUseStreaming(currentModel)) {
230 callMxChatStream(message, function(response) {
231 $('.bot-message.temporary-message').removeClass('temporary-message');
232 });
233 } else {
234 callMxChat(message, function(response) {
235 $('.temporary-message').remove();
236 replaceLastMessage("bot", response);
237 });
238 }
239 }
240
241 // Updated shouldUseStreaming function with debugging
242 function shouldUseStreaming(model) {
243 // Check if streaming is enabled in settings (using your toggle naming pattern)
244 const streamingEnabled = mxchatChat.enable_streaming_toggle === 'on';
245
246 // Check if model supports streaming
247 const streamingSupported = isStreamingSupported(model);
248
249
250 // Only use streaming if both enabled and supported
251 return streamingEnabled && streamingSupported;
252 }
253
254 function callMxChat(message, callback) {
255 // Get page context if contextual awareness is enabled
256 const pageContext = getPageContext();
257
258 // Prepare AJAX data
259 const ajaxData = {
260 action: 'mxchat_handle_chat_request',
261 message: message,
262 session_id: getChatSession(),
263 nonce: mxchatChat.nonce
264 };
265
266 // Add page context if available
267 if (pageContext) {
268 ajaxData.page_context = JSON.stringify(pageContext);
269 }
270
271 // CHECK FOR VISION FLAGS AND ADD THEM
272 if (window.mxchatVisionProcessed) {
273 ajaxData.vision_processed = true;
274 ajaxData.original_user_message = window.mxchatOriginalMessage || message;
275 ajaxData.vision_images_count = window.mxchatVisionImagesCount || 0;
276 // Clear the flags after use
277 window.mxchatVisionProcessed = false;
278 window.mxchatOriginalMessage = null;
279 window.mxchatVisionImagesCount = 0;
280 }
281
282 $.ajax({
283 url: mxchatChat.ajax_url,
284 type: 'POST',
285 dataType: 'json',
286 data: ajaxData,
287 success: function(response) {
288 // Log the full response for debugging
289 //console.log("API Response:", response);
290
291 // First check if this is a successful response by looking for text, html, or message fields
292 // This preserves compatibility with your server response format
293 if (response.text !== undefined || response.html !== undefined || response.message !== undefined ||
294 (response.success === true && response.data && response.data.status === 'waiting_for_agent')) {
295
296 // Handle successful response - this is your original success handling code
297
298 // Existing chat mode check
299 if (response.chat_mode) {
300 updateChatModeIndicator(response.chat_mode);
301 }
302 else if (response.fallbackResponse && response.fallbackResponse.chat_mode) {
303 updateChatModeIndicator(response.fallbackResponse.chat_mode);
304 }
305
306 // Add PDF filename handling
307 if (response.data && response.data.filename) {
308 showActivePdf(response.data.filename);
309 activePdfFile = response.data.filename;
310 }
311
312 // Add redirect check here
313 if (response.redirect_url) {
314 let responseText = response.text || '';
315 if (responseText) {
316 replaceLastMessage("bot", responseText);
317 }
318 setTimeout(() => {
319 window.location.href = response.redirect_url;
320 }, 1500);
321 return;
322 }
323
324 // Check for live agent response
325 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
326 updateChatModeIndicator('agent');
327 return;
328 }
329
330 // Handle other responses
331 let responseText = response.text || '';
332 let responseHtml = response.html || '';
333 let responseMessage = response.message || '';
334
335 if (responseText === 'You are now chatting with the AI chatbot.') {
336 updateChatModeIndicator('ai');
337 }
338
339 // Handle the message and show notification if chat is hidden
340 if (responseText || responseHtml || responseMessage) {
341
342 // ADD RESPONSE HOOKS HERE - BEFORE DISPLAYING
343 if (responseText && typeof customMxChatFilter === 'function') {
344 responseText = customMxChatFilter(responseText, "response");
345 }
346 if (responseMessage && typeof customMxChatFilter === 'function') {
347 responseMessage = customMxChatFilter(responseMessage, "response");
348 }
349
350 // Update the messages as before
351 if (responseText && responseHtml) {
352 replaceLastMessage("bot", responseText, responseHtml);
353 } else if (responseText) {
354 replaceLastMessage("bot", responseText);
355 } else if (responseHtml) {
356 replaceLastMessage("bot", "", responseHtml);
357 } else if (responseMessage) {
358 replaceLastMessage("bot", responseMessage);
359 }
360
361 // Check if chat is hidden and show notification
362 if ($('#floating-chatbot').hasClass('hidden')) {
363 const badge = $('#chat-notification-badge');
364 if (badge.length) {
365 badge.show();
366 }
367 }
368 } else {
369 ////console.error("Unexpected response format:", response);
370 replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists.");
371 }
372
373 if (response.message_id) {
374 lastSeenMessageId = response.message_id;
375 }
376
377 return;
378 }
379
380 // If we got here, it's likely an error response
381 // Now we can check for error conditions with our robust error handling
382
383 let errorMessage = "";
384 let errorCode = "";
385
386 // Check various possible error locations in the response
387 if (response.data && response.data.error_message) {
388 errorMessage = response.data.error_message;
389 errorCode = response.data.error_code || "";
390 } else if (response.error_message) {
391 errorMessage = response.error_message;
392 errorCode = response.error_code || "";
393 } else if (response.message) {
394 errorMessage = response.message;
395 } else if (typeof response.data === 'string') {
396 errorMessage = response.data;
397 } else if (!response.success) {
398 // Explicit check for success: false without other error info
399 errorMessage = "An error occurred. Please try again or contact support.";
400 } else {
401 // Fallback for any other unexpected response format
402 errorMessage = "Unexpected response received. Please try again or contact support.";
403 }
404
405 // Log the error with code for debugging
406 //console.log("Response data:", response.data);
407 ////console.error("API Error:", errorMessage, "Code:", errorCode);
408
409 // Format user-friendly error message
410 let displayMessage = errorMessage;
411
412 // Customize message for admin users
413 if (mxchatChat.is_admin) {
414 // For admin users, show more technical details including error code
415 displayMessage = errorMessage + (errorCode ? " (Error code: " + errorCode + ")" : "");
416 }
417
418 replaceLastMessage("bot", displayMessage);
419 },
420 error: function(xhr, status, error) {
421 //console.error("AJAX Error:", status, error);
422 //console.log("Response Text:", xhr.responseText);
423
424 let errorMessage = "An unexpected error occurred.";
425
426 // Try to parse the response if it's JSON
427 try {
428 const responseJson = JSON.parse(xhr.responseText);
429 //console.log("Parsed error response:", responseJson);
430
431 if (responseJson.data && responseJson.data.error_message) {
432 errorMessage = responseJson.data.error_message;
433 } else if (responseJson.message) {
434 errorMessage = responseJson.message;
435 }
436 } catch (e) {
437 // Not JSON or parsing failed, use HTTP status based messages
438 if (xhr.status === 0) {
439 errorMessage = "Network error: Please check your internet connection.";
440 } else if (xhr.status === 403) {
441 errorMessage = "Access denied: Your session may have expired. Please refresh the page.";
442 } else if (xhr.status === 404) {
443 errorMessage = "API endpoint not found. Please contact support.";
444 } else if (xhr.status === 429) {
445 errorMessage = "Too many requests. Please try again in a moment.";
446 } else if (xhr.status >= 500) {
447 errorMessage = "Server error: The server encountered an issue. Please try again later.";
448 }
449 }
450
451 replaceLastMessage("bot", errorMessage);
452 }
453 });
454 }
455
456 function callMxChatStream(message, callback) {
457 //console.log("Using streaming for message:", message);
458
459 const currentModel = mxchatChat.model || 'gpt-4o';
460 if (!isStreamingSupported(currentModel)) {
461 //console.log("Streaming not supported, falling back to regular call");
462 callMxChat(message, callback);
463 return;
464 }
465
466 // Get page context if contextual awareness is enabled
467 const pageContext = getPageContext();
468
469 const formData = new FormData();
470 formData.append('action', 'mxchat_stream_chat');
471 formData.append('message', message);
472 formData.append('session_id', getChatSession());
473 formData.append('nonce', mxchatChat.nonce);
474
475 // Add page context if available
476 if (pageContext) {
477 formData.append('page_context', JSON.stringify(pageContext));
478 }
479
480 // CHECK FOR VISION FLAGS AND ADD THEM
481 if (window.mxchatVisionProcessed) {
482 formData.append('vision_processed', 'true');
483 formData.append('original_user_message', window.mxchatOriginalMessage || message);
484 formData.append('vision_images_count', window.mxchatVisionImagesCount || '0');
485 // Clear the flags after use
486 window.mxchatVisionProcessed = false;
487 window.mxchatOriginalMessage = null;
488 window.mxchatVisionImagesCount = 0;
489 }
490
491 let accumulatedContent = '';
492 let testingDataReceived = false;
493
494 fetch(mxchatChat.ajax_url, {
495 method: 'POST',
496 body: formData,
497 credentials: 'same-origin'
498 })
499 .then(response => {
500 //console.log("Streaming response received:", response);
501
502 if (!response.ok) {
503 throw new Error('Network response was not ok');
504 }
505
506 // Check if response is JSON instead of streaming
507 const contentType = response.headers.get('content-type');
508 if (contentType && contentType.includes('application/json')) {
509 //console.log("Received JSON response instead of stream, handling as regular response");
510 return response.json().then(data => {
511 // Handle as regular JSON response
512 $('.bot-message.temporary-message').remove();
513
514 // FIXED: Always check for testing panel, not just in testing mode
515 if (window.mxchatTestPanelInstance && data.testing_data) {
516 //console.log('Testing data found in streaming JSON response:', data.testing_data);
517 window.mxchatTestPanelInstance.handleTestingData(data.testing_data);
518 }
519
520 // Handle different response formats (including intent responses)
521 if (data.text || data.html || data.message) {
522
523 // ADD RESPONSE HOOKS HERE - FOR STREAMING JSON RESPONSES
524 if (data.text && typeof customMxChatFilter === 'function') {
525 data.text = customMxChatFilter(data.text, "response");
526 }
527 if (data.message && typeof customMxChatFilter === 'function') {
528 data.message = customMxChatFilter(data.message, "response");
529 }
530
531 if (data.text && data.html) {
532 replaceLastMessage("bot", data.text, data.html);
533 } else if (data.text) {
534 replaceLastMessage("bot", data.text);
535 } else if (data.html) {
536 replaceLastMessage("bot", "", data.html);
537 } else if (data.message) {
538 replaceLastMessage("bot", data.message);
539 }
540 }
541
542 // Handle other response properties
543 if (data.chat_mode) {
544 updateChatModeIndicator(data.chat_mode);
545 }
546
547 if (data.data && data.data.filename) {
548 showActivePdf(data.data.filename);
549 activePdfFile = data.data.filename;
550 }
551
552 if (callback) {
553 callback(data.text || data.message || '');
554 }
555 });
556 }
557
558 // NEW: Check if it's event-stream but might contain JSON (Cloudflare issue)
559 if (contentType && contentType.includes('text/event-stream')) {
560 // First, try to read the entire response to check if it's actually JSON
561 return response.text().then(fullText => {
562 // Check if the response looks like a single JSON object instead of SSE format
563 const trimmedText = fullText.trim();
564 if (trimmedText.startsWith('{') && trimmedText.endsWith('}') && !trimmedText.includes('data: ')) {
565 //console.log("Detected JSON in event-stream wrapper, parsing as JSON fallback");
566 try {
567 const data = JSON.parse(trimmedText);
568
569 // Handle as regular JSON response
570 $('.bot-message.temporary-message').remove();
571
572 // Check for testing panel data
573 if (window.mxchatTestPanelInstance && data.testing_data) {
574 window.mxchatTestPanelInstance.handleTestingData(data.testing_data);
575 }
576
577 // Handle response content
578 if (data.text || data.html || data.message) {
579 if (data.text && typeof customMxChatFilter === 'function') {
580 data.text = customMxChatFilter(data.text, "response");
581 }
582 if (data.message && typeof customMxChatFilter === 'function') {
583 data.message = customMxChatFilter(data.message, "response");
584 }
585
586 if (data.text && data.html) {
587 replaceLastMessage("bot", data.text, data.html);
588 } else if (data.text) {
589 replaceLastMessage("bot", data.text);
590 } else if (data.html) {
591 replaceLastMessage("bot", "", data.html);
592 } else if (data.message) {
593 replaceLastMessage("bot", data.message);
594 }
595 }
596
597 // Handle other response properties
598 if (data.chat_mode) {
599 updateChatModeIndicator(data.chat_mode);
600 }
601
602 if (data.data && data.data.filename) {
603 showActivePdf(data.data.filename);
604 activePdfFile = data.data.filename;
605 }
606
607 if (callback) {
608 callback(data.text || data.message || '');
609 }
610
611 return; // Exit early, we handled it as JSON
612 } catch (e) {
613 //console.log("Failed to parse as JSON, continuing with streaming logic");
614 }
615 }
616
617 // If we get here, it's actual streaming data, process it normally
618 const lines = fullText.split('\n');
619 let buffer = '';
620
621 for (const line of lines) {
622 if (line.startsWith('data: ')) {
623 const data = line.substring(6);
624
625 if (data === '[DONE]') {
626 //console.log("Received [DONE] signal");
627 if (callback) {
628 callback(accumulatedContent);
629 }
630 return;
631 }
632
633 try {
634 const json = JSON.parse(data);
635
636 // Handle testing data
637 if (json.testing_data && !testingDataReceived) {
638 //console.log('Testing data received in stream:', json.testing_data);
639 if (window.mxchatTestPanelInstance) {
640 window.mxchatTestPanelInstance.handleTestingData(json.testing_data);
641 testingDataReceived = true;
642 }
643 }
644 // Handle content streaming
645 else if (json.content) {
646 accumulatedContent += json.content;
647 updateStreamingMessage(accumulatedContent);
648 }
649 // Handle errors
650 else if (json.error) {
651 //console.error("Streaming error:", json.error);
652 replaceLastMessage("bot", "Error: " + json.error);
653 return;
654 }
655 } catch (e) {
656 //console.error('Error parsing SSE data:', e, 'Data:', data);
657 }
658 }
659 }
660
661 if (callback) {
662 callback(accumulatedContent);
663 }
664 });
665 }
666
667 // If we get here, something unexpected happened, fall back to regular processing
668 //console.log("Unexpected content type, falling back to text processing");
669 return response.text().then(text => {
670 //console.log("Raw response text:", text);
671 // Try to extract any meaningful content and display it
672 replaceLastMessage("bot", "Received response but couldn't process streaming format. Please try again.");
673 });
674 })
675 .catch(error => {
676 //console.error('Streaming error:', error);
677 // Fall back to regular chat on any error
678 callMxChat(message, callback);
679 });
680 }
681
682
683 // Function to update message during streaming
684 function updateStreamingMessage(content) {
685 // ADD RESPONSE HOOK FOR REAL-TIME STREAMING
686 if (typeof customMxChatFilter === 'function') {
687 content = customMxChatFilter(content, "response");
688 }
689
690 const formattedContent = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(content))));
691
692 // Find the temporary message
693 const tempMessage = $('.bot-message.temporary-message').last();
694
695 if (tempMessage.length) {
696 // Update existing message
697 tempMessage.html(formattedContent);
698 } else {
699 // Create new temporary message if it doesn't exist
700 appendMessage("bot", content, '', [], true);
701 }
702 }
703
704 // UPGRADE: Function to check if streaming is supported for the current model
705 function isStreamingSupported(model) {
706 if (!model) return false;
707
708 //console.log("Checking streaming support for model:", model); // Debug log
709
710 // Get the model prefix
711 const modelPrefix = model.split('-')[0].toLowerCase();
712
713 //console.log("Model prefix:", modelPrefix); // Debug log
714
715 // Support streaming for OpenAI, Claude, and Grok models
716 const isSupported = modelPrefix === 'gpt' || modelPrefix === 'o1' || modelPrefix === 'claude' || modelPrefix === 'grok';
717
718 //console.log("Streaming supported:", isSupported); // Debug log
719
720 return isSupported;
721 }
722
723 // Update the event handlers to use the correct function names
724 $('#send-button').off('click').on('click', function() {
725 sendMessage(); // Use the updated sendMessage function
726 });
727
728 // Override enter key handler
729 $('#chat-input').off('keypress').on('keypress', function(e) {
730 if (e.which == 13 && !e.shiftKey) {
731 e.preventDefault();
732 sendMessage(); // Use the updated sendMessage function
733 }
734 });
735
736
737 function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
738 try {
739 // Determine styles based on sender type
740 let messageClass, bgColor, fontColor;
741
742 if (sender === "user") {
743 messageClass = "user-message";
744 bgColor = userMessageBgColor;
745 fontColor = userMessageFontColor;
746 // Only sanitize user input
747 messageText = sanitizeUserInput(messageText);
748 } else if (sender === "agent") {
749 messageClass = "agent-message";
750 bgColor = liveAgentMessageBgColor;
751 fontColor = liveAgentMessageFontColor;
752 } else {
753 messageClass = "bot-message";
754 bgColor = botMessageBgColor;
755 fontColor = botMessageFontColor;
756 }
757
758 const messageDiv = $('<div>')
759 .addClass(messageClass)
760 .attr('dir', 'auto') // Add dir="auto" for automatic text direction
761 .css({
762 'background': bgColor,
763 'color': fontColor,
764 'margin-bottom': '1em'
765 });
766
767 // Process the message content based on sender
768 let fullMessage;
769 if (sender === "user") {
770 // For user messages, apply linkify after sanitization
771 fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
772 } else {
773 // For bot/agent messages, preserve HTML
774 fullMessage = messageText;
775 }
776
777 // Add images if provided
778 if (images && images.length > 0) {
779 fullMessage += '<div class="image-gallery" dir="auto">'; // Add dir="auto" to image gallery
780 images.forEach(img => {
781 // Ensure image URLs and titles are properly escaped
782 const safeTitle = sanitizeUserInput(img.title);
783 const safeUrl = encodeURI(img.image_url);
784 const safeThumbnail = encodeURI(img.thumbnail_url);
785
786 fullMessage += `
787 <div style="margin-bottom: 10px;">
788 <strong>${safeTitle}</strong><br>
789 <a href="${safeUrl}" target="_blank">
790 <img src="${safeThumbnail}" alt="${safeTitle}" style="max-width: 100px; height: auto; margin: 5px;" />
791 </a>
792 </div>`;
793 });
794 fullMessage += '</div>';
795 }
796
797 // Append HTML content if provided
798 if (messageHtml && sender !== "user") {
799 fullMessage += '<br><br>' + messageHtml;
800 }
801
802 messageDiv.html(fullMessage);
803
804 if (isTemporary) {
805 messageDiv.addClass('temporary-message');
806 }
807
808 messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() {
809 if (sender === "bot") {
810 const lastUserMessage = $('#chat-box').find('.user-message').last();
811 if (lastUserMessage.length) {
812 scrollElementToTop(lastUserMessage);
813 }
814 }
815 });
816
817 if (messageText.id) {
818 lastSeenMessageId = messageText.id;
819 hideNotification();
820 }
821 } catch (error) {
822 //console.error("Error rendering message:", error);
823 }
824 }
825
826 function replaceLastMessage(sender, responseText, responseHtml = '', images = []) {
827 var messageClass = sender === "user" ? "user-message" : sender === "agent" ? "agent-message" : "bot-message";
828 var lastMessageDiv = $('#chat-box').find('.bot-message.temporary-message, .agent-message.temporary-message').last();
829
830 // Determine styles
831 let bgColor, fontColor;
832 if (sender === "user") {
833 bgColor = userMessageBgColor;
834 fontColor = userMessageFontColor;
835 } else if (sender === "agent") {
836 bgColor = liveAgentMessageBgColor;
837 fontColor = liveAgentMessageFontColor;
838 } else {
839 bgColor = botMessageBgColor;
840 fontColor = botMessageFontColor;
841 }
842
843 var fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(responseText))));
844 if (responseHtml) {
845 fullMessage += '<br><br>' + responseHtml;
846 }
847
848 if (images.length > 0) {
849 fullMessage += '<div class="image-gallery" dir="auto">'; // Add dir="auto" to image gallery
850 images.forEach(img => {
851 fullMessage += `
852 <div style="margin-bottom: 10px;">
853 <strong>${img.title}</strong><br>
854 <a href="${img.image_url}" target="_blank">
855 <img src="${img.thumbnail_url}" alt="${img.title}" style="max-width: 100px; height: auto; margin: 5px;" />
856 </a>
857 </div>`;
858 });
859 fullMessage += '</div>';
860 }
861
862 if (lastMessageDiv.length) {
863 lastMessageDiv.fadeOut(200, function() {
864 $(this)
865 .html(fullMessage)
866 .removeClass('bot-message user-message')
867 .addClass(messageClass)
868 .attr('dir', 'auto') // Add dir="auto" for automatic text direction
869 .css({
870 'background-color': bgColor,
871 'color': fontColor,
872 })
873 .removeClass('temporary-message')
874 .fadeIn(200, function() {
875 if (sender === "bot" || sender === "agent") {
876 const lastUserMessage = $('#chat-box').find('.user-message').last();
877 if (lastUserMessage.length) {
878 scrollElementToTop(lastUserMessage);
879 }
880 // Show notification if chat is hidden
881 if ($('#floating-chatbot').hasClass('hidden')) {
882 showNotification();
883 }
884 }
885 });
886 });
887 } else {
888 appendMessage(sender, responseText, responseHtml, images);
889 }
890 }
891
892 function appendThinkingMessage() {
893 // Remove any existing thinking dots first
894 $('.thinking-dots').remove();
895
896 // Retrieve the bot message font color and background color
897 var botMessageFontColor = mxchatChat.bot_message_font_color;
898 var botMessageBgColor = mxchatChat.bot_message_bg_color;
899
900
901 var thinkingHtml = '<div class="thinking-dots-container">' +
902 '<div class="thinking-dots">' +
903 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
904 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
905 '<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' +
906 '</div>' +
907 '</div>';
908
909 // Append the thinking dots to the chat container (or within the temporary message div)
910 $("#chat-box").append('<div class="bot-message temporary-message" style="background-color: ' + botMessageBgColor + ';">' + thinkingHtml + '</div>');
911 scrollToBottom();
912 }
913
914 function removeThinkingDots() {
915 $('.thinking-dots').closest('.temporary-message').remove();
916 }
917
918
919 // ====================================
920 // TEXT FORMATTING & PROCESSING
921 // ====================================
922
923
924 function linkify(inputText) {
925 if (!inputText) return '';
926
927 // Process markdown headers
928 let processedText = formatMarkdownHeaders(inputText);
929
930 // Process markdown links
931 const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
932 processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
933 const safeUrl = encodeURI(url);
934 const safeText = sanitizeUserInput(text);
935 return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
936 });
937
938 // Process phone numbers (tel:)
939 const phonePattern = /\[([^\]]+)\]\((tel:[\d+]+)\)/g;
940 processedText = processedText.replace(phonePattern, (match, text, phone) => {
941 const safePhone = encodeURI(phone);
942 const safeText = sanitizeUserInput(text);
943 return `<a href="${safePhone}">${safeText}</a>`;
944 });
945
946 // Process standalone URLs
947 const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
948 processedText = processedText.replace(urlPattern, (match, prefix, url) => {
949 const safeUrl = encodeURI(url);
950 return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
951 });
952
953 // Process www. URLs
954 const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
955 processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
956 const safeUrl = encodeURI(`http://${url}`);
957 return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
958 });
959
960 // Add this after your phone pattern
961 const mailtoPattern = /\[([^\]]+)\]\((mailto:[^\)]+)\)/g;
962 processedText = processedText.replace(mailtoPattern, (match, text, mailto) => {
963 const safeMailto = encodeURI(mailto);
964 const safeText = sanitizeUserInput(text);
965 return `<a href="${safeMailto}">${safeText}</a>`;
966 });
967
968 return processedText;
969 }
970
971 function formatMarkdownHeaders(text) {
972 // Handle h1 to h6 headers
973 return text.replace(/^(#{1,6})\s(.+)$/gm, function(match, hashes, content) {
974 const level = hashes.length;
975 return `<h${level} class="chat-heading">${content}</h${level}>`;
976 });
977 }
978
979 function formatBoldText(text) {
980 return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
981 }
982
983 function convertNewlinesToBreaks(text) {
984 // Split the text into paragraphs (marked by double newlines or multiple <br> tags)
985 const paragraphs = text.split(/(?:\n\n|\<br\>\s*\<br\>)/g);
986
987 // Wrap each paragraph in <p> tags
988 return paragraphs
989 .map(para => `<p>${para.trim()}</p>`)
990 .join('');
991 }
992
993 function formatCodeBlocks(text) {
994 // First handle raw PHP tags
995 text = text.replace(/(<\?php[\s\S]*?\?>)/g, (match) => {
996 return `<pre><code class="language-php">${escapeHtml(match)}</code></pre>`;
997 });
998
999 // Then handle code blocks with backticks
1000 text = text.replace(/```php5?\n([\s\S]+?)```/gi, (match, code) => {
1001 return `<pre><code class="language-php">${escapeHtml(code)}</code></pre>`;
1002 });
1003
1004 return text;
1005 }
1006
1007 function sanitizeUserInput(text) {
1008 const div = document.createElement('div');
1009 div.textContent = text;
1010 return div.innerHTML;
1011 }
1012
1013
1014 function escapeHtml(unsafe) {
1015 // First check if it's already a code block
1016 if (unsafe.includes('<pre><code') || unsafe.includes('</code></pre>')) {
1017 return unsafe;
1018 }
1019
1020 return unsafe
1021 .replace(/&/g, "&amp;")
1022 .replace(/</g, "&lt;")
1023 .replace(/>/g, "&gt;")
1024 .replace(/"/g, "&quot;")
1025 .replace(/'/g, "&#039;");
1026 }
1027
1028 function decodeHTMLEntities(text) {
1029 var textArea = document.createElement('textarea');
1030 textArea.innerHTML = text;
1031 return textArea.value;
1032 }
1033
1034
1035 // ====================================
1036 // UI & SCROLLING CONTROLS
1037 // ====================================
1038
1039 function scrollToBottom(instant = false) {
1040 var chatBox = $('#chat-box');
1041 if (instant) {
1042 // Instantly set the scroll position to the bottom
1043 chatBox.scrollTop(chatBox.prop("scrollHeight"));
1044 } else {
1045 // Use requestAnimationFrame for smoother scrolling if needed
1046 let start = null;
1047 const scrollHeight = chatBox.prop("scrollHeight");
1048 const initialScroll = chatBox.scrollTop();
1049 const distance = scrollHeight - initialScroll;
1050 const duration = 500; // Duration in ms
1051
1052 function smoothScroll(timestamp) {
1053 if (!start) start = timestamp;
1054 const progress = timestamp - start;
1055 const currentScroll = initialScroll + (distance * (progress / duration));
1056 chatBox.scrollTop(currentScroll);
1057
1058 if (progress < duration) {
1059 requestAnimationFrame(smoothScroll);
1060 } else {
1061 chatBox.scrollTop(scrollHeight); // Ensure it's exactly at the bottom
1062 }
1063 }
1064
1065 requestAnimationFrame(smoothScroll);
1066 }
1067 }
1068
1069 function scrollElementToTop(element) {
1070 var chatBox = $('#chat-box');
1071 var elementTop = element.position().top + chatBox.scrollTop();
1072 chatBox.animate({ scrollTop: elementTop }, 500);
1073 }
1074
1075 function showChatWidget() {
1076 // First ensure display is set
1077 $('#floating-chatbot-button').css('display', 'flex');
1078 // Then handle the fade
1079 $('#floating-chatbot-button').fadeTo(500, 1);
1080 // Force visibility
1081 $('#floating-chatbot-button').removeClass('hidden');
1082 //console.log('Showing widget');
1083 }
1084
1085 function hideChatWidget() {
1086 $('#floating-chatbot-button').css('display', 'none');
1087 $('#floating-chatbot-button').addClass('hidden');
1088 //console.log('Hiding widget');
1089 }
1090
1091 function disableScroll() {
1092 if (isMobile()) {
1093 $('body').css('overflow', 'hidden');
1094 }
1095 }
1096
1097 function enableScroll() {
1098 if (isMobile()) {
1099 $('body').css('overflow', '');
1100 }
1101 }
1102
1103 function isMobile() {
1104 // This can be a simple check, or more sophisticated detection of mobile devices
1105 return window.innerWidth <= 768; // Example threshold for mobile devices
1106 }
1107
1108 function setFullHeight() {
1109 var vh = $(window).innerHeight() * 0.01;
1110 $(':root').css('--vh', vh + 'px');
1111 }
1112
1113
1114 // ====================================
1115 // NOTIFICATION SYSTEM
1116 // ====================================
1117
1118 function createNotificationBadge() {
1119 //console.log("Creating notification badge...");
1120 const chatButton = document.getElementById('floating-chatbot-button');
1121 //console.log("Chat button found:", !!chatButton);
1122
1123 if (!chatButton) return;
1124
1125 // Remove any existing badge first
1126 const existingBadge = chatButton.querySelector('.chat-notification-badge');
1127 if (existingBadge) {
1128 //console.log("Removing existing badge");
1129 existingBadge.remove();
1130 }
1131
1132 notificationBadge = document.createElement('div');
1133 notificationBadge.className = 'chat-notification-badge';
1134 notificationBadge.style.cssText = `
1135 display: none;
1136 position: absolute;
1137 top: -5px;
1138 right: -5px;
1139 background-color: red;
1140 color: white;
1141 border-radius: 50%;
1142 padding: 4px 8px;
1143 font-size: 12px;
1144 font-weight: bold;
1145 z-index: 10001;
1146 `;
1147 chatButton.style.position = 'relative';
1148 chatButton.appendChild(notificationBadge);
1149
1150 }
1151
1152 function showNotification() {
1153 const badge = document.getElementById('chat-notification-badge');
1154 if (badge && $('#floating-chatbot').hasClass('hidden')) {
1155 badge.style.display = 'block';
1156 badge.textContent = '1';
1157 }
1158 }
1159
1160 function hideNotification() {
1161 const badge = document.getElementById('chat-notification-badge');
1162 if (badge) {
1163 badge.style.display = 'none';
1164 }
1165 }
1166
1167 function startNotificationChecking() {
1168 const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
1169 if (!chatPersistenceEnabled) return;
1170
1171 createNotificationBadge();
1172 notificationCheckInterval = setInterval(checkForNewMessages, 30000); // Check every 30 seconds
1173 }
1174
1175 function stopNotificationChecking() {
1176 if (notificationCheckInterval) {
1177 clearInterval(notificationCheckInterval);
1178 }
1179 }
1180
1181 function checkForNewMessages() {
1182 const sessionId = getChatSession();
1183 const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
1184
1185 if (!chatPersistenceEnabled) return;
1186
1187 $.ajax({
1188 url: mxchatChat.ajax_url,
1189 type: 'POST',
1190 data: {
1191 action: 'mxchat_check_new_messages',
1192 session_id: sessionId,
1193 last_seen_id: lastSeenMessageId,
1194 nonce: mxchatChat.nonce
1195 },
1196 success: function(response) {
1197 if (response.success && response.data.hasNewMessages) {
1198 showNotification();
1199 }
1200 }
1201 });
1202 }
1203
1204
1205 // ====================================
1206 // LIVE AGENT FUNCTIONALITY
1207 // ====================================
1208
1209 function updateChatModeIndicator(mode) {
1210 const indicator = document.getElementById('chat-mode-indicator');
1211 if (indicator) {
1212 // For Live Agent, keep as is; for AI mode, use the customized text
1213 if (mode === 'agent') {
1214 indicator.textContent = 'Live Agent';
1215 } else {
1216 // Get the custom AI agent text from a data attribute we'll add to the element
1217 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
1218 indicator.textContent = customAiText;
1219 }
1220 }
1221 // Start or stop polling based on mode
1222 if (mode === 'agent') {
1223 startPolling();
1224 } else {
1225 stopPolling();
1226 }
1227 }
1228
1229 function startPolling() {
1230 // Clear any existing interval first
1231 stopPolling();
1232 // Start new polling interval
1233 pollingInterval = setInterval(checkForAgentMessages, 5000);
1234 //console.log("Started agent message polling");
1235 }
1236
1237 function stopPolling() {
1238 if (pollingInterval) {
1239 clearInterval(pollingInterval);
1240 pollingInterval = null;
1241 //console.log("Stopped agent message polling");
1242 }
1243 }
1244
1245 function checkForAgentMessages() {
1246 const sessionId = getChatSession();
1247 $.ajax({
1248 url: mxchatChat.ajax_url,
1249 type: 'POST',
1250 dataType: 'json',
1251 data: {
1252 action: 'mxchat_fetch_new_messages',
1253 session_id: sessionId,
1254 last_seen_id: lastSeenMessageId,
1255 persistence_enabled: 'true', // Add this too
1256 nonce: mxchatChat.nonce
1257 },
1258 success: function (response) {
1259 if (response.success && response.data?.new_messages) {
1260 let hasNewMessage = false;
1261
1262 response.data.new_messages.forEach(function (message) {
1263 if (message.role === "agent" && !processedMessageIds.has(message.id)) {
1264 hasNewMessage = true;
1265 // CHANGE THIS LINE:
1266 appendMessage("agent", message.content); // Instead of replaceLastMessage
1267 lastSeenMessageId = message.id;
1268 processedMessageIds.add(message.id);
1269 }
1270 });
1271
1272 if (hasNewMessage && $('#floating-chatbot').hasClass('hidden')) {
1273 showNotification();
1274 }
1275
1276 scrollToBottom(true);
1277 }
1278 },
1279 error: function (xhr, status, error) {
1280 //console.error("Polling error:", xhr, status, error);
1281 }
1282 });
1283 }
1284
1285 // ====================================
1286 // CHAT HISTORY & PERSISTENCE
1287 // ====================================
1288
1289 function loadChatHistory() {
1290 var sessionId = getChatSession();
1291 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
1292
1293 if (chatPersistenceEnabled && sessionId) {
1294 $.ajax({
1295 url: mxchatChat.ajax_url,
1296 type: 'POST',
1297 dataType: 'json',
1298 data: {
1299 action: 'mxchat_fetch_conversation_history',
1300 session_id: sessionId
1301 },
1302 success: function(response) {
1303 if (response.success && response.data && Array.isArray(response.data.conversation)) {
1304
1305
1306 var $chatBox = $('#chat-box');
1307 var $fragment = $(document.createDocumentFragment());
1308 let highestMessageId = lastSeenMessageId;
1309
1310 if (response.data.chat_mode) {
1311 updateChatModeIndicator(response.data.chat_mode);
1312 }
1313
1314 $.each(response.data.conversation, function(index, message) {
1315 // Skip agent messages if persistence is off
1316 if (!chatPersistenceEnabled && message.role === 'agent') {
1317 return;
1318 }
1319
1320 var messageClass, messageBgColor, messageFontColor;
1321
1322 switch (message.role) {
1323 case 'user':
1324 messageClass = 'user-message';
1325 messageBgColor = userMessageBgColor;
1326 messageFontColor = userMessageFontColor;
1327 break;
1328 case 'agent':
1329 messageClass = 'agent-message';
1330 messageBgColor = liveAgentMessageBgColor;
1331 messageFontColor = liveAgentMessageFontColor;
1332 break;
1333 default:
1334 messageClass = 'bot-message';
1335 messageBgColor = botMessageBgColor;
1336 messageFontColor = botMessageFontColor;
1337 break;
1338 }
1339
1340 var messageElement = $('<div>').addClass(messageClass)
1341 .css({
1342 'background': messageBgColor,
1343 'color': messageFontColor
1344 });
1345
1346 var content = message.content;
1347 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
1348 content = decodeHTMLEntities(content);
1349
1350 if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
1351 messageElement.html(content);
1352 } else {
1353 var formattedContent = linkify(
1354 formatBoldText(
1355 convertNewlinesToBreaks(formatCodeBlocks(content))
1356 )
1357 );
1358 messageElement.html(formattedContent);
1359 }
1360
1361 $fragment.append(messageElement);
1362
1363 // In loadChatHistory, change this part:
1364 if (message.id) {
1365 highestMessageId = Math.max(highestMessageId, message.id);
1366 processedMessageIds.add(message.id); // Add all message IDs to processed set
1367 }
1368 });
1369
1370 $chatBox.append($fragment);
1371 scrollToBottom(true);
1372
1373 if (response.data.conversation.length > 0 && hasQuickQuestions()) {
1374 collapseQuickQuestions();
1375 }
1376
1377 // Update lastSeenMessageId after history loads
1378 lastSeenMessageId = highestMessageId;
1379
1380 // Only update chat mode if persistence is enabled
1381 if (chatPersistenceEnabled && response.data.conversation.length > 0) {
1382 var lastMessage = response.data.conversation[response.data.conversation.length - 1];
1383 if (lastMessage.role === 'agent') {
1384 updateChatModeIndicator('agent');
1385 }
1386 }
1387 } else {
1388 console.warn("No conversation history found.");
1389 }
1390 },
1391 error: function(xhr, status, error) {
1392 //console.error("Error loading chat history:", status, error);
1393 appendMessage("bot", "Unable to load chat history.");
1394 }
1395 });
1396 } else {
1397 console.warn("Chat persistence is disabled or no session ID found. Not loading history.");
1398 }
1399 }
1400
1401
1402 // ====================================
1403 // FILE UPLOAD FUNCTIONALITY
1404 // ====================================
1405
1406 function addSafeEventListener(elementId, eventType, handler) {
1407 const element = document.getElementById(elementId);
1408 if (element) {
1409 element.addEventListener(eventType, handler);
1410 }
1411 }
1412
1413 function showActivePdf(filename) {
1414 const container = document.getElementById('active-pdf-container');
1415 const nameElement = document.getElementById('active-pdf-name');
1416
1417 if (!container || !nameElement) {
1418 //console.error('PDF container elements not found');
1419 return;
1420 }
1421
1422 nameElement.textContent = filename;
1423 container.style.display = 'flex';
1424 }
1425
1426 function showActiveWord(filename) {
1427 const container = document.getElementById('active-word-container');
1428 const nameElement = document.getElementById('active-word-name');
1429
1430 if (!container || !nameElement) {
1431 //console.error('Word document container elements not found');
1432 return;
1433 }
1434
1435 nameElement.textContent = filename;
1436 container.style.display = 'flex';
1437 }
1438
1439 function removeActivePdf() {
1440 const container = document.getElementById('active-pdf-container');
1441 const nameElement = document.getElementById('active-pdf-name');
1442
1443 if (!container || !nameElement || !activePdfFile) return;
1444
1445 fetch(mxchatChat.ajax_url, {
1446 method: 'POST',
1447 headers: {
1448 'Content-Type': 'application/x-www-form-urlencoded',
1449 },
1450 body: new URLSearchParams({
1451 'action': 'mxchat_remove_pdf',
1452 'session_id': sessionId,
1453 'nonce': mxchatChat.nonce
1454 })
1455 })
1456 .then(response => response.json())
1457 .then(data => {
1458 if (data.success) {
1459 container.style.display = 'none';
1460 nameElement.textContent = '';
1461 activePdfFile = null;
1462 appendMessage('bot', 'PDF removed.');
1463 }
1464 })
1465 .catch(error => {
1466 //console.error('Error removing PDF:', error);
1467 });
1468 }
1469
1470 function removeActiveWord() {
1471 const container = document.getElementById('active-word-container');
1472 const nameElement = document.getElementById('active-word-name');
1473
1474 if (!container || !nameElement || !activeWordFile) return;
1475
1476 fetch(mxchatChat.ajax_url, {
1477 method: 'POST',
1478 headers: {
1479 'Content-Type': 'application/x-www-form-urlencoded',
1480 },
1481 body: new URLSearchParams({
1482 'action': 'mxchat_remove_word',
1483 'session_id': sessionId,
1484 'nonce': mxchatChat.nonce
1485 })
1486 })
1487 .then(response => response.json())
1488 .then(data => {
1489 if (data.success) {
1490 container.style.display = 'none';
1491 nameElement.textContent = '';
1492 activeWordFile = null;
1493 appendMessage('bot', 'Word document removed.');
1494 }
1495 })
1496 .catch(error => {
1497 //console.error('Error removing Word document:', error);
1498 });
1499 }
1500
1501 // ====================================
1502 // CONSENT & COMPLIANCE (GDPR)
1503 // ====================================
1504
1505 function initializeChatVisibility() {
1506 //console.log('Initializing chat visibility');
1507 const complianzEnabled = mxchatChat.complianz_toggle === 'on' ||
1508 mxchatChat.complianz_toggle === '1' ||
1509 mxchatChat.complianz_toggle === 1;
1510
1511 if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') {
1512 // Initial check
1513 checkConsentAndShowChat();
1514
1515 // Listen for consent changes
1516 $(document).on('cmplz_status_change', function(event) {
1517 //console.log('Status change detected');
1518 checkConsentAndShowChat();
1519 });
1520 } else {
1521 // If Complianz is not enabled, always show
1522 $('#floating-chatbot-button')
1523 .css('display', 'flex')
1524 .removeClass('hidden no-consent')
1525 .fadeTo(500, 1);
1526
1527 // Also check pre-chat message when Complianz is not enabled
1528 checkPreChatDismissal();
1529 }
1530 }
1531
1532
1533 function checkConsentAndShowChat() {
1534 var consentStatus = cmplz_has_consent('marketing');
1535 var consentType = complianz.consenttype;
1536
1537 //console.log('Checking consent:', {status: consentStatus,type: consentType});
1538
1539 let $widget = $('#floating-chatbot-button');
1540 let $chatbot = $('#floating-chatbot');
1541 let $preChat = $('#pre-chat-message');
1542
1543 if (consentStatus === true) {
1544 //console.log('Consent granted - showing widget');
1545 $widget
1546 .removeClass('no-consent')
1547 .css('display', 'flex')
1548 .removeClass('hidden')
1549 .fadeTo(500, 1);
1550 $chatbot.removeClass('no-consent');
1551
1552 // Show pre-chat message if not dismissed
1553 checkPreChatDismissal();
1554 } else {
1555 //console.log('No consent - hiding widget');
1556 $widget
1557 .addClass('no-consent')
1558 .fadeTo(500, 0, function() {
1559 $(this)
1560 .css('display', 'none')
1561 .addClass('hidden');
1562 });
1563 $chatbot.addClass('no-consent');
1564
1565 // Hide pre-chat message when no consent
1566 $preChat.hide();
1567 }
1568 }
1569
1570
1571 // ====================================
1572 // PRE-CHAT MESSAGE HANDLING
1573 // ====================================
1574
1575 function checkPreChatDismissal() {
1576 $.ajax({
1577 url: mxchatChat.ajax_url,
1578 type: 'POST',
1579 data: {
1580 action: 'mxchat_check_pre_chat_message_status',
1581 _ajax_nonce: mxchatChat.nonce
1582 },
1583 success: function(response) {
1584 if (response.success && !response.data.dismissed) {
1585 $('#pre-chat-message').fadeIn(250);
1586 } else {
1587 $('#pre-chat-message').hide();
1588 }
1589 },
1590 error: function() {
1591 //console.error('Failed to check pre-chat message dismissal status.');
1592 }
1593 });
1594 }
1595
1596 function handlePreChatDismissal() {
1597 $('#pre-chat-message').fadeOut(200);
1598 $.ajax({
1599 url: mxchatChat.ajax_url,
1600 type: 'POST',
1601 data: {
1602 action: 'mxchat_dismiss_pre_chat_message',
1603 _ajax_nonce: mxchatChat.nonce
1604 },
1605 success: function() {
1606 $('#pre-chat-message').hide();
1607 },
1608 error: function() {
1609 //console.error('Failed to dismiss pre-chat message.');
1610 }
1611 });
1612 }
1613
1614
1615 // ====================================
1616 // UTILITY FUNCTIONS
1617 // ====================================
1618
1619 function copyToClipboard(text) {
1620 var tempInput = $('<input>');
1621 $('body').append(tempInput);
1622 tempInput.val(text).select();
1623 document.execCommand('copy');
1624 tempInput.remove();
1625 }
1626
1627
1628 function isImageHtml(str) {
1629 return str.startsWith('<img') && str.endsWith('>');
1630 }
1631
1632
1633 // ====================================
1634 // EVENT HANDLERS & INITIALIZATION
1635 // ====================================
1636
1637 $(document).on('click', '.mxchat-popular-question', function () {
1638 var question = $(this).text();
1639
1640 // Append the question as if the user typed it
1641 appendMessage("user", question);
1642
1643 // Only collapse if there are questions
1644 if (hasQuickQuestions()) {
1645 collapseQuickQuestions();
1646 }
1647
1648 // Send the question to the server
1649 sendMessageToChatbot(question);
1650 });
1651
1652 $(document).on('click', '.questions-toggle-btn', function(e) {
1653 e.preventDefault();
1654 e.stopPropagation();
1655 expandQuickQuestions();
1656 });
1657
1658 $(document).on('click', '.questions-collapse-btn', function(e) {
1659 e.preventDefault();
1660 e.stopPropagation();
1661 collapseQuickQuestions();
1662 });
1663
1664 // Chatbot visibility toggle handlers
1665 $(document).on('click', '#floating-chatbot-button', function() {
1666 var chatbot = $('#floating-chatbot');
1667 if (chatbot.hasClass('hidden')) {
1668 chatbot.removeClass('hidden').addClass('visible');
1669 $(this).addClass('hidden');
1670 $('#chat-notification-badge').hide(); // Hide notification when opening chat
1671 disableScroll();
1672 $('#pre-chat-message').fadeOut(250);
1673 } else {
1674 chatbot.removeClass('visible').addClass('hidden');
1675 $(this).removeClass('hidden');
1676 enableScroll();
1677 checkPreChatDismissal();
1678 }
1679 });
1680
1681 $(document).on('click', '#exit-chat-button', function() {
1682 $('#floating-chatbot').addClass('hidden').removeClass('visible');
1683 $('#floating-chatbot-button').removeClass('hidden');
1684 enableScroll();
1685 });
1686
1687 $(document).on('click', '.close-pre-chat-message', function(e) {
1688 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
1689 $('#pre-chat-message').fadeOut(200, function() {
1690 $(this).remove();
1691 });
1692 });
1693
1694 // Add to Cart button handler
1695 $(document).on('click', '.mxchat-add-to-cart-button', function() {
1696 var productId = $(this).data('product-id');
1697
1698 // Get the button text instead of hardcoded "add to cart"
1699 var buttonText = $(this).text() || "add to cart";
1700
1701 // Add a special prefix to indicate this is from button
1702 appendMessage("user", buttonText);
1703 sendMessageToChatbot("!addtocart"); // Special command to indicate button click
1704 });
1705
1706 // PDF upload button handlers
1707 if (document.getElementById('pdf-upload-btn')) {
1708 document.getElementById('pdf-upload-btn').addEventListener('click', function() {
1709 document.getElementById('pdf-upload').click();
1710 });
1711 }
1712
1713 // Word upload button handlers
1714 if (document.getElementById('word-upload-btn')) {
1715 document.getElementById('word-upload-btn').addEventListener('click', function() {
1716 document.getElementById('word-upload').click();
1717 });
1718 }
1719
1720 // PDF file input change handler
1721 addSafeEventListener('pdf-upload', 'change', async function(e) {
1722 const file = e.target.files[0];
1723
1724 if (!file || file.type !== 'application/pdf') {
1725 alert('Please select a valid PDF file.');
1726 return;
1727 }
1728
1729 if (!sessionId) {
1730 //console.error('No session ID found');
1731 alert('Error: No session ID found');
1732 return;
1733 }
1734
1735 if (!mxchatChat || !mxchatChat.ajax_url || !mxchatChat.nonce) {
1736 //console.error('mxchatChat not properly configured:', mxchatChat);
1737 alert('Error: Ajax configuration missing');
1738 return;
1739 }
1740
1741 // Disable buttons and show loading state
1742 const uploadBtn = document.getElementById('pdf-upload-btn');
1743 const sendBtn = document.getElementById('send-button');
1744 const originalBtnContent = uploadBtn.innerHTML;
1745
1746 try {
1747 const formData = new FormData();
1748 formData.append('action', 'mxchat_upload_pdf');
1749 formData.append('pdf_file', file);
1750 formData.append('session_id', sessionId);
1751 formData.append('nonce', mxchatChat.nonce);
1752
1753 uploadBtn.disabled = true;
1754 sendBtn.disabled = true;
1755 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1756 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1757 </svg>`;
1758
1759 const response = await fetch(mxchatChat.ajax_url, {
1760 method: 'POST',
1761 body: formData
1762 });
1763
1764 const data = await response.json();
1765
1766 if (data.success) {
1767 // Hide popular questions if they exist
1768 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1769 if (hasQuickQuestions()) {
1770 collapseQuickQuestions();
1771 }
1772
1773 // Show the active PDF name
1774 showActivePdf(data.data.filename);
1775
1776 appendMessage('bot', data.data.message);
1777 scrollToBottom();
1778 activePdfFile = data.data.filename;
1779 } else {
1780 //console.error('Upload failed:', data.data);
1781 alert('Failed to upload PDF. Please try again.');
1782 }
1783 } catch (error) {
1784 //console.error('Upload error:', error);
1785 alert('Error uploading file. Please try again.');
1786 } finally {
1787 uploadBtn.disabled = false;
1788 sendBtn.disabled = false;
1789 uploadBtn.innerHTML = originalBtnContent;
1790 this.value = ''; // Reset file input
1791 }
1792 });
1793
1794 // Word file input change handler
1795 addSafeEventListener('word-upload', 'change', async function(e) {
1796 const file = e.target.files[0];
1797
1798 if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1799 alert('Please select a valid Word document (.docx).');
1800 return;
1801 }
1802
1803 if (!sessionId) {
1804 //console.error('No session ID found');
1805 alert('Error: No session ID found');
1806 return;
1807 }
1808
1809 // Disable buttons and show loading state
1810 const uploadBtn = document.getElementById('word-upload-btn');
1811 const sendBtn = document.getElementById('send-button');
1812 const originalBtnContent = uploadBtn.innerHTML;
1813
1814 try {
1815 const formData = new FormData();
1816 formData.append('action', 'mxchat_upload_word');
1817 formData.append('word_file', file);
1818 formData.append('session_id', sessionId);
1819 formData.append('nonce', mxchatChat.nonce);
1820
1821 uploadBtn.disabled = true;
1822 sendBtn.disabled = true;
1823 uploadBtn.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
1824 <circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
1825 </svg>`;
1826
1827 const response = await fetch(mxchatChat.ajax_url, {
1828 method: 'POST',
1829 body: formData
1830 });
1831
1832 const data = await response.json();
1833
1834 if (data.success) {
1835 // Hide popular questions if they exist
1836 const popularQuestionsContainer = document.getElementById('mxchat-popular-questions');
1837 if (hasQuickQuestions()) {
1838 collapseQuickQuestions();
1839 }
1840
1841 // Show the active Word document name
1842 showActiveWord(data.data.filename);
1843
1844 appendMessage('bot', data.data.message);
1845 scrollToBottom();
1846 activeWordFile = data.data.filename;
1847 } else {
1848 //console.error('Upload failed:', data.data);
1849 alert('Failed to upload Word document. Please try again.');
1850 }
1851 } catch (error) {
1852 //console.error('Upload error:', error);
1853 alert('Error uploading file. Please try again.');
1854 } finally {
1855 uploadBtn.disabled = false;
1856 sendBtn.disabled = false;
1857 uploadBtn.innerHTML = originalBtnContent;
1858 this.value = ''; // Reset file input
1859 }
1860 });
1861
1862 // Remove button click handlers
1863 document.getElementById('remove-pdf-btn')?.addEventListener('click', function(e) {
1864 e.preventDefault();
1865 e.stopPropagation();
1866 removeActivePdf();
1867 });
1868
1869 document.getElementById('remove-word-btn')?.addEventListener('click', function(e) {
1870 e.preventDefault();
1871 e.stopPropagation();
1872 removeActiveWord();
1873 });
1874
1875 // Window resize handlers
1876 $(window).on('resize orientationchange', function() {
1877 setFullHeight();
1878 });
1879
1880
1881 // ====================================
1882 // TOOLBAR & STYLING SETUP
1883 // ====================================
1884
1885 // Apply toolbar settings
1886 if (mxchatChat.chat_toolbar_toggle === 'on') {
1887 $('.chat-toolbar').show();
1888 } else {
1889 $('.chat-toolbar').hide();
1890 }
1891
1892 // Apply toolbar icon colors
1893 const toolbarElements = [
1894 '#mxchat-chatbot .toolbar-btn svg',
1895 '#mxchat-chatbot .active-pdf-name',
1896 '#mxchat-chatbot .active-word-name',
1897 '#mxchat-chatbot .remove-pdf-btn svg',
1898 '#mxchat-chatbot .remove-word-btn svg',
1899 '#mxchat-chatbot .toolbar-perplexity svg'
1900 ];
1901
1902 toolbarElements.forEach(selector => {
1903 $(selector).css({
1904 'fill': toolbarIconColor,
1905 'stroke': toolbarIconColor,
1906 'color': toolbarIconColor
1907 });
1908 });
1909
1910
1911 // ====================================
1912 // IMPROVED EMAIL COLLECTION SETUP
1913 // ====================================
1914
1915 // Email collection form setup and handlers
1916 const emailForm = document.getElementById('email-collection-form');
1917 const emailBlocker = document.getElementById('email-blocker');
1918 const chatbotWrapper = document.getElementById('chat-container');
1919
1920 if (emailForm && emailBlocker && chatbotWrapper) {
1921 // Add loading state management
1922 let isSubmitting = false;
1923
1924 // Check if email exists for the current session
1925 function checkSessionAndEmail() {
1926 const sessionId = getChatSession();
1927
1928 // Add timeout to prevent hanging
1929 const controller = new AbortController();
1930 const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
1931
1932 fetch(mxchatChat.ajax_url, {
1933 method: 'POST',
1934 headers: {
1935 'Content-Type': 'application/x-www-form-urlencoded',
1936 },
1937 body: new URLSearchParams({
1938 action: 'mxchat_check_email_provided',
1939 session_id: sessionId,
1940 nonce: mxchatChat.nonce,
1941 }),
1942 signal: controller.signal
1943 })
1944 .then((response) => {
1945 clearTimeout(timeoutId);
1946 if (!response.ok) {
1947 throw new Error(`HTTP error! status: ${response.status}`);
1948 }
1949 return response.json();
1950 })
1951 .then((data) => {
1952 if (data.success) {
1953 if (data.data.logged_in || data.data.email) {
1954 showChatContainer();
1955 } else {
1956 showEmailForm();
1957 }
1958 } else {
1959 // On error, default to showing email form
1960 showEmailForm();
1961 }
1962 })
1963 .catch((error) => {
1964 clearTimeout(timeoutId);
1965 console.warn('Email check failed, defaulting to email form:', error);
1966 showEmailForm();
1967 });
1968 }
1969
1970 // Optimized UI transition functions
1971 function showEmailForm() {
1972 emailBlocker.style.display = 'flex';
1973 chatbotWrapper.style.display = 'none';
1974 }
1975
1976 function showChatContainer() {
1977 // Show chat immediately without delay
1978 emailBlocker.style.display = 'none';
1979 chatbotWrapper.style.display = 'flex';
1980
1981 // Load chat history only after showing chat container
1982 if (typeof loadChatHistory === 'function') {
1983 loadChatHistory();
1984 }
1985 }
1986
1987 // Enhanced email validation
1988 function isValidEmail(email) {
1989 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1990 return emailRegex.test(email.trim()) && email.length <= 254; // RFC 5321 limit
1991 }
1992
1993 // Show loading state with spinner
1994 function setSubmissionState(loading) {
1995 const submitButton = document.getElementById('email-submit-button');
1996 const emailInput = document.getElementById('user-email');
1997
1998 if (loading) {
1999 isSubmitting = true;
2000 submitButton.disabled = true;
2001 emailInput.disabled = true;
2002
2003 // Store original content and add spinner
2004 if (!submitButton.getAttribute('data-original-html')) {
2005 submitButton.setAttribute('data-original-html', submitButton.innerHTML);
2006 }
2007
2008 // Add loading spinner while keeping original text
2009 const originalText = submitButton.textContent;
2010 submitButton.innerHTML = `
2011 <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
2012 <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
2013 <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
2014 <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
2015 </circle>
2016 </svg>
2017 ${originalText}
2018 `;
2019
2020 submitButton.style.opacity = '0.8';
2021 } else {
2022 isSubmitting = false;
2023 submitButton.disabled = false;
2024 emailInput.disabled = false;
2025
2026 // Restore original content
2027 const originalHtml = submitButton.getAttribute('data-original-html');
2028 if (originalHtml) {
2029 submitButton.innerHTML = originalHtml;
2030 }
2031
2032 submitButton.style.opacity = '1';
2033 }
2034 }
2035
2036 // Add CSS for spinner animation if not already present
2037 if (!document.getElementById('email-spinner-styles')) {
2038 const style = document.createElement('style');
2039 style.id = 'email-spinner-styles';
2040 style.textContent = `
2041 @keyframes spin {
2042 from { transform: rotate(0deg); }
2043 to { transform: rotate(360deg); }
2044 }
2045 .email-spinner {
2046 display: inline-block;
2047 vertical-align: middle;
2048 }
2049 `;
2050 document.head.appendChild(style);
2051 }
2052
2053 // Handle email form submission with improved error handling
2054 emailForm.addEventListener('submit', function (event) {
2055 event.preventDefault();
2056
2057 // Prevent double submission
2058 if (isSubmitting) {
2059 return;
2060 }
2061
2062 const userEmail = document.getElementById('user-email').value.trim();
2063 const sessionId = getChatSession();
2064
2065 // Validate email before submission
2066 if (!userEmail) {
2067 showEmailError('Please enter your email address.');
2068 return;
2069 }
2070
2071 if (!isValidEmail(userEmail)) {
2072 showEmailError('Please enter a valid email address.');
2073 return;
2074 }
2075
2076 // Clear any existing errors
2077 clearEmailError();
2078 setSubmissionState(true);
2079
2080 // Add timeout for submission
2081 const controller = new AbortController();
2082 const timeoutId = setTimeout(() => {
2083 controller.abort();
2084 setSubmissionState(false);
2085 showEmailError('Request timed out. Please try again.');
2086 }, 15000); // 15 second timeout
2087
2088 fetch(mxchatChat.ajax_url, {
2089 method: 'POST',
2090 headers: {
2091 'Content-Type': 'application/x-www-form-urlencoded',
2092 },
2093 body: new URLSearchParams({
2094 action: 'mxchat_handle_save_email_and_response',
2095 email: userEmail,
2096 session_id: sessionId,
2097 nonce: mxchatChat.nonce,
2098 }),
2099 signal: controller.signal
2100 })
2101 .then((response) => {
2102 clearTimeout(timeoutId);
2103 if (!response.ok) {
2104 throw new Error(`HTTP error! status: ${response.status}`);
2105 }
2106 return response.json();
2107 })
2108 .then((data) => {
2109 setSubmissionState(false);
2110
2111 if (data.success) {
2112 // Show chat immediately
2113 showChatContainer();
2114
2115 // Handle bot response if provided
2116 if (data.message && typeof appendMessage === 'function') {
2117 setTimeout(() => {
2118 appendMessage('bot', data.message);
2119 if (typeof scrollToBottom === 'function') {
2120 scrollToBottom();
2121 }
2122 }, 100);
2123 }
2124 } else {
2125 showEmailError(data.message || 'Failed to save email. Please try again.');
2126 }
2127 })
2128 .catch((error) => {
2129 clearTimeout(timeoutId);
2130 setSubmissionState(false);
2131
2132 if (error.name === 'AbortError') {
2133 showEmailError('Request timed out. Please try again.');
2134 } else {
2135 console.error('Email submission error:', error);
2136 showEmailError('An error occurred. Please try again.');
2137 }
2138 });
2139 });
2140
2141 // Real-time email validation
2142 const emailInput = document.getElementById('user-email');
2143 if (emailInput) {
2144 let validationTimeout;
2145
2146 emailInput.addEventListener('input', function() {
2147 // Clear previous validation timeout
2148 if (validationTimeout) {
2149 clearTimeout(validationTimeout);
2150 }
2151
2152 // Debounce validation
2153 validationTimeout = setTimeout(() => {
2154 const email = this.value.trim();
2155 clearEmailError();
2156
2157 if (email && !isValidEmail(email)) {
2158 showEmailError('Please enter a valid email address.');
2159 }
2160 }, 500);
2161 });
2162
2163 // Handle Enter key
2164 emailInput.addEventListener('keypress', function(e) {
2165 if (e.key === 'Enter' && !isSubmitting) {
2166 emailForm.dispatchEvent(new Event('submit'));
2167 }
2168 });
2169 }
2170
2171 // Error display functions
2172 function showEmailError(message) {
2173 clearEmailError();
2174
2175 const errorDiv = document.createElement('div');
2176 errorDiv.className = 'email-error';
2177 errorDiv.style.cssText = `
2178 color: #e74c3c;
2179 font-size: 12px;
2180 margin-top: 8px;
2181 padding: 4px 0;
2182 animation: fadeInError 0.3s ease;
2183 `;
2184 errorDiv.textContent = message;
2185
2186 // Add CSS animation if not already present
2187 if (!document.getElementById('email-error-styles')) {
2188 const style = document.createElement('style');
2189 style.id = 'email-error-styles';
2190 style.textContent = `
2191 @keyframes fadeInError {
2192 from { opacity: 0; transform: translateY(-5px); }
2193 to { opacity: 1; transform: translateY(0); }
2194 }
2195 .email-input-shake {
2196 animation: shake 0.5s ease-in-out;
2197 }
2198 @keyframes shake {
2199 0%, 100% { transform: translateX(0); }
2200 25% { transform: translateX(-5px); }
2201 75% { transform: translateX(5px); }
2202 }
2203 `;
2204 document.head.appendChild(style);
2205 }
2206
2207 emailForm.appendChild(errorDiv);
2208
2209 // Add shake animation to input
2210 if (emailInput) {
2211 emailInput.classList.add('email-input-shake');
2212 setTimeout(() => {
2213 emailInput.classList.remove('email-input-shake');
2214 }, 500);
2215 }
2216 }
2217
2218 function clearEmailError() {
2219 const existingErrors = emailForm.querySelectorAll('.email-error');
2220 existingErrors.forEach(error => error.remove());
2221 }
2222
2223 // Initialize email check with delay to prevent race conditions
2224 setTimeout(checkSessionAndEmail, 100);
2225
2226 } else if (mxchatChat.email_collection_enabled) {
2227 console.error('Essential elements for email handling are missing:', {
2228 emailForm: !!emailForm,
2229 emailBlocker: !!emailBlocker,
2230 chatbotWrapper: !!chatbotWrapper
2231 });
2232 }
2233
2234
2235 // Open chatbot when pre-chat message is clicked
2236 $(document).on('click', '#pre-chat-message', function() {
2237 var chatbot = $('#floating-chatbot');
2238 if (chatbot.hasClass('hidden')) {
2239 chatbot.removeClass('hidden').addClass('visible');
2240 $('#floating-chatbot-button').addClass('hidden');
2241 $('#pre-chat-message').fadeOut(250); // Hide pre-chat message
2242 disableScroll(); // Disable scroll when chatbot opens
2243 }
2244 });
2245
2246 var closeButton = document.querySelector('.close-pre-chat-message');
2247 if (closeButton) {
2248 closeButton.addEventListener('click', function() {
2249 $('#pre-chat-message').fadeOut(200); // Hide the message
2250
2251 // Send an AJAX request to set the transient flag for 24 hours
2252 $.ajax({
2253 url: mxchatChat.ajax_url,
2254 type: 'POST',
2255 data: {
2256 action: 'mxchat_dismiss_pre_chat_message',
2257 _ajax_nonce: mxchatChat.nonce
2258 },
2259 success: function() {
2260 //console.log('Pre-chat message dismissed for 24 hours.');
2261
2262 // Ensure the message is hidden after dismissal
2263 $('#pre-chat-message').hide();
2264 },
2265 error: function() {
2266 ////console.error('Failed to dismiss pre-chat message.');
2267 }
2268 });
2269 });
2270 }
2271
2272
2273 function hasQuickQuestions() {
2274 const questionButtons = document.querySelectorAll('#mxchat-popular-questions .mxchat-popular-question');
2275 return questionButtons.length > 0;
2276 }
2277
2278 function collapseQuickQuestions() {
2279 const questionsContainer = document.getElementById('mxchat-popular-questions');
2280 if (questionsContainer && hasQuickQuestions()) {
2281 questionsContainer.classList.add('collapsed');
2282 questionsContainer.classList.add('has-been-collapsed');
2283 try {
2284 sessionStorage.setItem('mxchat_questions_collapsed', 'true');
2285 sessionStorage.setItem('mxchat_questions_has_been_collapsed', 'true');
2286 } catch (e) {
2287 // Ignore if sessionStorage is not available
2288 }
2289 }
2290 }
2291
2292 function expandQuickQuestions() {
2293 const questionsContainer = document.getElementById('mxchat-popular-questions');
2294 if (questionsContainer && hasQuickQuestions()) {
2295 questionsContainer.classList.remove('collapsed');
2296 try {
2297 sessionStorage.setItem('mxchat_questions_collapsed', 'false');
2298 } catch (e) {
2299 // Ignore if sessionStorage is not available
2300 }
2301 }
2302 }
2303
2304 function checkQuickQuestionsState() {
2305 if (!hasQuickQuestions()) {
2306 return; // Don't do anything if no questions exist
2307 }
2308
2309 try {
2310 const isCollapsed = sessionStorage.getItem('mxchat_questions_collapsed');
2311 const hasBeenCollapsed = sessionStorage.getItem('mxchat_questions_has_been_collapsed');
2312
2313 const questionsContainer = document.getElementById('mxchat-popular-questions');
2314 if (questionsContainer) {
2315 if (hasBeenCollapsed === 'true') {
2316 questionsContainer.classList.add('has-been-collapsed');
2317 }
2318 if (isCollapsed === 'true') {
2319 questionsContainer.classList.add('collapsed');
2320 }
2321 }
2322 } catch (e) {
2323 // Ignore if sessionStorage is not available
2324 }
2325 }
2326
2327
2328
2329
2330 // ====================================
2331 // MAIN INITIALIZATION
2332 // ====================================
2333
2334 if ($('#floating-chatbot').hasClass('hidden')) {
2335 $('#floating-chatbot-button').removeClass('hidden');
2336 }
2337 // Initialize when document is ready
2338 setFullHeight();
2339 initializeChatVisibility();
2340 loadChatHistory();
2341
2342 // Make functions globally available for add-ons
2343 window.hasQuickQuestions = hasQuickQuestions;
2344 window.collapseQuickQuestions = collapseQuickQuestions;
2345 window.appendMessage = appendMessage;
2346 window.appendThinkingMessage = appendThinkingMessage;
2347 window.scrollToBottom = scrollToBottom;
2348 window.scrollElementToTop = scrollElementToTop;
2349 window.replaceLastMessage = replaceLastMessage;
2350 window.callMxChat = callMxChat;
2351 window.callMxChatStream = callMxChatStream;
2352 window.shouldUseStreaming = shouldUseStreaming;
2353 window.getChatSession = getChatSession;
2354 window.getPageContext = getPageContext;
2355 window.updateStreamingMessage = updateStreamingMessage;
2356
2357 }); // End of jQuery ready
2358
2359
2360 // ====================================
2361 // GLOBAL EVENT LISTENERS (Outside jQuery)
2362 // ====================================
2363
2364 // Event listener for copy button (code blocks)
2365 document.addEventListener("click", (e) => {
2366 if (e.target.classList.contains("mxchat-copy-button")) {
2367 const copyButton = e.target;
2368 const codeBlock = copyButton
2369 .closest(".mxchat-code-block-container")
2370 .querySelector(".mxchat-code-block code");
2371
2372 if (codeBlock) {
2373 // Preserve formatting using innerText
2374 navigator.clipboard.writeText(codeBlock.innerText).then(() => {
2375 copyButton.textContent = "Copied!";
2376 copyButton.setAttribute("aria-label", "Copied to clipboard");
2377
2378 setTimeout(() => {
2379 copyButton.textContent = "Copy";
2380 copyButton.setAttribute("aria-label", "Copy to clipboard");
2381 }, 2000);
2382 });
2383 }
2384 }
2385 });
2386
2387
2388
2389
2390
2391
2392