From a90fe54e5bb5f5c1e12041f5d77ec5d1d5a92603 Mon Sep 17 00:00:00 2001 From: Bronya Rand Date: Mon, 4 Dec 2023 16:05:05 -0600 Subject: [PATCH] bak should not be here --- public/scripts/world-info.js | 7 +- public/scripts/world-info.js.bak | 2494 ------------------------------ 2 files changed, 4 insertions(+), 2497 deletions(-) delete mode 100644 public/scripts/world-info.js.bak diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index afe281f3d..5d5178e6d 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -1780,9 +1780,10 @@ async function checkWorldInfo(chat, maxContext) { console.debug(`WI UID:${entry.uid}: Filtering for secondary keyword - "${secondarySubstituted}".`); // Simplified AND/NOT if statement. (Proper fix for PR#1356 by Bronya) - if (selectiveLogic === 0 && secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim()) || - selectiveLogic === 1 && !(secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim()))) { - if (selectiveLogic === 0) { + if (selectiveLogic === 0 && (secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim())) || // If AND logic and the main checks pass + selectiveLogic === 1 && !(secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim()))) { // OR if NOT logic and the main checks do not pass + // Differ both logic statements in the debugger + if (selectiveLogic === 0) { console.debug(`(AND Check) Activating WI Entry ${entry.uid}. Found match for word: ${substituted} ${secondarySubstituted}`); } else { console.debug(`(NOT Check) Activating WI Entry ${entry.uid}. Found match for word "${substituted}" without secondary keyword: ${secondarySubstituted}`); diff --git a/public/scripts/world-info.js.bak b/public/scripts/world-info.js.bak deleted file mode 100644 index 19626a44e..000000000 --- a/public/scripts/world-info.js.bak +++ /dev/null @@ -1,2494 +0,0 @@ -import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId } from '../script.js'; -import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean } from './utils.js'; -import { extension_settings, getContext } from './extensions.js'; -import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; -import { registerSlashCommand } from './slash-commands.js'; -import { getDeviceInfo } from './RossAscends-mods.js'; -import { FILTER_TYPES, FilterHelper } from './filters.js'; -import { getTokenCount } from './tokenizers.js'; -import { power_user } from './power-user.js'; -import { getTagKeyForCharacter } from './tags.js'; -import { resolveVariable } from './variables.js'; - -export { - world_info, - world_info_budget, - world_info_depth, - world_info_min_activations, - world_info_min_activations_depth_max, - world_info_recursive, - world_info_overflow_alert, - world_info_case_sensitive, - world_info_match_whole_words, - world_info_character_strategy, - world_info_budget_cap, - world_names, - checkWorldInfo, - deleteWorldInfo, - setWorldInfoSettings, - getWorldInfoPrompt, -}; - -const world_info_insertion_strategy = { - evenly: 0, - character_first: 1, - global_first: 2, -}; - -let world_info = {}; -let selected_world_info = []; -let world_names; -let world_info_depth = 2; -let world_info_min_activations = 0; // if > 0, will continue seeking chat until minimum world infos are activated -let world_info_min_activations_depth_max = 0; // used when (world_info_min_activations > 0) - -let world_info_budget = 25; -let world_info_recursive = false; -let world_info_overflow_alert = false; -let world_info_case_sensitive = false; -let world_info_match_whole_words = false; -let world_info_character_strategy = world_info_insertion_strategy.character_first; -let world_info_budget_cap = 0; -const saveWorldDebounced = debounce(async (name, data) => await _save(name, data), 1000); -const saveSettingsDebounced = debounce(() => { - Object.assign(world_info, { globalSelect: selected_world_info }); - saveSettings(); -}, 1000); -const sortFn = (a, b) => b.order - a.order; -let updateEditor = (navigation) => { navigation; }; - -// Do not optimize. updateEditor is a function that is updated by the displayWorldEntries with new data. -const worldInfoFilter = new FilterHelper(() => updateEditor()); -const SORT_ORDER_KEY = 'world_info_sort_order'; -const METADATA_KEY = 'world_info'; - -const DEFAULT_DEPTH = 4; - -export function getWorldInfoSettings() { - return { - world_info, - world_info_depth, - world_info_min_activations, - world_info_min_activations_depth_max, - world_info_budget, - world_info_recursive, - world_info_overflow_alert, - world_info_case_sensitive, - world_info_match_whole_words, - world_info_character_strategy, - world_info_budget_cap, - }; -} - -const world_info_position = { - before: 0, - after: 1, - ANTop: 2, - ANBottom: 3, - atDepth: 4, -}; - -const worldInfoCache = {}; - -async function getWorldInfoPrompt(chat2, maxContext) { - let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = ''; - - const activatedWorldInfo = await checkWorldInfo(chat2, maxContext); - worldInfoBefore = activatedWorldInfo.worldInfoBefore; - worldInfoAfter = activatedWorldInfo.worldInfoAfter; - worldInfoString = worldInfoBefore + worldInfoAfter; - - return { - worldInfoString, - worldInfoBefore, - worldInfoAfter, - worldInfoDepth: activatedWorldInfo.WIDepthEntries, - }; -} - -function setWorldInfoSettings(settings, data) { - if (settings.world_info_depth !== undefined) - world_info_depth = Number(settings.world_info_depth); - if (settings.world_info_min_activations !== undefined) - world_info_min_activations = Number(settings.world_info_min_activations); - if (settings.world_info_min_activations_depth_max !== undefined) - world_info_min_activations_depth_max = Number(settings.world_info_min_activations_depth_max); - if (settings.world_info_budget !== undefined) - world_info_budget = Number(settings.world_info_budget); - if (settings.world_info_recursive !== undefined) - world_info_recursive = Boolean(settings.world_info_recursive); - if (settings.world_info_overflow_alert !== undefined) - world_info_overflow_alert = Boolean(settings.world_info_overflow_alert); - if (settings.world_info_case_sensitive !== undefined) - world_info_case_sensitive = Boolean(settings.world_info_case_sensitive); - if (settings.world_info_match_whole_words !== undefined) - world_info_match_whole_words = Boolean(settings.world_info_match_whole_words); - if (settings.world_info_character_strategy !== undefined) - world_info_character_strategy = Number(settings.world_info_character_strategy); - if (settings.world_info_budget_cap !== undefined) - world_info_budget_cap = Number(settings.world_info_budget_cap); - - // Migrate old settings - if (world_info_budget > 100) { - world_info_budget = 25; - } - - // Reset selected world from old string and delete old keys - // TODO: Remove next release - const existingWorldInfo = settings.world_info; - if (typeof existingWorldInfo === 'string') { - delete settings.world_info; - selected_world_info = [existingWorldInfo]; - } else if (Array.isArray(existingWorldInfo)) { - delete settings.world_info; - selected_world_info = existingWorldInfo; - } - - world_info = settings.world_info ?? {}; - - $('#world_info_depth_counter').val(world_info_depth); - $('#world_info_depth').val(world_info_depth); - - $('#world_info_min_activations_counter').val(world_info_min_activations); - $('#world_info_min_activations').val(world_info_min_activations); - - $('#world_info_min_activations_depth_max_counter').val(world_info_min_activations_depth_max); - $('#world_info_min_activations_depth_max').val(world_info_min_activations_depth_max); - - $('#world_info_budget_counter').val(world_info_budget); - $('#world_info_budget').val(world_info_budget); - - $('#world_info_recursive').prop('checked', world_info_recursive); - $('#world_info_overflow_alert').prop('checked', world_info_overflow_alert); - $('#world_info_case_sensitive').prop('checked', world_info_case_sensitive); - $('#world_info_match_whole_words').prop('checked', world_info_match_whole_words); - - $(`#world_info_character_strategy option[value='${world_info_character_strategy}']`).prop('selected', true); - $('#world_info_character_strategy').val(world_info_character_strategy); - - $('#world_info_budget_cap').val(world_info_budget_cap); - $('#world_info_budget_cap_counter').val(world_info_budget_cap); - - world_names = data.world_names?.length ? data.world_names : []; - - // Add to existing selected WI if it exists - selected_world_info = selected_world_info.concat(settings.world_info?.globalSelect?.filter((e) => world_names.includes(e)) ?? []); - - if (world_names.length > 0) { - $('#world_info').empty(); - } - - world_names.forEach((item, i) => { - $('#world_info').append(``); - $('#world_editor_select').append(``); - }); - - $('#world_info_sort_order').val(localStorage.getItem(SORT_ORDER_KEY) || '0'); - $('#world_editor_select').trigger('change'); - - eventSource.on(event_types.CHAT_CHANGED, () => { - const hasWorldInfo = !!chat_metadata[METADATA_KEY] && world_names.includes(chat_metadata[METADATA_KEY]); - $('.chat_lorebook_button').toggleClass('world_set', hasWorldInfo); - }); - - // Add slash commands - registerWorldInfoSlashCommands(); -} - -function registerWorldInfoSlashCommands() { - function reloadEditor(file) { - const selectedIndex = world_names.indexOf(file); - if (selectedIndex !== -1) { - $('#world_editor_select').val(selectedIndex).trigger('change'); - } - } - - async function getEntriesFromFile(file) { - if (!file || !world_names.includes(file)) { - toastr.warning('Valid World Info file name is required'); - return ''; - } - - const data = await loadWorldInfoData(file); - - if (!data || !('entries' in data)) { - toastr.warning('World Info file has an invalid format'); - return ''; - } - - const entries = Object.values(data.entries); - - if (!entries || entries.length === 0) { - toastr.warning('World Info file has no entries'); - return ''; - } - - return entries; - } - - async function getChatBookCallback() { - const chatId = getCurrentChatId(); - - if (!chatId) { - toastr.warning('Open a chat to get a name of the chat-bound lorebook'); - return ''; - } - - if (chat_metadata[METADATA_KEY] && world_names.includes(chat_metadata[METADATA_KEY])) { - return chat_metadata[METADATA_KEY]; - } - - // Replace non-alphanumeric characters with underscores, cut to 64 characters - const name = `Chat Book ${getCurrentChatId()}`.replace(/[^a-z0-9]/gi, '_').replace(/_{2,}/g, '_').substring(0, 64); - await createNewWorldInfo(name); - - chat_metadata[METADATA_KEY] = name; - await saveMetadata(); - $('.chat_lorebook_button').addClass('world_set'); - return name; - } - - async function findBookEntryCallback(args, value) { - const file = resolveVariable(args.file); - const field = args.field || 'key'; - - const entries = await getEntriesFromFile(file); - - if (!entries) { - return ''; - } - - const fuse = new Fuse(entries, { - keys: [{ name: field, weight: 1 }], - includeScore: true, - threshold: 0.3, - }); - - const results = fuse.search(value); - - if (!results || results.length === 0) { - return ''; - } - - const result = results[0]?.item?.uid; - - if (result === undefined) { - return ''; - } - - return result; - } - - async function getEntryFieldCallback(args, uid) { - const file = resolveVariable(args.file); - const field = args.field || 'content'; - - const entries = await getEntriesFromFile(file); - - if (!entries) { - return ''; - } - - const entry = entries.find(x => x.uid === uid); - - if (!entry) { - toastr.warning('Valid UID is required'); - return ''; - } - - if (newEntryTemplate[field] === undefined) { - toastr.warning('Valid field name is required'); - return ''; - } - - const fieldValue = entry[field]; - - if (fieldValue === undefined) { - return ''; - } - - if (Array.isArray(fieldValue)) { - return fieldValue.map(x => substituteParams(x)).join(', '); - } - - return substituteParams(String(fieldValue)); - } - - async function createEntryCallback(args, content) { - const file = resolveVariable(args.file); - const key = args.key; - - const data = await loadWorldInfoData(file); - - if (!data || !('entries' in data)) { - toastr.warning('Valid World Info file name is required'); - return ''; - } - - const entry = createWorldInfoEntry(file, data, true); - - if (key) { - entry.key.push(key); - entry.addMemo = true; - entry.comment = key; - } - - if (content) { - entry.content = content; - } - - await saveWorldInfo(file, data, true); - reloadEditor(file); - - return entry.uid; - } - - async function setEntryFieldCallback(args, value) { - const file = resolveVariable(args.file); - const uid = resolveVariable(args.uid); - const field = args.field || 'content'; - - if (value === undefined) { - toastr.warning('Value is required'); - return ''; - } - - const data = await loadWorldInfoData(file); - - if (!data || !('entries' in data)) { - toastr.warning('Valid World Info file name is required'); - return ''; - } - - const entry = data.entries[uid]; - - if (!entry) { - toastr.warning('Valid UID is required'); - return ''; - } - - if (newEntryTemplate[field] === undefined) { - toastr.warning('Valid field name is required'); - return ''; - } - - if (Array.isArray(entry[field])) { - entry[field] = value.split(',').map(x => x.trim()).filter(x => x); - } else if (typeof entry[field] === 'boolean') { - entry[field] = isTrueBoolean(value); - } else if (typeof entry[field] === 'number') { - entry[field] = Number(value); - } else { - entry[field] = value; - } - - if (originalDataKeyMap[field]) { - setOriginalDataValue(data, uid, originalDataKeyMap[field], entry[field]); - } - - await saveWorldInfo(file, data, true); - reloadEditor(file); - return ''; - } - - registerSlashCommand('getchatbook', getChatBookCallback, ['getchatlore', 'getchatwi'], '– get a name of the chat-bound lorebook or create a new one if was unbound, and pass it down the pipe', true, true); - registerSlashCommand('findentry', findBookEntryCallback, ['findlore', 'findwi'], '(file=bookName field=field [texts]) – find a UID of the record from the specified book using the fuzzy match of a field value (default: key) and pass it down the pipe, e.g. /findentry file=chatLore field=key Shadowfang', true, true); - registerSlashCommand('getentryfield', getEntryFieldCallback, ['getlorefield', 'getwifield'], '(file=bookName field=field [UID]) – get a field value (default: content) of the record with the UID from the specified book and pass it down the pipe, e.g. /getentryfield file=chatLore field=content 123', true, true); - registerSlashCommand('createentry', createEntryCallback, ['createlore', 'createwi'], '(file=bookName key=key [content]) – create a new record in the specified book with the key and content (both are optional) and pass the UID down the pipe, e.g. /createentry file=chatLore key=Shadowfang The sword of the king', true, true); - registerSlashCommand('setentryfield', setEntryFieldCallback, ['setlorefield', 'setwifield'], '(file=bookName uid=UID field=field [value]) – set a field value (default: content) of the record with the UID from the specified book. To set multiple values for key fields, use comma-delimited list as a value, e.g. /setentryfield file=chatLore uid=123 field=key Shadowfang,sword,weapon', true, true); -} - -// World Info Editor -async function showWorldEditor(name) { - if (!name) { - hideWorldEditor(); - return; - } - - const wiData = await loadWorldInfoData(name); - displayWorldEntries(name, wiData); -} - -async function loadWorldInfoData(name) { - if (!name) { - return; - } - - if (worldInfoCache[name]) { - return worldInfoCache[name]; - } - - const response = await fetch('/getworldinfo', { - method: 'POST', - headers: getRequestHeaders(), - body: JSON.stringify({ name: name }), - cache: 'no-cache', - }); - - if (response.ok) { - const data = await response.json(); - worldInfoCache[name] = data; - return data; - } - - return null; -} - -async function updateWorldInfoList() { - const result = await fetch('/getsettings', { - method: 'POST', - headers: getRequestHeaders(), - body: JSON.stringify({}), - }); - - if (result.ok) { - var data = await result.json(); - world_names = data.world_names?.length ? data.world_names : []; - $('#world_info').find('option[value!=""]').remove(); - $('#world_editor_select').find('option[value!=""]').remove(); - - world_names.forEach((item, i) => { - $('#world_info').append(``); - $('#world_editor_select').append(``); - }); - } -} - -function hideWorldEditor() { - displayWorldEntries(null, null); -} - -function getWIElement(name) { - const wiElement = $('#world_info').children().filter(function () { - return $(this).text().toLowerCase() === name.toLowerCase(); - }); - - return wiElement; -} - -/** - * @param {any[]} data WI entries - * @returns {any[]} Sorted data - */ -function sortEntries(data) { - const option = $('#world_info_sort_order').find(':selected'); - const sortField = option.data('field'); - const sortOrder = option.data('order'); - const sortRule = option.data('rule'); - const orderSign = sortOrder === 'asc' ? 1 : -1; - - if (sortRule === 'custom') { - // First by display index, then by order, then by uid - data.sort((a, b) => { - const aValue = a.displayIndex; - const bValue = b.displayIndex; - - return (aValue - bValue || b.order - a.order || a.uid - b.uid); - }); - } else if (sortRule === 'priority') { - // First constant, then normal, then disabled. Then sort by order - data.sort((a, b) => { - const aValue = a.constant ? 0 : a.disable ? 2 : 1; - const bValue = b.constant ? 0 : b.disable ? 2 : 1; - - return (aValue - bValue || b.order - a.order); - }); - } else { - const primarySort = (a, b) => { - const aValue = a[sortField]; - const bValue = b[sortField]; - - // Sort strings - if (typeof aValue === 'string' && typeof bValue === 'string') { - if (sortRule === 'length') { - // Sort by string length - return orderSign * (aValue.length - bValue.length); - } else { - // Sort by A-Z ordinal - return orderSign * aValue.localeCompare(bValue); - } - } - - // Sort numbers - return orderSign * (Number(aValue) - Number(bValue)); - }; - const secondarySort = (a, b) => a.order - b.order; - const tertiarySort = (a, b) => a.uid - b.uid; - - data.sort((a, b) => { - const primary = primarySort(a, b); - - if (primary !== 0) { - return primary; - } - - const secondary = secondarySort(a, b); - - if (secondary !== 0) { - return secondary; - } - - return tertiarySort(a, b); - }); - } - - return data; -} - -function nullWorldInfo() { - toastr.info('Create or import a new World Info file first.', 'World Info is not set', { timeOut: 10000, preventDuplicates: true }); -} - -function displayWorldEntries(name, data, navigation = navigation_option.none) { - updateEditor = (navigation) => displayWorldEntries(name, data, navigation); - - $('#world_popup_entries_list').empty().show(); - - if (!data || !('entries' in data)) { - $('#world_popup_new').off('click').on('click', nullWorldInfo); - $('#world_popup_name_button').off('click').on('click', nullWorldInfo); - $('#world_popup_export').off('click').on('click', nullWorldInfo); - $('#world_popup_delete').off('click').on('click', nullWorldInfo); - $('#world_popup_entries_list').hide(); - $('#world_info_pagination').html(''); - return; - } - - function getDataArray(callback) { - // Convert the data.entries object into an array - let entriesArray = Object.keys(data.entries).map(uid => { - const entry = data.entries[uid]; - entry.displayIndex = entry.displayIndex ?? entry.uid; - return entry; - }); - - // Sort the entries array by displayIndex and uid - entriesArray.sort((a, b) => a.displayIndex - b.displayIndex || a.uid - b.uid); - entriesArray = sortEntries(entriesArray); - entriesArray = worldInfoFilter.applyFilters(entriesArray); - typeof callback === 'function' && callback(entriesArray); - return entriesArray; - } - - let startPage = 1; - - if (navigation === navigation_option.previous) { - startPage = $('#world_info_pagination').pagination('getCurrentPageNum'); - } - - const storageKey = 'WI_PerPage'; - const perPageDefault = 25; - $('#world_info_pagination').pagination({ - dataSource: getDataArray, - pageSize: Number(localStorage.getItem(storageKey)) || perPageDefault, - sizeChangerOptions: [10, 25, 50, 100], - showSizeChanger: true, - pageRange: 1, - pageNumber: startPage, - position: 'top', - showPageNumbers: false, - prevText: '<', - nextText: '>', - formatNavigator: PAGINATION_TEMPLATE, - showNavigator: true, - callback: function (/** @type {object[]} */ page) { - $('#world_popup_entries_list').empty(); - const keywordHeaders = ` -
- - Title/Memo - - - Status - - - Position - - - Depth - - - Order - - - Trigger % - -
`; - const blocks = page.map(entry => getWorldEntry(name, data, entry)).filter(x => x); - const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom'; - if (!isCustomOrder) { - blocks.forEach(block => { - block.find('.drag-handle').remove(); - }); - } - $('#world_popup_entries_list').append(keywordHeaders); - $('#world_popup_entries_list').append(blocks); - }, - afterSizeSelectorChange: function (e) { - localStorage.setItem(storageKey, e.target.value); - }, - }); - - if (typeof navigation === 'number' && Number(navigation) >= 0) { - const selector = `#world_popup_entries_list [uid="${navigation}"]`; - const data = getDataArray(); - const uidIndex = data.findIndex(x => x.uid === navigation); - const perPage = Number(localStorage.getItem(storageKey)) || perPageDefault; - const page = Math.floor(uidIndex / perPage) + 1; - $('#world_info_pagination').pagination('go', page); - waitUntilCondition(() => document.querySelector(selector) !== null).finally(() => { - const element = $(selector); - - if (element.length === 0) { - console.log(`Could not find element for uid ${navigation}`); - return; - } - - const elementOffset = element.offset(); - const parentOffset = element.parent().offset(); - const scrollOffset = elementOffset.top - parentOffset.top; - $('#WorldInfo').scrollTop(scrollOffset); - element.addClass('flash animated'); - setTimeout(() => element.removeClass('flash animated'), 2000); - }); - } - - $('#world_popup_new').off('click').on('click', () => { - createWorldInfoEntry(name, data); - }); - - $('#world_popup_name_button').off('click').on('click', async () => { - await renameWorldInfo(name, data); - }); - - $('#world_backfill_memos').off('click').on('click', async () => { - let counter = 0; - for (const entry of Object.values(data.entries)) { - if (!entry.comment && Array.isArray(entry.key) && entry.key.length > 0) { - entry.comment = entry.key[0]; - setOriginalDataValue(data, entry.uid, 'comment', entry.comment); - counter++; - } - } - - if (counter > 0) { - toastr.info(`Backfilled ${counter} titles`); - await saveWorldInfo(name, data, true); - updateEditor(navigation_option.previous); - } - }); - - $('#world_popup_export').off('click').on('click', () => { - if (name && data) { - const jsonValue = JSON.stringify(data); - const fileName = `${name}.json`; - download(jsonValue, fileName, 'application/json'); - } - }); - - $('#world_popup_delete').off('click').on('click', async () => { - const confirmation = await callPopup(`

Delete the World/Lorebook: "${name}"?

This action is irreversible!`, 'confirm'); - - if (!confirmation) { - return; - } - - if (world_info.charLore) { - world_info.charLore.forEach((charLore, index) => { - if (charLore.extraBooks?.includes(name)) { - const tempCharLore = charLore.extraBooks.filter((e) => e !== name); - if (tempCharLore.length === 0) { - world_info.charLore.splice(index, 1); - } else { - charLore.extraBooks = tempCharLore; - } - } - }); - - saveSettingsDebounced(); - } - - // Selected world_info automatically refreshes - await deleteWorldInfo(name); - }); - - // Check if a sortable instance exists - if ($('#world_popup_entries_list').sortable('instance') !== undefined) { - // Destroy the instance - $('#world_popup_entries_list').sortable('destroy'); - } - - $('#world_popup_entries_list').sortable({ - delay: getSortableDelay(), - handle: '.drag-handle', - stop: async function (event, ui) { - const firstEntryUid = $('#world_popup_entries_list .world_entry').first().data('uid'); - const minDisplayIndex = data?.entries[firstEntryUid]?.displayIndex ?? 0; - $('#world_popup_entries_list .world_entry').each(function (index) { - const uid = $(this).data('uid'); - - // Update the display index in the data array - const item = data.entries[uid]; - - if (!item) { - console.debug(`Could not find entry with uid ${uid}`); - return; - } - - item.displayIndex = minDisplayIndex + index; - setOriginalDataValue(data, uid, 'extensions.display_index', item.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); - }, - }); - //$("#world_popup_entries_list").disableSelection(); -} - -const originalDataKeyMap = { - 'displayIndex': 'extensions.display_index', - 'excludeRecursion': 'extensions.exclude_recursion', - 'selectiveLogic': 'selectiveLogic', - 'comment': 'comment', - 'constant': 'constant', - 'order': 'insertion_order', - 'depth': 'extensions.depth', - 'probability': 'extensions.probability', - 'position': 'extensions.position', - 'content': 'content', - 'enabled': 'enabled', - 'key': 'keys', - 'keysecondary': 'secondary_keys', - 'selective': 'selective', -}; - -function setOriginalDataValue(data, uid, key, value) { - if (data.originalData && Array.isArray(data.originalData.entries)) { - let originalEntry = data.originalData.entries.find(x => x.uid === uid); - - if (!originalEntry) { - return; - } - - const keyParts = key.split('.'); - let currentObject = originalEntry; - - for (let i = 0; i < keyParts.length - 1; i++) { - const part = keyParts[i]; - - if (!Object.hasOwn(currentObject, part)) { - currentObject[part] = {}; - } - - currentObject = currentObject[part]; - } - - currentObject[keyParts[keyParts.length - 1]] = value; - } -} - -function deleteOriginalDataValue(data, uid) { - if (data.originalData && Array.isArray(data.originalData.entries)) { - const originalIndex = data.originalData.entries.findIndex(x => x.uid === uid); - - if (originalIndex >= 0) { - data.originalData.entries.splice(originalIndex, 1); - } - } -} - -function getWorldEntry(name, data, entry) { - if (!data.entries[entry.uid]) { - return; - } - - const template = $('#entry_edit_template .world_entry').clone(); - template.data('uid', entry.uid); - template.attr('uid', entry.uid); - - // key - const keyInput = template.find('textarea[name="key"]'); - keyInput.data('uid', entry.uid); - keyInput.on('click', function (event) { - // Prevent closing the drawer on clicking the input - event.stopPropagation(); - }); - - keyInput.on('input', function () { - const uid = $(this).data('uid'); - const value = String($(this).val()); - resetScrollHeight(this); - data.entries[uid].key = value - .split(',') - .map((x) => x.trim()) - .filter((x) => x); - - setOriginalDataValue(data, uid, 'keys', data.entries[uid].key); - saveWorldInfo(name, data); - }); - keyInput.val(entry.key.join(', ')).trigger('input'); - //initScrollHeight(keyInput); - - // logic AND/NOT - const selectiveLogicDropdown = template.find('select[name="entryLogicType"]'); - selectiveLogicDropdown.data('uid', entry.uid); - - selectiveLogicDropdown.on('click', function (event) { - event.stopPropagation(); - }); - - selectiveLogicDropdown.on('input', function () { - const uid = $(this).data('uid'); - const value = Number($(this).val()); - console.debug(`logic for ${entry.uid} set to ${value}`); - data.entries[uid].selectiveLogic = !isNaN(value) ? value : 0; - setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic); - saveWorldInfo(name, data); - }); - - template - .find(`select[name="entryLogicType"] option[value=${entry.selectiveLogic}]`) - .prop('selected', true) - .trigger('input'); - - // Character filter - const characterFilterLabel = template.find('label[for="characterFilter"] > small'); - characterFilterLabel.text(entry.characterFilter?.isExclude ? 'Exclude Character(s)' : 'Filter to Character(s)'); - - // exclude characters checkbox - const characterExclusionInput = template.find('input[name="character_exclusion"]'); - characterExclusionInput.data('uid', entry.uid); - characterExclusionInput.on('input', function () { - const uid = $(this).data('uid'); - const value = $(this).prop('checked'); - characterFilterLabel.text(value ? 'Exclude Character(s)' : 'Filter to Character(s)'); - if (data.entries[uid].characterFilter) { - if (!value && data.entries[uid].characterFilter.names.length === 0 && data.entries[uid].characterFilter.tags.length === 0) { - delete data.entries[uid].characterFilter; - } else { - data.entries[uid].characterFilter.isExclude = value; - } - } else if (value) { - Object.assign( - data.entries[uid], - { - characterFilter: { - isExclude: true, - names: [], - tags: [], - }, - }, - ); - } - - setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter); - saveWorldInfo(name, data); - }); - characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input'); - - const characterFilter = template.find('select[name="characterFilter"]'); - characterFilter.data('uid', entry.uid); - const deviceInfo = getDeviceInfo(); - if (deviceInfo && deviceInfo.device.type === 'desktop') { - $(characterFilter).select2({ - width: '100%', - placeholder: 'All characters will pull from this entry.', - allowClear: true, - closeOnSelect: false, - }); - } - - const characters = getContext().characters; - characters.forEach((character) => { - const option = document.createElement('option'); - const name = character.avatar.replace(/\.[^/.]+$/, '') ?? character.name; - option.innerText = name; - option.selected = entry.characterFilter?.names?.includes(name); - option.setAttribute('data-type', 'character'); - characterFilter.append(option); - }); - - const tags = getContext().tags; - tags.forEach((tag) => { - const option = document.createElement('option'); - option.innerText = `[Tag] ${tag.name}`; - option.selected = entry.characterFilter?.tags?.includes(tag.id); - option.value = tag.id; - option.setAttribute('data-type', 'tag'); - characterFilter.append(option); - }); - - characterFilter.on('mousedown change', async function (e) { - // If there's no world names, don't do anything - if (world_names.length === 0) { - e.preventDefault(); - return; - } - - const uid = $(this).data('uid'); - const selected = $(this).find(':selected'); - if ((!selected || selected?.length === 0) && !data.entries[uid].characterFilter?.isExclude) { - delete data.entries[uid].characterFilter; - } else { - const names = selected.filter('[data-type="character"]').map((_, e) => e instanceof HTMLOptionElement && e.innerText).toArray(); - const tags = selected.filter('[data-type="tag"]').map((_, e) => e instanceof HTMLOptionElement && e.value).toArray(); - Object.assign( - data.entries[uid], - { - characterFilter: { - isExclude: data.entries[uid].characterFilter?.isExclude ?? false, - names: names, - tags: tags, - }, - }, - ); - } - setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter); - saveWorldInfo(name, data); - }); - - // keysecondary - const keySecondaryInput = template.find('textarea[name="keysecondary"]'); - keySecondaryInput.data('uid', entry.uid); - keySecondaryInput.on('input', function () { - const uid = $(this).data('uid'); - const value = String($(this).val()); - resetScrollHeight(this); - data.entries[uid].keysecondary = value - .split(',') - .map((x) => x.trim()) - .filter((x) => x); - - setOriginalDataValue(data, uid, 'secondary_keys', data.entries[uid].keysecondary); - saveWorldInfo(name, data); - }); - - keySecondaryInput.val(entry.keysecondary.join(', ')).trigger('input'); - initScrollHeight(keySecondaryInput); - - // comment - const commentInput = template.find('textarea[name="comment"]'); - const commentToggle = template.find('input[name="addMemo"]'); - commentInput.data('uid', entry.uid); - commentInput.on('input', function () { - const uid = $(this).data('uid'); - const value = $(this).val(); - resetScrollHeight(this); - data.entries[uid].comment = value; - - setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment); - saveWorldInfo(name, data); - }); - commentToggle.data('uid', entry.uid); - commentToggle.on('input', function () { - const uid = $(this).data('uid'); - const value = $(this).prop('checked'); - //console.log(value) - const commentContainer = $(this) - .closest('.world_entry') - .find('.commentContainer'); - data.entries[uid].addMemo = value; - saveWorldInfo(name, data); - value ? commentContainer.show() : commentContainer.hide(); - }); - - commentInput.val(entry.comment).trigger('input'); - initScrollHeight(commentInput); - commentToggle.prop('checked', true /* entry.addMemo */).trigger('input'); - commentToggle.parent().hide(); - - // content - const counter = template.find('.world_entry_form_token_counter'); - const countTokensDebounced = debounce(function (counter, value) { - const numberOfTokens = getTokenCount(value); - $(counter).text(numberOfTokens); - }, 1000); - - const contentInput = template.find('textarea[name="content"]'); - contentInput.data('uid', entry.uid); - contentInput.on('input', function (_, { skipCount } = {}) { - const uid = $(this).data('uid'); - const value = $(this).val(); - data.entries[uid].content = value; - - setOriginalDataValue(data, uid, 'content', data.entries[uid].content); - saveWorldInfo(name, data); - - if (skipCount) { - return; - } - - // count tokens - countTokensDebounced(counter, value); - }); - contentInput.val(entry.content).trigger('input', { skipCount: true }); - //initScrollHeight(contentInput); - - template.find('.inline-drawer-toggle').on('click', function () { - if (counter.data('first-run')) { - counter.data('first-run', false); - countTokensDebounced(counter, contentInput.val()); - } - }); - - // selective - const selectiveInput = template.find('input[name="selective"]'); - selectiveInput.data('uid', entry.uid); - selectiveInput.on('input', function () { - const uid = $(this).data('uid'); - const value = $(this).prop('checked'); - data.entries[uid].selective = value; - - setOriginalDataValue(data, uid, 'selective', data.entries[uid].selective); - saveWorldInfo(name, data); - - const keysecondary = $(this) - .closest('.world_entry') - .find('.keysecondary'); - - const keysecondarytextpole = $(this) - .closest('.world_entry') - .find('.keysecondarytextpole'); - - const keyprimarytextpole = $(this) - .closest('.world_entry') - .find('.keyprimarytextpole'); - - const keyprimaryHeight = keyprimarytextpole.outerHeight(); - keysecondarytextpole.css('height', keyprimaryHeight + 'px'); - - value ? keysecondary.show() : keysecondary.hide(); - - }); - //forced on, ignored if empty - selectiveInput.prop('checked', true /* entry.selective */).trigger('input'); - selectiveInput.parent().hide(); - - - // constant - /* - const constantInput = template.find('input[name="constant"]'); - constantInput.data("uid", entry.uid); - constantInput.on("input", function () { - const uid = $(this).data("uid"); - const value = $(this).prop("checked"); - data.entries[uid].constant = value; - setOriginalDataValue(data, uid, "constant", data.entries[uid].constant); - saveWorldInfo(name, data); - }); - constantInput.prop("checked", entry.constant).trigger("input"); - */ - - // order - const orderInput = template.find('input[name="order"]'); - orderInput.data('uid', entry.uid); - orderInput.on('input', function () { - const uid = $(this).data('uid'); - const value = Number($(this).val()); - - data.entries[uid].order = !isNaN(value) ? value : 0; - updatePosOrdDisplay(uid); - setOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order); - saveWorldInfo(name, data); - }); - orderInput.val(entry.order).trigger('input'); - orderInput.css('width', 'calc(3em + 15px)'); - - // probability - if (entry.probability === undefined) { - entry.probability = null; - } - - // depth - const depthInput = template.find('input[name="depth"]'); - depthInput.data('uid', entry.uid); - - depthInput.on('input', function () { - const uid = $(this).data('uid'); - const value = Number($(this).val()); - - data.entries[uid].depth = !isNaN(value) ? value : 0; - updatePosOrdDisplay(uid); - setOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth); - saveWorldInfo(name, data); - }); - depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input'); - depthInput.css('width', 'calc(3em + 15px)'); - - // Hide by default unless depth is specified - if (entry.position === world_info_position.atDepth) { - //depthInput.parent().hide(); - } - - const probabilityInput = template.find('input[name="probability"]'); - probabilityInput.data('uid', entry.uid); - probabilityInput.on('input', function () { - const uid = $(this).data('uid'); - const value = parseInt($(this).val()); - - data.entries[uid].probability = !isNaN(value) ? value : null; - - // Clamp probability to 0-100 - if (data.entries[uid].probability !== null) { - data.entries[uid].probability = Math.min(100, Math.max(0, data.entries[uid].probability)); - - if (data.entries[uid].probability !== value) { - $(this).val(data.entries[uid].probability); - } - } - - setOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability); - saveWorldInfo(name, data); - }); - probabilityInput.val(entry.probability).trigger('input'); - probabilityInput.css('width', 'calc(3em + 15px)'); - - // probability toggle - if (entry.useProbability === undefined) { - entry.useProbability = false; - } - - const probabilityToggle = template.find('input[name="useProbability"]'); - probabilityToggle.data('uid', entry.uid); - probabilityToggle.on('input', function () { - const uid = $(this).data('uid'); - const value = $(this).prop('checked'); - data.entries[uid].useProbability = value; - const probabilityContainer = $(this) - .closest('.world_entry') - .find('.probabilityContainer'); - saveWorldInfo(name, data); - value ? probabilityContainer.show() : probabilityContainer.hide(); - - if (value && data.entries[uid].probability === null) { - data.entries[uid].probability = 100; - } - - if (!value) { - data.entries[uid].probability = null; - } - - probabilityInput.val(data.entries[uid].probability).trigger('input'); - }); - //forced on, 100% by default - probabilityToggle.prop('checked', true /* entry.useProbability */).trigger('input'); - probabilityToggle.parent().hide(); - - // position - if (entry.position === undefined) { - entry.position = 0; - } - - const positionInput = template.find('select[name="position"]'); - initScrollHeight(positionInput); - positionInput.data('uid', entry.uid); - positionInput.on('click', function (event) { - // Prevent closing the drawer on clicking the input - event.stopPropagation(); - }); - positionInput.on('input', function () { - const uid = $(this).data('uid'); - const value = Number($(this).val()); - data.entries[uid].position = !isNaN(value) ? value : 0; - if (value === world_info_position.atDepth) { - depthInput.prop('disabled', false); - depthInput.css('visibility', 'visible'); - //depthInput.parent().show(); - } else { - depthInput.prop('disabled', true); - depthInput.css('visibility', 'hidden'); - //depthInput.parent().hide(); - } - updatePosOrdDisplay(uid); - // Spec v2 only supports before_char and after_char - setOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char'); - // Write the original value as extensions field - setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position); - saveWorldInfo(name, data); - }); - - template - .find(`select[name="position"] option[value=${entry.position}]`) - .prop('selected', true) - .trigger('input'); - - //add UID above content box (less important doesn't need to be always visible) - template.find('.world_entry_form_uid_value').text(`(UID: ${entry.uid})`); - - // disable - /* - const disableInput = template.find('input[name="disable"]'); - disableInput.data("uid", entry.uid); - disableInput.on("input", function () { - const uid = $(this).data("uid"); - const value = $(this).prop("checked"); - data.entries[uid].disable = value; - setOriginalDataValue(data, uid, "enabled", !data.entries[uid].disable); - saveWorldInfo(name, data); - }); - disableInput.prop("checked", entry.disable).trigger("input"); - */ - - //new tri-state selector for constant/normal/disabled - const entryStateSelector = template.find('select[name="entryStateSelector"]'); - entryStateSelector.data('uid', entry.uid); - console.log(entry.uid); - entryStateSelector.on('click', function (event) { - // Prevent closing the drawer on clicking the input - event.stopPropagation(); - }); - entryStateSelector.on('input', function () { - const uid = entry.uid; - const value = $(this).val(); - switch (value) { - case 'constant': - data.entries[uid].constant = true; - data.entries[uid].disable = false; - setOriginalDataValue(data, uid, 'enabled', true); - setOriginalDataValue(data, uid, 'constant', true); - template.removeClass('disabledWIEntry'); - console.debug('set to constant'); - break; - case 'normal': - data.entries[uid].constant = false; - data.entries[uid].disable = false; - setOriginalDataValue(data, uid, 'enabled', true); - setOriginalDataValue(data, uid, 'constant', false); - template.removeClass('disabledWIEntry'); - console.debug('set to normal'); - break; - case 'disabled': - data.entries[uid].constant = false; - data.entries[uid].disable = true; - setOriginalDataValue(data, uid, 'enabled', false); - setOriginalDataValue(data, uid, 'constant', false); - template.addClass('disabledWIEntry'); - console.debug('set to disabled'); - break; - } - saveWorldInfo(name, data); - - }); - - const entryState = function () { - - console.log(`constant: ${entry.constant}, disabled: ${entry.disable}`); - if (entry.constant === true) { - console.debug('found constant'); - return 'constant'; - } else if (entry.disable === true) { - console.debug('found disabled'); - return 'disabled'; - } else { - console.debug('found normal'); - return 'normal'; - } - - }; - template - .find(`select[name="entryStateSelector"] option[value=${entryState()}]`) - .prop('selected', true) - .trigger('input'); - - saveWorldInfo(name, data); - - // exclude recursion - const excludeRecursionInput = template.find('input[name="exclude_recursion"]'); - excludeRecursionInput.data('uid', entry.uid); - excludeRecursionInput.on('input', function () { - const uid = $(this).data('uid'); - const value = $(this).prop('checked'); - data.entries[uid].excludeRecursion = value; - setOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion); - saveWorldInfo(name, data); - }); - excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input'); - - // delete button - const deleteButton = template.find('.delete_entry_button'); - deleteButton.data('uid', entry.uid); - deleteButton.on('click', function () { - const uid = $(this).data('uid'); - deleteWorldInfoEntry(data, uid); - deleteOriginalDataValue(data, uid); - saveWorldInfo(name, data); - updateEditor(navigation_option.previous); - }); - - template.find('.inline-drawer-content').css('display', 'none'); //entries start collapsed - - function updatePosOrdDisplay(uid) { - // display position/order info left of keyword box - let entry = data.entries[uid]; - let posText = entry.position; - switch (entry.position) { - case 0: - posText = '↑CD'; - break; - case 1: - posText = 'CD↓'; - break; - case 2: - posText = '↑AN'; - break; - case 3: - posText = 'AN↓'; - break; - case 4: - posText = `@D${entry.depth}`; - break; - } - template.find('.world_entry_form_position_value').text(`(${posText} ${entry.order})`); - } - - return template; -} - -async function deleteWorldInfoEntry(data, uid) { - if (!data || !('entries' in data)) { - return; - } - - if (!confirm(`Delete the entry with UID: ${uid}? This action is irreversible!`)) { - throw new Error('User cancelled deletion'); - } - - delete data.entries[uid]; -} - -const newEntryTemplate = { - key: [], - keysecondary: [], - comment: '', - content: '', - constant: false, - selective: true, - selectiveLogic: 0, - addMemo: false, - order: 100, - position: 0, - disable: false, - excludeRecursion: false, - probability: 100, - useProbability: true, - depth: DEFAULT_DEPTH, - group: '', -}; - -function createWorldInfoEntry(name, data, fromSlashCommand = false) { - const newUid = getFreeWorldEntryUid(data); - - if (!Number.isInteger(newUid)) { - console.error('Couldn\'t assign UID to a new entry'); - return; - } - - const newEntry = { uid: newUid, ...structuredClone(newEntryTemplate) }; - data.entries[newUid] = newEntry; - - if (!fromSlashCommand) { - updateEditor(newUid); - } - - return newEntry; -} - -async function _save(name, data) { - await fetch('/editworldinfo', { - method: 'POST', - headers: getRequestHeaders(), - body: JSON.stringify({ name: name, data: data }), - }); -} - -async function saveWorldInfo(name, data, immediately) { - if (!name || !data) { - return; - } - - delete worldInfoCache[name]; - - if (immediately) { - return await _save(name, data); - } - - saveWorldDebounced(name, data); -} - -async function renameWorldInfo(name, data) { - const oldName = name; - const newName = await callPopup('

Rename World Info

Enter a new name:', 'input', oldName); - - if (oldName === newName || !newName) { - console.debug('World info rename cancelled'); - return; - } - - const entryPreviouslySelected = selected_world_info.findIndex((e) => e === oldName); - - await saveWorldInfo(newName, data, true); - await deleteWorldInfo(oldName); - - const existingCharLores = world_info.charLore?.filter((e) => e.extraBooks.includes(oldName)); - if (existingCharLores && existingCharLores.length > 0) { - existingCharLores.forEach((charLore) => { - const tempCharLore = charLore.extraBooks.filter((e) => e !== oldName); - tempCharLore.push(newName); - charLore.extraBooks = tempCharLore; - }); - saveSettingsDebounced(); - } - - if (entryPreviouslySelected !== -1) { - const wiElement = getWIElement(newName); - wiElement.prop('selected', true); - $('#world_info').trigger('change'); - } - - const selectedIndex = world_names.indexOf(newName); - if (selectedIndex !== -1) { - $('#world_editor_select').val(selectedIndex).trigger('change'); - } -} - -async function deleteWorldInfo(worldInfoName) { - if (!world_names.includes(worldInfoName)) { - return; - } - - const response = await fetch('/deleteworldinfo', { - method: 'POST', - headers: getRequestHeaders(), - body: JSON.stringify({ name: worldInfoName }), - }); - - if (response.ok) { - const existingWorldIndex = selected_world_info.findIndex((e) => e === worldInfoName); - if (existingWorldIndex !== -1) { - selected_world_info.splice(existingWorldIndex, 1); - saveSettingsDebounced(); - } - - await updateWorldInfoList(); - $('#world_editor_select').trigger('change'); - - if ($('#character_world').val() === worldInfoName) { - $('#character_world').val('').trigger('change'); - setWorldInfoButtonClass(undefined, false); - if (menu_type != 'create') { - saveCharacterDebounced(); - } - } - } -} - -function getFreeWorldEntryUid(data) { - if (!data || !('entries' in data)) { - return null; - } - - const MAX_UID = 1_000_000; // <- should be safe enough :) - for (let uid = 0; uid < MAX_UID; uid++) { - if (uid in data.entries) { - continue; - } - return uid; - } - - return null; -} - -function getFreeWorldName() { - const MAX_FREE_NAME = 100_000; - for (let index = 1; index < MAX_FREE_NAME; index++) { - const newName = `New World (${index})`; - if (world_names.includes(newName)) { - continue; - } - return newName; - } - - return undefined; -} - -async function createNewWorldInfo(worldInfoName) { - const worldInfoTemplate = { entries: {} }; - - if (!worldInfoName) { - return; - } - - await saveWorldInfo(worldInfoName, worldInfoTemplate, true); - await updateWorldInfoList(); - - const selectedIndex = world_names.indexOf(worldInfoName); - if (selectedIndex !== -1) { - $('#world_editor_select').val(selectedIndex).trigger('change'); - } else { - hideWorldEditor(); - } -} - -// Gets a string that respects the case sensitivity setting -function transformString(str) { - return world_info_case_sensitive ? str : str.toLowerCase(); -} - -async function getCharacterLore() { - const character = characters[this_chid]; - const name = character?.name; - let worldsToSearch = new Set(); - - const baseWorldName = character?.data?.extensions?.world; - if (baseWorldName) { - worldsToSearch.add(baseWorldName); - } else { - console.debug(`Character ${name}'s base world could not be found or is empty! Skipping...`); - return []; - } - - // TODO: Maybe make the utility function not use the window context? - const fileName = getCharaFilename(this_chid); - const extraCharLore = world_info.charLore?.find((e) => e.name === fileName); - if (extraCharLore) { - worldsToSearch = new Set([...worldsToSearch, ...extraCharLore.extraBooks]); - } - - let entries = []; - for (const worldName of worldsToSearch) { - if (selected_world_info.includes(worldName)) { - console.debug(`Character ${name}'s world ${worldName} is already activated in global world info! Skipping...`); - continue; - } - - if (chat_metadata[METADATA_KEY] === worldName) { - console.debug(`Character ${name}'s world ${worldName} is already activated in chat lore! Skipping...`); - continue; - } - - const data = await loadWorldInfoData(worldName); - const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : []; - entries = entries.concat(newEntries); - } - - console.debug(`Character ${characters[this_chid]?.name} lore (${baseWorldName}) has ${entries.length} world info entries`); - return entries; -} - -async function getGlobalLore() { - if (!selected_world_info) { - return []; - } - - let entries = []; - for (const worldName of selected_world_info) { - const data = await loadWorldInfoData(worldName); - const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : []; - entries = entries.concat(newEntries); - } - - console.debug(`Global world info has ${entries.length} entries`); - - return entries; -} - -async function getChatLore() { - const chatWorld = chat_metadata[METADATA_KEY]; - - if (!chatWorld) { - return []; - } - - if (selected_world_info.includes(chatWorld)) { - console.debug(`Chat world ${chatWorld} is already activated in global world info! Skipping...`); - return []; - } - - const data = await loadWorldInfoData(chatWorld); - const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]) : []; - - console.debug(`Chat lore has ${entries.length} entries`); - - return entries; -} - -async function getSortedEntries() { - try { - const globalLore = await getGlobalLore(); - const characterLore = await getCharacterLore(); - const chatLore = await getChatLore(); - - let entries; - - switch (Number(world_info_character_strategy)) { - case world_info_insertion_strategy.evenly: - console.debug('WI using evenly'); - entries = [...globalLore, ...characterLore].sort(sortFn); - break; - case world_info_insertion_strategy.character_first: - console.debug('WI using char first'); - entries = [...characterLore.sort(sortFn), ...globalLore.sort(sortFn)]; - break; - case world_info_insertion_strategy.global_first: - console.debug('WI using global first'); - entries = [...globalLore.sort(sortFn), ...characterLore.sort(sortFn)]; - break; - default: - console.error('Unknown WI insertion strategy: ', world_info_character_strategy, 'defaulting to evenly'); - entries = [...globalLore, ...characterLore].sort(sortFn); - break; - } - - // Chat lore always goes first - entries = [...chatLore.sort(sortFn), ...entries]; - - console.debug(`Sorted ${entries.length} world lore entries using strategy ${world_info_character_strategy}`); - - // Need to deep clone the entries to avoid modifying the cached data - return structuredClone(entries); - } - catch (e) { - console.error(e); - return []; - } -} - -async function checkWorldInfo(chat, maxContext) { - const context = getContext(); - const messagesToLookBack = world_info_depth * 2 || 1; - - // Combine the chat - let textToScan = chat.slice(0, messagesToLookBack).join(''); - let minActivationMsgIndex = messagesToLookBack; // tracks chat index to satisfy `world_info_min_activations` - - // Add the depth or AN if enabled - // Put this code here since otherwise, the chat reference is modified - if (extension_settings.note.allowWIScan) { - for (const key of Object.keys(context.extensionPrompts)) { - if (key.startsWith('DEPTH_PROMPT')) { - const depthPrompt = getExtensionPromptByName(key); - if (depthPrompt) { - textToScan = `${depthPrompt}\n${textToScan}`; - } - } - } - - const anPrompt = getExtensionPromptByName(NOTE_MODULE_NAME); - if (anPrompt) { - textToScan = `${anPrompt}\n${textToScan}`; - } - } - - // Transform the resulting string - textToScan = transformString(textToScan); - - let needsToScan = true; - let token_budget_overflowed = false; - let count = 0; - let allActivatedEntries = new Set(); - let failedProbabilityChecks = new Set(); - let allActivatedText = ''; - - let budget = Math.round(world_info_budget * maxContext / 100) || 1; - - if (world_info_budget_cap > 0 && budget > world_info_budget_cap) { - console.debug(`Budget ${budget} exceeds cap ${world_info_budget_cap}, using cap`); - budget = world_info_budget_cap; - } - - console.debug(`Context size: ${maxContext}; WI budget: ${budget} (max% = ${world_info_budget}%, cap = ${world_info_budget_cap})`); - const sortedEntries = await getSortedEntries(); - - if (sortedEntries.length === 0) { - return { worldInfoBefore: '', worldInfoAfter: '' }; - } - - while (needsToScan) { - // Track how many times the loop has run - count++; - - let activatedNow = new Set(); - - for (let entry of sortedEntries) { - // Check if this entry applies to the character or if it's excluded - if (entry.characterFilter && entry.characterFilter?.names?.length > 0) { - const nameIncluded = entry.characterFilter.names.includes(getCharaFilename()); - const filtered = entry.characterFilter.isExclude ? nameIncluded : !nameIncluded; - - if (filtered) { - console.debug(`WI entry ${entry.uid} filtered out by character`); - continue; - } - } - - if (entry.characterFilter && entry.characterFilter?.tags?.length > 0) { - const tagKey = getTagKeyForCharacter(this_chid); - - if (tagKey) { - const tagMapEntry = context.tagMap[tagKey]; - - if (Array.isArray(tagMapEntry)) { - // If tag map intersects with the tag exclusion list, skip - const includesTag = tagMapEntry.some((tag) => entry.characterFilter.tags.includes(tag)); - const filtered = entry.characterFilter.isExclude ? includesTag : !includesTag; - - if (filtered) { - console.debug(`WI entry ${entry.uid} filtered out by tag`); - continue; - } - } - } - } - - if (failedProbabilityChecks.has(entry)) { - continue; - } - - if (allActivatedEntries.has(entry) || entry.disable == true || (count > 1 && world_info_recursive && entry.excludeRecursion)) { - continue; - } - - if (entry.constant) { - entry.content = substituteParams(entry.content); - activatedNow.add(entry); - continue; - } - - if (Array.isArray(entry.key) && entry.key.length) { //check for keywords existing - - // If selectiveLogic isn't found, assume it's AND, only do this once per entry - const selectiveLogic = entry.selectiveLogic ?? 0; - let causeWords = { - primary: "", - secondary: "" - }; - - primary: for (let key of entry.key) { - // For Debug Purposes - causeWords = { - primary: "", - secondary: "" - }; - const substituted = substituteParams(key); - - console.debug(`${entry.uid}: ${substituted}`); - - if (substituted && matchKeys(textToScan, substituted.trim())) { - console.debug(`${entry.uid}: got primary match`); - // Add Primary to Debug Dictionary - causeWords.primary = substituted; - - //selective logic begins - if ( - entry.selective && //all entries are selective now - Array.isArray(entry.keysecondary) && //always true - entry.keysecondary.length //ignore empties - ) { - console.debug(`uid:${entry.uid}: checking logic: ${entry.selectiveLogic}`); - secondary: for (let keysecondary of entry.keysecondary) { - const secondarySubstituted = substituteParams(keysecondary); - console.debug(`uid:${entry.uid}: filtering ${secondarySubstituted}`); - - //AND operator - if (selectiveLogic === 0) { - console.debug('Saw AND logic, checking...'); - if (secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim())) { - console.debug(`activating entry ${entry.uid} with AND found: ${substituted} ${secondarySubstituted}`); - // Add Secondary to Debug Dictionary - causeWords.secondary = secondarySubstituted; - - activatedNow.add(entry); - break secondary; - } - } - - //NOT operator - if (selectiveLogic === 1) { - console.debug('Saw NOT logic, checking...'); - // Since AND is opposite of NOT, this should be opposite too. - if (!(secondarySubstituted && matchKeys(textToScan, secondarySubstituted.trim()))) { - console.debug(`activating entry ${entry.uid} with NOT found: ${substituted} ${secondarySubstituted}`); - causeWords.secondary = secondarySubstituted; - - activatedNow.add(entry); - break secondary; - } - } - } - //handle cases where secondary is empty - } else { - console.debug(`uid ${entry.uid}: activated without filter logic`); - activatedNow.add(entry); - break primary; - } - } else { console.debug('no active entries for logic checks yet'); } - } - console.debug(`DEBUG Output: ${entry.uid}. Primary Word Trigger: ${causeWords.primary} | Secondary Word Trigger: ${causeWords.secondary}.`) - } - } - - needsToScan = world_info_recursive && activatedNow.size > 0; - const newEntries = [...activatedNow] - .sort((a, b) => sortedEntries.indexOf(a) - sortedEntries.indexOf(b)); - let newContent = ''; - const textToScanTokens = getTokenCount(allActivatedText); - const probabilityChecksBefore = failedProbabilityChecks.size; - console.debug('-- PROBABILITY CHECKS BEGIN --'); - for (const entry of newEntries) { - const rollValue = Math.random() * 100; - - - if (entry.useProbability && rollValue > entry.probability) { - console.debug(`WI entry ${entry.uid} ${entry.key} failed probability check, skipping`); - failedProbabilityChecks.add(entry); - continue; - } else { console.debug(`uid:${entry.uid} passed probability check, inserting to prompt`); } - - newContent += `${substituteParams(entry.content)}\n`; - - if (textToScanTokens + getTokenCount(newContent) >= budget) { - console.debug('WI budget reached, stopping'); - if (world_info_overflow_alert) { - console.log('Alerting'); - toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info'); - } - needsToScan = false; - token_budget_overflowed = true; - break; - } - - allActivatedEntries.add(entry); - console.debug('WI entry activated:', entry); - } - - const probabilityChecksAfter = failedProbabilityChecks.size; - - if ((probabilityChecksAfter - probabilityChecksBefore) === activatedNow.size) { - console.debug('WI probability checks failed for all activated entries, stopping'); - needsToScan = false; - } - - if (needsToScan) { - const text = newEntries - .filter(x => !failedProbabilityChecks.has(x)) - .map(x => x.content).join('\n'); - const currentlyActivatedText = transformString(text); - textToScan = (currentlyActivatedText + '\n' + textToScan); - allActivatedText = (currentlyActivatedText + '\n' + allActivatedText); - } - - // world_info_min_activations - if (!needsToScan && !token_budget_overflowed) { - if (world_info_min_activations > 0 && (allActivatedEntries.size < world_info_min_activations)) { - let over_max = false; - over_max = ( - world_info_min_activations_depth_max > 0 && - minActivationMsgIndex > world_info_min_activations_depth_max - ) || (minActivationMsgIndex >= chat.length); - if (!over_max) { - needsToScan = true; - textToScan = transformString(chat.slice(minActivationMsgIndex, minActivationMsgIndex + 1).join('')); - minActivationMsgIndex += 1; - } - } - } - } - - // Forward-sorted list of entries for joining - const WIBeforeEntries = []; - const WIAfterEntries = []; - const ANTopEntries = []; - const ANBottomEntries = []; - const WIDepthEntries = []; - - // Appends from insertion order 999 to 1. Use unshift for this purpose - [...allActivatedEntries].sort(sortFn).forEach((entry) => { - switch (entry.position) { - case world_info_position.before: - WIBeforeEntries.unshift(substituteParams(entry.content)); - break; - case world_info_position.after: - WIAfterEntries.unshift(substituteParams(entry.content)); - break; - case world_info_position.ANTop: - ANTopEntries.unshift(entry.content); - break; - case world_info_position.ANBottom: - ANBottomEntries.unshift(entry.content); - break; - case world_info_position.atDepth: { - const existingDepthIndex = WIDepthEntries.findIndex((e) => e.depth === entry.depth ?? DEFAULT_DEPTH); - if (existingDepthIndex !== -1) { - WIDepthEntries[existingDepthIndex].entries.unshift(entry.content); - } else { - WIDepthEntries.push({ - depth: entry.depth, - entries: [entry.content], - }); - } - break; - } - default: - break; - } - }); - - const worldInfoBefore = WIBeforeEntries.length ? WIBeforeEntries.join('\n') : ''; - const worldInfoAfter = WIAfterEntries.length ? WIAfterEntries.join('\n') : ''; - - if (shouldWIAddPrompt) { - const originalAN = context.extensionPrompts[NOTE_MODULE_NAME].value; - const ANWithWI = `${ANTopEntries.join('\n')}\n${originalAN}\n${ANBottomEntries.join('\n')}`; - context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]); - } - - return { worldInfoBefore, worldInfoAfter, WIDepthEntries }; -} - -function matchKeys(haystack, needle) { - const transformedString = transformString(needle); - - if (world_info_match_whole_words) { - const keyWords = transformedString.split(/\s+/); - - if (keyWords.length > 1) { - return haystack.includes(transformedString); - } - else { - const regex = new RegExp(`\\b${escapeRegex(transformedString)}\\b`); - if (regex.test(haystack)) { - return true; - } - } - - } else { - return haystack.includes(transformedString); - } - - return false; -} - -function convertAgnaiMemoryBook(inputObj) { - const outputObj = { entries: {} }; - - inputObj.entries.forEach((entry, index) => { - outputObj.entries[index] = { - uid: index, - key: entry.keywords, - keysecondary: [], - comment: entry.name, - content: entry.entry, - constant: false, - selective: false, - order: entry.weight, - position: 0, - disable: !entry.enabled, - addMemo: !!entry.name, - excludeRecursion: false, - displayIndex: index, - probability: null, - useProbability: false, - group: '', - }; - }); - - return outputObj; -} - -function convertRisuLorebook(inputObj) { - const outputObj = { entries: {} }; - - inputObj.data.forEach((entry, index) => { - outputObj.entries[index] = { - uid: index, - key: entry.key.split(',').map(x => x.trim()), - keysecondary: entry.secondkey ? entry.secondkey.split(',').map(x => x.trim()) : [], - comment: entry.comment, - content: entry.content, - constant: entry.alwaysActive, - selective: entry.selective, - order: entry.insertorder, - position: world_info_position.before, - disable: false, - addMemo: true, - excludeRecursion: false, - displayIndex: index, - probability: entry.activationPercent ?? null, - useProbability: entry.activationPercent ?? false, - group: '', - }; - }); - - return outputObj; -} - -function convertNovelLorebook(inputObj) { - const outputObj = { - entries: {}, - }; - - inputObj.entries.forEach((entry, index) => { - const displayName = entry.displayName; - const addMemo = displayName !== undefined && displayName.trim() !== ''; - - outputObj.entries[index] = { - uid: index, - key: entry.keys, - keysecondary: [], - comment: displayName || '', - content: entry.text, - constant: false, - selective: false, - order: entry.contextConfig?.budgetPriority ?? 0, - position: 0, - disable: !entry.enabled, - addMemo: addMemo, - excludeRecursion: false, - displayIndex: index, - probability: null, - useProbability: false, - group: '', - }; - }); - - return outputObj; -} - -function convertCharacterBook(characterBook) { - const result = { entries: {}, originalData: characterBook }; - - characterBook.entries.forEach((entry, index) => { - // Not in the spec, but this is needed to find the entry in the original data - if (entry.id === undefined) { - entry.id = index; - } - - result.entries[entry.id] = { - uid: entry.id, - key: entry.keys, - keysecondary: entry.secondary_keys || [], - comment: entry.comment || '', - content: entry.content, - constant: entry.constant || false, - selective: entry.selective || false, - order: entry.insertion_order, - position: entry.extensions?.position ?? (entry.position === 'before_char' ? world_info_position.before : world_info_position.after), - excludeRecursion: entry.extensions?.exclude_recursion ?? false, - disable: !entry.enabled, - addMemo: entry.comment ? true : false, - displayIndex: entry.extensions?.display_index ?? index, - probability: entry.extensions?.probability ?? null, - useProbability: entry.extensions?.useProbability ?? false, - depth: entry.extensions?.depth ?? DEFAULT_DEPTH, - selectiveLogic: entry.extensions?.selectiveLogic ?? 0, - group: entry.extensions?.group ?? '', - }; - }); - - return result; -} - -export function setWorldInfoButtonClass(chid, forceValue = undefined) { - if (forceValue !== undefined) { - $('#set_character_world, #world_button').toggleClass('world_set', forceValue); - return; - } - - if (!chid) { - return; - } - - const world = characters[chid]?.data?.extensions?.world; - const worldSet = Boolean(world && world_names.includes(world)); - $('#set_character_world, #world_button').toggleClass('world_set', worldSet); -} - -export function checkEmbeddedWorld(chid) { - $('#import_character_info').hide(); - - if (chid === undefined) { - return false; - } - - if (characters[chid]?.data?.character_book) { - $('#import_character_info').data('chid', chid).show(); - - // Only show the alert once per character - const checkKey = `AlertWI_${characters[chid].avatar}`; - const worldName = characters[chid]?.data?.extensions?.world; - if (!localStorage.getItem(checkKey) && (!worldName || !world_names.includes(worldName))) { - localStorage.setItem(checkKey, 1); - - if (power_user.world_import_dialog) { - const html = `

This character has an embedded World/Lorebook.

-

Would you like to import it now?

-
If you want to import it later, select "Import Card Lore" in the "More..." dropdown menu on the character panel.
`; - const checkResult = (result) => { - if (result) { - importEmbeddedWorldInfo(true); - } - }; - callPopup(html, 'confirm', '', { okButton: 'Yes' }).then(checkResult); - } - else { - toastr.info( - 'To import and use it, select "Import Card Lore" in the "More..." dropdown menu on the character panel.', - `${characters[chid].name} has an embedded World/Lorebook`, - { timeOut: 5000, extendedTimeOut: 10000, positionClass: 'toast-top-center' }, - ); - } - } - return true; - } - - return false; -} - -export async function importEmbeddedWorldInfo(skipPopup = false) { - const chid = $('#import_character_info').data('chid'); - - if (chid === undefined) { - return; - } - - const bookName = characters[chid]?.data?.character_book?.name || `${characters[chid]?.name}'s Lorebook`; - const confirmationText = (`

Are you sure you want to import "${bookName}"?

`) + (world_names.includes(bookName) ? 'It will overwrite the World/Lorebook with the same name.' : ''); - - if (!skipPopup) { - const confirmation = await callPopup(confirmationText, 'confirm'); - - if (!confirmation) { - return; - } - } - - const convertedBook = convertCharacterBook(characters[chid].data.character_book); - - await saveWorldInfo(bookName, convertedBook, true); - await updateWorldInfoList(); - $('#character_world').val(bookName).trigger('change'); - - toastr.success(`The world "${bookName}" has been imported and linked to the character successfully.`, 'World/Lorebook imported'); - - const newIndex = world_names.indexOf(bookName); - if (newIndex >= 0) { - //show&draw the WI panel before.. - $('#WIDrawerIcon').trigger('click'); - //..auto-opening the new imported WI - $('#world_editor_select').val(newIndex).trigger('change'); - } - - setWorldInfoButtonClass(chid, true); -} - -function onWorldInfoChange(_, text) { - if (_ !== '__notSlashCommand__') { // if it's a slash command - if (text.trim() !== '') { // and args are provided - const slashInputSplitText = text.trim().toLowerCase().split(','); - - slashInputSplitText.forEach((worldName) => { - const wiElement = getWIElement(worldName); - if (wiElement.length > 0) { - selected_world_info.push(wiElement.text()); - wiElement.prop('selected', true); - toastr.success(`Activated world: ${wiElement.text()}`); - } else { - toastr.error(`No world found named: ${worldName}`); - } - }); - $('#world_info').trigger('change'); - } else { // if no args, unset all worlds - toastr.success('Deactivated all worlds'); - selected_world_info = []; - $('#world_info').val(null).trigger('change'); - } - } else { //if it's a pointer selection - let tempWorldInfo = []; - let selectedWorlds = $('#world_info').val().map((e) => Number(e)).filter((e) => !isNaN(e)); - if (selectedWorlds.length > 0) { - selectedWorlds.forEach((worldIndex) => { - const existingWorldName = world_names[worldIndex]; - if (existingWorldName) { - tempWorldInfo.push(existingWorldName); - } else { - const wiElement = getWIElement(existingWorldName); - wiElement.prop('selected', false); - toastr.error(`The world with ${existingWorldName} is invalid or corrupted.`); - } - }); - } - selected_world_info = tempWorldInfo; - } - - saveSettingsDebounced(); - eventSource.emit(event_types.WORLDINFO_SETTINGS_UPDATED); -} - -export async function importWorldInfo(file) { - if (!file) { - return; - } - - const formData = new FormData(); - formData.append('avatar', file); - - try { - let jsonData; - - if (file.name.endsWith('.png')) { - const buffer = new Uint8Array(await getFileBuffer(file)); - jsonData = extractDataFromPng(buffer, 'naidata'); - } else { - // File should be a JSON file - jsonData = await parseJsonFile(file); - } - - if (jsonData === undefined || jsonData === null) { - toastr.error(`File is not valid: ${file.name}`); - return; - } - - // Convert Novel Lorebook - if (jsonData.lorebookVersion !== undefined) { - console.log('Converting Novel Lorebook'); - formData.append('convertedData', JSON.stringify(convertNovelLorebook(jsonData))); - } - - // Convert Agnai Memory Book - if (jsonData.kind === 'memory') { - console.log('Converting Agnai Memory Book'); - formData.append('convertedData', JSON.stringify(convertAgnaiMemoryBook(jsonData))); - } - - // Convert Risu Lorebook - if (jsonData.type === 'risu') { - console.log('Converting Risu Lorebook'); - formData.append('convertedData', JSON.stringify(convertRisuLorebook(jsonData))); - } - } catch (error) { - toastr.error(`Error parsing file: ${error}`); - return; - } - - jQuery.ajax({ - type: 'POST', - url: '/importworldinfo', - data: formData, - beforeSend: () => { }, - cache: false, - contentType: false, - processData: false, - success: async function (data) { - if (data.name) { - await updateWorldInfoList(); - - const newIndex = world_names.indexOf(data.name); - if (newIndex >= 0) { - $('#world_editor_select').val(newIndex).trigger('change'); - } - - toastr.info(`World Info "${data.name}" imported successfully!`); - } - }, - error: (jqXHR, exception) => { }, - }); -} - -function assignLorebookToChat() { - const selectedName = chat_metadata[METADATA_KEY]; - const template = $('#chat_world_template .chat_world').clone(); - - const worldSelect = template.find('select'); - const chatName = template.find('.chat_name'); - chatName.text(getCurrentChatId()); - - for (const worldName of world_names) { - const option = document.createElement('option'); - option.value = worldName; - option.innerText = worldName; - option.selected = selectedName === worldName; - worldSelect.append(option); - } - - worldSelect.on('change', function () { - const worldName = $(this).val(); - - if (worldName) { - chat_metadata[METADATA_KEY] = worldName; - $('.chat_lorebook_button').addClass('world_set'); - } else { - delete chat_metadata[METADATA_KEY]; - $('.chat_lorebook_button').removeClass('world_set'); - } - - saveMetadata(); - }); - - callPopup(template, 'text'); -} - -jQuery(() => { - - $(document).ready(function () { - registerSlashCommand('world', onWorldInfoChange, [], '(optional name) – sets active World, or unsets if no args provided', true, true); - }); - - - $('#world_info').on('mousedown change', async function (e) { - // If there's no world names, don't do anything - if (world_names.length === 0) { - e.preventDefault(); - return; - } - - onWorldInfoChange('__notSlashCommand__'); - }); - - //**************************WORLD INFO IMPORT EXPORT*************************// - $('#world_import_button').on('click', function () { - $('#world_import_file').trigger('click'); - }); - - $('#world_import_file').on('change', async function (e) { - const file = e.target.files[0]; - - await importWorldInfo(file); - - // Will allow to select the same file twice in a row - e.target.value = ''; - }); - - $('#world_create_button').on('click', async () => { - const tempName = getFreeWorldName(); - const finalName = await callPopup('

Create a new World Info?

Enter a name for the new file:', 'input', tempName); - - if (finalName) { - await createNewWorldInfo(finalName); - } - }); - - $('#world_editor_select').on('change', async () => { - $('#world_info_search').val(''); - worldInfoFilter.setFilterData(FILTER_TYPES.WORLD_INFO_SEARCH, '', true); - const selectedIndex = String($('#world_editor_select').find(':selected').val()); - - if (selectedIndex === '') { - hideWorldEditor(); - } else { - const worldName = world_names[selectedIndex]; - showWorldEditor(worldName); - } - }); - - const saveSettings = () => { - saveSettingsDebounced(); - eventSource.emit(event_types.WORLDINFO_SETTINGS_UPDATED); - }; - - $('#world_info_depth').on('input', function () { - world_info_depth = Number($(this).val()); - $('#world_info_depth_counter').val($(this).val()); - saveSettings(); - }); - - $('#world_info_min_activations').on('input', function () { - world_info_min_activations = Number($(this).val()); - $('#world_info_min_activations_counter').val($(this).val()); - saveSettings(); - }); - - $('#world_info_min_activations_depth_max').on('input', function () { - world_info_min_activations_depth_max = Number($(this).val()); - $('#world_info_min_activations_depth_max_counter').val($(this).val()); - saveSettings(); - }); - - $('#world_info_budget').on('input', function () { - world_info_budget = Number($(this).val()); - $('#world_info_budget_counter').val($(this).val()); - saveSettings(); - }); - - $('#world_info_recursive').on('input', function () { - world_info_recursive = !!$(this).prop('checked'); - saveSettings(); - }); - - $('#world_info_case_sensitive').on('input', function () { - world_info_case_sensitive = !!$(this).prop('checked'); - saveSettings(); - }); - - $('#world_info_match_whole_words').on('input', function () { - world_info_match_whole_words = !!$(this).prop('checked'); - saveSettings(); - }); - - $('#world_info_character_strategy').on('change', function () { - world_info_character_strategy = Number($(this).val()); - saveSettings(); - }); - - $('#world_info_overflow_alert').on('change', function () { - world_info_overflow_alert = !!$(this).prop('checked'); - saveSettingsDebounced(); - }); - - $('#world_info_budget_cap').on('input', function () { - world_info_budget_cap = Number($(this).val()); - $('#world_info_budget_cap_counter').val(world_info_budget_cap); - saveSettings(); - }); - - $('#world_button').on('click', async function (event) { - const chid = $('#set_character_world').data('chid'); - - if (chid) { - const worldName = characters[chid]?.data?.extensions?.world; - const hasEmbed = checkEmbeddedWorld(chid); - if (worldName && world_names.includes(worldName) && !event.shiftKey) { - if (!$('#WorldInfo').is(':visible')) { - $('#WIDrawerIcon').trigger('click'); - } - const index = world_names.indexOf(worldName); - $('#world_editor_select').val(index).trigger('change'); - } else if (hasEmbed && !event.shiftKey) { - await importEmbeddedWorldInfo(); - saveCharacterDebounced(); - } - else { - $('#char-management-dropdown').val($('#set_character_world').val()).trigger('change'); - } - } - }); - - $('#world_info_search').on('input', function () { - const term = $(this).val(); - worldInfoFilter.setFilterData(FILTER_TYPES.WORLD_INFO_SEARCH, term); - }); - - $('#world_refresh').on('click', () => { - updateEditor(navigation_option.previous); - }); - - $('#world_info_sort_order').on('change', function () { - const value = String($(this).find(':selected').val()); - localStorage.setItem(SORT_ORDER_KEY, value); - updateEditor(navigation_option.none); - }); - - $(document).on('click', '.chat_lorebook_button', assignLorebookToChat); - - // Not needed on mobile - const deviceInfo = getDeviceInfo(); - if (deviceInfo && deviceInfo.device.type === 'desktop') { - $('#world_info').select2({ - width: '100%', - placeholder: 'No Worlds active. Click here to select.', - allowClear: true, - closeOnSelect: false, - }); - } -});