Merge branch 'staging' into parser-followup-2

This commit is contained in:
LenAnderson 2024-07-24 08:39:00 -04:00
commit 8cb31d2f7b
10 changed files with 234 additions and 103 deletions

View File

@ -5,6 +5,10 @@
width: unset; width: unset;
} }
#sheldWidthToggleBlock {
display: none;
}
.bg_button { .bg_button {
font-size: 15px; font-size: 15px;
} }

View File

@ -2862,6 +2862,11 @@
</div> </div>
<h4 data-i18n="Groq Model">Groq Model</h4> <h4 data-i18n="Groq Model">Groq Model</h4>
<select id="model_groq_select"> <select id="model_groq_select">
<option value="llama-3.1-405b-reasoning">llama-3.1-405b-reasoning</option>
<option value="llama-3.1-70b-versatile">llama-3.1-70b-versatile</option>
<option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option>
<option value="llama3-groq-70b-8192-tool-use-preview">llama3-groq-70b-8192-tool-use-preview</option>
<option value="llama3-groq-8b-8192-tool-use-preview">llama3-groq-8b-8192-tool-use-preview</option>
<option value="llama3-8b-8192">llama3-8b-8192</option> <option value="llama3-8b-8192">llama3-8b-8192</option>
<option value="llama3-70b-8192">llama3-70b-8192</option> <option value="llama3-70b-8192">llama3-70b-8192</option>
<option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option> <option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option>

View File

@ -1640,5 +1640,26 @@
"Ask": "Спрашивать", "Ask": "Спрашивать",
"tag_import_all": "Все", "tag_import_all": "Все",
"Existing": "Только существующие", "Existing": "Только существующие",
"tag_import_none": "Не импортировать" "tag_import_none": "Не импортировать",
} "Using a proxy that you're not running yourself is a risk to your data privacy.": "Помните, что используя чужую прокси, вы подвергаете риску конфиденциальность своих данных.",
"ANY support requests will be REFUSED if you are using a proxy.": "НЕ РАССЧИТЫВАЙТЕ на нашу поддержку, если используете прокси.",
"Do not proceed if you do not agree to this!": "Не продолжайте, если не согласны с этими условиями!",
"Injection position. Relative (to other prompts in prompt manager) or In-chat @ Depth.": "Как рассчитывать позицию, на которую вставляется данный промпт. Относительно других промтов в менеджере, либо на опред. глубину в чате.",
"prompt_manager_in_chat": "На глубине в чате",
"01.AI API Key": "Ключ от API 01.AI",
"01.AI Model": "Модель 01.AI",
"Load a custom asset list or select": "Загрузите набор внешних ресурсов или выберите",
"Install Extension": "Установить расширение",
"to install 3rd party extensions.": ", чтобы установить стороннее расширение.",
"Load an asset list": "Загрузить набор ресурсов",
"load_asset_list_desc": "Загрузить набор ресурсов и/или расширений из определённого списка.\n\nДефолтный URL содержит описание набора стандартных ресурсов, идущих в комплекте.\nЕсли хотите скачать ресурсы из стороннего набора, вставьте в это поле свой URL.\n\nЧтобы установить одиночное расширение от стороннего разработчика, воспользуйтесь кнопкой \"Установить расширение\" в левом верхнем углу.",
"Show group chat queue": "Показывать очерёдность в групповых чатах",
"In group chat, highlight the character(s) that are currently queued to generate responses and the order in which they will respond.": "Подсвечивать персонажей, которые скоро будут генерировать ответ в групповом чате, а также порядок, в котором они будут это делать",
"Sequence Breakers": "Брейкеры для строк",
"DRY_Sequence_Breakers_desc": "Токены, которые прерывают сопоставление/поиск строк. Вводятся через запятую, каждый брейкер в отдельных кавычках.",
"ext_regex_user_input_desc": "Сообщения, отправленные пользователем",
"ext_regex_ai_output_desc": "Сообщения, полученные от API",
"ext_regex_sts_desc": "Сообщения, отправленные с помощью команд STscript",
"ext_regex_wi_desc": "Содержимое лорбуков и миров. Для работы требует включения флажка \"Только промпт\"!",
"ext_regex_only_format_display_desc": "История чата не изменится, замена будет осуществляться только в отображаемом сообщении (в UI)"
}

View File

@ -8961,14 +8961,6 @@ API Settings: ${JSON.stringify(getSettingsContents[getSettingsContents.main_api
} }
jQuery(async function () { jQuery(async function () {
if (isMobile()) {
console.debug('hiding movingUI and sheldWidth toggles for mobile');
$('#sheldWidthToggleBlock').hide();
$('#movingUIModeCheckBlock').hide();
}
async function doForceSave() { async function doForceSave() {
await saveSettings(); await saveSettings();
await saveChatConditional(); await saveChatConditional();
@ -9261,12 +9253,26 @@ jQuery(async function () {
} }
}); });
const chatElementScroll = document.getElementById('chat'); const chatElementScroll = document.getElementById('chat');
chatElementScroll.addEventListener('wheel', function () { const chatScrollHandler = function () {
scrollLock = true; if (power_user.waifuMode) {
}, { passive: true }); scrollLock = true;
chatElementScroll.addEventListener('touchstart', function () { return;
scrollLock = true; }
}, { passive: true });
const scrollIsAtBottom = Math.abs(chatElementScroll.scrollHeight - chatElementScroll.clientHeight - chatElementScroll.scrollTop) < 1;
// Resume autoscroll if the user scrolls to the bottom
if (scrollLock && scrollIsAtBottom) {
scrollLock = false;
}
// Cancel autoscroll if the user scrolls up
if (!scrollLock && !scrollIsAtBottom) {
scrollLock = true;
}
};
chatElementScroll.addEventListener('wheel', chatScrollHandler, { passive: true });
chatElementScroll.addEventListener('touchmove', chatScrollHandler, { passive: true });
chatElementScroll.addEventListener('scroll', function () { chatElementScroll.addEventListener('scroll', function () {
if (is_use_scroll_holder) { if (is_use_scroll_holder) {
this.scrollTop = scroll_holder; this.scrollTop = scroll_holder;

View File

@ -9,20 +9,20 @@
<span data-i18n="Load a custom asset list or select"> <span data-i18n="Load a custom asset list or select">
Load a custom asset list or select Load a custom asset list or select
</span> </span>
<a class="assets-install-hint-link" data-i18n="Install&nbsp;Extension">Install&nbsp;Extension</a> <a class="assets-install-hint-link" data-i18n="Install extension">Install&nbsp;Extension</a>
<span data-i18n="to install 3rd party extensions."> <span data-i18n="to install 3rd party extensions.">
to install 3rd party extensions. to install 3rd party extensions.
</span> </span>
</small> </small>
<div class="assets-url-block m-b-1 m-t-1"> <div class="assets-url-block m-b-1 m-t-1">
<label for="assets-json-url-field" data-i18n="Assets URL">Assets URL</label> <label for="assets-json-url-field" data-i18n="Assets URL">Assets URL</label>
<small title="Load a list of extensions & assets based on an asset list file. <small data-i18n="[title]load_asset_list_desc" title="Load a list of extensions & assets based on an asset list file.
The default Asset URL in this field points to the list of offical first party extensions and assets. The default Asset URL in this field points to the list of offical first party extensions and assets.
If you have a custom asset list, you can insert it here. If you have a custom asset list, you can insert it here.
To install a single 3rd party extension, use the &quot;Install Extensions&quot; button on the top right."> To install a single 3rd party extension, use the &quot;Install Extensions&quot; button on the top right.">
<span>Load an asset list</span> <span data-i18n="Load an asset list">Load an asset list</span>
<div class="fa-solid fa-circle-info opacity50p"></div> <div class="fa-solid fa-circle-info opacity50p"></div>
</small> </small>
<div class="assets-connect-div"> <div class="assets-connect-div">

View File

@ -97,9 +97,9 @@ class SystemTtsProvider {
return `<p>Uses the voices provided by your operating system</p> return `<p>Uses the voices provided by your operating system</p>
<label for="system_tts_rate">Rate: <span id="system_tts_rate_output"></span></label> <label for="system_tts_rate">Rate: <span id="system_tts_rate_output"></span></label>
<input id="system_tts_rate" type="range" value="${this.defaultSettings.rate}" min="0.5" max="2" step="0.1" /> <input id="system_tts_rate" type="range" value="${this.defaultSettings.rate}" min="0.1" max="2" step="0.01" />
<label for="system_tts_pitch">Pitch: <span id="system_tts_pitch_output"></span></label> <label for="system_tts_pitch">Pitch: <span id="system_tts_pitch_output"></span></label>
<input id="system_tts_pitch" type="range" value="${this.defaultSettings.pitch}" min="0" max="2" step="0.1" />`; <input id="system_tts_pitch" type="range" value="${this.defaultSettings.pitch}" min="0" max="2" step="0.01" />`;
} }
onSettingsChange() { onSettingsChange() {
@ -147,7 +147,7 @@ class SystemTtsProvider {
// Trigger updates // Trigger updates
$('#system_tts_rate').on('input', () => { this.onSettingsChange(); }); $('#system_tts_rate').on('input', () => { this.onSettingsChange(); });
$('#system_tts_rate').on('input', () => { this.onSettingsChange(); }); $('#system_tts_pitch').on('input', () => { this.onSettingsChange(); });
$('#system_tts_pitch_output').text(this.settings.pitch); $('#system_tts_pitch_output').text(this.settings.pitch);
$('#system_tts_rate_output').text(this.settings.rate); $('#system_tts_rate_output').text(this.settings.rate);
@ -198,8 +198,8 @@ class SystemTtsProvider {
const text = getPreviewString(voice.lang); const text = getPreviewString(voice.lang);
const utterance = new SpeechSynthesisUtterance(text); const utterance = new SpeechSynthesisUtterance(text);
utterance.voice = voice; utterance.voice = voice;
utterance.rate = 1; utterance.rate = this.settings.rate || 1;
utterance.pitch = 1; utterance.pitch = this.settings.pitch || 1;
speechSynthesis.speak(utterance); speechSynthesis.speak(utterance);
} }

View File

@ -4220,6 +4220,12 @@ async function onModelChange() {
if (oai_settings.max_context_unlocked) { if (oai_settings.max_context_unlocked) {
$('#openai_max_context').attr('max', unlocked_max); $('#openai_max_context').attr('max', unlocked_max);
} }
else if (oai_settings.groq_model.includes('llama-3.1')) {
$('#openai_max_context').attr('max', max_128k);
}
else if (oai_settings.groq_model.includes('llama3-groq')) {
$('#openai_max_context').attr('max', max_8k);
}
else if (['llama3-8b-8192', 'llama3-70b-8192', 'gemma-7b-it', 'gemma2-9b-it'].includes(oai_settings.groq_model)) { else if (['llama3-8b-8192', 'llama3-70b-8192', 'gemma-7b-it', 'gemma2-9b-it'].includes(oai_settings.groq_model)) {
$('#openai_max_context').attr('max', max_8k); $('#openai_max_context').attr('max', max_8k);
} }

View File

@ -0,0 +1,56 @@
/**
* A specialized Map class that provides consistent data storage by performing deep cloning of values.
*
* @template K, V
* @extends Map<K, V>
*/
export class StructuredCloneMap extends Map {
/**
* Constructs a new StructuredCloneMap.
* @param {object} options - Options for the map
* @param {boolean} options.cloneOnGet - Whether to clone the value when getting it from the map
* @param {boolean} options.cloneOnSet - Whether to clone the value when setting it in the map
*/
constructor({ cloneOnGet, cloneOnSet } = { cloneOnGet: true, cloneOnSet: true }) {
super();
this.cloneOnGet = cloneOnGet;
this.cloneOnSet = cloneOnSet;
}
/**
* 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.
*
* The set value will always be a deep clone of the provided value to provide consistent data storage.
*
* @param {K} key - The key to set
* @param {V} value - The value to set
* @returns {this} The updated map
*/
set(key, value) {
if (!this.cloneOnSet) {
return super.set(key, value);
}
const clonedValue = structuredClone(value);
super.set(key, clonedValue);
return this;
}
/**
* Returns a specified element from the Map object.
* 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.
*
* The returned value will always be a deep clone of the cached value.
*
* @param {K} key - The key to get the value for
* @returns {V | undefined} Returns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
*/
get(key) {
if (!this.cloneOnGet) {
return super.get(key);
}
const value = super.get(key);
return structuredClone(value);
}
}

View File

@ -270,6 +270,13 @@ export function getStringHash(str, seed = 0) {
return 4294967296 * (2097151 & h2) + (h1 >>> 0); return 4294967296 * (2097151 & h2) + (h1 >>> 0);
} }
/**
* Map of debounced functions to their timers.
* Weak map is used to avoid memory leaks.
* @type {WeakMap<function, any>}
*/
const debounceMap = new WeakMap();
/** /**
* Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked. * Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.
* @param {function} func The function to debounce. * @param {function} func The function to debounce.
@ -278,10 +285,26 @@ export function getStringHash(str, seed = 0) {
*/ */
export function debounce(func, timeout = debounce_timeout.standard) { export function debounce(func, timeout = debounce_timeout.standard) {
let timer; let timer;
return (...args) => { let fn = (...args) => {
clearTimeout(timer); clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout); timer = setTimeout(() => { func.apply(this, args); }, timeout);
debounceMap.set(func, timer);
debounceMap.set(fn, timer);
}; };
return fn;
}
/**
* Cancels a scheduled debounced function.
* Does nothing if the function is not debounced or not scheduled.
* @param {function} func The function to cancel. Either the original or the debounced function.
*/
export function cancelDebounce(func) {
if (debounceMap.has(func)) {
clearTimeout(debounceMap.get(func));
debounceMap.delete(func);
}
} }
/** /**

View File

@ -1,5 +1,5 @@
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js'; import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles } from '../script.js';
import { 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'; import { 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';
import { extension_settings, getContext } from './extensions.js'; import { extension_settings, getContext } from './extensions.js';
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
import { isMobile } from './RossAscends-mods.js'; import { isMobile } from './RossAscends-mods.js';
@ -16,6 +16,7 @@ import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandE
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
import { StructuredCloneMap } from './util/StructuredCloneMap.js';
export { export {
world_info, world_info,
@ -746,7 +747,8 @@ export const wi_anchor_position = {
after: 1, after: 1,
}; };
const worldInfoCache = new Map(); /** @type {StructuredCloneMap<string,object>} */
const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOnSet: false });
/** /**
* Gets the world info based on chat messages. * Gets the world info based on chat messages.
@ -885,9 +887,15 @@ function setWorldInfoSettings(settings, data) {
} }
function registerWorldInfoSlashCommands() { function registerWorldInfoSlashCommands() {
function reloadEditor(file) { /**
* Reloads the editor with the specified world info file
* @param {string} file - The file to load in the editor
* @param {boolean} [loadIfNotSelected=false] - Indicates whether to load the file even if it's not currently selected
*/
function reloadEditor(file, loadIfNotSelected = false) {
const currentIndex = $('#world_editor_select').val();
const selectedIndex = world_names.indexOf(file); const selectedIndex = world_names.indexOf(file);
if (selectedIndex !== -1) { if (selectedIndex !== -1 && (loadIfNotSelected || currentIndex === selectedIndex)) {
$('#world_editor_select').val(selectedIndex).trigger('change'); $('#world_editor_select').val(selectedIndex).trigger('change');
} }
} }
@ -1049,7 +1057,7 @@ function registerWorldInfoSlashCommands() {
entry.content = content; entry.content = content;
} }
await saveWorldInfo(file, data, true); await saveWorldInfo(file, data);
reloadEditor(file); reloadEditor(file);
return String(entry.uid); return String(entry.uid);
@ -1100,7 +1108,7 @@ function registerWorldInfoSlashCommands() {
setOriginalDataValue(data, uid, originalDataKeyMap[field], entry[field]); setOriginalDataValue(data, uid, originalDataKeyMap[field], entry[field]);
} }
await saveWorldInfo(file, data, true); await saveWorldInfo(file, data);
reloadEditor(file); reloadEditor(file);
return ''; return '';
} }
@ -1840,7 +1848,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
nextText: '>', nextText: '>',
formatNavigator: PAGINATION_TEMPLATE, formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true, showNavigator: true,
callback: function (/** @type {object[]} */ page) { callback: async function (/** @type {object[]} */ page) {
// We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements // We save costly performance by removing all events before emptying. Because we know there are no relevant event handlers reacting on removing elements
// This prevents jQuery from actually going through all registered events on the controls for each entry when removing it // This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
worldEntriesList.find('*').off(); worldEntriesList.find('*').off();
@ -1926,7 +1934,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
if (counter > 0) { if (counter > 0) {
toastr.info(`Backfilled ${counter} titles`); toastr.info(`Backfilled ${counter} titles`);
await saveWorldInfo(name, data, true); await saveWorldInfo(name, data);
updateEditor(navigation_option.previous); updateEditor(navigation_option.previous);
} }
}); });
@ -2026,7 +2034,7 @@ function displayWorldEntries(name, data, navigation = navigation_option.none, fl
console.table(Object.keys(data.entries).map(uid => data.entries[uid]).map(x => ({ uid: x.uid, key: x.key.join(','), displayIndex: x.displayIndex }))); console.table(Object.keys(data.entries).map(uid => data.entries[uid]).map(x => ({ uid: x.uid, key: x.key.join(','), displayIndex: x.displayIndex })));
await saveWorldInfo(name, data, true); await saveWorldInfo(name, data);
}, },
}); });
//$("#world_popup_entries_list").disableSelection(); //$("#world_popup_entries_list").disableSelection();
@ -2298,7 +2306,7 @@ function getWorldEntry(name, data, entry) {
templateResult: item => templateStyling(item, { searchStyle: true }), templateResult: item => templateStyling(item, { searchStyle: true }),
templateSelection: item => templateStyling(item), templateSelection: item => templateStyling(item),
}); });
input.on('change', function (_, { skipReset, noSave } = {}) { input.on('change', async function (_, { skipReset, noSave } = {}) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
/** @type {string[]} */ /** @type {string[]} */
const keys = ($(this).select2('data')).map(x => x.text); const keys = ($(this).select2('data')).map(x => x.text);
@ -2307,7 +2315,7 @@ function getWorldEntry(name, data, entry) {
if (!noSave) { if (!noSave) {
data.entries[uid][entryPropName] = keys; data.entries[uid][entryPropName] = keys;
setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]); setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
} }
}); });
input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data])); input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data]));
@ -2338,14 +2346,14 @@ function getWorldEntry(name, data, entry) {
template.find(`select[name="${entryPropName}"]`).hide(); template.find(`select[name="${entryPropName}"]`).hide();
input.show(); input.show();
input.on('input', function (_, { skipReset, noSave } = {}) { input.on('input', async function (_, { skipReset, noSave } = {}) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = String($(this).val()); const value = String($(this).val());
!skipReset && resetScrollHeight(this); !skipReset && resetScrollHeight(this);
if (!noSave) { if (!noSave) {
data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value); data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]); setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
} }
}); });
input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true }); input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });
@ -2384,12 +2392,12 @@ function getWorldEntry(name, data, entry) {
event.stopPropagation(); event.stopPropagation();
}); });
selectiveLogicDropdown.on('input', function () { selectiveLogicDropdown.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY; data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic); setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
template template
@ -2404,7 +2412,7 @@ function getWorldEntry(name, data, entry) {
// exclude characters checkbox // exclude characters checkbox
const characterExclusionInput = template.find('input[name="character_exclusion"]'); const characterExclusionInput = template.find('input[name="character_exclusion"]');
characterExclusionInput.data('uid', entry.uid); characterExclusionInput.data('uid', entry.uid);
characterExclusionInput.on('input', function () { characterExclusionInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
characterFilterLabel.text(value ? 'Exclude Character(s)' : 'Filter to Character(s)'); characterFilterLabel.text(value ? 'Exclude Character(s)' : 'Filter to Character(s)');
@ -2438,7 +2446,7 @@ function getWorldEntry(name, data, entry) {
} }
setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter); setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input'); characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');
@ -2500,24 +2508,24 @@ function getWorldEntry(name, data, entry) {
); );
} }
setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter); setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
// comment // comment
const commentInput = template.find('textarea[name="comment"]'); const commentInput = template.find('textarea[name="comment"]');
const commentToggle = template.find('input[name="addMemo"]'); const commentToggle = template.find('input[name="addMemo"]');
commentInput.data('uid', entry.uid); commentInput.data('uid', entry.uid);
commentInput.on('input', function (_, { skipReset } = {}) { commentInput.on('input', async function (_, { skipReset } = {}) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).val(); const value = $(this).val();
!skipReset && resetScrollHeight(this); !skipReset && resetScrollHeight(this);
data.entries[uid].comment = value; data.entries[uid].comment = value;
setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment); setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
commentToggle.data('uid', entry.uid); commentToggle.data('uid', entry.uid);
commentToggle.on('input', function () { commentToggle.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
//console.log(value) //console.log(value)
@ -2525,7 +2533,7 @@ function getWorldEntry(name, data, entry) {
.closest('.world_entry') .closest('.world_entry')
.find('.commentContainer'); .find('.commentContainer');
data.entries[uid].addMemo = value; data.entries[uid].addMemo = value;
saveWorldInfo(name, data); await saveWorldInfo(name, data);
value ? commentContainer.show() : commentContainer.hide(); value ? commentContainer.show() : commentContainer.hide();
}); });
@ -2543,13 +2551,13 @@ function getWorldEntry(name, data, entry) {
const contentInput = template.find('textarea[name="content"]'); const contentInput = template.find('textarea[name="content"]');
contentInput.data('uid', entry.uid); contentInput.data('uid', entry.uid);
contentInput.on('input', function (_, { skipCount } = {}) { contentInput.on('input', async function (_, { skipCount } = {}) {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).val(); const value = $(this).val();
data.entries[uid].content = value; data.entries[uid].content = value;
setOriginalDataValue(data, uid, 'content', data.entries[uid].content); setOriginalDataValue(data, uid, 'content', data.entries[uid].content);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
if (skipCount) { if (skipCount) {
return; return;
@ -2573,13 +2581,13 @@ function getWorldEntry(name, data, entry) {
// selective // selective
const selectiveInput = template.find('input[name="selective"]'); const selectiveInput = template.find('input[name="selective"]');
selectiveInput.data('uid', entry.uid); selectiveInput.data('uid', entry.uid);
selectiveInput.on('input', function () { selectiveInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].selective = value; data.entries[uid].selective = value;
setOriginalDataValue(data, uid, 'selective', data.entries[uid].selective); setOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
const keysecondary = $(this) const keysecondary = $(this)
.closest('.world_entry') .closest('.world_entry')
@ -2608,12 +2616,12 @@ function getWorldEntry(name, data, entry) {
/* /*
const constantInput = template.find('input[name="constant"]'); const constantInput = template.find('input[name="constant"]');
constantInput.data("uid", entry.uid); constantInput.data("uid", entry.uid);
constantInput.on("input", function () { constantInput.on("input", async function () {
const uid = $(this).data("uid"); const uid = $(this).data("uid");
const value = $(this).prop("checked"); const value = $(this).prop("checked");
data.entries[uid].constant = value; data.entries[uid].constant = value;
setOriginalDataValue(data, uid, "constant", data.entries[uid].constant); setOriginalDataValue(data, uid, "constant", data.entries[uid].constant);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
constantInput.prop("checked", entry.constant).trigger("input"); constantInput.prop("checked", entry.constant).trigger("input");
*/ */
@ -2621,14 +2629,14 @@ function getWorldEntry(name, data, entry) {
// order // order
const orderInput = template.find('input[name="order"]'); const orderInput = template.find('input[name="order"]');
orderInput.data('uid', entry.uid); orderInput.data('uid', entry.uid);
orderInput.on('input', function () { orderInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].order = !isNaN(value) ? value : 0; data.entries[uid].order = !isNaN(value) ? value : 0;
updatePosOrdDisplay(uid); updatePosOrdDisplay(uid);
setOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order); setOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
orderInput.val(entry.order).trigger('input'); orderInput.val(entry.order).trigger('input');
orderInput.css('width', 'calc(3em + 15px)'); orderInput.css('width', 'calc(3em + 15px)');
@ -2636,13 +2644,13 @@ function getWorldEntry(name, data, entry) {
// group // group
const groupInput = template.find('input[name="group"]'); const groupInput = template.find('input[name="group"]');
groupInput.data('uid', entry.uid); groupInput.data('uid', entry.uid);
groupInput.on('input', function () { groupInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = String($(this).val()).trim(); const value = String($(this).val()).trim();
data.entries[uid].group = value; data.entries[uid].group = value;
setOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group); setOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
groupInput.val(entry.group ?? '').trigger('input'); groupInput.val(entry.group ?? '').trigger('input');
setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1); setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);
@ -2650,19 +2658,19 @@ function getWorldEntry(name, data, entry) {
// inclusion priority // inclusion priority
const groupOverrideInput = template.find('input[name="groupOverride"]'); const groupOverrideInput = template.find('input[name="groupOverride"]');
groupOverrideInput.data('uid', entry.uid); groupOverrideInput.data('uid', entry.uid);
groupOverrideInput.on('input', function () { groupOverrideInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].groupOverride = value; data.entries[uid].groupOverride = value;
setOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride); setOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
groupOverrideInput.prop('checked', entry.groupOverride).trigger('input'); groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
// group weight // group weight
const groupWeightInput = template.find('input[name="groupWeight"]'); const groupWeightInput = template.find('input[name="groupWeight"]');
groupWeightInput.data('uid', entry.uid); groupWeightInput.data('uid', entry.uid);
groupWeightInput.on('input', function () { groupWeightInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
let value = Number($(this).val()); let value = Number($(this).val());
const min = Number($(this).attr('min')); const min = Number($(this).attr('min'));
@ -2679,46 +2687,46 @@ function getWorldEntry(name, data, entry) {
data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1; data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight); setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input'); groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
// sticky // sticky
const sticky = template.find('input[name="sticky"]'); const sticky = template.find('input[name="sticky"]');
sticky.data('uid', entry.uid); sticky.data('uid', entry.uid);
sticky.on('input', function () { sticky.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].sticky = !isNaN(value) ? value : null; data.entries[uid].sticky = !isNaN(value) ? value : null;
setOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky); setOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input'); sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');
// cooldown // cooldown
const cooldown = template.find('input[name="cooldown"]'); const cooldown = template.find('input[name="cooldown"]');
cooldown.data('uid', entry.uid); cooldown.data('uid', entry.uid);
cooldown.on('input', function () { cooldown.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].cooldown = !isNaN(value) ? value : null; data.entries[uid].cooldown = !isNaN(value) ? value : null;
setOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown); setOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input'); cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');
// delay // delay
const delay = template.find('input[name="delay"]'); const delay = template.find('input[name="delay"]');
delay.data('uid', entry.uid); delay.data('uid', entry.uid);
delay.on('input', function () { delay.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].delay = !isNaN(value) ? value : null; data.entries[uid].delay = !isNaN(value) ? value : null;
setOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay); setOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
delay.val(entry.delay > 0 ? entry.delay : '').trigger('input'); delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');
@ -2731,14 +2739,14 @@ function getWorldEntry(name, data, entry) {
const depthInput = template.find('input[name="depth"]'); const depthInput = template.find('input[name="depth"]');
depthInput.data('uid', entry.uid); depthInput.data('uid', entry.uid);
depthInput.on('input', function () { depthInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].depth = !isNaN(value) ? value : 0; data.entries[uid].depth = !isNaN(value) ? value : 0;
updatePosOrdDisplay(uid); updatePosOrdDisplay(uid);
setOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth); setOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input'); depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');
depthInput.css('width', 'calc(3em + 15px)'); depthInput.css('width', 'calc(3em + 15px)');
@ -2750,7 +2758,7 @@ function getWorldEntry(name, data, entry) {
const probabilityInput = template.find('input[name="probability"]'); const probabilityInput = template.find('input[name="probability"]');
probabilityInput.data('uid', entry.uid); probabilityInput.data('uid', entry.uid);
probabilityInput.on('input', function () { probabilityInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); const value = Number($(this).val());
@ -2766,7 +2774,7 @@ function getWorldEntry(name, data, entry) {
} }
setOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability); setOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
probabilityInput.val(entry.probability).trigger('input'); probabilityInput.val(entry.probability).trigger('input');
probabilityInput.css('width', 'calc(3em + 15px)'); probabilityInput.css('width', 'calc(3em + 15px)');
@ -2778,14 +2786,14 @@ function getWorldEntry(name, data, entry) {
const probabilityToggle = template.find('input[name="useProbability"]'); const probabilityToggle = template.find('input[name="useProbability"]');
probabilityToggle.data('uid', entry.uid); probabilityToggle.data('uid', entry.uid);
probabilityToggle.on('input', function () { probabilityToggle.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].useProbability = value; data.entries[uid].useProbability = value;
const probabilityContainer = $(this) const probabilityContainer = $(this)
.closest('.world_entry') .closest('.world_entry')
.find('.probabilityContainer'); .find('.probabilityContainer');
saveWorldInfo(name, data); await saveWorldInfo(name, data);
value ? probabilityContainer.show() : probabilityContainer.hide(); value ? probabilityContainer.show() : probabilityContainer.hide();
if (value && data.entries[uid].probability === null) { if (value && data.entries[uid].probability === null) {
@ -2814,7 +2822,7 @@ function getWorldEntry(name, data, entry) {
// Prevent closing the drawer on clicking the input // Prevent closing the drawer on clicking the input
event.stopPropagation(); event.stopPropagation();
}); });
positionInput.on('input', function () { positionInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = Number($(this).val()); const value = Number($(this).val());
data.entries[uid].position = !isNaN(value) ? value : 0; data.entries[uid].position = !isNaN(value) ? value : 0;
@ -2836,7 +2844,7 @@ function getWorldEntry(name, data, entry) {
// Write the original value as extensions field // Write the original value as extensions field
setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position); setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
setOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role); setOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : ''; const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';
@ -2852,12 +2860,12 @@ function getWorldEntry(name, data, entry) {
/* /*
const disableInput = template.find('input[name="disable"]'); const disableInput = template.find('input[name="disable"]');
disableInput.data("uid", entry.uid); disableInput.data("uid", entry.uid);
disableInput.on("input", function () { disableInput.on("input", async function () {
const uid = $(this).data("uid"); const uid = $(this).data("uid");
const value = $(this).prop("checked"); const value = $(this).prop("checked");
data.entries[uid].disable = value; data.entries[uid].disable = value;
setOriginalDataValue(data, uid, "enabled", !data.entries[uid].disable); setOriginalDataValue(data, uid, "enabled", !data.entries[uid].disable);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
disableInput.prop("checked", entry.disable).trigger("input"); disableInput.prop("checked", entry.disable).trigger("input");
*/ */
@ -2869,7 +2877,7 @@ function getWorldEntry(name, data, entry) {
// Prevent closing the drawer on clicking the input // Prevent closing the drawer on clicking the input
event.stopPropagation(); event.stopPropagation();
}); });
entryStateSelector.on('input', function () { entryStateSelector.on('input', async function () {
const uid = entry.uid; const uid = entry.uid;
const value = $(this).val(); const value = $(this).val();
switch (value) { switch (value) {
@ -2910,7 +2918,7 @@ function getWorldEntry(name, data, entry) {
template.addClass('disabledWIEntry'); template.addClass('disabledWIEntry');
break; break;
} }
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
@ -2930,52 +2938,50 @@ function getWorldEntry(name, data, entry) {
.prop('selected', true) .prop('selected', true)
.trigger('input'); .trigger('input');
saveWorldInfo(name, data);
// exclude recursion // exclude recursion
const excludeRecursionInput = template.find('input[name="exclude_recursion"]'); const excludeRecursionInput = template.find('input[name="exclude_recursion"]');
excludeRecursionInput.data('uid', entry.uid); excludeRecursionInput.data('uid', entry.uid);
excludeRecursionInput.on('input', function () { excludeRecursionInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].excludeRecursion = value; data.entries[uid].excludeRecursion = value;
setOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion); setOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input'); excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');
// prevent recursion // prevent recursion
const preventRecursionInput = template.find('input[name="prevent_recursion"]'); const preventRecursionInput = template.find('input[name="prevent_recursion"]');
preventRecursionInput.data('uid', entry.uid); preventRecursionInput.data('uid', entry.uid);
preventRecursionInput.on('input', function () { preventRecursionInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].preventRecursion = value; data.entries[uid].preventRecursion = value;
setOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion); setOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input'); preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
// delay until recursion // delay until recursion
const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]'); const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
delayUntilRecursionInput.data('uid', entry.uid); delayUntilRecursionInput.data('uid', entry.uid);
delayUntilRecursionInput.on('input', function () { delayUntilRecursionInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).prop('checked'); const value = $(this).prop('checked');
data.entries[uid].delayUntilRecursion = value; data.entries[uid].delayUntilRecursion = value;
setOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion); setOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input'); delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
// duplicate button // duplicate button
const duplicateButton = template.find('.duplicate_entry_button'); const duplicateButton = template.find('.duplicate_entry_button');
duplicateButton.data('uid', entry.uid); duplicateButton.data('uid', entry.uid);
duplicateButton.on('click', function () { duplicateButton.on('click', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const entry = duplicateWorldInfoEntry(data, uid); const entry = duplicateWorldInfoEntry(data, uid);
if (entry) { if (entry) {
saveWorldInfo(name, data); await saveWorldInfo(name, data);
updateEditor(entry.uid); updateEditor(entry.uid);
} }
}); });
@ -2983,18 +2989,18 @@ function getWorldEntry(name, data, entry) {
// delete button // delete button
const deleteButton = template.find('.delete_entry_button'); const deleteButton = template.find('.delete_entry_button');
deleteButton.data('uid', entry.uid); deleteButton.data('uid', entry.uid);
deleteButton.on('click', function () { deleteButton.on('click', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
deleteWorldInfoEntry(data, uid); deleteWorldInfoEntry(data, uid);
deleteOriginalDataValue(data, uid); deleteOriginalDataValue(data, uid);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
updateEditor(navigation_option.previous); updateEditor(navigation_option.previous);
}); });
// scan depth // scan depth
const scanDepthInput = template.find('input[name="scanDepth"]'); const scanDepthInput = template.find('input[name="scanDepth"]');
scanDepthInput.data('uid', entry.uid); scanDepthInput.data('uid', entry.uid);
scanDepthInput.on('input', function () { scanDepthInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const isEmpty = $(this).val() === ''; const isEmpty = $(this).val() === '';
const value = Number($(this).val()); const value = Number($(this).val());
@ -3014,59 +3020,59 @@ function getWorldEntry(name, data, entry) {
data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null; data.entries[uid].scanDepth = !isEmpty && !isNaN(value) && value >= 0 && value <= MAX_SCAN_DEPTH ? Math.floor(value) : null;
setOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth); setOriginalDataValue(data, uid, 'extensions.scan_depth', data.entries[uid].scanDepth);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
scanDepthInput.val(entry.scanDepth ?? null).trigger('input'); scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
// case sensitive select // case sensitive select
const caseSensitiveSelect = template.find('select[name="caseSensitive"]'); const caseSensitiveSelect = template.find('select[name="caseSensitive"]');
caseSensitiveSelect.data('uid', entry.uid); caseSensitiveSelect.data('uid', entry.uid);
caseSensitiveSelect.on('input', function () { caseSensitiveSelect.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).val(); const value = $(this).val();
data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true'; data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
setOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive); setOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input'); caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');
// match whole words select // match whole words select
const matchWholeWordsSelect = template.find('select[name="matchWholeWords"]'); const matchWholeWordsSelect = template.find('select[name="matchWholeWords"]');
matchWholeWordsSelect.data('uid', entry.uid); matchWholeWordsSelect.data('uid', entry.uid);
matchWholeWordsSelect.on('input', function () { matchWholeWordsSelect.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).val(); const value = $(this).val();
data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true'; data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
setOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords); setOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input'); matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
// use group scoring select // use group scoring select
const useGroupScoringSelect = template.find('select[name="useGroupScoring"]'); const useGroupScoringSelect = template.find('select[name="useGroupScoring"]');
useGroupScoringSelect.data('uid', entry.uid); useGroupScoringSelect.data('uid', entry.uid);
useGroupScoringSelect.on('input', function () { useGroupScoringSelect.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).val(); const value = $(this).val();
data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true'; data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
setOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring); setOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input'); useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
// automation id // automation id
const automationIdInput = template.find('input[name="automationId"]'); const automationIdInput = template.find('input[name="automationId"]');
automationIdInput.data('uid', entry.uid); automationIdInput.data('uid', entry.uid);
automationIdInput.on('input', function () { automationIdInput.on('input', async function () {
const uid = $(this).data('uid'); const uid = $(this).data('uid');
const value = $(this).val(); const value = $(this).val();
data.entries[uid].automationId = value; data.entries[uid].automationId = value;
setOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId); setOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
saveWorldInfo(name, data); await saveWorldInfo(name, data);
}); });
automationIdInput.val(entry.automationId ?? '').trigger('input'); automationIdInput.val(entry.automationId ?? '').trigger('input');
setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1); setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1);
@ -3301,6 +3307,9 @@ function createWorldInfoEntry(_name, data) {
} }
async function _save(name, data) { async function _save(name, data) {
// Prevent double saving if both immediate and debounced save are called
cancelDebounce(saveWorldDebounced);
await fetch('/api/worldinfo/edit', { await fetch('/api/worldinfo/edit', {
method: 'POST', method: 'POST',
headers: getRequestHeaders(), headers: getRequestHeaders(),
@ -3309,12 +3318,13 @@ async function _save(name, data) {
eventSource.emit(event_types.WORLDINFO_UPDATED, name, data); eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);
} }
async function saveWorldInfo(name, data, immediately) { async function saveWorldInfo(name, data, immediately = false) {
if (!name || !data) { if (!name || !data) {
return; return;
} }
worldInfoCache.delete(name); // Update cache immediately, so any future call can pull from this
worldInfoCache.set(name, data);
if (immediately) { if (immediately) {
return await _save(name, data); return await _save(name, data);