Merge pull request #3497 from SillyTavern/fix-char-rename-aux-connections Fix renaming characters losing connections of several aux fields
Signed| @@ -497,6 +497,7 @@ export const event_types = { | ||
| 497 | 497 | // TODO: Naming convention is inconsistent with other events |
| 498 | 498 | CHARACTER_DELETED: 'characterDeleted', |
| 499 | 499 | CHARACTER_DUPLICATED: 'character_duplicated', |
| 500 | + CHARACTER_RENAMED: 'character_renamed', | |
| 500 | 501 | /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */ |
| 501 | 502 | SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received', |
| 502 | 503 | STREAM_TOKEN_RECEIVED: 'stream_token_received', |
| @@ -1031,12 +1032,22 @@ export function setAnimationDuration(ms = null) { | ||
| 1031 | 1032 | document.documentElement.style.setProperty('--animation-duration', `${animation_duration}ms`); |
| 1032 | 1033 | } |
| 1033 | 1034 | |
| 1035 | +/** | |
| 1036 | + * Sets the currently active character | |
| 1037 | + * @param {object|number|string} [entityOrKey] - An entity with id property (character, group, tag), or directly an id or tag key. If not provided, the active character is reset to `null`. | |
| 1038 | + */ | |
| 1034 | 1039 | export function setActiveCharacter(entityOrKey) { |
| 1035 | 1040 | active_character = entityOrKey ? getTagKeyForEntity(entityOrKey) : null; |
| 1041 | + if (active_character) active_group = null; | |
| 1036 | 1042 | } |
| 1037 | 1043 | |
| 1044 | +/** | |
| 1045 | + * Sets the currently active group. | |
| 1046 | + * @param {object|number|string} [entityOrKey] - An entity with id property (character, group, tag), or directly an id or tag key. If not provided, the active group is reset to `null`. | |
| 1047 | + */ | |
| 1038 | 1048 | export function setActiveGroup(entityOrKey) { |
| 1039 | 1049 | active_group = entityOrKey ? getTagKeyForEntity(entityOrKey) : null; |
| 1050 | + if (active_group) active_character = null; | |
| 1040 | 1051 | } |
| 1041 | 1052 | |
| 1042 | 1053 | /** |
| @@ -6246,9 +6257,35 @@ export async function renameCharacter(name = null, { silent = false, renameChats | ||
| 6246 | 6257 | const data = await response.json(); |
| 6247 | 6258 | const newAvatar = data.avatar; |
| 6248 | 6259 | |
| 6249 | - // Replace tags list | |
| 6260 | + const oldName = getCharaFilename(null, { manualAvatarKey: oldAvatar }); | |
| 6261 | + const newName = getCharaFilename(null, { manualAvatarKey: newAvatar }); | |
| 6262 | + | |
| 6263 | + // Replace other auxillery fields where was referenced by avatar key | |
| 6264 | + // Tag List | |
| 6250 | 6265 | renameTagKey(oldAvatar, newAvatar); |
| 6251 | 6266 | |
| 6267 | + // Addtional lore books | |
| 6268 | + const charLore = world_info.charLore?.find(x => x.name == oldName); | |
| 6269 | + if (charLore) { | |
| 6270 | + charLore.name = newName; | |
| 6271 | + saveSettingsDebounced(); | |
| 6272 | + } | |
| 6273 | + | |
| 6274 | + // Char-bound Author's Notes | |
| 6275 | + const charNote = extension_settings.note.chara?.find(x => x.name == oldName); | |
| 6276 | + if (charNote) { | |
| 6277 | + charNote.name = newName; | |
| 6278 | + saveSettingsDebounced(); | |
| 6279 | + } | |
| 6280 | + | |
| 6281 | + // Update active character, if the current one was the currently active one | |
| 6282 | + if (active_character === oldAvatar) { | |
| 6283 | + active_character = newAvatar; | |
| 6284 | + saveSettingsDebounced(); | |
| 6285 | + } | |
| 6286 | + | |
| 6287 | + await eventSource.emit(event_types.CHARACTER_RENAMED, oldAvatar, newAvatar); | |
| 6288 | + | |
| 6252 | 6289 | // Reload characters list |
| 6253 | 6290 | await getCharacters(); |
| 6254 | 6291 | |
| @@ -280,17 +280,32 @@ async function RA_autoloadchat() { | ||
| 280 | 280 | // active character is the name, we should look it up in the character list and get the id |
| 281 | 281 | if (active_character !== null && active_character !== undefined) { |
| 282 | 282 | const active_character_id = characters.findIndex(x => getTagKeyForEntity(x) === active_character); |
| 283 | 283 | if (active_character_id !== null-1) { |
| 284 | 284 | await selectCharacterById(String(active_character_id)); |
| 285 | 285 | |
| 286 | 286 | // Do a little tomfoolery to spoof the tag selector |
| 287 | 287 | const selectedCharElement = $(`#rm_print_characters_block .character_select[chid="${active_character_id}"]`); |
| 288 | 288 | applyTagsOnCharacterSelect.call(selectedCharElement); |
| 289 | + } else { | |
| 290 | + setActiveCharacter(null); | |
| 291 | + saveSettingsDebounced(); | |
| 292 | + console.warn(`Currently active character with ID ${active_character} not found. Resetting to no active character.`); | |
| 289 | 293 | } |
| 290 | 294 | } |
| 291 | 295 | |
| 292 | 296 | if (active_group !== null && active_group !== undefined) { |
| 293 | - await openGroupById(String(active_group)); | |
| 297 | + if (active_character) { | |
| 298 | + console.warn('Active character and active group are both set. Only active character will be loaded. Resetting active group.'); | |
| 299 | + setActiveGroup(null); | |
| 300 | + saveSettingsDebounced(); | |
| 301 | + } else { | |
| 302 | + const result = await openGroupById(String(active_group)); | |
| 303 | + if (!result) { | |
| 304 | + setActiveGroup(null); | |
| 305 | + saveSettingsDebounced(); | |
| 306 | + console.warn(`Currently active group with ID ${active_group} not found. Resetting to no active group.`); | |
| 307 | + } | |
| 308 | + } | |
| 294 | 309 | } |
| 295 | 310 | |
| 296 | 311 | // if the character list hadn't been loaded yet, try again. |
| @@ -1664,12 +1664,12 @@ function updateFavButtonState(state) { | ||
| 1664 | 1664 | export async function openGroupById(groupId) { |
| 1665 | 1665 | if (isChatSaving) { |
| 1666 | 1666 | toastr.info(t`Please wait until the chat is saved before switching characters.`, t`Your chat is still saving...`); |
| 1667 | 1667 | return false; |
| 1668 | 1668 | } |
| 1669 | 1669 | |
| 1670 | 1670 | if (!groups.find(x => x.id === groupId)) { |
| 1671 | 1671 | console.log('Group not found', groupId); |
| 1672 | 1672 | return false; |
| 1673 | 1673 | } |
| 1674 | 1674 | |
| 1675 | 1675 | if (!is_send_press && !is_group_generating) { |
| @@ -1686,8 +1686,11 @@ export async function openGroupById(groupId) { | ||
| 1686 | 1686 | updateChatMetadata({}, true); |
| 1687 | 1687 | chat.length = 0; |
| 1688 | 1688 | await getGroupChat(groupId); |
| 1689 | + return true; | |
| 1689 | 1690 | } |
| 1690 | 1691 | } |
| 1692 | + | |
| 1693 | + return false; | |
| 1691 | 1694 | } |
| 1692 | 1695 | |
| 1693 | 1696 | function openCharacterDefinition(characterSelect) { |
| @@ -1007,13 +1007,18 @@ export function getImageSizeFromDataURL(dataUrl) { | ||
| 1007 | 1007 | }); |
| 1008 | 1008 | } |
| 1009 | 1009 | |
| 1010 | -export function getCharaFilename(chid) { | |
| 1010 | +/** | |
| 1011 | + * Gets the filename of the character avatar without extension | |
| 1012 | + * @param {number?} [chid=null] - Character ID. If not provided, uses the current character ID | |
| 1013 | + * @param {object} [options={}] - Options arguments | |
| 1014 | + * @param {string?} [options.manualAvatarKey=null] - Manually take the following avatar key, instead of using the chid to determine the name | |
| 1015 | + * @returns {string?} The filename of the character avatar without extension, or null if the character ID is invalid | |
| 1016 | + */ | |
| 1017 | +export function getCharaFilename(chid = null, { manualAvatarKey = null } = {}) { | |
| 1011 | 1018 | const context = getContext(); |
| 1012 | 1019 | const fileName = manualAvatarKey ?? context.characters[chid ?? context.characterId]?.avatar; |
| 1013 | 1020 | |
| 1014 | - if (fileName) { | |
| 1021 | + return fileName?.replace(/\.[^/.]+$/, '') ?? null; | |
| 1015 | - return fileName.replace(/\.[^/.]+$/, ''); | |
| 1016 | - } | |
| 1017 | 1022 | } |
| 1018 | 1023 | |
| 1019 | 1024 | /** |