| @@ -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); |
| @@ -390,8 +409,20 @@ | ||
| 390 | 409 | if (response.data && response.data.filename) { |
| 391 | 410 | showActivePdf(response.data.filename); |
| 392 | 411 | activePdfFile = response.data.filename; |
| 393 | 412 | } |
| 413 | + | |
| 414 | + // Add redirect check here | |
| 415 | + if (response.redirect_url) { | |
| 416 | + let responseText = response.text || ''; | |
| 417 | + if (responseText) { | |
| 418 | + replaceLastMessage("bot", responseText); | |
| 419 | + } | |
| 420 | + setTimeout(() => { | |
| 421 | + window.location.href = response.redirect_url; | |
| 422 | + }, 1500); | |
| 423 | + return; | |
| 424 | + } | |
| 394 | 425 | |
| 395 | 426 | |
| 396 | 427 | // Check for live agent response |
| 397 | 428 | if (response.success && response.data && response.data.status === 'waiting_for_agent') { |
| @@ -442,11 +473,17 @@ | ||
| 442 | 473 | } |
| 443 | 474 | }); |
| 444 | 475 | } |
| 445 | 476 | |
| 477 | +// Sanitize only user input | |
| 478 | +function sanitizeUserInput(text) { | |
| 479 | + const div = document.createElement('div'); | |
| 480 | + div.textContent = text; | |
| 481 | + return div.innerHTML; | |
| 482 | +} | |
| 483 | + | |
| 484 | +// Modified appendMessage function that only sanitizes user content | |
| 446 | 485 | function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) { |
| 447 | - //console.log("Appending message. Sender:", sender, "Content:", messageText); | |
| 448 | - | |
| 449 | 486 | try { |
| 450 | 487 | // Determine styles based on sender type |
| 451 | 488 | let messageClass, bgColor, fontColor; |
| 452 | 489 | |
| @@ -453,42 +490,52 @@ | ||
| 453 | 490 | if (sender === "user") { |
| 454 | 491 | messageClass = "user-message"; |
| 455 | 492 | bgColor = userMessageBgColor; |
| 456 | 493 | fontColor = userMessageFontColor; |
| 457 | - } else if (sender === "agent") { | |
| 458 | - messageClass = "agent-message"; | |
| 459 | - bgColor = liveAgentMessageBgColor; | |
| 460 | - fontColor = liveAgentMessageFontColor; | |
| 461 | - } else { | |
| 494 | + // Only sanitize user input | |
| 495 | + messageText = sanitizeUserInput(messageText); | |
| 496 | + } else if (sender === "agent") { | |
| 497 | + messageClass = "agent-message"; | |
| 498 | + bgColor = liveAgentMessageBgColor; | |
| 499 | + fontColor = liveAgentMessageFontColor; | |
| 500 | + } else { | |
| 462 | 501 | messageClass = "bot-message"; |
| 463 | 502 | bgColor = botMessageBgColor; |
| 464 | 503 | fontColor = botMessageFontColor; |
| 465 | 504 | } |
| 466 | 505 | |
| 467 | - const messageDiv = $('<div>') | |
| 468 | - .addClass(messageClass) | |
| 469 | - .css({ | |
| 470 | - 'background': bgColor, | |
| 471 | - 'color': fontColor, | |
| 472 | - }); | |
| 506 | + const messageDiv = $('<div>') | |
| 507 | + .addClass(messageClass) | |
| 508 | + .css({ | |
| 509 | + 'background': bgColor, | |
| 510 | + 'color': fontColor, | |
| 511 | + 'margin-bottom': '1em' | |
| 512 | + }); | |
| 473 | 513 | |
| 474 | - // Add CSS for paragraphs | |
| 475 | - messageDiv.css({ | |
| 476 | - 'margin-bottom': '1em' | |
| 477 | - }); | |
| 514 | + // Process the message content based on sender | |
| 515 | + let fullMessage; | |
| 516 | + if (sender === "user") { | |
| 517 | + // For user messages, apply linkify after sanitization | |
| 518 | + fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText)))); | |
| 519 | + } else { | |
| 520 | + // For bot/agent messages, preserve HTML | |
| 521 | + fullMessage = messageText; | |
| 522 | + } | |
| 478 | 523 | |
| 479 | - // Format and process the message content | |
| 480 | - let fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText)))); | |
| 481 | - | |
| 482 | 524 | // Add images if provided |
| 483 | 525 | if (images && images.length > 0) { |
| 484 | 526 | fullMessage += '<div class="image-gallery">'; |
| 485 | 527 | images.forEach(img => { |
| 528 | + // Ensure image URLs and titles are properly escaped | |
| 529 | + const safeTitle = sanitizeUserInput(img.title); | |
| 530 | + const safeUrl = encodeURI(img.image_url); | |
| 531 | + const safeThumbnail = encodeURI(img.thumbnail_url); | |
| 532 | + | |
| 486 | 533 | fullMessage += ` |
| 487 | 534 | <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;" /> | |
| 535 | + <strong>${safeTitle}</strong><br> | |
| 536 | + <a href="${safeUrl}" target="_blank"> | |
| 537 | + <img src="${safeThumbnail}" alt="${safeTitle}" style="max-width: 100px; height: auto; margin: 5px;" /> | |
| 491 | 538 | </a> |
| 492 | 539 | </div>`; |
| 493 | 540 | }); |
| 494 | 541 | fullMessage += '</div>'; |
| @@ -494,23 +541,20 @@ | ||
| 494 | 541 | fullMessage += '</div>'; |
| 495 | 542 | } |
| 496 | 543 | |
| 497 | 544 | // Append HTML content if provided |
| 498 | - if (messageHtml) { | |
| 545 | + if (messageHtml && sender !== "user") { | |
| 499 | 546 | fullMessage += '<br><br>' + messageHtml; |
| 500 | 547 | } |
| 501 | 548 | |
| 502 | 549 | messageDiv.html(fullMessage); |
| 503 | 550 | |
| 504 | - // Add a class for temporary messages if needed | |
| 505 | 551 | if (isTemporary) { |
| 506 | 552 | messageDiv.addClass('temporary-message'); |
| 507 | 553 | } |
| 508 | 554 | |
| 509 | - // Append the message to the chat box | |
| 510 | - messageDiv.hide().appendTo('#chat-box').fadeIn(300, function () { | |
| 555 | + messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() { | |
| 511 | 556 | if (sender === "bot") { |
| 512 | - // After bot's message is displayed, scroll the last user message to the top | |
| 513 | 557 | const lastUserMessage = $('#chat-box').find('.user-message').last(); |
| 514 | 558 | if (lastUserMessage.length) { |
| 515 | 559 | scrollElementToTop(lastUserMessage); |
| 516 | 560 | } |
| @@ -517,13 +561,13 @@ | ||
| 517 | 561 | } |
| 518 | 562 | }); |
| 519 | 563 | |
| 520 | 564 | if (messageText.id) { |
| 521 | - lastSeenMessageId = messageText.id; | |
| 522 | - hideNotification(); | |
| 523 | - } | |
| 565 | + lastSeenMessageId = messageText.id; | |
| 566 | + hideNotification(); | |
| 567 | + } | |
| 524 | 568 | } catch (error) { |
| 525 | - console.error("Error rendering message with images:", error); | |
| 569 | + console.error("Error rendering message:", error); | |
| 526 | 570 | } |
| 527 | 571 | } |
| 528 | 572 | |
| 529 | 573 | |
| @@ -1091,10 +1135,18 @@ | ||
| 1091 | 1135 | document.getElementById('word-upload').click(); |
| 1092 | 1136 | }); |
| 1093 | 1137 | } |
| 1094 | 1138 | |
| 1139 | +function addSafeEventListener(elementId, eventType, handler) { | |
| 1140 | + const element = document.getElementById(elementId); | |
| 1141 | + if (element) { | |
| 1142 | + element.addEventListener(eventType, handler); | |
| 1143 | + } | |
| 1144 | +} | |
| 1145 | + | |
| 1146 | + | |
| 1095 | 1147 | // PDF file input change handler |
| 1096 | -document.getElementById('pdf-upload').addEventListener('change', async function(e) { | |
| 1148 | +addSafeEventListener('pdf-upload', 'change', async function(e) { | |
| 1097 | 1149 | const file = e.target.files[0]; |
| 1098 | 1150 | |
| 1099 | 1151 | if (!file || file.type !== 'application/pdf') { |
| 1100 | 1152 | alert('Please select a valid PDF file.'); |
| @@ -1166,9 +1218,9 @@ | ||
| 1166 | 1218 | } |
| 1167 | 1219 | }); |
| 1168 | 1220 | |
| 1169 | 1221 | // Word file input change handler |
| 1170 | -document.getElementById('word-upload').addEventListener('change', async function(e) { | |
| 1222 | +addSafeEventListener('word-upload', 'change', async function(e) { | |
| 1171 | 1223 | const file = e.target.files[0]; |
| 1172 | 1224 | |
| 1173 | 1225 | if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') { |
| 1174 | 1226 | alert('Please select a valid Word document (.docx).'); |
| @@ -1401,21 +1453,24 @@ | ||
| 1401 | 1453 | document.addEventListener('DOMContentLoaded', function() { |
| 1402 | 1454 | checkInitialDocumentStatus(); |
| 1403 | 1455 | }); |
| 1404 | 1456 | |
| 1405 | -// Style all toolbar elements | |
| 1406 | 1457 | const toolbarElements = [ |
| 1407 | 1458 | '#mxchat-chatbot .toolbar-btn svg', |
| 1408 | 1459 | '#mxchat-chatbot .active-pdf-name', |
| 1409 | 1460 | '#mxchat-chatbot .active-word-name', |
| 1410 | 1461 | '#mxchat-chatbot .remove-pdf-btn svg', |
| 1411 | - '#mxchat-chatbot .remove-word-btn svg' | |
| 1462 | + '#mxchat-chatbot .remove-word-btn svg', | |
| 1463 | + '#mxchat-chatbot .toolbar-perplexity svg' | |
| 1412 | 1464 | ]; |
| 1413 | -$(toolbarElements.join(', ')).css({ | |
| 1414 | - 'fill': toolbarIconColor, | |
| 1415 | - 'color': toolbarIconColor | |
| 1465 | + | |
| 1466 | +toolbarElements.forEach(selector => { | |
| 1467 | + $(selector).css({ | |
| 1468 | + 'fill': toolbarIconColor, | |
| 1469 | + 'stroke': toolbarIconColor, | |
| 1470 | + 'color': toolbarIconColor | |
| 1471 | + }); | |
| 1416 | 1472 | }); |
| 1417 | - | |
| 1418 | 1473 | |
| 1419 | 1474 | |
| 1420 | 1475 | // Ensure essential elements are defined |
| 1421 | 1476 | const emailForm = document.getElementById('email-collection-form'); |