Merge pull request #2542 from SillyTavern/wi-slash-commands-performance-improvements World Info: slash commands performance improvements

256f0a58db755de66f10f37ae9dd13874d538e96

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

Signed
3 files changed, +168 -79Ignore whitespace
public/scripts/util/StructuredCloneMap.js+56 -0
@@ -0,0 +1,56 @@
1/**
2 * A specialized Map class that provides consistent data storage by performing deep cloning of values.
3 *
4 * @template K, V
5 * @extends Map<K, V>
6 */
7export class StructuredCloneMap extends Map {
8 /**
9 * Constructs a new StructuredCloneMap.
10 * @param {object} options - Options for the map
11 * @param {boolean} options.cloneOnGet - Whether to clone the value when getting it from the map
12 * @param {boolean} options.cloneOnSet - Whether to clone the value when setting it in the map
13 */
14 constructor({ cloneOnGet, cloneOnSet } = { cloneOnGet: true, cloneOnSet: true }) {
15 super();
16 this.cloneOnGet = cloneOnGet;
17 this.cloneOnSet = cloneOnSet;
18 }
19
20 /**
21 * Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
22 *
23 * The set value will always be a deep clone of the provided value to provide consistent data storage.
24 *
25 * @param {K} key - The key to set
26 * @param {V} value - The value to set
27 * @returns {this} The updated map
28 */
29 set(key, value) {
30 if (!this.cloneOnSet) {
31 return super.set(key, value);
32 }
33
34 const clonedValue = structuredClone(value);
35 super.set(key, clonedValue);
36 return this;
37 }
38
39 /**
40 * Returns a specified element from the Map object.
41 * If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.
42 *
43 * The returned value will always be a deep clone of the cached value.
44 *
45 * @param {K} key - The key to get the value for
46 * @returns {V | undefined} Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
47 */
48 get(key) {
49 if (!this.cloneOnGet) {
50 return super.get(key);
51 }
52
53 const value = super.get(key);
54 return structuredClone(value);
55 }
56}
public/scripts/utils.js+24 -1
@@ -271,6 +271,13 @@ export function getStringHash(str, seed = 0) {
271}271}
272272
273/**273/**
274 * Map of debounced functions to their timers.
275 * Weak map is used to avoid memory leaks.
276 * @type {WeakMap<function, any>}
277 */
278const debounceMap = new WeakMap();
279
280/**
274 * Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.281 * Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.
275 * @param {function} func The function to debounce.282 * @param {function} func The function to debounce.
276 * @param {debounce_timeout|number} [timeout=debounce_timeout.default] The timeout based on the common enum values, or in milliseconds.283 * @param {debounce_timeout|number} [timeout=debounce_timeout.default] The timeout based on the common enum values, or in milliseconds.
@@ -278,10 +285,26 @@ export function getStringHash(str, seed = 0) {
278 */285 */
279export function debounce(func, timeout = debounce_timeout.standard) {286export function debounce(func, timeout = debounce_timeout.standard) {
280 let timer;287 let timer;
281 return (...args) => {288 let fn = (...args) => {
282 clearTimeout(timer);289 clearTimeout(timer);
283 timer = setTimeout(() => { func.apply(this, args); }, timeout);290 timer = setTimeout(() => { func.apply(this, args); }, timeout);
291 debounceMap.set(func, timer);
292 debounceMap.set(fn, timer);
284 };293 };
294
295 return fn;
296}
297
298/**
299 * Cancels a scheduled debounced function.
300 * Does nothing if the function is not debounced or not scheduled.
301 * @param {function} func The function to cancel. Either the original or the debounced function.
302 */
303export function cancelDebounce(func) {
304 if (debounceMap.has(func)) {
305 clearTimeout(debounceMap.get(func));
306 debounceMap.delete(func);
307 }
285}308}
286309
287/**310/**
public/scripts/world-info.js+88 -78
@@ -1,5 +1,5 @@
1import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';1import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
2import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray } from './utils.js';2import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce } from './utils.js';
3import { extension_settings, getContext } from './extensions.js';3import { extension_settings, getContext } from './extensions.js';
4import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';4import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
5import { isMobile } from './RossAscends-mods.js';5import { isMobile } from './RossAscends-mods.js';
@@ -16,6 +16,7 @@ import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandE
16import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';16import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
17import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';17import { 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';
1920
20export {21export {
21 world_info,22 world_info,
@@ -746,7 +747,8 @@ export const wi_anchor_position = {
746 after: 1,747 after: 1,
747};748};
748749
749const worldInfoCache = new Map();750/** @type {StructuredCloneMap<string,object>} */
751const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: false });
750752
751/**753/**
752 * Gets the world info based on chat messages.754 * Gets the world info based on chat messages.
@@ -885,9 +887,15 @@ function setWorldInfoSettings(settings, data) {
885}887}
886888
887function registerWorldInfoSlashCommands() {889function registerWorldInfoSlashCommands() {
888 function reloadEditor(file) {890 /**
891 * Reloads the editor with the specified world info file
892 * @param {string} file - The file to load in the editor
893 * @param {boolean} [loadIfNotSelected=false] - Indicates whether to load the file even if it's not currently selected
894 */
895 function reloadEditor(file, loadIfNotSelected = false) {
896 const currentIndex = $('#world_editor_select').val();
889 const selectedIndex = world_names.indexOf(file);897 const selectedIndex = world_names.indexOf(file);
890 if (selectedIndex !== -1) {898 if (selectedIndex !== -1 && (loadIfNotSelected || currentIndex === selectedIndex)) {
891 $('#world_editor_select').val(selectedIndex).trigger('change');899 $('#world_editor_select').val(selectedIndex).trigger('change');
892 }900 }
893 }901 }
@@ -1049,7 +1057,7 @@ function registerWorldInfoSlashCommands() {
1049 entry.content = content;1057 entry.content = content;
1050 }1058 }
10511059
1052 await saveWorldInfo(file, data, true);1060 await saveWorldInfo(file, data);
1053 reloadEditor(file);1061 reloadEditor(file);
10541062
1055 return String(entry.uid);1063 return String(entry.uid);
@@ -1100,7 +1108,7 @@ function registerWorldInfoSlashCommands() {
1100 setOriginalDataValue(data, uid, originalDataKeyMap[field], entry[field]);1108 setOriginalDataValue(data, uid, originalDataKeyMap[field], entry[field]);
1101 }1109 }
11021110
1103 await saveWorldInfo(file, data, true);1111 await saveWorldInfo(file, data);
1104 reloadEditor(file);1112 reloadEditor(file);
1105 return '';1113 return '';
1106 }1114 }
@@ -1840,7 +1848,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
1840 nextText: '>',1848 nextText: '>',
1841 formatNavigator: PAGINATION_TEMPLATE,1849 formatNavigator: PAGINATION_TEMPLATE,
1842 showNavigator: true,1850 showNavigator: true,
1843 callback: function (/** @type {object[]} */ page) {1851 callback: async function (/** @type {object[]} */ page) {
1844 // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements1852 // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
1845 // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it1853 // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
1846 worldEntriesList.find('*').off();1854 worldEntriesList.find('*').off();
@@ -1926,7 +1934,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
19261934
1927 if (counter > 0) {1935 if (counter > 0) {
1928 toastr.info(`Backfilled ${counter} titles`);1936 toastr.info(`Backfilled ${counter} titles`);
1929 await saveWorldInfo(name, data, true);1937 await saveWorldInfo(name, data);
1930 updateEditor(navigation_option.previous);1938 updateEditor(navigation_option.previous);
1931 }1939 }
1932 });1940 });
@@ -2026,7 +2034,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
20262034
2027 console.table(Object.keys(data.entries).map(uid => data.entries[uid]).map(x => ({ uid: x.uid, key: x.key.join(','), displayIndex: x.displayIndex })));2035 console.table(Object.keys(data.entries).map(uid => data.entries[uid]).map(x => ({ uid: x.uid, key: x.key.join(','), displayIndex: x.displayIndex })));
20282036
2029 await saveWorldInfo(name, data, true);2037 await saveWorldInfo(name, data);
2030 },2038 },
2031 });2039 });
2032 //$("#world_popup_entries_list").disableSelection();2040 //$("#world_popup_entries_list").disableSelection();
@@ -2298,7 +2306,7 @@ function getWorldEntry(name, data, entry) {
2298 templateResult: item => templateStyling(item, { searchStyle: true }),2306 templateResult: item => templateStyling(item, { searchStyle: true }),
2299 templateSelection: item => templateStyling(item),2307 templateSelection: item => templateStyling(item),
2300 });2308 });
2301 input.on('change', function (_, { skipReset, noSave } = {}) {2309 input.on('change', async function (_, { skipReset, noSave } = {}) {
2302 const uid = $(this).data('uid');2310 const uid = $(this).data('uid');
2303 /** @type {string[]} */2311 /** @type {string[]} */
2304 const keys = ($(this).select2('data')).map(x => x.text);2312 const keys = ($(this).select2('data')).map(x => x.text);
@@ -2307,7 +2315,7 @@ function getWorldEntry(name, data, entry) {
2307 if (!noSave) {2315 if (!noSave) {
2308 data.entries[uid][entryPropName] = keys;2316 data.entries[uid][entryPropName] = keys;
2309 setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);2317 setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2310 saveWorldInfo(name, data);2318 await saveWorldInfo(name, data);
2311 }2319 }
2312 });2320 });
2313 input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data]));2321 input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data]));
@@ -2338,14 +2346,14 @@ function getWorldEntry(name, data, entry) {
2338 template.find(`select[name="${entryPropName}"]`).hide();2346 template.find(`select[name="${entryPropName}"]`).hide();
2339 input.show();2347 input.show();
23402348
2341 input.on('input', function (_, { skipReset, noSave } = {}) {2349 input.on('input', async function (_, { skipReset, noSave } = {}) {
2342 const uid = $(this).data('uid');2350 const uid = $(this).data('uid');
2343 const value = String($(this).val());2351 const value = String($(this).val());
2344 !skipReset && resetScrollHeight(this);2352 !skipReset && resetScrollHeight(this);
2345 if (!noSave) {2353 if (!noSave) {
2346 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);2354 data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
2347 setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);2355 setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
2348 saveWorldInfo(name, data);2356 await saveWorldInfo(name, data);
2349 }2357 }
2350 });2358 });
2351 input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });2359 input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });
@@ -2384,12 +2392,12 @@ function getWorldEntry(name, data, entry) {
2384 event.stopPropagation();2392 event.stopPropagation();
2385 });2393 });
23862394
2387 selectiveLogicDropdown.on('input', function () {2395 selectiveLogicDropdown.on('input', async function () {
2388 const uid = $(this).data('uid');2396 const uid = $(this).data('uid');
2389 const value = Number($(this).val());2397 const value = Number($(this).val());
2390 data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;2398 data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
2391 setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);2399 setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
2392 saveWorldInfo(name, data);2400 await saveWorldInfo(name, data);
2393 });2401 });
23942402
2395 template2403 template
@@ -2404,7 +2412,7 @@ function getWorldEntry(name, data, entry) {
2404 // exclude characters checkbox2412 // exclude characters checkbox
2405 const characterExclusionInput = template.find('input[name="character_exclusion"]');2413 const characterExclusionInput = template.find('input[name="character_exclusion"]');
2406 characterExclusionInput.data('uid', entry.uid);2414 characterExclusionInput.data('uid', entry.uid);
2407 characterExclusionInput.on('input', function () {2415 characterExclusionInput.on('input', async function () {
2408 const uid = $(this).data('uid');2416 const uid = $(this).data('uid');
2409 const value = $(this).prop('checked');2417 const value = $(this).prop('checked');
2410 characterFilterLabel.text(value ? 'Exclude Character(s)' : 'Filter to Character(s)');2418 characterFilterLabel.text(value ? 'Exclude Character(s)' : 'Filter to Character(s)');
@@ -2438,7 +2446,7 @@ function getWorldEntry(name, data, entry) {
2438 }2446 }
24392447
2440 setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);2448 setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2441 saveWorldInfo(name, data);2449 await saveWorldInfo(name, data);
2442 });2450 });
2443 characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');2451 characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');
24442452
@@ -2500,24 +2508,24 @@ function getWorldEntry(name, data, entry) {
2500 );2508 );
2501 }2509 }
2502 setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);2510 setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
2503 saveWorldInfo(name, data);2511 await saveWorldInfo(name, data);
2504 });2512 });
25052513
2506 // comment2514 // comment
2507 const commentInput = template.find('textarea[name="comment"]');2515 const commentInput = template.find('textarea[name="comment"]');
2508 const commentToggle = template.find('input[name="addMemo"]');2516 const commentToggle = template.find('input[name="addMemo"]');
2509 commentInput.data('uid', entry.uid);2517 commentInput.data('uid', entry.uid);
2510 commentInput.on('input', function (_, { skipReset } = {}) {2518 commentInput.on('input', async function (_, { skipReset } = {}) {
2511 const uid = $(this).data('uid');2519 const uid = $(this).data('uid');
2512 const value = $(this).val();2520 const value = $(this).val();
2513 !skipReset && resetScrollHeight(this);2521 !skipReset && resetScrollHeight(this);
2514 data.entries[uid].comment = value;2522 data.entries[uid].comment = value;
25152523
2516 setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);2524 setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
2517 saveWorldInfo(name, data);2525 await saveWorldInfo(name, data);
2518 });2526 });
2519 commentToggle.data('uid', entry.uid);2527 commentToggle.data('uid', entry.uid);
2520 commentToggle.on('input', function () {2528 commentToggle.on('input', async function () {
2521 const uid = $(this).data('uid');2529 const uid = $(this).data('uid');
2522 const value = $(this).prop('checked');2530 const value = $(this).prop('checked');
2523 //console.log(value)2531 //console.log(value)
@@ -2525,7 +2533,7 @@ function getWorldEntry(name, data, entry) {
2525 .closest('.world_entry')2533 .closest('.world_entry')
2526 .find('.commentContainer');2534 .find('.commentContainer');
2527 data.entries[uid].addMemo = value;2535 data.entries[uid].addMemo = value;
2528 saveWorldInfo(name, data);2536 await saveWorldInfo(name, data);
2529 value ? commentContainer.show() : commentContainer.hide();2537 value ? commentContainer.show() : commentContainer.hide();
2530 });2538 });
25312539
@@ -2543,13 +2551,13 @@ function getWorldEntry(name, data, entry) {
25432551
2544 const contentInput = template.find('textarea[name="content"]');2552 const contentInput = template.find('textarea[name="content"]');
2545 contentInput.data('uid', entry.uid);2553 contentInput.data('uid', entry.uid);
2546 contentInput.on('input', function (_, { skipCount } = {}) {2554 contentInput.on('input', async function (_, { skipCount } = {}) {
2547 const uid = $(this).data('uid');2555 const uid = $(this).data('uid');
2548 const value = $(this).val();2556 const value = $(this).val();
2549 data.entries[uid].content = value;2557 data.entries[uid].content = value;
25502558
2551 setOriginalDataValue(data, uid, 'content', data.entries[uid].content);2559 setOriginalDataValue(data, uid, 'content', data.entries[uid].content);
2552 saveWorldInfo(name, data);2560 await saveWorldInfo(name, data);
25532561
2554 if (skipCount) {2562 if (skipCount) {
2555 return;2563 return;
@@ -2573,13 +2581,13 @@ function getWorldEntry(name, data, entry) {
2573 // selective2581 // selective
2574 const selectiveInput = template.find('input[name="selective"]');2582 const selectiveInput = template.find('input[name="selective"]');
2575 selectiveInput.data('uid', entry.uid);2583 selectiveInput.data('uid', entry.uid);
2576 selectiveInput.on('input', function () {2584 selectiveInput.on('input', async function () {
2577 const uid = $(this).data('uid');2585 const uid = $(this).data('uid');
2578 const value = $(this).prop('checked');2586 const value = $(this).prop('checked');
2579 data.entries[uid].selective = value;2587 data.entries[uid].selective = value;
25802588
2581 setOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);2589 setOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
2582 saveWorldInfo(name, data);2590 await saveWorldInfo(name, data);
25832591
2584 const keysecondary = $(this)2592 const keysecondary = $(this)
2585 .closest('.world_entry')2593 .closest('.world_entry')
@@ -2608,12 +2616,12 @@ function getWorldEntry(name, data, entry) {
2608 /*2616 /*
2609 const constantInput = template.find('input[name="constant"]');2617 const constantInput = template.find('input[name="constant"]');
2610 constantInput.data("uid", entry.uid);2618 constantInput.data("uid", entry.uid);
2611 constantInput.on("input", function () {2619 constantInput.on("input", async function () {
2612 const uid = $(this).data("uid");2620 const uid = $(this).data("uid");
2613 const value = $(this).prop("checked");2621 const value = $(this).prop("checked");
2614 data.entries[uid].constant = value;2622 data.entries[uid].constant = value;
2615 setOriginalDataValue(data, uid, "constant", data.entries[uid].constant);2623 setOriginalDataValue(data, uid, "constant", data.entries[uid].constant);
2616 saveWorldInfo(name, data);2624 await saveWorldInfo(name, data);
2617 });2625 });
2618 constantInput.prop("checked", entry.constant).trigger("input");2626 constantInput.prop("checked", entry.constant).trigger("input");
2619 */2627 */
@@ -2621,14 +2629,14 @@ function getWorldEntry(name, data, entry) {
2621 // order2629 // order
2622 const orderInput = template.find('input[name="order"]');2630 const orderInput = template.find('input[name="order"]');
2623 orderInput.data('uid', entry.uid);2631 orderInput.data('uid', entry.uid);
2624 orderInput.on('input', function () {2632 orderInput.on('input', async function () {
2625 const uid = $(this).data('uid');2633 const uid = $(this).data('uid');
2626 const value = Number($(this).val());2634 const value = Number($(this).val());
26272635
2628 data.entries[uid].order = !isNaN(value) ? value : 0;2636 data.entries[uid].order = !isNaN(value) ? value : 0;
2629 updatePosOrdDisplay(uid);2637 updatePosOrdDisplay(uid);
2630 setOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);2638 setOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
2631 saveWorldInfo(name, data);2639 await saveWorldInfo(name, data);
2632 });2640 });
2633 orderInput.val(entry.order).trigger('input');2641 orderInput.val(entry.order).trigger('input');
2634 orderInput.css('width', 'calc(3em + 15px)');2642 orderInput.css('width', 'calc(3em + 15px)');
@@ -2636,13 +2644,13 @@ function getWorldEntry(name, data, entry) {
2636 // group2644 // group
2637 const groupInput = template.find('input[name="group"]');2645 const groupInput = template.find('input[name="group"]');
2638 groupInput.data('uid', entry.uid);2646 groupInput.data('uid', entry.uid);
2639 groupInput.on('input', function () {2647 groupInput.on('input', async function () {
2640 const uid = $(this).data('uid');2648 const uid = $(this).data('uid');
2641 const value = String($(this).val()).trim();2649 const value = String($(this).val()).trim();
26422650
2643 data.entries[uid].group = value;2651 data.entries[uid].group = value;
2644 setOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);2652 setOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
2645 saveWorldInfo(name, data);2653 await saveWorldInfo(name, data);
2646 });2654 });
2647 groupInput.val(entry.group ?? '').trigger('input');2655 groupInput.val(entry.group ?? '').trigger('input');
2648 setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);2656 setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);
@@ -2650,19 +2658,19 @@ function getWorldEntry(name, data, entry) {
2650 // inclusion priority2658 // inclusion priority
2651 const groupOverrideInput = template.find('input[name="groupOverride"]');2659 const groupOverrideInput = template.find('input[name="groupOverride"]');
2652 groupOverrideInput.data('uid', entry.uid);2660 groupOverrideInput.data('uid', entry.uid);
2653 groupOverrideInput.on('input', function () {2661 groupOverrideInput.on('input', async function () {
2654 const uid = $(this).data('uid');2662 const uid = $(this).data('uid');
2655 const value = $(this).prop('checked');2663 const value = $(this).prop('checked');
2656 data.entries[uid].groupOverride = value;2664 data.entries[uid].groupOverride = value;
2657 setOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);2665 setOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
2658 saveWorldInfo(name, data);2666 await saveWorldInfo(name, data);
2659 });2667 });
2660 groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');2668 groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
26612669
2662 // group weight2670 // group weight
2663 const groupWeightInput = template.find('input[name="groupWeight"]');2671 const groupWeightInput = template.find('input[name="groupWeight"]');
2664 groupWeightInput.data('uid', entry.uid);2672 groupWeightInput.data('uid', entry.uid);
2665 groupWeightInput.on('input', function () {2673 groupWeightInput.on('input', async function () {
2666 const uid = $(this).data('uid');2674 const uid = $(this).data('uid');
2667 let value = Number($(this).val());2675 let value = Number($(this).val());
2668 const min = Number($(this).attr('min'));2676 const min = Number($(this).attr('min'));
@@ -2679,46 +2687,46 @@ function getWorldEntry(name, data, entry) {
26792687
2680 data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;2688 data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
2681 setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);2689 setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
2682 saveWorldInfo(name, data);2690 await saveWorldInfo(name, data);
2683 });2691 });
2684 groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');2692 groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
26852693
2686 // sticky2694 // sticky
2687 const sticky = template.find('input[name="sticky"]');2695 const sticky = template.find('input[name="sticky"]');
2688 sticky.data('uid', entry.uid);2696 sticky.data('uid', entry.uid);
2689 sticky.on('input', function () {2697 sticky.on('input', async function () {
2690 const uid = $(this).data('uid');2698 const uid = $(this).data('uid');
2691 const value = Number($(this).val());2699 const value = Number($(this).val());
2692 data.entries[uid].sticky = !isNaN(value) ? value : null;2700 data.entries[uid].sticky = !isNaN(value) ? value : null;
26932701
2694 setOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);2702 setOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);
2695 saveWorldInfo(name, data);2703 await saveWorldInfo(name, data);
2696 });2704 });
2697 sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');2705 sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');
26982706
2699 // cooldown2707 // cooldown
2700 const cooldown = template.find('input[name="cooldown"]');2708 const cooldown = template.find('input[name="cooldown"]');
2701 cooldown.data('uid', entry.uid);2709 cooldown.data('uid', entry.uid);
2702 cooldown.on('input', function () {2710 cooldown.on('input', async function () {
2703 const uid = $(this).data('uid');2711 const uid = $(this).data('uid');
2704 const value = Number($(this).val());2712 const value = Number($(this).val());
2705 data.entries[uid].cooldown = !isNaN(value) ? value : null;2713 data.entries[uid].cooldown = !isNaN(value) ? value : null;
27062714
2707 setOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);2715 setOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);
2708 saveWorldInfo(name, data);2716 await saveWorldInfo(name, data);
2709 });2717 });
2710 cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');2718 cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');
27112719
2712 // delay2720 // delay
2713 const delay = template.find('input[name="delay"]');2721 const delay = template.find('input[name="delay"]');
2714 delay.data('uid', entry.uid);2722 delay.data('uid', entry.uid);
2715 delay.on('input', function () {2723 delay.on('input', async function () {
2716 const uid = $(this).data('uid');2724 const uid = $(this).data('uid');
2717 const value = Number($(this).val());2725 const value = Number($(this).val());
2718 data.entries[uid].delay = !isNaN(value) ? value : null;2726 data.entries[uid].delay = !isNaN(value) ? value : null;
27192727
2720 setOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);2728 setOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);
2721 saveWorldInfo(name, data);2729 await saveWorldInfo(name, data);
2722 });2730 });
2723 delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');2731 delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');
27242732
@@ -2731,14 +2739,14 @@ function getWorldEntry(name, data, entry) {
2731 const depthInput = template.find('input[name="depth"]');2739 const depthInput = template.find('input[name="depth"]');
2732 depthInput.data('uid', entry.uid);2740 depthInput.data('uid', entry.uid);
27332741
2734 depthInput.on('input', function () {2742 depthInput.on('input', async function () {
2735 const uid = $(this).data('uid');2743 const uid = $(this).data('uid');
2736 const value = Number($(this).val());2744 const value = Number($(this).val());
27372745
2738 data.entries[uid].depth = !isNaN(value) ? value : 0;2746 data.entries[uid].depth = !isNaN(value) ? value : 0;
2739 updatePosOrdDisplay(uid);2747 updatePosOrdDisplay(uid);
2740 setOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);2748 setOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);
2741 saveWorldInfo(name, data);2749 await saveWorldInfo(name, data);
2742 });2750 });
2743 depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');2751 depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');
2744 depthInput.css('width', 'calc(3em + 15px)');2752 depthInput.css('width', 'calc(3em + 15px)');
@@ -2750,7 +2758,7 @@ function getWorldEntry(name, data, entry) {
27502758
2751 const probabilityInput = template.find('input[name="probability"]');2759 const probabilityInput = template.find('input[name="probability"]');
2752 probabilityInput.data('uid', entry.uid);2760 probabilityInput.data('uid', entry.uid);
2753 probabilityInput.on('input', function () {2761 probabilityInput.on('input', async function () {
2754 const uid = $(this).data('uid');2762 const uid = $(this).data('uid');
2755 const value = Number($(this).val());2763 const value = Number($(this).val());
27562764
@@ -2766,7 +2774,7 @@ function getWorldEntry(name, data, entry) {
2766 }2774 }
27672775
2768 setOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);2776 setOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
2769 saveWorldInfo(name, data);2777 await saveWorldInfo(name, data);
2770 });2778 });
2771 probabilityInput.val(entry.probability).trigger('input');2779 probabilityInput.val(entry.probability).trigger('input');
2772 probabilityInput.css('width', 'calc(3em + 15px)');2780 probabilityInput.css('width', 'calc(3em + 15px)');
@@ -2778,14 +2786,14 @@ function getWorldEntry(name, data, entry) {
27782786
2779 const probabilityToggle = template.find('input[name="useProbability"]');2787 const probabilityToggle = template.find('input[name="useProbability"]');
2780 probabilityToggle.data('uid', entry.uid);2788 probabilityToggle.data('uid', entry.uid);
2781 probabilityToggle.on('input', function () {2789 probabilityToggle.on('input', async function () {
2782 const uid = $(this).data('uid');2790 const uid = $(this).data('uid');
2783 const value = $(this).prop('checked');2791 const value = $(this).prop('checked');
2784 data.entries[uid].useProbability = value;2792 data.entries[uid].useProbability = value;
2785 const probabilityContainer = $(this)2793 const probabilityContainer = $(this)
2786 .closest('.world_entry')2794 .closest('.world_entry')
2787 .find('.probabilityContainer');2795 .find('.probabilityContainer');
2788 saveWorldInfo(name, data);2796 await saveWorldInfo(name, data);
2789 value ? probabilityContainer.show() : probabilityContainer.hide();2797 value ? probabilityContainer.show() : probabilityContainer.hide();
27902798
2791 if (value && data.entries[uid].probability === null) {2799 if (value && data.entries[uid].probability === null) {
@@ -2814,7 +2822,7 @@ function getWorldEntry(name, data, entry) {
2814 // Prevent closing the drawer on clicking the input2822 // Prevent closing the drawer on clicking the input
2815 event.stopPropagation();2823 event.stopPropagation();
2816 });2824 });
2817 positionInput.on('input', function () {2825 positionInput.on('input', async function () {
2818 const uid = $(this).data('uid');2826 const uid = $(this).data('uid');
2819 const value = Number($(this).val());2827 const value = Number($(this).val());
2820 data.entries[uid].position = !isNaN(value) ? value : 0;2828 data.entries[uid].position = !isNaN(value) ? value : 0;
@@ -2836,7 +2844,7 @@ function getWorldEntry(name, data, entry) {
2836 // Write the original value as extensions field2844 // Write the original value as extensions field
2837 setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);2845 setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
2838 setOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);2846 setOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
2839 saveWorldInfo(name, data);2847 await saveWorldInfo(name, data);
2840 });2848 });
28412849
2842 const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';2850 const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';
@@ -2852,12 +2860,12 @@ function getWorldEntry(name, data, entry) {
2852 /*2860 /*
2853 const disableInput = template.find('input[name="disable"]');2861 const disableInput = template.find('input[name="disable"]');
2854 disableInput.data("uid", entry.uid);2862 disableInput.data("uid", entry.uid);
2855 disableInput.on("input", function () {2863 disableInput.on("input", async function () {
2856 const uid = $(this).data("uid");2864 const uid = $(this).data("uid");
2857 const value = $(this).prop("checked");2865 const value = $(this).prop("checked");
2858 data.entries[uid].disable = value;2866 data.entries[uid].disable = value;
2859 setOriginalDataValue(data, uid, "enabled", !data.entries[uid].disable);2867 setOriginalDataValue(data, uid, "enabled", !data.entries[uid].disable);
2860 saveWorldInfo(name, data);2868 await saveWorldInfo(name, data);
2861 });2869 });
2862 disableInput.prop("checked", entry.disable).trigger("input");2870 disableInput.prop("checked", entry.disable).trigger("input");
2863 */2871 */
@@ -2869,7 +2877,7 @@ function getWorldEntry(name, data, entry) {
2869 // Prevent closing the drawer on clicking the input2877 // Prevent closing the drawer on clicking the input
2870 event.stopPropagation();2878 event.stopPropagation();
2871 });2879 });
2872 entryStateSelector.on('input', function () {2880 entryStateSelector.on('input', async function () {
2873 const uid = entry.uid;2881 const uid = entry.uid;
2874 const value = $(this).val();2882 const value = $(this).val();
2875 switch (value) {2883 switch (value) {
@@ -2910,7 +2918,7 @@ function getWorldEntry(name, data, entry) {
2910 template.addClass('disabledWIEntry');2918 template.addClass('disabledWIEntry');
2911 break;2919 break;
2912 }2920 }
2913 saveWorldInfo(name, data);2921 await saveWorldInfo(name, data);
29142922
2915 });2923 });
29162924
@@ -2930,52 +2938,50 @@ function getWorldEntry(name, data, entry) {
2930 .prop('selected', true)2938 .prop('selected', true)
2931 .trigger('input');2939 .trigger('input');
29322940
2933 saveWorldInfo(name, data);
2934
2935 // exclude recursion2941 // exclude recursion
2936 const excludeRecursionInput = template.find('input[name="exclude_recursion"]');2942 const excludeRecursionInput = template.find('input[name="exclude_recursion"]');
2937 excludeRecursionInput.data('uid', entry.uid);2943 excludeRecursionInput.data('uid', entry.uid);
2938 excludeRecursionInput.on('input', function () {2944 excludeRecursionInput.on('input', async function () {
2939 const uid = $(this).data('uid');2945 const uid = $(this).data('uid');
2940 const value = $(this).prop('checked');2946 const value = $(this).prop('checked');
2941 data.entries[uid].excludeRecursion = value;2947 data.entries[uid].excludeRecursion = value;
2942 setOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);2948 setOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);
2943 saveWorldInfo(name, data);2949 await saveWorldInfo(name, data);
2944 });2950 });
2945 excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');2951 excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');
29462952
2947 // prevent recursion2953 // prevent recursion
2948 const preventRecursionInput = template.find('input[name="prevent_recursion"]');2954 const preventRecursionInput = template.find('input[name="prevent_recursion"]');
2949 preventRecursionInput.data('uid', entry.uid);2955 preventRecursionInput.data('uid', entry.uid);
2950 preventRecursionInput.on('input', function () {2956 preventRecursionInput.on('input', async function () {
2951 const uid = $(this).data('uid');2957 const uid = $(this).data('uid');
2952 const value = $(this).prop('checked');2958 const value = $(this).prop('checked');
2953 data.entries[uid].preventRecursion = value;2959 data.entries[uid].preventRecursion = value;
2954 setOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);2960 setOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);
2955 saveWorldInfo(name, data);2961 await saveWorldInfo(name, data);
2956 });2962 });
2957 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');2963 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
29582964
2959 // delay until recursion2965 // delay until recursion
2960 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');2966 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
2961 delayUntilRecursionInput.data('uid', entry.uid);2967 delayUntilRecursionInput.data('uid', entry.uid);
2962 delayUntilRecursionInput.on('input', function () {2968 delayUntilRecursionInput.on('input', async function () {
2963 const uid = $(this).data('uid');2969 const uid = $(this).data('uid');
2964 const value = $(this).prop('checked');2970 const value = $(this).prop('checked');
2965 data.entries[uid].delayUntilRecursion = value;2971 data.entries[uid].delayUntilRecursion = value;
2966 setOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);2972 setOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
2967 saveWorldInfo(name, data);2973 await saveWorldInfo(name, data);
2968 });2974 });
2969 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');2975 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
29702976
2971 // duplicate button2977 // duplicate button
2972 const duplicateButton = template.find('.duplicate_entry_button');2978 const duplicateButton = template.find('.duplicate_entry_button');
2973 duplicateButton.data('uid', entry.uid);2979 duplicateButton.data('uid', entry.uid);
2974 duplicateButton.on('click', function () {2980 duplicateButton.on('click', async function () {
2975 const uid = $(this).data('uid');2981 const uid = $(this).data('uid');
2976 const entry = duplicateWorldInfoEntry(data, uid);2982 const entry = duplicateWorldInfoEntry(data, uid);
2977 if (entry) {2983 if (entry) {
2978 saveWorldInfo(name, data);2984 await saveWorldInfo(name, data);
2979 updateEditor(entry.uid);2985 updateEditor(entry.uid);
2980 }2986 }
2981 });2987 });
@@ -2983,18 +2989,18 @@ function getWorldEntry(name, data, entry) {
2983 // delete button2989 // delete button
2984 const deleteButton = template.find('.delete_entry_button');2990 const deleteButton = template.find('.delete_entry_button');
2985 deleteButton.data('uid', entry.uid);2991 deleteButton.data('uid', entry.uid);
2986 deleteButton.on('click', function () {2992 deleteButton.on('click', async function () {
2987 const uid = $(this).data('uid');2993 const uid = $(this).data('uid');
2988 deleteWorldInfoEntry(data, uid);2994 deleteWorldInfoEntry(data, uid);
2989 deleteOriginalDataValue(data, uid);2995 deleteOriginalDataValue(data, uid);
2990 saveWorldInfo(name, data);2996 await saveWorldInfo(name, data);
2991 updateEditor(navigation_option.previous);2997 updateEditor(navigation_option.previous);
2992 });2998 });
29932999
2994 // scan depth3000 // scan depth
2995 const scanDepthInput = template.find('input[name="scanDepth"]');3001 const scanDepthInput = template.find('input[name="scanDepth"]');
2996 scanDepthInput.data('uid', entry.uid);3002 scanDepthInput.data('uid', entry.uid);
2997 scanDepthInput.on('input', function () {3003 scanDepthInput.on('input', async function () {
2998 const uid = $(this).data('uid');3004 const uid = $(this).data('uid');
2999 const isEmpty = $(this).val() === '';3005 const isEmpty = $(this).val() === '';
3000 const value = Number($(this).val());3006 const value = Number($(this).val());
@@ -3014,59 +3020,59 @@ function getWorldEntry(name, data, entry) {
30143020
3015 data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;3021 data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;
3016 setOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);3022 setOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
3017 saveWorldInfo(name, data);3023 await saveWorldInfo(name, data);
3018 });3024 });
3019 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');3025 scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
30203026
3021 // case sensitive select3027 // case sensitive select
3022 const caseSensitiveSelect = template.find('select[name="caseSensitive"]');3028 const caseSensitiveSelect = template.find('select[name="caseSensitive"]');
3023 caseSensitiveSelect.data('uid', entry.uid);3029 caseSensitiveSelect.data('uid', entry.uid);
3024 caseSensitiveSelect.on('input', function () {3030 caseSensitiveSelect.on('input', async function () {
3025 const uid = $(this).data('uid');3031 const uid = $(this).data('uid');
3026 const value = $(this).val();3032 const value = $(this).val();
30273033
3028 data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';3034 data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
3029 setOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);3035 setOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
3030 saveWorldInfo(name, data);3036 await saveWorldInfo(name, data);
3031 });3037 });
3032 caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');3038 caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');
30333039
3034 // match whole words select3040 // match whole words select
3035 const matchWholeWordsSelect = template.find('select[name="matchWholeWords"]');3041 const matchWholeWordsSelect = template.find('select[name="matchWholeWords"]');
3036 matchWholeWordsSelect.data('uid', entry.uid);3042 matchWholeWordsSelect.data('uid', entry.uid);
3037 matchWholeWordsSelect.on('input', function () {3043 matchWholeWordsSelect.on('input', async function () {
3038 const uid = $(this).data('uid');3044 const uid = $(this).data('uid');
3039 const value = $(this).val();3045 const value = $(this).val();
30403046
3041 data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';3047 data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
3042 setOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);3048 setOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
3043 saveWorldInfo(name, data);3049 await saveWorldInfo(name, data);
3044 });3050 });
3045 matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');3051 matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
30463052
3047 // use group scoring select3053 // use group scoring select
3048 const useGroupScoringSelect = template.find('select[name="useGroupScoring"]');3054 const useGroupScoringSelect = template.find('select[name="useGroupScoring"]');
3049 useGroupScoringSelect.data('uid', entry.uid);3055 useGroupScoringSelect.data('uid', entry.uid);
3050 useGroupScoringSelect.on('input', function () {3056 useGroupScoringSelect.on('input', async function () {
3051 const uid = $(this).data('uid');3057 const uid = $(this).data('uid');
3052 const value = $(this).val();3058 const value = $(this).val();
30533059
3054 data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';3060 data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
3055 setOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);3061 setOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
3056 saveWorldInfo(name, data);3062 await saveWorldInfo(name, data);
3057 });3063 });
3058 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');3064 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
30593065
3060 // automation id3066 // automation id
3061 const automationIdInput = template.find('input[name="automationId"]');3067 const automationIdInput = template.find('input[name="automationId"]');
3062 automationIdInput.data('uid', entry.uid);3068 automationIdInput.data('uid', entry.uid);
3063 automationIdInput.on('input', function () {3069 automationIdInput.on('input', async function () {
3064 const uid = $(this).data('uid');3070 const uid = $(this).data('uid');
3065 const value = $(this).val();3071 const value = $(this).val();
30663072
3067 data.entries[uid].automationId = value;3073 data.entries[uid].automationId = value;
3068 setOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);3074 setOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
3069 saveWorldInfo(name, data);3075 await saveWorldInfo(name, data);
3070 });3076 });
3071 automationIdInput.val(entry.automationId ?? '').trigger('input');3077 automationIdInput.val(entry.automationId ?? '').trigger('input');
3072 setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1);3078 setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1);
@@ -3301,6 +3307,9 @@ function createWorldInfoEntry(_name, data) {
3301}3307}
33023308
3303async function _save(name, data) {3309async function _save(name, data) {
3310 // Prevent double saving if both immediate and debounced save are called
3311 cancelDebounce(saveWorldDebounced);
3312
3304 await fetch('/api/worldinfo/edit', {3313 await fetch('/api/worldinfo/edit', {
3305 method: 'POST',3314 method: 'POST',
3306 headers: getRequestHeaders(),3315 headers: getRequestHeaders(),
@@ -3309,12 +3318,13 @@ async function _save(name, data) {
3309 eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);3318 eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);
3310}3319}
33113320
3312async function saveWorldInfo(name, data, immediately) {3321async function saveWorldInfo(name, data, immediately = false) {
3313 if (!name || !data) {3322 if (!name || !data) {
3314 return;3323 return;
3315 }3324 }
33163325
3317 worldInfoCache.delete(name);3326 // Update cache immediately, so any future call can pull from this
3327 worldInfoCache.set(name, data);
33183328
3319 if (immediately) {3329 if (immediately) {
3320 return await _save(name, data);3330 return await _save(name, data);