PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.1.4
MxChat – AI Chatbot & Content Generation for WordPress v2.1.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
← All changes | js/chat-script.js +115 -54 2.0.42.1.4 View file →
@@ -265,29 +265,48 @@
265 265 return `<h${level} class="chat-heading">${content}</h${level}>`;
266 266 });
267 267 }
268 268
269 -// Update the linkify function to handle both URLs and markdown
269 +// Update the linkify function to handle URLs, markdown, and phone numbers
270 270 function linkify(inputText) {
271 - // First process markdown headers
271 + if (!inputText) return '';
272 +
273 + // Process markdown headers
272 274 let processedText = formatMarkdownHeaders(inputText);
273 275
274 - // Then process links as before
275 - var markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
276 - processedText = processedText.replace(markdownLinkPattern, '<a href="$2" target="' + linkTarget + '">$1</a>');
276 + // Process markdown links
277 + const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
278 + processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
279 + const safeUrl = encodeURI(url);
280 + const safeText = sanitizeUserInput(text);
281 + return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
282 + });
277 283
278 - // Replace standalone URLs not already in an <a> tag
279 - var urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
280 - processedText = processedText.replace(urlPattern, '$1<a href="$2" target="' + linkTarget + '">$2</a>');
284 + // Process phone numbers (tel:)
285 + const phonePattern = /\[([^\]]+)\]\((tel:[\d+]+)\)/g;
286 + processedText = processedText.replace(phonePattern, (match, text, phone) => {
287 + const safePhone = encodeURI(phone);
288 + const safeText = sanitizeUserInput(text);
289 + return `<a href="${safePhone}">${safeText}</a>`;
290 + });
281 291
282 - // Replace "www." prefixed URLs not already in an <a> tag
283 - var wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
284 - processedText = processedText.replace(wwwPattern, '$1<a href="http://$2" target="' + linkTarget + '">$2</a>');
292 + // Process standalone URLs
293 + const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
294 + processedText = processedText.replace(urlPattern, (match, prefix, url) => {
295 + const safeUrl = encodeURI(url);
296 + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
297 + });
285 298
299 + // Process www. URLs
300 + const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
301 + processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
302 + const safeUrl = encodeURI(`http://${url}`);
303 + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
304 + });
305 +
286 306 return processedText;
287 307 }
288 308
289 -
290 309 function scrollElementToTop(element) {
291 310 var chatBox = $('#chat-box');
292 311 var elementTop = element.position().top + chatBox.scrollTop();
293 312 chatBox.animate({ scrollTop: elementTop }, 500);
@@ -354,11 +373,17 @@
354 373
355 374 function updateChatModeIndicator(mode) {
356 375 const indicator = document.getElementById('chat-mode-indicator');
357 376 if (indicator) {
358 - indicator.textContent = mode === 'agent' ? 'Live Agent' : 'AI Agent';
377 + // For Live Agent, keep as is; for AI mode, use the customized text
378 + if (mode === 'agent') {
379 + indicator.textContent = 'Live Agent';
380 + } else {
381 + // Get the custom AI agent text from a data attribute we'll add to the element
382 + const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
383 + indicator.textContent = customAiText;
384 + }
359 385 }
360 -
361 386 // Start or stop polling based on mode
362 387 if (mode === 'agent') {
363 388 startPolling();
364 389 } else {
@@ -390,8 +415,20 @@
390 415 if (response.data && response.data.filename) {
391 416 showActivePdf(response.data.filename);
392 417 activePdfFile = response.data.filename;
393 418 }
419 +
420 + // Add redirect check here
421 + if (response.redirect_url) {
422 + let responseText = response.text || '';
423 + if (responseText) {
424 + replaceLastMessage("bot", responseText);
425 + }
426 + setTimeout(() => {
427 + window.location.href = response.redirect_url;
428 + }, 1500);
429 + return;
430 + }
394 431
395 432
396 433 // Check for live agent response
397 434 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
@@ -442,11 +479,17 @@
442 479 }
443 480 });
444 481 }
445 482
483 +// Sanitize only user input
484 +function sanitizeUserInput(text) {
485 + const div = document.createElement('div');
486 + div.textContent = text;
487 + return div.innerHTML;
488 +}
489 +
490 +// Modified appendMessage function that only sanitizes user content
446 491 function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
447 - //console.log("Appending message. Sender:", sender, "Content:", messageText);
448 -
449 492 try {
450 493 // Determine styles based on sender type
451 494 let messageClass, bgColor, fontColor;
452 495
@@ -453,42 +496,52 @@
453 496 if (sender === "user") {
454 497 messageClass = "user-message";
455 498 bgColor = userMessageBgColor;
456 499 fontColor = userMessageFontColor;
457 - } else if (sender === "agent") {
458 - messageClass = "agent-message";
459 - bgColor = liveAgentMessageBgColor;
460 - fontColor = liveAgentMessageFontColor;
461 - } else {
500 + // Only sanitize user input
501 + messageText = sanitizeUserInput(messageText);
502 + } else if (sender === "agent") {
503 + messageClass = "agent-message";
504 + bgColor = liveAgentMessageBgColor;
505 + fontColor = liveAgentMessageFontColor;
506 + } else {
462 507 messageClass = "bot-message";
463 508 bgColor = botMessageBgColor;
464 509 fontColor = botMessageFontColor;
465 510 }
466 511
467 - const messageDiv = $('<div>')
468 - .addClass(messageClass)
469 - .css({
470 - 'background': bgColor,
471 - 'color': fontColor,
472 - });
512 + const messageDiv = $('<div>')
513 + .addClass(messageClass)
514 + .css({
515 + 'background': bgColor,
516 + 'color': fontColor,
517 + 'margin-bottom': '1em'
518 + });
473 519
474 - // Add CSS for paragraphs
475 - messageDiv.css({
476 - 'margin-bottom': '1em'
477 - });
520 + // Process the message content based on sender
521 + let fullMessage;
522 + if (sender === "user") {
523 + // For user messages, apply linkify after sanitization
524 + fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
525 + } else {
526 + // For bot/agent messages, preserve HTML
527 + fullMessage = messageText;
528 + }
478 529
479 - // Format and process the message content
480 - let fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
481 -
482 530 // Add images if provided
483 531 if (images && images.length > 0) {
484 532 fullMessage += '<div class="image-gallery">';
485 533 images.forEach(img => {
534 + // Ensure image URLs and titles are properly escaped
535 + const safeTitle = sanitizeUserInput(img.title);
536 + const safeUrl = encodeURI(img.image_url);
537 + const safeThumbnail = encodeURI(img.thumbnail_url);
538 +
486 539 fullMessage += `
487 540 <div style="margin-bottom: 10px;">
488 - <strong>${img.title}</strong><br>
489 - <a href="${img.image_url}" target="_blank">
490 - <img src="${img.thumbnail_url}" alt="${img.title}" style="max-width: 100px; height: auto; margin: 5px;" />
541 + <strong>${safeTitle}</strong><br>
542 + <a href="${safeUrl}" target="_blank">
543 + <img src="${safeThumbnail}" alt="${safeTitle}" style="max-width: 100px; height: auto; margin: 5px;" />
491 544 </a>
492 545 </div>`;
493 546 });
494 547 fullMessage += '</div>';
@@ -494,23 +547,20 @@
494 547 fullMessage += '</div>';
495 548 }
496 549
497 550 // Append HTML content if provided
498 - if (messageHtml) {
551 + if (messageHtml && sender !== "user") {
499 552 fullMessage += '<br><br>' + messageHtml;
500 553 }
501 554
502 555 messageDiv.html(fullMessage);
503 556
504 - // Add a class for temporary messages if needed
505 557 if (isTemporary) {
506 558 messageDiv.addClass('temporary-message');
507 559 }
508 560
509 - // Append the message to the chat box
510 - messageDiv.hide().appendTo('#chat-box').fadeIn(300, function () {
561 + messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() {
511 562 if (sender === "bot") {
512 - // After bot's message is displayed, scroll the last user message to the top
513 563 const lastUserMessage = $('#chat-box').find('.user-message').last();
514 564 if (lastUserMessage.length) {
515 565 scrollElementToTop(lastUserMessage);
516 566 }
@@ -517,13 +567,13 @@
517 567 }
518 568 });
519 569
520 570 if (messageText.id) {
521 - lastSeenMessageId = messageText.id;
522 - hideNotification();
523 - }
571 + lastSeenMessageId = messageText.id;
572 + hideNotification();
573 + }
524 574 } catch (error) {
525 - console.error("Error rendering message with images:", error);
575 + console.error("Error rendering message:", error);
526 576 }
527 577 }
528 578
529 579
@@ -1091,10 +1141,18 @@
1091 1141 document.getElementById('word-upload').click();
1092 1142 });
1093 1143 }
1094 1144
1145 +function addSafeEventListener(elementId, eventType, handler) {
1146 + const element = document.getElementById(elementId);
1147 + if (element) {
1148 + element.addEventListener(eventType, handler);
1149 + }
1150 +}
1151 +
1152 +
1095 1153 // PDF file input change handler
1096 -document.getElementById('pdf-upload').addEventListener('change', async function(e) {
1154 +addSafeEventListener('pdf-upload', 'change', async function(e) {
1097 1155 const file = e.target.files[0];
1098 1156
1099 1157 if (!file || file.type !== 'application/pdf') {
1100 1158 alert('Please select a valid PDF file.');
@@ -1166,9 +1224,9 @@
1166 1224 }
1167 1225 });
1168 1226
1169 1227 // Word file input change handler
1170 -document.getElementById('word-upload').addEventListener('change', async function(e) {
1228 +addSafeEventListener('word-upload', 'change', async function(e) {
1171 1229 const file = e.target.files[0];
1172 1230
1173 1231 if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1174 1232 alert('Please select a valid Word document (.docx).');
@@ -1401,21 +1459,24 @@
1401 1459 document.addEventListener('DOMContentLoaded', function() {
1402 1460 checkInitialDocumentStatus();
1403 1461 });
1404 1462
1405 -// Style all toolbar elements
1406 1463 const toolbarElements = [
1407 1464 '#mxchat-chatbot .toolbar-btn svg',
1408 1465 '#mxchat-chatbot .active-pdf-name',
1409 1466 '#mxchat-chatbot .active-word-name',
1410 1467 '#mxchat-chatbot .remove-pdf-btn svg',
1411 - '#mxchat-chatbot .remove-word-btn svg'
1468 + '#mxchat-chatbot .remove-word-btn svg',
1469 + '#mxchat-chatbot .toolbar-perplexity svg'
1412 1470 ];
1413 -$(toolbarElements.join(', ')).css({
1414 - 'fill': toolbarIconColor,
1415 - 'color': toolbarIconColor
1471 +
1472 +toolbarElements.forEach(selector => {
1473 + $(selector).css({
1474 + 'fill': toolbarIconColor,
1475 + 'stroke': toolbarIconColor,
1476 + 'color': toolbarIconColor
1477 + });
1416 1478 });
1417 -
1418 1479
1419 1480
1420 1481 // Ensure essential elements are defined
1421 1482 const emailForm = document.getElementById('email-collection-form');