Reprint non-tainted group chat on switching personas (#4603) * fix: reprint non-tainted group chat on switching personas * feat: mark chat as tainted when sending user message * feat: mark chat as tainted when adding custom messages or captions * feat: export createOrEditCharacter function and integrate it into retriggerFirstMessageOnEmptyChat * fix: optimize loadPersonaForCurrentChat by storing current chat ID in a variable * Improve conditions of first message retrigger --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

9463d1a1529255b0fd390eeb60f9a4d6fd9ef656

Wolfsblvt <wolfsblvt@gmail.com>

Signed
5 files changed, +58 -29Showing whitespace changes
public/script.js+3 -1
@@ -393,7 +393,7 @@ let isExportPopupOpen = false;
393393
394394// Saved here for performance reasons
395395const messageTemplate = $('#message_template .mes');
396396export const chatElement = $('#chat');
397397
398398let dialogueResolve = null;
399399let dialogueCloseStop = false;
@@ -5042,6 +5042,8 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
50425042 await populateFileAttachment(message);
50435043 statMesProcess(message, 'user', characters, this_chid, '');
50445044
5045+ chat_metadata['tainted'] = true;
5046+
50455047 if (typeof insertAt === 'number' && insertAt >= 0 && insertAt <= chat.length) {
50465048 chat.splice(insertAt, 0, message);
50475049 await saveChatConditional();
public/scripts/extensions/caption/index.js+2 -1
@@ -1,6 +1,6 @@
11import { ensureImageFormatSupported, getBase64Async, getFileExtension, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
22import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
33import { appendMediaToMessage, chat_metadata, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
44import { getMessageTimeStamp } from '../../RossAscends-mods.js';
55import { SECRET_KEYS, secret_state } from '../../secrets.js';
66import { getMultimodalCaption } from '../shared.js';
@@ -174,6 +174,7 @@ async function sendCaptionedMessage(caption, image) {
174174 inline_image: !!extension_settings.caption.show_in_chat,
175175 },
176176 };
177+ chat_metadata['tainted'] = true;
177178 context.chat.push(message);
178179 const messageId = context.chat.length - 1;
179180 await eventSource.emit(event_types.MESSAGE_SENT, messageId);
public/scripts/group-chats.js+9 -8
@@ -76,6 +76,7 @@ import {
7676 depth_prompt_role_default,
7777 shouldAutoContinue,
7878 unshallowCharacter,
79+ chatElement,
7980} from '../script.js';
8081import { printTagList, createTagMapFromList, applyTagsOnCharacterSelect, tag_map, applyTagsOnGroupSelect } from './tags.js';
8182import { FILTER_TYPES, FilterHelper } from './filters.js';
@@ -236,17 +237,13 @@ export async function getGroupChat(groupId, reload = false) {
236237 const chat_id = group.chat_id;
237238 const data = await loadGroupChat(chat_id);
238239 const metadata = group.chat_metadata ?? {};
239240 letconst freshChat = false!metadata.tainted;
240241
241242 await loadItemizedPrompts(getCurrentChatId());
242243
243- if (Array.isArray(data) && data.length) {
244- data[0].is_group = true;
245- chat.splice(0, chat.length, ...data);
246- await printMessages();
247- } else {
248- freshChat = !metadata.tainted;
249244 if (group && Array.isArray(group.members) && freshChat) {
245+ chat.splice(0, chat.length);
246+ chatElement.find('.mes').remove();
250247 for (let member of group.members) {
251248 const character = characters.find(x => x.avatar === member || x.name === member);
252249 if (!character) {
@@ -266,7 +263,11 @@ export async function getGroupChat(groupId, reload = false) {
266263 await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1), 'first_message');
267264 }
268265 await saveGroupChat(groupId, false);
269- }
266+ } else if (Array.isArray(data) && data.length) {
267+ data[0].is_group = true;
268+ chat.splice(0, chat.length, ...data);
269+ chatElement.find('.mes').remove();
270+ await printMessages();
270271 }
271272
272273 updateChatMetadata(metadata, true);
public/scripts/personas.js+36 -19
@@ -4,9 +4,11 @@ import {
44 characters,
55 chat,
66 chat_metadata,
7+ createOrEditCharacter,
78 default_user_avatar,
89 eventSource,
910 event_types,
11+ getCurrentChatId,
1012 getRequestHeaders,
1113 getThumbnailUrl,
1214 groupToEntity,
@@ -69,6 +71,9 @@ export let user_avatar = '';
6971/** @type {FilterHelper} Filter helper for the persona list */
7072export const personasFilter = new FilterHelper(debounce(getUserAvatars, debounce_timeout.quick));
7173
74+/** @type {string} The last loaded chat id to remember for persona loading */
75+let personaLastLoadedChatId = null;
76+
7277/** @type {function(string): void} */
7378let navigateToAvatar = () => { };
7479
@@ -107,12 +112,16 @@ export function initUserAvatar(avatar) {
107112 * @param {boolean} [options.toastPersonaNameChange=true] Whether to show a toast when the persona name is changed
108113 * @param {boolean} [options.navigateToCurrent=false] Whether to navigate to the current persona after setting the avatar
109114 */
110115export async function setUserAvatar(imgfile, { toastPersonaNameChange = true, navigateToCurrent = false } = {}) {
116+ const currentUserAvatar = user_avatar;
111117 user_avatar = imgfile && typeof imgfile === 'string' ? imgfile : $(this).attr('data-avatar-id');
118+ if (currentUserAvatar === user_avatar) {
119+ return;
120+ }
112121 reloadUserAvatar();
113122 updatePersonaUIStates({ navigateToCurrent: navigateToCurrent });
114123 selectCurrentPersona({ toastPersonaNameChange: toastPersonaNameChange });
115124 await retriggerFirstMessageOnEmptyChat();
116125 saveSettingsDebounced();
117126 $('.zoomed_avatar[forchar]').remove();
118127}
@@ -732,13 +741,13 @@ export async function askForPersonaSelection(title, text, personas, { okButton =
732741/**
733742 * Automatically selects a persona based on the given name if a matching persona exists.
734743 * @param {string} name - The name to search for
735744 * @returns {Promise<boolean>} True if a matching persona was found and selected, false otherwise
736745 */
737746export async function autoSelectPersona(name) {
738747 for (const [key, value] of Object.entries(power_user.personas)) {
739748 if (value === name) {
740749 console.log(`Auto-selecting persona ${key} for name ${name}`);
741750 await setUserAvatar(key);
742751 return true;
743752 }
744753 }
@@ -1429,13 +1438,17 @@ function getPersonaTemporaryLockInfo() {
14291438 * @returns {Promise<boolean>} - A promise that resolves to a boolean indicating whether a persona was selected
14301439 */
14311440async function loadPersonaForCurrentChat({ doRender = false } = {}) {
1441+ const currentChatId = getCurrentChatId();
1442+ if (currentChatId === personaLastLoadedChatId) return;
1443+ personaLastLoadedChatId = currentChatId;
1444+
14321445 // Cache persona list to check if they exist
14331446 const userAvatars = await getUserAvatars(doRender);
14341447
14351448 // Check if the user avatar is set and exists in the list of user avatars
14361449 if (userAvatars.length && !userAvatars.includes(user_avatar)) {
14371450 console.log(`User avatar ${user_avatar} not found in user avatars list, pick the first available one`);
14381451 await setUserAvatar(userAvatars[0], { toastPersonaNameChange: false, navigateToCurrent: true });
14391452 }
14401453
14411454 // Define a persona for this chat
@@ -1530,7 +1543,7 @@ async function loadPersonaForCurrentChat({ doRender = false } = {}) {
15301543 // Persona avatar found, select it
15311544 if (chatPersona && user_avatar !== chatPersona) {
15321545 const willAutoLock = power_user.persona_auto_lock && user_avatar !== chat_metadata['persona'];
15331546 await setUserAvatar(chatPersona, { toastPersonaNameChange: false, navigateToCurrent: true });
15341547
15351548 if (power_user.persona_show_notifications) {
15361549 let message = t`Auto-selected persona based on ${connectType} connection.<br />Your messages will now be sent as ${power_user.personas[chatPersona]}.`;
@@ -1542,7 +1555,7 @@ async function loadPersonaForCurrentChat({ doRender = false } = {}) {
15421555 }
15431556 // Even if it's the same persona, we still might need to auto-lock to chat if that's enabled
15441557 else if (chatPersona && power_user.persona_auto_lock && !chat_metadata['persona']) {
15451558 await lockPersona('chat');
15461559 }
15471560
15481561 updatePersonaUIStates();
@@ -1606,7 +1619,7 @@ export async function showCharConnections() {
16061619
16071620 // One of the persona was selected. So load it.
16081621 if (!isRemoving && selectedPersona) {
16091622 await setUserAvatar(selectedPersona, { toastPersonaNameChange: false });
16101623 if (power_user.persona_show_notifications) {
16111624 toastr.success(t`Selected persona ${power_user.personas[selectedPersona]} for current chat.`, t`Connected Persona Selected`);
16121625 }
@@ -1736,12 +1749,16 @@ async function syncUserNameToPersona() {
17361749
17371750/**
17381751 * Retriggers the first message to reload it from the char definition.
1739- *
1740- * Only works if only the first message is present, and not in group mode.
17411752 */
17421753export async function retriggerFirstMessageOnEmptyChat() {
1743- if (Number(this_chid) >= 0 && !selected_group && chat.length === 1) {
1754+ if (chat_metadata.tainted) {
1744- $('#firstmessage_textarea').trigger('input');
1755+ return;
1756+ }
1757+ if (selected_group) {
1758+ await reloadCurrentChat();
1759+ }
1760+ if (!selected_group && Number(this_chid) >= 0 && chat.length === 1) {
1761+ await createOrEditCharacter();
17451762 }
17461763}
17471764
@@ -1838,9 +1855,9 @@ async function lockPersonaCallback(_args, value) {
18381855 * Sets a persona name and optionally an avatar.
18391856 * @param {{mode: 'lookup' | 'temp' | 'all'}} namedArgs Named arguments
18401857 * @param {string} name Name to set
18411858 * @returns {Promise<string>}
18421859 */
18431860async function setNameCallback({ mode = 'all' }, name) {
18441861 if (!name) {
18451862 toastr.warning('You must specify a name to change to');
18461863 return '';
@@ -1858,7 +1875,7 @@ function setNameCallback({ mode = 'all' }, name) {
18581875 let persona = Object.entries(power_user.personas).find(([avatar, _]) => avatar === name)?.[1];
18591876 if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1];
18601877 if (persona) {
18611878 await autoSelectPersona(persona);
18621879 return '';
18631880 } else if (mode === 'lookup') {
18641881 toastr.warning(`Persona ${name} not found`);
@@ -2016,9 +2033,9 @@ export async function initPersonas() {
20162033 $('#sync_name_button').on('click', syncUserNameToPersona);
20172034 $('#avatar_upload_file').on('change', changeUserAvatar);
20182035
20192036 $(document).on('click', '#user_avatar_block .avatar-container', async function () {
20202037 const imgfile = $(this).attr('data-avatar-id');
20212038 await setUserAvatar(imgfile);
20222039 });
20232040
20242041 $('#persona_rename_button').on('click', () => renamePersona(user_avatar));
public/scripts/slash-commands.js+8 -0
@@ -4585,6 +4585,8 @@ export async function sendMessageAs(args, text) {
45854585 insertAt = chat.length + insertAt;
45864586 }
45874587
4588+ chat_metadata['tainted'] = true;
4589+
45884590 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
45894591 chat.splice(insertAt, 0, message);
45904592 await saveChatConditional();
@@ -4635,6 +4637,8 @@ export async function sendNarratorMessage(args, text) {
46354637 insertAt = chat.length + insertAt;
46364638 }
46374639
4640+ chat_metadata['tainted'] = true;
4641+
46384642 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
46394643 chat.splice(insertAt, 0, message);
46404644 await saveChatConditional();
@@ -4685,6 +4689,8 @@ export async function promptQuietForLoudResponse(who, text) {
46854689 },
46864690 };
46874691
4692+ chat_metadata['tainted'] = true;
4693+
46884694 chat.push(message);
46894695 await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
46904696 addOneMessage(message);
@@ -4719,6 +4725,8 @@ async function sendCommentMessage(args, text) {
47194725 insertAt = chat.length + insertAt;
47204726 }
47214727
4728+ chat_metadata['tainted'] = true;
4729+
47224730 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
47234731 chat.splice(insertAt, 0, message);
47244732 await saveChatConditional();