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
| @@ -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> |
| 4986 | 4986 | ||
| 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> |
| @@ -30,6 +30,7 @@ import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | |||
| 30 | import { t } from './i18n.js'; | 30 | import { t } from './i18n.js'; |
| 31 | import { openWorldInfoEditor, world_names } from './world-info.js'; | 31 | import { openWorldInfoEditor, world_names } from './world-info.js'; |
| 32 | import { renderTemplateAsync } from './templates.js'; | 32 | import { renderTemplateAsync } from './templates.js'; |
| 33 | import { saveMetadataDebounced } from './extensions.js'; | ||
| 33 | 34 | ||
| 34 | /** | 35 | /** |
| 35 | * @typedef {object} PersonaConnection A connection between a character and a character or group entity | 36 | * @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); |
| 553 | 554 | ||
| 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); |
| 557 | 559 | ||
| @@ -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 | }); |
| 569 | 571 | ||
| 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 | } |
| 707 | 710 | ||
| 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 | } |
| 711 | 724 | ||
| @@ -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 chat | 1165 | // Define a persona for this chat |
| 1137 | let chatPersona = ''; | 1166 | let chatPersona = ''; |
| 1138 | 1167 | ||
| 1168 | /** @type {'chat' | 'character' | 'default' | null} */ | ||
| 1169 | let connectType = null; | ||
| 1170 | |||
| 1139 | // If persona is locked in chat metadata, select it | 1171 | // 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 | } |
| 1153 | 1185 | ||
| 1154 | // TODO: TEMP | ||
| 1155 | power_user.persona_allow_multi_connections = true; | ||
| 1156 | |||
| 1157 | // Check if we have any persona connected to the current character | 1186 | // 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 | } |
| 1175 | 1206 | ||
| 1176 | // Last check if default persona is set, select it | 1207 | // 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 | } |
| 1181 | 1214 | ||
| 1182 | // Whatever way we selected a persona, if it doesn't exist, unlock this chat | 1215 | // 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 it | 1228 | // 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 | } |
| 1199 | 1236 | ||
| 1200 | updatePersonaLockIcons(); | 1237 | updatePersonaLockIcons(); |
| @@ -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 | }); |
| 3654 | 3656 | ||
| 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(); |