Improve performance of printMessages (#4979) * Improve performance of printMessages * Add clarifying comment * Refactor printMessages into redisplayChat. * const https://github.com/SillyTavern/SillyTavern/pull/4981#discussion_r2675632422 * Use `toFixed(3)` instead of 17 decimals. https://github.com/SillyTavern/SillyTavern/pull/4981#discussion_r2675647691 * Removed `.find is faster than .children.` comment. https://github.com/SillyTavern/SillyTavern/pull/4981#discussion_r2675625188 * Simplified add 'last_mes'. https://github.com/SillyTavern/SillyTavern/pull/4981#discussion_r2675616734 --------- Co-authored-by: user <user@exmaple.com>
Signed| @@ -8,6 +8,7 @@ import { | |||
| 8 | Popper, | 8 | Popper, |
| 9 | initLibraryShims, | 9 | initLibraryShims, |
| 10 | default as libs, | 10 | default as libs, |
| 11 | lodash, | ||
| 11 | } from './lib.js'; | 12 | } from './lib.js'; |
| 12 | 13 | ||
| 13 | import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods } from './scripts/RossAscends-mods.js'; | 14 | import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods } from './scripts/RossAscends-mods.js'; |
| @@ -1422,19 +1423,54 @@ export async function printMessages() { | |||
| 1422 | chatElement.append('<div id="show_more_messages">Show more messages</div>'); | 1423 | chatElement.append('<div id="show_more_messages">Show more messages</div>'); |
| 1423 | } | 1424 | } |
| 1424 | 1425 | ||
| 1425 | for (let i = startIndex; i < chat.length; i++) { | 1426 | await redisplayChat({ startIndex, fade: false }); |
| 1426 | const item = chat[i]; | ||
| 1427 | addOneMessage(item, { scroll: false, forceId: i, showSwipes: false }); | ||
| 1428 | } | ||
| 1429 | 1427 | ||
| 1430 | chatElement.find('.mes').removeClass('last_mes'); | ||
| 1431 | chatElement.find('.mes').last().addClass('last_mes'); | ||
| 1432 | refreshSwipeButtons(false, false); | ||
| 1433 | applyStylePins(); | ||
| 1434 | scrollChatToBottom({ waitForFrame: true }); | 1428 | scrollChatToBottom({ waitForFrame: true }); |
| 1435 | delay(debounce_timeout.short).then(() => scrollOnMediaLoad()); | 1429 | delay(debounce_timeout.short).then(() => scrollOnMediaLoad()); |
| 1436 | } | 1430 | } |
| 1437 | 1431 | ||
| 1432 | /** | ||
| 1433 | * Visually updates all chat messages including and after index by removing them, then adding them. | ||
| 1434 | * @param {object} [options] Options | ||
| 1435 | * @param {ChatMessage[]} [options.targetChat=chat] All messages in chat before startIndex will remain unchanged. | ||
| 1436 | * @param {Number} [options.startIndex=0] Everything including and after startIndex will be replaced. | ||
| 1437 | * @param {Boolean} [options.fade=true] When false, the swipe chevrons will not fade in. | ||
| 1438 | */ | ||
| 1439 | export async function redisplayChat({ targetChat = chat, startIndex = 0, fade = true } = {}) { | ||
| 1440 | const messageElements = chatElement.find('.mes'); | ||
| 1441 | messageElements.removeClass('last_mes'); | ||
| 1442 | |||
| 1443 | //Remove messages after index. | ||
| 1444 | messageElements.filter(`.mes[mesid="${startIndex}"]`).nextAll('.mes').addBack().remove(); | ||
| 1445 | |||
| 1446 | const t1 = performance.now(); | ||
| 1447 | |||
| 1448 | const messages = targetChat.slice(startIndex); | ||
| 1449 | |||
| 1450 | if (messages.length > 0) { | ||
| 1451 | const newMessageElements = messages.map( (message, offset) => { | ||
| 1452 | const i = startIndex + offset; | ||
| 1453 | const messageElement = addOneMessage(message, { scroll: false, forceId: i, showSwipes: false, insert: false }); | ||
| 1454 | |||
| 1455 | return messageElement[0]; | ||
| 1456 | }); | ||
| 1457 | |||
| 1458 | //The last_mes has been removed, add it to the new last message. | ||
| 1459 | newMessageElements.at(-1).classList.add('last_mes'); | ||
| 1460 | |||
| 1461 | //Append to chat in one DOM update. | ||
| 1462 | chatElement.append(newMessageElements); | ||
| 1463 | |||
| 1464 | applyCharacterTagsToMessageDivs({ mesIds: lodash.range(startIndex, targetChat.length, 1) }); | ||
| 1465 | } | ||
| 1466 | |||
| 1467 | refreshSwipeButtons(false, fade); | ||
| 1468 | applyStylePins(); | ||
| 1469 | updateEditArrowClasses(); | ||
| 1470 | |||
| 1471 | console.info(`Rendered ${targetChat.length - startIndex} messages in ${((performance.now() - t1) / 1000).toFixed(3)} seconds.`); | ||
| 1472 | } | ||
| 1473 | |||
| 1438 | export function scrollOnMediaLoad() { | 1474 | export function scrollOnMediaLoad() { |
| 1439 | const started = Date.now(); | 1475 | const started = Date.now(); |
| 1440 | const media = chatElement.find('.mes_block img, .mes_block video, .mes_block audio').toArray(); | 1476 | const media = chatElement.find('.mes_block img, .mes_block video, .mes_block audio').toArray(); |
| @@ -2406,9 +2442,10 @@ export function addCopyToCodeBlocks(messageElement) { | |||
| 2406 | * @param {number} [options.insertBefore=null] Message ID to insert the new message before | 2442 | * @param {number} [options.insertBefore=null] Message ID to insert the new message before |
| 2407 | * @param {number} [options.forceId=null] Force the message ID | 2443 | * @param {number} [options.forceId=null] Force the message ID |
| 2408 | * @param {boolean} [options.showSwipes=true] Whether to refresh the swipe buttons. | 2444 | * @param {boolean} [options.showSwipes=true] Whether to refresh the swipe buttons. |
| 2409 | * @returns {void} | 2445 | * @param {boolean} [options.insert=true] Whether to insert the message into the DOM. |
| 2446 | * @returns {JQuery<HTMLElement>} The newly added message element | ||
| 2410 | */ | 2447 | */ |
| 2411 | export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true } = {}) { | 2448 | export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true, insert = true } = {}) { |
| 2412 | let messageText = mes.mes; | 2449 | let messageText = mes.mes; |
| 2413 | const momentDate = timestampToMoment(mes.send_date); | 2450 | const momentDate = timestampToMoment(mes.send_date); |
| 2414 | const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : ''; | 2451 | const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : ''; |
| @@ -2485,7 +2522,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2485 | 2522 | ||
| 2486 | const renderedMessage = getMessageFromTemplate(params); | 2523 | const renderedMessage = getMessageFromTemplate(params); |
| 2487 | 2524 | ||
| 2488 | if (type !== 'swipe') { | 2525 | if (type !== 'swipe' && insert) { |
| 2489 | if (!insertAfter && !insertBefore) { | 2526 | if (!insertAfter && !insertBefore) { |
| 2490 | chatElement.append(renderedMessage); | 2527 | chatElement.append(renderedMessage); |
| 2491 | } | 2528 | } |
| @@ -2501,7 +2538,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2501 | // Callers push the new message to chat before calling addOneMessage | 2538 | // Callers push the new message to chat before calling addOneMessage |
| 2502 | const newMessageId = typeof forceId == 'number' ? forceId : chat.length - 1; | 2539 | const newMessageId = typeof forceId == 'number' ? forceId : chat.length - 1; |
| 2503 | 2540 | ||
| 2504 | const newMessage = chatElement.find(`[mesid="${newMessageId}"]`); | 2541 | const newMessage = insert ? chatElement.find(`[mesid="${newMessageId}"]`) : renderedMessage; |
| 2505 | const isSmallSys = mes?.extra?.isSmallSys; | 2542 | const isSmallSys = mes?.extra?.isSmallSys; |
| 2506 | 2543 | ||
| 2507 | if (isSmallSys === true) { | 2544 | if (isSmallSys === true) { |
| @@ -2529,25 +2566,24 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2529 | }); | 2566 | }); |
| 2530 | 2567 | ||
| 2531 | if (type === 'swipe') { | 2568 | if (type === 'swipe') { |
| 2532 | const swipeMessage = chatElement.find(`[mesid="${newMessageId}"]`); | 2569 | newMessage.attr('swipeid', params.swipeId); |
| 2533 | swipeMessage.attr('swipeid', params.swipeId); | 2570 | newMessage.find('.mes_text').html(messageText).attr('title', title); |
| 2534 | swipeMessage.find('.mes_text').html(messageText).attr('title', title); | 2571 | newMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`); |
| 2535 | swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`); | 2572 | updateReasoningUI(newMessage); |
| 2536 | updateReasoningUI(swipeMessage); | 2573 | appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE); |
| 2537 | appendMediaToMessage(mes, swipeMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE); | ||
| 2538 | if (power_user.timestamp_model_icon && params.extra?.api) { | 2574 | if (power_user.timestamp_model_icon && params.extra?.api) { |
| 2539 | insertSVGIcon(swipeMessage, params.extra); | 2575 | insertSVGIcon(newMessage, params.extra); |
| 2540 | } | 2576 | } |
| 2541 | 2577 | ||
| 2542 | if (mes.swipe_id == mes.swipes.length - 1) { | 2578 | if (mes.swipe_id == mes.swipes.length - 1) { |
| 2543 | swipeMessage.find('.mes_timer').text(params.timerValue).attr('title', params.timerTitle); | 2579 | newMessage.find('.mes_timer').text(params.timerValue).attr('title', params.timerTitle); |
| 2544 | swipeMessage.find('.tokenCounterDisplay').text(`${params.tokenCount}t`); | 2580 | newMessage.find('.tokenCounterDisplay').text(`${params.tokenCount}t`); |
| 2545 | } else { | 2581 | } else { |
| 2546 | swipeMessage.find('.mes_timer').empty(); | 2582 | newMessage.find('.mes_timer').empty(); |
| 2547 | swipeMessage.find('.tokenCounterDisplay').empty(); | 2583 | newMessage.find('.tokenCounterDisplay').empty(); |
| 2548 | } | 2584 | } |
| 2549 | } else { | 2585 | } else { |
| 2550 | chatElement.find(`[mesid="${newMessageId}"] .mes_text`).append(messageText); | 2586 | newMessage.find('.mes_text').append(messageText); |
| 2551 | appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE); | 2587 | appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE); |
| 2552 | } | 2588 | } |
| 2553 | 2589 | ||
| @@ -2555,7 +2591,12 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2555 | 2591 | ||
| 2556 | // Set the swipes counter for all non-user messages. | 2592 | // Set the swipes counter for all non-user messages. |
| 2557 | if (!params.isUser) { | 2593 | if (!params.isUser) { |
| 2558 | updateSwipeCounter(newMessageId); | 2594 | updateSwipeCounter(newMessageId, { messageElement: newMessage }); |
| 2595 | } | ||
| 2596 | |||
| 2597 | // The caller should handle the rest after adding a message to DOM. | ||
| 2598 | if (!insert) { | ||
| 2599 | return newMessage; | ||
| 2559 | } | 2600 | } |
| 2560 | 2601 | ||
| 2561 | //last_mes should always be updated. | 2602 | //last_mes should always be updated. |
| @@ -2572,6 +2613,8 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2572 | 2613 | ||
| 2573 | applyCharacterTagsToMessageDivs({ mesIds: newMessageId }); | 2614 | applyCharacterTagsToMessageDivs({ mesIds: newMessageId }); |
| 2574 | updateEditArrowClasses(); | 2615 | updateEditArrowClasses(); |
| 2616 | |||
| 2617 | return newMessage; | ||
| 2575 | } | 2618 | } |
| 2576 | 2619 | ||
| 2577 | /** | 2620 | /** |
| @@ -9620,23 +9663,6 @@ export async function createOrEditCharacter(e) { | |||
| 9620 | } | 9663 | } |
| 9621 | 9664 | ||
| 9622 | /** | 9665 | /** |
| 9623 | * Visually updates all chat messages including andd after index by removing them, then adding them. | ||
| 9624 | * @param {ChatMessage[]} chat All messages in chat before index will remain unchanged. | ||
| 9625 | * @param {Number} index The last unchanged messageId. | ||
| 9626 | */ | ||
| 9627 | export async function redisplayChat(chat, index) { | ||
| 9628 | //Remove messages after index. | ||
| 9629 | chatElement.children(`.mes[mesid="${index}"]`).nextAll('.mes').addBack().remove(); | ||
| 9630 | |||
| 9631 | //Skip to index, then add extra messages. | ||
| 9632 | for (let i = index; i <= chat.length - 1; i++) { | ||
| 9633 | //addOneMessage will update last_mes. | ||
| 9634 | addOneMessage(chat[i], { scroll: false, showSwipes: false, forceId: i }); | ||
| 9635 | } | ||
| 9636 | refreshSwipeButtons(); | ||
| 9637 | } | ||
| 9638 | |||
| 9639 | /** | ||
| 9640 | * Formats a counter for a swipe view. | 9666 | * Formats a counter for a swipe view. |
| 9641 | * @param {number} current The current number of items. | 9667 | * @param {number} current The current number of items. |
| 9642 | * @param {number} total The total number of items. | 9668 | * @param {number} total The total number of items. |
| @@ -9790,7 +9816,7 @@ export async function swipe(event, direction, { source, repeated, message = chat | |||
| 9790 | 9816 | ||
| 9791 | //Update the chat. | 9817 | //Update the chat. |
| 9792 | await loadFromSwipeId(mesId, chat[mesId].swipe_id); | 9818 | await loadFromSwipeId(mesId, chat[mesId].swipe_id); |
| 9793 | await redisplayChat(chat, mesId); | 9819 | await redisplayChat({ startIndex: mesId }); |
| 9794 | } | 9820 | } |
| 9795 | else { | 9821 | else { |
| 9796 | await Popup.show.confirm( | 9822 | await Popup.show.confirm( |