Merge pull request #2601 from SillyTavern/wi-blackbox-be-gone

WI blackbox begone (Export, document and refactor a bit of the WI API)
This commit is contained in:
Cohee 2024-08-03 11:57:52 +03:00 committed by GitHub
commit d5576cd7b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 189 additions and 144 deletions

View File

@ -18,33 +18,13 @@ import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
import { StructuredCloneMap } from './util/StructuredCloneMap.js'; import { StructuredCloneMap } from './util/StructuredCloneMap.js';
export { export const world_info_insertion_strategy = {
world_info,
world_info_budget,
world_info_depth,
world_info_min_activations,
world_info_min_activations_depth_max,
world_info_include_names,
world_info_recursive,
world_info_overflow_alert,
world_info_case_sensitive,
world_info_match_whole_words,
world_info_character_strategy,
world_info_budget_cap,
world_names,
checkWorldInfo,
deleteWorldInfo,
setWorldInfoSettings,
getWorldInfoPrompt,
};
const world_info_insertion_strategy = {
evenly: 0, evenly: 0,
character_first: 1, character_first: 1,
global_first: 2, global_first: 2,
}; };
const world_info_logic = { export const world_info_logic = {
AND_ANY: 0, AND_ANY: 0,
NOT_ALL: 1, NOT_ALL: 1,
NOT_ANY: 2, NOT_ANY: 2,
@ -54,7 +34,7 @@ const world_info_logic = {
/** /**
* @enum {number} Possible states of the WI evaluation * @enum {number} Possible states of the WI evaluation
*/ */
const scan_state = { export const scan_state = {
/** /**
* The scan will be stopped. * The scan will be stopped.
*/ */
@ -75,23 +55,23 @@ const scan_state = {
const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry'); const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');
let world_info = {}; export let world_info = {};
let selected_world_info = []; export let selected_world_info = [];
/** @type {string[]} */ /** @type {string[]} */
let world_names; export let world_names;
let world_info_depth = 2; export let world_info_depth = 2;
let world_info_min_activations = 0; // if > 0, will continue seeking chat until minimum world infos are activated export let world_info_min_activations = 0; // if > 0, will continue seeking chat until minimum world infos are activated
let world_info_min_activations_depth_max = 0; // used when (world_info_min_activations > 0) export let world_info_min_activations_depth_max = 0; // used when (world_info_min_activations > 0)
let world_info_budget = 25; export let world_info_budget = 25;
let world_info_include_names = true; export let world_info_include_names = true;
let world_info_recursive = false; export let world_info_recursive = false;
let world_info_overflow_alert = false; export let world_info_overflow_alert = false;
let world_info_case_sensitive = false; export let world_info_case_sensitive = false;
let world_info_match_whole_words = false; export let world_info_match_whole_words = false;
let world_info_use_group_scoring = false; export let world_info_use_group_scoring = false;
let world_info_character_strategy = world_info_insertion_strategy.character_first; export let world_info_character_strategy = world_info_insertion_strategy.character_first;
let world_info_budget_cap = 0; export let world_info_budget_cap = 0;
const saveWorldDebounced = debounce(async (name, data) => await _save(name, data), debounce_timeout.relaxed); const saveWorldDebounced = debounce(async (name, data) => await _save(name, data), debounce_timeout.relaxed);
const saveSettingsDebounced = debounce(() => { const saveSettingsDebounced = debounce(() => {
Object.assign(world_info, { globalSelect: selected_world_info }); Object.assign(world_info, { globalSelect: selected_world_info });
@ -101,13 +81,13 @@ const sortFn = (a, b) => b.order - a.order;
let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); }; let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };
// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data. // Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
const worldInfoFilter = new FilterHelper(() => updateEditor()); export const worldInfoFilter = new FilterHelper(() => updateEditor());
const SORT_ORDER_KEY = 'world_info_sort_order'; export const SORT_ORDER_KEY = 'world_info_sort_order';
const METADATA_KEY = 'world_info'; export const METADATA_KEY = 'world_info';
const DEFAULT_DEPTH = 4; export const DEFAULT_DEPTH = 4;
const DEFAULT_WEIGHT = 100; export const DEFAULT_WEIGHT = 100;
const MAX_SCAN_DEPTH = 1000; export const MAX_SCAN_DEPTH = 1000;
const KNOWN_DECORATORS = ['@@activate', '@@dont_activate']; const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];
// Typedef area // Typedef area
@ -732,7 +712,7 @@ export function getWorldInfoSettings() {
}; };
} }
const world_info_position = { export const world_info_position = {
before: 0, before: 0,
after: 1, after: 1,
ANTop: 2, ANTop: 2,
@ -747,8 +727,18 @@ export const wi_anchor_position = {
after: 1, after: 1,
}; };
/** @type {StructuredCloneMap<string,object>} */ /**
const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: false }); * The cache of all world info data that was loaded from the backend.
*
* Calling `loadWorldInfo` will fill this cache and utilize this cache, so should be the preferred way to load any world info data.
* Only use the cache directly if you need synchronous access.
*
* This will return a deep clone of the data, so no way to modify the data without actually saving it.
* Should generally be only used for readonly access.
*
* @type {StructuredCloneMap<string,object>}
* */
export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: false });
/** /**
* Gets the world info based on chat messages. * Gets the world info based on chat messages.
@ -758,7 +748,7 @@ const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: fa
* @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult
* @returns {Promise<WIPromptResult>} The world info string and depth. * @returns {Promise<WIPromptResult>} The world info string and depth.
*/ */
async function getWorldInfoPrompt(chat, maxContext, isDryRun) { export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = ''; let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';
const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun); const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun);
@ -780,7 +770,7 @@ async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
}; };
} }
function setWorldInfoSettings(settings, data) { export function setWorldInfoSettings(settings, data) {
if (settings.world_info_depth !== undefined) if (settings.world_info_depth !== undefined)
world_info_depth = Number(settings.world_info_depth); world_info_depth = Number(settings.world_info_depth);
if (settings.world_info_min_activations !== undefined) if (settings.world_info_min_activations !== undefined)
@ -916,7 +906,7 @@ function registerWorldInfoSlashCommands() {
return ''; return '';
} }
const data = await loadWorldInfoData(file); const data = await loadWorldInfo(file);
if (!data || !('entries' in data)) { if (!data || !('entries' in data)) {
toastr.warning('World Info file has an invalid format'); toastr.warning('World Info file has an invalid format');
@ -965,7 +955,7 @@ function registerWorldInfoSlashCommands() {
return ''; return '';
} }
if (typeof newEntryTemplate[field] === 'boolean') { if (typeof newWorldInfoEntryTemplate[field] === 'boolean') {
const isTrue = isTrueBoolean(value); const isTrue = isTrueBoolean(value);
const isFalse = isFalseBoolean(value); const isFalse = isFalseBoolean(value);
@ -1016,7 +1006,7 @@ function registerWorldInfoSlashCommands() {
return ''; return '';
} }
if (newEntryTemplate[field] === undefined) { if (newWorldInfoEntryTemplate[field] === undefined) {
toastr.warning('Valid field name is required'); toastr.warning('Valid field name is required');
return ''; return '';
} }
@ -1038,7 +1028,7 @@ function registerWorldInfoSlashCommands() {
const file = args.file; const file = args.file;
const key = args.key; const key = args.key;
const data = await loadWorldInfoData(file); const data = await loadWorldInfo(file);
if (!data || !('entries' in data)) { if (!data || !('entries' in data)) {
toastr.warning('Valid World Info file name is required'); toastr.warning('Valid World Info file name is required');
@ -1075,7 +1065,7 @@ function registerWorldInfoSlashCommands() {
value = value.replace(/\\([{}|])/g, '$1'); value = value.replace(/\\([{}|])/g, '$1');
const data = await loadWorldInfoData(file); const data = await loadWorldInfo(file);
if (!data || !('entries' in data)) { if (!data || !('entries' in data)) {
toastr.warning('Valid World Info file name is required'); toastr.warning('Valid World Info file name is required');
@ -1089,7 +1079,7 @@ function registerWorldInfoSlashCommands() {
return ''; return '';
} }
if (newEntryTemplate[field] === undefined) { if (newWorldInfoEntryTemplate[field] === undefined) {
toastr.warning('Valid field name is required'); toastr.warning('Valid field name is required');
return ''; return '';
} }
@ -1104,8 +1094,8 @@ function registerWorldInfoSlashCommands() {
entry[field] = value; entry[field] = value;
} }
if (originalDataKeyMap[field]) { if (originalWIDataKeyMap[field]) {
setOriginalDataValue(data, uid, originalDataKeyMap[field], entry[field]); setWIOriginalDataValue(data, uid, originalWIDataKeyMap[field], entry[field]);
} }
await saveWorldInfo(file, data); await saveWorldInfo(file, data);
@ -1226,7 +1216,7 @@ function registerWorldInfoSlashCommands() {
/** A collection of local enum providers for this context of world info */ /** A collection of local enum providers for this context of world info */
const localEnumProviders = { const localEnumProviders = {
/** All possible fields that can be set in a WI entry */ /** All possible fields that can be set in a WI entry */
wiEntryFields: () => Object.entries(newEntryDefinition).map(([key, value]) => wiEntryFields: () => Object.entries(newWorldInfoEntryDefinition).map(([key, value]) =>
new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : value.default)}`, new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : value.default)}`,
enumTypes.enum, enumIcons.getDataTypeIcon(value.type))), enumTypes.enum, enumIcons.getDataTypeIcon(value.type))),
@ -1566,18 +1556,32 @@ function registerWorldInfoSlashCommands() {
})); }));
} }
// World Info Editor
async function showWorldEditor(name) { /**
* Loads the given world into the World Editor.
*
* @param {string} name - The name of the world
* @return {Promise<void>} A promise that resolves when the world editor is loaded
*/
export async function showWorldEditor(name) {
if (!name) { if (!name) {
hideWorldEditor(); hideWorldEditor();
return; return;
} }
const wiData = await loadWorldInfoData(name); const wiData = await loadWorldInfo(name);
displayWorldEntries(name, wiData); displayWorldEntries(name, wiData);
} }
async function loadWorldInfoData(name) { /**
* Loads world info from the backend.
*
* This function will return from `worldInfoCache` if it has already been loaded before.
*
* @param {string} name - The name of the world to load
* @return {Promise<Object|null>} A promise that resolves to the loaded world information, or null if the request fails.
*/
export async function loadWorldInfo(name) {
if (!name) { if (!name) {
return; return;
} }
@ -1635,14 +1639,18 @@ function getWIElement(name) {
} }
/** /**
* Sorts the given data based on the selected sort option
*
* @param {any[]} data WI entries * @param {any[]} data WI entries
* @param {object} [options={}] - Optional arguments
* @param {{sortField?: string, sortOrder?: string, sortRule?: string}} [options.customSort={}] - Custom sort options, instead of the chosen UI sort
* @returns {any[]} Sorted data * @returns {any[]} Sorted data
*/ */
function sortEntries(data) { export function sortWorldInfoEntries(data, { customSort = null } = {}) {
const option = $('#world_info_sort_order').find(':selected'); const option = $('#world_info_sort_order').find(':selected');
const sortField = option.data('field'); const sortField = customSort?.sortField ?? option.data('field');
const sortOrder = option.data('order'); const sortOrder = customSort?.sortOrder ?? option.data('order');
const sortRule = option.data('rule'); const sortRule = customSort?.sortRule ?? option.data('rule');
const orderSign = sortOrder === 'asc' ? 1 : -1; const orderSign = sortOrder === 'asc' ? 1 : -1;
if (!data.length) return data; if (!data.length) return data;
@ -1801,7 +1809,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
// Apply the filter and do the chosen sorting // Apply the filter and do the chosen sorting
entriesArray = worldInfoFilter.applyFilters(entriesArray); entriesArray = worldInfoFilter.applyFilters(entriesArray);
entriesArray = sortEntries(entriesArray); entriesArray = sortWorldInfoEntries(entriesArray);
// Cache keys // Cache keys
const keys = entriesArray.flatMap(entry => [...entry.key, ...entry.keysecondary]); const keys = entriesArray.flatMap(entry => [...entry.key, ...entry.keysecondary]);
@ -1919,7 +1927,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
for (const entry of Object.values(data.entries)) { for (const entry of Object.values(data.entries)) {
if (!entry.comment && Array.isArray(entry.key) && entry.key.length > 0) { if (!entry.comment && Array.isArray(entry.key) && entry.key.length > 0) {
entry.comment = entry.key[0]; entry.comment = entry.key[0];
setOriginalDataValue(data, entry.uid, 'comment', entry.comment); setWIOriginalDataValue(data, entry.uid, 'comment', entry.comment);
counter++; counter++;
} }
} }
@ -1954,7 +1962,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
// We need to sort the entries here, as the data source isn't sorted // We need to sort the entries here, as the data source isn't sorted
const entries = Object.values(data.entries); const entries = Object.values(data.entries);
sortEntries(entries); sortWorldInfoEntries(entries);
let updated = 0, current = start; let updated = 0, current = start;
for (const entry of entries) { for (const entry of entries) {
@ -1962,7 +1970,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
if (entry.order === newOrder) continue; if (entry.order === newOrder) continue;
entry.order = newOrder; entry.order = newOrder;
setOriginalDataValue(data, entry.order, 'order', entry.order); setWIOriginalDataValue(data, entry.order, 'order', entry.order);
updated++; updated++;
} }
@ -2025,7 +2033,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
} }
item.displayIndex = minDisplayIndex + index; item.displayIndex = minDisplayIndex + index;
setOriginalDataValue(data, uid, 'extensions.display_index', item.displayIndex); setWIOriginalDataValue(data, uid, 'extensions.display_index', item.displayIndex);
}); });
console.table(Object.keys(data.entries).map(uid => data.entries[uid]).map(x => ({ uid: x.uid, key: x.key.join(','), displayIndex: x.displayIndex }))); console.table(Object.keys(data.entries).map(uid => data.entries[uid]).map(x => ({ uid: x.uid, key: x.key.join(','), displayIndex: x.displayIndex })));
@ -2036,7 +2044,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
//$("#world_popup_entries_list").disableSelection(); //$("#world_popup_entries_list").disableSelection();
} }
const originalDataKeyMap = { export const originalWIDataKeyMap = {
'displayIndex': 'extensions.display_index', 'displayIndex': 'extensions.display_index',
'excludeRecursion': 'extensions.exclude_recursion', 'excludeRecursion': 'extensions.exclude_recursion',
'preventRecursion': 'extensions.prevent_recursion', 'preventRecursion': 'extensions.prevent_recursion',
@ -2087,7 +2095,17 @@ function verifyWorldInfoSearchSortRule() {
} }
} }
function setOriginalDataValue(data, uid, key, value) { /**
* Sets the value of a specific key in the original data entry corresponding to the given uid
* This needs to be called whenever you update JSON data fields.
* Use `originalWIDataKeyMap` to find the correct value to be set.
*
* @param {object} data - The data object containing the original data entries.
* @param {string} uid - The unique identifier of the data entry.
* @param {string} key - The key of the value to be set.
* @param {any} value - The value to be set.
*/
export function setWIOriginalDataValue(data, uid, key, value) {
if (data.originalData && Array.isArray(data.originalData.entries)) { if (data.originalData && Array.isArray(data.originalData.entries)) {
let originalEntry = data.originalData.entries.find(x => x.uid === uid); let originalEntry = data.originalData.entries.find(x => x.uid === uid);
@ -2099,7 +2117,13 @@ function setOriginalDataValue(data, uid, key, value) {
} }
} }
function deleteOriginalDataValue(data, uid) { /**
* Deletes the original data entry corresponding to the given uid from the provided data object
*
* @param {object} data - The data object containing the original data entries
* @param {string} uid - The unique identifier of the data entry to be deleted
*/
export function deleteWIOriginalDataValue(data, uid) {
if (data.originalData && Array.isArray(data.originalData.entries)) { if (data.originalData && Array.isArray(data.originalData.entries)) {
const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid); const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid);
@ -2120,7 +2144,7 @@ function deleteOriginalDataValue(data, uid) {
* @param {string} input - One or multiple keywords or regexes, separated by commas * @param {string} input - One or multiple keywords or regexes, separated by commas
* @returns {string[]} An array of keywords and regexes * @returns {string[]} An array of keywords and regexes
*/ */
function splitKeywordsAndRegexes(input) { export function splitKeywordsAndRegexes(input) {
/** @type {string[]} */ /** @type {string[]} */
let keywordsAndRegexes = []; let keywordsAndRegexes = [];
@ -2224,7 +2248,7 @@ function isValidRegex(input) {
* @param {string} input - A delimited regex string * @param {string} input - A delimited regex string
* @returns {RegExp|null} The regex object, or null if not a valid regex * @returns {RegExp|null} The regex object, or null if not a valid regex
*/ */
function parseRegexFromString(input) { export function parseRegexFromString(input) {
// Extracting the regex pattern and flags // Extracting the regex pattern and flags
let match = input.match(/^\/([\w\W]+?)\/([gimsuy]*)$/); let match = input.match(/^\/([\w\W]+?)\/([gimsuy]*)$/);
if (!match) { if (!match) {
@ -2310,7 +2334,7 @@ function getWorldEntry(name, data, entry) {
!skipReset && resetScrollHeight(this); !skipReset && resetScrollHeight(this);
if (!noSave) { if (!noSave) {
data.entries[uid][entryPropName] = keys; data.entries[uid][entryPropName] = keys;
setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]); setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
} }
}); });
@ -2348,7 +2372,7 @@ function getWorldEntry(name, data, entry) {
!skipReset && resetScrollHeight(this); !skipReset && resetScrollHeight(this);
if (!noSave) { if (!noSave) {
data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value); data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]); setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
} }
}); });
@ -2392,7 +2416,7 @@ function getWorldEntry(name, data, entry) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY; data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic); setWIOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
@ -2441,7 +2465,7 @@ function getWorldEntry(name, data, entry) {
} }
} }
setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter); setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input'); characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');
@ -2503,7 +2527,7 @@ function getWorldEntry(name, data, entry) {
}, },
); );
} }
setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter); setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
@ -2517,7 +2541,7 @@ function getWorldEntry(name, data, entry) {
!skipReset && resetScrollHeight(this); !skipReset && resetScrollHeight(this);
data.entries[uid].comment = value; data.entries[uid].comment = value;
setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment); setWIOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
commentToggle.data('uid', entry.uid); commentToggle.data('uid', entry.uid);
@ -2552,7 +2576,7 @@ function getWorldEntry(name, data, entry) {
const value = $(this).val(); const value = $(this).val();
data.entries[uid].content = value; data.entries[uid].content = value;
setOriginalDataValue(data, uid, 'content', data.entries[uid].content); setWIOriginalDataValue(data, uid, 'content', data.entries[uid].content);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
if (skipCount) { if (skipCount) {
@ -2582,7 +2606,7 @@ function getWorldEntry(name, data, entry) {
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].selective = value; data.entries[uid].selective = value;
setOriginalDataValue(data, uid, 'selective', data.entries[uid].selective); setWIOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
const keysecondary = $(this) const keysecondary = $(this)
@ -2631,7 +2655,7 @@ function getWorldEntry(name, data, entry) {
data.entries[uid].order = !isNaN(value) ? value : 0; data.entries[uid].order = !isNaN(value) ? value : 0;
updatePosOrdDisplay(uid); updatePosOrdDisplay(uid);
setOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order); setWIOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
orderInput.val(entry.order).trigger('input'); orderInput.val(entry.order).trigger('input');
@ -2645,7 +2669,7 @@ function getWorldEntry(name, data, entry) {
const value = String($(this).val()).trim(); const value = String($(this).val()).trim();
data.entries[uid].group = value; data.entries[uid].group = value;
setOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group); setWIOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
groupInput.val(entry.group ?? '').trigger('input'); groupInput.val(entry.group ?? '').trigger('input');
@ -2658,7 +2682,7 @@ function getWorldEntry(name, data, entry) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].groupOverride = value; data.entries[uid].groupOverride = value;
setOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride); setWIOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
groupOverrideInput.prop('checked', entry.groupOverride).trigger('input'); groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
@ -2682,7 +2706,7 @@ function getWorldEntry(name, data, entry) {
} }
data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1; data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight); setWIOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input'); groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
@ -2695,7 +2719,7 @@ function getWorldEntry(name, data, entry) {
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].sticky = !isNaN(value) ? value : null; data.entries[uid].sticky = !isNaN(value) ? value : null;
setOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky); setWIOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input'); sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');
@ -2708,7 +2732,7 @@ function getWorldEntry(name, data, entry) {
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].cooldown = !isNaN(value) ? value : null; data.entries[uid].cooldown = !isNaN(value) ? value : null;
setOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown); setWIOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input'); cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');
@ -2721,7 +2745,7 @@ function getWorldEntry(name, data, entry) {
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].delay = !isNaN(value) ? value : null; data.entries[uid].delay = !isNaN(value) ? value : null;
setOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay); setWIOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
delay.val(entry.delay > 0 ? entry.delay : '').trigger('input'); delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');
@ -2741,7 +2765,7 @@ function getWorldEntry(name, data, entry) {
data.entries[uid].depth = !isNaN(value) ? value : 0; data.entries[uid].depth = !isNaN(value) ? value : 0;
updatePosOrdDisplay(uid); updatePosOrdDisplay(uid);
setOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth); setWIOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input'); depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');
@ -2769,7 +2793,7 @@ function getWorldEntry(name, data, entry) {
} }
} }
setOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability); setWIOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
probabilityInput.val(entry.probability).trigger('input'); probabilityInput.val(entry.probability).trigger('input');
@ -2836,10 +2860,10 @@ function getWorldEntry(name, data, entry) {
} }
updatePosOrdDisplay(uid); updatePosOrdDisplay(uid);
// Spec v2 only supports before_char and after_char // Spec v2 only supports before_char and after_char
setOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char'); setWIOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
// Write the original value as extensions field // Write the original value as extensions field
setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position); setWIOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
setOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role); setWIOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
@ -2881,36 +2905,36 @@ function getWorldEntry(name, data, entry) {
data.entries[uid].constant = true; data.entries[uid].constant = true;
data.entries[uid].disable = false; data.entries[uid].disable = false;
data.entries[uid].vectorized = false; data.entries[uid].vectorized = false;
setOriginalDataValue(data, uid, 'enabled', true); setWIOriginalDataValue(data, uid, 'enabled', true);
setOriginalDataValue(data, uid, 'constant', true); setWIOriginalDataValue(data, uid, 'constant', true);
setOriginalDataValue(data, uid, 'extensions.vectorized', false); setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
template.removeClass('disabledWIEntry'); template.removeClass('disabledWIEntry');
break; break;
case 'normal': case 'normal':
data.entries[uid].constant = false; data.entries[uid].constant = false;
data.entries[uid].disable = false; data.entries[uid].disable = false;
data.entries[uid].vectorized = false; data.entries[uid].vectorized = false;
setOriginalDataValue(data, uid, 'enabled', true); setWIOriginalDataValue(data, uid, 'enabled', true);
setOriginalDataValue(data, uid, 'constant', false); setWIOriginalDataValue(data, uid, 'constant', false);
setOriginalDataValue(data, uid, 'extensions.vectorized', false); setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
template.removeClass('disabledWIEntry'); template.removeClass('disabledWIEntry');
break; break;
case 'vectorized': case 'vectorized':
data.entries[uid].constant = false; data.entries[uid].constant = false;
data.entries[uid].disable = false; data.entries[uid].disable = false;
data.entries[uid].vectorized = true; data.entries[uid].vectorized = true;
setOriginalDataValue(data, uid, 'enabled', true); setWIOriginalDataValue(data, uid, 'enabled', true);
setOriginalDataValue(data, uid, 'constant', false); setWIOriginalDataValue(data, uid, 'constant', false);
setOriginalDataValue(data, uid, 'extensions.vectorized', true); setWIOriginalDataValue(data, uid, 'extensions.vectorized', true);
template.removeClass('disabledWIEntry'); template.removeClass('disabledWIEntry');
break; break;
case 'disabled': case 'disabled':
data.entries[uid].constant = false; data.entries[uid].constant = false;
data.entries[uid].disable = true; data.entries[uid].disable = true;
data.entries[uid].vectorized = false; data.entries[uid].vectorized = false;
setOriginalDataValue(data, uid, 'enabled', false); setWIOriginalDataValue(data, uid, 'enabled', false);
setOriginalDataValue(data, uid, 'constant', false); setWIOriginalDataValue(data, uid, 'constant', false);
setOriginalDataValue(data, uid, 'extensions.vectorized', false); setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
template.addClass('disabledWIEntry'); template.addClass('disabledWIEntry');
break; break;
} }
@ -2941,7 +2965,7 @@ function getWorldEntry(name, data, entry) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].excludeRecursion = value; data.entries[uid].excludeRecursion = value;
setOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion); setWIOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input'); excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');
@ -2953,7 +2977,7 @@ function getWorldEntry(name, data, entry) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].preventRecursion = value; data.entries[uid].preventRecursion = value;
setOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion); setWIOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input'); preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
@ -2965,7 +2989,7 @@ function getWorldEntry(name, data, entry) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].delayUntilRecursion = value; data.entries[uid].delayUntilRecursion = value;
setOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion); setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input'); delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
@ -2985,10 +3009,12 @@ function getWorldEntry(name, data, entry) {
// delete button // delete button
const deleteButton = template.find('.delete_entry_button'); const deleteButton = template.find('.delete_entry_button');
deleteButton.data('uid', entry.uid); deleteButton.data('uid', entry.uid);
deleteButton.on('click', async function () { deleteButton.on('click', async function (e) {
e.stopPropagation();
const uid = $(this).data('uid'); const uid = $(this).data('uid');
deleteWorldInfoEntry(data, uid); const deleted = await deleteWorldInfoEntry(data, uid);
deleteOriginalDataValue(data, uid); if (!deleted) return;
deleteWIOriginalDataValue(data, uid);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
updateEditor(navigation_option.previous); updateEditor(navigation_option.previous);
}); });
@ -3015,7 +3041,7 @@ function getWorldEntry(name, data, entry) {
} }
data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null; data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;
setOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth); setWIOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
scanDepthInput.val(entry.scanDepth ?? null).trigger('input'); scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
@ -3028,7 +3054,7 @@ function getWorldEntry(name, data, entry) {
const value = $(this).val(); const value = $(this).val();
data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true'; data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
setOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive); setWIOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input'); caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');
@ -3041,7 +3067,7 @@ function getWorldEntry(name, data, entry) {
const value = $(this).val(); const value = $(this).val();
data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true'; data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
setOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords); setWIOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input'); matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
@ -3054,7 +3080,7 @@ function getWorldEntry(name, data, entry) {
const value = $(this).val(); const value = $(this).val();
data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true'; data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
setOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring); setWIOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input'); useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
@ -3067,7 +3093,7 @@ function getWorldEntry(name, data, entry) {
const value = $(this).val(); const value = $(this).val();
data.entries[uid].automationId = value; data.entries[uid].automationId = value;
setOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId); setWIOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
await saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
automationIdInput.val(entry.automationId ?? '').trigger('input'); automationIdInput.val(entry.automationId ?? '').trigger('input');
@ -3200,12 +3226,12 @@ function createEntryInputAutocomplete(input, callback, { allowMultiple = false }
/** /**
* Duplicated a WI entry by copying all of its properties and assigning a new uid * Duplicate a WI entry by copying all of its properties and assigning a new uid
* @param {*} data - The data of the book * @param {*} data - The data of the book
* @param {number} uid - The uid of the entry to copy in this book * @param {number} uid - The uid of the entry to copy in this book
* @returns {*} The new WI duplicated entry * @returns {*} The new WI duplicated entry
*/ */
function duplicateWorldInfoEntry(data, uid) { export function duplicateWorldInfoEntry(data, uid) {
if (!data || !('entries' in data) || !data.entries[uid]) { if (!data || !('entries' in data) || !data.entries[uid]) {
return; return;
} }
@ -3225,17 +3251,22 @@ function duplicateWorldInfoEntry(data, uid) {
* Deletes a WI entry, with a user confirmation dialog * Deletes a WI entry, with a user confirmation dialog
* @param {*[]} data - The data of the book * @param {*[]} data - The data of the book
* @param {number} uid - The uid of the entry to copy in this book * @param {number} uid - The uid of the entry to copy in this book
* @param {object} [options={}] - Optional arguments
* @param {boolean} [options.silent=false] - Whether to prompt the user for deletion or just do it
* @returns {Promise<boolean>} Whether the entry deletion was successful
*/ */
function deleteWorldInfoEntry(data, uid) { export async function deleteWorldInfoEntry(data, uid, { silent = false } = {}) {
if (!data || !('entries' in data)) { if (!data || !('entries' in data)) {
return; return;
} }
if (!confirm(`Delete the entry with UID: ${uid}? This action is irreversible!`)) { const confirmation = silent || await Popup.show.confirm(`Delete the entry with UID: ${uid}?`, 'This action is irreversible!');
throw new Error('User cancelled deletion'); if (!confirmation) {
return false;
} }
delete data.entries[uid]; delete data.entries[uid];
return true;
} }
/** /**
@ -3245,7 +3276,7 @@ function deleteWorldInfoEntry(data, uid) {
* *
* @type {{[key: string]: { default: any, type: string }}} * @type {{[key: string]: { default: any, type: string }}}
*/ */
const newEntryDefinition = { export const newWorldInfoEntryDefinition = {
key: { default: [], type: 'array' }, key: { default: [], type: 'array' },
keysecondary: { default: [], type: 'array' }, keysecondary: { default: [], type: 'array' },
comment: { default: '', type: 'string' }, comment: { default: '', type: 'string' },
@ -3278,8 +3309,8 @@ const newEntryDefinition = {
delay: { default: null, type: 'number?' }, delay: { default: null, type: 'number?' },
}; };
const newEntryTemplate = Object.fromEntries( export const newWorldInfoEntryTemplate = Object.fromEntries(
Object.entries(newEntryDefinition).map(([key, value]) => [key, value.default]), Object.entries(newWorldInfoEntryDefinition).map(([key, value]) => [key, value.default]),
); );
/** /**
@ -3288,7 +3319,7 @@ const newEntryTemplate = Object.fromEntries(
* @param {any} data WI data * @param {any} data WI data
* @returns {object | undefined} New entry object or undefined if failed * @returns {object | undefined} New entry object or undefined if failed
*/ */
function createWorldInfoEntry(_name, data) { export function createWorldInfoEntry(_name, data) {
const newUid = getFreeWorldEntryUid(data); const newUid = getFreeWorldEntryUid(data);
if (!Number.isInteger(newUid)) { if (!Number.isInteger(newUid)) {
@ -3296,7 +3327,7 @@ function createWorldInfoEntry(_name, data) {
return; return;
} }
const newEntry = { uid: newUid, ...structuredClone(newEntryTemplate) }; const newEntry = { uid: newUid, ...structuredClone(newWorldInfoEntryTemplate) };
data.entries[newUid] = newEntry; data.entries[newUid] = newEntry;
return newEntry; return newEntry;
@ -3314,7 +3345,21 @@ async function _save(name, data) {
eventSource.emit(event_types.WORLDINFO_UPDATED, name, data); eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);
} }
async function saveWorldInfo(name, data, immediately = false) {
/**
* Saves the world info
*
* This will also refresh the `worldInfoCache`.
* Note, for performance reasons the saved cache will not make a deep clone of the data.
* It is your responsibility to not modify the saved data object after calling this function, or there will be data inconsistencies.
* Call `loadWorldInfoData` or query directly from cache if you need the object again.
*
* @param {string} name - The name of the world info
* @param {any} data - The data to be saved
* @param {boolean} [immediately=false] - Whether to save immediately or use debouncing
* @return {Promise<void>} A promise that resolves when the world info is saved
*/
export async function saveWorldInfo(name, data, immediately = false) {
if (!name || !data) { if (!name || !data) {
return; return;
} }
@ -3371,7 +3416,7 @@ async function renameWorldInfo(name, data) {
* @param {string} worldInfoName - The name of the world info to delete * @param {string} worldInfoName - The name of the world info to delete
* @returns {Promise<boolean>} A promise that resolves to true if the world info was successfully deleted, false otherwise * @returns {Promise<boolean>} A promise that resolves to true if the world info was successfully deleted, false otherwise
*/ */
async function deleteWorldInfo(worldInfoName) { export async function deleteWorldInfo(worldInfoName) {
if (!world_names.includes(worldInfoName)) { if (!world_names.includes(worldInfoName)) {
return false; return false;
} }
@ -3406,7 +3451,7 @@ async function deleteWorldInfo(worldInfoName) {
return true; return true;
} }
function getFreeWorldEntryUid(data) { export function getFreeWorldEntryUid(data) {
if (!data || !('entries' in data)) { if (!data || !('entries' in data)) {
return null; return null;
} }
@ -3422,7 +3467,7 @@ function getFreeWorldEntryUid(data) {
return null; return null;
} }
function getFreeWorldName() { export function getFreeWorldName() {
const MAX_FREE_NAME = 100_000; const MAX_FREE_NAME = 100_000;
for (let index = 1; index < MAX_FREE_NAME; index++) { for (let index = 1; index < MAX_FREE_NAME; index++) {
const newName = `New World (${index})`; const newName = `New World (${index})`;
@ -3444,7 +3489,7 @@ function getFreeWorldName() {
* @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when overwriting an existing world * @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when overwriting an existing world
* @returns {Promise<boolean>} - True if the world info was successfully created, false otherwise * @returns {Promise<boolean>} - True if the world info was successfully created, false otherwise
*/ */
async function createNewWorldInfo(worldName, { interactive = false } = {}) { export async function createNewWorldInfo(worldName, { interactive = false } = {}) {
const worldInfoTemplate = { entries: {} }; const worldInfoTemplate = { entries: {} };
if (!worldName) { if (!worldName) {
@ -3505,7 +3550,7 @@ async function getCharacterLore() {
continue; continue;
} }
const data = await loadWorldInfoData(worldName); const data = await loadWorldInfo(worldName);
const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : []; const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];
entries = entries.concat(newEntries); entries = entries.concat(newEntries);
@ -3525,7 +3570,7 @@ async function getGlobalLore() {
let entries = []; let entries = [];
for (const worldName of selected_world_info) { for (const worldName of selected_world_info) {
const data = await loadWorldInfoData(worldName); const data = await loadWorldInfo(worldName);
const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : []; const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];
entries = entries.concat(newEntries); entries = entries.concat(newEntries);
} }
@ -3547,7 +3592,7 @@ async function getChatLore() {
return []; return [];
} }
const data = await loadWorldInfoData(chatWorld); const data = await loadWorldInfo(chatWorld);
const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: chatWorld, ...rest })) : []; const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: chatWorld, ...rest })) : [];
console.debug(`[WI] Chat lore has ${entries.length} entries`, [chatWorld]); console.debug(`[WI] Chat lore has ${entries.length} entries`, [chatWorld]);
@ -3663,7 +3708,7 @@ function parseDecorators(content) {
* @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
* @returns {Promise<WIActivated>} The world info activated. * @returns {Promise<WIActivated>} The world info activated.
*/ */
async function checkWorldInfo(chat, maxContext, isDryRun) { export async function checkWorldInfo(chat, maxContext, isDryRun) {
const context = getContext(); const context = getContext();
const buffer = new WorldInfoBuffer(chat); const buffer = new WorldInfoBuffer(chat);
@ -4233,7 +4278,7 @@ function convertAgnaiMemoryBook(inputObj) {
inputObj.entries.forEach((entry, index) => { inputObj.entries.forEach((entry, index) => {
outputObj.entries[index] = { outputObj.entries[index] = {
...newEntryTemplate, ...newWorldInfoEntryTemplate,
uid: index, uid: index,
key: entry.keywords, key: entry.keywords,
keysecondary: [], keysecondary: [],
@ -4275,7 +4320,7 @@ function convertRisuLorebook(inputObj) {
inputObj.data.forEach((entry, index) => { inputObj.data.forEach((entry, index) => {
outputObj.entries[index] = { outputObj.entries[index] = {
...newEntryTemplate, ...newWorldInfoEntryTemplate,
uid: index, uid: index,
key: entry.key.split(',').map(x => x.trim()), key: entry.key.split(',').map(x => x.trim()),
keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [], keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [],
@ -4322,7 +4367,7 @@ function convertNovelLorebook(inputObj) {
const addMemo = displayName !== undefined && displayName.trim() !== ''; const addMemo = displayName !== undefined && displayName.trim() !== '';
outputObj.entries[index] = { outputObj.entries[index] = {
...newEntryTemplate, ...newWorldInfoEntryTemplate,
uid: index, uid: index,
key: entry.keys, key: entry.keys,
keysecondary: [], keysecondary: [],
@ -4369,7 +4414,7 @@ function convertCharacterBook(characterBook) {
} }
result.entries[entry.id] = { result.entries[entry.id] = {
...newEntryTemplate, ...newWorldInfoEntryTemplate,
uid: entry.id, uid: entry.id,
key: entry.keys, key: entry.keys,
keysecondary: entry.secondary_keys || [], keysecondary: entry.secondary_keys || [],
@ -4499,7 +4544,7 @@ export async function importEmbeddedWorldInfo(skipPopup = false) {
setWorldInfoButtonClass(chid, true); setWorldInfoButtonClass(chid, true);
} }
function onWorldInfoChange(args, text) { export function onWorldInfoChange(args, text) {
if (args !== '__notSlashCommand__') { // if it's a slash command if (args !== '__notSlashCommand__') { // if it's a slash command
const silent = isTrueBoolean(args.silent); const silent = isTrueBoolean(args.silent);
if (text.trim() !== '') { // and args are provided if (text.trim() !== '') { // and args are provided
@ -4650,7 +4695,7 @@ export async function importWorldInfo(file) {
}); });
} }
function assignLorebookToChat() { export function assignLorebookToChat() {
const selectedName = chat_metadata[METADATA_KEY]; const selectedName = chat_metadata[METADATA_KEY];
const template = $('#chat_world_template .chat_world').clone(); const template = $('#chat_world_template .chat_world').clone();