Export and doc most of wi functions

3897b8c0821c4b1b040af76f8078e9afe469592a

Wolfsblvt <wolfsblvt@gmail.com>

1 files changed, +168 -138Showing whitespace changes
public/scripts/world-info.js+168 -138
@@ -18,33 +18,13 @@ import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
1818import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
1919import { StructuredCloneMap } from './util/StructuredCloneMap.js';
2020
2121export const world_info_insertion_strategy = {
22- world_info,
23- world_info_budget,
24- world_info_depth,
25- world_info_min_activations,
26- world_info_min_activations_depth_max,
27- world_info_include_names,
28- world_info_recursive,
29- world_info_overflow_alert,
30- world_info_case_sensitive,
31- world_info_match_whole_words,
32- world_info_character_strategy,
33- world_info_budget_cap,
34- world_names,
35- checkWorldInfo,
36- deleteWorldInfo,
37- setWorldInfoSettings,
38- getWorldInfoPrompt,
39-};
40-
41-const world_info_insertion_strategy = {
4222 evenly: 0,
4323 character_first: 1,
4424 global_first: 2,
4525};
4626
4727export const world_info_logic = {
4828 AND_ANY: 0,
4929 NOT_ALL: 1,
5030 NOT_ANY: 2,
@@ -54,7 +34,7 @@ const world_info_logic = {
5434/**
5535 * @enum {number} Possible states of the WI evaluation
5636 */
5737export const scan_state = {
5838 /**
5939 * The scan will be stopped.
6040 */
@@ -75,23 +55,23 @@ const scan_state = {
7555
7656const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');
7757
7858export let world_info = {};
7959export let selected_world_info = [];
8060/** @type {string[]} */
8161export let world_names;
8262export let world_info_depth = 2;
8363export let world_info_min_activations = 0; // if > 0, will continue seeking chat until minimum world infos are activated
8464export let world_info_min_activations_depth_max = 0; // used when (world_info_min_activations > 0)
8565
8666export let world_info_budget = 25;
8767export let world_info_include_names = true;
8868export let world_info_recursive = false;
8969export let world_info_overflow_alert = false;
9070export let world_info_case_sensitive = false;
9171export let world_info_match_whole_words = false;
9272export let world_info_use_group_scoring = false;
9373export let world_info_character_strategy = world_info_insertion_strategy.character_first;
9474export let world_info_budget_cap = 0;
9575const saveWorldDebounced = debounce(async (name, data) => await _save(name, data), debounce_timeout.relaxed);
9676const saveSettingsDebounced = debounce(() => {
9777 Object.assign(world_info, { globalSelect: selected_world_info });
@@ -101,13 +81,13 @@ const sortFn = (a, b) => b.order - a.order;
10181let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };
10282
10383// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
10484export const worldInfoFilter = new FilterHelper(() => updateEditor());
10585export const SORT_ORDER_KEY = 'world_info_sort_order';
10686export const METADATA_KEY = 'world_info';
10787
10888export const DEFAULT_DEPTH = 4;
10989export const DEFAULT_WEIGHT = 100;
11090export const MAX_SCAN_DEPTH = 1000;
11191const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];
11292
11393// Typedef area
@@ -732,7 +712,7 @@ export function getWorldInfoSettings() {
732712 };
733713}
734714
735715export const world_info_position = {
736716 before: 0,
737717 after: 1,
738718 ANTop: 2,
@@ -747,8 +727,18 @@ export const wi_anchor_position = {
747727 after: 1,
748728};
749729
750-/** @type {StructuredCloneMap<string,object>} */
730+/**
751-const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: false });
731+ * The cache of all world info data that was loaded from the backend.
732+ *
733+ * Calling `loadWorldInfo` will fill this cache and utilize this cache, so should be the preferred way to load any world info data.
734+ * Only use the cache directly if you need synchronous access.
735+ *
736+ * This will return a deep clone of the data, so no way to modify the data without actually saving it.
737+ * Should generally be only used for readonly access.
738+ *
739+ * @type {StructuredCloneMap<string,object>}
740+ * */
741+export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: false });
752742
753743/**
754744 * Gets the world info based on chat messages.
@@ -758,7 +748,7 @@ const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: fa
758748 * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult
759749 * @returns {Promise<WIPromptResult>} The world info string and depth.
760750 */
761751export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
762752 let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';
763753
764754 const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun);
@@ -780,7 +770,7 @@ async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
780770 };
781771}
782772
783773export function setWorldInfoSettings(settings, data) {
784774 if (settings.world_info_depth !== undefined)
785775 world_info_depth = Number(settings.world_info_depth);
786776 if (settings.world_info_min_activations !== undefined)
@@ -916,7 +906,7 @@ function registerWorldInfoSlashCommands() {
916906 return '';
917907 }
918908
919909 const data = await loadWorldInfoDataloadWorldInfo(file);
920910
921911 if (!data || !('entries' in data)) {
922912 toastr.warning('World Info file has an invalid format');
@@ -965,7 +955,7 @@ function registerWorldInfoSlashCommands() {
965955 return '';
966956 }
967957
968958 if (typeof newEntryTemplatenewWorldInfoEntryTemplate[field] === 'boolean') {
969959 const isTrue = isTrueBoolean(value);
970960 const isFalse = isFalseBoolean(value);
971961
@@ -1016,7 +1006,7 @@ function registerWorldInfoSlashCommands() {
10161006 return '';
10171007 }
10181008
10191009 if (newEntryTemplatenewWorldInfoEntryTemplate[field] === undefined) {
10201010 toastr.warning('Valid field name is required');
10211011 return '';
10221012 }
@@ -1038,7 +1028,7 @@ function registerWorldInfoSlashCommands() {
10381028 const file = args.file;
10391029 const key = args.key;
10401030
10411031 const data = await loadWorldInfoDataloadWorldInfo(file);
10421032
10431033 if (!data || !('entries' in data)) {
10441034 toastr.warning('Valid World Info file name is required');
@@ -1075,7 +1065,7 @@ function registerWorldInfoSlashCommands() {
10751065
10761066 value = value.replace(/\\([{}|])/g, '$1');
10771067
10781068 const data = await loadWorldInfoDataloadWorldInfo(file);
10791069
10801070 if (!data || !('entries' in data)) {
10811071 toastr.warning('Valid World Info file name is required');
@@ -1089,7 +1079,7 @@ function registerWorldInfoSlashCommands() {
10891079 return '';
10901080 }
10911081
10921082 if (newEntryTemplatenewWorldInfoEntryTemplate[field] === undefined) {
10931083 toastr.warning('Valid field name is required');
10941084 return '';
10951085 }
@@ -1104,8 +1094,8 @@ function registerWorldInfoSlashCommands() {
11041094 entry[field] = value;
11051095 }
11061096
11071097 if (originalDataKeyMaporiginalWIDataKeyMap[field]) {
11081098 setOriginalDataValuesetWIOriginalDataValue(data, uid, originalDataKeyMaporiginalWIDataKeyMap[field], entry[field]);
11091099 }
11101100
11111101 await saveWorldInfo(file, data);
@@ -1226,7 +1216,7 @@ function registerWorldInfoSlashCommands() {
12261216 /** A collection of local enum providers for this context of world info */
12271217 const localEnumProviders = {
12281218 /** All possible fields that can be set in a WI entry */
12291219 wiEntryFields: () => Object.entries(newEntryDefinitionnewWorldInfoEntryDefinition).map(([key, value]) =>
12301220 new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : value.default)}`,
12311221 enumTypes.enum, enumIcons.getDataTypeIcon(value.type))),
12321222
@@ -1566,18 +1556,32 @@ function registerWorldInfoSlashCommands() {
15661556 }));
15671557}
15681558
1569-// World Info Editor
1559+
1570-async function showWorldEditor(name) {
1560+/**
1561+ * Loads the given world into the World Editor.
1562+ *
1563+ * @param {string} name - The name of the world
1564+ * @return {Promise<void>} A promise that resolves when the world editor is loaded
1565+ */
1566+export async function showWorldEditor(name) {
15711567 if (!name) {
15721568 hideWorldEditor();
15731569 return;
15741570 }
15751571
15761572 const wiData = await loadWorldInfoDataloadWorldInfo(name);
15771573 displayWorldEntries(name, wiData);
15781574}
15791575
1580-async function loadWorldInfoData(name) {
1576+/**
1577+ * Loads world info from the backend.
1578+ *
1579+ * This function will return from `worldInfoCache` if it has already been loaded before.
1580+ *
1581+ * @param {string} name - The name of the world to load
1582+ * @return {Promise<Object|null>} A promise that resolves to the loaded world information, or null if the request fails.
1583+ */
1584+export async function loadWorldInfo(name) {
15811585 if (!name) {
15821586 return;
15831587 }
@@ -1635,10 +1639,12 @@ function getWIElement(name) {
16351639}
16361640
16371641/**
1642+ * Sorts the given data based on the selected sort option
1643+ *
16381644 * @param {any[]} data WI entries
16391645 * @returns {any[]} Sorted data
16401646 */
16411647export function sortEntriessortWorldInfoEntries(data) {
16421648 const option = $('#world_info_sort_order').find(':selected');
16431649 const sortField = option.data('field');
16441650 const sortOrder = option.data('order');
@@ -1801,7 +1807,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
18011807
18021808 // Apply the filter and do the chosen sorting
18031809 entriesArray = worldInfoFilter.applyFilters(entriesArray);
18041810 entriesArray = sortEntriessortWorldInfoEntries(entriesArray);
18051811
18061812 // Cache keys
18071813 const keys = entriesArray.flatMap(entry => [...entry.key, ...entry.keysecondary]);
@@ -1919,7 +1925,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
19191925 for (const entry of Object.values(data.entries)) {
19201926 if (!entry.comment && Array.isArray(entry.key) && entry.key.length > 0) {
19211927 entry.comment = entry.key[0];
19221928 setOriginalDataValuesetWIOriginalDataValue(data, entry.uid, 'comment', entry.comment);
19231929 counter++;
19241930 }
19251931 }
@@ -1954,7 +1960,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
19541960
19551961 // We need to sort the entries here, as the data source isn't sorted
19561962 const entries = Object.values(data.entries);
19571963 sortEntriessortWorldInfoEntries(entries);
19581964
19591965 let updated = 0, current = start;
19601966 for (const entry of entries) {
@@ -1962,7 +1968,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
19621968 if (entry.order === newOrder) continue;
19631969
19641970 entry.order = newOrder;
19651971 setOriginalDataValuesetWIOriginalDataValue(data, entry.order, 'order', entry.order);
19661972 updated++;
19671973 }
19681974
@@ -2025,7 +2031,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
20252031 }
20262032
20272033 item.displayIndex = minDisplayIndex + index;
20282034 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.display_index', item.displayIndex);
20292035 });
20302036
20312037 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 +2042,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
20362042 //$("#world_popup_entries_list").disableSelection();
20372043}
20382044
20392045export const originalDataKeyMaporiginalWIDataKeyMap = {
20402046 'displayIndex': 'extensions.display_index',
20412047 'excludeRecursion': 'extensions.exclude_recursion',
20422048 'preventRecursion': 'extensions.prevent_recursion',
@@ -2087,7 +2093,17 @@ function verifyWorldInfoSearchSortRule() {
20872093 }
20882094}
20892095
2090-function setOriginalDataValue(data, uid, key, value) {
2096+/**
2097+ * Sets the value of a specific key in the original data entry corresponding to the given uid
2098+ * This needs to be called whenever you update JSON data fields.
2099+ * Use `originalWIDataKeyMap` to find the correct value to be set.
2100+ *
2101+ * @param {object} data - The data object containing the original data entries.
2102+ * @param {string} uid - The unique identifier of the data entry.
2103+ * @param {string} key - The key of the value to be set.
2104+ * @param {any} value - The value to be set.
2105+ */
2106+export function setWIOriginalDataValue(data, uid, key, value) {
20912107 if (data.originalData && Array.isArray(data.originalData.entries)) {
20922108 let originalEntry = data.originalData.entries.find(x => x.uid === uid);
20932109
@@ -2099,7 +2115,7 @@ function setOriginalDataValue(data, uid, key, value) {
20992115 }
21002116}
21012117
21022118function deleteOriginalDataValuedeleteWIOriginalDataValue(data, uid) {
21032119 if (data.originalData && Array.isArray(data.originalData.entries)) {
21042120 const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid);
21052121
@@ -2120,7 +2136,7 @@ function deleteOriginalDataValue(data, uid) {
21202136 * @param {string} input - One or multiple keywords or regexes, separated by commas
21212137 * @returns {string[]} An array of keywords and regexes
21222138 */
21232139export function splitKeywordsAndRegexes(input) {
21242140 /** @type {string[]} */
21252141 let keywordsAndRegexes = [];
21262142
@@ -2224,7 +2240,7 @@ function isValidRegex(input) {
22242240 * @param {string} input - A delimited regex string
22252241 * @returns {RegExp|null} The regex object, or null if not a valid regex
22262242 */
22272243export function parseRegexFromString(input) {
22282244 // Extracting the regex pattern and flags
22292245 let match = input.match(/^\/([\w\W]+?)\/([gimsuy]*)$/);
22302246 if (!match) {
@@ -2310,7 +2326,7 @@ function getWorldEntry(name, data, entry) {
23102326 !skipReset && resetScrollHeight(this);
23112327 if (!noSave) {
23122328 data.entries[uid][entryPropName] = keys;
23132329 setOriginalDataValuesetWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
23142330 await saveWorldInfo(name, data);
23152331 }
23162332 });
@@ -2348,7 +2364,7 @@ function getWorldEntry(name, data, entry) {
23482364 !skipReset && resetScrollHeight(this);
23492365 if (!noSave) {
23502366 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
23512367 setOriginalDataValuesetWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
23522368 await saveWorldInfo(name, data);
23532369 }
23542370 });
@@ -2392,7 +2408,7 @@ function getWorldEntry(name, data, entry) {
23922408 const uid = $(this).data('uid');
23932409 const value = Number($(this).val());
23942410 data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
23952411 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
23962412 await saveWorldInfo(name, data);
23972413 });
23982414
@@ -2441,7 +2457,7 @@ function getWorldEntry(name, data, entry) {
24412457 }
24422458 }
24432459
24442460 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
24452461 await saveWorldInfo(name, data);
24462462 });
24472463 characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');
@@ -2503,7 +2519,7 @@ function getWorldEntry(name, data, entry) {
25032519 },
25042520 );
25052521 }
25062522 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
25072523 await saveWorldInfo(name, data);
25082524 });
25092525
@@ -2517,7 +2533,7 @@ function getWorldEntry(name, data, entry) {
25172533 !skipReset && resetScrollHeight(this);
25182534 data.entries[uid].comment = value;
25192535
25202536 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
25212537 await saveWorldInfo(name, data);
25222538 });
25232539 commentToggle.data('uid', entry.uid);
@@ -2552,7 +2568,7 @@ function getWorldEntry(name, data, entry) {
25522568 const value = $(this).val();
25532569 data.entries[uid].content = value;
25542570
25552571 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'content', data.entries[uid].content);
25562572 await saveWorldInfo(name, data);
25572573
25582574 if (skipCount) {
@@ -2582,7 +2598,7 @@ function getWorldEntry(name, data, entry) {
25822598 const value = $(this).prop('checked');
25832599 data.entries[uid].selective = value;
25842600
25852601 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
25862602 await saveWorldInfo(name, data);
25872603
25882604 const keysecondary = $(this)
@@ -2631,7 +2647,7 @@ function getWorldEntry(name, data, entry) {
26312647
26322648 data.entries[uid].order = !isNaN(value) ? value : 0;
26332649 updatePosOrdDisplay(uid);
26342650 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
26352651 await saveWorldInfo(name, data);
26362652 });
26372653 orderInput.val(entry.order).trigger('input');
@@ -2645,7 +2661,7 @@ function getWorldEntry(name, data, entry) {
26452661 const value = String($(this).val()).trim();
26462662
26472663 data.entries[uid].group = value;
26482664 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
26492665 await saveWorldInfo(name, data);
26502666 });
26512667 groupInput.val(entry.group ?? '').trigger('input');
@@ -2658,7 +2674,7 @@ function getWorldEntry(name, data, entry) {
26582674 const uid = $(this).data('uid');
26592675 const value = $(this).prop('checked');
26602676 data.entries[uid].groupOverride = value;
26612677 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
26622678 await saveWorldInfo(name, data);
26632679 });
26642680 groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
@@ -2682,7 +2698,7 @@ function getWorldEntry(name, data, entry) {
26822698 }
26832699
26842700 data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
26852701 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
26862702 await saveWorldInfo(name, data);
26872703 });
26882704 groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
@@ -2695,7 +2711,7 @@ function getWorldEntry(name, data, entry) {
26952711 const value = Number($(this).val());
26962712 data.entries[uid].sticky = !isNaN(value) ? value : null;
26972713
26982714 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);
26992715 await saveWorldInfo(name, data);
27002716 });
27012717 sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');
@@ -2708,7 +2724,7 @@ function getWorldEntry(name, data, entry) {
27082724 const value = Number($(this).val());
27092725 data.entries[uid].cooldown = !isNaN(value) ? value : null;
27102726
27112727 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);
27122728 await saveWorldInfo(name, data);
27132729 });
27142730 cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');
@@ -2721,7 +2737,7 @@ function getWorldEntry(name, data, entry) {
27212737 const value = Number($(this).val());
27222738 data.entries[uid].delay = !isNaN(value) ? value : null;
27232739
27242740 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);
27252741 await saveWorldInfo(name, data);
27262742 });
27272743 delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');
@@ -2741,7 +2757,7 @@ function getWorldEntry(name, data, entry) {
27412757
27422758 data.entries[uid].depth = !isNaN(value) ? value : 0;
27432759 updatePosOrdDisplay(uid);
27442760 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);
27452761 await saveWorldInfo(name, data);
27462762 });
27472763 depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');
@@ -2769,7 +2785,7 @@ function getWorldEntry(name, data, entry) {
27692785 }
27702786 }
27712787
27722788 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
27732789 await saveWorldInfo(name, data);
27742790 });
27752791 probabilityInput.val(entry.probability).trigger('input');
@@ -2836,10 +2852,10 @@ function getWorldEntry(name, data, entry) {
28362852 }
28372853 updatePosOrdDisplay(uid);
28382854 // Spec v2 only supports before_char and after_char
28392855 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
28402856 // Write the original value as extensions field
28412857 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
28422858 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
28432859 await saveWorldInfo(name, data);
28442860 });
28452861
@@ -2881,36 +2897,36 @@ function getWorldEntry(name, data, entry) {
28812897 data.entries[uid].constant = true;
28822898 data.entries[uid].disable = false;
28832899 data.entries[uid].vectorized = false;
28842900 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'enabled', true);
28852901 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'constant', true);
28862902 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
28872903 template.removeClass('disabledWIEntry');
28882904 break;
28892905 case 'normal':
28902906 data.entries[uid].constant = false;
28912907 data.entries[uid].disable = false;
28922908 data.entries[uid].vectorized = false;
28932909 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'enabled', true);
28942910 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'constant', false);
28952911 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
28962912 template.removeClass('disabledWIEntry');
28972913 break;
28982914 case 'vectorized':
28992915 data.entries[uid].constant = false;
29002916 data.entries[uid].disable = false;
29012917 data.entries[uid].vectorized = true;
29022918 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'enabled', true);
29032919 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'constant', false);
29042920 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.vectorized', true);
29052921 template.removeClass('disabledWIEntry');
29062922 break;
29072923 case 'disabled':
29082924 data.entries[uid].constant = false;
29092925 data.entries[uid].disable = true;
29102926 data.entries[uid].vectorized = false;
29112927 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'enabled', false);
29122928 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'constant', false);
29132929 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
29142930 template.addClass('disabledWIEntry');
29152931 break;
29162932 }
@@ -2941,7 +2957,7 @@ function getWorldEntry(name, data, entry) {
29412957 const uid = $(this).data('uid');
29422958 const value = $(this).prop('checked');
29432959 data.entries[uid].excludeRecursion = value;
29442960 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);
29452961 await saveWorldInfo(name, data);
29462962 });
29472963 excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');
@@ -2953,7 +2969,7 @@ function getWorldEntry(name, data, entry) {
29532969 const uid = $(this).data('uid');
29542970 const value = $(this).prop('checked');
29552971 data.entries[uid].preventRecursion = value;
29562972 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);
29572973 await saveWorldInfo(name, data);
29582974 });
29592975 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
@@ -2965,7 +2981,7 @@ function getWorldEntry(name, data, entry) {
29652981 const uid = $(this).data('uid');
29662982 const value = $(this).prop('checked');
29672983 data.entries[uid].delayUntilRecursion = value;
29682984 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
29692985 await saveWorldInfo(name, data);
29702986 });
29712987 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
@@ -2988,7 +3004,7 @@ function getWorldEntry(name, data, entry) {
29883004 deleteButton.on('click', async function () {
29893005 const uid = $(this).data('uid');
29903006 deleteWorldInfoEntry(data, uid);
29913007 deleteOriginalDataValuedeleteWIOriginalDataValue(data, uid);
29923008 await saveWorldInfo(name, data);
29933009 updateEditor(navigation_option.previous);
29943010 });
@@ -3015,7 +3031,7 @@ function getWorldEntry(name, data, entry) {
30153031 }
30163032
30173033 data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;
30183034 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
30193035 await saveWorldInfo(name, data);
30203036 });
30213037 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
@@ -3028,7 +3044,7 @@ function getWorldEntry(name, data, entry) {
30283044 const value = $(this).val();
30293045
30303046 data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
30313047 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
30323048 await saveWorldInfo(name, data);
30333049 });
30343050 caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');
@@ -3041,7 +3057,7 @@ function getWorldEntry(name, data, entry) {
30413057 const value = $(this).val();
30423058
30433059 data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
30443060 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
30453061 await saveWorldInfo(name, data);
30463062 });
30473063 matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
@@ -3054,7 +3070,7 @@ function getWorldEntry(name, data, entry) {
30543070 const value = $(this).val();
30553071
30563072 data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
30573073 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
30583074 await saveWorldInfo(name, data);
30593075 });
30603076 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
@@ -3067,7 +3083,7 @@ function getWorldEntry(name, data, entry) {
30673083 const value = $(this).val();
30683084
30693085 data.entries[uid].automationId = value;
30703086 setOriginalDataValuesetWIOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
30713087 await saveWorldInfo(name, data);
30723088 });
30733089 automationIdInput.val(entry.automationId ?? '').trigger('input');
@@ -3200,12 +3216,12 @@ function createEntryInputAutocomplete(input, callback, { allowMultiple = false }
32003216
32013217
32023218/**
32033219 * DuplicatedDuplicate a WI entry by copying all of its properties and assigning a new uid
32043220 * @param {*} data - The data of the book
32053221 * @param {number} uid - The uid of the entry to copy in this book
32063222 * @returns {*} The new WI duplicated entry
32073223 */
32083224export function duplicateWorldInfoEntry(data, uid) {
32093225 if (!data || !('entries' in data) || !data.entries[uid]) {
32103226 return;
32113227 }
@@ -3226,7 +3242,7 @@ function duplicateWorldInfoEntry(data, uid) {
32263242 * @param {*[]} data - The data of the book
32273243 * @param {number} uid - The uid of the entry to copy in this book
32283244 */
32293245export function deleteWorldInfoEntry(data, uid) {
32303246 if (!data || !('entries' in data)) {
32313247 return;
32323248 }
@@ -3245,7 +3261,7 @@ function deleteWorldInfoEntry(data, uid) {
32453261 *
32463262 * @type {{[key: string]: { default: any, type: string }}}
32473263 */
32483264export const newEntryDefinitionnewWorldInfoEntryDefinition = {
32493265 key: { default: [], type: 'array' },
32503266 keysecondary: { default: [], type: 'array' },
32513267 comment: { default: '', type: 'string' },
@@ -3278,8 +3294,8 @@ const newEntryDefinition = {
32783294 delay: { default: null, type: 'number?' },
32793295};
32803296
32813297export const newEntryTemplatenewWorldInfoEntryTemplate = Object.fromEntries(
32823298 Object.entries(newEntryDefinitionnewWorldInfoEntryDefinition).map(([key, value]) => [key, value.default]),
32833299);
32843300
32853301/**
@@ -3288,7 +3304,7 @@ const newEntryTemplate = Object.fromEntries(
32883304 * @param {any} data WI data
32893305 * @returns {object | undefined} New entry object or undefined if failed
32903306 */
32913307export function createWorldInfoEntry(_name, data) {
32923308 const newUid = getFreeWorldEntryUid(data);
32933309
32943310 if (!Number.isInteger(newUid)) {
@@ -3296,7 +3312,7 @@ function createWorldInfoEntry(_name, data) {
32963312 return;
32973313 }
32983314
32993315 const newEntry = { uid: newUid, ...structuredClone(newEntryTemplatenewWorldInfoEntryTemplate) };
33003316 data.entries[newUid] = newEntry;
33013317
33023318 return newEntry;
@@ -3314,7 +3330,21 @@ async function _save(name, data) {
33143330 eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);
33153331}
33163332
3317-async function saveWorldInfo(name, data, immediately = false) {
3333+
3334+/**
3335+ * Saves the world info
3336+ *
3337+ * This will also refresh the `worldInfoCache`.
3338+ * Note, for performance reasons the saved cache will not make a deep clone of the data.
3339+ * It is your responsibility to not modify the saved data object after calling this function, or there will be data inconsistencies.
3340+ * Call `loadWorldInfoData` or query directly from cache if you need the object again.
3341+ *
3342+ * @param {string} name - The name of the world info
3343+ * @param {any} data - The data to be saved
3344+ * @param {boolean} [immediately=false] - Whether to save immediately or use debouncing
3345+ * @return {Promise<void>} A promise that resolves when the world info is saved
3346+ */
3347+export async function saveWorldInfo(name, data, immediately = false) {
33183348 if (!name || !data) {
33193349 return;
33203350 }
@@ -3371,7 +3401,7 @@ async function renameWorldInfo(name, data) {
33713401 * @param {string} worldInfoName - The name of the world info to delete
33723402 * @returns {Promise<boolean>} A promise that resolves to true if the world info was successfully deleted, false otherwise
33733403 */
33743404export async function deleteWorldInfo(worldInfoName) {
33753405 if (!world_names.includes(worldInfoName)) {
33763406 return false;
33773407 }
@@ -3406,7 +3436,7 @@ async function deleteWorldInfo(worldInfoName) {
34063436 return true;
34073437}
34083438
34093439export function getFreeWorldEntryUid(data) {
34103440 if (!data || !('entries' in data)) {
34113441 return null;
34123442 }
@@ -3422,7 +3452,7 @@ function getFreeWorldEntryUid(data) {
34223452 return null;
34233453}
34243454
34253455export function getFreeWorldName() {
34263456 const MAX_FREE_NAME = 100_000;
34273457 for (let index = 1; index < MAX_FREE_NAME; index++) {
34283458 const newName = `New World (${index})`;
@@ -3444,7 +3474,7 @@ function getFreeWorldName() {
34443474 * @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when overwriting an existing world
34453475 * @returns {Promise<boolean>} - True if the world info was successfully created, false otherwise
34463476 */
34473477export async function createNewWorldInfo(worldName, { interactive = false } = {}) {
34483478 const worldInfoTemplate = { entries: {} };
34493479
34503480 if (!worldName) {
@@ -3505,7 +3535,7 @@ async function getCharacterLore() {
35053535 continue;
35063536 }
35073537
35083538 const data = await loadWorldInfoDataloadWorldInfo(worldName);
35093539 const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];
35103540 entries = entries.concat(newEntries);
35113541
@@ -3525,7 +3555,7 @@ async function getGlobalLore() {
35253555
35263556 let entries = [];
35273557 for (const worldName of selected_world_info) {
35283558 const data = await loadWorldInfoDataloadWorldInfo(worldName);
35293559 const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];
35303560 entries = entries.concat(newEntries);
35313561 }
@@ -3547,7 +3577,7 @@ async function getChatLore() {
35473577 return [];
35483578 }
35493579
35503580 const data = await loadWorldInfoDataloadWorldInfo(chatWorld);
35513581 const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: chatWorld, ...rest })) : [];
35523582
35533583 console.debug(`[WI] Chat lore has ${entries.length} entries`, [chatWorld]);
@@ -3663,7 +3693,7 @@ function parseDecorators(content) {
36633693 * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
36643694 * @returns {Promise<WIActivated>} The world info activated.
36653695 */
36663696export async function checkWorldInfo(chat, maxContext, isDryRun) {
36673697 const context = getContext();
36683698 const buffer = new WorldInfoBuffer(chat);
36693699
@@ -4233,7 +4263,7 @@ function convertAgnaiMemoryBook(inputObj) {
42334263
42344264 inputObj.entries.forEach((entry, index) => {
42354265 outputObj.entries[index] = {
42364266 ...newEntryTemplatenewWorldInfoEntryTemplate,
42374267 uid: index,
42384268 key: entry.keywords,
42394269 keysecondary: [],
@@ -4275,7 +4305,7 @@ function convertRisuLorebook(inputObj) {
42754305
42764306 inputObj.data.forEach((entry, index) => {
42774307 outputObj.entries[index] = {
42784308 ...newEntryTemplatenewWorldInfoEntryTemplate,
42794309 uid: index,
42804310 key: entry.key.split(',').map(x => x.trim()),
42814311 keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [],
@@ -4322,7 +4352,7 @@ function convertNovelLorebook(inputObj) {
43224352 const addMemo = displayName !== undefined && displayName.trim() !== '';
43234353
43244354 outputObj.entries[index] = {
43254355 ...newEntryTemplatenewWorldInfoEntryTemplate,
43264356 uid: index,
43274357 key: entry.keys,
43284358 keysecondary: [],
@@ -4369,7 +4399,7 @@ function convertCharacterBook(characterBook) {
43694399 }
43704400
43714401 result.entries[entry.id] = {
43724402 ...newEntryTemplatenewWorldInfoEntryTemplate,
43734403 uid: entry.id,
43744404 key: entry.keys,
43754405 keysecondary: entry.secondary_keys || [],
@@ -4499,7 +4529,7 @@ export async function importEmbeddedWorldInfo(skipPopup = false) {
44994529 setWorldInfoButtonClass(chid, true);
45004530}
45014531
45024532export function onWorldInfoChange(args, text) {
45034533 if (args !== '__notSlashCommand__') { // if it's a slash command
45044534 const silent = isTrueBoolean(args.silent);
45054535 if (text.trim() !== '') { // and args are provided
@@ -4650,7 +4680,7 @@ export async function importWorldInfo(file) {
46504680 });
46514681}
46524682
46534683export function assignLorebookToChat() {
46544684 const selectedName = chat_metadata[METADATA_KEY];
46554685 const template = $('#chat_world_template .chat_world').clone();
46564686