From 00d74ec683632226dc9790438b8a2714af8ebeeb Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 25 Jul 2024 23:44:34 +0300 Subject: [PATCH 1/4] #2558 Add persona duping --- public/index.html | 3 +++ public/scripts/personas.js | 48 ++++++++++++++++++++++++++++++++++++++ public/style.css | 4 ++++ 3 files changed, 55 insertions(+) diff --git a/public/index.html b/public/index.html index 5e2959124..5547589b6 100644 --- a/public/index.html +++ b/public/index.html @@ -6440,6 +6440,9 @@ + diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 45f515c5f..ea6952495 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -1001,6 +1001,42 @@ export function retriggerFirstMessageOnEmptyChat() { } } +/** + * Duplicates a persona. + * @param {string} avatarId + * @returns {Promise} + */ +async function duplicatePersona(avatarId) { + const personaName = power_user.personas[avatarId]; + + if (!personaName) { + toastr.warning('Current avatar is not a persona'); + return; + } + + const confirm = await Popup.show.confirm('Are you sure you want to duplicate this persona?', personaName); + + if (!confirm) { + console.debug('User cancelled duplicating persona'); + return; + } + + const newAvatarId = `${Date.now()}-${personaName.replace(/[^a-zA-Z0-9]/g, '')}.png`; + const descriptor = power_user.persona_descriptions[avatarId]; + + power_user.personas[newAvatarId] = personaName; + power_user.persona_descriptions[newAvatarId] = { + description: descriptor?.description || '', + position: descriptor?.position || persona_description_positions.IN_PROMPT, + depth: descriptor?.depth || DEFAULT_DEPTH, + role: descriptor?.role || DEFAULT_ROLE, + }; + + await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId); + await getUserAvatars(true, newAvatarId); + saveSettingsDebounced(); +} + export function initPersonas() { $(document).on('click', '.bind_user_name', bindUserNameToPersona); $(document).on('click', '.set_default_persona', setDefaultPersona); @@ -1059,6 +1095,18 @@ export function initPersonas() { $('#avatar_upload_file').trigger('click'); }); + $(document).on('click', '#user_avatar_block .duplicate_persona', function (e) { + e.stopPropagation(); + const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile'); + + if (!avatarId) { + console.log('no imgfile'); + return; + } + + duplicatePersona(avatarId); + }); + $(document).on('click', '#user_avatar_block .set_persona_image', function (e) { e.stopPropagation(); const avatarId = $(this).closest('.avatar-container').find('.avatar').attr('imgfile'); diff --git a/public/style.css b/public/style.css index 95a61cdae..13faa3439 100644 --- a/public/style.css +++ b/public/style.css @@ -3032,6 +3032,10 @@ grammarly-extension { opacity: 1; } +.avatar-container .avatar-buttons .menu_button { + padding: 3px; +} + /* Ross should be able to handle this later */ /*.big-avatars .avatar-buttons{ justify-content: center; From 472b08d0e59049d3a473caaacd93b71b5e56d688 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 25 Jul 2024 23:57:00 +0300 Subject: [PATCH 2/4] Update popups to new --- public/scripts/personas.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/public/scripts/personas.js b/public/scripts/personas.js index ea6952495..2505be390 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -1,5 +1,4 @@ import { - callPopup, characters, chat, chat_metadata, @@ -22,7 +21,7 @@ import { PAGINATION_TEMPLATE, debounce, delay, download, ensureImageFormatSuppor import { debounce_timeout } from './constants.js'; import { FILTER_TYPES, FilterHelper } from './filters.js'; import { selected_group } from './group-chats.js'; -import { POPUP_TYPE, Popup } from './popup.js'; +import { POPUP_RESULT, POPUP_TYPE, Popup } from './popup.js'; let savePersonasPage = 0; const GRID_STORAGE_KEY = 'Personas_GridView'; @@ -332,15 +331,14 @@ async function changeUserAvatar(e) { * @returns {Promise} Promise that resolves when the persona is set */ export async function createPersona(avatarId) { - const personaName = await callPopup('

Enter a name for this persona:

Cancel if you\'re just uploading an avatar.', 'input', ''); + const personaName = await Popup.show.input('Enter a name for this persona:', 'Cancel if you\'re just uploading an avatar.', ''); if (!personaName) { console.debug('User cancelled creating a persona'); return; } - await delay(500); - const personaDescription = await callPopup('

Enter a description for this persona:

You can always add or change it later.', 'input', '', { rows: 4 }); + const personaDescription = await Popup.show.input('Enter a description for this persona:', 'You can always add or change it later.', '', { rows: 4 }); initPersona(avatarId, personaName, personaDescription); if (power_user.persona_show_notifications) { @@ -349,7 +347,7 @@ export async function createPersona(avatarId) { } async function createDummyPersona() { - const personaName = await callPopup('

Enter a name for this persona:

', 'input', ''); + const personaName = await Popup.show.input('Enter a name for this persona:', null); if (!personaName) { console.debug('User cancelled creating dummy persona'); @@ -508,15 +506,20 @@ async function bindUserNameToPersona(e) { return; } + let personaUnbind = false; const existingPersona = power_user.personas[avatarId]; - const personaName = await callPopup('

Enter a name for this persona:

(If empty name is provided, this will unbind the name from this avatar)', 'input', existingPersona || ''); + const personaName = await Popup.show.input( + 'Enter a name for this persona:', + '(If empty name is provided, this will unbind the name from this avatar)', + existingPersona || '', + { onClose: (p) => { personaUnbind = p.value === '' && p.result === POPUP_RESULT.AFFIRMATIVE; } }); // If the user clicked cancel, don't do anything - if (personaName === false) { + if (personaName === null && !personaUnbind) { return; } - if (personaName.length > 0) { + if (personaName && personaName.length > 0) { // If the user clicked ok and entered a name, bind the name to the persona console.log(`Binding persona ${avatarId} to name ${personaName}`); power_user.personas[avatarId] = personaName; @@ -672,7 +675,7 @@ async function deleteUserAvatar(e) { return; } - const confirm = await callPopup('

Are you sure you want to delete this avatar?

All information associated with its linked persona will be lost.', 'confirm'); + const confirm = await Popup.show.confirm('Are you sure you want to delete this avatar?', 'All information associated with its linked persona will be lost.'); if (!confirm) { console.debug('User cancelled deleting avatar'); @@ -806,7 +809,7 @@ async function setDefaultPersona(e) { const personaName = power_user.personas[avatarId]; if (avatarId === currentDefault) { - const confirm = await callPopup('Are you sure you want to remove the default persona?', 'confirm'); + const confirm = await Popup.show.confirm('Are you sure you want to remove the default persona?', personaName); if (!confirm) { console.debug('User cancelled removing default persona'); @@ -819,8 +822,7 @@ async function setDefaultPersona(e) { } delete power_user.default_persona; } else { - const confirm = await callPopup(`

Are you sure you want to set "${personaName}" as the default persona?

- This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.`, 'confirm'); + const confirm = await Popup.show.confirm(`Are you sure you want to set "${personaName}" as the default persona?`, 'This name and avatar will be used for all new chats, as well as existing chats where the user persona is not locked.'); if (!confirm) { console.debug('User cancelled setting default persona'); @@ -978,7 +980,7 @@ async function onPersonasRestoreInput(e) { } async function syncUserNameToPersona() { - const confirmation = await callPopup(`

Are you sure?

All user-sent messages in this chat will be attributed to ${name1}.`, 'confirm'); + const confirmation = await Popup.show.confirm('Are you sure?', `All user-sent messages in this chat will be attributed to ${name1}.`); if (!confirmation) { return; From 63512c208f7e868d7e52bc3be650d52bd0fa4768 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 26 Jul 2024 18:17:32 +0200 Subject: [PATCH 3/4] Fix popup not respecting for text --- public/scripts/popup.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/scripts/popup.js b/public/scripts/popup.js index 691f42aae..25f0b65c2 100644 --- a/public/scripts/popup.js +++ b/public/scripts/popup.js @@ -73,8 +73,8 @@ const showPopupHelper = { /** * Asynchronously displays an input popup with the given header and text, and returns the user's input. * - * @param {string} header - The header text for the popup. - * @param {string} text - The main text for the popup. + * @param {string?} header - The header text for the popup. + * @param {string?} text - The main text for the popup. * @param {string} [defaultValue=''] - The default value for the input field. * @param {PopupOptions} [popupOptions={}] - Options for the popup. * @return {Promise} A Promise that resolves with the user's input. @@ -591,15 +591,15 @@ class PopupUtils { /** * Builds popup content with header and text below * - * @param {string} header - The header to be added to the text - * @param {string} text - The main text content + * @param {string?} header - The header to be added to the text + * @param {string?} text - The main text content */ static BuildTextWithHeader(header, text) { if (!header) { return text; } return `

${header}

- ${text}`; + ${text ?? ''}`; // Convert no text to empty string } } From 9cf53c6a557962d1b352e45220c9c508566401c9 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 26 Jul 2024 19:45:45 +0300 Subject: [PATCH 4/4] Handle null values for missing persona descriptor fields --- public/scripts/personas.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 2505be390..318e4e338 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -646,7 +646,12 @@ async function lockPersona() { ); } power_user.personas[user_avatar] = name1; - power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.IN_PROMPT }; + power_user.persona_descriptions[user_avatar] = { + description: '', + position: persona_description_positions.IN_PROMPT, + depth: DEFAULT_DEPTH, + role: DEFAULT_ROLE, + }; } chat_metadata['persona'] = user_avatar; @@ -1012,7 +1017,7 @@ async function duplicatePersona(avatarId) { const personaName = power_user.personas[avatarId]; if (!personaName) { - toastr.warning('Current avatar is not a persona'); + toastr.warning('Chosen avatar is not a persona'); return; } @@ -1028,10 +1033,10 @@ async function duplicatePersona(avatarId) { power_user.personas[newAvatarId] = personaName; power_user.persona_descriptions[newAvatarId] = { - description: descriptor?.description || '', - position: descriptor?.position || persona_description_positions.IN_PROMPT, - depth: descriptor?.depth || DEFAULT_DEPTH, - role: descriptor?.role || DEFAULT_ROLE, + description: descriptor?.description ?? '', + position: descriptor?.position ?? persona_description_positions.IN_PROMPT, + depth: descriptor?.depth ?? DEFAULT_DEPTH, + role: descriptor?.role ?? DEFAULT_ROLE, }; await uploadUserAvatar(getUserAvatar(avatarId), newAvatarId);