Renaming a lorebook now re-links itself to cards using it (with a confirmation prompt) (#5323) * renaming a lorebook prompts to update existing links * used suggested api and logic * add world property to shallow function * Fix type error in assignLorebookToChat invoke * Remove debug console logs * Fix activeCharacterUpdated * Extract updateWorldInfoLinks into a func * Invert if for an early return --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -1,6 +1,6 @@ | |||
| 1 | import { Fuse } from '../lib.js'; | 1 | import { Fuse } from '../lib.js'; |
| 2 | 2 | ||
| 3 | import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles, create_save, createOrEditCharacter, name1 } from '../script.js'; | 3 | import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles, create_save, createOrEditCharacter, name1, getOneCharacter, select_selected_character } from '../script.js'; |
| 4 | import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents, uuidv4, normalizeArray, getUniqueName, logSlashCommandWarn, addLongPressEvent } from './utils.js'; | 4 | import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents, uuidv4, normalizeArray, getUniqueName, logSlashCommandWarn, addLongPressEvent } from './utils.js'; |
| 5 | import { extension_settings, getContext } from './extensions.js'; | 5 | import { extension_settings, getContext } from './extensions.js'; |
| 6 | import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; | 6 | import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; |
| @@ -835,7 +835,7 @@ export function updateWorldInfoSettings(settings, activeWorldInfo) { | |||
| 835 | world_info_use_group_scoring: (value) => world_info_use_group_scoring = Boolean(value), | 835 | world_info_use_group_scoring: (value) => world_info_use_group_scoring = Boolean(value), |
| 836 | world_info_max_recursion_steps: (value) => world_info_max_recursion_steps = Number(value), | 836 | world_info_max_recursion_steps: (value) => world_info_max_recursion_steps = Number(value), |
| 837 | // Unused | 837 | // Unused |
| 838 | world_info: (_value) => {}, | 838 | world_info: (_value) => { }, |
| 839 | }; | 839 | }; |
| 840 | 840 | ||
| 841 | for (const [key, setter] of Object.entries(fields)) { | 841 | for (const [key, setter] of Object.entries(fields)) { |
| @@ -4109,6 +4109,27 @@ async function renameWorldInfo(name, data) { | |||
| 4109 | await saveWorldInfo(newName, data, true); | 4109 | await saveWorldInfo(newName, data, true); |
| 4110 | await deleteWorldInfo(oldName); | 4110 | await deleteWorldInfo(oldName); |
| 4111 | 4111 | ||
| 4112 | await updateWorldInfoLinks(oldName, newName); | ||
| 4113 | |||
| 4114 | if (entryPreviouslySelected !== -1) { | ||
| 4115 | const wiElement = getWIElement(newName); | ||
| 4116 | wiElement.prop('selected', true); | ||
| 4117 | $('#world_info').trigger('change'); | ||
| 4118 | } | ||
| 4119 | |||
| 4120 | const selectedIndex = world_names.indexOf(newName); | ||
| 4121 | if (selectedIndex !== -1) { | ||
| 4122 | $('#world_editor_select').val(selectedIndex).trigger('change'); | ||
| 4123 | } | ||
| 4124 | } | ||
| 4125 | |||
| 4126 | /** | ||
| 4127 | * Retargets all character lore links from an old world info name to a new one, with an optional confirmation for primary lorebook links | ||
| 4128 | * @param {string} oldName Previous WI file name | ||
| 4129 | * @param {string} newName New WI file name | ||
| 4130 | * @returns {Promise<void>} | ||
| 4131 | */ | ||
| 4132 | async function updateWorldInfoLinks(oldName, newName) { | ||
| 4112 | const existingCharLores = world_info.charLore?.filter((e) => e.extraBooks.includes(oldName)); | 4133 | const existingCharLores = world_info.charLore?.filter((e) => e.extraBooks.includes(oldName)); |
| 4113 | if (existingCharLores && existingCharLores.length > 0) { | 4134 | if (existingCharLores && existingCharLores.length > 0) { |
| 4114 | existingCharLores.forEach((charLore) => { | 4135 | existingCharLores.forEach((charLore) => { |
| @@ -4119,15 +4140,70 @@ async function renameWorldInfo(name, data) { | |||
| 4119 | saveSettingsDebounced(); | 4140 | saveSettingsDebounced(); |
| 4120 | } | 4141 | } |
| 4121 | 4142 | ||
| 4122 | if (entryPreviouslySelected !== -1) { | 4143 | // find all characters using the old lorebook name as their primary world |
| 4123 | const wiElement = getWIElement(newName); | 4144 | const linkedChIDs = []; |
| 4124 | wiElement.prop('selected', true); | 4145 | characters.forEach((character, chid) => { |
| 4125 | $('#world_info').trigger('change'); | 4146 | if (character.data?.extensions?.world === oldName) { |
| 4147 | linkedChIDs.push(chid); | ||
| 4148 | } | ||
| 4149 | }); | ||
| 4150 | |||
| 4151 | if (!linkedChIDs.length) { | ||
| 4152 | return; | ||
| 4126 | } | 4153 | } |
| 4127 | 4154 | ||
| 4128 | const selectedIndex = world_names.indexOf(newName); | 4155 | // Trigger the confirmation popup |
| 4129 | if (selectedIndex !== -1) { | 4156 | const updatePastLinksConfirm = await Popup.show.confirm( |
| 4130 | $('#world_editor_select').val(selectedIndex).trigger('change'); | 4157 | t`World/Lorebook renamed!`, |
| 4158 | `<p>${t`Auxiliary Lorebook links have been updated. Would you like to update primary lorebook links for ${linkedChIDs.length} character(s) as well?`}</p>`, | ||
| 4159 | ) == POPUP_RESULT.AFFIRMATIVE; | ||
| 4160 | |||
| 4161 | if (updatePastLinksConfirm) { | ||
| 4162 | let activeCharacterUpdated = false; | ||
| 4163 | |||
| 4164 | for (const chid of linkedChIDs) { | ||
| 4165 | const character = characters[chid]; | ||
| 4166 | |||
| 4167 | try { | ||
| 4168 | // /merge-attributes API call to update the file on the backend silently | ||
| 4169 | const response = await fetch('/api/characters/merge-attributes', { | ||
| 4170 | method: 'POST', | ||
| 4171 | headers: getRequestHeaders(), | ||
| 4172 | body: JSON.stringify({ | ||
| 4173 | avatar: character.avatar, | ||
| 4174 | data: { | ||
| 4175 | extensions: { | ||
| 4176 | world: newName, | ||
| 4177 | }, | ||
| 4178 | }, | ||
| 4179 | }), | ||
| 4180 | }); | ||
| 4181 | |||
| 4182 | if (!response.ok) { | ||
| 4183 | throw new Error(`Merge API returned ${response.status}`); | ||
| 4184 | } | ||
| 4185 | |||
| 4186 | // used to update the data in the browser's memory | ||
| 4187 | await getOneCharacter(character.avatar); | ||
| 4188 | |||
| 4189 | // Flag if the currently open character was affected | ||
| 4190 | if (String(chid) === String(this_chid)) { | ||
| 4191 | activeCharacterUpdated = true; | ||
| 4192 | } | ||
| 4193 | |||
| 4194 | toastr.success(`Successfully updated link for ${character.name}.`); | ||
| 4195 | } catch (e) { | ||
| 4196 | toastr.error(`Failed to update link for ${character.name}.`); | ||
| 4197 | console.error(`Backend update for character ${character.name} failed:`, e); | ||
| 4198 | } | ||
| 4199 | } | ||
| 4200 | |||
| 4201 | // update the UI fields | ||
| 4202 | // only required if the currently selected character was changed | ||
| 4203 | if (activeCharacterUpdated) { | ||
| 4204 | select_selected_character(this_chid, { switchMenu: false }); | ||
| 4205 | setWorldInfoButtonClass(this_chid, true); | ||
| 4206 | } | ||
| 4131 | } | 4207 | } |
| 4132 | } | 4208 | } |
| 4133 | 4209 | ||
| @@ -5732,13 +5808,15 @@ export function openWorldInfoEditor(worldName) { | |||
| 5732 | 5808 | ||
| 5733 | /** | 5809 | /** |
| 5734 | * Assigns a lorebook to the current chat. | 5810 | * Assigns a lorebook to the current chat. |
| 5735 | * @param {JQuery.ClickEvent<Document, undefined, any, any>} event Pointer event | 5811 | * @param {Object} options - The options for assigning the lorebook. |
| 5812 | * @param {boolean} options.shiftKey - Whether the Shift key is pressed. | ||
| 5813 | * @param {boolean} options.altKey - Whether the Alt key is pressed. | ||
| 5736 | * @returns {Promise<void>} | 5814 | * @returns {Promise<void>} |
| 5737 | */ | 5815 | */ |
| 5738 | export async function assignLorebookToChat(event) { | 5816 | export async function assignLorebookToChat({ shiftKey, altKey }) { |
| 5739 | const selectedName = chat_metadata[METADATA_KEY]; | 5817 | const selectedName = chat_metadata[METADATA_KEY]; |
| 5740 | 5818 | ||
| 5741 | if (selectedName && !event.shiftKey && !event.altKey) { | 5819 | if (selectedName && !shiftKey && !altKey) { |
| 5742 | openWorldInfoEditor(selectedName); | 5820 | openWorldInfoEditor(selectedName); |
| 5743 | return; | 5821 | return; |
| 5744 | } | 5822 | } |
| @@ -388,6 +388,7 @@ const toShallow = (character) => { | |||
| 388 | tags: _.get(character, 'data.tags', []), | 388 | tags: _.get(character, 'data.tags', []), |
| 389 | extensions: { | 389 | extensions: { |
| 390 | fav: _.get(character, 'data.extensions.fav', false), | 390 | fav: _.get(character, 'data.extensions.fav', false), |
| 391 | world: _.get(character, 'data.extensions.world', ''), | ||
| 391 | }, | 392 | }, |
| 392 | }, | 393 | }, |
| 393 | }; | 394 | }; |