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 -16Showing whitespace changes
public/script.js+3 -0
@@ -10610,6 +10610,9 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
10610 if (currentChatId) {10610 if (currentChatId) {
10611 await reloadCurrentChat();10611 await reloadCurrentChat();
10612 }10612 }
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);
10613 } catch {10616 } catch {
10614 await delay(500);10617 await delay(500);
10615 await callGenericPopup('An error has occurred. Chat was not renamed.', POPUP_TYPE.TEXT);10618 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 = {
50 FORCE_SET_BACKGROUND: 'force_set_background',50 FORCE_SET_BACKGROUND: 'force_set_background',
51 CHAT_DELETED: 'chat_deleted',51 CHAT_DELETED: 'chat_deleted',
52 CHAT_CREATED: 'chat_created',52 CHAT_CREATED: 'chat_created',
53 CHAT_RENAMED: 'chat_renamed',
53 GROUP_CHAT_DELETED: 'group_chat_deleted',54 GROUP_CHAT_DELETED: 'group_chat_deleted',
54 GROUP_CHAT_CREATED: 'group_chat_created',55 GROUP_CHAT_CREATED: 'group_chat_created',
55 GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',56 GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts',
@@ -97,6 +98,10 @@ export const event_types = {
97 WORLDINFO_SCAN_DONE: 'worldinfo_scan_done',98 WORLDINFO_SCAN_DONE: 'worldinfo_scan_done',
98 MEDIA_ATTACHMENT_DELETED: 'media_attachment_deleted',99 MEDIA_ATTACHMENT_DELETED: 'media_attachment_deleted',
99 PERSONA_CHANGED: 'persona_changed',100 PERSONA_CHANGED: 'persona_changed',
101 PERSONA_CREATED: 'persona_created',
102 PERSONA_UPDATED: 'persona_updated',
103 PERSONA_RENAMED: 'persona_renamed',
104 PERSONA_DELETED: 'persona_deleted',
100 TTS_JOB_STARTED: 'tts_job_started',105 TTS_JOB_STARTED: 'tts_job_started',
101 TTS_AUDIO_READY: 'tts_audio_ready',106 TTS_AUDIO_READY: 'tts_audio_ready',
102 TTS_JOB_COMPLETE: 'tts_job_complete',107 TTS_JOB_COMPLETE: 'tts_job_complete',
public/scripts/personas.js+46 -14
@@ -24,7 +24,7 @@ import {
24} from '../script.js';24} from '../script.js';
25import { persona_description_positions, power_user } from './power-user.js';25import { persona_description_positions, power_user } from './power-user.js';
26import { getTokenCountAsync } from './tokenizers.js';26import { getTokenCountAsync } from './tokenizers.js';
27import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler, addLongPressEvent } from './utils.js';27import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler, addLongPressEvent, uuidv4 } from './utils.js';
28import { debounce_timeout } from './constants.js';28import { debounce_timeout } from './constants.js';
29import { FILTER_TYPES, FilterHelper } from './filters.js';29import { FILTER_TYPES, FilterHelper } from './filters.js';
30import { groups, selected_group } from './group-chats.js';30import { groups, selected_group } from './group-chats.js';
@@ -217,11 +217,12 @@ function getUserAvatarBlock(avatarId) {
217/**217/**
218 * Initialize missing personas in the power user settings.218 * Initialize missing personas in the power user settings.
219 * @param {string[]} avatarsList List of avatar file names219 * @param {string[]} avatarsList List of avatar file names
220 * @returns {Promise<void>}
220 */221 */
221function addMissingPersonas(avatarsList) {222async function addMissingPersonas(avatarsList) {
222 for (const persona of avatarsList) {223 for (const persona of avatarsList) {
223 if (!power_user.personas[persona]) {224 if (!power_user.personas[persona]) {
224 initPersona(persona, '[Unnamed Persona]', '', '');225 await initPersona(persona, '[Unnamed Persona]', '', '', { silent: true });
225 }226 }
226 }227 }
227}228}
@@ -249,7 +250,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
249 }250 }
250251
251 // If any persona is missing from the power user settings, we add it252 // If any persona is missing from the power user settings, we add it
252 addMissingPersonas(allEntities);253 await addMissingPersonas(allEntities);
253 // Before printing the personas, we check if we should enable/disable search sorting254 // Before printing the personas, we check if we should enable/disable search sorting
254 verifyPersonaSearchSortRule();255 verifyPersonaSearchSortRule();
255256
@@ -429,7 +430,7 @@ export async function createPersona(avatarId) {
429430
430 const personaDescription = await Popup.show.input(t`Enter a description for this persona:`, t`You can always add or change it later.`, '', { rows: 4 });431 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
432 initPersona(avatarId, personaName, personaDescription, '');433 await initPersona(avatarId, personaName, personaDescription, '');
433 if (power_user.persona_show_notifications) {434 if (power_user.persona_show_notifications) {
434 toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`);435 toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`);
435 }436 }
@@ -454,7 +455,7 @@ async function createDummyPersona() {
454455
455 // Date + name (only ASCII) to make it unique456 // Date + name (only ASCII) to make it unique
456 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;457 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
457 initPersona(avatarId, personaName, '', personaTitle);458 await initPersona(avatarId, personaName, '', personaTitle);
458 await uploadUserAvatar(default_user_avatar, avatarId);459 await uploadUserAvatar(default_user_avatar, avatarId);
459}460}
460461
@@ -464,9 +465,11 @@ async function createDummyPersona() {
464 * @param {string} personaName Name for the persona465 * @param {string} personaName Name for the persona
465 * @param {string} personaDescription Optional description for the persona466 * @param {string} personaDescription Optional description for the persona
466 * @param {string} personaTitle Optional title for the persona467 * @param {string} personaTitle Optional title for the persona
467 * @returns {void}468 * @param {object} [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>}
468 */471 */
469export function initPersona(avatarId, personaName, personaDescription, personaTitle) {472export async function initPersona(avatarId, personaName, personaDescription, personaTitle, { silent = false } = {}) {
470 power_user.personas[avatarId] = personaName;473 power_user.personas[avatarId] = personaName;
471 power_user.persona_descriptions[avatarId] = {474 power_user.persona_descriptions[avatarId] = {
472 description: personaDescription || '',475 description: personaDescription || '',
@@ -478,6 +481,10 @@ export function initPersona(avatarId, personaName, personaDescription, personaTi
478 };481 };
479482
480 saveSettingsDebounced();483 saveSettingsDebounced();
484
485 if (!silent) {
486 await eventSource.emit(event_types.PERSONA_CREATED, { avatarId, name: personaName, description: personaDescription || '', title: personaTitle || '' });
487 }
481}488}
482489
483/**490/**
@@ -540,6 +547,7 @@ export async function convertCharacterToPersona(characterId = null) {
540 }547 }
541548
542 saveSettingsDebounced();549 saveSettingsDebounced();
550 await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: overwriteName, name, description, title: '' });
543551
544 console.log('Persona for character created');552 console.log('Persona for character created');
545 toastr.success(t`You can now pick ${name} as a persona in the Persona Management menu.`, t`Persona Created`);553 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) {
778 delete power_user.persona_descriptions[avatarId].title;786 delete power_user.persona_descriptions[avatarId].title;
779 await getUserAvatars(true, avatarId);787 await getUserAvatars(true, avatarId);
780 saveSettingsDebounced();788 saveSettingsDebounced();
789 await eventSource.emit(event_types.PERSONA_UPDATED, avatarId);
781 return;790 return;
782 }791 }
783792
@@ -786,6 +795,7 @@ async function editPersonaTitle(popup, avatarId, currentTitle) {
786 console.log(`Updated persona title for ${avatarId} to ${newTitle}`);795 console.log(`Updated persona title for ${avatarId} to ${newTitle}`);
787 await getUserAvatars(true, avatarId);796 await getUserAvatars(true, avatarId);
788 saveSettingsDebounced();797 saveSettingsDebounced();
798 await eventSource.emit(event_types.PERSONA_UPDATED, avatarId);
789 return;799 return;
790 }800 }
791}801}
@@ -821,6 +831,7 @@ async function renamePersona(avatarId) {
821 }831 }
822832
823 saveSettingsDebounced();833 saveSettingsDebounced();
834 await eventSource.emit(event_types.PERSONA_RENAMED, { avatarId, oldName: currentName, newName });
824 await getUserAvatars(true, avatarId);835 await getUserAvatars(true, avatarId);
825 updatePersonaUIStates();836 updatePersonaUIStates();
826 setPersonaDescription();837 setPersonaDescription();
@@ -1013,6 +1024,7 @@ async function lockPersona(type = 'chat') {
1013 connections: [],1024 connections: [],
1014 title: '',1025 title: '',
1015 };1026 };
1027 await eventSource.emit(event_types.PERSONA_CREATED, { avatarId: user_avatar, name: name1, description: '', title: '' });
1016 }1028 }
10171029
1018 switch (type) {1030 switch (type) {
@@ -1113,13 +1125,15 @@ async function deleteUserAvatar() {
1113 }1125 }
11141126
1115 saveSettingsDebounced();1127 saveSettingsDebounced();
1128 await eventSource.emit(event_types.PERSONA_DELETED, { avatarId, name });
11161129
1117 // Use the existing mechanism to re-render the persona list and choose the next persona here1130 // 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
1118 await loadPersonaForCurrentChat({ doRender: true });1132 await loadPersonaForCurrentChat({ doRender: true });
1119 }1133 }
1120}1134}
11211135
1122function onPersonaDescriptionInput() {1136async function onPersonaDescriptionInput() {
1123 power_user.persona_description = String($('#persona_description').val());1137 power_user.persona_description = String($('#persona_description').val());
1124 countPersonaDescriptionTokens();1138 countPersonaDescriptionTokens();
11251139
@@ -1145,25 +1159,35 @@ function onPersonaDescriptionInput() {
1145 .text(power_user.persona_description || $('#user_avatar_block').attr('no_desc_text'))1159 .text(power_user.persona_description || $('#user_avatar_block').attr('no_desc_text'))
1146 .toggleClass('text_muted', !power_user.persona_description);1160 .toggleClass('text_muted', !power_user.persona_description);
1147 saveSettingsDebounced();1161 saveSettingsDebounced();
1162
1163 if (power_user.personas[user_avatar]) {
1164 await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1165 }
1148}1166}
11491167
1150function onPersonaDescriptionDepthValueInput() {1168async function onPersonaDescriptionDepthValueInput() {
1151 power_user.persona_description_depth = Number($('#persona_depth_value').val());1169 power_user.persona_description_depth = Number($('#persona_depth_value').val());
11521170
1153 if (power_user.personas[user_avatar]) {1171 if (power_user.personas[user_avatar]) {
1154 const object = getOrCreatePersonaDescriptor();1172 const object = getOrCreatePersonaDescriptor();
1155 object.depth = power_user.persona_description_depth;1173 object.depth = power_user.persona_description_depth;
1174 saveSettingsDebounced();
1175 await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1176 return;
1156 }1177 }
11571178
1158 saveSettingsDebounced();1179 saveSettingsDebounced();
1159}1180}
11601181
1161function onPersonaDescriptionDepthRoleInput() {1182async function onPersonaDescriptionDepthRoleInput() {
1162 power_user.persona_description_role = Number($('#persona_depth_role').find(':selected').val());1183 power_user.persona_description_role = Number($('#persona_depth_role').find(':selected').val());
11631184
1164 if (power_user.personas[user_avatar]) {1185 if (power_user.personas[user_avatar]) {
1165 const object = getOrCreatePersonaDescriptor();1186 const object = getOrCreatePersonaDescriptor();
1166 object.role = power_user.persona_description_role;1187 object.role = power_user.persona_description_role;
1188 saveSettingsDebounced();
1189 await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1190 return;
1167 }1191 }
11681192
1169 saveSettingsDebounced();1193 saveSettingsDebounced();
@@ -1200,7 +1224,7 @@ async function onPersonaLoreButtonClick({ shiftKey, altKey }) {
1200 worldSelect.append(option);1224 worldSelect.append(option);
1201 }1225 }
12021226
1203 worldSelect.on('change', function () {1227 worldSelect.on('change', async function () {
1204 power_user.persona_description_lorebook = String($(this).val());1228 power_user.persona_description_lorebook = String($(this).val());
12051229
1206 if (power_user.personas[user_avatar]) {1230 if (power_user.personas[user_avatar]) {
@@ -1210,12 +1234,16 @@ async function onPersonaLoreButtonClick({ shiftKey, altKey }) {
12101234
1211 $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook);1235 $('#persona_lore_button').toggleClass('world_set', !!power_user.persona_description_lorebook);
1212 saveSettingsDebounced();1236 saveSettingsDebounced();
1237
1238 if (power_user.personas[user_avatar]) {
1239 await eventSource.emit(event_types.PERSONA_UPDATED, user_avatar);
1240 }
1213 });1241 });
12141242
1215 await callGenericPopup(template, POPUP_TYPE.TEXT);1243 await callGenericPopup(template, POPUP_TYPE.TEXT);
1216}1244}
12171245
1218function onPersonaDescriptionPositionInput() {1246async function onPersonaDescriptionPositionInput() {
1219 power_user.persona_description_position = Number(1247 power_user.persona_description_position = Number(
1220 $('#persona_description_position').find(':selected').val(),1248 $('#persona_description_position').find(':selected').val(),
1221 );1249 );
@@ -1223,6 +1251,10 @@ function onPersonaDescriptionPositionInput() {
1223 if (power_user.personas[user_avatar]) {1251 if (power_user.personas[user_avatar]) {
1224 const object = getOrCreatePersonaDescriptor();1252 const object = getOrCreatePersonaDescriptor();
1225 object.position = power_user.persona_description_position;1253 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;
1226 }1258 }
12271259
1228 saveSettingsDebounced();1260 saveSettingsDebounced();
@@ -1807,7 +1839,7 @@ async function migrateNonPersonaUser() {
1807 return;1839 return;
1808 }1840 }
18091841
1810 initPersona(user_avatar, name1, '', '');1842 await initPersona(user_avatar, name1, '', '', { silent: true });
1811 setPersonaDescription();1843 setPersonaDescription();
1812 await getUserAvatars(true, user_avatar);1844 await getUserAvatars(true, user_avatar);
1813}1845}
public/scripts/welcome-screen.js+4 -2
@@ -550,7 +550,6 @@ async function renameRecentCharacterChat(avatarId, fileName) {
550 newFileName: newName,550 newFileName: newName,
551 loader: false,551 loader: false,
552 });552 });
553 PinnedChatsManager.rename({ avatar: avatarId, group: '', file_name: fileName }, newName);
554 await updateRemoteChatName(characterId, newName);553 await updateRemoteChatName(characterId, newName);
555 await refreshWelcomeScreen();554 await refreshWelcomeScreen();
556 toastr.success(t`Chat renamed.`);555 toastr.success(t`Chat renamed.`);
@@ -584,7 +583,6 @@ async function renameRecentGroupChat(groupId, fileName) {
584 newFileName: String(newName),583 newFileName: String(newName),
585 loader: false,584 loader: false,
586 });585 });
587 PinnedChatsManager.rename({ avatar: '', group: groupId, file_name: fileName }, String(newName));
588 await refreshWelcomeScreen();586 await refreshWelcomeScreen();
589 toastr.success(t`Group chat renamed.`);587 toastr.success(t`Group chat renamed.`);
590 } catch (error) {588 } catch (error) {
@@ -944,4 +942,8 @@ export function initWelcomeScreen() {
944 accountStorage.setItem(assistantAvatarKey, newAvatar);942 accountStorage.setItem(assistantAvatarKey, newAvatar);
945 }943 }
946 });944 });
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 });
947}949}