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 | // TODO: Naming convention is inconsistent with other events | 497 | // TODO: Naming convention is inconsistent with other events |
| 498 | CHARACTER_DELETED: 'characterDeleted', | 498 | CHARACTER_DELETED: 'characterDeleted', |
| 499 | CHARACTER_DUPLICATED: 'character_duplicated', | 499 | CHARACTER_DUPLICATED: 'character_duplicated', |
| 500 | CHARACTER_RENAMED: 'character_renamed', | ||
| 500 | /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */ | 501 | /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */ |
| 501 | SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received', | 502 | SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received', |
| 502 | STREAM_TOKEN_RECEIVED: 'stream_token_received', | 503 | STREAM_TOKEN_RECEIVED: 'stream_token_received', |
| @@ -1031,12 +1032,22 @@ export function setAnimationDuration(ms = null) { | |||
| 1031 | document.documentElement.style.setProperty('--animation-duration', `${animation_duration}ms`); | 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 | export function setActiveCharacter(entityOrKey) { | 1039 | export function setActiveCharacter(entityOrKey) { |
| 1035 | active_character = getTagKeyForEntity(entityOrKey); | 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 | export function setActiveGroup(entityOrKey) { | 1048 | export function setActiveGroup(entityOrKey) { |
| 1039 | active_group = getTagKeyForEntity(entityOrKey); | 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 | const data = await response.json(); | 6257 | const data = await response.json(); |
| 6247 | const newAvatar = data.avatar; | 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 | renameTagKey(oldAvatar, newAvatar); | 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 | // Reload characters list | 6289 | // Reload characters list |
| 6253 | await getCharacters(); | 6290 | await getCharacters(); |
| 6254 | 6291 | ||
| @@ -280,17 +280,32 @@ async function RA_autoloadchat() { | |||
| 280 | // active character is the name, we should look it up in the character list and get the id | 280 | // active character is the name, we should look it up in the character list and get the id |
| 281 | if (active_character !== null && active_character !== undefined) { | 281 | if (active_character !== null && active_character !== undefined) { |
| 282 | const active_character_id = characters.findIndex(x => getTagKeyForEntity(x) === active_character); | 282 | const active_character_id = characters.findIndex(x => getTagKeyForEntity(x) === active_character); |
| 283 | if (active_character_id !== null) { | 283 | if (active_character_id !== -1) { |
| 284 | await selectCharacterById(String(active_character_id)); | 284 | await selectCharacterById(String(active_character_id)); |
| 285 | 285 | ||
| 286 | // Do a little tomfoolery to spoof the tag selector | 286 | // Do a little tomfoolery to spoof the tag selector |
| 287 | const selectedCharElement = $(`#rm_print_characters_block .character_select[chid="${active_character_id}"]`); | 287 | const selectedCharElement = $(`#rm_print_characters_block .character_select[chid="${active_character_id}"]`); |
| 288 | applyTagsOnCharacterSelect.call(selectedCharElement); | 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 | if (active_group !== null && active_group !== undefined) { | 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 | // if the character list hadn't been loaded yet, try again. | 311 | // if the character list hadn't been loaded yet, try again. |
| @@ -1664,12 +1664,12 @@ function updateFavButtonState(state) { | |||
| 1664 | export async function openGroupById(groupId) { | 1664 | export async function openGroupById(groupId) { |
| 1665 | if (isChatSaving) { | 1665 | if (isChatSaving) { |
| 1666 | toastr.info(t`Please wait until the chat is saved before switching characters.`, t`Your chat is still saving...`); | 1666 | toastr.info(t`Please wait until the chat is saved before switching characters.`, t`Your chat is still saving...`); |
| 1667 | return; | 1667 | return false; |
| 1668 | } | 1668 | } |
| 1669 | 1669 | ||
| 1670 | if (!groups.find(x => x.id === groupId)) { | 1670 | if (!groups.find(x => x.id === groupId)) { |
| 1671 | console.log('Group not found', groupId); | 1671 | console.log('Group not found', groupId); |
| 1672 | return; | 1672 | return false; |
| 1673 | } | 1673 | } |
| 1674 | 1674 | ||
| 1675 | if (!is_send_press && !is_group_generating) { | 1675 | if (!is_send_press && !is_group_generating) { |
| @@ -1686,8 +1686,11 @@ export async function openGroupById(groupId) { | |||
| 1686 | updateChatMetadata({}, true); | 1686 | updateChatMetadata({}, true); |
| 1687 | chat.length = 0; | 1687 | chat.length = 0; |
| 1688 | await getGroupChat(groupId); | 1688 | await getGroupChat(groupId); |
| 1689 | return true; | ||
| 1689 | } | 1690 | } |
| 1690 | } | 1691 | } |
| 1692 | |||
| 1693 | return false; | ||
| 1691 | } | 1694 | } |
| 1692 | 1695 | ||
| 1693 | function openCharacterDefinition(characterSelect) { | 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 | const context = getContext(); | 1018 | const context = getContext(); |
| 1012 | const fileName = context.characters[chid ?? context.characterId]?.avatar; | 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 | /** |