diff --git a/public/script.js b/public/script.js index ca5f87850..cf18599fa 100644 --- a/public/script.js +++ b/public/script.js @@ -50,6 +50,7 @@ import { importWorldInfo, wi_anchor_position, world_info_include_names, + initWorldInfo, } from './scripts/world-info.js'; import { @@ -992,6 +993,7 @@ async function firstLoadInit() { initBackgrounds(); initAuthorsNote(); await initPersonas(); + initWorldInfo(); initRossMods(); initStats(); initCfg(); diff --git a/public/scripts/utils.js b/public/scripts/utils.js index 47bdaa253..56b4f4a70 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -1047,7 +1047,7 @@ export function getImageSizeFromDataURL(dataUrl) { /** * Gets the filename of the character avatar without extension - * @param {number?} [chid=null] - Character ID. If not provided, uses the current character ID + * @param {string|number?} [chid=null] - Character ID. If not provided, uses the current character ID * @param {object} [options={}] - Options arguments * @param {string?} [options.manualAvatarKey=null] - Manually take the following avatar key, instead of using the chid to determine the name * @returns {string?} The filename of the character avatar without extension, or null if the character ID is invalid diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 917840532..52c25c1da 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -966,7 +966,7 @@ function registerWorldInfoSlashCommands() { /** * Gets the name of the character-bound lorebook. * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments - * @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} name Character name + * @param {string} name Character name * @returns {string} The name of the character-bound lorebook, a JSON string of the character's lorebooks, or an empty string */ function getCharBookCallback({ type }, name) { @@ -3443,7 +3443,7 @@ function createEntryInputAutocomplete(input, callback, { allowMultiple = false } }); $(input).on('focus click', function () { - $(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : $(input).val()); + $(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : String($(input).val())); }); } @@ -5118,7 +5118,7 @@ export function openWorldInfoEditor(worldName) { /** * Assigns a lorebook to the current chat. - * @param {PointerEvent} event Pointer event + * @param {JQuery.ClickEvent} event Pointer event * @returns {Promise} */ export async function assignLorebookToChat(event) { @@ -5157,11 +5157,106 @@ export async function assignLorebookToChat(event) { saveMetadata(); }); - return callGenericPopup(template, POPUP_TYPE.TEXT); + await callGenericPopup(template, POPUP_TYPE.TEXT); } -jQuery(() => { +/** + * Moves a World Info entry from a source lorebook to a target lorebook. + * + * @param {string} sourceName - The name of the source lorebook file. + * @param {string} targetName - The name of the target lorebook file. + * @param {string|number} uid - The UID of the entry to move from the source lorebook. + * @returns {Promise} True if the move was successful, false otherwise. + */ +export async function moveWorldInfoEntry(sourceName, targetName, uid) { + if (sourceName === targetName) { + return false; + } + if (!world_names.includes(sourceName)) { + toastr.error(t`Source lorebook '${sourceName}' not found.`); + console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`); + return false; + } + + if (!world_names.includes(targetName)) { + toastr.error(t`Target lorebook '${targetName}' not found.`); + console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`); + return false; + } + + const entryUidString = String(uid); + + try { + const sourceData = await loadWorldInfo(sourceName); + const targetData = await loadWorldInfo(targetName); + + if (!sourceData || !sourceData.entries) { + toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`); + console.error(`[WI Move] Could not load source data for '${sourceName}'.`); + return false; + } + if (!targetData || !targetData.entries) { + toastr.error(t`Failed to load data for target lorebook '${targetName}'.`); + console.error(`[WI Move] Could not load target data for '${targetName}'.`); + return false; + } + + if (!sourceData.entries[entryUidString]) { + toastr.error(t`Entry not found in source lorebook '${sourceName}'.`); + console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`); + return false; + } + + const entryToMove = structuredClone(sourceData.entries[entryUidString]); + + + const newUid = getFreeWorldEntryUid(targetData); + if (newUid === null) { + console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`); + return false; + } + + entryToMove.uid = newUid; + // Place the entry at the end of the target lorebook + const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1); + entryToMove.displayIndex = maxDisplayIndex + 1; + + targetData.entries[newUid] = entryToMove; + + delete sourceData.entries[entryUidString]; + // Remove from originalData if it exists + deleteWIOriginalDataValue(sourceData, entryUidString); + // TODO: setWIOriginalDataValue + console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`); + + + await saveWorldInfo(targetName, targetData, true); + console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); + await saveWorldInfo(sourceName, sourceData, true); + console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`); + + + console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`); + + // Check if the currently viewed book in the editor is the source or target and reload it + const currentEditorBookIndex = Number($('#world_editor_select').val()); + if (!isNaN(currentEditorBookIndex)) { + const currentEditorBookName = world_names[currentEditorBookIndex]; + if (currentEditorBookName === sourceName || currentEditorBookName === targetName) { + reloadEditor(currentEditorBookName); + } + } + + return true; + } catch (error) { + toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); + console.error('[WI Move] Unexpected error:', error); + return false; + } +} + +export function initWorldInfo() { $('#world_info').on('mousedown change', async function (e) { // If there's no world names, don't do anything if (world_names.length === 0) { @@ -5348,7 +5443,7 @@ jQuery(() => { if (!isMobile()) { $('#world_info').select2({ width: '100%', - placeholder: 'No Worlds active. Click here to select.', + placeholder: t`No Worlds active. Click here to select.`, allowClear: true, closeOnSelect: false, }); @@ -5373,100 +5468,4 @@ jQuery(() => { } }); }); -}); - -/** - * Moves a World Info entry from a source lorebook to a target lorebook. - * - * @param {string} sourceName - The name of the source lorebook file. - * @param {string} targetName - The name of the target lorebook file. - * @param {string|number} uid - The UID of the entry to move from the source lorebook. - * @returns {Promise} True if the move was successful, false otherwise. - */ -export async function moveWorldInfoEntry(sourceName, targetName, uid) { - if (sourceName === targetName) { - return false; - } - - if (!world_names.includes(sourceName)) { - toastr.error(t`Source lorebook '${sourceName}' not found.`); - console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`); - return false; - } - - if (!world_names.includes(targetName)) { - toastr.error(t`Target lorebook '${targetName}' not found.`); - console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`); - return false; - } - - const entryUidString = String(uid); - - try { - const sourceData = await loadWorldInfo(sourceName); - const targetData = await loadWorldInfo(targetName); - - if (!sourceData || !sourceData.entries) { - toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`); - console.error(`[WI Move] Could not load source data for '${sourceName}'.`); - return false; - } - if (!targetData || !targetData.entries) { - toastr.error(t`Failed to load data for target lorebook '${targetName}'.`); - console.error(`[WI Move] Could not load target data for '${targetName}'.`); - return false; - } - - if (!sourceData.entries[entryUidString]) { - toastr.error(t`Entry not found in source lorebook '${sourceName}'.`); - console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`); - return false; - } - - const entryToMove = structuredClone(sourceData.entries[entryUidString]); - - - const newUid = getFreeWorldEntryUid(targetData); - if (newUid === null) { - console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`); - return false; - } - - entryToMove.uid = newUid; - // Place the entry at the end of the target lorebook - const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1); - entryToMove.displayIndex = maxDisplayIndex + 1; - - targetData.entries[newUid] = entryToMove; - - delete sourceData.entries[entryUidString]; - // Remove from originalData if it exists - deleteWIOriginalDataValue(sourceData, entryUidString); - // TODO: setWIOriginalDataValue - console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`); - - - await saveWorldInfo(targetName, targetData, true); - console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); - await saveWorldInfo(sourceName, sourceData, true); - console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`); - - - console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`); - - // Check if the currently viewed book in the editor is the source or target and reload it - const currentEditorBookIndex = Number($('#world_editor_select').val()); - if (!isNaN(currentEditorBookIndex)) { - const currentEditorBookName = world_names[currentEditorBookIndex]; - if (currentEditorBookName === sourceName || currentEditorBookName === targetName) { - reloadEditor(currentEditorBookName); - } - } - - return true; - } catch (error) { - toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); - console.error('[WI Move] Unexpected error:', error); - return false; - } }