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);