Export and doc most of wi functions

3897b8c0821c4b1b040af76f8078e9afe469592a

Wolfsblvt <wolfsblvt@gmail.com>

1 files changed, +168 -138Ignore whitespace
public/scripts/world-info.js+168 -138
@@ -18,33 +18,13 @@ import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
18import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';18import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
19import { StructuredCloneMap } from './util/StructuredCloneMap.js';19import { StructuredCloneMap } from './util/StructuredCloneMap.js';
2020
21export {21export 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
41const world_info_insertion_strategy = {
42 evenly: 0,22 evenly: 0,
43 character_first: 1,23 character_first: 1,
44 global_first: 2,24 global_first: 2,
45};25};
4626
47const world_info_logic = {27export const world_info_logic = {
48 AND_ANY: 0,28 AND_ANY: 0,
49 NOT_ALL: 1,29 NOT_ALL: 1,
50 NOT_ANY: 2,30 NOT_ANY: 2,
@@ -54,7 +34,7 @@ const world_info_logic = {
54/**34/**
55 * @enum {number} Possible states of the WI evaluation35 * @enum {number} Possible states of the WI evaluation
56 */36 */
57const scan_state = {37export const scan_state = {
58 /**38 /**
59 * The scan will be stopped.39 * The scan will be stopped.
60 */40 */
@@ -75,23 +55,23 @@ const scan_state = {
7555
76const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');56const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');
7757
78let world_info = {};58export let world_info = {};
79let selected_world_info = [];59export let selected_world_info = [];
80/** @type {string[]} */60/** @type {string[]} */
81let world_names;61export let world_names;
82let world_info_depth = 2;62export let world_info_depth = 2;
83let world_info_min_activations = 0; // if > 0, will continue seeking chat until minimum world infos are activated63export let world_info_min_activations = 0; // if > 0, will continue seeking chat until minimum world infos are activated
84let world_info_min_activations_depth_max = 0; // used when (world_info_min_activations > 0)64export let world_info_min_activations_depth_max = 0; // used when (world_info_min_activations > 0)
8565
86let world_info_budget = 25;66export let world_info_budget = 25;
87let world_info_include_names = true;67export let world_info_include_names = true;
88let world_info_recursive = false;68export let world_info_recursive = false;
89let world_info_overflow_alert = false;69export let world_info_overflow_alert = false;
90let world_info_case_sensitive = false;70export let world_info_case_sensitive = false;
91let world_info_match_whole_words = false;71export let world_info_match_whole_words = false;
92let world_info_use_group_scoring = false;72export let world_info_use_group_scoring = false;
93let world_info_character_strategy = world_info_insertion_strategy.character_first;73export let world_info_character_strategy = world_info_insertion_strategy.character_first;
94let world_info_budget_cap = 0;74export let world_info_budget_cap = 0;
95const saveWorldDebounced = debounce(async (name, data) => await _save(name, data), debounce_timeout.relaxed);75const saveWorldDebounced = debounce(async (name, data) => await _save(name, data), debounce_timeout.relaxed);
96const saveSettingsDebounced = debounce(() => {76const saveSettingsDebounced = debounce(() => {
97 Object.assign(world_info, { globalSelect: selected_world_info });77 Object.assign(world_info, { globalSelect: selected_world_info });
@@ -101,13 +81,13 @@ const sortFn = (a, b) => b.order - a.order;
101let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };81let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };
10282
103// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.83// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data.
104const worldInfoFilter = new FilterHelper(() => updateEditor());84export const worldInfoFilter = new FilterHelper(() => updateEditor());
105const SORT_ORDER_KEY = 'world_info_sort_order';85export const SORT_ORDER_KEY = 'world_info_sort_order';
106const METADATA_KEY = 'world_info';86export const METADATA_KEY = 'world_info';
10787
108const DEFAULT_DEPTH = 4;88export const DEFAULT_DEPTH = 4;
109const DEFAULT_WEIGHT = 100;89export const DEFAULT_WEIGHT = 100;
110const MAX_SCAN_DEPTH = 1000;90export const MAX_SCAN_DEPTH = 1000;
111const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];91const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];
11292
113// Typedef area93// Typedef area
@@ -732,7 +712,7 @@ export function getWorldInfoSettings() {
732 };712 };
733}713}
734714
735const world_info_position = {715export const world_info_position = {
736 before: 0,716 before: 0,
737 after: 1,717 after: 1,
738 ANTop: 2,718 ANTop: 2,
@@ -747,8 +727,18 @@ export const wi_anchor_position = {
747 after: 1,727 after: 1,
748};728};
749729
750/** @type {StructuredCloneMap<string,object>} */730/**
751const 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 * */
741export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: false });
752742
753/**743/**
754 * Gets the world info based on chat messages.744 * Gets the world info based on chat messages.
@@ -758,7 +748,7 @@ const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: fa
758 * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult748 * @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult
759 * @returns {Promise<WIPromptResult>} The world info string and depth.749 * @returns {Promise<WIPromptResult>} The world info string and depth.
760 */750 */
761async function getWorldInfoPrompt(chat, maxContext, isDryRun) {751export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
762 let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';752 let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';
763753
764 const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun);754 const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun);
@@ -780,7 +770,7 @@ async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
780 };770 };
781}771}
782772
783function setWorldInfoSettings(settings, data) {773export function setWorldInfoSettings(settings, data) {
784 if (settings.world_info_depth !== undefined)774 if (settings.world_info_depth !== undefined)
785 world_info_depth = Number(settings.world_info_depth);775 world_info_depth = Number(settings.world_info_depth);
786 if (settings.world_info_min_activations !== undefined)776 if (settings.world_info_min_activations !== undefined)
@@ -916,7 +906,7 @@ function registerWorldInfoSlashCommands() {
916 return '';906 return '';
917 }907 }
918908
919 const data = await loadWorldInfoData(file);909 const data = await loadWorldInfo(file);
920910
921 if (!data || !('entries' in data)) {911 if (!data || !('entries' in data)) {
922 toastr.warning('World Info file has an invalid format');912 toastr.warning('World Info file has an invalid format');
@@ -965,7 +955,7 @@ function registerWorldInfoSlashCommands() {
965 return '';955 return '';
966 }956 }
967957
968 if (typeof newEntryTemplate[field] === 'boolean') {958 if (typeof newWorldInfoEntryTemplate[field] === 'boolean') {
969 const isTrue = isTrueBoolean(value);959 const isTrue = isTrueBoolean(value);
970 const isFalse = isFalseBoolean(value);960 const isFalse = isFalseBoolean(value);
971961
@@ -1016,7 +1006,7 @@ function registerWorldInfoSlashCommands() {
1016 return '';1006 return '';
1017 }1007 }
10181008
1019 if (newEntryTemplate[field] === undefined) {1009 if (newWorldInfoEntryTemplate[field] === undefined) {
1020 toastr.warning('Valid field name is required');1010 toastr.warning('Valid field name is required');
1021 return '';1011 return '';
1022 }1012 }
@@ -1038,7 +1028,7 @@ function registerWorldInfoSlashCommands() {
1038 const file = args.file;1028 const file = args.file;
1039 const key = args.key;1029 const key = args.key;
10401030
1041 const data = await loadWorldInfoData(file);1031 const data = await loadWorldInfo(file);
10421032
1043 if (!data || !('entries' in data)) {1033 if (!data || !('entries' in data)) {
1044 toastr.warning('Valid World Info file name is required');1034 toastr.warning('Valid World Info file name is required');
@@ -1075,7 +1065,7 @@ function registerWorldInfoSlashCommands() {
10751065
1076 value = value.replace(/\\([{}|])/g, '$1');1066 value = value.replace(/\\([{}|])/g, '$1');
10771067
1078 const data = await loadWorldInfoData(file);1068 const data = await loadWorldInfo(file);
10791069
1080 if (!data || !('entries' in data)) {1070 if (!data || !('entries' in data)) {
1081 toastr.warning('Valid World Info file name is required');1071 toastr.warning('Valid World Info file name is required');
@@ -1089,7 +1079,7 @@ function registerWorldInfoSlashCommands() {
1089 return '';1079 return '';
1090 }1080 }
10911081
1092 if (newEntryTemplate[field] === undefined) {1082 if (newWorldInfoEntryTemplate[field] === undefined) {
1093 toastr.warning('Valid field name is required');1083 toastr.warning('Valid field name is required');
1094 return '';1084 return '';
1095 }1085 }
@@ -1104,8 +1094,8 @@ function registerWorldInfoSlashCommands() {
1104 entry[field] = value;1094 entry[field] = value;
1105 }1095 }
11061096
1107 if (originalDataKeyMap[field]) {1097 if (originalWIDataKeyMap[field]) {
1108 setOriginalDataValue(data, uid, originalDataKeyMap[field], entry[field]);1098 setWIOriginalDataValue(data, uid, originalWIDataKeyMap[field], entry[field]);
1109 }1099 }
11101100
1111 await saveWorldInfo(file, data);1101 await saveWorldInfo(file, data);
@@ -1226,7 +1216,7 @@ function registerWorldInfoSlashCommands() {
1226 /** A collection of local enum providers for this context of world info */1216 /** A collection of local enum providers for this context of world info */
1227 const localEnumProviders = {1217 const localEnumProviders = {
1228 /** All possible fields that can be set in a WI entry */1218 /** All possible fields that can be set in a WI entry */
1229 wiEntryFields: () => Object.entries(newEntryDefinition).map(([key, value]) =>1219 wiEntryFields: () => Object.entries(newWorldInfoEntryDefinition).map(([key, value]) =>
1230 new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : value.default)}`,1220 new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : value.default)}`,
1231 enumTypes.enum, enumIcons.getDataTypeIcon(value.type))),1221 enumTypes.enum, enumIcons.getDataTypeIcon(value.type))),
12321222
@@ -1566,18 +1556,32 @@ function registerWorldInfoSlashCommands() {
1566 }));1556 }));
1567}1557}
15681558
1569// World Info Editor1559
1570async 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 */
1566export async function showWorldEditor(name) {
1571 if (!name) {1567 if (!name) {
1572 hideWorldEditor();1568 hideWorldEditor();
1573 return;1569 return;
1574 }1570 }
15751571
1576 const wiData = await loadWorldInfoData(name);1572 const wiData = await loadWorldInfo(name);
1577 displayWorldEntries(name, wiData);1573 displayWorldEntries(name, wiData);
1578}1574}
15791575
1580async 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 */
1584export async function loadWorldInfo(name) {
1581 if (!name) {1585 if (!name) {
1582 return;1586 return;
1583 }1587 }
@@ -1635,10 +1639,12 @@ function getWIElement(name) {
1635}1639}
16361640
1637/**1641/**
1642 * Sorts the given data based on the selected sort option
1643 *
1638 * @param {any[]} data WI entries1644 * @param {any[]} data WI entries
1639 * @returns {any[]} Sorted data1645 * @returns {any[]} Sorted data
1640 */1646 */
1641function sortEntries(data) {1647export function sortWorldInfoEntries(data) {
1642 const option = $('#world_info_sort_order').find(':selected');1648 const option = $('#world_info_sort_order').find(':selected');
1643 const sortField = option.data('field');1649 const sortField = option.data('field');
1644 const sortOrder = option.data('order');1650 const sortOrder = option.data('order');
@@ -1801,7 +1807,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
18011807
1802 // Apply the filter and do the chosen sorting1808 // Apply the filter and do the chosen sorting
1803 entriesArray = worldInfoFilter.applyFilters(entriesArray);1809 entriesArray = worldInfoFilter.applyFilters(entriesArray);
1804 entriesArray = sortEntries(entriesArray);1810 entriesArray = sortWorldInfoEntries(entriesArray);
18051811
1806 // Cache keys1812 // Cache keys
1807 const keys = entriesArray.flatMap(entry => [...entry.key, ...entry.keysecondary]);1813 const keys = entriesArray.flatMap(entry => [...entry.key, ...entry.keysecondary]);
@@ -1919,7 +1925,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
1919 for (const entry of Object.values(data.entries)) {1925 for (const entry of Object.values(data.entries)) {
1920 if (!entry.comment && Array.isArray(entry.key) && entry.key.length > 0) {1926 if (!entry.comment && Array.isArray(entry.key) && entry.key.length > 0) {
1921 entry.comment = entry.key[0];1927 entry.comment = entry.key[0];
1922 setOriginalDataValue(data, entry.uid, 'comment', entry.comment);1928 setWIOriginalDataValue(data, entry.uid, 'comment', entry.comment);
1923 counter++;1929 counter++;
1924 }1930 }
1925 }1931 }
@@ -1954,7 +1960,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
19541960
1955 // We need to sort the entries here, as the data source isn't sorted1961 // We need to sort the entries here, as the data source isn't sorted
1956 const entries = Object.values(data.entries);1962 const entries = Object.values(data.entries);
1957 sortEntries(entries);1963 sortWorldInfoEntries(entries);
19581964
1959 let updated = 0, current = start;1965 let updated = 0, current = start;
1960 for (const entry of entries) {1966 for (const entry of entries) {
@@ -1962,7 +1968,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
1962 if (entry.order === newOrder) continue;1968 if (entry.order === newOrder) continue;
19631969
1964 entry.order = newOrder;1970 entry.order = newOrder;
1965 setOriginalDataValue(data, entry.order, 'order', entry.order);1971 setWIOriginalDataValue(data, entry.order, 'order', entry.order);
1966 updated++;1972 updated++;
1967 }1973 }
19681974
@@ -2025,7 +2031,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
2025 }2031 }
20262032
2027 item.displayIndex = minDisplayIndex + index;2033 item.displayIndex = minDisplayIndex + index;
2028 setOriginalDataValue(data, uid, 'extensions.display_index', item.displayIndex);2034 setWIOriginalDataValue(data, uid, 'extensions.display_index', item.displayIndex);
2029 });2035 });
20302036
2031 console.table(Object.keys(data.entries).map(uid => data.entries[uid]).map(x => ({ uid: x.uid, key: x.key.join(','), displayIndex: x.displayIndex })));2037 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
2036 //$("#world_popup_entries_list").disableSelection();2042 //$("#world_popup_entries_list").disableSelection();
2037}2043}
20382044
2039const originalDataKeyMap = {2045export const originalWIDataKeyMap = {
2040 'displayIndex': 'extensions.display_index',2046 'displayIndex': 'extensions.display_index',
2041 'excludeRecursion': 'extensions.exclude_recursion',2047 'excludeRecursion': 'extensions.exclude_recursion',
2042 'preventRecursion': 'extensions.prevent_recursion',2048 'preventRecursion': 'extensions.prevent_recursion',
@@ -2087,7 +2093,17 @@ function verifyWorldInfoSearchSortRule() {
2087 }2093 }
2088}2094}
20892095
2090function 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 */
2106export function setWIOriginalDataValue(data, uid, key, value) {
2091 if (data.originalData && Array.isArray(data.originalData.entries)) {2107 if (data.originalData && Array.isArray(data.originalData.entries)) {
2092 let originalEntry = data.originalData.entries.find(x => x.uid === uid);2108 let originalEntry = data.originalData.entries.find(x => x.uid === uid);
20932109
@@ -2099,7 +2115,7 @@ function setOriginalDataValue(data, uid, key, value) {
2099 }2115 }
2100}2116}
21012117
2102function deleteOriginalDataValue(data, uid) {2118function deleteWIOriginalDataValue(data, uid) {
2103 if (data.originalData && Array.isArray(data.originalData.entries)) {2119 if (data.originalData && Array.isArray(data.originalData.entries)) {
2104 const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid);2120 const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid);
21052121
@@ -2120,7 +2136,7 @@ function deleteOriginalDataValue(data, uid) {
2120 * @param {string} input - One or multiple keywords or regexes, separated by commas2136 * @param {string} input - One or multiple keywords or regexes, separated by commas
2121 * @returns {string[]} An array of keywords and regexes2137 * @returns {string[]} An array of keywords and regexes
2122 */2138 */
2123function splitKeywordsAndRegexes(input) {2139export function splitKeywordsAndRegexes(input) {
2124 /** @type {string[]} */2140 /** @type {string[]} */
2125 let keywordsAndRegexes = [];2141 let keywordsAndRegexes = [];
21262142
@@ -2224,7 +2240,7 @@ function isValidRegex(input) {
2224 * @param {string} input - A delimited regex string2240 * @param {string} input - A delimited regex string
2225 * @returns {RegExp|null} The regex object, or null if not a valid regex2241 * @returns {RegExp|null} The regex object, or null if not a valid regex
2226 */2242 */
2227function parseRegexFromString(input) {2243export function parseRegexFromString(input) {
2228 // Extracting the regex pattern and flags2244 // Extracting the regex pattern and flags
2229 let match = input.match(/^\/([\w\W]+?)\/([gimsuy]*)$/);2245 let match = input.match(/^\/([\w\W]+?)\/([gimsuy]*)$/);
2230 if (!match) {2246 if (!match) {
@@ -2310,7 +2326,7 @@ function getWorldEntry(name, data, entry) {
2310 !skipReset && resetScrollHeight(this);2326 !skipReset && resetScrollHeight(this);
2311 if (!noSave) {2327 if (!noSave) {
2312 data.entries[uid][entryPropName] = keys;2328 data.entries[uid][entryPropName] = keys;
2313 setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);2329 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2314 await saveWorldInfo(name, data);2330 await saveWorldInfo(name, data);
2315 }2331 }
2316 });2332 });
@@ -2348,7 +2364,7 @@ function getWorldEntry(name, data, entry) {
2348 !skipReset && resetScrollHeight(this);2364 !skipReset && resetScrollHeight(this);
2349 if (!noSave) {2365 if (!noSave) {
2350 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);2366 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
2351 setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);2367 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2352 await saveWorldInfo(name, data);2368 await saveWorldInfo(name, data);
2353 }2369 }
2354 });2370 });
@@ -2392,7 +2408,7 @@ function getWorldEntry(name, data, entry) {
2392 const uid = $(this).data('uid');2408 const uid = $(this).data('uid');
2393 const value = Number($(this).val());2409 const value = Number($(this).val());
2394 data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;2410 data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
2395 setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);2411 setWIOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
2396 await saveWorldInfo(name, data);2412 await saveWorldInfo(name, data);
2397 });2413 });
23982414
@@ -2441,7 +2457,7 @@ function getWorldEntry(name, data, entry) {
2441 }2457 }
2442 }2458 }
24432459
2444 setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);2460 setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2445 await saveWorldInfo(name, data);2461 await saveWorldInfo(name, data);
2446 });2462 });
2447 characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');2463 characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');
@@ -2503,7 +2519,7 @@ function getWorldEntry(name, data, entry) {
2503 },2519 },
2504 );2520 );
2505 }2521 }
2506 setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);2522 setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2507 await saveWorldInfo(name, data);2523 await saveWorldInfo(name, data);
2508 });2524 });
25092525
@@ -2517,7 +2533,7 @@ function getWorldEntry(name, data, entry) {
2517 !skipReset && resetScrollHeight(this);2533 !skipReset && resetScrollHeight(this);
2518 data.entries[uid].comment = value;2534 data.entries[uid].comment = value;
25192535
2520 setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);2536 setWIOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
2521 await saveWorldInfo(name, data);2537 await saveWorldInfo(name, data);
2522 });2538 });
2523 commentToggle.data('uid', entry.uid);2539 commentToggle.data('uid', entry.uid);
@@ -2552,7 +2568,7 @@ function getWorldEntry(name, data, entry) {
2552 const value = $(this).val();2568 const value = $(this).val();
2553 data.entries[uid].content = value;2569 data.entries[uid].content = value;
25542570
2555 setOriginalDataValue(data, uid, 'content', data.entries[uid].content);2571 setWIOriginalDataValue(data, uid, 'content', data.entries[uid].content);
2556 await saveWorldInfo(name, data);2572 await saveWorldInfo(name, data);
25572573
2558 if (skipCount) {2574 if (skipCount) {
@@ -2582,7 +2598,7 @@ function getWorldEntry(name, data, entry) {
2582 const value = $(this).prop('checked');2598 const value = $(this).prop('checked');
2583 data.entries[uid].selective = value;2599 data.entries[uid].selective = value;
25842600
2585 setOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);2601 setWIOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
2586 await saveWorldInfo(name, data);2602 await saveWorldInfo(name, data);
25872603
2588 const keysecondary = $(this)2604 const keysecondary = $(this)
@@ -2631,7 +2647,7 @@ function getWorldEntry(name, data, entry) {
26312647
2632 data.entries[uid].order = !isNaN(value) ? value : 0;2648 data.entries[uid].order = !isNaN(value) ? value : 0;
2633 updatePosOrdDisplay(uid);2649 updatePosOrdDisplay(uid);
2634 setOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);2650 setWIOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
2635 await saveWorldInfo(name, data);2651 await saveWorldInfo(name, data);
2636 });2652 });
2637 orderInput.val(entry.order).trigger('input');2653 orderInput.val(entry.order).trigger('input');
@@ -2645,7 +2661,7 @@ function getWorldEntry(name, data, entry) {
2645 const value = String($(this).val()).trim();2661 const value = String($(this).val()).trim();
26462662
2647 data.entries[uid].group = value;2663 data.entries[uid].group = value;
2648 setOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);2664 setWIOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
2649 await saveWorldInfo(name, data);2665 await saveWorldInfo(name, data);
2650 });2666 });
2651 groupInput.val(entry.group ?? '').trigger('input');2667 groupInput.val(entry.group ?? '').trigger('input');
@@ -2658,7 +2674,7 @@ function getWorldEntry(name, data, entry) {
2658 const uid = $(this).data('uid');2674 const uid = $(this).data('uid');
2659 const value = $(this).prop('checked');2675 const value = $(this).prop('checked');
2660 data.entries[uid].groupOverride = value;2676 data.entries[uid].groupOverride = value;
2661 setOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);2677 setWIOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
2662 await saveWorldInfo(name, data);2678 await saveWorldInfo(name, data);
2663 });2679 });
2664 groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');2680 groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
@@ -2682,7 +2698,7 @@ function getWorldEntry(name, data, entry) {
2682 }2698 }
26832699
2684 data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;2700 data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
2685 setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);2701 setWIOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
2686 await saveWorldInfo(name, data);2702 await saveWorldInfo(name, data);
2687 });2703 });
2688 groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');2704 groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
@@ -2695,7 +2711,7 @@ function getWorldEntry(name, data, entry) {
2695 const value = Number($(this).val());2711 const value = Number($(this).val());
2696 data.entries[uid].sticky = !isNaN(value) ? value : null;2712 data.entries[uid].sticky = !isNaN(value) ? value : null;
26972713
2698 setOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);2714 setWIOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);
2699 await saveWorldInfo(name, data);2715 await saveWorldInfo(name, data);
2700 });2716 });
2701 sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');2717 sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');
@@ -2708,7 +2724,7 @@ function getWorldEntry(name, data, entry) {
2708 const value = Number($(this).val());2724 const value = Number($(this).val());
2709 data.entries[uid].cooldown = !isNaN(value) ? value : null;2725 data.entries[uid].cooldown = !isNaN(value) ? value : null;
27102726
2711 setOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);2727 setWIOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);
2712 await saveWorldInfo(name, data);2728 await saveWorldInfo(name, data);
2713 });2729 });
2714 cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');2730 cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');
@@ -2721,7 +2737,7 @@ function getWorldEntry(name, data, entry) {
2721 const value = Number($(this).val());2737 const value = Number($(this).val());
2722 data.entries[uid].delay = !isNaN(value) ? value : null;2738 data.entries[uid].delay = !isNaN(value) ? value : null;
27232739
2724 setOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);2740 setWIOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);
2725 await saveWorldInfo(name, data);2741 await saveWorldInfo(name, data);
2726 });2742 });
2727 delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');2743 delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');
@@ -2741,7 +2757,7 @@ function getWorldEntry(name, data, entry) {
27412757
2742 data.entries[uid].depth = !isNaN(value) ? value : 0;2758 data.entries[uid].depth = !isNaN(value) ? value : 0;
2743 updatePosOrdDisplay(uid);2759 updatePosOrdDisplay(uid);
2744 setOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);2760 setWIOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);
2745 await saveWorldInfo(name, data);2761 await saveWorldInfo(name, data);
2746 });2762 });
2747 depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');2763 depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');
@@ -2769,7 +2785,7 @@ function getWorldEntry(name, data, entry) {
2769 }2785 }
2770 }2786 }
27712787
2772 setOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);2788 setWIOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
2773 await saveWorldInfo(name, data);2789 await saveWorldInfo(name, data);
2774 });2790 });
2775 probabilityInput.val(entry.probability).trigger('input');2791 probabilityInput.val(entry.probability).trigger('input');
@@ -2836,10 +2852,10 @@ function getWorldEntry(name, data, entry) {
2836 }2852 }
2837 updatePosOrdDisplay(uid);2853 updatePosOrdDisplay(uid);
2838 // Spec v2 only supports before_char and after_char2854 // Spec v2 only supports before_char and after_char
2839 setOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');2855 setWIOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
2840 // Write the original value as extensions field2856 // Write the original value as extensions field
2841 setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);2857 setWIOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
2842 setOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);2858 setWIOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
2843 await saveWorldInfo(name, data);2859 await saveWorldInfo(name, data);
2844 });2860 });
28452861
@@ -2881,36 +2897,36 @@ function getWorldEntry(name, data, entry) {
2881 data.entries[uid].constant = true;2897 data.entries[uid].constant = true;
2882 data.entries[uid].disable = false;2898 data.entries[uid].disable = false;
2883 data.entries[uid].vectorized = false;2899 data.entries[uid].vectorized = false;
2884 setOriginalDataValue(data, uid, 'enabled', true);2900 setWIOriginalDataValue(data, uid, 'enabled', true);
2885 setOriginalDataValue(data, uid, 'constant', true);2901 setWIOriginalDataValue(data, uid, 'constant', true);
2886 setOriginalDataValue(data, uid, 'extensions.vectorized', false);2902 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
2887 template.removeClass('disabledWIEntry');2903 template.removeClass('disabledWIEntry');
2888 break;2904 break;
2889 case 'normal':2905 case 'normal':
2890 data.entries[uid].constant = false;2906 data.entries[uid].constant = false;
2891 data.entries[uid].disable = false;2907 data.entries[uid].disable = false;
2892 data.entries[uid].vectorized = false;2908 data.entries[uid].vectorized = false;
2893 setOriginalDataValue(data, uid, 'enabled', true);2909 setWIOriginalDataValue(data, uid, 'enabled', true);
2894 setOriginalDataValue(data, uid, 'constant', false);2910 setWIOriginalDataValue(data, uid, 'constant', false);
2895 setOriginalDataValue(data, uid, 'extensions.vectorized', false);2911 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
2896 template.removeClass('disabledWIEntry');2912 template.removeClass('disabledWIEntry');
2897 break;2913 break;
2898 case 'vectorized':2914 case 'vectorized':
2899 data.entries[uid].constant = false;2915 data.entries[uid].constant = false;
2900 data.entries[uid].disable = false;2916 data.entries[uid].disable = false;
2901 data.entries[uid].vectorized = true;2917 data.entries[uid].vectorized = true;
2902 setOriginalDataValue(data, uid, 'enabled', true);2918 setWIOriginalDataValue(data, uid, 'enabled', true);
2903 setOriginalDataValue(data, uid, 'constant', false);2919 setWIOriginalDataValue(data, uid, 'constant', false);
2904 setOriginalDataValue(data, uid, 'extensions.vectorized', true);2920 setWIOriginalDataValue(data, uid, 'extensions.vectorized', true);
2905 template.removeClass('disabledWIEntry');2921 template.removeClass('disabledWIEntry');
2906 break;2922 break;
2907 case 'disabled':2923 case 'disabled':
2908 data.entries[uid].constant = false;2924 data.entries[uid].constant = false;
2909 data.entries[uid].disable = true;2925 data.entries[uid].disable = true;
2910 data.entries[uid].vectorized = false;2926 data.entries[uid].vectorized = false;
2911 setOriginalDataValue(data, uid, 'enabled', false);2927 setWIOriginalDataValue(data, uid, 'enabled', false);
2912 setOriginalDataValue(data, uid, 'constant', false);2928 setWIOriginalDataValue(data, uid, 'constant', false);
2913 setOriginalDataValue(data, uid, 'extensions.vectorized', false);2929 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
2914 template.addClass('disabledWIEntry');2930 template.addClass('disabledWIEntry');
2915 break;2931 break;
2916 }2932 }
@@ -2941,7 +2957,7 @@ function getWorldEntry(name, data, entry) {
2941 const uid = $(this).data('uid');2957 const uid = $(this).data('uid');
2942 const value = $(this).prop('checked');2958 const value = $(this).prop('checked');
2943 data.entries[uid].excludeRecursion = value;2959 data.entries[uid].excludeRecursion = value;
2944 setOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);2960 setWIOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);
2945 await saveWorldInfo(name, data);2961 await saveWorldInfo(name, data);
2946 });2962 });
2947 excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');2963 excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');
@@ -2953,7 +2969,7 @@ function getWorldEntry(name, data, entry) {
2953 const uid = $(this).data('uid');2969 const uid = $(this).data('uid');
2954 const value = $(this).prop('checked');2970 const value = $(this).prop('checked');
2955 data.entries[uid].preventRecursion = value;2971 data.entries[uid].preventRecursion = value;
2956 setOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);2972 setWIOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);
2957 await saveWorldInfo(name, data);2973 await saveWorldInfo(name, data);
2958 });2974 });
2959 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');2975 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
@@ -2965,7 +2981,7 @@ function getWorldEntry(name, data, entry) {
2965 const uid = $(this).data('uid');2981 const uid = $(this).data('uid');
2966 const value = $(this).prop('checked');2982 const value = $(this).prop('checked');
2967 data.entries[uid].delayUntilRecursion = value;2983 data.entries[uid].delayUntilRecursion = value;
2968 setOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);2984 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
2969 await saveWorldInfo(name, data);2985 await saveWorldInfo(name, data);
2970 });2986 });
2971 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');2987 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
@@ -2988,7 +3004,7 @@ function getWorldEntry(name, data, entry) {
2988 deleteButton.on('click', async function () {3004 deleteButton.on('click', async function () {
2989 const uid = $(this).data('uid');3005 const uid = $(this).data('uid');
2990 deleteWorldInfoEntry(data, uid);3006 deleteWorldInfoEntry(data, uid);
2991 deleteOriginalDataValue(data, uid);3007 deleteWIOriginalDataValue(data, uid);
2992 await saveWorldInfo(name, data);3008 await saveWorldInfo(name, data);
2993 updateEditor(navigation_option.previous);3009 updateEditor(navigation_option.previous);
2994 });3010 });
@@ -3015,7 +3031,7 @@ function getWorldEntry(name, data, entry) {
3015 }3031 }
30163032
3017 data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;3033 data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;
3018 setOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);3034 setWIOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
3019 await saveWorldInfo(name, data);3035 await saveWorldInfo(name, data);
3020 });3036 });
3021 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');3037 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
@@ -3028,7 +3044,7 @@ function getWorldEntry(name, data, entry) {
3028 const value = $(this).val();3044 const value = $(this).val();
30293045
3030 data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';3046 data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
3031 setOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);3047 setWIOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
3032 await saveWorldInfo(name, data);3048 await saveWorldInfo(name, data);
3033 });3049 });
3034 caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');3050 caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');
@@ -3041,7 +3057,7 @@ function getWorldEntry(name, data, entry) {
3041 const value = $(this).val();3057 const value = $(this).val();
30423058
3043 data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';3059 data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
3044 setOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);3060 setWIOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
3045 await saveWorldInfo(name, data);3061 await saveWorldInfo(name, data);
3046 });3062 });
3047 matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');3063 matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
@@ -3054,7 +3070,7 @@ function getWorldEntry(name, data, entry) {
3054 const value = $(this).val();3070 const value = $(this).val();
30553071
3056 data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';3072 data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
3057 setOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);3073 setWIOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
3058 await saveWorldInfo(name, data);3074 await saveWorldInfo(name, data);
3059 });3075 });
3060 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');3076 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
@@ -3067,7 +3083,7 @@ function getWorldEntry(name, data, entry) {
3067 const value = $(this).val();3083 const value = $(this).val();
30683084
3069 data.entries[uid].automationId = value;3085 data.entries[uid].automationId = value;
3070 setOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);3086 setWIOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
3071 await saveWorldInfo(name, data);3087 await saveWorldInfo(name, data);
3072 });3088 });
3073 automationIdInput.val(entry.automationId ?? '').trigger('input');3089 automationIdInput.val(entry.automationId ?? '').trigger('input');
@@ -3200,12 +3216,12 @@ function createEntryInputAutocomplete(input, callback, { allowMultiple = false }
32003216
32013217
3202/**3218/**
3203 * Duplicated a WI entry by copying all of its properties and assigning a new uid3219 * Duplicate a WI entry by copying all of its properties and assigning a new uid
3204 * @param {*} data - The data of the book3220 * @param {*} data - The data of the book
3205 * @param {number} uid - The uid of the entry to copy in this book3221 * @param {number} uid - The uid of the entry to copy in this book
3206 * @returns {*} The new WI duplicated entry3222 * @returns {*} The new WI duplicated entry
3207 */3223 */
3208function duplicateWorldInfoEntry(data, uid) {3224export function duplicateWorldInfoEntry(data, uid) {
3209 if (!data || !('entries' in data) || !data.entries[uid]) {3225 if (!data || !('entries' in data) || !data.entries[uid]) {
3210 return;3226 return;
3211 }3227 }
@@ -3226,7 +3242,7 @@ function duplicateWorldInfoEntry(data, uid) {
3226 * @param {*[]} data - The data of the book3242 * @param {*[]} data - The data of the book
3227 * @param {number} uid - The uid of the entry to copy in this book3243 * @param {number} uid - The uid of the entry to copy in this book
3228 */3244 */
3229function deleteWorldInfoEntry(data, uid) {3245export function deleteWorldInfoEntry(data, uid) {
3230 if (!data || !('entries' in data)) {3246 if (!data || !('entries' in data)) {
3231 return;3247 return;
3232 }3248 }
@@ -3245,7 +3261,7 @@ function deleteWorldInfoEntry(data, uid) {
3245 *3261 *
3246 * @type {{[key: string]: { default: any, type: string }}}3262 * @type {{[key: string]: { default: any, type: string }}}
3247 */3263 */
3248const newEntryDefinition = {3264export const newWorldInfoEntryDefinition = {
3249 key: { default: [], type: 'array' },3265 key: { default: [], type: 'array' },
3250 keysecondary: { default: [], type: 'array' },3266 keysecondary: { default: [], type: 'array' },
3251 comment: { default: '', type: 'string' },3267 comment: { default: '', type: 'string' },
@@ -3278,8 +3294,8 @@ const newEntryDefinition = {
3278 delay: { default: null, type: 'number?' },3294 delay: { default: null, type: 'number?' },
3279};3295};
32803296
3281const newEntryTemplate = Object.fromEntries(3297export const newWorldInfoEntryTemplate = Object.fromEntries(
3282 Object.entries(newEntryDefinition).map(([key, value]) => [key, value.default]),3298 Object.entries(newWorldInfoEntryDefinition).map(([key, value]) => [key, value.default]),
3283);3299);
32843300
3285/**3301/**
@@ -3288,7 +3304,7 @@ const newEntryTemplate = Object.fromEntries(
3288 * @param {any} data WI data3304 * @param {any} data WI data
3289 * @returns {object | undefined} New entry object or undefined if failed3305 * @returns {object | undefined} New entry object or undefined if failed
3290 */3306 */
3291function createWorldInfoEntry(_name, data) {3307export function createWorldInfoEntry(_name, data) {
3292 const newUid = getFreeWorldEntryUid(data);3308 const newUid = getFreeWorldEntryUid(data);
32933309
3294 if (!Number.isInteger(newUid)) {3310 if (!Number.isInteger(newUid)) {
@@ -3296,7 +3312,7 @@ function createWorldInfoEntry(_name, data) {
3296 return;3312 return;
3297 }3313 }
32983314
3299 const newEntry = { uid: newUid, ...structuredClone(newEntryTemplate) };3315 const newEntry = { uid: newUid, ...structuredClone(newWorldInfoEntryTemplate) };
3300 data.entries[newUid] = newEntry;3316 data.entries[newUid] = newEntry;
33013317
3302 return newEntry;3318 return newEntry;
@@ -3314,7 +3330,21 @@ async function _save(name, data) {
3314 eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);3330 eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);
3315}3331}
33163332
3317async 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 */
3347export async function saveWorldInfo(name, data, immediately = false) {
3318 if (!name || !data) {3348 if (!name || !data) {
3319 return;3349 return;
3320 }3350 }
@@ -3371,7 +3401,7 @@ async function renameWorldInfo(name, data) {
3371 * @param {string} worldInfoName - The name of the world info to delete3401 * @param {string} worldInfoName - The name of the world info to delete
3372 * @returns {Promise<boolean>} A promise that resolves to true if the world info was successfully deleted, false otherwise3402 * @returns {Promise<boolean>} A promise that resolves to true if the world info was successfully deleted, false otherwise
3373 */3403 */
3374async function deleteWorldInfo(worldInfoName) {3404export async function deleteWorldInfo(worldInfoName) {
3375 if (!world_names.includes(worldInfoName)) {3405 if (!world_names.includes(worldInfoName)) {
3376 return false;3406 return false;
3377 }3407 }
@@ -3406,7 +3436,7 @@ async function deleteWorldInfo(worldInfoName) {
3406 return true;3436 return true;
3407}3437}
34083438
3409function getFreeWorldEntryUid(data) {3439export function getFreeWorldEntryUid(data) {
3410 if (!data || !('entries' in data)) {3440 if (!data || !('entries' in data)) {
3411 return null;3441 return null;
3412 }3442 }
@@ -3422,7 +3452,7 @@ function getFreeWorldEntryUid(data) {
3422 return null;3452 return null;
3423}3453}
34243454
3425function getFreeWorldName() {3455export function getFreeWorldName() {
3426 const MAX_FREE_NAME = 100_000;3456 const MAX_FREE_NAME = 100_000;
3427 for (let index = 1; index < MAX_FREE_NAME; index++) {3457 for (let index = 1; index < MAX_FREE_NAME; index++) {
3428 const newName = `New World (${index})`;3458 const newName = `New World (${index})`;
@@ -3444,7 +3474,7 @@ function getFreeWorldName() {
3444 * @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when overwriting an existing world3474 * @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when overwriting an existing world
3445 * @returns {Promise<boolean>} - True if the world info was successfully created, false otherwise3475 * @returns {Promise<boolean>} - True if the world info was successfully created, false otherwise
3446 */3476 */
3447async function createNewWorldInfo(worldName, { interactive = false } = {}) {3477export async function createNewWorldInfo(worldName, { interactive = false } = {}) {
3448 const worldInfoTemplate = { entries: {} };3478 const worldInfoTemplate = { entries: {} };
34493479
3450 if (!worldName) {3480 if (!worldName) {
@@ -3505,7 +3535,7 @@ async function getCharacterLore() {
3505 continue;3535 continue;
3506 }3536 }
35073537
3508 const data = await loadWorldInfoData(worldName);3538 const data = await loadWorldInfo(worldName);
3509 const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];3539 const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];
3510 entries = entries.concat(newEntries);3540 entries = entries.concat(newEntries);
35113541
@@ -3525,7 +3555,7 @@ async function getGlobalLore() {
35253555
3526 let entries = [];3556 let entries = [];
3527 for (const worldName of selected_world_info) {3557 for (const worldName of selected_world_info) {
3528 const data = await loadWorldInfoData(worldName);3558 const data = await loadWorldInfo(worldName);
3529 const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];3559 const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];
3530 entries = entries.concat(newEntries);3560 entries = entries.concat(newEntries);
3531 }3561 }
@@ -3547,7 +3577,7 @@ async function getChatLore() {
3547 return [];3577 return [];
3548 }3578 }
35493579
3550 const data = await loadWorldInfoData(chatWorld);3580 const data = await loadWorldInfo(chatWorld);
3551 const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: chatWorld, ...rest })) : [];3581 const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: chatWorld, ...rest })) : [];
35523582
3553 console.debug(`[WI] Chat lore has ${entries.length} entries`, [chatWorld]);3583 console.debug(`[WI] Chat lore has ${entries.length} entries`, [chatWorld]);
@@ -3663,7 +3693,7 @@ function parseDecorators(content) {
3663 * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated3693 * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
3664 * @returns {Promise<WIActivated>} The world info activated.3694 * @returns {Promise<WIActivated>} The world info activated.
3665 */3695 */
3666async function checkWorldInfo(chat, maxContext, isDryRun) {3696export async function checkWorldInfo(chat, maxContext, isDryRun) {
3667 const context = getContext();3697 const context = getContext();
3668 const buffer = new WorldInfoBuffer(chat);3698 const buffer = new WorldInfoBuffer(chat);
36693699
@@ -4233,7 +4263,7 @@ function convertAgnaiMemoryBook(inputObj) {
42334263
4234 inputObj.entries.forEach((entry, index) => {4264 inputObj.entries.forEach((entry, index) => {
4235 outputObj.entries[index] = {4265 outputObj.entries[index] = {
4236 ...newEntryTemplate,4266 ...newWorldInfoEntryTemplate,
4237 uid: index,4267 uid: index,
4238 key: entry.keywords,4268 key: entry.keywords,
4239 keysecondary: [],4269 keysecondary: [],
@@ -4275,7 +4305,7 @@ function convertRisuLorebook(inputObj) {
42754305
4276 inputObj.data.forEach((entry, index) => {4306 inputObj.data.forEach((entry, index) => {
4277 outputObj.entries[index] = {4307 outputObj.entries[index] = {
4278 ...newEntryTemplate,4308 ...newWorldInfoEntryTemplate,
4279 uid: index,4309 uid: index,
4280 key: entry.key.split(',').map(x => x.trim()),4310 key: entry.key.split(',').map(x => x.trim()),
4281 keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [],4311 keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [],
@@ -4322,7 +4352,7 @@ function convertNovelLorebook(inputObj) {
4322 const addMemo = displayName !== undefined && displayName.trim() !== '';4352 const addMemo = displayName !== undefined && displayName.trim() !== '';
43234353
4324 outputObj.entries[index] = {4354 outputObj.entries[index] = {
4325 ...newEntryTemplate,4355 ...newWorldInfoEntryTemplate,
4326 uid: index,4356 uid: index,
4327 key: entry.keys,4357 key: entry.keys,
4328 keysecondary: [],4358 keysecondary: [],
@@ -4369,7 +4399,7 @@ function convertCharacterBook(characterBook) {
4369 }4399 }
43704400
4371 result.entries[entry.id] = {4401 result.entries[entry.id] = {
4372 ...newEntryTemplate,4402 ...newWorldInfoEntryTemplate,
4373 uid: entry.id,4403 uid: entry.id,
4374 key: entry.keys,4404 key: entry.keys,
4375 keysecondary: entry.secondary_keys || [],4405 keysecondary: entry.secondary_keys || [],
@@ -4499,7 +4529,7 @@ export async function importEmbeddedWorldInfo(skipPopup = false) {
4499 setWorldInfoButtonClass(chid, true);4529 setWorldInfoButtonClass(chid, true);
4500}4530}
45014531
4502function onWorldInfoChange(args, text) {4532export function onWorldInfoChange(args, text) {
4503 if (args !== '__notSlashCommand__') { // if it's a slash command4533 if (args !== '__notSlashCommand__') { // if it's a slash command
4504 const silent = isTrueBoolean(args.silent);4534 const silent = isTrueBoolean(args.silent);
4505 if (text.trim() !== '') { // and args are provided4535 if (text.trim() !== '') { // and args are provided
@@ -4650,7 +4680,7 @@ export async function importWorldInfo(file) {
4650 });4680 });
4651}4681}
46524682
4653function assignLorebookToChat() {4683export function assignLorebookToChat() {
4654 const selectedName = chat_metadata[METADATA_KEY];4684 const selectedName = chat_metadata[METADATA_KEY];
4655 const template = $('#chat_world_template .chat_world').clone();4685 const template = $('#chat_world_template .chat_world').clone();
46564686