| @@ -261,28 +261,42 @@ | ||
| 261 | 261 | function formatMarkdownHeaders(text) { |
| 262 | 262 | // Handle h1 to h6 headers |
| 263 | 263 | return text.replace(/^(#{1,6})\s(.+)$/gm, function(match, hashes, content) { |
| 264 | 264 | const level = hashes.length; |
| 265 | - return `<h${level} class="chat-heading">${content}</h${level}>`; | |
| 265 | + // Apply the same color as the bot message font color | |
| 266 | + return `<h${level} class="chat-heading" style="color: inherit;">${content}</h${level}>`; | |
| 266 | 267 | }); |
| 267 | 268 | } |
| 268 | 269 | |
| 269 | 270 | // Update the linkify function to handle both URLs and markdown |
| 271 | +// Modified linkify function that only processes URLs in user content | |
| 270 | 272 | function linkify(inputText) { |
| 271 | - // First process markdown headers | |
| 273 | + if (!inputText) return ''; | |
| 274 | + | |
| 275 | + // Process markdown headers | |
| 272 | 276 | let processedText = formatMarkdownHeaders(inputText); |
| 273 | 277 | |
| 274 | - // Then process links as before | |
| 275 | - var markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g; | |
| 276 | - processedText = processedText.replace(markdownLinkPattern, '<a href="$2" target="' + linkTarget + '">$1</a>'); | |
| 278 | + // Process markdown links | |
| 279 | + const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g; | |
| 280 | + processedText = processedText.replace(markdownLinkPattern, (match, text, url) => { | |
| 281 | + const safeUrl = encodeURI(url); | |
| 282 | + const safeText = sanitizeUserInput(text); | |
| 283 | + return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`; | |
| 284 | + }); | |
| 277 | 285 | |
| 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>'); | |
| 286 | + // Process standalone URLs | |
| 287 | + const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim; | |
| 288 | + processedText = processedText.replace(urlPattern, (match, prefix, url) => { | |
| 289 | + const safeUrl = encodeURI(url); | |
| 290 | + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`; | |
| 291 | + }); | |
| 281 | 292 | |
| 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>'); | |
| 293 | + // Process www. URLs | |
| 294 | + const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim; | |
| 295 | + processedText = processedText.replace(wwwPattern, (match, prefix, url) => { | |
| 296 | + const safeUrl = encodeURI(`http://${url}`); | |
| 297 | + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`; | |
| 298 | + }); | |
| 285 | 299 | |
| 286 | 300 | return processedText; |
| 287 | 301 | } |
| 288 | 302 | |
| @@ -390,8 +404,20 @@ | ||
| 390 | 404 | if (response.data && response.data.filename) { |
| 391 | 405 | showActivePdf(response.data.filename); |
| 392 | 406 | activePdfFile = response.data.filename; |
| 393 | 407 | } |
| 408 | + | |
| 409 | + // Add redirect check here | |
| 410 | + if (response.redirect_url) { | |
| 411 | + let responseText = response.text || ''; | |
| 412 | + if (responseText) { | |
| 413 | + replaceLastMessage("bot", responseText); | |
| 414 | + } | |
| 415 | + setTimeout(() => { | |
| 416 | + window.location.href = response.redirect_url; | |
| 417 | + }, 1500); | |
| 418 | + return; | |
| 419 | + } | |
| 394 | 420 | |
| 395 | 421 | |
| 396 | 422 | // Check for live agent response |
| 397 | 423 | if (response.success && response.data && response.data.status === 'waiting_for_agent') { |
| @@ -442,11 +468,17 @@ | ||
| 442 | 468 | } |
| 443 | 469 | }); |
| 444 | 470 | } |
| 445 | 471 | |
| 472 | +// Sanitize only user input | |
| 473 | +function sanitizeUserInput(text) { | |
| 474 | + const div = document.createElement('div'); | |
| 475 | + div.textContent = text; | |
| 476 | + return div.innerHTML; | |
| 477 | +} | |
| 478 | + | |
| 479 | +// Modified appendMessage function that only sanitizes user content | |
| 446 | 480 | function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) { |
| 447 | - //console.log("Appending message. Sender:", sender, "Content:", messageText); | |
| 448 | - | |
| 449 | 481 | try { |
| 450 | 482 | // Determine styles based on sender type |
| 451 | 483 | let messageClass, bgColor, fontColor; |
| 452 | 484 | |
| @@ -453,42 +485,52 @@ | ||
| 453 | 485 | if (sender === "user") { |
| 454 | 486 | messageClass = "user-message"; |
| 455 | 487 | bgColor = userMessageBgColor; |
| 456 | 488 | fontColor = userMessageFontColor; |
| 457 | - } else if (sender === "agent") { | |
| 458 | - messageClass = "agent-message"; | |
| 459 | - bgColor = liveAgentMessageBgColor; | |
| 460 | - fontColor = liveAgentMessageFontColor; | |
| 461 | - } else { | |
| 489 | + // Only sanitize user input | |
| 490 | + messageText = sanitizeUserInput(messageText); | |
| 491 | + } else if (sender === "agent") { | |
| 492 | + messageClass = "agent-message"; | |
| 493 | + bgColor = liveAgentMessageBgColor; | |
| 494 | + fontColor = liveAgentMessageFontColor; | |
| 495 | + } else { | |
| 462 | 496 | messageClass = "bot-message"; |
| 463 | 497 | bgColor = botMessageBgColor; |
| 464 | 498 | fontColor = botMessageFontColor; |
| 465 | 499 | } |
| 466 | 500 | |
| 467 | - const messageDiv = $('<div>') | |
| 468 | - .addClass(messageClass) | |
| 469 | - .css({ | |
| 470 | - 'background': bgColor, | |
| 471 | - 'color': fontColor, | |
| 472 | - }); | |
| 501 | + const messageDiv = $('<div>') | |
| 502 | + .addClass(messageClass) | |
| 503 | + .css({ | |
| 504 | + 'background': bgColor, | |
| 505 | + 'color': fontColor, | |
| 506 | + 'margin-bottom': '1em' | |
| 507 | + }); | |
| 473 | 508 | |
| 474 | - // Add CSS for paragraphs | |
| 475 | - messageDiv.css({ | |
| 476 | - 'margin-bottom': '1em' | |
| 477 | - }); | |
| 509 | + // Process the message content based on sender | |
| 510 | + let fullMessage; | |
| 511 | + if (sender === "user") { | |
| 512 | + // For user messages, apply linkify after sanitization | |
| 513 | + fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText)))); | |
| 514 | + } else { | |
| 515 | + // For bot/agent messages, preserve HTML | |
| 516 | + fullMessage = messageText; | |
| 517 | + } | |
| 478 | 518 | |
| 479 | - // Format and process the message content | |
| 480 | - let fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText)))); | |
| 481 | - | |
| 482 | 519 | // Add images if provided |
| 483 | 520 | if (images && images.length > 0) { |
| 484 | 521 | fullMessage += '<div class="image-gallery">'; |
| 485 | 522 | images.forEach(img => { |
| 523 | + // Ensure image URLs and titles are properly escaped | |
| 524 | + const safeTitle = sanitizeUserInput(img.title); | |
| 525 | + const safeUrl = encodeURI(img.image_url); | |
| 526 | + const safeThumbnail = encodeURI(img.thumbnail_url); | |
| 527 | + | |
| 486 | 528 | fullMessage += ` |
| 487 | 529 | <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;" /> | |
| 530 | + <strong>${safeTitle}</strong><br> | |
| 531 | + <a href="${safeUrl}" target="_blank"> | |
| 532 | + <img src="${safeThumbnail}" alt="${safeTitle}" style="max-width: 100px; height: auto; margin: 5px;" /> | |
| 491 | 533 | </a> |
| 492 | 534 | </div>`; |
| 493 | 535 | }); |
| 494 | 536 | fullMessage += '</div>'; |
| @@ -494,23 +536,20 @@ | ||
| 494 | 536 | fullMessage += '</div>'; |
| 495 | 537 | } |
| 496 | 538 | |
| 497 | 539 | // Append HTML content if provided |
| 498 | - if (messageHtml) { | |
| 540 | + if (messageHtml && sender !== "user") { | |
| 499 | 541 | fullMessage += '<br><br>' + messageHtml; |
| 500 | 542 | } |
| 501 | 543 | |
| 502 | 544 | messageDiv.html(fullMessage); |
| 503 | 545 | |
| 504 | - // Add a class for temporary messages if needed | |
| 505 | 546 | if (isTemporary) { |
| 506 | 547 | messageDiv.addClass('temporary-message'); |
| 507 | 548 | } |
| 508 | 549 | |
| 509 | - // Append the message to the chat box | |
| 510 | - messageDiv.hide().appendTo('#chat-box').fadeIn(300, function () { | |
| 550 | + messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() { | |
| 511 | 551 | if (sender === "bot") { |
| 512 | - // After bot's message is displayed, scroll the last user message to the top | |
| 513 | 552 | const lastUserMessage = $('#chat-box').find('.user-message').last(); |
| 514 | 553 | if (lastUserMessage.length) { |
| 515 | 554 | scrollElementToTop(lastUserMessage); |
| 516 | 555 | } |
| @@ -517,13 +556,13 @@ | ||
| 517 | 556 | } |
| 518 | 557 | }); |
| 519 | 558 | |
| 520 | 559 | if (messageText.id) { |
| 521 | - lastSeenMessageId = messageText.id; | |
| 522 | - hideNotification(); | |
| 523 | - } | |
| 560 | + lastSeenMessageId = messageText.id; | |
| 561 | + hideNotification(); | |
| 562 | + } | |
| 524 | 563 | } catch (error) { |
| 525 | - console.error("Error rendering message with images:", error); | |
| 564 | + console.error("Error rendering message:", error); | |
| 526 | 565 | } |
| 527 | 566 | } |
| 528 | 567 | |
| 529 | 568 | |
| @@ -1091,10 +1130,18 @@ | ||
| 1091 | 1130 | document.getElementById('word-upload').click(); |
| 1092 | 1131 | }); |
| 1093 | 1132 | } |
| 1094 | 1133 | |
| 1134 | +function addSafeEventListener(elementId, eventType, handler) { | |
| 1135 | + const element = document.getElementById(elementId); | |
| 1136 | + if (element) { | |
| 1137 | + element.addEventListener(eventType, handler); | |
| 1138 | + } | |
| 1139 | +} | |
| 1140 | + | |
| 1141 | + | |
| 1095 | 1142 | // PDF file input change handler |
| 1096 | -document.getElementById('pdf-upload').addEventListener('change', async function(e) { | |
| 1143 | +addSafeEventListener('pdf-upload', 'change', async function(e) { | |
| 1097 | 1144 | const file = e.target.files[0]; |
| 1098 | 1145 | |
| 1099 | 1146 | if (!file || file.type !== 'application/pdf') { |
| 1100 | 1147 | alert('Please select a valid PDF file.'); |
| @@ -1166,9 +1213,9 @@ | ||
| 1166 | 1213 | } |
| 1167 | 1214 | }); |
| 1168 | 1215 | |
| 1169 | 1216 | // Word file input change handler |
| 1170 | -document.getElementById('word-upload').addEventListener('change', async function(e) { | |
| 1217 | +addSafeEventListener('word-upload', 'change', async function(e) { | |
| 1171 | 1218 | const file = e.target.files[0]; |
| 1172 | 1219 | |
| 1173 | 1220 | if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') { |
| 1174 | 1221 | alert('Please select a valid Word document (.docx).'); |
| @@ -1401,21 +1448,24 @@ | ||
| 1401 | 1448 | document.addEventListener('DOMContentLoaded', function() { |
| 1402 | 1449 | checkInitialDocumentStatus(); |
| 1403 | 1450 | }); |
| 1404 | 1451 | |
| 1405 | -// Style all toolbar elements | |
| 1406 | 1452 | const toolbarElements = [ |
| 1407 | 1453 | '#mxchat-chatbot .toolbar-btn svg', |
| 1408 | 1454 | '#mxchat-chatbot .active-pdf-name', |
| 1409 | 1455 | '#mxchat-chatbot .active-word-name', |
| 1410 | 1456 | '#mxchat-chatbot .remove-pdf-btn svg', |
| 1411 | - '#mxchat-chatbot .remove-word-btn svg' | |
| 1457 | + '#mxchat-chatbot .remove-word-btn svg', | |
| 1458 | + '#mxchat-chatbot .toolbar-perplexity svg' | |
| 1412 | 1459 | ]; |
| 1413 | -$(toolbarElements.join(', ')).css({ | |
| 1414 | - 'fill': toolbarIconColor, | |
| 1415 | - 'color': toolbarIconColor | |
| 1460 | + | |
| 1461 | +toolbarElements.forEach(selector => { | |
| 1462 | + $(selector).css({ | |
| 1463 | + 'fill': toolbarIconColor, | |
| 1464 | + 'stroke': toolbarIconColor, | |
| 1465 | + 'color': toolbarIconColor | |
| 1466 | + }); | |
| 1416 | 1467 | }); |
| 1417 | - | |
| 1418 | 1468 | |
| 1419 | 1469 | |
| 1420 | 1470 | // Ensure essential elements are defined |
| 1421 | 1471 | const emailForm = document.getElementById('email-collection-form'); |