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

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

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