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, +91 -12Ignore whitespace
public/scripts/world-info.js+90 -12
@@ -1,6 +1,6 @@
11import { Fuse } from '../lib.js';
22
33import { 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';
44import { 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';
55import { extension_settings, getContext } from './extensions.js';
66import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
@@ -835,7 +835,7 @@ export function updateWorldInfoSettings(settings, activeWorldInfo) {
835835 world_info_use_group_scoring: (value) => world_info_use_group_scoring = Boolean(value),
836836 world_info_max_recursion_steps: (value) => world_info_max_recursion_steps = Number(value),
837837 // Unused
838838 world_info: (_value) => { },
839839 };
840840
841841 for (const [key, setter] of Object.entries(fields)) {
@@ -4109,6 +4109,27 @@ async function renameWorldInfo(name, data) {
41094109 await saveWorldInfo(newName, data, true);
41104110 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+ */
4132+async function updateWorldInfoLinks(oldName, newName) {
41124133 const existingCharLores = world_info.charLore?.filter((e) => e.extraBooks.includes(oldName));
41134134 if (existingCharLores && existingCharLores.length > 0) {
41144135 existingCharLores.forEach((charLore) => {
@@ -4119,15 +4140,70 @@ async function renameWorldInfo(name, data) {
41194140 saveSettingsDebounced();
41204141 }
41214142
4122- if (entryPreviouslySelected !== -1) {
4143+ // find all characters using the old lorebook name as their primary world
41234144 const wiElementlinkedChIDs = getWIElement(newName)[];
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;
41264153 }
41274154
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+ }
41314207 }
41324208}
41334209
@@ -5732,13 +5808,15 @@ export function openWorldInfoEditor(worldName) {
57325808
57335809/**
57345810 * 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.
57365814 * @returns {Promise<void>}
57375815 */
57385816export async function assignLorebookToChat(event{ shiftKey, altKey }) {
57395817 const selectedName = chat_metadata[METADATA_KEY];
57405818
57415819 if (selectedName && !event.shiftKey && !event.altKey) {
57425820 openWorldInfoEditor(selectedName);
57435821 return;
57445822 }
src/endpoints/characters.js+1 -0
@@ -388,6 +388,7 @@ const toShallow = (character) => {
388388 tags: _.get(character, 'data.tags', []),
389389 extensions: {
390390 fav: _.get(character, 'data.extensions.fav', false),
391+ world: _.get(character, 'data.extensions.world', ''),
391392 },
392393 },
393394 };