Merge pull request #3915 from SillyTavern/feat/refactor-wi-init Refactor WI init to init function for more consistent startup
Signed| @@ -50,6 +50,7 @@ import { | ||
| 50 | 50 | importWorldInfo, |
| 51 | 51 | wi_anchor_position, |
| 52 | 52 | world_info_include_names, |
| 53 | + initWorldInfo, | |
| 53 | 54 | } from './scripts/world-info.js'; |
| 54 | 55 | |
| 55 | 56 | import { |
| @@ -992,6 +993,7 @@ async function firstLoadInit() { | ||
| 992 | 993 | initBackgrounds(); |
| 993 | 994 | initAuthorsNote(); |
| 994 | 995 | await initPersonas(); |
| 996 | + initWorldInfo(); | |
| 995 | 997 | initRossMods(); |
| 996 | 998 | initStats(); |
| 997 | 999 | initCfg(); |
| @@ -1047,7 +1047,7 @@ export function getImageSizeFromDataURL(dataUrl) { | ||
| 1047 | 1047 | |
| 1048 | 1048 | /** |
| 1049 | 1049 | * Gets the filename of the character avatar without extension |
| 1050 | 1050 | * @param {string|number?} [chid=null] - Character ID. If not provided, uses the current character ID |
| 1051 | 1051 | * @param {object} [options={}] - Options arguments |
| 1052 | 1052 | * @param {string?} [options.manualAvatarKey=null] - Manually take the following avatar key, instead of using the chid to determine the name |
| 1053 | 1053 | * @returns {string?} The filename of the character avatar without extension, or null if the character ID is invalid |
| @@ -966,7 +966,7 @@ function registerWorldInfoSlashCommands() { | ||
| 966 | 966 | /** |
| 967 | 967 | * Gets the name of the character-bound lorebook. |
| 968 | 968 | * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments |
| 969 | 969 | * @param {import('./slash-commands/SlashCommand.js').UnnamedArgumentsstring} name Character name |
| 970 | 970 | * @returns {string} The name of the character-bound lorebook, a JSON string of the character's lorebooks, or an empty string |
| 971 | 971 | */ |
| 972 | 972 | function getCharBookCallback({ type }, name) { |
| @@ -3443,7 +3443,7 @@ function createEntryInputAutocomplete(input, callback, { allowMultiple = false } | ||
| 3443 | 3443 | }); |
| 3444 | 3444 | |
| 3445 | 3445 | $(input).on('focus click', function () { |
| 3446 | 3446 | $(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : String($(input).val())); |
| 3447 | 3447 | }); |
| 3448 | 3448 | } |
| 3449 | 3449 | |
| @@ -5118,7 +5118,7 @@ export function openWorldInfoEditor(worldName) { | ||
| 5118 | 5118 | |
| 5119 | 5119 | /** |
| 5120 | 5120 | * Assigns a lorebook to the current chat. |
| 5121 | 5121 | * @param {PointerEventJQuery.ClickEvent<Document, undefined, any, any>} event Pointer event |
| 5122 | 5122 | * @returns {Promise<void>} |
| 5123 | 5123 | */ |
| 5124 | 5124 | export async function assignLorebookToChat(event) { |
| @@ -5157,11 +5157,106 @@ export async function assignLorebookToChat(event) { | ||
| 5157 | 5157 | saveMetadata(); |
| 5158 | 5158 | }); |
| 5159 | 5159 | |
| 5160 | 5160 | returnawait callGenericPopup(template, POPUP_TYPE.TEXT); |
| 5161 | 5161 | } |
| 5162 | 5162 | |
| 5163 | -jQuery(() => { | |
| 5163 | +/** | |
| 5164 | + * Moves a World Info entry from a source lorebook to a target lorebook. | |
| 5165 | + * | |
| 5166 | + * @param {string} sourceName - The name of the source lorebook file. | |
| 5167 | + * @param {string} targetName - The name of the target lorebook file. | |
| 5168 | + * @param {string|number} uid - The UID of the entry to move from the source lorebook. | |
| 5169 | + * @returns {Promise<boolean>} True if the move was successful, false otherwise. | |
| 5170 | + */ | |
| 5171 | +export async function moveWorldInfoEntry(sourceName, targetName, uid) { | |
| 5172 | + if (sourceName === targetName) { | |
| 5173 | + return false; | |
| 5174 | + } | |
| 5175 | + | |
| 5176 | + if (!world_names.includes(sourceName)) { | |
| 5177 | + toastr.error(t`Source lorebook '${sourceName}' not found.`); | |
| 5178 | + console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`); | |
| 5179 | + return false; | |
| 5180 | + } | |
| 5181 | + | |
| 5182 | + if (!world_names.includes(targetName)) { | |
| 5183 | + toastr.error(t`Target lorebook '${targetName}' not found.`); | |
| 5184 | + console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`); | |
| 5185 | + return false; | |
| 5186 | + } | |
| 5187 | + | |
| 5188 | + const entryUidString = String(uid); | |
| 5189 | + | |
| 5190 | + try { | |
| 5191 | + const sourceData = await loadWorldInfo(sourceName); | |
| 5192 | + const targetData = await loadWorldInfo(targetName); | |
| 5193 | + | |
| 5194 | + if (!sourceData || !sourceData.entries) { | |
| 5195 | + toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`); | |
| 5196 | + console.error(`[WI Move] Could not load source data for '${sourceName}'.`); | |
| 5197 | + return false; | |
| 5198 | + } | |
| 5199 | + if (!targetData || !targetData.entries) { | |
| 5200 | + toastr.error(t`Failed to load data for target lorebook '${targetName}'.`); | |
| 5201 | + console.error(`[WI Move] Could not load target data for '${targetName}'.`); | |
| 5202 | + return false; | |
| 5203 | + } | |
| 5204 | + | |
| 5205 | + if (!sourceData.entries[entryUidString]) { | |
| 5206 | + toastr.error(t`Entry not found in source lorebook '${sourceName}'.`); | |
| 5207 | + console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`); | |
| 5208 | + return false; | |
| 5209 | + } | |
| 5210 | + | |
| 5211 | + const entryToMove = structuredClone(sourceData.entries[entryUidString]); | |
| 5212 | + | |
| 5213 | + | |
| 5214 | + const newUid = getFreeWorldEntryUid(targetData); | |
| 5215 | + if (newUid === null) { | |
| 5216 | + console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`); | |
| 5217 | + return false; | |
| 5218 | + } | |
| 5219 | + | |
| 5220 | + entryToMove.uid = newUid; | |
| 5221 | + // Place the entry at the end of the target lorebook | |
| 5222 | + const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1); | |
| 5223 | + entryToMove.displayIndex = maxDisplayIndex + 1; | |
| 5224 | + | |
| 5225 | + targetData.entries[newUid] = entryToMove; | |
| 5226 | + | |
| 5227 | + delete sourceData.entries[entryUidString]; | |
| 5228 | + // Remove from originalData if it exists | |
| 5229 | + deleteWIOriginalDataValue(sourceData, entryUidString); | |
| 5230 | + // TODO: setWIOriginalDataValue | |
| 5231 | + console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`); | |
| 5232 | + | |
| 5233 | + | |
| 5234 | + await saveWorldInfo(targetName, targetData, true); | |
| 5235 | + console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); | |
| 5236 | + await saveWorldInfo(sourceName, sourceData, true); | |
| 5237 | + console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`); | |
| 5238 | + | |
| 5239 | + | |
| 5240 | + console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`); | |
| 5241 | + | |
| 5242 | + // Check if the currently viewed book in the editor is the source or target and reload it | |
| 5243 | + const currentEditorBookIndex = Number($('#world_editor_select').val()); | |
| 5244 | + if (!isNaN(currentEditorBookIndex)) { | |
| 5245 | + const currentEditorBookName = world_names[currentEditorBookIndex]; | |
| 5246 | + if (currentEditorBookName === sourceName || currentEditorBookName === targetName) { | |
| 5247 | + reloadEditor(currentEditorBookName); | |
| 5248 | + } | |
| 5249 | + } | |
| 5250 | + | |
| 5251 | + return true; | |
| 5252 | + } catch (error) { | |
| 5253 | + toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); | |
| 5254 | + console.error('[WI Move] Unexpected error:', error); | |
| 5255 | + return false; | |
| 5256 | + } | |
| 5257 | +} | |
| 5164 | 5258 | |
| 5259 | +export function initWorldInfo() { | |
| 5165 | 5260 | $('#world_info').on('mousedown change', async function (e) { |
| 5166 | 5261 | // If there's no world names, don't do anything |
| 5167 | 5262 | if (world_names.length === 0) { |
| @@ -5348,7 +5443,7 @@ jQuery(() => { | ||
| 5348 | 5443 | if (!isMobile()) { |
| 5349 | 5444 | $('#world_info').select2({ |
| 5350 | 5445 | width: '100%', |
| 5351 | 5446 | placeholder: 't`No Worlds active. Click here to select.'`, |
| 5352 | 5447 | allowClear: true, |
| 5353 | 5448 | closeOnSelect: false, |
| 5354 | 5449 | }); |
| @@ -5373,100 +5468,4 @@ jQuery(() => { | ||
| 5373 | 5468 | } |
| 5374 | 5469 | }); |
| 5375 | 5470 | }); |
| 5376 | -}); | |
| 5377 | - | |
| 5378 | -/** | |
| 5379 | - * Moves a World Info entry from a source lorebook to a target lorebook. | |
| 5380 | - * | |
| 5381 | - * @param {string} sourceName - The name of the source lorebook file. | |
| 5382 | - * @param {string} targetName - The name of the target lorebook file. | |
| 5383 | - * @param {string|number} uid - The UID of the entry to move from the source lorebook. | |
| 5384 | - * @returns {Promise<boolean>} True if the move was successful, false otherwise. | |
| 5385 | - */ | |
| 5386 | -export async function moveWorldInfoEntry(sourceName, targetName, uid) { | |
| 5387 | - if (sourceName === targetName) { | |
| 5388 | - return false; | |
| 5389 | - } | |
| 5390 | - | |
| 5391 | - if (!world_names.includes(sourceName)) { | |
| 5392 | - toastr.error(t`Source lorebook '${sourceName}' not found.`); | |
| 5393 | - console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`); | |
| 5394 | - return false; | |
| 5395 | - } | |
| 5396 | - | |
| 5397 | - if (!world_names.includes(targetName)) { | |
| 5398 | - toastr.error(t`Target lorebook '${targetName}' not found.`); | |
| 5399 | - console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`); | |
| 5400 | - return false; | |
| 5401 | - } | |
| 5402 | - | |
| 5403 | - const entryUidString = String(uid); | |
| 5404 | - | |
| 5405 | - try { | |
| 5406 | - const sourceData = await loadWorldInfo(sourceName); | |
| 5407 | - const targetData = await loadWorldInfo(targetName); | |
| 5408 | - | |
| 5409 | - if (!sourceData || !sourceData.entries) { | |
| 5410 | - toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`); | |
| 5411 | - console.error(`[WI Move] Could not load source data for '${sourceName}'.`); | |
| 5412 | - return false; | |
| 5413 | - } | |
| 5414 | - if (!targetData || !targetData.entries) { | |
| 5415 | - toastr.error(t`Failed to load data for target lorebook '${targetName}'.`); | |
| 5416 | - console.error(`[WI Move] Could not load target data for '${targetName}'.`); | |
| 5417 | - return false; | |
| 5418 | - } | |
| 5419 | - | |
| 5420 | - if (!sourceData.entries[entryUidString]) { | |
| 5421 | - toastr.error(t`Entry not found in source lorebook '${sourceName}'.`); | |
| 5422 | - console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`); | |
| 5423 | - return false; | |
| 5424 | - } | |
| 5425 | - | |
| 5426 | - const entryToMove = structuredClone(sourceData.entries[entryUidString]); | |
| 5427 | - | |
| 5428 | - | |
| 5429 | - const newUid = getFreeWorldEntryUid(targetData); | |
| 5430 | - if (newUid === null) { | |
| 5431 | - console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`); | |
| 5432 | - return false; | |
| 5433 | - } | |
| 5434 | - | |
| 5435 | - entryToMove.uid = newUid; | |
| 5436 | - // Place the entry at the end of the target lorebook | |
| 5437 | - const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1); | |
| 5438 | - entryToMove.displayIndex = maxDisplayIndex + 1; | |
| 5439 | - | |
| 5440 | - targetData.entries[newUid] = entryToMove; | |
| 5441 | - | |
| 5442 | - delete sourceData.entries[entryUidString]; | |
| 5443 | - // Remove from originalData if it exists | |
| 5444 | - deleteWIOriginalDataValue(sourceData, entryUidString); | |
| 5445 | - // TODO: setWIOriginalDataValue | |
| 5446 | - console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`); | |
| 5447 | - | |
| 5448 | - | |
| 5449 | - await saveWorldInfo(targetName, targetData, true); | |
| 5450 | - console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); | |
| 5451 | - await saveWorldInfo(sourceName, sourceData, true); | |
| 5452 | - console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`); | |
| 5453 | - | |
| 5454 | - | |
| 5455 | - console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`); | |
| 5456 | - | |
| 5457 | - // Check if the currently viewed book in the editor is the source or target and reload it | |
| 5458 | - const currentEditorBookIndex = Number($('#world_editor_select').val()); | |
| 5459 | - if (!isNaN(currentEditorBookIndex)) { | |
| 5460 | - const currentEditorBookName = world_names[currentEditorBookIndex]; | |
| 5461 | - if (currentEditorBookName === sourceName || currentEditorBookName === targetName) { | |
| 5462 | - reloadEditor(currentEditorBookName); | |
| 5463 | - } | |
| 5464 | - } | |
| 5465 | - | |
| 5466 | - return true; | |
| 5467 | - } catch (error) { | |
| 5468 | - toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); | |
| 5469 | - console.error('[WI Move] Unexpected error:', error); | |
| 5470 | - return false; | |
| 5471 | - } | |
| 5472 | 5471 | } |