`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
13921392 console.debug('Inserting messages before', messageId, 'count', count, 'chat length', chat.length);
13931393 const prevHeight = chatElement.prop('scrollHeight');
13941394 const isButtonInViewshowMoreButton = isElementInViewport($('#show_more_messages')[0]);
1395-
1395+ const isButtonInView = isElementInViewport(showMoreButton[0]);
1396- while (messageId > 0 && count > 0) {
1396+
13971397 let const newMessageIdfirstId = clamp(messageId - 1count, 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);
14011408 }
1409+
14021410 refreshSwipeButtons();
14031411
14041412 if (messageIdfirstId === 0) {
14051413 $('#show_more_messages')showMoreButton.remove();
14061414 }
14071415
14081416 if (isButtonInView) {
@@ -1448,7 +1456,7 @@ export async function redisplayChat({ targetChat = chat, startIndex = 0, fade =
14481456 const messages = targetChat.slice(startIndex);
14491457
14501458 if (messages.length > 0) {
14511459 const newMessageElements = messages.map( (message, offset) => {
14521460 const i = startIndex + offset;
14531461 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 =
14611469 //Append to chat in one DOM update.
14621470 chatElement.append(newMessageElements);
14631471
14641472 applyCharacterTagsToMessageDivs({ mesIds: lodash.range(startIndex, targetChat.length, 1) });
14651473 }
14661474
14671475 refreshSwipeButtons(false, fade);
@@ -1682,7 +1690,7 @@ export async function sendTextareaMessage() {
16821690 * @param {boolean} isSystem If the message was sent by the system
16831691 * @param {boolean} isUser If the message was sent by the user
16841692 * @param {number} messageId Message index in chat array
16851693 * @param {objectPartial<DOMPurify.Config>} [sanitizerOverrides] DOMPurify sanitizer option overrides
16861694 * @param {boolean} [isReasoning] If the message is reasoning output
16871695 * @returns {string} HTML string
16881696 */
@@ -1831,7 +1839,7 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
18311839 mes = mes.replace(new RegExp(`(^|\n)${escapeRegex(ch_name)}:`, 'g'), '$1');
18321840 }
18331841
18341842 /** @type {import('dompurify')DOMPurify.Config & { RETURN_DOM_FRAGMENT: false; RETURN_DOM: false }} */
18351843 const config = {
18361844 RETURN_DOM: false,
18371845 RETURN_DOM_FRAGMENT: false,
@@ -2375,6 +2383,46 @@ export function addCopyToCodeBlocks(messageElement) {
23752383 }
23762384}
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+ */
2394+function 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+ */
2411+function 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
23792427/**
23802428 * Adds a single message to the chat.
@@ -2391,18 +2439,27 @@ export function addCopyToCodeBlocks(messageElement) {
23912439 */
23922440export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true, insert = true } = {}) {
23932441 // 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;
23972459 const momentDate = timestampToMoment(mes.send_date);
23982460 const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';
23992461
2400- if (mes?.extra?.display_text) {
2401- messageText = mes.extra.display_text;
2402- }
2403-
24042462 let avatarImg = getThumbnailUrl('persona', user_avatar);
2405- const isSystem = mes.is_system;
24062463
24072464 //for non-user messages
24082465 if (!mes.is_user) {
@@ -2426,18 +2483,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
24262483 avatarImg = mes.force_avatar;
24272484 }
24282485
2429- // if mes.extra.uses_system_ui is true, set an override on the sanitizer options
2486+ 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- );
24412487 let newMessage;
24422488
24432489 if (type === 'swipe') {
@@ -2488,7 +2534,6 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
24882534 insertSVGIcon(newMessage, mes.extra);
24892535 }
24902536
2491-
24922537 if (type !== 'swipe' && insert) {
24932538 if (!insertAfter && !insertBefore) {
24942539 chatElement.append(newMessage);
@@ -2512,13 +2557,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
25122557 newMessage.addClass('toolCall');
25132558 }
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
25232562 newMessage.find('.avatar img').on('error', function () {
25242563 $(this).hide();
@@ -2526,8 +2565,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
25262565 });
25272566
25282567 appendMediaToMessage(mes, newMessage, scroll ? SCROLL_BEHAVIOR.ADJUST : SCROLL_BEHAVIOR.NONE);
25292568 newMessage.find('.mes_text').html(messageTextmessageHTML);
2530-
25312569 addCopyToCodeBlocks(newMessage);
25322570
25332571 // Set the swipes counter for all non-user messages.
@@ -11651,14 +11689,16 @@ jQuery(async function () {
1165111689 const oldScroll = chatElement[0].scrollTop;
1165211690 const clone = structuredClone(chat[this_edit_mes_id]);
1165311691 clone.send_date = Date.now();
1165411692 clone.mesconst this_edit_mes_element = $(this).closest('.mes').find('.edit_textarea').val().toString();
11693+ clone.mes = this_edit_mes_element.find('.edit_textarea').val().toString();
1165511694
1165611695 if (power_user.trim_spaces) {
1165711696 clone.mes = clone.mes.trim();
1165811697 }
1165911698
1166011699 chat.splice(Number(this_edit_mes_id) + 1, 0, clone);
1166111700 const newMessageElement = addOneMessage(clone, { insertAfterinsert: this_edit_mes_idfalse });
11701+ this_edit_mes_element.after(newMessageElement);
1166211702
1166311703 updateViewMessageIds();
1166411704 await saveChatConditional();
public/scripts/chats.js+1 -1
@@ -685,7 +685,7 @@ export function formatCreatorNotes(text, avatarId) {
685685 const preference = new StylesPreference(avatarId);
686686 const sanitizeStyles = !preference.get();
687687 const decodeStyleParam = { prefix: sanitizeStyles ? '#creator_notes_spoiler ' : '' };
688688 /** @type {import('dompurify')DOMPurify.Config} */
689689 const config = {
690690 RETURN_DOM: false,
691691 RETURN_DOM_FRAGMENT: false,
public/scripts/slash-commands.js+4 -2
@@ -4642,7 +4642,8 @@ async function messageRoleCallback(args, role) {
46424642 await eventSource.emit(event_types.MESSAGE_EDITED, modifyAt);
46434643 const existingMessage = chatElement.find(`.mes[mesid="${modifyAt}"]`);
46444644 if (existingMessage.length) {
46454645 const newMessageElement = addOneMessage(message, { forceId: modifyAt, insertAfterinsert: modifyAtfalse, scroll: false });
4646+ existingMessage.after(newMessageElement);
46464647 existingMessage.remove();
46474648 }
46484649 await eventSource.emit(event_types.MESSAGE_UPDATED, modifyAt);
@@ -4707,7 +4708,8 @@ async function messageNameCallback(args, name) {
47074708 await eventSource.emit(event_types.MESSAGE_EDITED, modifyAt);
47084709 const existingMessage = chatElement.find(`.mes[mesid="${modifyAt}"]`);
47094710 if (existingMessage.length) {
47104711 const newMessageElement = addOneMessage(message, { forceId: modifyAt, insertAfterinsert: modifyAtfalse, scroll: false });
4712+ existingMessage.after(newMessageElement);
47114713 existingMessage.remove();
47124714 }
47134715 await eventSource.emit(event_types.MESSAGE_UPDATED, modifyAt);