1function linkify(inputText) { 2 var markdownLinkPattern = /\[([^\]]+)]\((http[s]?:\/\/[^\s]+)\)/g; 3 var replacedText = inputText.replace(markdownLinkPattern, '<a href="$2" target="_blank">$1</a>'); 4 var urlPattern = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])(?![^<]*>|[^<>]*<\/a>)/gim; 5 replacedText = replacedText.replace(urlPattern, '<a href="$1" target="_blank">$1</a>'); 6 var pseudoUrlPattern = /(^|[^\/])(www\.[\S]+(\b|$))(?![^<]*>|[^<>]*<\/a>)/gim; 7 replacedText = replacedText.replace(pseudoUrlPattern, '$1<a href="http://$2" target="_blank">$2</a>'); 8 var emailAddressPattern = /(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,6})(?![^<]*>|[^<>]*<\/a>)/gim; 9 replacedText = replacedText.replace(emailAddressPattern, '<a href="mailto:$1">$1</a>'); 10 var phoneNumberPattern = /(\+1|1)?(?:\W*\d{3}\W*)\d{3}\W*\d{4}(?![^<]*>|[^<>]*<\/a>)/gim; 11 replacedText = replacedText.replace(phoneNumberPattern, '<a href="tel:$&">$&</a>'); 12 13 return replacedText; 14} 15 16 17 18// Function to sanitize HTML input 19function sanitizeHtml(str) { 20 var temp = document.createElement('div'); 21 temp.textContent = str; 22 return temp.innerHTML; 23} 24 25jQuery(document).ready(function($) { 26 var chatToggleSelector = '#AI24AI-chatbot-toggle'; 27 28 var chatContainerSelector = '#AI24AI-chatbot-container'; 29 var chatInputSelector = '#chat-input'; 30 var chatMessagesSelector = '#chat-messages'; 31 var chatMinimizeSelector = '#chatbox-minimize'; 32 var chatCloseSelector = '#chatbox-close'; 33 var confirmExitSelector = '#confirm-exit'; 34 var cancelExitSelector = '#cancel-exit'; 35 var isTransitioning = false; 36 var isRequestPending = false; 37 var typingAnimation; 38 var typingIndicator; 39 var userOpenedChat = false; 40 var typingTimeouts = []; 41 var welcomeMessageInProgress = false 42 var displayMessagesInstantly = false; 43 var currentMessageIndex = 0; 44 var baseHeight = 50; 45 46 /*RadicalK */ 47 // Ensure BroadcastChannel is initialized at the top of AI24AI-script.js 48 const channel = new BroadcastChannel('ai24_chat'); 49 50 // Handle incoming messages, including CLOSE_UI, to reset the UI when needed 51 channel.onmessage = function(event) { 52 if (event.data.type === 'CLOSE_UI') { 53 54 // Clear chat container and reset toggle 55 $(chatContainerSelector).removeClass('active').hide(); 56 $(chatToggleSelector).removeClass('hide-toggle').addClass('toggle-visible').css({ 57 'opacity': '1', 58 'pointer-events': 'auto' 59 }); 60 61 // Fully clear local storage and session storage 62 localStorage.removeItem('AI24AI_chatState'); 63 sessionStorage.removeItem('initialGreetingDisplayed'); 64 localStorage.removeItem('AI24AI_chatThreadId'); 65 } 66 }; 67 /*RadicalK here ends */ 68 69 // Function to fix input bar sizing 70 $('#chat-input').on('input', function() { 71 var baseHeight = 50; // This should be the height of the input field for one line of text, including padding. 72 this.style.height = baseHeight + 'px'; // Reset to base height before calculating scrollHeight 73 var newHeight = this.scrollHeight > baseHeight ? this.scrollHeight : baseHeight; 74 this.style.height = newHeight + 'px'; // Set the new height 75 }).on('keydown', function(e) { 76 if (e.keyCode === 13 && e.shiftKey) { // Shift+Enter is pressed 77 e.preventDefault(); 78 var start = this.selectionStart; 79 var end = this.selectionEnd; 80 81 // Insert the new line at the current cursor position 82 this.value = this.value.substring(0, start) + '\n' + this.value.substring(end); 83 84 // Move the cursor to the new position 85 this.selectionStart = this.selectionEnd = start + 1; 86 87 // Trigger the input event to resize the textarea 88 $(this).trigger('input'); 89 } 90 }).on('keyup', function(e) { 91 if (e.keyCode == 13 && !e.shiftKey) { // Enter key is pressed without Shift or input is clear 92 this.style.height = baseHeight + 'px'; // Reset to initial height 93 } 94 }); 95 96 97 /**Radical Koala */ 98 function resetChatCompletely() { 99 // function for handling chat reset when the user prompts to close the chat - IMPORTANT for cross tab modification. 100 const vaImage = AI24AI_params?.image || ''; 101 const vaName = AI24AI_params?.name || ''; 102 const vaDescription = AI24AI_params?.description || ''; 103 104 // 1. Clear all storage 105 localStorage.removeItem('AI24AI_chatState');
106 localStorage.removeItem('AI24AI_chatThreadId'); 107 localStorage.removeItem('initialGreetingDisplayed'); 108 sessionStorage.removeItem('initialGreetingDisplayed'); 109 sessionStorage.removeItem('AI24AI_chatThreadId'); 110 111 // 2. Broadcast reset to other tabs 112 channel.postMessage({ 113 type: 'CHAT_RESET', 114 timestamp: new Date().toISOString() 115 }); 116 117 // 3. Reset all flags 118 welcomeMessageInProgress = false; 119 displayMessagesInstantly = false; 120 userOpenedChat = false; 121 currentMessageIndex = 0; 122 123 // 4. Clear UI 124 clearInitialMessageTimeouts(); 125 hideTypingIndicator(); 126 127 // 5. Reset chat container to initial state 128 $('#chat-messages').html(` 129 <div class="va-info-section"> 130 <div class="va-top-info"> 131 ${vaImage ? `<img src="${vaImage}" alt="${vaName}" class="va-image">` : ''} 132 <div class="va-name">${vaName}</div> 133 ${vaDescription ? `<div class="va-description">${vaDescription}</div>` : ''} 134 <div class="va-info-separator"></div> 135 </div> 136 </div> 137 <div class="messages-content"></div> 138 `); 139 140 } 141 142 /**Radical Koala */ 143 144 145 // Function to move the typing indicator to the bottom of the chat 146 function moveTypingIndicatorToBottom() { 147 if (typingIndicator) { 148 typingIndicator.detach(); // Detach the typing indicator from its current location 149 $(chatMessagesSelector).append(typingIndicator); // Re-append it to move it to the bottom 150 } 151 } 152 153 // Function to create the typing indicator element at the bottom 154 function createTypingIndicator() { 155 if (typingIndicator) { 156 typingIndicator.remove(); // Remove any existing indicator 157 } 158 159 // Create a container for the typing indicator without message styles 160 typingIndicator = $('<div class="typing-indicator">' + 161 '<div id="typing-animation" class="typing-animation-style"></div>' + 162 '</div>'); 163 164 // Append it to the chat messages container 165 $(chatMessagesSelector).append(typingIndicator); 166 167 // Initialize the Lottie animation within this container 168 typingAnimation = lottie.loadAnimation({ 169 container: document.getElementById('typing-animation'), // Directly target the new div 170 renderer: 'svg', 171 loop: true, 172 autoplay: false, 173 path: AI24AI_params.typingAnimationPath // Use the localized path 174 }); 175 176 moveTypingIndicatorToBottom(); // Move the indicator to the bottom after creation 177 } 178 179 // Call createTypingIndicator on script load to ensure it's ready when needed 180 createTypingIndicator(); 181 182 // Function to show the typing indicator 183 function showTypingIndicator() { 184 if (!typingIndicator) { 185 createTypingIndicator(); 186 } 187 typingIndicator.show(); 188 typingAnimation.play(); 189 moveTypingIndicatorToBottom(); // Ensure it is at the bottom every time it is shown 190 // Trigger a scroll after a slight delay to account for the space the indicator will occupy 191 setTimeout(function() { 192 $(chatMessagesSelector).scrollTop($(chatMessagesSelector)[0].scrollHeight); 193 }, 100); // The delay can be adjusted if necessary 194 } 195 196 // Function to hide the typing indicator 197 function hideTypingIndicator() { 198 if (typingIndicator) { 199 typingIndicator.hide(); 200 typingAnimation.stop(); 201 } 202 } 203 204 // Initially hide the typing indicator until needed 205 hideTypingIndicator(); 206 207 //Initial Greeting 208 function displayInitialGreeting() { 209 /*RadicalK changed this ---> */ 210 211 // Check localStorage instead of sessionStorage 212 if (localStorage.getItem('initialGreetingDisplayed')) { 213 return; 214 } 215 216 localStorage.setItem('initialGreetingDisplayed', 'true'); 217 welcomeMessageInProgress = true; 218 /*RadicalK changed this ---> */ 219 220 sessionStorage.setItem('initialGreetingDisplayed', 'true'); // Flag the session right away 221 welcomeMessageInProgress = true; 222 223 // Function to display a single message with typing animation 224 function displayMessageWithTypingAnimation(messages, index = 0) { 225 if (displayMessagesInstantly) { 226 displayAllRemainingMessages(messages); 227 return; // Exit the function as we're done displaying messages. 228 } 229 if (index < messages.length) { 230 showTypingIndicator(); 231 232 // Clear any previous timeouts for safety
233 typingTimeouts.forEach(clearTimeout); 234 235 // Create a new timeout for the typing indicator 236 var timeoutId = setTimeout(function() { 237 hideTypingIndicator(); 238 var message = messages[index]; 239 var currentTime = new Date(); 240 var messageTimestamp = currentTime.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); 241 242 // Append message to chat area 243 $(chatMessagesSelector).append('<div class="chat-message assistant-message fade-in">' + 244 message + 245 '<span class="chat-timestamp-assistant">' + messageTimestamp + '</span>' + 246 '</div>'); 247 $(chatMessagesSelector).scrollTop($(chatMessagesSelector)[0].scrollHeight); 248 249 currentMessageIndex = index + 1; 250 displayMessageWithTypingAnimation(messages, index + 1); 251 }, 1600); // Adjust this delay to manage the typing speed 252 253 // Store the timeoutId to clear later if needed 254 typingTimeouts.push(timeoutId); 255 } else { 256 welcomeMessageInProgress = false; // Sequence complete, allow re-entry if session cleared 257 // Clear all timeouts since the animation sequence is complete 258 typingTimeouts.forEach(clearTimeout); 259 typingTimeouts = []; // Clear the array for next use 260 } 261 } 262 263 // Start the message display process after an initial delay 264 var initialDelayId = setTimeout(() => { 265 displayMessageWithTypingAnimation(AI24AI_params.contextMessages); 266 }, 750); // Adjust this initial delay as needed 267 268 typingTimeouts.push(initialDelayId); // Store the initial delay timeoutId as well 269 } 270 271 // Function to hide the toggle button when the chatbox is active, specifically for the sidebar option 272 function handleSidebarToggle() { 273 var widgetPosition = 'sidebar'; // Replace this with the actual condition to determine if the sidebar option is active 274 var $chatToggle = $(chatToggleSelector); 275 var $chatContainer = $(chatContainerSelector); 276 277 if (widgetPosition === 'sidebar' && $chatContainer.hasClass('active')) { 278 $chatToggle.addClass('hide-toggle'); // Hides the toggle button 279 } else { 280 $chatToggle.removeClass('hide-toggle'); // Shows the toggle button 281 } 282 } 283 284 // Function to toggle chat 285 // function toggleChatWidget() { 286 // if (isTransitioning) return; 287 // isTransitioning = true; 288 289 // var $chatContainer = $(chatContainerSelector); 290 // var $chatToggle = $(chatToggleSelector); 291 292 // // Check if we are on a mobile device 293 // var isMobile = window.matchMedia("only screen and (max-width: 540px)").matches; 294 295 // if ($chatContainer.hasClass('active')) { 296 // // If chat is currently active, hide it 297 // $chatContainer.removeClass('active').one('transitionend', function() { 298 // $(this).hide(); 299 // isTransitioning = false; 300 // if (isMobile) { 301 // $chatToggle.addClass('toggle-visible').removeClass('hide-toggle'); // Make toggle reappear smoothly on mobile when chat is closed 302 // $chatToggle.css('pointer-events', 'auto'); // Enable pointer events 303 // } else { 304 // // Handle the toggle for the sidebar when it's not a mobile device 305 // handleSidebarToggle(); // Call to hide or show the sidebar toggle 306 // } 307 // $chatToggle.prop('disabled', false); 308 // }); 309 // } else { 310 // // If chat is not active, show it 311 // userOpenedChat = true; 312 // $chatContainer.show(0, function() { 313 // $(this).addClass('active'); 314 // isTransitioning = false;
315 // if (isMobile) { 316 // $chatToggle.addClass('hide-toggle').removeClass('toggle-visible'); // Hide the toggle button when chat is opened on mobile 317 // $chatToggle.css('pointer-events', 'none'); // Disable pointer events 318 // } else { 319 // // Handle the toggle for the sidebar when it's not a mobile device 320 // handleSidebarToggle(); // Call to hide or show the sidebar toggle 321 // } 322 // $chatToggle.prop('disabled', false); 323 // if (!sessionStorage.getItem('initialGreetingDisplayed')) { 324 // displayInitialGreeting(); 325 // } 326 327 // // Focus the input field immediately 328 // // $(chatInputSelector).focus(); 329 // }); 330 // } 331 // } 332 333 /** WORKING FUNCTION IS BEING UPDATED::::::::::::::: */ 334 function toggleChatWidget() { 335 if (isTransitioning) return; 336 isTransitioning = true; 337 //radical 338 // check chat state 339 if (localStorage.getItem('AI24AI_chatFullyClosed') === 'true') { 340 // close chat fully 341 resetChatCompletely(); 342 localStorage.removeItem('AI24AI_chatFullyClosed'); 343 } 344 //radical 345 346 var $chatContainer = $(chatContainerSelector); 347 var $chatToggle = $(chatToggleSelector); 348 349 // Check if we are on a mobile device 350 var isMobile = window.matchMedia("only screen and (max-width: 540px)").matches; 351 352 if ($chatContainer.hasClass('active')) { 353 // If chat is currently active, hide it 354 $chatContainer.removeClass('active').one('transitionend', function() { 355 $(this).hide(); 356 isTransitioning = false; 357 if (isMobile) { 358 $chatToggle.addClass('toggle-visible').removeClass('hide-toggle'); 359 $chatToggle.css('pointer-events', 'auto'); 360 } else { 361 handleSidebarToggle(); 362 } 363 $chatToggle.prop('disabled', false); 364 }); 365 } else { 366 // If chat is not active, show it 367 userOpenedChat = true; 368 $chatContainer.show(0, function() { 369 $(this).addClass('active'); 370 isTransitioning = false; 371 if (isMobile) { 372 $chatToggle.addClass('hide-toggle').removeClass('toggle-visible'); 373 $chatToggle.css('pointer-events', 'none'); 374 } else { 375 handleSidebarToggle(); 376 } 377 $chatToggle.prop('disabled', false); 378 if (!sessionStorage.getItem('initialGreetingDisplayed')) { 379 displayInitialGreeting(); 380 } 381 }); 382 } 383 } 384 $(chatToggleSelector).on('click', toggleChatWidget); 385 386 387 function displayAllRemainingMessages(messages) { 388 if (currentMessageIndex < messages.length) { 389 messages.slice(currentMessageIndex).forEach(function(message) { 390 var currentTime = new Date(); 391 var messageTimestamp = currentTime.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); 392 393 // Append the remaining messages to ensure they are in the correct position 394 $(chatMessagesSelector).append('<div class="chat-message assistant-message fade-in">' + 395 message + 396 '<span class="chat-timestamp-assistant">' + messageTimestamp + '</span>' + 397 '</div>'); 398 }); 399 400 // After displaying all remaining messages, update the currentMessageIndex 401 currentMessageIndex = messages.length; 402 403 // Reset the flag to indicate the welcome message sequence is complete 404 welcomeMessageInProgress = false; 405 406 // Scroll to the newest message 407 $(chatMessagesSelector).scrollTop($(chatMessagesSelector)[0].scrollHeight); 408 } 409 } 410 411 // Minimize chat functionality 412 $(chatMinimizeSelector).on('click', function() { 413 if (isTransitioning) return; 414 isTransitioning = true; 415 416 // Toggle the chat container's visibility. 417 $(chatContainerSelector).toggleClass('active'); 418 419 if (window.matchMedia("(max-width: 768px)").matches) { 420 // Mobile-specific behavior 421 if ($(chatContainerSelector).hasClass('active')) { 422 $(chatToggleSelector).addClass('hide-toggle').removeClass('toggle-visible'); 423 } else { 424 $(chatToggleSelector).removeClass('hide-toggle').addClass('toggle-visible'); 425 // Ensure it is clickable again 426 $(chatToggleSelector).css('pointer-events', 'auto'); 427 } 428 } else { 429 // Non-mobile behavior 430 handleSidebarToggle(); // Update the toggle button visibility for sidebar 431 } 432 433 // Listen for the end of the transition to reset the isTransitioning flag. 434 $(chatContainerSelector).one('transitionend', function() { 435 isTransitioning = false;
436 }); 437 }); 438 439 // Close button functionality 440 $(chatCloseSelector).on('click', function() { 441 $('#exit-confirmation-modal').addClass('active'); // Show the modal 442 443 444 }); 445 446 // Cancel exit functionality 447 $(cancelExitSelector).on('click', function() { 448 $('#exit-confirmation-modal').removeClass('active'); // Hide the modal 449 450 }); 451 452 function clearInitialMessageTimeouts() { 453 typingTimeouts.forEach(clearTimeout); // Clear timeouts for pending messages 454 typingTimeouts = []; 455 welcomeMessageInProgress = false; 456 } 457 458 // Function to confirm exit 459 $(confirmExitSelector).on('click', function() { 460 461 // Send pause and close UI messages 462 channel.postMessage({ 463 type: 'PAUSE_BROADCAST' 464 }); 465 channel.postMessage({ 466 type: 'CLOSE_UI' 467 }); 468 469 // Step 1: Broadcast close event across tabs 470 // localStorage.setItem('AI24AI_chatClosed', new Date().toISOString()); 471 472 // // Broadcast a definitive close event 473 // const closeEvent = new CustomEvent('chatFullClose', { 474 // detail: { 475 // timestamp: new Date().toISOString(), 476 // action: 'FORCE_CLOSE' 477 // } 478 // }); 479 // document.dispatchEvent(closeEvent); 480 localStorage.setItem('AI24AI_chatFullyClosed', 'true'); //add chat fully closed 481 // cklear chat state 482 localStorage.removeItem('AI24AI_chatState'); 483 sessionStorage.removeItem('initialGreetingDisplayed'); // Allow initial greetings to re-trigger if chat is reopened 484 localStorage.removeItem('initialGreetingDisplayed'); // Allow initial greetings to re-trigger if chat is reopened 485 localStorage.removeItem('AI24AI_chatThreadId'); 486 487 488 489 490 // Try more thorough clearing - necessary to clear html within the chat 491 $('#chat-messages').html(` 492 <div class="va-info-section"> 493 <div class="va-top-info"> 494 ${$('.va-info-section .va-top-info').html()} 495 </div> 496 </div> 497 <div class="messages-content"></div> 498 `); 499 500 501 if (isTransitioning) return; 502 isTransitioning = true; 503 clearThreadId(); 504 // Clear timeouts and messages 505 clearInitialMessageTimeouts(); // Assuming this now also removes messages as per your update 506 // Additional steps to reset the chat state fully 507 hideTypingIndicator(); // Ensure typing indicator is hidden 508 509 welcomeMessageInProgress = false; // Reset this flag 510 displayMessagesInstantly = false; // Reset this if you're using it to control message display logic 511 userOpenedChat = false; 512 513 $('.messages-content').empty(); 514 $('#chat-messages .chat-message').remove(); 515 516 517 518 519 // Handle UI transitions 520 $(chatContainerSelector).removeClass('active').one('transitionend', function() { 521 $(this).hide(); 522 isTransitioning = false; 523 524 if (window.matchMedia("(max-width: 768px)").matches) { 525 setTimeout(function() { 526 $(chatToggleSelector).removeClass('hide-toggle').addClass('toggle-visible').css({ 527 'opacity': '1', 528 'pointer-events': 'auto' 529 }); 530 }, 20); 531 } else { 532 // Call handleSidebarToggle to slide the toggle back into view 533 handleSidebarToggle(); // Update the toggle button visibility for sidebar 534 } 535 }); 536 $('#exit-confirmation-modal').removeClass('active'); 537 $(chatToggleSelector).prop('disabled', false); 538 }); 539 540 /*ATHARVA CHANGED THIS:: */ 541 542 function storeThreadId(threadId) { 543 // Store in both session and local storage 544 sessionStorage.setItem('AI24AI_chatThreadId', threadId); 545 localStorage.setItem('AI24AI_chatThreadId', threadId); 546 } 547 548 function retrieveThreadId() { 549 // Check session first, then local
550 return sessionStorage.getItem('AI24AI_chatThreadId') || 551 localStorage.getItem('AI24AI_chatThreadId') || 552 ''; 553 } 554 555 /*ATHARVA CHANGED THIS:: */ 556 557function clearThreadId() { 558 sessionStorage.removeItem('AI24AI_chatThreadId'); 559} 560 561// Event listener for the Enter key press in the chat input 562$(document).ready(function() { 563 function sendMessage(user_input, inputElement) { 564 if (user_input !== '') { 565 if (welcomeMessageInProgress) { 566 clearInitialMessageTimeouts(); 567 displayAllRemainingMessages(AI24AI_params.contextMessages); 568 } 569 570 inputElement.style.height = '50px'; 571 $(inputElement).val(''); 572 573 var currentTime = new Date(); 574 var userTimestamp = currentTime.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); 575 576 // Sanitize and convert newlines to <br> for HTML rendering 577 var sanitizedInput = sanitizeHtml(user_input).replace(/\n/g, '<br>'); 578 var formattedMessage = linkify(sanitizedInput); 579 580 // Append user message and timestamp in the same div 581 var messageHtml = '<div class="chat-message user-message fade-in">' + 582 formattedMessage + // The actual message content 583 '<span class="chat-timestamp-user">' + userTimestamp + '</span>' + // The timestamp 584 '</div>'; 585 586 // Append the combined message and timestamp to the chat messages 587 $(chatMessagesSelector).append(messageHtml); 588 $(chatMessagesSelector).scrollTop($(chatMessagesSelector)[0].scrollHeight); 589 $(this).val(''); 590 591 isRequestPending = true; 592 593 // console.log("Nonce being sent in AJAX request:", AI24AI_params.nonce); 594 595 var ajaxData = { 596 action: 'AI24AI_chat', 597 nonce: AI24AI_params.nonce, 598 user_input: user_input, 599 start_new_thread: !retrieveThreadId() 600 }; 601 602 if (retrieveThreadId()) { 603 ajaxData.thread_id = retrieveThreadId(); 604 } 605 606 $.ajax({ 607 url: AI24AI_params.ajax_url, 608 type: 'POST', 609 dataType: 'json', 610 data: ajaxData, 611 beforeSend: function() { 612 showTypingIndicator(); 613 }, 614 success: function(response) { 615 if (response.success) { 616 var assistantMessages = response.data.messages.filter(function(message) { 617 return message.role === 'assistant'; 618 }); 619 620 // Check if there are any assistant messages 621 if (assistantMessages.length > 0) { 622 var latestAssistantMessage = assistantMessages[0]; 623 var messageContentArray = latestAssistantMessage.content || latestAssistantMessage.clean_content; 624 if (Array.isArray(messageContentArray) && messageContentArray.length > 0) { 625 var messageText = messageContentArray[0]['text'] && messageContentArray[0]['text']['value'] ? messageContentArray[0]['text']['value'] : 'Message content not available'; 626 } else { 627 console.log("No content available or content structure is incorrect", messageContentArray); 628 } 629 630 // Convert links in the message content 631 messageText = linkify(messageText); 632 633 var currentTime = new Date(); 634 var assistantTimestamp = currentTime.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }); 635 636 // Ensure HTML content is rendered correctly 637 var assistantMessageHtml = '<div class="chat-message assistant-message fade-in">' + 638 '<span class="message-content">' + messageText + '</span>' + 639 '<span class="chat-timestamp-assistant">' + assistantTimestamp + '</span>' + 640 '</div>'; 641 $(chatMessagesSelector).append(assistantMessageHtml); 642 $(chatMessagesSelector).scrollTop($(chatMessagesSelector)[0].scrollHeight); 643 } 644 645 // Store the thread ID if it's provided in the response 646 if (response.data.thread_id) {
647 storeThreadId(response.data.thread_id); 648 } 649 } else { 650 // Log removed: console.error('Error:', response.data.error); 651 } 652 }, 653 error: function(xhr, status, error) { 654 // Log removed: console.error('AJAX error:', status, error); 655 }, 656 complete: function() { 657 hideTypingIndicator(); 658 isRequestPending = false; 659 $(chatInputSelector).focus(); // Make sure to remove focus from the input field 660 661 // Scroll adjustment to encourage the browser to reset zoom 662 setTimeout(function() { 663 if(window.scrollY != 0) { 664 window.scrollTo({top: window.scrollY - 1}); 665 window.scrollTo({top: window.scrollY + 1}); 666 } 667 }, 50); 668 669 // Ensure focus is maintained after assistant message is appended 670 setTimeout(function() { 671 $(chatInputSelector).focus(); 672 }, 100); 673 } 674 }); 675 } 676 } 677 678 // Event listener for the Enter key press in the chat input 679 $(chatInputSelector).on('keypress', function(e) { 680 if (e.which == 13 && e.shiftKey) { 681 // Allow shift+enter to insert a newline 682 var cursorPos = this.selectionStart; 683 var value = $(this).val(); 684 $(this).val(value.substring(0, cursorPos) + "\n" + value.substring(cursorPos)); 685 this.selectionStart = this.selectionEnd = cursorPos + 1; // Move cursor to the next line 686 e.stopPropagation(); // Prevent the default action 687 } else if (e.which == 13 && !e.shiftKey && !isRequestPending) { 688 e.preventDefault(); // Prevent the default action (new line) 689 var user_input = $(this).val().trim(); 690 sendMessage(user_input, this); // Use 'this' to reference the input element 691 } 692 }); 693 694 $('#send-button').on('click touchstart', function(event) { 695 event.preventDefault(); // Prevent native handling of touch events that might cause issues. 696 if (!isRequestPending) { 697 var $inputElement = $(chatInputSelector); 698 var user_input = $inputElement.val().trim(); 699 if (user_input !== '') { 700 sendMessage(user_input, $inputElement[0]); // Use the existing sendMessage function 701 } 702 } 703 }); 704 }); 705});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.