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>

2d1a96f91d675c618e7fc9832cda3bea5c2ac8d4

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
1 files changed, +69 -43Showing whitespace changes
public/script.js+69 -43
@@ -8,6 +8,7 @@ import {
88 Popper,
99 initLibraryShims,
1010 default as libs,
11+ lodash,
1112} from './lib.js';
1213
1314import { humanizedDateTime, favsToHotswap, getMessageTimeStamp, dragElement, isMobile, initRossMods } from './scripts/RossAscends-mods.js';
@@ -1422,19 +1423,54 @@ export async function printMessages() {
14221423 chatElement.append('<div id="show_more_messages">Show more messages</div>');
14231424 }
14241425
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- }
14291427
1430- chatElement.find('.mes').removeClass('last_mes');
1431- chatElement.find('.mes').last().addClass('last_mes');
1432- refreshSwipeButtons(false, false);
1433- applyStylePins();
14341428 scrollChatToBottom({ waitForFrame: true });
14351429 delay(debounce_timeout.short).then(() => scrollOnMediaLoad());
14361430}
14371431
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+
14381474export function scrollOnMediaLoad() {
14391475 const started = Date.now();
14401476 const media = chatElement.find('.mes_block img, .mes_block video, .mes_block audio').toArray();
@@ -2406,9 +2442,10 @@ export function addCopyToCodeBlocks(messageElement) {
24062442 * @param {number} [options.insertBefore=null] Message ID to insert the new message before
24072443 * @param {number} [options.forceId=null] Force the message ID
24082444 * @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
24102447 */
24112448export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true, insert = true } = {}) {
24122449 let messageText = mes.mes;
24132450 const momentDate = timestampToMoment(mes.send_date);
24142451 const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';
@@ -2485,7 +2522,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
24852522
24862523 const renderedMessage = getMessageFromTemplate(params);
24872524
24882525 if (type !== 'swipe' && insert) {
24892526 if (!insertAfter && !insertBefore) {
24902527 chatElement.append(renderedMessage);
24912528 }
@@ -2501,7 +2538,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
25012538 // Callers push the new message to chat before calling addOneMessage
25022539 const newMessageId = typeof forceId == 'number' ? forceId : chat.length - 1;
25032540
25042541 const newMessage = insert ? chatElement.find(`[mesid="${newMessageId}"]`) : renderedMessage;
25052542 const isSmallSys = mes?.extra?.isSmallSys;
25062543
25072544 if (isSmallSys === true) {
@@ -2529,25 +2566,24 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
25292566 });
25302567
25312568 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);
25342571 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);
25382574 if (power_user.timestamp_model_icon && params.extra?.api) {
25392575 insertSVGIcon(swipeMessagenewMessage, params.extra);
25402576 }
25412577
25422578 if (mes.swipe_id == mes.swipes.length - 1) {
25432579 swipeMessagenewMessage.find('.mes_timer').text(params.timerValue).attr('title', params.timerTitle);
25442580 swipeMessagenewMessage.find('.tokenCounterDisplay').text(`${params.tokenCount}t`);
25452581 } else {
25462582 swipeMessagenewMessage.find('.mes_timer').empty();
25472583 swipeMessagenewMessage.find('.tokenCounterDisplay').empty();
25482584 }
25492585 } else {
25502586 chatElementnewMessage.find(`[mesid="${newMessageId}"] '.mes_text`').append(messageText);
25512587 appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE);
25522588 }
25532589
@@ -2555,7 +2591,12 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
25552591
25562592 // Set the swipes counter for all non-user messages.
25572593 if (!params.isUser) {
25582594 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;
25592600 }
25602601
25612602 //last_mes should always be updated.
@@ -2572,6 +2613,8 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
25722613
25732614 applyCharacterTagsToMessageDivs({ mesIds: newMessageId });
25742615 updateEditArrowClasses();
2616+
2617+ return newMessage;
25752618}
25762619
25772620/**
@@ -9620,23 +9663,6 @@ export async function createOrEditCharacter(e) {
96209663}
96219664
96229665/**
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-/**
96409666 * Formats a counter for a swipe view.
96419667 * @param {number} current The current number of items.
96429668 * @param {number} total The total number of items.
@@ -9790,7 +9816,7 @@ export async function swipe(event, direction, { source, repeated, message = chat
97909816
97919817 //Update the chat.
97929818 await loadFromSwipeId(mesId, chat[mesId].swipe_id);
97939819 await redisplayChat(chat,{ startIndex: mesId });
97949820 }
97959821 else {
97969822 await Popup.show.confirm(