Merge pull request #2601 from SillyTavern/wi-blackbox-be-gone WI blackbox begone (Export, document and refactor a bit of the WI API)

d5576cd7b78cb84990c4b3c47fbb39c9ed07ce63

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
1 files changed, +190 -145Showing whitespace changes
public/scripts/world-info.js+190 -145
@@ -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,14 +1639,18 @@ 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
1645 * @param {object} [options={}] - Optional arguments
1646 * @param {{sortField?: string, sortOrder?: string, sortRule?: string}} [options.customSort={}] - Custom sort options, instead of the chosen UI sort
1639 * @returns {any[]} Sorted data1647 * @returns {any[]} Sorted data
1640 */1648 */
1641function sortEntries(data) {1649export function sortWorldInfoEntries(data, { customSort = null } = {}) {
1642 const option = $('#world_info_sort_order').find(':selected');1650 const option = $('#world_info_sort_order').find(':selected');
1643 const sortField = option.data('field');1651 const sortField = customSort?.sortField ?? option.data('field');
1644 const sortOrder = option.data('order');1652 const sortOrder = customSort?.sortOrder ?? option.data('order');
1645 const sortRule = option.data('rule');1653 const sortRule = customSort?.sortRule ?? option.data('rule');
1646 const orderSign = sortOrder === 'asc' ? 1 : -1;1654 const orderSign = sortOrder === 'asc' ? 1 : -1;
16471655
1648 if (!data.length) return data;1656 if (!data.length) return data;
@@ -1801,7 +1809,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
18011809
1802 // Apply the filter and do the chosen sorting1810 // Apply the filter and do the chosen sorting
1803 entriesArray = worldInfoFilter.applyFilters(entriesArray);1811 entriesArray = worldInfoFilter.applyFilters(entriesArray);
1804 entriesArray = sortEntries(entriesArray);1812 entriesArray = sortWorldInfoEntries(entriesArray);
18051813
1806 // Cache keys1814 // Cache keys
1807 const keys = entriesArray.flatMap(entry => [...entry.key, ...entry.keysecondary]);1815 const keys = entriesArray.flatMap(entry => [...entry.key, ...entry.keysecondary]);
@@ -1919,7 +1927,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
1919 for (const entry of Object.values(data.entries)) {1927 for (const entry of Object.values(data.entries)) {
1920 if (!entry.comment && Array.isArray(entry.key) && entry.key.length > 0) {1928 if (!entry.comment && Array.isArray(entry.key) && entry.key.length > 0) {
1921 entry.comment = entry.key[0];1929 entry.comment = entry.key[0];
1922 setOriginalDataValue(data, entry.uid, 'comment', entry.comment);1930 setWIOriginalDataValue(data, entry.uid, 'comment', entry.comment);
1923 counter++;1931 counter++;
1924 }1932 }
1925 }1933 }
@@ -1954,7 +1962,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
19541962
1955 // We need to sort the entries here, as the data source isn't sorted1963 // We need to sort the entries here, as the data source isn't sorted
1956 const entries = Object.values(data.entries);1964 const entries = Object.values(data.entries);
1957 sortEntries(entries);1965 sortWorldInfoEntries(entries);
19581966
1959 let updated = 0, current = start;1967 let updated = 0, current = start;
1960 for (const entry of entries) {1968 for (const entry of entries) {
@@ -1962,7 +1970,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
1962 if (entry.order === newOrder) continue;1970 if (entry.order === newOrder) continue;
19631971
1964 entry.order = newOrder;1972 entry.order = newOrder;
1965 setOriginalDataValue(data, entry.order, 'order', entry.order);1973 setWIOriginalDataValue(data, entry.order, 'order', entry.order);
1966 updated++;1974 updated++;
1967 }1975 }
19681976
@@ -2025,7 +2033,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
2025 }2033 }
20262034
2027 item.displayIndex = minDisplayIndex + index;2035 item.displayIndex = minDisplayIndex + index;
2028 setOriginalDataValue(data, uid, 'extensions.display_index', item.displayIndex);2036 setWIOriginalDataValue(data, uid, 'extensions.display_index', item.displayIndex);
2029 });2037 });
20302038
2031 console.table(Object.keys(data.entries).map(uid => data.entries[uid]).map(x => ({ uid: x.uid, key: x.key.join(','), displayIndex: x.displayIndex })));2039 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
2036 //$("#world_popup_entries_list").disableSelection();2044 //$("#world_popup_entries_list").disableSelection();
2037}2045}
20382046
2039const originalDataKeyMap = {2047export const originalWIDataKeyMap = {
2040 'displayIndex': 'extensions.display_index',2048 'displayIndex': 'extensions.display_index',
2041 'excludeRecursion': 'extensions.exclude_recursion',2049 'excludeRecursion': 'extensions.exclude_recursion',
2042 'preventRecursion': 'extensions.prevent_recursion',2050 'preventRecursion': 'extensions.prevent_recursion',
@@ -2087,7 +2095,17 @@ function verifyWorldInfoSearchSortRule() {
2087 }2095 }
2088}2096}
20892097
2090function setOriginalDataValue(data, uid, key, value) {2098/**
2099 * Sets the value of a specific key in the original data entry corresponding to the given uid
2100 * This needs to be called whenever you update JSON data fields.
2101 * Use `originalWIDataKeyMap` to find the correct value to be set.
2102 *
2103 * @param {object} data - The data object containing the original data entries.
2104 * @param {string} uid - The unique identifier of the data entry.
2105 * @param {string} key - The key of the value to be set.
2106 * @param {any} value - The value to be set.
2107 */
2108export function setWIOriginalDataValue(data, uid, key, value) {
2091 if (data.originalData && Array.isArray(data.originalData.entries)) {2109 if (data.originalData && Array.isArray(data.originalData.entries)) {
2092 let originalEntry = data.originalData.entries.find(x => x.uid === uid);2110 let originalEntry = data.originalData.entries.find(x => x.uid === uid);
20932111
@@ -2099,7 +2117,13 @@ function setOriginalDataValue(data, uid, key, value) {
2099 }2117 }
2100}2118}
21012119
2102function deleteOriginalDataValue(data, uid) {2120/**
2121 * Deletes the original data entry corresponding to the given uid from the provided data object
2122 *
2123 * @param {object} data - The data object containing the original data entries
2124 * @param {string} uid - The unique identifier of the data entry to be deleted
2125 */
2126export function deleteWIOriginalDataValue(data, uid) {
2103 if (data.originalData && Array.isArray(data.originalData.entries)) {2127 if (data.originalData && Array.isArray(data.originalData.entries)) {
2104 const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid);2128 const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid);
21052129
@@ -2120,7 +2144,7 @@ function deleteOriginalDataValue(data, uid) {
2120 * @param {string} input - One or multiple keywords or regexes, separated by commas2144 * @param {string} input - One or multiple keywords or regexes, separated by commas
2121 * @returns {string[]} An array of keywords and regexes2145 * @returns {string[]} An array of keywords and regexes
2122 */2146 */
2123function splitKeywordsAndRegexes(input) {2147export function splitKeywordsAndRegexes(input) {
2124 /** @type {string[]} */2148 /** @type {string[]} */
2125 let keywordsAndRegexes = [];2149 let keywordsAndRegexes = [];
21262150
@@ -2224,7 +2248,7 @@ function isValidRegex(input) {
2224 * @param {string} input - A delimited regex string2248 * @param {string} input - A delimited regex string
2225 * @returns {RegExp|null} The regex object, or null if not a valid regex2249 * @returns {RegExp|null} The regex object, or null if not a valid regex
2226 */2250 */
2227function parseRegexFromString(input) {2251export function parseRegexFromString(input) {
2228 // Extracting the regex pattern and flags2252 // Extracting the regex pattern and flags
2229 let match = input.match(/^\/([\w\W]+?)\/([gimsuy]*)$/);2253 let match = input.match(/^\/([\w\W]+?)\/([gimsuy]*)$/);
2230 if (!match) {2254 if (!match) {
@@ -2310,7 +2334,7 @@ function getWorldEntry(name, data, entry) {
2310 !skipReset && resetScrollHeight(this);2334 !skipReset && resetScrollHeight(this);
2311 if (!noSave) {2335 if (!noSave) {
2312 data.entries[uid][entryPropName] = keys;2336 data.entries[uid][entryPropName] = keys;
2313 setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);2337 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2314 await saveWorldInfo(name, data);2338 await saveWorldInfo(name, data);
2315 }2339 }
2316 });2340 });
@@ -2348,7 +2372,7 @@ function getWorldEntry(name, data, entry) {
2348 !skipReset && resetScrollHeight(this);2372 !skipReset && resetScrollHeight(this);
2349 if (!noSave) {2373 if (!noSave) {
2350 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);2374 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
2351 setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);2375 setWIOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2352 await saveWorldInfo(name, data);2376 await saveWorldInfo(name, data);
2353 }2377 }
2354 });2378 });
@@ -2392,7 +2416,7 @@ function getWorldEntry(name, data, entry) {
2392 const uid = $(this).data('uid');2416 const uid = $(this).data('uid');
2393 const value = Number($(this).val());2417 const value = Number($(this).val());
2394 data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;2418 data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
2395 setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);2419 setWIOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
2396 await saveWorldInfo(name, data);2420 await saveWorldInfo(name, data);
2397 });2421 });
23982422
@@ -2441,7 +2465,7 @@ function getWorldEntry(name, data, entry) {
2441 }2465 }
2442 }2466 }
24432467
2444 setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);2468 setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2445 await saveWorldInfo(name, data);2469 await saveWorldInfo(name, data);
2446 });2470 });
2447 characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');2471 characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');
@@ -2503,7 +2527,7 @@ function getWorldEntry(name, data, entry) {
2503 },2527 },
2504 );2528 );
2505 }2529 }
2506 setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);2530 setWIOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2507 await saveWorldInfo(name, data);2531 await saveWorldInfo(name, data);
2508 });2532 });
25092533
@@ -2517,7 +2541,7 @@ function getWorldEntry(name, data, entry) {
2517 !skipReset && resetScrollHeight(this);2541 !skipReset && resetScrollHeight(this);
2518 data.entries[uid].comment = value;2542 data.entries[uid].comment = value;
25192543
2520 setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);2544 setWIOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
2521 await saveWorldInfo(name, data);2545 await saveWorldInfo(name, data);
2522 });2546 });
2523 commentToggle.data('uid', entry.uid);2547 commentToggle.data('uid', entry.uid);
@@ -2552,7 +2576,7 @@ function getWorldEntry(name, data, entry) {
2552 const value = $(this).val();2576 const value = $(this).val();
2553 data.entries[uid].content = value;2577 data.entries[uid].content = value;
25542578
2555 setOriginalDataValue(data, uid, 'content', data.entries[uid].content);2579 setWIOriginalDataValue(data, uid, 'content', data.entries[uid].content);
2556 await saveWorldInfo(name, data);2580 await saveWorldInfo(name, data);
25572581
2558 if (skipCount) {2582 if (skipCount) {
@@ -2582,7 +2606,7 @@ function getWorldEntry(name, data, entry) {
2582 const value = $(this).prop('checked');2606 const value = $(this).prop('checked');
2583 data.entries[uid].selective = value;2607 data.entries[uid].selective = value;
25842608
2585 setOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);2609 setWIOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
2586 await saveWorldInfo(name, data);2610 await saveWorldInfo(name, data);
25872611
2588 const keysecondary = $(this)2612 const keysecondary = $(this)
@@ -2631,7 +2655,7 @@ function getWorldEntry(name, data, entry) {
26312655
2632 data.entries[uid].order = !isNaN(value) ? value : 0;2656 data.entries[uid].order = !isNaN(value) ? value : 0;
2633 updatePosOrdDisplay(uid);2657 updatePosOrdDisplay(uid);
2634 setOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);2658 setWIOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
2635 await saveWorldInfo(name, data);2659 await saveWorldInfo(name, data);
2636 });2660 });
2637 orderInput.val(entry.order).trigger('input');2661 orderInput.val(entry.order).trigger('input');
@@ -2645,7 +2669,7 @@ function getWorldEntry(name, data, entry) {
2645 const value = String($(this).val()).trim();2669 const value = String($(this).val()).trim();
26462670
2647 data.entries[uid].group = value;2671 data.entries[uid].group = value;
2648 setOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);2672 setWIOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
2649 await saveWorldInfo(name, data);2673 await saveWorldInfo(name, data);
2650 });2674 });
2651 groupInput.val(entry.group ?? '').trigger('input');2675 groupInput.val(entry.group ?? '').trigger('input');
@@ -2658,7 +2682,7 @@ function getWorldEntry(name, data, entry) {
2658 const uid = $(this).data('uid');2682 const uid = $(this).data('uid');
2659 const value = $(this).prop('checked');2683 const value = $(this).prop('checked');
2660 data.entries[uid].groupOverride = value;2684 data.entries[uid].groupOverride = value;
2661 setOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);2685 setWIOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
2662 await saveWorldInfo(name, data);2686 await saveWorldInfo(name, data);
2663 });2687 });
2664 groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');2688 groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
@@ -2682,7 +2706,7 @@ function getWorldEntry(name, data, entry) {
2682 }2706 }
26832707
2684 data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;2708 data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
2685 setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);2709 setWIOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
2686 await saveWorldInfo(name, data);2710 await saveWorldInfo(name, data);
2687 });2711 });
2688 groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');2712 groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
@@ -2695,7 +2719,7 @@ function getWorldEntry(name, data, entry) {
2695 const value = Number($(this).val());2719 const value = Number($(this).val());
2696 data.entries[uid].sticky = !isNaN(value) ? value : null;2720 data.entries[uid].sticky = !isNaN(value) ? value : null;
26972721
2698 setOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);2722 setWIOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);
2699 await saveWorldInfo(name, data);2723 await saveWorldInfo(name, data);
2700 });2724 });
2701 sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');2725 sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');
@@ -2708,7 +2732,7 @@ function getWorldEntry(name, data, entry) {
2708 const value = Number($(this).val());2732 const value = Number($(this).val());
2709 data.entries[uid].cooldown = !isNaN(value) ? value : null;2733 data.entries[uid].cooldown = !isNaN(value) ? value : null;
27102734
2711 setOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);2735 setWIOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);
2712 await saveWorldInfo(name, data);2736 await saveWorldInfo(name, data);
2713 });2737 });
2714 cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');2738 cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');
@@ -2721,7 +2745,7 @@ function getWorldEntry(name, data, entry) {
2721 const value = Number($(this).val());2745 const value = Number($(this).val());
2722 data.entries[uid].delay = !isNaN(value) ? value : null;2746 data.entries[uid].delay = !isNaN(value) ? value : null;
27232747
2724 setOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);2748 setWIOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);
2725 await saveWorldInfo(name, data);2749 await saveWorldInfo(name, data);
2726 });2750 });
2727 delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');2751 delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');
@@ -2741,7 +2765,7 @@ function getWorldEntry(name, data, entry) {
27412765
2742 data.entries[uid].depth = !isNaN(value) ? value : 0;2766 data.entries[uid].depth = !isNaN(value) ? value : 0;
2743 updatePosOrdDisplay(uid);2767 updatePosOrdDisplay(uid);
2744 setOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);2768 setWIOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);
2745 await saveWorldInfo(name, data);2769 await saveWorldInfo(name, data);
2746 });2770 });
2747 depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');2771 depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');
@@ -2769,7 +2793,7 @@ function getWorldEntry(name, data, entry) {
2769 }2793 }
2770 }2794 }
27712795
2772 setOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);2796 setWIOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
2773 await saveWorldInfo(name, data);2797 await saveWorldInfo(name, data);
2774 });2798 });
2775 probabilityInput.val(entry.probability).trigger('input');2799 probabilityInput.val(entry.probability).trigger('input');
@@ -2836,10 +2860,10 @@ function getWorldEntry(name, data, entry) {
2836 }2860 }
2837 updatePosOrdDisplay(uid);2861 updatePosOrdDisplay(uid);
2838 // Spec v2 only supports before_char and after_char2862 // Spec v2 only supports before_char and after_char
2839 setOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');2863 setWIOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
2840 // Write the original value as extensions field2864 // Write the original value as extensions field
2841 setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);2865 setWIOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
2842 setOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);2866 setWIOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
2843 await saveWorldInfo(name, data);2867 await saveWorldInfo(name, data);
2844 });2868 });
28452869
@@ -2881,36 +2905,36 @@ function getWorldEntry(name, data, entry) {
2881 data.entries[uid].constant = true;2905 data.entries[uid].constant = true;
2882 data.entries[uid].disable = false;2906 data.entries[uid].disable = false;
2883 data.entries[uid].vectorized = false;2907 data.entries[uid].vectorized = false;
2884 setOriginalDataValue(data, uid, 'enabled', true);2908 setWIOriginalDataValue(data, uid, 'enabled', true);
2885 setOriginalDataValue(data, uid, 'constant', true);2909 setWIOriginalDataValue(data, uid, 'constant', true);
2886 setOriginalDataValue(data, uid, 'extensions.vectorized', false);2910 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
2887 template.removeClass('disabledWIEntry');2911 template.removeClass('disabledWIEntry');
2888 break;2912 break;
2889 case 'normal':2913 case 'normal':
2890 data.entries[uid].constant = false;2914 data.entries[uid].constant = false;
2891 data.entries[uid].disable = false;2915 data.entries[uid].disable = false;
2892 data.entries[uid].vectorized = false;2916 data.entries[uid].vectorized = false;
2893 setOriginalDataValue(data, uid, 'enabled', true);2917 setWIOriginalDataValue(data, uid, 'enabled', true);
2894 setOriginalDataValue(data, uid, 'constant', false);2918 setWIOriginalDataValue(data, uid, 'constant', false);
2895 setOriginalDataValue(data, uid, 'extensions.vectorized', false);2919 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
2896 template.removeClass('disabledWIEntry');2920 template.removeClass('disabledWIEntry');
2897 break;2921 break;
2898 case 'vectorized':2922 case 'vectorized':
2899 data.entries[uid].constant = false;2923 data.entries[uid].constant = false;
2900 data.entries[uid].disable = false;2924 data.entries[uid].disable = false;
2901 data.entries[uid].vectorized = true;2925 data.entries[uid].vectorized = true;
2902 setOriginalDataValue(data, uid, 'enabled', true);2926 setWIOriginalDataValue(data, uid, 'enabled', true);
2903 setOriginalDataValue(data, uid, 'constant', false);2927 setWIOriginalDataValue(data, uid, 'constant', false);
2904 setOriginalDataValue(data, uid, 'extensions.vectorized', true);2928 setWIOriginalDataValue(data, uid, 'extensions.vectorized', true);
2905 template.removeClass('disabledWIEntry');2929 template.removeClass('disabledWIEntry');
2906 break;2930 break;
2907 case 'disabled':2931 case 'disabled':
2908 data.entries[uid].constant = false;2932 data.entries[uid].constant = false;
2909 data.entries[uid].disable = true;2933 data.entries[uid].disable = true;
2910 data.entries[uid].vectorized = false;2934 data.entries[uid].vectorized = false;
2911 setOriginalDataValue(data, uid, 'enabled', false);2935 setWIOriginalDataValue(data, uid, 'enabled', false);
2912 setOriginalDataValue(data, uid, 'constant', false);2936 setWIOriginalDataValue(data, uid, 'constant', false);
2913 setOriginalDataValue(data, uid, 'extensions.vectorized', false);2937 setWIOriginalDataValue(data, uid, 'extensions.vectorized', false);
2914 template.addClass('disabledWIEntry');2938 template.addClass('disabledWIEntry');
2915 break;2939 break;
2916 }2940 }
@@ -2941,7 +2965,7 @@ function getWorldEntry(name, data, entry) {
2941 const uid = $(this).data('uid');2965 const uid = $(this).data('uid');
2942 const value = $(this).prop('checked');2966 const value = $(this).prop('checked');
2943 data.entries[uid].excludeRecursion = value;2967 data.entries[uid].excludeRecursion = value;
2944 setOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);2968 setWIOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);
2945 await saveWorldInfo(name, data);2969 await saveWorldInfo(name, data);
2946 });2970 });
2947 excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');2971 excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');
@@ -2953,7 +2977,7 @@ function getWorldEntry(name, data, entry) {
2953 const uid = $(this).data('uid');2977 const uid = $(this).data('uid');
2954 const value = $(this).prop('checked');2978 const value = $(this).prop('checked');
2955 data.entries[uid].preventRecursion = value;2979 data.entries[uid].preventRecursion = value;
2956 setOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);2980 setWIOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);
2957 await saveWorldInfo(name, data);2981 await saveWorldInfo(name, data);
2958 });2982 });
2959 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');2983 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
@@ -2965,7 +2989,7 @@ function getWorldEntry(name, data, entry) {
2965 const uid = $(this).data('uid');2989 const uid = $(this).data('uid');
2966 const value = $(this).prop('checked');2990 const value = $(this).prop('checked');
2967 data.entries[uid].delayUntilRecursion = value;2991 data.entries[uid].delayUntilRecursion = value;
2968 setOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);2992 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
2969 await saveWorldInfo(name, data);2993 await saveWorldInfo(name, data);
2970 });2994 });
2971 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');2995 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
@@ -2985,10 +3009,12 @@ function getWorldEntry(name, data, entry) {
2985 // delete button3009 // delete button
2986 const deleteButton = template.find('.delete_entry_button');3010 const deleteButton = template.find('.delete_entry_button');
2987 deleteButton.data('uid', entry.uid);3011 deleteButton.data('uid', entry.uid);
2988 deleteButton.on('click', async function () {3012 deleteButton.on('click', async function (e) {
3013 e.stopPropagation();
2989 const uid = $(this).data('uid');3014 const uid = $(this).data('uid');
2990 deleteWorldInfoEntry(data, uid);3015 const deleted = await deleteWorldInfoEntry(data, uid);
2991 deleteOriginalDataValue(data, uid);3016 if (!deleted) return;
3017 deleteWIOriginalDataValue(data, uid);
2992 await saveWorldInfo(name, data);3018 await saveWorldInfo(name, data);
2993 updateEditor(navigation_option.previous);3019 updateEditor(navigation_option.previous);
2994 });3020 });
@@ -3015,7 +3041,7 @@ function getWorldEntry(name, data, entry) {
3015 }3041 }
30163042
3017 data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;3043 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);3044 setWIOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
3019 await saveWorldInfo(name, data);3045 await saveWorldInfo(name, data);
3020 });3046 });
3021 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');3047 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
@@ -3028,7 +3054,7 @@ function getWorldEntry(name, data, entry) {
3028 const value = $(this).val();3054 const value = $(this).val();
30293055
3030 data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';3056 data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
3031 setOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);3057 setWIOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
3032 await saveWorldInfo(name, data);3058 await saveWorldInfo(name, data);
3033 });3059 });
3034 caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');3060 caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');
@@ -3041,7 +3067,7 @@ function getWorldEntry(name, data, entry) {
3041 const value = $(this).val();3067 const value = $(this).val();
30423068
3043 data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';3069 data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
3044 setOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);3070 setWIOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
3045 await saveWorldInfo(name, data);3071 await saveWorldInfo(name, data);
3046 });3072 });
3047 matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');3073 matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
@@ -3054,7 +3080,7 @@ function getWorldEntry(name, data, entry) {
3054 const value = $(this).val();3080 const value = $(this).val();
30553081
3056 data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';3082 data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
3057 setOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);3083 setWIOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
3058 await saveWorldInfo(name, data);3084 await saveWorldInfo(name, data);
3059 });3085 });
3060 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');3086 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
@@ -3067,7 +3093,7 @@ function getWorldEntry(name, data, entry) {
3067 const value = $(this).val();3093 const value = $(this).val();
30683094
3069 data.entries[uid].automationId = value;3095 data.entries[uid].automationId = value;
3070 setOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);3096 setWIOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
3071 await saveWorldInfo(name, data);3097 await saveWorldInfo(name, data);
3072 });3098 });
3073 automationIdInput.val(entry.automationId ?? '').trigger('input');3099 automationIdInput.val(entry.automationId ?? '').trigger('input');
@@ -3200,12 +3226,12 @@ function createEntryInputAutocomplete(input, callback, { allowMultiple = false }
32003226
32013227
3202/**3228/**
3203 * Duplicated a WI entry by copying all of its properties and assigning a new uid3229 * Duplicate a WI entry by copying all of its properties and assigning a new uid
3204 * @param {*} data - The data of the book3230 * @param {*} data - The data of the book
3205 * @param {number} uid - The uid of the entry to copy in this book3231 * @param {number} uid - The uid of the entry to copy in this book
3206 * @returns {*} The new WI duplicated entry3232 * @returns {*} The new WI duplicated entry
3207 */3233 */
3208function duplicateWorldInfoEntry(data, uid) {3234export function duplicateWorldInfoEntry(data, uid) {
3209 if (!data || !('entries' in data) || !data.entries[uid]) {3235 if (!data || !('entries' in data) || !data.entries[uid]) {
3210 return;3236 return;
3211 }3237 }
@@ -3225,17 +3251,22 @@ function duplicateWorldInfoEntry(data, uid) {
3225 * Deletes a WI entry, with a user confirmation dialog3251 * Deletes a WI entry, with a user confirmation dialog
3226 * @param {*[]} data - The data of the book3252 * @param {*[]} data - The data of the book
3227 * @param {number} uid - The uid of the entry to copy in this book3253 * @param {number} uid - The uid of the entry to copy in this book
3254 * @param {object} [options={}] - Optional arguments
3255 * @param {boolean} [options.silent=false] - Whether to prompt the user for deletion or just do it
3256 * @returns {Promise<boolean>} Whether the entry deletion was successful
3228 */3257 */
3229function deleteWorldInfoEntry(data, uid) {3258export async function deleteWorldInfoEntry(data, uid, { silent = false } = {}) {
3230 if (!data || !('entries' in data)) {3259 if (!data || !('entries' in data)) {
3231 return;3260 return;
3232 }3261 }
32333262
3234 if (!confirm(`Delete the entry with UID: ${uid}? This action is irreversible!`)) {3263 const confirmation = silent || await Popup.show.confirm(`Delete the entry with UID: ${uid}?`, 'This action is irreversible!');
3235 throw new Error('User cancelled deletion');3264 if (!confirmation) {
3265 return false;
3236 }3266 }
32373267
3238 delete data.entries[uid];3268 delete data.entries[uid];
3269 return true;
3239}3270}
32403271
3241/**3272/**
@@ -3245,7 +3276,7 @@ function deleteWorldInfoEntry(data, uid) {
3245 *3276 *
3246 * @type {{[key: string]: { default: any, type: string }}}3277 * @type {{[key: string]: { default: any, type: string }}}
3247 */3278 */
3248const newEntryDefinition = {3279export const newWorldInfoEntryDefinition = {
3249 key: { default: [], type: 'array' },3280 key: { default: [], type: 'array' },
3250 keysecondary: { default: [], type: 'array' },3281 keysecondary: { default: [], type: 'array' },
3251 comment: { default: '', type: 'string' },3282 comment: { default: '', type: 'string' },
@@ -3278,8 +3309,8 @@ const newEntryDefinition = {
3278 delay: { default: null, type: 'number?' },3309 delay: { default: null, type: 'number?' },
3279};3310};
32803311
3281const newEntryTemplate = Object.fromEntries(3312export const newWorldInfoEntryTemplate = Object.fromEntries(
3282 Object.entries(newEntryDefinition).map(([key, value]) => [key, value.default]),3313 Object.entries(newWorldInfoEntryDefinition).map(([key, value]) => [key, value.default]),
3283);3314);
32843315
3285/**3316/**
@@ -3288,7 +3319,7 @@ const newEntryTemplate = Object.fromEntries(
3288 * @param {any} data WI data3319 * @param {any} data WI data
3289 * @returns {object | undefined} New entry object or undefined if failed3320 * @returns {object | undefined} New entry object or undefined if failed
3290 */3321 */
3291function createWorldInfoEntry(_name, data) {3322export function createWorldInfoEntry(_name, data) {
3292 const newUid = getFreeWorldEntryUid(data);3323 const newUid = getFreeWorldEntryUid(data);
32933324
3294 if (!Number.isInteger(newUid)) {3325 if (!Number.isInteger(newUid)) {
@@ -3296,7 +3327,7 @@ function createWorldInfoEntry(_name, data) {
3296 return;3327 return;
3297 }3328 }
32983329
3299 const newEntry = { uid: newUid, ...structuredClone(newEntryTemplate) };3330 const newEntry = { uid: newUid, ...structuredClone(newWorldInfoEntryTemplate) };
3300 data.entries[newUid] = newEntry;3331 data.entries[newUid] = newEntry;
33013332
3302 return newEntry;3333 return newEntry;
@@ -3314,7 +3345,21 @@ async function _save(name, data) {
3314 eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);3345 eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);
3315}3346}
33163347
3317async function saveWorldInfo(name, data, immediately = false) {3348
3349/**
3350 * Saves the world info
3351 *
3352 * This will also refresh the `worldInfoCache`.
3353 * Note, for performance reasons the saved cache will not make a deep clone of the data.
3354 * It is your responsibility to not modify the saved data object after calling this function, or there will be data inconsistencies.
3355 * Call `loadWorldInfoData` or query directly from cache if you need the object again.
3356 *
3357 * @param {string} name - The name of the world info
3358 * @param {any} data - The data to be saved
3359 * @param {boolean} [immediately=false] - Whether to save immediately or use debouncing
3360 * @return {Promise<void>} A promise that resolves when the world info is saved
3361 */
3362export async function saveWorldInfo(name, data, immediately = false) {
3318 if (!name || !data) {3363 if (!name || !data) {
3319 return;3364 return;
3320 }3365 }
@@ -3371,7 +3416,7 @@ async function renameWorldInfo(name, data) {
3371 * @param {string} worldInfoName - The name of the world info to delete3416 * @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 otherwise3417 * @returns {Promise<boolean>} A promise that resolves to true if the world info was successfully deleted, false otherwise
3373 */3418 */
3374async function deleteWorldInfo(worldInfoName) {3419export async function deleteWorldInfo(worldInfoName) {
3375 if (!world_names.includes(worldInfoName)) {3420 if (!world_names.includes(worldInfoName)) {
3376 return false;3421 return false;
3377 }3422 }
@@ -3406,7 +3451,7 @@ async function deleteWorldInfo(worldInfoName) {
3406 return true;3451 return true;
3407}3452}
34083453
3409function getFreeWorldEntryUid(data) {3454export function getFreeWorldEntryUid(data) {
3410 if (!data || !('entries' in data)) {3455 if (!data || !('entries' in data)) {
3411 return null;3456 return null;
3412 }3457 }
@@ -3422,7 +3467,7 @@ function getFreeWorldEntryUid(data) {
3422 return null;3467 return null;
3423}3468}
34243469
3425function getFreeWorldName() {3470export function getFreeWorldName() {
3426 const MAX_FREE_NAME = 100_000;3471 const MAX_FREE_NAME = 100_000;
3427 for (let index = 1; index < MAX_FREE_NAME; index++) {3472 for (let index = 1; index < MAX_FREE_NAME; index++) {
3428 const newName = `New World (${index})`;3473 const newName = `New World (${index})`;
@@ -3444,7 +3489,7 @@ function getFreeWorldName() {
3444 * @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when overwriting an existing world3489 * @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 otherwise3490 * @returns {Promise<boolean>} - True if the world info was successfully created, false otherwise
3446 */3491 */
3447async function createNewWorldInfo(worldName, { interactive = false } = {}) {3492export async function createNewWorldInfo(worldName, { interactive = false } = {}) {
3448 const worldInfoTemplate = { entries: {} };3493 const worldInfoTemplate = { entries: {} };
34493494
3450 if (!worldName) {3495 if (!worldName) {
@@ -3505,7 +3550,7 @@ async function getCharacterLore() {
3505 continue;3550 continue;
3506 }3551 }
35073552
3508 const data = await loadWorldInfoData(worldName);3553 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 })) : [];3554 const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];
3510 entries = entries.concat(newEntries);3555 entries = entries.concat(newEntries);
35113556
@@ -3525,7 +3570,7 @@ async function getGlobalLore() {
35253570
3526 let entries = [];3571 let entries = [];
3527 for (const worldName of selected_world_info) {3572 for (const worldName of selected_world_info) {
3528 const data = await loadWorldInfoData(worldName);3573 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 })) : [];3574 const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: worldName, ...rest })) : [];
3530 entries = entries.concat(newEntries);3575 entries = entries.concat(newEntries);
3531 }3576 }
@@ -3547,7 +3592,7 @@ async function getChatLore() {
3547 return [];3592 return [];
3548 }3593 }
35493594
3550 const data = await loadWorldInfoData(chatWorld);3595 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 })) : [];3596 const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(({ uid, ...rest }) => ({ uid, world: chatWorld, ...rest })) : [];
35523597
3553 console.debug(`[WI] Chat lore has ${entries.length} entries`, [chatWorld]);3598 console.debug(`[WI] Chat lore has ${entries.length} entries`, [chatWorld]);
@@ -3663,7 +3708,7 @@ function parseDecorators(content) {
3663 * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated3708 * @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
3664 * @returns {Promise<WIActivated>} The world info activated.3709 * @returns {Promise<WIActivated>} The world info activated.
3665 */3710 */
3666async function checkWorldInfo(chat, maxContext, isDryRun) {3711export async function checkWorldInfo(chat, maxContext, isDryRun) {
3667 const context = getContext();3712 const context = getContext();
3668 const buffer = new WorldInfoBuffer(chat);3713 const buffer = new WorldInfoBuffer(chat);
36693714
@@ -4233,7 +4278,7 @@ function convertAgnaiMemoryBook(inputObj) {
42334278
4234 inputObj.entries.forEach((entry, index) => {4279 inputObj.entries.forEach((entry, index) => {
4235 outputObj.entries[index] = {4280 outputObj.entries[index] = {
4236 ...newEntryTemplate,4281 ...newWorldInfoEntryTemplate,
4237 uid: index,4282 uid: index,
4238 key: entry.keywords,4283 key: entry.keywords,
4239 keysecondary: [],4284 keysecondary: [],
@@ -4275,7 +4320,7 @@ function convertRisuLorebook(inputObj) {
42754320
4276 inputObj.data.forEach((entry, index) => {4321 inputObj.data.forEach((entry, index) => {
4277 outputObj.entries[index] = {4322 outputObj.entries[index] = {
4278 ...newEntryTemplate,4323 ...newWorldInfoEntryTemplate,
4279 uid: index,4324 uid: index,
4280 key: entry.key.split(',').map(x => x.trim()),4325 key: entry.key.split(',').map(x => x.trim()),
4281 keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [],4326 keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [],
@@ -4322,7 +4367,7 @@ function convertNovelLorebook(inputObj) {
4322 const addMemo = displayName !== undefined && displayName.trim() !== '';4367 const addMemo = displayName !== undefined && displayName.trim() !== '';
43234368
4324 outputObj.entries[index] = {4369 outputObj.entries[index] = {
4325 ...newEntryTemplate,4370 ...newWorldInfoEntryTemplate,
4326 uid: index,4371 uid: index,
4327 key: entry.keys,4372 key: entry.keys,
4328 keysecondary: [],4373 keysecondary: [],
@@ -4369,7 +4414,7 @@ function convertCharacterBook(characterBook) {
4369 }4414 }
43704415
4371 result.entries[entry.id] = {4416 result.entries[entry.id] = {
4372 ...newEntryTemplate,4417 ...newWorldInfoEntryTemplate,
4373 uid: entry.id,4418 uid: entry.id,
4374 key: entry.keys,4419 key: entry.keys,
4375 keysecondary: entry.secondary_keys || [],4420 keysecondary: entry.secondary_keys || [],
@@ -4499,7 +4544,7 @@ export async function importEmbeddedWorldInfo(skipPopup = false) {
4499 setWorldInfoButtonClass(chid, true);4544 setWorldInfoButtonClass(chid, true);
4500}4545}
45014546
4502function onWorldInfoChange(args, text) {4547export function onWorldInfoChange(args, text) {
4503 if (args !== '__notSlashCommand__') { // if it's a slash command4548 if (args !== '__notSlashCommand__') { // if it's a slash command
4504 const silent = isTrueBoolean(args.silent);4549 const silent = isTrueBoolean(args.silent);
4505 if (text.trim() !== '') { // and args are provided4550 if (text.trim() !== '') { // and args are provided
@@ -4650,7 +4695,7 @@ export async function importWorldInfo(file) {
4650 });4695 });
4651}4696}
46524697
4653function assignLorebookToChat() {4698export function assignLorebookToChat() {
4654 const selectedName = chat_metadata[METADATA_KEY];4699 const selectedName = chat_metadata[METADATA_KEY];
4655 const template = $('#chat_world_template .chat_world').clone();4700 const template = $('#chat_world_template .chat_world').clone();
46564701