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>

a794a3780aea7e289b42957e7c7a7019d6ec68d6

SoniaNvm <sonianevermindwaifu@gmail.com>

Signed
2 files changed, +90 -11Showing whitespace changes
public/scripts/world-info.js+89 -11
@@ -1,6 +1,6 @@
1import { Fuse } from '../lib.js';1import { Fuse } from '../lib.js';
22
3import { 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';3import { 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';
4import { 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';4import { 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';
5import { extension_settings, getContext } from './extensions.js';5import { extension_settings, getContext } from './extensions.js';
6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';6import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
@@ -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);
41114111
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 */
4132async 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 }
41214142
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);
4126 }4148 }
4149 });
41274150
4128 const selectedIndex = world_names.indexOf(newName);4151 if (!linkedChIDs.length) {
4129 if (selectedIndex !== -1) {4152 return;
4130 $('#world_editor_select').val(selectedIndex).trigger('change');4153 }
4154
4155 // Trigger the confirmation popup
4156 const updatePastLinksConfirm = await Popup.show.confirm(
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}
41334209
@@ -5732,13 +5808,15 @@ export function openWorldInfoEditor(worldName) {
57325808
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 event5811 * @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 */
5738export async function assignLorebookToChat(event) {5816export async function assignLorebookToChat({ shiftKey, altKey }) {
5739 const selectedName = chat_metadata[METADATA_KEY];5817 const selectedName = chat_metadata[METADATA_KEY];
57405818
5741 if (selectedName && !event.shiftKey && !event.altKey) {5819 if (selectedName && !shiftKey && !altKey) {
5742 openWorldInfoEditor(selectedName);5820 openWorldInfoEditor(selectedName);
5743 return;5821 return;
5744 }5822 }
src/endpoints/characters.js+1 -0
@@ -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 };