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 | 8 | Popper, |
| 9 | 9 | initLibraryShims, |
| 10 | 10 | default as libs, |
| 11 | + lodash, | |
| 11 | 12 | } from './lib.js'; |
| 12 | 13 | |
| 13 | 14 | import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods } from './scripts/RossAscends-mods.js'; |
| @@ -1422,19 +1423,54 @@ export async function printMessages() { | ||
| 1422 | 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 | 1428 | scrollChatToBottom({ waitForFrame: true }); |
| 1435 | 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 | 1474 | export function scrollOnMediaLoad() { |
| 1439 | 1475 | const started = Date.now(); |
| 1440 | 1476 | const media = chatElement.find('.mes_block img, .mes_block video, .mes_block audio').toArray(); |
| @@ -2406,9 +2442,10 @@ export function addCopyToCodeBlocks(messageElement) { | ||
| 2406 | 2442 | * @param {number} [options.insertBefore=null] Message ID to insert the new message before |
| 2407 | 2443 | * @param {number} [options.forceId=null] Force the message ID |
| 2408 | 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 | 2448 | export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true, insert = true } = {}) { |
| 2412 | 2449 | let messageText = mes.mes; |
| 2413 | 2450 | const momentDate = timestampToMoment(mes.send_date); |
| 2414 | 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 | 2523 | const renderedMessage = getMessageFromTemplate(params); |
| 2487 | 2524 | |
| 2488 | 2525 | if (type !== 'swipe' && insert) { |
| 2489 | 2526 | if (!insertAfter && !insertBefore) { |
| 2490 | 2527 | chatElement.append(renderedMessage); |
| 2491 | 2528 | } |
| @@ -2501,7 +2538,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2501 | 2538 | // Callers push the new message to chat before calling addOneMessage |
| 2502 | 2539 | const newMessageId = typeof forceId == 'number' ? forceId : chat.length - 1; |
| 2503 | 2540 | |
| 2504 | 2541 | const newMessage = insert ? chatElement.find(`[mesid="${newMessageId}"]`) : renderedMessage; |
| 2505 | 2542 | const isSmallSys = mes?.extra?.isSmallSys; |
| 2506 | 2543 | |
| 2507 | 2544 | if (isSmallSys === true) { |
| @@ -2529,25 +2566,24 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2529 | 2566 | }); |
| 2530 | 2567 | |
| 2531 | 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 | 2571 | swipeMessagenewMessage.find('.mes_texttimestamp').htmltext(messageTexttimestamp).attr('title', 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 | 2574 | if (power_user.timestamp_model_icon && params.extra?.api) { |
| 2539 | 2575 | insertSVGIcon(swipeMessagenewMessage, params.extra); |
| 2540 | 2576 | } |
| 2541 | 2577 | |
| 2542 | 2578 | if (mes.swipe_id == mes.swipes.length - 1) { |
| 2543 | 2579 | swipeMessagenewMessage.find('.mes_timer').text(params.timerValue).attr('title', params.timerTitle); |
| 2544 | 2580 | swipeMessagenewMessage.find('.tokenCounterDisplay').text(`${params.tokenCount}t`); |
| 2545 | 2581 | } else { |
| 2546 | 2582 | swipeMessagenewMessage.find('.mes_timer').empty(); |
| 2547 | 2583 | swipeMessagenewMessage.find('.tokenCounterDisplay').empty(); |
| 2548 | 2584 | } |
| 2549 | 2585 | } else { |
| 2550 | 2586 | chatElementnewMessage.find(`[mesid="${newMessageId}"] '.mes_text`').append(messageText); |
| 2551 | 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 | 2592 | // Set the swipes counter for all non-user messages. |
| 2557 | 2593 | if (!params.isUser) { |
| 2558 | 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 | 2602 | //last_mes should always be updated. |
| @@ -2572,6 +2613,8 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | ||
| 2572 | 2613 | |
| 2573 | 2614 | applyCharacterTagsToMessageDivs({ mesIds: newMessageId }); |
| 2574 | 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 | 9666 | * Formats a counter for a swipe view. |
| 9641 | 9667 | * @param {number} current The current number of items. |
| 9642 | 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 | 9817 | //Update the chat. |
| 9792 | 9818 | await loadFromSwipeId(mesId, chat[mesId].swipe_id); |
| 9793 | 9819 | await redisplayChat(chat,{ startIndex: mesId }); |
| 9794 | 9820 | } |
| 9795 | 9821 | else { |
| 9796 | 9822 | await Popup.show.confirm( |