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 | importWorldInfo, | 50 | importWorldInfo, |
| 51 | wi_anchor_position, | 51 | wi_anchor_position, |
| 52 | world_info_include_names, | 52 | world_info_include_names, |
| 53 | initWorldInfo, | ||
| 53 | } from './scripts/world-info.js'; | 54 | } from './scripts/world-info.js'; |
| 54 | 55 | ||
| 55 | import { | 56 | import { |
| @@ -992,6 +993,7 @@ async function firstLoadInit() { | |||
| 992 | initBackgrounds(); | 993 | initBackgrounds(); |
| 993 | initAuthorsNote(); | 994 | initAuthorsNote(); |
| 994 | await initPersonas(); | 995 | await initPersonas(); |
| 996 | initWorldInfo(); | ||
| 995 | initRossMods(); | 997 | initRossMods(); |
| 996 | initStats(); | 998 | initStats(); |
| 997 | initCfg(); | 999 | initCfg(); |
| @@ -1047,7 +1047,7 @@ export function getImageSizeFromDataURL(dataUrl) { | |||
| 1047 | 1047 | ||
| 1048 | /** | 1048 | /** |
| 1049 | * Gets the filename of the character avatar without extension | 1049 | * Gets the filename of the character avatar without extension |
| 1050 | * @param {number?} [chid=null] - Character ID. If not provided, uses the current character ID | 1050 | * @param {string|number?} [chid=null] - Character ID. If not provided, uses the current character ID |
| 1051 | * @param {object} [options={}] - Options arguments | 1051 | * @param {object} [options={}] - Options arguments |
| 1052 | * @param {string?} [options.manualAvatarKey=null] - Manually take the following avatar key, instead of using the chid to determine the name | 1052 | * @param {string?} [options.manualAvatarKey=null] - Manually take the following avatar key, instead of using the chid to determine the name |
| 1053 | * @returns {string?} The filename of the character avatar without extension, or null if the character ID is invalid | 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 | * Gets the name of the character-bound lorebook. | 967 | * Gets the name of the character-bound lorebook. |
| 968 | * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments | 968 | * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args Named arguments |
| 969 | * @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} name Character name | 969 | * @param {string} name Character name |
| 970 | * @returns {string} The name of the character-bound lorebook, a JSON string of the character's lorebooks, or an empty string | 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 | function getCharBookCallback({ type }, name) { | 972 | function getCharBookCallback({ type }, name) { |
| @@ -3443,7 +3443,7 @@ function createEntryInputAutocomplete(input, callback, { allowMultiple = false } | |||
| 3443 | }); | 3443 | }); |
| 3444 | 3444 | ||
| 3445 | $(input).on('focus click', function () { | 3445 | $(input).on('focus click', function () { |
| 3446 | $(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : $(input).val()); | 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 | * Assigns a lorebook to the current chat. | 5120 | * Assigns a lorebook to the current chat. |
| 5121 | * @param {PointerEvent} event Pointer event | 5121 | * @param {JQuery.ClickEvent<Document, undefined, any, any>} event Pointer event |
| 5122 | * @returns {Promise<void>} | 5122 | * @returns {Promise<void>} |
| 5123 | */ | 5123 | */ |
| 5124 | export async function assignLorebookToChat(event) { | 5124 | export async function assignLorebookToChat(event) { |
| @@ -5157,11 +5157,106 @@ export async function assignLorebookToChat(event) { | |||
| 5157 | saveMetadata(); | 5157 | saveMetadata(); |
| 5158 | }); | 5158 | }); |
| 5159 | 5159 | ||
| 5160 | return callGenericPopup(template, POPUP_TYPE.TEXT); | 5160 | await 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 | $('#world_info').on('mousedown change', async function (e) { | 5260 | $('#world_info').on('mousedown change', async function (e) { |
| 5166 | // If there's no world names, don't do anything | 5261 | // If there's no world names, don't do anything |
| 5167 | if (world_names.length === 0) { | 5262 | if (world_names.length === 0) { |
| @@ -5348,7 +5443,7 @@ jQuery(() => { | |||
| 5348 | if (!isMobile()) { | 5443 | if (!isMobile()) { |
| 5349 | $('#world_info').select2({ | 5444 | $('#world_info').select2({ |
| 5350 | width: '100%', | 5445 | width: '100%', |
| 5351 | placeholder: 'No Worlds active. Click here to select.', | 5446 | placeholder: t`No Worlds active. Click here to select.`, |
| 5352 | allowClear: true, | 5447 | allowClear: true, |
| 5353 | closeOnSelect: false, | 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 | } |