Persona settings for auto-lock & multi connection - Add setting to auto-lock persona to chat. Triggered on chat changed (creation/loaded) and on persona selection. - Add setting to allow multiple persona<->char connections, which then triggers the popup to select one - If no multi connections allowed, the connect toggle will remove all existing connections from personas to that character

776dedea8230811a937828922759d0bdcd85bb4e

Wolfsblvt <wolfsblvt@gmail.com>

3 files changed, +75 -10Ignore whitespace
public/index.html+17 -1
@@ -4984,7 +4984,7 @@
4984 <div class="persona_management_global_settings">4984 <div class="persona_management_global_settings">
4985 <h4 class="standoutHeader" data-i18n="Global Settings">Global Settings</h4>4985 <h4 class="standoutHeader" data-i18n="Global Settings">Global Settings</h4>
49864986
4987 <div class="persona_management_show_notifications range-block">4987 <div class="range-block">
4988 <label for="persona_show_notifications" class="checkbox_label">4988 <label for="persona_show_notifications" class="checkbox_label">
4989 <input id="persona_show_notifications" type="checkbox" />4989 <input id="persona_show_notifications" type="checkbox" />
4990 <span data-i18n="Show notifications on switching personas">4990 <span data-i18n="Show notifications on switching personas">
@@ -4992,6 +4992,22 @@
4992 </span>4992 </span>
4993 </label>4993 </label>
4994 </div>4994 </div>
4995 <div class="range-block">
4996 <label for="persona_allow_multi_connections" class="checkbox_label" title="When multiple personas are connected to a character, a popup will appear to select which one to use." data-i18n="[title]When multiple personas are connected to a character, a popup will appear to select which one to use">
4997 <input id="persona_allow_multi_connections" type="checkbox" />
4998 <span data-i18n="Allow multiple persona connections per character">
4999 Allow multiple persona connections per character
5000 </span>
5001 </label>
5002 </div>
5003 <div class="range-block">
5004 <label for="persona_auto_lock" class="checkbox_label" title="Whenever a persona is selected, it will be locked to the current chat and automatically selected when the chat is opened." data-i18n="[title]Whenever a persona is selected, it will be locked to the current chat and automatically selected when the chat is opened.">
5005 <input id="persona_auto_lock" type="checkbox" />
5006 <span data-i18n="Auto-lock a chosen persona to the chat">
5007 Auto-lock a chosen persona to the chat
5008 </span>
5009 </label>
5010 </div>
4995 </div>5011 </div>
4996 </div>5012 </div>
4997 </div>5013 </div>
public/scripts/personas.js+46 -9
@@ -30,6 +30,7 @@ import { 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';
33import { saveMetadataDebounced } from './extensions.js';
3334
34/**35/**
35 * @typedef {object} PersonaConnection A connection between a character and a character or group entity36 * @typedef {object} PersonaConnection A connection between a character and a character or group entity
@@ -552,6 +553,7 @@ export async function askForPersonaSelection(title, text, personas) {
552 content.appendChild(titleElement);553 content.appendChild(titleElement);
553554
554 const textElement = document.createElement('div');555 const textElement = document.createElement('div');
556 textElement.classList.add('m-b-1');
555 textElement.textContent = text;557 textElement.textContent = text;
556 content.appendChild(textElement);558 content.appendChild(textElement);
557559
@@ -567,7 +569,7 @@ export async function askForPersonaSelection(title, text, personas) {
567 block.dataset.result = String(100 + personas.indexOf(block.dataset.pid));569 block.dataset.result = String(100 + personas.indexOf(block.dataset.pid));
568 });570 });
569571
570 const popup = new Popup(content, POPUP_TYPE.TEXT, '', {});572 const popup = new Popup(content, POPUP_TYPE.TEXT, '', { okButton: 'None' });
571 const result = await popup.show();573 const result = await popup.show();
572 return Number(result) > 100 ? personas[Number(result) - 100] : null;574 return Number(result) > 100 ? personas[Number(result) - 100] : null;
573}575}
@@ -702,10 +704,21 @@ function selectCurrentPersona() {
702 depth: DEFAULT_DEPTH,704 depth: DEFAULT_DEPTH,
703 role: DEFAULT_ROLE,705 role: DEFAULT_ROLE,
704 lorebook: '',706 lorebook: '',
707 connections: [],
705 };708 };
706 }709 }
707710
708 setPersonaDescription();711 setPersonaDescription();
712
713 // Update the locked persona if setting is enabled
714 if (power_user.persona_auto_lock && personaName && user_avatar !== chat_metadata['persona']) {
715 chat_metadata['persona'] = user_avatar;
716 console.log(`Auto locked persona to ${user_avatar}`);
717 if (power_user.persona_show_notifications) {
718 toastr.info(`Auto locked persona ${personaName} to current chat`);
719 }
720 saveMetadataDebounced();
721 }
709 }722 }
710}723}
711724
@@ -834,8 +847,7 @@ async function lockPersona(type = 'chat') {
834 case 'chat': {847 case 'chat': {
835 console.log(`Locking persona ${user_avatar} to this chat`);848 console.log(`Locking persona ${user_avatar} to this chat`);
836 chat_metadata['persona'] = user_avatar;849 chat_metadata['persona'] = user_avatar;
837 await saveMetadata();850 saveMetadataDebounced();
838 saveSettingsDebounced();
839 if (power_user.persona_show_notifications) {851 if (power_user.persona_show_notifications) {
840 toastr.success(t`User persona ${name1} is locked to ${name2} in this chat`);852 toastr.success(t`User persona ${name1} is locked to ${name2} in this chat`);
841 }853 }
@@ -849,10 +861,27 @@ async function lockPersona(type = 'chat') {
849 if (newConnection && newConnection.id) {861 if (newConnection && newConnection.id) {
850 console.log(`Locking persona ${user_avatar} to this character ${name2}`);862 console.log(`Locking persona ${user_avatar} to this character ${name2}`);
851 power_user.persona_descriptions[user_avatar].connections = [...connections, newConnection];863 power_user.persona_descriptions[user_avatar].connections = [...connections, newConnection];
864
865 const unlinkedCharacters = [];
866 if (!power_user.persona_allow_multi_connections) {
867 for (const [avatarId, description] of Object.entries(power_user.persona_descriptions)) {
868 if (avatarId === user_avatar) continue;
869
870 const filteredConnections = description.connections?.filter(c => !(c.type === newConnection.type && c.id === newConnection.id)) ?? [];
871 if (filteredConnections.length !== description.connections?.length) {
872 description.connections = filteredConnections;
873 unlinkedCharacters.push(power_user.personas[avatarId]);
874 }
875 }
876 }
877
852 saveSettingsDebounced();878 saveSettingsDebounced();
853 updatePersonaConnectionsAvatarList();879 updatePersonaConnectionsAvatarList();
854 if (power_user.persona_show_notifications) {880 if (power_user.persona_show_notifications) {
855 toastr.success(t`User persona ${name1} is locked to character ${name2}`);881 let additional = '';
882 if (unlinkedCharacters.length)
883 additional += `<br /><br />${t`Unlinked existing persona${unlinkedCharacters.length > 1 ? 's' : ''}: ${unlinkedCharacters.join(', ')}`}`;
884 toastr.success(t`User persona ${name1} is locked to character ${name2}${additional}`, null, { escapeHtml: false });
856 }885 }
857 }886 }
858 break;887 break;
@@ -1136,6 +1165,9 @@ async function setChatLockedPersona() {
1136 // Define a persona for this chat1165 // Define a persona for this chat
1137 let chatPersona = '';1166 let chatPersona = '';
11381167
1168 /** @type {'chat' | 'character' | 'default' | null} */
1169 let connectType = null;
1170
1139 // If persona is locked in chat metadata, select it1171 // If persona is locked in chat metadata, select it
1140 if (chat_metadata['persona']) {1172 if (chat_metadata['persona']) {
1141 console.log(`Using locked persona ${chat_metadata['persona']}`);1173 console.log(`Using locked persona ${chat_metadata['persona']}`);
@@ -1145,15 +1177,12 @@ async function setChatLockedPersona() {
1145 if (!userAvatars.includes(chatPersona)) {1177 if (!userAvatars.includes(chatPersona)) {
1146 console.warn('Chat-locked persona avatar not found, unlocking persona');1178 console.warn('Chat-locked persona avatar not found, unlocking persona');
1147 delete chat_metadata['persona'];1179 delete chat_metadata['persona'];
1148 updatePersonaLockIcons();1180 saveSettingsDebounced();
1149 chatPersona = '';1181 chatPersona = '';
1150 return;
1151 }1182 }
1183 if (chatPersona) connectType = 'chat';
1152 }1184 }
11531185
1154 // TODO: TEMP
1155 power_user.persona_allow_multi_connections = true;
1156
1157 // Check if we have any persona connected to the current character1186 // Check if we have any persona connected to the current character
1158 if (!chatPersona) {1187 if (!chatPersona) {
1159 const characterKey = menu_type === 'group_edit' ? selected_group : characters[this_chid]?.avatar;1188 const characterKey = menu_type === 'group_edit' ? selected_group : characters[this_chid]?.avatar;
@@ -1171,12 +1200,16 @@ async function setChatLockedPersona() {
1171 connectedPersonas);1200 connectedPersonas);
1172 }1201 }
1173 }1202 }
1203
1204 if (chatPersona) connectType = 'character';
1174 }1205 }
11751206
1176 // Last check if default persona is set, select it1207 // Last check if default persona is set, select it
1177 if (!chatPersona && power_user.default_persona) {1208 if (!chatPersona && power_user.default_persona) {
1178 console.log(`Using default persona ${power_user.default_persona}`);1209 console.log(`Using default persona ${power_user.default_persona}`);
1179 chatPersona = power_user.default_persona;1210 chatPersona = power_user.default_persona;
1211
1212 if (chatPersona) connectType = 'default';
1180 }1213 }
11811214
1182 // Whatever way we selected a persona, if it doesn't exist, unlock this chat1215 // Whatever way we selected a persona, if it doesn't exist, unlock this chat
@@ -1195,6 +1228,10 @@ async function setChatLockedPersona() {
1195 // Persona avatar found, select it1228 // Persona avatar found, select it
1196 if (chatPersona) {1229 if (chatPersona) {
1197 setUserAvatar(chatPersona);1230 setUserAvatar(chatPersona);
1231
1232 if (power_user.persona_show_notifications) {
1233 toastr.info(t`Auto-selected persona ${power_user.personas[chatPersona]} based on ${connectType} connection.`, t`Persona Management`);
1234 }
1198 }1235 }
11991236
1200 updatePersonaLockIcons();1237 updatePersonaLockIcons();
public/scripts/power-user.js+12 -0
@@ -1477,6 +1477,8 @@ async function loadPowerUserSettings(settings, data) {
1477 $('#custom_stopping_strings_macro').prop('checked', power_user.custom_stopping_strings_macro);1477 $('#custom_stopping_strings_macro').prop('checked', power_user.custom_stopping_strings_macro);
1478 $('#fuzzy_search_checkbox').prop('checked', power_user.fuzzy_search);1478 $('#fuzzy_search_checkbox').prop('checked', power_user.fuzzy_search);
1479 $('#persona_show_notifications').prop('checked', power_user.persona_show_notifications);1479 $('#persona_show_notifications').prop('checked', power_user.persona_show_notifications);
1480 $('#persona_allow_multi_connections').prop('checked', power_user.persona_allow_multi_connections);
1481 $('#persona_auto_lock').prop('checked', power_user.persona_auto_lock);
1480 $('#encode_tags').prop('checked', power_user.encode_tags);1482 $('#encode_tags').prop('checked', power_user.encode_tags);
1481 $('#example_messages_behavior').val(getExampleMessagesBehavior());1483 $('#example_messages_behavior').val(getExampleMessagesBehavior());
1482 $(`#example_messages_behavior option[value="${getExampleMessagesBehavior()}"]`).prop('selected', true);1484 $(`#example_messages_behavior option[value="${getExampleMessagesBehavior()}"]`).prop('selected', true);
@@ -3652,6 +3654,16 @@ $(document).ready(() => {
3652 saveSettingsDebounced();3654 saveSettingsDebounced();
3653 });3655 });
36543656
3657 $('#persona_allow_multi_connections').on('input', function () {
3658 power_user.persona_allow_multi_connections = !!$(this).prop('checked');
3659 saveSettingsDebounced();
3660 });
3661
3662 $('#persona_auto_lock').on('input', function () {
3663 power_user.persona_auto_lock = !!$(this).prop('checked');
3664 saveSettingsDebounced();
3665 });
3666
3655 $('#encode_tags').on('input', async function () {3667 $('#encode_tags').on('input', async function () {
3656 power_user.encode_tags = !!$(this).prop('checked');3668 power_user.encode_tags = !!$(this).prop('checked');
3657 await reloadCurrentChat();3669 await reloadCurrentChat();