PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 1.6.3
MxChat – AI Chatbot & Content Generation for WordPress v1.6.3
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
← All changes | js/chat-script.js +111 -258 2.0.51.6.3 View file →
@@ -1,8 +1,8 @@
1 1 jQuery(document).ready(function($) {
2 2 //console.log('mxchatChat object:', mxchatChat);
3 3 //console.log('Link Target Toggle Value:', mxchatChat.link_target_toggle);
4 -// Add these variables at the top of your chat-script.js file
4 +
5 5 const toolbarIconColor = mxchatChat.toolbar_icon_color || '#212121';
6 6
7 7 // Initialize color settings
8 8 var userMessageBgColor = mxchatChat.user_message_bg_color;
@@ -14,11 +14,9 @@
14 14 var liveAgentMessageFontColor = mxchatChat.live_agent_message_font_color;
15 15
16 16
17 17 var linkTarget = mxchatChat.link_target_toggle === 'on' ? '_blank' : '_self';
18 - let lastSeenMessageId = '';
19 - let notificationCheckInterval;
20 - let notificationBadge;
18 + var lastSeenMessageId = '';
21 19 // Initialize session ID
22 20 var sessionId = getChatSession();
23 21
24 22 let pollingInterval; // Variable to store the interval ID
@@ -28,107 +26,10 @@
28 26 let activePdfFile = null;
29 27 let activeWordFile = null;
30 28
31 29
32 -// Function to create and append notification badge
33 -// Function to create and append notification badge
34 -function createNotificationBadge() {
35 - console.log("Creating notification badge...");
36 - const chatButton = document.getElementById('floating-chatbot-button');
37 - console.log("Chat button found:", !!chatButton);
38 -
39 - if (!chatButton) return;
40 30
41 - // Remove any existing badge first
42 - const existingBadge = chatButton.querySelector('.chat-notification-badge');
43 - if (existingBadge) {
44 - console.log("Removing existing badge");
45 - existingBadge.remove();
46 - }
47 31
48 - notificationBadge = document.createElement('div');
49 - notificationBadge.className = 'chat-notification-badge';
50 - notificationBadge.style.cssText = `
51 - display: none;
52 - position: absolute;
53 - top: -5px;
54 - right: -5px;
55 - background-color: red;
56 - color: white;
57 - border-radius: 50%;
58 - padding: 4px 8px;
59 - font-size: 12px;
60 - font-weight: bold;
61 - z-index: 10001;
62 - `;
63 - chatButton.style.position = 'relative';
64 - chatButton.appendChild(notificationBadge);
65 -
66 - console.log("Notification badge created and appended:", {
67 - exists: !!notificationBadge,
68 - parent: notificationBadge?.parentNode?.id,
69 - display: notificationBadge?.style?.display
70 - });
71 -}
72 -
73 -// Function to check for new messages
74 -function checkForNewMessages() {
75 - const sessionId = getChatSession();
76 - const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
77 -
78 - if (!chatPersistenceEnabled) return;
79 -
80 - $.ajax({
81 - url: mxchatChat.ajax_url,
82 - type: 'POST',
83 - data: {
84 - action: 'mxchat_check_new_messages',
85 - session_id: sessionId,
86 - last_seen_id: lastSeenMessageId,
87 - nonce: mxchatChat.nonce
88 - },
89 - success: function(response) {
90 - if (response.success && response.data.hasNewMessages) {
91 - showNotification();
92 - }
93 - }
94 - });
95 -}
96 -
97 -// Function to show notification
98 -function showNotification() {
99 - const badge = document.getElementById('chat-notification-badge');
100 - if (badge && $('#floating-chatbot').hasClass('hidden')) {
101 - badge.style.display = 'block';
102 - badge.textContent = '1';
103 - }
104 -}
105 -
106 -function hideNotification() {
107 - const badge = document.getElementById('chat-notification-badge');
108 - if (badge) {
109 - badge.style.display = 'none';
110 - }
111 -}
112 -// Function to start notification checking
113 -function startNotificationChecking() {
114 - const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
115 - if (!chatPersistenceEnabled) return;
116 -
117 - createNotificationBadge();
118 - notificationCheckInterval = setInterval(checkForNewMessages, 30000); // Check every 30 seconds
119 -}
120 -
121 -// Function to stop notification checking
122 -function stopNotificationChecking() {
123 - if (notificationCheckInterval) {
124 - clearInterval(notificationCheckInterval);
125 - }
126 -}
127 -
128 -
129 -
130 -
131 32 function getChatSession() {
132 33 var sessionId = getCookie('mxchat_session_id');
133 34 //console.log("Session ID retrieved from cookie: ", sessionId);
134 35
@@ -256,48 +157,24 @@
256 157 sendMessageToChatbot(question);
257 158 });
258 159
259 160
260 -// Add this new function to handle markdown headers
261 -function formatMarkdownHeaders(text) {
262 - // Handle h1 to h6 headers
263 - return text.replace(/^(#{1,6})\s(.+)$/gm, function(match, hashes, content) {
264 - const level = hashes.length;
265 - return `<h${level} class="chat-heading">${content}</h${level}>`;
266 - });
267 -}
268 -
269 -// Update the linkify function to handle both URLs and markdown
270 -// Modified linkify function that only processes URLs in user content
161 +// Use the linkTarget in your linkify function
271 162 function linkify(inputText) {
272 - if (!inputText) return '';
273 -
274 - // Process markdown headers
275 - let processedText = formatMarkdownHeaders(inputText);
276 -
277 - // Process markdown links
278 - const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
279 - processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
280 - const safeUrl = encodeURI(url);
281 - const safeText = sanitizeUserInput(text);
282 - return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
283 - });
163 + // Check for already linked URLs and skip them
164 + // We use negative lookaheads to skip anything already in an <a> tag
165 + var markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
166 + var replacedText = inputText.replace(markdownLinkPattern, '<a href="$2" target="' + linkTarget + '">$1</a>');
284 167
285 - // Process standalone URLs
286 - const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
287 - processedText = processedText.replace(urlPattern, (match, prefix, url) => {
288 - const safeUrl = encodeURI(url);
289 - return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
290 - });
168 + // Replace standalone URLs not already in an <a> tag
169 + var urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
170 + replacedText = replacedText.replace(urlPattern, '$1<a href="$2" target="' + linkTarget + '">$2</a>');
291 171
292 - // Process www. URLs
293 - const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
294 - processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
295 - const safeUrl = encodeURI(`http://${url}`);
296 - return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
297 - });
172 + // Replace "www." prefixed URLs not already in an <a> tag
173 + var wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
174 + replacedText = replacedText.replace(wwwPattern, '$1<a href="http://$2" target="' + linkTarget + '">$2</a>');
298 175
299 - return processedText;
176 + return replacedText;
300 177 }
301 178
302 179
303 180 function scrollElementToTop(element) {
@@ -379,8 +256,10 @@
379 256 }
380 257 }
381 258
382 259 function callMxChat(message, callback) {
260 + //console.log("Sending message to chatbot:", message);
261 +
383 262 $.ajax({
384 263 url: mxchatChat.ajax_url,
385 264 type: 'POST',
386 265 dataType: 'json',
@@ -390,38 +269,41 @@
390 269 session_id: getChatSession(),
391 270 nonce: mxchatChat.nonce
392 271 },
393 272 success: function(response) {
394 - // Existing chat mode check
273 + //console.log("callMxChat response:", response);
274 +
275 + // Check for chat_mode in the response
395 276 if (response.chat_mode) {
396 277 updateChatModeIndicator(response.chat_mode);
397 278 }
279 + // Also check in fallbackResponse if exists
398 280 else if (response.fallbackResponse && response.fallbackResponse.chat_mode) {
399 281 updateChatModeIndicator(response.fallbackResponse.chat_mode);
400 282 }
401 -
402 283 // Add PDF filename handling
403 284 if (response.data && response.data.filename) {
404 285 showActivePdf(response.data.filename);
405 286 activePdfFile = response.data.filename;
406 287 }
407 -
408 - // Add redirect check here
288 + // Add redirect check here
409 289 if (response.redirect_url) {
290 + // Show the message first
410 291 let responseText = response.text || '';
411 292 if (responseText) {
412 293 replaceLastMessage("bot", responseText);
413 294 }
295 + // Then redirect after a short delay
414 296 setTimeout(() => {
415 297 window.location.href = response.redirect_url;
416 298 }, 1500);
417 - return;
299 + return; // Exit early since we're redirecting
418 300 }
419 301
420 -
421 302 // Check for live agent response
422 303 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
423 304 updateChatModeIndicator('agent');
305 + // Do not replace the thinking dots; just wait for the agent's actual response
424 306 return;
425 307 }
426 308
427 309 // Handle other responses
@@ -428,33 +310,31 @@
428 310 let responseText = response.text || '';
429 311 let responseHtml = response.html || '';
430 312 let responseMessage = response.message || '';
431 313
314 + // Check for mode change in text response
432 315 if (responseText === 'You are now chatting with the AI chatbot.') {
433 316 updateChatModeIndicator('ai');
434 317 }
435 318
436 - // Handle the message and show notification if chat is hidden
437 - if (responseText || responseHtml || responseMessage) {
438 - // Update the messages as before
439 - if (responseText && responseHtml) {
440 - replaceLastMessage("bot", responseText, responseHtml);
441 - } else if (responseText) {
442 - replaceLastMessage("bot", responseText);
443 - } else if (responseHtml) {
444 - replaceLastMessage("bot", "", responseHtml);
445 - } else if (responseMessage) {
446 - replaceLastMessage("bot", responseMessage);
447 - }
448 -
449 - // Check if chat is hidden and show notification
450 - if ($('#floating-chatbot').hasClass('hidden')) {
451 - const badge = $('#chat-notification-badge');
452 - if (badge.length) {
453 - badge.show();
454 - }
455 - }
456 - } else {
319 + // For product card or chatbot responses with HTML
320 + if (responseText && responseHtml) {
321 + replaceLastMessage("bot", responseText, responseHtml);
322 + }
323 + // For regular chatbot responses with just text
324 + else if (responseText) {
325 + replaceLastMessage("bot", responseText);
326 + }
327 + // For responses with only HTML (like product cards)
328 + else if (responseHtml) {
329 + replaceLastMessage("bot", "", responseHtml);
330 + }
331 + // For legacy message format
332 + else if (responseMessage) {
333 + replaceLastMessage("bot", responseMessage);
334 + }
335 + // Fallback error case
336 + else {
457 337 console.error("Unexpected response format:", response);
458 338 replaceLastMessage("bot", "I'm sorry, something went wrong.");
459 339 }
460 340
@@ -462,22 +342,17 @@
462 342 lastSeenMessageId = response.message_id;
463 343 }
464 344 },
465 345 error: function(xhr, status, error) {
346 + //console.log("Error communicating with the server:", xhr.status, error);
466 347 replaceLastMessage("bot", "An unexpected error occurred.");
467 348 }
468 349 });
469 350 }
470 351
471 -// Sanitize only user input
472 -function sanitizeUserInput(text) {
473 - const div = document.createElement('div');
474 - div.textContent = text;
475 - return div.innerHTML;
476 -}
352 +function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
353 + //console.log("Appending message. Sender:", sender, "Content:", messageText);
477 354
478 -// Modified appendMessage function that only sanitizes user content
479 -function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
480 355 try {
481 356 // Determine styles based on sender type
482 357 let messageClass, bgColor, fontColor;
483 358
@@ -484,52 +359,42 @@
484 359 if (sender === "user") {
485 360 messageClass = "user-message";
486 361 bgColor = userMessageBgColor;
487 362 fontColor = userMessageFontColor;
488 - // Only sanitize user input
489 - messageText = sanitizeUserInput(messageText);
490 - } else if (sender === "agent") {
491 - messageClass = "agent-message";
492 - bgColor = liveAgentMessageBgColor;
493 - fontColor = liveAgentMessageFontColor;
494 - } else {
363 + } else if (sender === "agent") {
364 + messageClass = "agent-message";
365 + bgColor = liveAgentMessageBgColor;
366 + fontColor = liveAgentMessageFontColor;
367 + } else {
495 368 messageClass = "bot-message";
496 369 bgColor = botMessageBgColor;
497 370 fontColor = botMessageFontColor;
498 371 }
499 372
500 - const messageDiv = $('<div>')
501 - .addClass(messageClass)
502 - .css({
503 - 'background': bgColor,
504 - 'color': fontColor,
505 - 'margin-bottom': '1em'
506 - });
373 + const messageDiv = $('<div>')
374 + .addClass(messageClass)
375 + .css({
376 + 'background': bgColor,
377 + 'color': fontColor,
378 + });
507 379
508 - // Process the message content based on sender
509 - let fullMessage;
510 - if (sender === "user") {
511 - // For user messages, apply linkify after sanitization
512 - fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
513 - } else {
514 - // For bot/agent messages, preserve HTML
515 - fullMessage = messageText;
516 - }
380 + // Add CSS for paragraphs
381 + messageDiv.css({
382 + 'margin-bottom': '1em'
383 + });
517 384
385 + // Format and process the message content
386 + let fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
387 +
518 388 // Add images if provided
519 389 if (images && images.length > 0) {
520 390 fullMessage += '<div class="image-gallery">';
521 391 images.forEach(img => {
522 - // Ensure image URLs and titles are properly escaped
523 - const safeTitle = sanitizeUserInput(img.title);
524 - const safeUrl = encodeURI(img.image_url);
525 - const safeThumbnail = encodeURI(img.thumbnail_url);
526 -
527 392 fullMessage += `
528 393 <div style="margin-bottom: 10px;">
529 - <strong>${safeTitle}</strong><br>
530 - <a href="${safeUrl}" target="_blank">
531 - <img src="${safeThumbnail}" alt="${safeTitle}" style="max-width: 100px; height: auto; margin: 5px;" />
394 + <strong>${img.title}</strong><br>
395 + <a href="${img.image_url}" target="_blank">
396 + <img src="${img.thumbnail_url}" alt="${img.title}" style="max-width: 100px; height: auto; margin: 5px;" />
532 397 </a>
533 398 </div>`;
534 399 });
535 400 fullMessage += '</div>';
@@ -535,20 +400,23 @@
535 400 fullMessage += '</div>';
536 401 }
537 402
538 403 // Append HTML content if provided
539 - if (messageHtml && sender !== "user") {
404 + if (messageHtml) {
540 405 fullMessage += '<br><br>' + messageHtml;
541 406 }
542 407
543 408 messageDiv.html(fullMessage);
544 409
410 + // Add a class for temporary messages if needed
545 411 if (isTemporary) {
546 412 messageDiv.addClass('temporary-message');
547 413 }
548 414
549 - messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() {
415 + // Append the message to the chat box
416 + messageDiv.hide().appendTo('#chat-box').fadeIn(300, function () {
550 417 if (sender === "bot") {
418 + // After bot's message is displayed, scroll the last user message to the top
551 419 const lastUserMessage = $('#chat-box').find('.user-message').last();
552 420 if (lastUserMessage.length) {
553 421 scrollElementToTop(lastUserMessage);
554 422 }
@@ -554,14 +422,14 @@
554 422 }
555 423 }
556 424 });
557 425
426 + // Update the last seen message ID if applicable
558 427 if (messageText.id) {
559 428 lastSeenMessageId = messageText.id;
560 - hideNotification();
561 429 }
562 430 } catch (error) {
563 - console.error("Error rendering message:", error);
431 + console.error("Error rendering message with images:", error);
564 432 }
565 433 }
566 434
567 435
@@ -612,17 +480,14 @@
612 480 'color': fontColor,
613 481 })
614 482 .removeClass('temporary-message')
615 483 .fadeIn(200, function() {
484 + // After message is displayed, scroll last user message to top
616 485 if (sender === "bot" || sender === "agent") {
617 486 const lastUserMessage = $('#chat-box').find('.user-message').last();
618 487 if (lastUserMessage.length) {
619 488 scrollElementToTop(lastUserMessage);
620 489 }
621 - // Show notification if chat is hidden
622 - if ($('#floating-chatbot').hasClass('hidden')) {
623 - showNotification();
624 - }
625 490 }
626 491 });
627 492 });
628 493 } else {
@@ -647,11 +512,11 @@
647 512 }
648 513 }
649 514
650 515
651 -// Update your checkForAgentMessages function
652 516 function checkForAgentMessages() {
653 517 const sessionId = getChatSession();
518 +
654 519 $.ajax({
655 520 url: mxchatChat.ajax_url,
656 521 type: 'POST',
657 522 dataType: 'json',
@@ -661,24 +526,17 @@
661 526 last_seen_id: lastSeenMessageId,
662 527 nonce: mxchatChat.nonce
663 528 },
664 529 success: function (response) {
530 + //console.log("Agent messages polling response:", response);
665 531 if (response.success && response.data?.new_messages) {
666 - let hasNewMessage = false;
667 -
668 532 response.data.new_messages.forEach(function (message) {
669 533 if (message.role === "agent" && !processedMessageIds.has(message.id)) {
670 - hasNewMessage = true;
671 534 replaceLastMessage("agent", message.content);
672 535 lastSeenMessageId = message.id;
673 536 processedMessageIds.add(message.id);
674 537 }
675 538 });
676 -
677 - if (hasNewMessage && $('#floating-chatbot').hasClass('hidden')) {
678 - showNotification();
679 - }
680 -
681 539 scrollToBottom(true);
682 540 }
683 541 },
684 542 error: function (xhr, status, error) {
@@ -805,37 +663,29 @@
805 663 return textArea.value;
806 664 }
807 665
808 666
809 -// Update formatCodeBlocks function
810 667 function formatCodeBlocks(text) {
811 - // First handle raw PHP tags
812 - text = text.replace(/(<\?php[\s\S]*?\?>)/g, (match) => {
813 - return `<pre><code class="language-php">${escapeHtml(match)}</code></pre>`;
814 - });
668 + // Ensure the input is a string; otherwise, convert or return empty
669 + if (typeof text !== 'string') {
670 + console.error("formatCodeBlocks: Input is not a string:", text);
671 + return typeof text === 'object' && text.text ? text.text : ""; // Use .text if available, else empty
672 + }
815 673
816 - // Then handle code blocks with backticks
817 - text = text.replace(/```php5?\n([\s\S]+?)```/gi, (match, code) => {
818 - return `<pre><code class="language-php">${escapeHtml(code)}</code></pre>`;
674 + const codeBlockPattern = /```(\w+)?\n?([\s\S]+?)```/g;
675 +
676 + return text.replace(codeBlockPattern, (_, language, codeContent) => {
677 + language = language || 'plaintext';
678 +
679 + return `
680 + <div class="mxchat-code-block-container">
681 + <button class="mxchat-copy-button" aria-label="Copy to clipboard">Copy</button>
682 + <pre class="mxchat-code-block"><code class="mxchat-language-${language}">${escapeHtml(codeContent)}</code></pre>
683 + </div>`;
819 684 });
685 +}
820 686
821 - return text;
822 -}
823 687
824 -// Update escapeHtml function to preserve existing code blocks
825 -function escapeHtml(unsafe) {
826 - // First check if it's already a code block
827 - if (unsafe.includes('<pre><code') || unsafe.includes('</code></pre>')) {
828 - return unsafe;
829 - }
830 -
831 - return unsafe
832 - .replace(/&/g, "&amp;")
833 - .replace(/</g, "&lt;")
834 - .replace(/>/g, "&gt;")
835 - .replace(/"/g, "&quot;")
836 - .replace(/'/g, "&#039;");
837 -}
838 688 // Utility function to escape HTML
839 689 function escapeHtml(unsafe) {
840 690 return unsafe
841 691 .replace(/&/g, "&amp;")
@@ -849,18 +699,20 @@
849 699
850 700
851 701 // Function to convert newlines, skipping preformatted text
852 702 function convertNewlinesToBreaks(text) {
853 - // Split while preserving code blocks
854 - return text.split(/(<pre\b[^>]*>[\s\S]*?<\/pre>)/g).map(part => {
855 - if (part.startsWith('<pre')) return part;
856 - return part.replace(/(^|[^>])\n/g, '$1<br>');
857 - }).join('');
703 + // Regex to exclude <pre> and <code> tags from adding <br> tags
704 + return text.replace(/(^|[^>])\n/g, '$1<br>');
858 705 }
859 706
860 707
861 708
709 +$(document).ready(function() {
710 + loadChatHistory();
711 +});
862 712
713 +
714 +
863 715 // Helper function to check if a string is an image HTML
864 716 function isImageHtml(str) {
865 717 return str.startsWith('<img') && str.endsWith('>');
866 718 }
@@ -952,11 +804,15 @@
952 804 // Also check pre-chat message when Complianz is not enabled
953 805 checkPreChatDismissal();
954 806 }
955 807 }
808 +// Initialize when document is ready
809 +$(document).ready(function() {
810 + //console.log('Document ready - starting initialization');
811 + initializeChatVisibility();
812 +});
956 813
957 814
958 -
959 815 function checkConsentAndShowChat() {
960 816 var consentStatus = cmplz_has_consent('marketing');
961 817 var consentType = complianz.consenttype;
962 818
@@ -1023,15 +879,16 @@
1023 879 var chatbot = $('#floating-chatbot');
1024 880 if (chatbot.hasClass('hidden')) {
1025 881 chatbot.removeClass('hidden').addClass('visible');
1026 882 $(this).addClass('hidden');
1027 - $('#chat-notification-badge').hide(); // Hide notification when opening chat
1028 883 disableScroll();
884 + // Hide the pre-chat message without dismissing it
1029 885 $('#pre-chat-message').fadeOut(250);
1030 886 } else {
1031 887 chatbot.removeClass('visible').addClass('hidden');
1032 888 $(this).removeClass('hidden');
1033 889 enableScroll();
890 + // Show the pre-chat message again if it hasn't been dismissed
1034 891 checkPreChatDismissal();
1035 892 }
1036 893 });
1037 894
@@ -1070,10 +927,12 @@
1070 927 $(':root').css('--vh', vh + 'px');
1071 928 }
1072 929
1073 930 // Set the height when the page loads
931 + $(document).ready(function() {
932 + setFullHeight();
933 + });
1074 934
1075 -
1076 935 // Set the height on resize and orientation change events
1077 936 $(window).on('resize orientationchange', function() {
1078 937 setFullHeight();
1079 938 });
@@ -1558,15 +1417,9 @@
1558 1417 console.error('Essential elements for email handling are missing.');
1559 1418 }
1560 1419
1561 1420
1562 -// Initialize when document is ready
1563 -$(document).ready(function() {
1564 - setFullHeight();
1565 - initializeChatVisibility();
1566 - loadChatHistory();
1567 1421
1568 -});
1569 1422
1570 1423 });
1571 1424
1572 1425 // Event listener for copy button