Chore: Add persona lifecycle events (`PERSONA_CREATED`, `PERSONA_UPDATED`, `PERSONA_RENAMED`, `PERSONA_DELETED`) (#5443) * Add persona lifecycle events (created, updated, renamed, deleted) and emit them throughout persona management functions * fix: await event emissions * fix: await event in convertCharacterToPersona * fix: await PERSONA_UPDATED in callbacks * fix: delete multiple personas without reloading the chat * fix: add CHAT_RENAMED event and adjust welcome screen pin update logic --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

7d3d1231a9891c587689834c39a851c29cfb49a4

Wolfsblvt <wolfsblvt@gmail.com>

Signed
4 files changed, +58 -16Ignore whitespace
public/script.js+3 -0
@@ -10610,6 +10610,9 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
1061010610 if (currentChatId) {
1061110611 await reloadCurrentChat();
1061210612 }
10613+
10614+ const eventData = { avatarId: body.avatar_url, groupId, oldFileName: body.original_file, newFileName: body.renamed_file };
10615+ await eventSource.emit(event_types.CHAT_RENAMED, eventData);
1061310616 } catch {
1061410617 await delay(500);
1061510618 await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT);
public/scripts/events.js+5 -0
@@ -50,6 +50,7 @@ export const event_types = {
5050 FORCE_SET_BACKGROUND: 'force_set_background',
5151 CHAT_DELETED: 'chat_deleted',
5252 CHAT_CREATED: 'chat_created',
53+ CHAT_RENAMED: 'chat_renamed',
5354 GROUP_CHAT_DELETED: 'group_chat_deleted',
5455 GROUP_CHAT_CREATED: 'group_chat_created',
5556 GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
@@ -97,6 +98,10 @@ export const event_types = {
9798 WORLDINFO_SCAN_DONE: 'worldinfo_scan_done',
9899 MEDIA_ATTACHMENT_DELETED: 'media_attachment_deleted',
99100 PERSONA_CHANGED: 'persona_changed',
101+ PERSONA_CREATED: 'persona_created',
102+ PERSONA_UPDATED: 'persona_updated',
103+ PERSONA_RENAMED: 'persona_renamed',
104+ PERSONA_DELETED: 'persona_deleted',
100105 TTS_JOB_STARTED: 'tts_job_started',
101106 TTS_AUDIO_READY: 'tts_audio_ready',
102107 TTS_JOB_COMPLETE: 'tts_job_complete',
public/scripts/personas.js+46 -14
@@ -24,7 +24,7 @@ import {
2424} from '../script.js';
2525import { persona_description_positions, power_user } from './power-user.js';
2626import { getTokenCountAsync } from './tokenizers.js';
2727import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler, addLongPressEvent, uuidv4 } from './utils.js';
2828import { debounce_timeout } from './constants.js';
2929import { FILTER_TYPES, FilterHelper } from './filters.js';
3030import { groups, selected_group } from './group-chats.js';
@@ -217,11 +217,12 @@ function getUserAvatarBlock(avatarId) {
217217/**
218218 * Initialize missing personas in the power user settings.
219219 * @param {string[]} avatarsList List of avatar file names
220+ * @returns {Promise<void>}
220221 */
221222async function addMissingPersonas(avatarsList) {
222223 for (const persona of avatarsList) {
223224 if (!power_user.personas[persona]) {
224225 await initPersona(persona, '[Unnamed Persona]', '', '', { silent: true });
225226 }
226227 }
227228}
@@ -249,7 +250,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
249250 }
250251
251252 // If any persona is missing from the power user settings, we add it
252253 await addMissingPersonas(allEntities);
253254 // Before printing the personas, we check if we should enable/disable search sorting
254255 verifyPersonaSearchSortRule();
255256
@@ -429,7 +430,7 @@ export async function createPersona(avatarId) {
429430
430431 const personaDescription = await Popup.show.input(t`Enter a description for this persona:`, t`You can always add or change it later.`, '', { rows: 4 });
431432
432433 await initPersona(avatarId, personaName, personaDescription, '');
433434 if (power_user.persona_show_notifications) {
434435 toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`);
435436 }
@@ -454,7 +455,7 @@ async function createDummyPersona() {
454455
455456 // Date + name (only ASCII) to make it unique
456457 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
457458 await initPersona(avatarId, personaName, '', personaTitle);
458459 await uploadUserAvatar(default_user_avatar, avatarId);
459460}
460461
@@ -464,9 +465,11 @@ async function createDummyPersona() {
464465 * @param {string} personaName Name for the persona
465466 * @param {string} personaDescription Optional description for the persona
466467 * @param {string} personaTitle Optional title for the persona
467468 * @returnsparam {voidobject} [options] Optional settings
469+ * @param {boolean} [options.silent=false] If true, no PERSONA_CREATED event is emitted (used for background migrations)
470+ * @returns {Promise<void>}
468471 */
469472export async function initPersona(avatarId, personaName, personaDescription, personaTitle, { silent = false } = {}) {
470473 power_user.personas[avatarId] = personaName;
471474 power_user.persona_descriptions[avatarId] = {
472475 description: personaDescription || '',
@@ -478,6 +481,10 @@ export function initPersona(avatarId, personaName, personaDescription, personaTi
478481 };
479482
480483 saveSettingsDebounced();
484+
485+ if (!silent) {
486+ await eventSource.emit(event_types.PERSONA_CREATED, { avatarId, name: personaName, description: personaDescription || '', title: personaTitle || '' });
487+ }
481488}
482489
483490/**
@@ -540,6 +547,7 @@ export async function convertCharacterToPersona(characterId = null) {
540547 }
541548
542549 saveSettingsDebounced();
550+ await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: overwriteName, name, description, title: '' });
543551
544552 console.log('Persona for character created');
545553 toastr.success(t`You can now pick ${name} as a persona in the Persona Management menu.`, t`Persona Created`);
@@ -778,6 +786,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) {
778786 delete power_user.persona_descriptions[avatarId].title;
779787 await getUserAvatars(true, avatarId);
780788 saveSettingsDebounced();
789+ await eventSource.emit(event_types.PERSONA_UPDATED, avatarId);
781790 return;
782791 }
783792
@@ -786,6 +795,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) {
786795 console.log(`Updated persona title for ${avatarId} to ${newTitle}`);
787796 await getUserAvatars(true, avatarId);
788797 saveSettingsDebounced();
798+ await eventSource.emit(event_types.PERSONA_UPDATED, avatarId);
789799 return;
790800 }
791801}
@@ -821,6 +831,7 @@ async function renamePersona(avatarId) {
821831 }
822832
823833 saveSettingsDebounced();
834+ await eventSource.emit(event_types.PERSONA_RENAMED, { avatarId, oldName: currentName, newName });
824835 await getUserAvatars(true, avatarId);
825836 updatePersonaUIStates();
826837 setPersonaDescription();
@@ -1013,6 +1024,7 @@ async function lockPersona(type = 'chat') {
10131024 connections: [],
10141025 title: '',
10151026 };
1027+ await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: user_avatar, name: name1, description: '', title: '' });
10161028 }
10171029
10181030 switch (type) {
@@ -1113,13 +1125,15 @@ async function deleteUserAvatar() {
11131125 }
11141126
11151127 saveSettingsDebounced();
1128+ await eventSource.emit(event_types.PERSONA_DELETED, { avatarId, name });
11161129
11171130 // Use the existing mechanism to re-render the persona list and choose the next persona here
1131+ personaLastLoadedChatId = uuidv4(); // Force reload by making a dummy chat id
11181132 await loadPersonaForCurrentChat({ doRender: true });
11191133 }
11201134}
11211135
11221136async function onPersonaDescriptionInput() {
11231137 power_user.persona_description = String($('#persona_description').val());
11241138 countPersonaDescriptionTokens();
11251139
@@ -1145,25 +1159,35 @@ function onPersonaDescriptionInput() {
11451159 .text(power_user.persona_description || $('#user_avatar_block').attr('no_desc_text'))
11461160 .toggleClass('text_muted', !power_user.persona_description);
11471161 saveSettingsDebounced();
1162+
1163+ if (power_user.personas[user_avatar]) {
1164+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1165+ }
11481166}
11491167
11501168async function onPersonaDescriptionDepthValueInput() {
11511169 power_user.persona_description_depth = Number($('#persona_depth_value').val());
11521170
11531171 if (power_user.personas[user_avatar]) {
11541172 const object = getOrCreatePersonaDescriptor();
11551173 object.depth = power_user.persona_description_depth;
1174+ saveSettingsDebounced();
1175+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1176+ return;
11561177 }
11571178
11581179 saveSettingsDebounced();
11591180}
11601181
11611182async function onPersonaDescriptionDepthRoleInput() {
11621183 power_user.persona_description_role = Number($('#persona_depth_role').find(':selected').val());
11631184
11641185 if (power_user.personas[user_avatar]) {
11651186 const object = getOrCreatePersonaDescriptor();
11661187 object.role = power_user.persona_description_role;
1188+ saveSettingsDebounced();
1189+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1190+ return;
11671191 }
11681192
11691193 saveSettingsDebounced();
@@ -1200,7 +1224,7 @@ async function onPersonaLoreButtonClick({ shiftKey, altKey }) {
12001224 worldSelect.append(option);
12011225 }
12021226
12031227 worldSelect.on('change', async function () {
12041228 power_user.persona_description_lorebook = String($(this).val());
12051229
12061230 if (power_user.personas[user_avatar]) {
@@ -1210,12 +1234,16 @@ async function onPersonaLoreButtonClick({ shiftKey, altKey }) {
12101234
12111235 $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook);
12121236 saveSettingsDebounced();
1237+
1238+ if (power_user.personas[user_avatar]) {
1239+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1240+ }
12131241 });
12141242
12151243 await callGenericPopup(template, POPUP_TYPE.TEXT);
12161244}
12171245
12181246async function onPersonaDescriptionPositionInput() {
12191247 power_user.persona_description_position = Number(
12201248 $('#persona_description_position').find(':selected').val(),
12211249 );
@@ -1223,6 +1251,10 @@ function onPersonaDescriptionPositionInput() {
12231251 if (power_user.personas[user_avatar]) {
12241252 const object = getOrCreatePersonaDescriptor();
12251253 object.position = power_user.persona_description_position;
1254+ saveSettingsDebounced();
1255+ await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1256+ $('#persona_depth_position_settings').toggle(power_user.persona_description_position === persona_description_positions.AT_DEPTH);
1257+ return;
12261258 }
12271259
12281260 saveSettingsDebounced();
@@ -1807,7 +1839,7 @@ async function migrateNonPersonaUser() {
18071839 return;
18081840 }
18091841
18101842 await initPersona(user_avatar, name1, '', '', { silent: true });
18111843 setPersonaDescription();
18121844 await getUserAvatars(true, user_avatar);
18131845}
public/scripts/welcome-screen.js+4 -2
@@ -550,7 +550,6 @@ async function renameRecentCharacterChat(avatarId, fileName) {
550550 newFileName: newName,
551551 loader: false,
552552 });
553- PinnedChatsManager.rename({ avatar: avatarId, group: '', file_name: fileName }, newName);
554553 await updateRemoteChatName(characterId, newName);
555554 await refreshWelcomeScreen();
556555 toastr.success(t`Chat renamed.`);
@@ -584,7 +583,6 @@ async function renameRecentGroupChat(groupId, fileName) {
584583 newFileName: String(newName),
585584 loader: false,
586585 });
587- PinnedChatsManager.rename({ avatar: '', group: groupId, file_name: fileName }, String(newName));
588586 await refreshWelcomeScreen();
589587 toastr.success(t`Group chat renamed.`);
590588 } catch (error) {
@@ -944,4 +942,8 @@ export function initWelcomeScreen() {
944942 accountStorage.setItem(assistantAvatarKey, newAvatar);
945943 }
946944 });
945+
946+ eventSource.on(event_types.CHAT_RENAMED, async ({ avatarId, groupId, oldFileName, newFileName }) => {
947+ PinnedChatsManager.rename({ avatar: avatarId, group: groupId, file_name: oldFileName }, newFileName);
948+ });
947949}