`refactor/perf-printMessages` #2: Removed `getMessageFromTemplate` (#4983) * Reduced redundant swipe code by fully updating the swipe message. * `getMessageFromTemplate` is an inconvenient function that requires more lines than it saves. It cannot be used in any other function, therefore it should not exist. This change also prevents an unnecessary call of `messageTemplate.clone` in addOneMessage when `type = 'swipe'`. ESLint. * Fixed: https://github.com/SillyTavern/SillyTavern/pull/4983#discussion_r2680379455 * Replaced ?? with &&: https://github.com/SillyTavern/SillyTavern/pull/4983#discussion_r2680366010 and removed comments. * Fixed bias check. "0" is falsy. * Remove scroll adjust early bail * Call appendMediaToMessage before updating content Because we need to preserve chatHeight and scrollPosition according to the previous swipe, not the new one --------- Co-authored-by: user <user@exmaple.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

a9f2d559f428029964c2dfb7ccc3d429d92f67ea

DeclineThyself <235079501+DeclineThyself@users.noreply.github.com>

Signed
1 files changed, +62 -121Ignore whitespace
public/script.js+62 -121
@@ -1855,9 +1855,7 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
1855 * the value in `extra.api`.1855 * the value in `extra.api`.
1856 *1856 *
1857 * @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced.1857 * @param {JQuery<HTMLElement>} mes - The message element containing the timestamp where the icon should be inserted or replaced.
1858 * @param {Object} extra - Contains the API and model details.1858 * @param {ChatMessageExtra} extra - Contains the API and model details.
1859 * @param {string} extra.api - The name of the API, used to determine which SVG to fetch.
1860 * @param {string} extra.model - The model name, used to check for the substring "claude".
1861 */1859 */
1862function insertSVGIcon(mes, extra) {1860function insertSVGIcon(mes, extra) {
1863 // Determine the SVG filename1861 // Determine the SVG filename
@@ -1905,56 +1903,6 @@ function insertSVGIcon(mes, extra) {
1905 createModelImage('thinking-icon', '.mes_reasoning_header_title', true);1903 createModelImage('thinking-icon', '.mes_reasoning_header_title', true);
1906}1904}
19071905
1908
1909function getMessageFromTemplate({
1910 mesId,
1911 swipeId,
1912 characterName,
1913 isUser,
1914 avatarImg,
1915 bias,
1916 isSystem,
1917 title,
1918 timerValue,
1919 timerTitle,
1920 bookmarkLink,
1921 forceAvatar,
1922 timestamp,
1923 tokenCount,
1924 extra,
1925 type,
1926}) {
1927 const mes = messageTemplate.clone();
1928 mes.attr({
1929 'mesid': mesId,
1930 'swipeid': swipeId,
1931 'ch_name': characterName,
1932 'is_user': isUser,
1933 'is_system': !!isSystem,
1934 'bookmark_link': bookmarkLink,
1935 'force_avatar': !!forceAvatar,
1936 'timestamp': timestamp,
1937 ...(type ? { type } : {}),
1938 });
1939 mes.find('.avatar img').attr('src', avatarImg);
1940 mes.find('.ch_name .name_text').text(characterName);
1941 mes.find('.mes_bias').html(bias);
1942 mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`);
1943 mes.find('.mesIDDisplay').text(`#${mesId}`);
1944 tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`);
1945 title && mes.attr('title', title);
1946 timerValue && mes.find('.mes_timer').attr('title', timerTitle).text(timerValue);
1947 bookmarkLink && updateBookmarkDisplay(mes);
1948
1949 updateReasoningUI(mes);
1950
1951 if (power_user.timestamp_model_icon && extra?.api) {
1952 insertSVGIcon(mes, extra);
1953 }
1954
1955 return mes;
1956}
1957
1958/**1906/**
1959 * Re-renders a message block with updated content.1907 * Re-renders a message block with updated content.
1960 * @param {number} messageId Message ID1908 * @param {number} messageId Message ID
@@ -2172,10 +2120,6 @@ export function appendMediaToMessage(mes, messageElement, scrollBehavior = SCROL
2172 chatElement.scrollTop(scrollPosition);2120 chatElement.scrollTop(scrollPosition);
2173 return;2121 return;
2174 }2122 }
2175 const newScrollPosition = chatElement.scrollTop();
2176 if (newScrollPosition > scrollPosition) {
2177 return;
2178 }
2179 const newChatHeight = chatElement.prop('scrollHeight');2123 const newChatHeight = chatElement.prop('scrollHeight');
2180 const diff = newChatHeight - chatHeight;2124 const diff = newChatHeight - chatHeight;
2181 chatElement.scrollTop(scrollPosition + diff);2125 chatElement.scrollTop(scrollPosition + diff);
@@ -2446,6 +2390,9 @@ export function addCopyToCodeBlocks(messageElement) {
2446 * @returns {JQuery<HTMLElement>} The newly added message element2390 * @returns {JQuery<HTMLElement>} The newly added message element
2447 */2391 */
2448export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true, insert = true } = {}) {2392export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true, insert = true } = {}) {
2393 // Callers push the new message to chat before calling addOneMessage
2394 const newMessageId = typeof forceId == 'number' ? forceId : chat.length - 1;
2395
2449 let messageText = mes.mes;2396 let messageText = mes.mes;
2450 const momentDate = timestampToMoment(mes.send_date);2397 const momentDate = timestampToMoment(mes.send_date);
2451 const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';2398 const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';
@@ -2454,16 +2401,8 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2454 messageText = mes.extra.display_text;2401 messageText = mes.extra.display_text;
2455 }2402 }
24562403
2457 // Forbidden black magic
2458 // This allows to use "continue" on user messages
2459 if (type === 'swipe' && mes.swipe_id === undefined) {
2460 mes.swipe_id = 0;
2461 mes.swipes = [mes.mes];
2462 }
2463
2464 let avatarImg = getThumbnailUrl('persona', user_avatar);2404 let avatarImg = getThumbnailUrl('persona', user_avatar);
2465 const isSystem = mes.is_system;2405 const isSystem = mes.is_system;
2466 const title = mes.title;
24672406
2468 //for non-user messages2407 //for non-user messages
2469 if (!mes.is_user) {2408 if (!mes.is_user) {
@@ -2479,7 +2418,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2479 }2418 }
2480 }2419 }
2481 //old processing:2420 //old processing:
2482 //if messge is from sytem, use the name provided in the message JSONL to proceed,2421 //if message is from system, use the name provided in the message JSONL to proceed,
2483 //if not system message, use name2 (char's name) to proceed2422 //if not system message, use name2 (char's name) to proceed
2484 //characterName = mes.is_system || mes.force_avatar ? mes.name : name2;2423 //characterName = mes.is_system || mes.force_avatar ? mes.name : name2;
2485 } else if (mes.is_user && mes.force_avatar) {2424 } else if (mes.is_user && mes.force_avatar) {
@@ -2499,46 +2438,70 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2499 sanitizerOverrides,2438 sanitizerOverrides,
2500 false,2439 false,
2501 );2440 );
2502 const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1, {}, false);2441 let newMessage;
2503 let bookmarkLink = mes?.extra?.bookmark_link ?? '';2442
25042443 if (type === 'swipe') {
2505 let params = {2444 // Forbidden black magic
2506 mesId: forceId ?? chat.length - 1,2445 // This allows to use "continue" on user messages
2507 swipeId: mes.swipe_id ?? 0,2446 mes.swipe_id ??= 0;
2508 characterName: mes.name,2447 mes.swipes ??= [mes.mes];
2509 isUser: mes.is_user,2448 //This keeps listeners intact.
2510 avatarImg: avatarImg,2449 newMessage = chatElement.find(`[mesid="${newMessageId}"]`);
2511 bias: bias,2450 } else {
2512 isSystem: isSystem,2451 newMessage = messageTemplate.clone();
2513 title: title,2452 }
2514 bookmarkLink: bookmarkLink,2453
2515 forceAvatar: mes.force_avatar,2454 const { timerValue, timerTitle } = formatGenerationTimer(mes.gen_started, mes.gen_finished, mes.extra?.token_count, mes.extra?.reasoning_duration, mes.extra?.time_to_first_token);
2516 timestamp: timestamp,2455 const tokenCount = mes.extra?.token_count;
2517 extra: mes.extra,2456 const bookmarkLink = mes?.extra?.bookmark_link;
2518 tokenCount: mes.extra?.token_count ?? 0,2457
2519 type: mes.extra?.type ?? '',2458 newMessage.attr({
2520 ...formatGenerationTimer(mes.gen_started, mes.gen_finished, mes.extra?.token_count, mes.extra?.reasoning_duration, mes.extra?.time_to_first_token),2459 'mesid': newMessageId,
2521 };2460 'swipeid': mes.swipe_id ?? 0,
2461 'ch_name': mes.name,
2462 'is_user': mes.is_user,
2463 'is_system': !!mes.is_system,
2464 'bookmark_link': bookmarkLink,
2465 'force_avatar': !!mes.force_avatar,
2466 'timestamp': timestamp,
2467 // ...(type ?? { type }),
2468 'type': mes.extra?.type ?? '',
2469 });
2470
2471 newMessage.find('.avatar img').attr('src', avatarImg);
2472 newMessage.find('.ch_name .name_text').text(mes.name);
2473 newMessage.find('.timestamp').text(timestamp).attr('title', `${mes.extra?.api ? mes.extra.api + ' - ' : ''}${mes.extra?.model ?? ''}`);
2474 newMessage.find('.mesIDDisplay').text(`#${newMessageId}`);
2475 tokenCount && newMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);
2476 mes.title && newMessage.attr('title', mes.title);
2477 timerValue && newMessage.find('.mes_timer').attr('title', timerTitle).text(timerValue);
2478 bookmarkLink && updateBookmarkDisplay(newMessage);
2479
2480 if (mes.extra?.bias !== '') {
2481 const bias = messageFormatting(mes.extra?.bias, '', false, false, -1, {}, false);
2482 newMessage.find('.mes_bias').html(bias);
2483 }
2484
2485 updateReasoningUI(newMessage);
2486
2487 if (power_user.timestamp_model_icon && mes.extra?.api) {
2488 insertSVGIcon(newMessage, mes.extra);
2489 }
25222490
2523 const renderedMessage = getMessageFromTemplate(params);
25242491
2525 if (type !== 'swipe' && insert) {2492 if (type !== 'swipe' && insert) {
2526 if (!insertAfter && !insertBefore) {2493 if (!insertAfter && !insertBefore) {
2527 chatElement.append(renderedMessage);2494 chatElement.append(newMessage);
2528 }2495 }
2529 else if (insertAfter) {2496 else if (insertAfter) {
2530 const target = chatElement.find(`.mes[mesid="${insertAfter}"]`);2497 const target = chatElement.find(`.mes[mesid="${insertAfter}"]`);
2531 $(renderedMessage).insertAfter(target);2498 $(newMessage).insertAfter(target);
2532 } else {2499 } else {
2533 const target = chatElement.find(`.mes[mesid="${insertBefore}"]`);2500 const target = chatElement.find(`.mes[mesid="${insertBefore}"]`);
2534 $(renderedMessage).insertBefore(target);2501 $(newMessage).insertBefore(target);
2535 }2502 }
2536 }2503 }
25372504
2538 // Callers push the new message to chat before calling addOneMessage
2539 const newMessageId = typeof forceId == 'number' ? forceId : chat.length - 1;
2540
2541 const newMessage = insert ? chatElement.find(`[mesid="${newMessageId}"]`) : renderedMessage;
2542 const isSmallSys = mes?.extra?.isSmallSys;2505 const isSmallSys = mes?.extra?.isSmallSys;
25432506
2544 if (isSmallSys === true) {2507 if (isSmallSys === true) {
@@ -2549,12 +2512,9 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2549 newMessage.addClass('toolCall');2512 newMessage.addClass('toolCall');
2550 }2513 }
25512514
2552 //shows or hides the Prompt display button
2553 let mesIdToFind = type === 'swipe' ? params.mesId - 1 : params.mesId; //Number(newMessage.attr('mesId'));
2554
2555 //if we have itemized messages, and the array isn't null..2515 //if we have itemized messages, and the array isn't null..
2556 if (params.isUser === false && Array.isArray(itemizedPrompts) && itemizedPrompts.length > 0) {2516 if (!mes.is_user && Array.isArray(itemizedPrompts) && itemizedPrompts.length > 0) {
2557 const itemizedPrompt = itemizedPrompts.find(x => Number(x.mesId) === Number(mesIdToFind));2517 const itemizedPrompt = itemizedPrompts.find(x => Number(x.mesId) === Number(newMessageId));
2558 if (itemizedPrompt) {2518 if (itemizedPrompt) {
2559 newMessage.find('.mes_prompt').show();2519 newMessage.find('.mes_prompt').show();
2560 }2520 }
@@ -2565,32 +2525,13 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2565 $(this).parent().html('<div class="missing-avatar fa-solid fa-user-slash"></div>');2525 $(this).parent().html('<div class="missing-avatar fa-solid fa-user-slash"></div>');
2566 });2526 });
25672527
2568 if (type === 'swipe') {2528 appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE);
2569 newMessage.attr('swipeid', params.swipeId);2529 newMessage.find('.mes_text').html(messageText);
2570 newMessage.find('.mes_text').html(messageText).attr('title', title);
2571 newMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`);
2572 updateReasoningUI(newMessage);
2573 appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE);
2574 if (power_user.timestamp_model_icon && params.extra?.api) {
2575 insertSVGIcon(newMessage, params.extra);
2576 }
2577
2578 if (mes.swipe_id == mes.swipes.length - 1) {
2579 newMessage.find('.mes_timer').text(params.timerValue).attr('title', params.timerTitle);
2580 newMessage.find('.tokenCounterDisplay').text(`${params.tokenCount}t`);
2581 } else {
2582 newMessage.find('.mes_timer').empty();
2583 newMessage.find('.tokenCounterDisplay').empty();
2584 }
2585 } else {
2586 newMessage.find('.mes_text').append(messageText);
2587 appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE);
2588 }
25892530
2590 addCopyToCodeBlocks(newMessage);2531 addCopyToCodeBlocks(newMessage);
25912532
2592 // Set the swipes counter for all non-user messages.2533 // Set the swipes counter for all non-user messages.
2593 if (!params.isUser) {2534 if (!mes.is_user) {
2594 updateSwipeCounter(newMessageId, { messageElement: newMessage });2535 updateSwipeCounter(newMessageId, { messageElement: newMessage });
2595 }2536 }
25962537