Add persona titles (#4224) * Add persona titles * Refactor persona title editing

51904c2f1058f19b3698035abd7a607df0245134

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
5 files changed, +78 -11Showing whitespace changes
public/css/toggle-dependent.css+2 -1
@@ -93,7 +93,8 @@ body.charListGrid #rm_print_characters_block .group_select .ch_name,
93body.charListGrid #rm_print_characters_block .group_select .group_select_counter,93body.charListGrid #rm_print_characters_block .group_select .group_select_counter,
94#user_avatar_block.gridView .avatar-container .ch_name,94#user_avatar_block.gridView .avatar-container .ch_name,
95#user_avatar_block.gridView .avatar-container .bogus_folder_counter,95#user_avatar_block.gridView .avatar-container .bogus_folder_counter,
96#user_avatar_block.gridView .avatar-container .group_select_counter {96#user_avatar_block.gridView .avatar-container .group_select_counter,
97#user_avatar_block.gridView .avatar-container .ch_additional_info {
97 width: 100%;98 width: 100%;
98 max-width: 100px;99 max-width: 100px;
99 text-align: center;100 text-align: center;
public/index.html+1 -0
@@ -5934,6 +5934,7 @@
5934 <div class="flex-container wide100pLess70px character_select_container">5934 <div class="flex-container wide100pLess70px character_select_container">
5935 <div class="wide100p character_name_block">5935 <div class="wide100p character_name_block">
5936 <span class="ch_name flex1"></span>5936 <span class="ch_name flex1"></span>
5937 <small class="ch_additional_info"></small>
5937 </div>5938 </div>
5938 <div class="ch_description"></div>5939 <div class="ch_description"></div>
5939 <div class="avatar_container_states buttons_block">5940 <div class="avatar_container_states buttons_block">
public/scripts/personas.js+73 -9
@@ -26,7 +26,7 @@ import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureI
26import { debounce_timeout } from './constants.js';26import { debounce_timeout } from './constants.js';
27import { FILTER_TYPES, FilterHelper } from './filters.js';27import { FILTER_TYPES, FilterHelper } from './filters.js';
28import { groups, selected_group } from './group-chats.js';28import { groups, selected_group } from './group-chats.js';
29import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';29import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
30import { t } from './i18n.js';30import { t } from './i18n.js';
31import { openWorldInfoEditor, world_names } from './world-info.js';31import { openWorldInfoEditor, world_names } from './world-info.js';
32import { renderTemplateAsync } from './templates.js';32import { renderTemplateAsync } from './templates.js';
@@ -183,9 +183,11 @@ function getUserAvatarBlock(avatarId) {
183 const template = $('#user_avatar_template .avatar-container').clone();183 const template = $('#user_avatar_template .avatar-container').clone();
184 const personaName = power_user.personas[avatarId];184 const personaName = power_user.personas[avatarId];
185 const personaDescription = power_user.persona_descriptions[avatarId]?.description;185 const personaDescription = power_user.persona_descriptions[avatarId]?.description;
186 const personaTitle = power_user.persona_descriptions[avatarId]?.title;
186187
187 template.find('.ch_name').text(personaName || '[Unnamed Persona]');188 template.find('.ch_name').text(personaName || '[Unnamed Persona]');
188 template.find('.ch_description').text(personaDescription || $('#user_avatar_block').attr('no_desc_text')).toggleClass('text_muted', !personaDescription);189 template.find('.ch_description').text(personaDescription || $('#user_avatar_block').attr('no_desc_text')).toggleClass('text_muted', !personaDescription);
190 template.find('.ch_additional_info').text(personaTitle || '');
189 template.attr('data-avatar-id', avatarId);191 template.attr('data-avatar-id', avatarId);
190 template.find('.avatar').attr('data-avatar-id', avatarId).attr('title', avatarId);192 template.find('.avatar').attr('data-avatar-id', avatarId).attr('title', avatarId);
191 template.toggleClass('default_persona', avatarId === power_user.default_persona);193 template.toggleClass('default_persona', avatarId === power_user.default_persona);
@@ -209,7 +211,7 @@ function getUserAvatarBlock(avatarId) {
209function addMissingPersonas(avatarsList) {211function addMissingPersonas(avatarsList) {
210 for (const persona of avatarsList) {212 for (const persona of avatarsList) {
211 if (!power_user.personas[persona]) {213 if (!power_user.personas[persona]) {
212 initPersona(persona, '[Unnamed Persona]', '');214 initPersona(persona, '[Unnamed Persona]', '', '');
213 }215 }
214 }216 }
215}217}
@@ -415,23 +417,32 @@ export async function createPersona(avatarId) {
415417
416 const personaDescription = await Popup.show.input(t`Enter a description for this persona:`, t`You can always add or change it later.`, '', { rows: 4 });418 const personaDescription = await Popup.show.input(t`Enter a description for this persona:`, t`You can always add or change it later.`, '', { rows: 4 });
417419
418 initPersona(avatarId, personaName, personaDescription);420 initPersona(avatarId, personaName, personaDescription, '');
419 if (power_user.persona_show_notifications) {421 if (power_user.persona_show_notifications) {
420 toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`);422 toastr.success(t`You can now pick ${personaName} as a persona in the Persona Management menu.`, t`Persona Created`);
421 }423 }
422}424}
423425
424async function createDummyPersona() {426async function createDummyPersona() {
425 const personaName = await Popup.show.input(t`Enter a name for this persona:`, null);427 const popup = new Popup(t`Enter a name for this persona:`, POPUP_TYPE.INPUT, '', {
428 customInputs: [{
429 id: 'persona_title',
430 type: 'text',
431 label: t`Persona Title (optional, display only)`,
432 }],
433 });
426434
427 if (!personaName) {435 const personaName = await popup.show();
436 const personaTitle = String(popup.inputResults.get('persona_title') || '').trim();
437
438 if (!personaName || typeof personaName !== 'string') {
428 console.debug('User cancelled creating dummy persona');439 console.debug('User cancelled creating dummy persona');
429 return;440 return;
430 }441 }
431442
432 // Date + name (only ASCII) to make it unique443 // Date + name (only ASCII) to make it unique
433 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;444 const avatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`;
434 initPersona(avatarId, personaName, '');445 initPersona(avatarId, personaName, '', personaTitle);
435 await uploadUserAvatar(default_user_avatar, avatarId);446 await uploadUserAvatar(default_user_avatar, avatarId);
436}447}
437448
@@ -440,9 +451,10 @@ async function createDummyPersona() {
440 * @param {string} avatarId User avatar id451 * @param {string} avatarId User avatar id
441 * @param {string} personaName Name for the persona452 * @param {string} personaName Name for the persona
442 * @param {string} personaDescription Optional description for the persona453 * @param {string} personaDescription Optional description for the persona
454 * @param {string} personaTitle Optional title for the persona
443 * @returns {void}455 * @returns {void}
444 */456 */
445export function initPersona(avatarId, personaName, personaDescription) {457export function initPersona(avatarId, personaName, personaDescription, personaTitle) {
446 power_user.personas[avatarId] = personaName;458 power_user.personas[avatarId] = personaName;
447 power_user.persona_descriptions[avatarId] = {459 power_user.persona_descriptions[avatarId] = {
448 description: personaDescription || '',460 description: personaDescription || '',
@@ -450,6 +462,7 @@ export function initPersona(avatarId, personaName, personaDescription) {
450 depth: DEFAULT_DEPTH,462 depth: DEFAULT_DEPTH,
451 role: DEFAULT_ROLE,463 role: DEFAULT_ROLE,
452 lorebook: '',464 lorebook: '',
465 title: personaTitle || '',
453 };466 };
454467
455 saveSettingsDebounced();468 saveSettingsDebounced();
@@ -506,6 +519,7 @@ export async function convertCharacterToPersona(characterId = null) {
506 depth: DEFAULT_DEPTH,519 depth: DEFAULT_DEPTH,
507 role: DEFAULT_ROLE,520 role: DEFAULT_ROLE,
508 lorebook: '',521 lorebook: '',
522 title: '',
509 };523 };
510524
511 // If the user is currently using this persona, update the description525 // If the user is currently using this persona, update the description
@@ -730,13 +744,58 @@ export function autoSelectPersona(name) {
730}744}
731745
732/**746/**
747 * Edits the title of a persona based on the input from a popup.
748 * @param {Popup} popup Popup instance
749 * @param {string} avatarId Avatar ID of the persona to edit
750 * @param {string} currentTitle Current title of the persona
751 */
752async function editPersonaTitle(popup, avatarId, currentTitle) {
753 if (popup.result !== POPUP_RESULT.AFFIRMATIVE) {
754 return;
755 }
756
757 if (!power_user.persona_descriptions[avatarId]) {
758 console.warn('Uninitialized persona descriptor for avatar:', avatarId);
759 return;
760 }
761
762 const newTitle = String(popup.inputResults.get('persona_title') || '').trim();
763
764 if (!newTitle && currentTitle) {
765 console.log(`Removed persona title for ${avatarId}`);
766 delete power_user.persona_descriptions[avatarId].title;
767 await getUserAvatars(true, avatarId);
768 saveSettingsDebounced();
769 return;
770 }
771
772 if (newTitle !== currentTitle) {
773 power_user.persona_descriptions[avatarId].title = newTitle;
774 console.log(`Updated persona title for ${avatarId} to ${newTitle}`);
775 await getUserAvatars(true, avatarId);
776 saveSettingsDebounced();
777 return;
778 }
779}
780
781/**
733 * Renames the persona with the given avatar ID by showing a popup to enter a new name.782 * Renames the persona with the given avatar ID by showing a popup to enter a new name.
734 * @param {string} avatarId - ID of the avatar to rename783 * @param {string} avatarId - ID of the avatar to rename
735 * @returns {Promise<boolean>} A promise that resolves to true if the persona was renamed, false otherwise784 * @returns {Promise<boolean>} A promise that resolves to true if the persona was renamed, false otherwise
736 */785 */
737async function renamePersona(avatarId) {786async function renamePersona(avatarId) {
738 const currentName = power_user.personas[avatarId];787 const currentName = power_user.personas[avatarId];
739 const newName = await Popup.show.input(t`Rename Persona`, t`Enter a new name for this persona:`, currentName);788 const currentTitle = power_user.persona_descriptions[avatarId]?.title || '';
789 const newName = await Popup.show.input(t`Rename Persona`, t`Enter a new name for this persona:`, currentName, {
790 customInputs: [{
791 id: 'persona_title',
792 type: 'text',
793 label: t`Persona Title (optional, display only)`,
794 defaultState: currentTitle,
795 }],
796 onClose: (popup) => editPersonaTitle(popup, avatarId, currentTitle),
797 });
798
740 if (!newName || newName === currentName) {799 if (!newName || newName === currentName) {
741 console.debug('User cancelled renaming persona or name is unchanged');800 console.debug('User cancelled renaming persona or name is unchanged');
742 return false;801 return false;
@@ -793,6 +852,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
793 role: DEFAULT_ROLE,852 role: DEFAULT_ROLE,
794 lorebook: '',853 lorebook: '',
795 connections: [],854 connections: [],
855 title: '',
796 };856 };
797 }857 }
798858
@@ -939,6 +999,7 @@ async function lockPersona(type = 'chat') {
939 role: DEFAULT_ROLE,999 role: DEFAULT_ROLE,
940 lorebook: '',1000 lorebook: '',
941 connections: [],1001 connections: [],
1002 title: '',
942 };1003 };
943 }1004 }
9441005
@@ -1060,6 +1121,7 @@ function onPersonaDescriptionInput() {
1060 depth: Number($('#persona_depth_value').val()),1121 depth: Number($('#persona_depth_value').val()),
1061 role: Number($('#persona_depth_role').find(':selected').val()),1122 role: Number($('#persona_depth_role').find(':selected').val()),
1062 lorebook: '',1123 lorebook: '',
1124 title: '',
1063 };1125 };
1064 power_user.persona_descriptions[user_avatar] = object;1126 power_user.persona_descriptions[user_avatar] = object;
1065 }1127 }
@@ -1166,6 +1228,7 @@ function getOrCreatePersonaDescriptor() {
1166 role: power_user.persona_description_role,1228 role: power_user.persona_description_role,
1167 lorebook: power_user.persona_description_lorebook,1229 lorebook: power_user.persona_description_lorebook,
1168 connections: [],1230 connections: [],
1231 title: '',
1169 };1232 };
1170 power_user.persona_descriptions[user_avatar] = object;1233 power_user.persona_descriptions[user_avatar] = object;
1171 }1234 }
@@ -1704,6 +1767,7 @@ async function duplicatePersona(avatarId) {
1704 depth: descriptor?.depth ?? DEFAULT_DEPTH,1767 depth: descriptor?.depth ?? DEFAULT_DEPTH,
1705 role: descriptor?.role ?? DEFAULT_ROLE,1768 role: descriptor?.role ?? DEFAULT_ROLE,
1706 lorebook: descriptor?.lorebook ?? '',1769 lorebook: descriptor?.lorebook ?? '',
1770 title: descriptor?.title ?? '',
1707 };1771 };
17081772
1709 await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId);1773 await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId);
@@ -1719,7 +1783,7 @@ async function migrateNonPersonaUser() {
1719 return;1783 return;
1720 }1784 }
17211785
1722 initPersona(user_avatar, name1, '');1786 initPersona(user_avatar, name1, '', '');
1723 setPersonaDescription();1787 setPersonaDescription();
1724 await getUserAvatars(true, user_avatar);1788 await getUserAvatars(true, user_avatar);
1725}1789}
public/scripts/popup.js+1 -1
@@ -293,7 +293,7 @@ export class Popup {
293 label.setAttribute('for', input.id);293 label.setAttribute('for', input.id);
294294
295 const inputElement = document.createElement('input');295 const inputElement = document.createElement('input');
296 inputElement.classList.add('text_pole');296 inputElement.classList.add('text_pole', 'result-control');
297 inputElement.type = 'text';297 inputElement.type = 'text';
298 inputElement.id = input.id;298 inputElement.id = input.id;
299 inputElement.value = String(input.defaultState ?? '');299 inputElement.value = String(input.defaultState ?? '');
public/style.css+1 -0
@@ -3216,6 +3216,7 @@ input[type=search]:focus::-webkit-search-cancel-button {
3216 display: block;3216 display: block;
3217}3217}
32183218
3219.avatar-container .ch_additional_info,
3219.character_name_block .character_version {3220.character_name_block .character_version {
3220 text-overflow: ellipsis;3221 text-overflow: ellipsis;
3221 overflow: hidden;3222 overflow: hidden;