`refactor/perf-printMessages` #3: Extracted `updateMessageItemizedPromptButton` and `getMessageHTML` from `addOneMessage` to improve readability. (#4984) * Extracted updateMessageItemizedPromptButton and getMessageHTML from addOneMessage to improve readability. * Fix types and function calls * Fixed insertBefore, insertAfter and messages without extra. * Use strict comparison operator * Use logical OR for display text fallback * Fixed newMessageId again. * Faster showMoreMessages. * removed `insertAfter` and `insertBefore` from `mes_edit_copy`, `/message-role` and `/message-name`. * Formatting fix * Refactor newMessageId --------- Co-authored-by: user <user@exmaple.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

69b7a17c7796175edd5d679cc3cb4927cb42b3aa

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

Signed
3 files changed, +89 -47Ignore whitespace
public/script.js+84 -44
@@ -1391,18 +1391,26 @@ export async function showMoreMessages(messagesToLoad = null) {
13911391
1392 console.debug('Inserting messages before', messageId, 'count', count, 'chat length', chat.length);1392 console.debug('Inserting messages before', messageId, 'count', count, 'chat length', chat.length);
1393 const prevHeight = chatElement.prop('scrollHeight');1393 const prevHeight = chatElement.prop('scrollHeight');
1394 const isButtonInView = isElementInViewport($('#show_more_messages')[0]);1394 const showMoreButton = $('#show_more_messages');
13951395 const isButtonInView = isElementInViewport(showMoreButton[0]);
1396 while (messageId > 0 && count > 0) {1396
1397 let newMessageId = messageId - 1;1397 const firstId = clamp(messageId - count, 0, Infinity);
1398 addOneMessage(chat[newMessageId], { insertBefore: messageId >= chat.length ? null : messageId, scroll: false, forceId: newMessageId, showSwipes: false });1398 const messageElements = [];
1399 count--;1399 chat.slice(firstId, messageId).forEach((message, id) => {
1400 messageId--;1400 messageElements.push(addOneMessage(message, { scroll: false, forceId: firstId + id, showSwipes: false, insert: false }));
1401 });
1402 // This could be faster: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement
1403 // Fallback to chatElement if the button isn't where it's expected to be.
1404 if (showMoreButton[0]) {
1405 showMoreButton.after(messageElements);
1406 } else {
1407 chatElement.prepend(messageElements);
1401 }1408 }
1409
1402 refreshSwipeButtons();1410 refreshSwipeButtons();
14031411
1404 if (messageId == 0) {1412 if (firstId === 0) {
1405 $('#show_more_messages').remove();1413 showMoreButton.remove();
1406 }1414 }
14071415
1408 if (isButtonInView) {1416 if (isButtonInView) {
@@ -1448,7 +1456,7 @@ export async function redisplayChat({ targetChat = chat, startIndex = 0, fade =
1448 const messages = targetChat.slice(startIndex);1456 const messages = targetChat.slice(startIndex);
14491457
1450 if (messages.length > 0) {1458 if (messages.length > 0) {
1451 const newMessageElements = messages.map( (message, offset) => {1459 const newMessageElements = messages.map((message, offset) => {
1452 const i = startIndex + offset;1460 const i = startIndex + offset;
1453 const messageElement = addOneMessage(message, { scroll: false, forceId: i, showSwipes: false, insert: false });1461 const messageElement = addOneMessage(message, { scroll: false, forceId: i, showSwipes: false, insert: false });
14541462
@@ -1461,7 +1469,7 @@ export async function redisplayChat({ targetChat = chat, startIndex = 0, fade =
1461 //Append to chat in one DOM update.1469 //Append to chat in one DOM update.
1462 chatElement.append(newMessageElements);1470 chatElement.append(newMessageElements);
14631471
1464 applyCharacterTagsToMessageDivs({ mesIds: lodash.range(startIndex, targetChat.length, 1) });1472 applyCharacterTagsToMessageDivs({ mesIds: lodash.range(startIndex, targetChat.length, 1) });
1465 }1473 }
14661474
1467 refreshSwipeButtons(false, fade);1475 refreshSwipeButtons(false, fade);
@@ -1682,7 +1690,7 @@ export async function sendTextareaMessage() {
1682 * @param {boolean} isSystem If the message was sent by the system1690 * @param {boolean} isSystem If the message was sent by the system
1683 * @param {boolean} isUser If the message was sent by the user1691 * @param {boolean} isUser If the message was sent by the user
1684 * @param {number} messageId Message index in chat array1692 * @param {number} messageId Message index in chat array
1685 * @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides1693 * @param {Partial<DOMPurify.Config>} [sanitizerOverrides] DOMPurify sanitizer option overrides
1686 * @param {boolean} [isReasoning] If the message is reasoning output1694 * @param {boolean} [isReasoning] If the message is reasoning output
1687 * @returns {string} HTML string1695 * @returns {string} HTML string
1688 */1696 */
@@ -1831,7 +1839,7 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
1831 mes = mes.replace(new RegExp(`(^|\n)${escapeRegex(ch_name)}:`, 'g'), '$1');1839 mes = mes.replace(new RegExp(`(^|\n)${escapeRegex(ch_name)}:`, 'g'), '$1');
1832 }1840 }
18331841
1834 /** @type {import('dompurify').Config & { RETURN_DOM_FRAGMENT: false; RETURN_DOM: false }} */1842 /** @type {DOMPurify.Config} */
1835 const config = {1843 const config = {
1836 RETURN_DOM: false,1844 RETURN_DOM: false,
1837 RETURN_DOM_FRAGMENT: false,1845 RETURN_DOM_FRAGMENT: false,
@@ -2375,6 +2383,46 @@ export function addCopyToCodeBlocks(messageElement) {
2375 }2383 }
2376}2384}
23772385
2386/**
2387 * Shows or hides the Prompt display button
2388 * @param {ChatMessage} message Message object
2389 * @param {object} options Options
2390 * @param {number} [options.messageId] Message ID
2391 * @param {JQuery<HTMLElement>} [options.messageElement] Message element
2392 * @return {void}
2393 */
2394function updateMessageItemizedPromptButton(message, { messageId = chat.indexOf(message), messageElement = chatElement.find(`.mes[mesid="${messageId}"]`) }) {
2395 //if we have itemized messages, and the array isn't null..
2396 if (!message.is_user && Array.isArray(itemizedPrompts) && itemizedPrompts.length > 0) {
2397 const itemizedPrompt = itemizedPrompts.find(x => Number(x.mesId) === Number(messageId));
2398 if (itemizedPrompt) {
2399 messageElement.find('.mes_prompt').show();
2400 }
2401 }
2402}
2403
2404/**
2405 * Gets messageFormatting for a ChatMessage object.
2406 * @param {ChatMessage} message
2407 * @param {object} options Options
2408 * @param {number} [options.messageId] Message ID
2409 * @returns {string} Formatted message HTML
2410 */
2411function getMessageTextHTML(message, { messageId = chat.indexOf(message) }) {
2412 // if mes.extra.uses_system_ui is true, set an override on the sanitizer options
2413 /** @type {Partial<DOMPurify.Config>} */
2414 const sanitizerOverrides = message.extra?.uses_system_ui ? { MESSAGE_ALLOW_SYSTEM_UI: true } : {};
2415
2416 return messageFormatting(
2417 message.extra?.display_text || message.mes,
2418 message.name,
2419 message.is_system,
2420 message.is_user,
2421 messageId,
2422 sanitizerOverrides,
2423 false,
2424 );
2425}
23782426
2379/**2427/**
2380 * Adds a single message to the chat.2428 * Adds a single message to the chat.
@@ -2391,18 +2439,27 @@ export function addCopyToCodeBlocks(messageElement) {
2391 */2439 */
2392export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true, insert = true } = {}) {2440export 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 addOneMessage2441 // Callers push the new message to chat before calling addOneMessage
2394 const newMessageId = typeof forceId == 'number' ? forceId : chat.length - 1;2442 const newMessageId = (() => {
2443 if (typeof forceId === 'number') {
2444 return forceId;
2445 }
2446 if (typeof insertBefore === 'number') {
2447 return insertBefore - 1;
2448 }
2449 if (typeof insertAfter === 'number') {
2450 return insertAfter + 1;
2451 }
2452 const index = chat.indexOf(mes);
2453 if (index !== -1) {
2454 return index;
2455 }
2456 return chat.length - 1;
2457 })();
23952458
2396 let messageText = mes.mes;
2397 const momentDate = timestampToMoment(mes.send_date);2459 const momentDate = timestampToMoment(mes.send_date);
2398 const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';2460 const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';
23992461
2400 if (mes?.extra?.display_text) {
2401 messageText = mes.extra.display_text;
2402 }
2403
2404 let avatarImg = getThumbnailUrl('persona', user_avatar);2462 let avatarImg = getThumbnailUrl('persona', user_avatar);
2405 const isSystem = mes.is_system;
24062463
2407 //for non-user messages2464 //for non-user messages
2408 if (!mes.is_user) {2465 if (!mes.is_user) {
@@ -2426,18 +2483,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2426 avatarImg = mes.force_avatar;2483 avatarImg = mes.force_avatar;
2427 }2484 }
24282485
2429 // if mes.extra.uses_system_ui is true, set an override on the sanitizer options2486 const messageHTML = getMessageTextHTML(mes, { messageId: newMessageId });
2430 const sanitizerOverrides = mes.extra?.uses_system_ui ? { MESSAGE_ALLOW_SYSTEM_UI: true } : {};
2431
2432 messageText = messageFormatting(
2433 messageText,
2434 mes.name,
2435 isSystem,
2436 mes.is_user,
2437 chat.indexOf(mes),
2438 sanitizerOverrides,
2439 false,
2440 );
2441 let newMessage;2487 let newMessage;
24422488
2443 if (type === 'swipe') {2489 if (type === 'swipe') {
@@ -2488,7 +2534,6 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2488 insertSVGIcon(newMessage, mes.extra);2534 insertSVGIcon(newMessage, mes.extra);
2489 }2535 }
24902536
2491
2492 if (type !== 'swipe' && insert) {2537 if (type !== 'swipe' && insert) {
2493 if (!insertAfter && !insertBefore) {2538 if (!insertAfter && !insertBefore) {
2494 chatElement.append(newMessage);2539 chatElement.append(newMessage);
@@ -2512,13 +2557,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2512 newMessage.addClass('toolCall');2557 newMessage.addClass('toolCall');
2513 }2558 }
25142559
2515 //if we have itemized messages, and the array isn't null..2560 updateMessageItemizedPromptButton(mes, { messageId: newMessageId, messageElement: newMessage });
2516 if (!mes.is_user && Array.isArray(itemizedPrompts) && itemizedPrompts.length > 0) {
2517 const itemizedPrompt = itemizedPrompts.find(x => Number(x.mesId) === Number(newMessageId));
2518 if (itemizedPrompt) {
2519 newMessage.find('.mes_prompt').show();
2520 }
2521 }
25222561
2523 newMessage.find('.avatar img').on('error', function () {2562 newMessage.find('.avatar img').on('error', function () {
2524 $(this).hide();2563 $(this).hide();
@@ -2526,8 +2565,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2526 });2565 });
25272566
2528 appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE);2567 appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE);
2529 newMessage.find('.mes_text').html(messageText);2568 newMessage.find('.mes_text').html(messageHTML);
2530
2531 addCopyToCodeBlocks(newMessage);2569 addCopyToCodeBlocks(newMessage);
25322570
2533 // Set the swipes counter for all non-user messages.2571 // Set the swipes counter for all non-user messages.
@@ -11651,14 +11689,16 @@ jQuery(async function () {
11651 const oldScroll = chatElement[0].scrollTop;11689 const oldScroll = chatElement[0].scrollTop;
11652 const clone = structuredClone(chat[this_edit_mes_id]);11690 const clone = structuredClone(chat[this_edit_mes_id]);
11653 clone.send_date = Date.now();11691 clone.send_date = Date.now();
11654 clone.mes = $(this).closest('.mes').find('.edit_textarea').val().toString();11692 const this_edit_mes_element = $(this).closest('.mes');
11693 clone.mes = this_edit_mes_element.find('.edit_textarea').val().toString();
1165511694
11656 if (power_user.trim_spaces) {11695 if (power_user.trim_spaces) {
11657 clone.mes = clone.mes.trim();11696 clone.mes = clone.mes.trim();
11658 }11697 }
1165911698
11660 chat.splice(Number(this_edit_mes_id) + 1, 0, clone);11699 chat.splice(Number(this_edit_mes_id) + 1, 0, clone);
11661 addOneMessage(clone, { insertAfter: this_edit_mes_id });11700 const newMessageElement = addOneMessage(clone, { insert: false });
11701 this_edit_mes_element.after(newMessageElement);
1166211702
11663 updateViewMessageIds();11703 updateViewMessageIds();
11664 await saveChatConditional();11704 await saveChatConditional();
public/scripts/chats.js+1 -1
@@ -685,7 +685,7 @@ export function formatCreatorNotes(text, avatarId) {
685 const preference = new StylesPreference(avatarId);685 const preference = new StylesPreference(avatarId);
686 const sanitizeStyles = !preference.get();686 const sanitizeStyles = !preference.get();
687 const decodeStyleParam = { prefix: sanitizeStyles ? '#creator_notes_spoiler ' : '' };687 const decodeStyleParam = { prefix: sanitizeStyles ? '#creator_notes_spoiler ' : '' };
688 /** @type {import('dompurify').Config} */688 /** @type {DOMPurify.Config} */
689 const config = {689 const config = {
690 RETURN_DOM: false,690 RETURN_DOM: false,
691 RETURN_DOM_FRAGMENT: false,691 RETURN_DOM_FRAGMENT: false,
public/scripts/slash-commands.js+4 -2
@@ -4642,7 +4642,8 @@ async function messageRoleCallback(args, role) {
4642 await eventSource.emit(event_types.MESSAGE_EDITED, modifyAt);4642 await eventSource.emit(event_types.MESSAGE_EDITED, modifyAt);
4643 const existingMessage = chatElement.find(`.mes[mesid="${modifyAt}"]`);4643 const existingMessage = chatElement.find(`.mes[mesid="${modifyAt}"]`);
4644 if (existingMessage.length) {4644 if (existingMessage.length) {
4645 addOneMessage(message, { forceId: modifyAt, insertAfter: modifyAt, scroll: false });4645 const newMessageElement = addOneMessage(message, { forceId: modifyAt, insert: false, scroll: false });
4646 existingMessage.after(newMessageElement);
4646 existingMessage.remove();4647 existingMessage.remove();
4647 }4648 }
4648 await eventSource.emit(event_types.MESSAGE_UPDATED, modifyAt);4649 await eventSource.emit(event_types.MESSAGE_UPDATED, modifyAt);
@@ -4707,7 +4708,8 @@ async function messageNameCallback(args, name) {
4707 await eventSource.emit(event_types.MESSAGE_EDITED, modifyAt);4708 await eventSource.emit(event_types.MESSAGE_EDITED, modifyAt);
4708 const existingMessage = chatElement.find(`.mes[mesid="${modifyAt}"]`);4709 const existingMessage = chatElement.find(`.mes[mesid="${modifyAt}"]`);
4709 if (existingMessage.length) {4710 if (existingMessage.length) {
4710 addOneMessage(message, { forceId: modifyAt, insertAfter: modifyAt, scroll: false });4711 const newMessageElement = addOneMessage(message, { forceId: modifyAt, insert: false, scroll: false });
4712 existingMessage.after(newMessageElement);
4711 existingMessage.remove();4713 existingMessage.remove();
4712 }4714 }
4713 await eventSource.emit(event_types.MESSAGE_UPDATED, modifyAt);4715 await eventSource.emit(event_types.MESSAGE_UPDATED, modifyAt);