SillyTavern/public/scripts/world-info.js

4731 lines
175 KiB
JavaScript
Raw Normal View History

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';
2024-06-23 19:24:53 +02:00
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';
2023-12-02 19:04:51 +01:00
import { extension_settings, getContext } from './extensions.js';
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js';
import { isMobile } from './RossAscends-mods.js';
2023-12-02 19:04:51 +01:00
import { FILTER_TYPES, FilterHelper } from './filters.js';
2024-04-13 20:33:19 +02:00
import { getTokenCountAsync } from './tokenizers.js';
2023-12-02 19:04:51 +01:00
import { power_user } from './power-user.js';
import { getTagKeyForEntity } from './tags.js';
import { debounce_timeout } from './constants.js';
2024-05-08 16:55:33 +02:00
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
2024-06-21 20:04:55 +02:00
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';
2023-07-20 19:32:15 +02:00
export {
world_info,
world_info_budget,
world_info_depth,
world_info_min_activations,
world_info_min_activations_depth_max,
world_info_include_names,
2023-07-20 19:32:15 +02:00
world_info_recursive,
world_info_overflow_alert,
2023-07-20 19:32:15 +02:00
world_info_case_sensitive,
world_info_match_whole_words,
world_info_character_strategy,
world_info_budget_cap,
2023-07-20 19:32:15 +02:00
world_names,
checkWorldInfo,
deleteWorldInfo,
setWorldInfoSettings,
getWorldInfoPrompt,
2023-12-02 20:11:06 +01:00
};
2023-07-20 19:32:15 +02:00
const world_info_insertion_strategy = {
evenly: 0,
character_first: 1,
global_first: 2,
};
2023-12-05 09:56:52 +01:00
const world_info_logic = {
2023-12-05 11:04:27 +01:00
AND_ANY: 0,
2023-12-05 10:00:26 +01:00
NOT_ALL: 1,
2023-12-05 11:04:27 +01:00
NOT_ANY: 2,
AND_ALL: 3,
2023-12-05 09:56:52 +01:00
};
/**
* @enum {number} Possible states of the WI evaluation
*/
const scan_state = {
/**
* The scan will be stopped.
*/
NONE: 0,
/**
2024-07-03 23:18:46 +02:00
* Initial state.
*/
2024-07-03 23:18:46 +02:00
INITIAL: 1,
/**
* The scan is triggered by a recursion step.
*/
RECURSION: 2,
/**
* The scan is triggered by a min activations depth skew.
*/
MIN_ACTIVATIONS: 3,
};
const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry');
2023-07-20 19:32:15 +02:00
let world_info = {};
let selected_world_info = [];
/** @type {string[]} */
2023-07-20 19:32:15 +02:00
let world_names;
let world_info_depth = 2;
2023-10-30 22:55:32 +01:00
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)
2023-10-30 22:55:32 +01:00
2023-07-20 19:32:15 +02:00
let world_info_budget = 25;
let world_info_include_names = true;
2023-07-20 19:32:15 +02:00
let world_info_recursive = false;
let world_info_overflow_alert = false;
2023-07-20 19:32:15 +02:00
let world_info_case_sensitive = false;
let world_info_match_whole_words = false;
2024-05-04 22:51:28 +02:00
let world_info_use_group_scoring = false;
2023-07-20 19:32:15 +02:00
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), debounce_timeout.relaxed);
2023-07-20 19:32:15 +02:00
const saveSettingsDebounced = debounce(() => {
2023-12-02 20:11:06 +01:00
Object.assign(world_info, { globalSelect: selected_world_info });
saveSettings();
}, debounce_timeout.relaxed);
2023-07-20 19:32:15 +02:00
const sortFn = (a, b) => b.order - a.order;
let updateEditor = (navigation, flashOnNav = true) => { console.debug('Triggered WI navigation', navigation, flashOnNav); };
// 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';
2023-10-16 22:03:42 +02:00
const METADATA_KEY = 'world_info';
const DEFAULT_DEPTH = 4;
2024-05-06 16:00:42 +02:00
const DEFAULT_WEIGHT = 100;
const MAX_SCAN_DEPTH = 1000;
2024-07-14 13:07:23 +02:00
const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];
2024-06-22 02:15:13 +02:00
// Typedef area
/**
* @typedef {object} WIScanEntry The entry that triggered the scan
* @property {number} [scanDepth] The depth of the scan
* @property {boolean} [caseSensitive] If the scan is case sensitive
* @property {boolean} [matchWholeWords] If the scan should match whole words
* @property {boolean} [useGroupScoring] If the scan should use group scoring
* @property {number} [uid] The UID of the entry that triggered the scan
2024-06-22 02:54:54 +02:00
* @property {string} [world] The world info book of origin of the entry
2024-06-22 02:15:13 +02:00
* @property {string[]} [key] The primary keys to scan for
* @property {string[]} [keysecondary] The secondary keys to scan for
* @property {number} [selectiveLogic] The logic to use for selective activation
* @property {number} [sticky] The sticky value of the entry
* @property {number} [cooldown] The cooldown of the entry
2024-06-26 21:43:30 +02:00
* @property {number} [delay] The delay of the entry
2024-06-22 02:15:13 +02:00
*/
/**
* @typedef {object} WITimedEffect Timed effect for world info
* @property {number} hash Hash of the entry that triggered the effect
* @property {number} start The chat index where the effect starts
* @property {number} end The chat index where the effect ends
* @property {boolean} protected The protected effect can't be removed if the chat does not advance
2024-06-22 02:15:13 +02:00
*/
2024-06-22 13:56:46 +02:00
/**
* @typedef TimedEffectType Type of timed effect
2024-06-26 21:43:30 +02:00
* @type {'sticky'|'cooldown'|'delay'}
2024-06-22 13:56:46 +02:00
*/
2024-06-22 02:15:13 +02:00
// End typedef area
/**
* Represents a scanning buffer for one evaluation of World Info.
*/
class WorldInfoBuffer {
/**
* @type {object[]} Array of entries that need to be activated no matter what
*/
static externalActivations = [];
/**
2024-01-24 12:07:56 +01:00
* @type {string[]} Array of messages sorted by ascending depth
*/
#depthBuffer = [];
/**
* @type {string[]} Array of strings added by recursive scanning
*/
#recurseBuffer = [];
/**
* @type {string[]} Array of strings added by prompt injections that are valid for the current scan
*/
#injectBuffer = [];
/**
2024-01-24 12:07:56 +01:00
* @type {number} The skew of the global scan depth. Used in "min activations"
*/
#skew = 0;
/**
* @type {number} The starting depth of the global scan depth. Incremented by "min activations" feature to not repeat scans. When > 0 it means a complete scan was done up to #startDepth already, and `advanceScanPosition` was called.
*/
#startDepth = 0;
/**
* Initialize the buffer with the given messages.
* @param {string[]} messages Array of messages to add to the buffer
*/
constructor(messages) {
this.#initDepthBuffer(messages);
}
/**
* Populates the buffer with the given messages.
* @param {string[]} messages Array of messages to add to the buffer
* @returns {void} Hardly seen nothing down here
*/
#initDepthBuffer(messages) {
for (let depth = 0; depth < MAX_SCAN_DEPTH; depth++) {
if (messages[depth]) {
this.#depthBuffer[depth] = messages[depth].trim();
}
// break if last message is reached
if (depth === messages.length - 1) {
break;
}
}
}
/**
* Gets a string that respects the case sensitivity setting
* @param {string} str The string to transform
* @param {WIScanEntry} entry The entry that triggered the scan
* @returns {string} The transformed string
*/
#transformString(str, entry) {
const caseSensitive = entry.caseSensitive ?? world_info_case_sensitive;
return caseSensitive ? str : str.toLowerCase();
}
/**
* Gets all messages up to the given depth + recursion buffer.
* @param {WIScanEntry} entry The entry that triggered the scan
* @param {number} scanState The state of the scan
* @returns {string} A slice of buffer until the given depth (inclusive)
*/
get(entry, scanState) {
let depth = entry.scanDepth ?? this.getDepth();
if (depth <= this.#startDepth) {
return '';
}
if (depth < 0) {
console.error(`Invalid WI scan depth ${depth}. Must be >= 0`);
return '';
}
if (depth > MAX_SCAN_DEPTH) {
console.warn(`Invalid WI scan depth ${depth}. Truncating to ${MAX_SCAN_DEPTH}`);
depth = MAX_SCAN_DEPTH;
}
let result = this.#depthBuffer.slice(this.#startDepth, depth).join('\n');
if (this.#injectBuffer.length > 0) {
result += '\n' + this.#injectBuffer.join('\n');
}
// Min activations should not include the recursion buffer
if (this.#recurseBuffer.length > 0 && scanState !== scan_state.MIN_ACTIVATIONS) {
result += '\n' + this.#recurseBuffer.join('\n');
}
return result;
}
/**
* Matches the given string against the buffer.
* @param {string} haystack The string to search in
* @param {string} needle The string to search for
* @param {WIScanEntry} entry The entry that triggered the scan
* @returns {boolean} True if the string was found in the buffer
*/
matchKeys(haystack, needle, entry) {
// If the needle is a regex, we do regex pattern matching and override all the other options
const keyRegex = parseRegexFromString(needle);
if (keyRegex) {
return keyRegex.test(haystack);
}
// Otherwise we do normal matching of plaintext with the chosen entry settings
haystack = this.#transformString(haystack, entry);
const transformedString = this.#transformString(needle, entry);
const matchWholeWords = entry.matchWholeWords ?? world_info_match_whole_words;
if (matchWholeWords) {
const keyWords = transformedString.split(/\s+/);
if (keyWords.length > 1) {
return haystack.includes(transformedString);
}
else {
// Use custom boundaries to include punctuation and other non-alphanumeric characters
const regex = new RegExp(`(?:^|\\W)(${escapeRegex(transformedString)})(?:$|\\W)`);
if (regex.test(haystack)) {
return true;
}
}
} else {
return haystack.includes(transformedString);
}
return false;
}
/**
* Adds a message to the recursion buffer.
* @param {string} message The message to add
*/
addRecurse(message) {
this.#recurseBuffer.push(message);
}
/**
* Adds an injection to the buffer.
* @param {string} message The injection to add
*/
addInject(message) {
this.#injectBuffer.push(message);
}
/**
* Increments skew and sets startDepth to previous depth.
*/
advanceScanPosition() {
this.#startDepth = this.getDepth();
this.#skew++;
}
/**
* @returns {number} Settings' depth + current skew.
*/
getDepth() {
return world_info_depth + this.#skew;
}
/**
* Check if the current entry is externally activated.
* @param {object} entry WI entry to check
* @returns {boolean} True if the entry is forcefully activated
*/
isExternallyActivated(entry) {
// Entries could be copied with structuredClone, so we need to compare them by string representation
return WorldInfoBuffer.externalActivations.some(x => JSON.stringify(x) === JSON.stringify(entry));
}
/**
2024-06-23 00:52:10 +02:00
* Clean-up the external effects for entries.
*/
resetExternalEffects() {
WorldInfoBuffer.externalActivations.splice(0, WorldInfoBuffer.externalActivations.length);
}
2024-05-04 22:51:28 +02:00
/**
* Gets the match score for the given entry.
* @param {WIScanEntry} entry Entry to check
* @param {number} scanState The state of the scan
2024-05-04 22:51:28 +02:00
* @returns {number} The number of key activations for the given entry
*/
getScore(entry, scanState) {
const bufferState = this.get(entry, scanState);
2024-05-04 22:51:28 +02:00
let numberOfPrimaryKeys = 0;
let numberOfSecondaryKeys = 0;
let primaryScore = 0;
let secondaryScore = 0;
// Increment score for every key found in the buffer
if (Array.isArray(entry.key)) {
numberOfPrimaryKeys = entry.key.length;
for (const key of entry.key) {
if (this.matchKeys(bufferState, key, entry)) {
primaryScore++;
}
}
}
// Increment score for every secondary key found in the buffer
if (Array.isArray(entry.keysecondary)) {
numberOfSecondaryKeys = entry.keysecondary.length;
for (const key of entry.keysecondary) {
if (this.matchKeys(bufferState, key, entry)) {
secondaryScore++;
}
}
}
// No keys == no score
if (!numberOfPrimaryKeys) {
return 0;
}
// Only positive logic influences the score
if (numberOfSecondaryKeys > 0) {
switch (entry.selectiveLogic) {
// AND_ANY: Add both scores
case world_info_logic.AND_ANY:
return primaryScore + secondaryScore;
// AND_ALL: Add both scores if all secondary keys are found, otherwise only primary score
case world_info_logic.AND_ALL:
return secondaryScore === numberOfSecondaryKeys ? primaryScore + secondaryScore : primaryScore;
}
}
return primaryScore;
}
}
2024-06-22 13:56:46 +02:00
/**
* Represents a timed effects manager for World Info.
2024-06-22 13:56:46 +02:00
*/
class WorldInfoTimedEffects {
2024-06-22 02:15:13 +02:00
/**
2024-06-22 13:56:46 +02:00
* Cache for entry hashes. Uses weak map to avoid memory leaks.
* @type {WeakMap<WIScanEntry, number>}
2024-06-22 02:15:13 +02:00
*/
2024-06-22 13:56:46 +02:00
#entryHashCache = new WeakMap();
2024-06-22 02:15:13 +02:00
/**
2024-06-24 01:33:51 +02:00
* Array of chat messages.
2024-06-23 00:53:45 +02:00
* @type {string[]}
2024-06-22 02:15:13 +02:00
*/
2024-06-22 13:56:46 +02:00
#chat = [];
2024-06-22 02:15:13 +02:00
/**
2024-06-24 01:33:51 +02:00
* Array of entries.
2024-06-23 00:53:45 +02:00
* @type {WIScanEntry[]}
2024-06-22 02:15:13 +02:00
*/
2024-06-22 13:56:46 +02:00
#entries = [];
2024-06-22 02:15:13 +02:00
/**
2024-06-24 01:33:51 +02:00
* Buffer for active timed effects.
* @type {Record<TimedEffectType, WIScanEntry[]>}
2024-06-22 02:15:13 +02:00
*/
2024-06-23 19:34:07 +02:00
#buffer = {
'sticky': [],
'cooldown': [],
2024-06-26 21:43:30 +02:00
'delay': [],
2024-06-23 19:34:07 +02:00
};
2024-06-22 02:15:13 +02:00
2024-06-24 01:33:51 +02:00
/**
* Callbacks for effect types ending.
* @type {Record<TimedEffectType, (entry: WIScanEntry) => void>}
*/
#onEnded = {
/**
* Callback for when a sticky entry ends.
* Sets an entry on cooldown immediately if it has a cooldown.
* @param {WIScanEntry} entry Entry that ended sticky
*/
'sticky': (entry) => {
if (!entry.cooldown) {
return;
}
const key = this.#getEntryKey(entry);
const effect = this.#getEntryTimedEffect('cooldown', entry, true);
2024-06-24 01:33:51 +02:00
chat_metadata.timedWorldInfo.cooldown[key] = effect;
console.log(`Adding cooldown entry ${key} on ended sticky: start=${effect.start}, end=${effect.end}, protected=${effect.protected}`);
2024-06-24 01:33:51 +02:00
// Set the cooldown immediately for this evaluation
this.#buffer.cooldown.push(entry);
2024-06-24 01:33:51 +02:00
},
/**
* Callback for when a cooldown entry ends.
* No-op, essentially.
* @param {WIScanEntry} entry Entry that ended cooldown
*/
'cooldown': (entry) => {
console.debug('Cooldown ended for entry', entry.uid);
},
2024-06-26 21:43:30 +02:00
'delay': () => {},
2024-06-24 01:33:51 +02:00
};
2024-06-22 02:15:13 +02:00
/**
* Initialize the timed effects with the given messages.
2024-06-22 02:15:13 +02:00
* @param {string[]} chat Array of chat messages
* @param {WIScanEntry[]} entries Array of entries
*/
constructor(chat, entries) {
this.#chat = chat;
this.#entries = entries;
this.#ensureChatMetadata();
}
/**
* Verify correct structure of chat metadata.
*/
#ensureChatMetadata() {
if (!chat_metadata.timedWorldInfo) {
2024-06-22 02:54:54 +02:00
chat_metadata.timedWorldInfo = {};
2024-06-22 02:15:13 +02:00
}
2024-06-22 02:54:54 +02:00
['sticky', 'cooldown'].forEach(type => {
// Ensure the property exists and is an object
if (!chat_metadata.timedWorldInfo[type] || typeof chat_metadata.timedWorldInfo[type] !== 'object') {
chat_metadata.timedWorldInfo[type] = {};
}
2024-06-22 02:15:13 +02:00
2024-06-22 02:54:54 +02:00
// Clean up invalid entries
Object.entries(chat_metadata.timedWorldInfo[type]).forEach(([key, value]) => {
if (!value || typeof value !== 'object') {
delete chat_metadata.timedWorldInfo[type][key];
}
});
});
2024-06-22 02:15:13 +02:00
}
/**
* Gets a hash for a WI entry.
2024-06-22 02:54:54 +02:00
* @param {WIScanEntry} entry WI entry
2024-06-22 02:15:13 +02:00
* @returns {number} String hash
*/
#getEntryHash(entry) {
2024-06-22 13:56:46 +02:00
if (this.#entryHashCache.has(entry)) {
return this.#entryHashCache.get(entry);
2024-06-22 02:15:13 +02:00
}
const hash = getStringHash(JSON.stringify(entry));
2024-06-22 13:56:46 +02:00
this.#entryHashCache.set(entry, hash);
2024-06-22 02:15:13 +02:00
return hash;
}
2024-06-22 02:54:54 +02:00
/**
* Gets a unique-ish key for a WI entry.
* @param {WIScanEntry} entry WI entry
* @returns {string} String key for the entry
*/
#getEntryKey(entry) {
return `${entry.world}.${entry.uid}`;
}
/**
* Gets a timed effect for a WI entry.
* @param {TimedEffectType} type Type of timed effect
* @param {WIScanEntry} entry WI entry
* @param {boolean} isProtected If the effect should be protected
* @returns {WITimedEffect} Timed effect for the entry
2024-06-22 02:54:54 +02:00
*/
#getEntryTimedEffect(type, entry, isProtected) {
2024-06-22 02:54:54 +02:00
return {
hash: this.#getEntryHash(entry),
start: this.#chat.length,
end: this.#chat.length + Number(entry[type]),
protected: !!isProtected,
2024-06-22 02:54:54 +02:00
};
}
2024-06-22 02:15:13 +02:00
/**
* Processes entries for a given type of timed effect.
* @param {TimedEffectType} type Identifier for the type of timed effect
2024-06-22 02:15:13 +02:00
* @param {WIScanEntry[]} buffer Buffer to store the entries
* @param {(entry: WIScanEntry) => void} onEnded Callback for when a timed effect ends
2024-06-22 02:15:13 +02:00
*/
#checkTimedEffectOfType(type, buffer, onEnded) {
/** @type {[string, WITimedEffect][]} */
const effects = Object.entries(chat_metadata.timedWorldInfo[type]);
for (const [key, value] of effects) {
2024-06-22 02:54:54 +02:00
console.log(`Processing ${type} entry ${key}`, value);
const entry = this.#entries.find(x => String(this.#getEntryHash(x)) === String(value.hash));
if (this.#chat.length <= Number(value.start) && !value.protected) {
2024-06-22 02:54:54 +02:00
console.log(`Removing ${type} entry ${key} from timedWorldInfo: chat not advanced`, value);
delete chat_metadata.timedWorldInfo[type][key];
2024-06-22 02:15:13 +02:00
continue;
}
2024-06-22 02:54:54 +02:00
// Missing entries (they could be from another character's lorebook)
2024-06-22 02:15:13 +02:00
if (!entry) {
if (this.#chat.length >= Number(value.end)) {
2024-06-22 02:54:54 +02:00
console.log(`Removing ${type} entry from timedWorldInfo: entry not found and interval passed`, entry);
delete chat_metadata.timedWorldInfo[type][key];
}
2024-06-22 02:15:13 +02:00
continue;
}
// Ignore invalid entries (not configured for timed effects)
if (!entry[type]) {
console.log(`Removing ${type} entry from timedWorldInfo: entry not ${type}`, entry);
2024-06-22 02:54:54 +02:00
delete chat_metadata.timedWorldInfo[type][key];
2024-06-22 02:15:13 +02:00
continue;
}
if (this.#chat.length >= Number(value.end)) {
2024-06-22 02:15:13 +02:00
console.log(`Removing ${type} entry from timedWorldInfo: ${type} interval passed`, entry);
2024-06-22 02:54:54 +02:00
delete chat_metadata.timedWorldInfo[type][key];
2024-06-22 02:15:13 +02:00
if (typeof onEnded === 'function') {
onEnded(entry);
}
continue;
}
buffer.push(entry);
console.log(`Timed effect "${type}" applied to entry`, entry);
}
}
2024-06-26 21:43:30 +02:00
/**
* Processes entries for the "delay" timed effect.
* @param {WIScanEntry[]} buffer Buffer to store the entries
*/
#checkDelayEffect(buffer) {
for (const entry of this.#entries) {
if (!entry.delay) {
continue;
}
if (this.#chat.length < entry.delay) {
buffer.push(entry);
console.log('Timed effect "delay" applied to entry', entry);
}
}
}
2024-06-22 02:15:13 +02:00
/**
2024-06-22 02:54:54 +02:00
* Checks for timed effects on chat messages.
2024-06-22 02:15:13 +02:00
*/
checkTimedEffects() {
2024-06-24 01:33:51 +02:00
this.#checkTimedEffectOfType('sticky', this.#buffer.sticky, this.#onEnded.sticky.bind(this));
this.#checkTimedEffectOfType('cooldown', this.#buffer.cooldown, this.#onEnded.cooldown.bind(this));
2024-06-26 21:43:30 +02:00
this.#checkDelayEffect(this.#buffer.delay);
2024-06-22 02:15:13 +02:00
}
/**
* Gets raw timed effect metadatum for a WI entry.
* @param {TimedEffectType} type Type of timed effect
* @param {WIScanEntry} entry WI entry
* @returns {WITimedEffect} Timed effect for the entry
*/
getEffectMetadata(type, entry) {
if (!this.isValidEffectType(type)) {
return null;
}
const key = this.#getEntryKey(entry);
return chat_metadata.timedWorldInfo[type][key];
}
2024-06-22 02:15:13 +02:00
/**
* Sets a timed effect for a WI entry.
* @param {TimedEffectType} type Type of timed effect
2024-06-22 02:54:54 +02:00
* @param {WIScanEntry} entry WI entry to check
2024-06-22 02:15:13 +02:00
*/
#setTimedEffectOfType(type, entry) {
2024-06-22 02:54:54 +02:00
// Skip if entry does not have the type (sticky or cooldown)
if (!entry[type]) {
return;
}
2024-06-22 02:15:13 +02:00
2024-06-22 02:54:54 +02:00
const key = this.#getEntryKey(entry);
if (!chat_metadata.timedWorldInfo[type][key]) {
const effect = this.#getEntryTimedEffect(type, entry, false);
chat_metadata.timedWorldInfo[type][key] = effect;
2024-06-22 02:54:54 +02:00
console.log(`Adding ${type} entry ${key}: start=${effect.start}, end=${effect.end}, protected=${effect.protected}`);
2024-06-22 02:54:54 +02:00
}
2024-06-22 02:15:13 +02:00
}
/**
* Sets timed effects on chat messages.
* @param {WIScanEntry[]} activatedEntries Entries that were activated
*/
setTimedEffects(activatedEntries) {
2024-06-22 02:15:13 +02:00
for (const entry of activatedEntries) {
this.#setTimedEffectOfType('sticky', entry);
this.#setTimedEffectOfType('cooldown', entry);
2024-06-22 02:15:13 +02:00
}
}
/**
* Force set a timed effect for a WI entry.
* @param {TimedEffectType} type Type of timed effect
* @param {WIScanEntry} entry WI entry
* @param {boolean} newState The state of the effect
*/
setTimedEffect(type, entry, newState) {
if (!this.isValidEffectType(type)) {
return;
}
const key = this.#getEntryKey(entry);
delete chat_metadata.timedWorldInfo[type][key];
if (newState) {
const effect = this.#getEntryTimedEffect(type, entry, false);
chat_metadata.timedWorldInfo[type][key] = effect;
console.log(`Adding ${type} entry ${key}: start=${effect.start}, end=${effect.end}, protected=${effect.protected}`);
}
}
/**
* Check if the string is a valid timed effect type.
* @param {string} type Name of the timed effect
2024-06-23 19:34:07 +02:00
* @returns {boolean} Is recognized type
2024-06-22 02:15:13 +02:00
*/
2024-06-23 19:34:07 +02:00
isValidEffectType(type) {
2024-06-26 21:43:30 +02:00
return typeof type === 'string' && ['sticky', 'cooldown', 'delay'].includes(type.trim().toLowerCase());
2024-06-22 02:15:13 +02:00
}
/**
2024-06-23 19:34:07 +02:00
* Check if the current entry is sticky activated.
* @param {TimedEffectType} type Type of timed effect
2024-06-23 19:34:07 +02:00
* @param {WIScanEntry} entry WI entry to check
* @returns {boolean} True if the entry is active
2024-06-22 02:15:13 +02:00
*/
2024-06-23 19:34:07 +02:00
isEffectActive(type, entry) {
if (!this.isValidEffectType(type)) {
return false;
}
return this.#buffer[type]?.some(x => this.#getEntryHash(x) === this.#getEntryHash(entry)) ?? false;
2024-06-22 02:15:13 +02:00
}
/**
* Clean-up previously set timed effects.
2024-06-22 02:15:13 +02:00
*/
cleanUp() {
2024-06-23 19:34:07 +02:00
for (const buffer of Object.values(this.#buffer)) {
buffer.splice(0, buffer.length);
}
2024-06-22 02:15:13 +02:00
}
}
export function getWorldInfoSettings() {
return {
world_info,
world_info_depth,
world_info_min_activations,
world_info_min_activations_depth_max,
world_info_budget,
world_info_include_names,
world_info_recursive,
world_info_overflow_alert,
world_info_case_sensitive,
world_info_match_whole_words,
world_info_character_strategy,
world_info_budget_cap,
2024-05-04 22:51:28 +02:00
world_info_use_group_scoring,
2023-12-02 20:11:06 +01:00
};
}
2023-07-20 19:32:15 +02:00
const world_info_position = {
before: 0,
after: 1,
ANTop: 2,
ANBottom: 3,
2023-09-21 09:04:34 +02:00
atDepth: 4,
EMTop: 5,
EMBottom: 6,
2023-07-20 19:32:15 +02:00
};
export const wi_anchor_position = {
before: 0,
after: 1,
2023-07-20 19:32:15 +02:00
};
const worldInfoCache = new Map();
2023-07-20 19:32:15 +02:00
/**
* Gets the world info based on chat messages.
* @param {string[]} chat The chat messages to scan, in reverse order.
* @param {number} maxContext The maximum context size of the generation.
* @param {boolean} isDryRun If true, the function will not emit any events.
* @typedef {{worldInfoString: string, worldInfoBefore: string, worldInfoAfter: string, worldInfoExamples: any[], worldInfoDepth: any[]}} WIPromptResult
* @returns {Promise<WIPromptResult>} The world info string and depth.
*/
async function getWorldInfoPrompt(chat, maxContext, isDryRun) {
2023-12-02 19:04:51 +01:00
let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';
2023-07-20 19:32:15 +02:00
const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun);
2023-07-20 19:32:15 +02:00
worldInfoBefore = activatedWorldInfo.worldInfoBefore;
worldInfoAfter = activatedWorldInfo.worldInfoAfter;
worldInfoString = worldInfoBefore + worldInfoAfter;
if (!isDryRun && activatedWorldInfo.allActivatedEntries && activatedWorldInfo.allActivatedEntries.size > 0) {
const arg = Array.from(activatedWorldInfo.allActivatedEntries);
await eventSource.emit(event_types.WORLD_INFO_ACTIVATED, arg);
}
return {
worldInfoString,
worldInfoBefore,
worldInfoAfter,
2024-05-08 14:04:17 +02:00
worldInfoExamples: activatedWorldInfo.EMEntries ?? [],
worldInfoDepth: activatedWorldInfo.WIDepthEntries ?? [],
};
2023-07-20 19:32:15 +02:00
}
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);
2023-07-20 19:32:15 +02:00
if (settings.world_info_budget !== undefined)
world_info_budget = Number(settings.world_info_budget);
if (settings.world_info_include_names !== undefined)
world_info_include_names = Boolean(settings.world_info_include_names);
2023-07-20 19:32:15 +02:00
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);
2023-07-20 19:32:15 +02:00
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);
2024-05-04 22:51:28 +02:00
if (settings.world_info_use_group_scoring !== undefined)
world_info_use_group_scoring = Boolean(settings.world_info_use_group_scoring);
2023-07-20 19:32:15 +02:00
// Migrate old settings
if (world_info_budget > 100) {
world_info_budget = 25;
}
2024-05-04 22:51:28 +02:00
if (world_info_use_group_scoring === undefined) {
world_info_use_group_scoring = false;
}
2023-07-20 19:32:15 +02:00
// Reset selected world from old string and delete old keys
// TODO: Remove next release
const existingWorldInfo = settings.world_info;
2023-12-02 19:04:51 +01:00
if (typeof existingWorldInfo === 'string') {
2023-07-20 19:32:15 +02:00
delete settings.world_info;
selected_world_info = [existingWorldInfo];
} else if (Array.isArray(existingWorldInfo)) {
delete settings.world_info;
selected_world_info = existingWorldInfo;
}
2023-12-02 20:11:06 +01:00
world_info = settings.world_info ?? {};
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
$('#world_info_depth_counter').val(world_info_depth);
$('#world_info_depth').val(world_info_depth);
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
$('#world_info_min_activations_counter').val(world_info_min_activations);
$('#world_info_min_activations').val(world_info_min_activations);
2023-12-02 19:04:51 +01:00
$('#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);
2023-12-02 19:04:51 +01:00
$('#world_info_budget_counter').val(world_info_budget);
$('#world_info_budget').val(world_info_budget);
2023-07-20 19:32:15 +02:00
$('#world_info_include_names').prop('checked', world_info_include_names);
2023-12-02 19:04:51 +01:00
$('#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);
2024-05-04 22:51:28 +02:00
$('#world_info_use_group_scoring').prop('checked', world_info_use_group_scoring);
2023-07-20 19:32:15 +02:00
$(`#world_info_character_strategy option[value='${world_info_character_strategy}']`).prop('selected', true);
2023-12-02 19:04:51 +01:00
$('#world_info_character_strategy').val(world_info_character_strategy);
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
$('#world_info_budget_cap').val(world_info_budget_cap);
$('#world_info_budget_cap_counter').val(world_info_budget_cap);
2023-07-20 19:32:15 +02:00
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) {
2023-12-02 19:04:51 +01:00
$('#world_info').empty();
2023-07-20 19:32:15 +02:00
}
world_names.forEach((item, i) => {
2023-12-02 19:04:51 +01:00
$('#world_info').append(`<option value='${i}'${selected_world_info.includes(item) ? ' selected' : ''}>${item}</option>`);
$('#world_editor_select').append(`<option value='${i}'>${item}</option>`);
2023-07-20 19:32:15 +02:00
});
$('#world_info_sort_order').val(localStorage.getItem(SORT_ORDER_KEY) || '0');
2024-05-02 20:04:24 +02:00
$('#world_info').trigger('change');
2023-12-02 19:04:51 +01:00
$('#world_editor_select').trigger('change');
2023-10-16 22:03:42 +02:00
eventSource.on(event_types.CHAT_CHANGED, async () => {
2023-10-16 22:03:42 +02:00
const hasWorldInfo = !!chat_metadata[METADATA_KEY] && world_names.includes(chat_metadata[METADATA_KEY]);
$('.chat_lorebook_button').toggleClass('world_set', hasWorldInfo);
// Pre-cache the world info data for the chat for quicker first prompt generation
await getSortedEntries();
2023-10-16 22:03:42 +02:00
});
eventSource.on(event_types.WORLDINFO_FORCE_ACTIVATE, (entries) => {
WorldInfoBuffer.externalActivations.push(...entries);
});
// Add slash commands
registerWorldInfoSlashCommands();
}
function registerWorldInfoSlashCommands() {
2023-12-01 20:51:49 +01:00
function reloadEditor(file) {
const selectedIndex = world_names.indexOf(file);
if (selectedIndex !== -1) {
$('#world_editor_select').val(selectedIndex).trigger('change');
}
}
2024-06-23 20:35:31 +02:00
/**
* Gets a *rough* approximation of the current chat context.
* Normally, it is provided externally by the prompt builder.
* Don't use for anything critical!
* @returns {string[]}
*/
function getScanningChat() {
return getContext().chat.filter(x => !x.is_system).map(x => x.mes);
}
async function getEntriesFromFile(file) {
if (!file || !world_names.includes(file)) {
2023-12-01 01:25:55 +01:00
toastr.warning('Valid World Info file name is required');
return '';
}
const data = await loadWorldInfoData(file);
2023-12-02 19:04:51 +01:00
if (!data || !('entries' in data)) {
2023-12-01 01:25:55 +01:00
toastr.warning('World Info file has an invalid format');
return '';
}
const entries = Object.values(data.entries);
if (!entries || entries.length === 0) {
2023-12-01 01:25:55 +01:00
toastr.warning('World Info file has no entries');
return '';
}
return entries;
}
async function getChatBookCallback() {
const chatId = getCurrentChatId();
if (!chatId) {
2023-12-01 01:25:55 +01:00
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 = args.file;
const field = args.field || 'key';
const entries = await getEntriesFromFile(file);
if (!entries) {
return '';
}
if (typeof newEntryTemplate[field] === 'boolean') {
const isTrue = isTrueBoolean(value);
const isFalse = isFalseBoolean(value);
if (isTrue) {
value = String(true);
}
if (isFalse) {
value = String(false);
}
}
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 = args.file;
const field = args.field || 'content';
const entries = await getEntriesFromFile(file);
if (!entries) {
return '';
}
2023-12-07 17:45:34 +01:00
const entry = entries.find(x => String(x.uid) === String(uid));
if (!entry) {
2023-12-01 20:51:49 +01:00
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 JSON.stringify(fieldValue.map(x => substituteParams(x)));
}
2023-12-01 20:51:49 +01:00
return substituteParams(String(fieldValue));
}
async function createEntryCallback(args, content) {
const file = args.file;
const key = args.key;
const data = await loadWorldInfoData(file);
2023-12-02 19:04:51 +01:00
if (!data || !('entries' in data)) {
2023-12-01 01:25:55 +01:00
toastr.warning('Valid World Info file name is required');
return '';
}
const entry = createWorldInfoEntry(file, data);
if (key) {
entry.key.push(key);
entry.addMemo = true;
entry.comment = key;
}
if (content) {
entry.content = content;
}
await saveWorldInfo(file, data, true);
2023-12-01 20:51:49 +01:00
reloadEditor(file);
return String(entry.uid);
2023-12-01 20:51:49 +01:00
}
async function setEntryFieldCallback(args, value) {
const file = args.file;
const uid = args.uid;
2023-12-01 20:51:49 +01:00
const field = args.field || 'content';
if (value === undefined) {
toastr.warning('Value is required');
return '';
}
value = value.replace(/\\([{}|])/g, '$1');
2023-12-01 20:51:49 +01:00
const data = await loadWorldInfoData(file);
2023-12-02 19:04:51 +01:00
if (!data || !('entries' in data)) {
2023-12-01 20:51:49 +01:00
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] = parseStringArray(value);
2023-12-01 20:51:49 +01:00
} 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 '';
}
2024-06-23 20:35:31 +02:00
async function getTimedEffectCallback(args, value) {
if (!getCurrentChatId()) {
throw new Error('This command can only be used in chat');
}
2024-06-24 00:08:24 +02:00
const file = args.file;
const uid = value;
2024-06-23 20:35:31 +02:00
const effect = args.effect;
const entries = await getEntriesFromFile(file);
if (!entries) {
return '';
}
/** @type {WIScanEntry} */
const entry = structuredClone(entries.find(x => String(x.uid) === String(uid)));
if (!entry) {
toastr.warning('Valid UID is required');
return '';
}
entry.world = file; // Required by the timed effects manager
const chat = getScanningChat();
const timedEffects = new WorldInfoTimedEffects(chat, [entry]);
if (!timedEffects.isValidEffectType(effect)) {
toastr.warning('Valid effect type is required');
return '';
}
const data = timedEffects.getEffectMetadata(effect, entry);
if (String(args.format).trim().toLowerCase() === ARGUMENT_TYPE.NUMBER) {
return String(data ? (data.end - chat.length) : 0);
}
return String(!!data);
2024-06-23 20:35:31 +02:00
}
async function setTimedEffectCallback(args, value) {
if (!getCurrentChatId()) {
throw new Error('This command can only be used in chat');
}
2024-06-24 00:08:24 +02:00
const file = args.file;
const uid = args.uid;
const effect = args.effect;
if (value === undefined) {
toastr.warning('New state is required');
return '';
}
const entries = await getEntriesFromFile(file);
if (!entries) {
return '';
}
/** @type {WIScanEntry} */
const entry = structuredClone(entries.find(x => String(x.uid) === String(uid)));
if (!entry) {
toastr.warning('Valid UID is required');
return '';
}
entry.world = file; // Required by the timed effects manager
2024-06-23 20:35:31 +02:00
const chat = getScanningChat();
const timedEffects = new WorldInfoTimedEffects(chat, [entry]);
if (!timedEffects.isValidEffectType(effect)) {
toastr.warning('Valid effect type is required');
return '';
}
if (!entry[effect]) {
toastr.warning('This entry does not have the selected effect. Configure it in the editor first.');
return '';
}
const getNewEffectState = () => {
const currentState = !!timedEffects.getEffectMetadata(effect, entry);
if (['toggle', 't', ''].includes(value.trim().toLowerCase())) {
return !currentState;
}
if (isTrueBoolean(value)) {
return true;
}
if (isFalseBoolean(value)) {
return false;
}
return currentState;
};
const newEffectState = getNewEffectState();
timedEffects.setTimedEffect(effect, entry, newEffectState);
await saveMetadata();
toastr.success(`Timed effect "${effect}" for entry ${entry.uid} is now ${newEffectState ? 'active' : 'inactive'}`);
return '';
}
/** A collection of local enum providers for this context of world info */
const localEnumProviders = {
/** All possible fields that can be set in a WI entry */
wiEntryFields: () => Object.entries(newEntryDefinition).map(([key, value]) =>
new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : value.default)}`,
2024-06-21 20:04:55 +02:00
enumTypes.enum, enumIcons.getDataTypeIcon(value.type))),
/** All existing UIDs based on the file argument as world name */
wiUids: (/** @type {SlashCommandExecutor} */ executor) => {
const file = executor.namedArgumentList.find(it => it.name == 'file')?.value;
if (file instanceof SlashCommandClosure) throw new Error('Argument \'file\' does not support closures');
// Try find world from cache
if (!worldInfoCache.has(file)) return [];
const world = worldInfoCache.get(file);
if (!world) return [];
return Object.entries(world.entries).map(([uid, data]) =>
new SlashCommandEnumValue(uid, `${data.comment ? `${data.comment}: ` : ''}${data.key.join(', ')}${data.keysecondary?.length ? ` [${Object.entries(world_info_logic).find(([_, value]) => value == data.selectiveLogic)[0]}] ${data.keysecondary.join(', ')}` : ''} [${getWiPositionString(data)}]`,
enumTypes.enum, enumIcons.getWiStatusIcon(data)));
},
timedEffects: () => [
new SlashCommandEnumValue('sticky', 'Stays active for N messages', enumTypes.enum, '📌'),
new SlashCommandEnumValue('cooldown', 'Cooldown for N messages', enumTypes.enum, '⌛'),
],
};
function getWiPositionString(entry) {
switch (entry.position) {
case world_info_position.before: return '↑Char';
case world_info_position.after: return '↓Char';
case world_info_position.EMTop: return '↑EM';
case world_info_position.EMBottom: return '↓EM';
case world_info_position.ANTop: return '↑AT';
case world_info_position.ANBottom: return '↓AT';
case world_info_position.atDepth: return `@D${enumIcons.getRoleIcon(entry.role)}`;
default: return '<Unknown>';
}
}
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'world',
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
callback: onWorldInfoChange,
namedArgumentList: [
new SlashCommandNamedArgument(
'state', 'set world state', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOffToggle')(),
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
),
new SlashCommandNamedArgument(
'silent', 'suppress toast messages', [ARGUMENT_TYPE.BOOLEAN], false,
),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'world name',
typeList: [ARGUMENT_TYPE.STRING],
enumProvider: commonEnumProviders.worlds,
}),
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
],
helpString: `
<div>
Sets active World, or unsets if no args provided, use <code>state=off</code> and <code>state=toggle</code> to deactivate or toggle a World, use <code>silent=true</code> to suppress toast messages.
</div>
`,
aliases: [],
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'getchatbook',
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
callback: getChatBookCallback,
returns: 'lorebook name',
helpString: 'Get a name of the chat-bound lorebook or create a new one if was unbound, and pass it down the pipe.',
aliases: ['getchatlore', 'getchatwi'],
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'findentry',
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
aliases: ['findlore', 'findwi'],
returns: 'UID',
callback: findBookEntryCallback,
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'file',
description: 'book name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.worlds,
}),
SlashCommandNamedArgument.fromProps({
name: 'field',
description: 'field value for fuzzy match (default: key)',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'key',
enumList: localEnumProviders.wiEntryFields(),
}),
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
],
unnamedArgumentList: [
new SlashCommandArgument(
'texts', ARGUMENT_TYPE.STRING, true, true,
),
],
helpString: `
<div>
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.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/findentry file=chatLore field=key Shadowfang</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'getentryfield',
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
aliases: ['getlorefield', 'getwifield'],
callback: getEntryFieldCallback,
returns: 'field value',
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'file',
description: 'book name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.worlds,
}),
SlashCommandNamedArgument.fromProps({
name: 'field',
description: 'field to retrieve (default: content)',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'content',
enumList: localEnumProviders.wiEntryFields(),
}),
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'record UID',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.wiUids,
}),
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
],
helpString: `
<div>
Get a field value (default: content) of the record with the UID from the specified book and pass it down the pipe.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/getentryfield file=chatLore field=content 123</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'createentry',
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
callback: createEntryCallback,
aliases: ['createlore', 'createwi'],
returns: 'UID of the new record',
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'file',
description: 'book name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.worlds,
}),
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
new SlashCommandNamedArgument(
'key', 'record key', [ARGUMENT_TYPE.STRING], false,
),
],
unnamedArgumentList: [
new SlashCommandArgument(
'content', [ARGUMENT_TYPE.STRING], false,
),
],
helpString: `
<div>
Create a new record in the specified book with the key and content (both are optional) and pass the UID down the pipe.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/createentry file=chatLore key=Shadowfang The sword of the king</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'setentryfield',
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
callback: setEntryFieldCallback,
aliases: ['setlorefield', 'setwifield'],
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'file',
description: 'book name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.worlds,
}),
SlashCommandNamedArgument.fromProps({
name: 'uid',
description: 'record UID',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.wiUids,
}),
SlashCommandNamedArgument.fromProps({
name: 'field',
description: 'field name (default: content)',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'content',
enumList: localEnumProviders.wiEntryFields(),
}),
STscript Parser Rewrite (#1965) * set isForced to true on input * make floating auto-complete follow horizontal scrolling * add callable closure vars * changes to /let and /var for callable closures * fix error message * fix scope for closure arguments * if should return the pipe result from closures * use /run to call closures and no arguments on immediate closures * throw exception from QRs window-function if no match * when to show autocomplete vs info only * autocomplete positioning * autocomplete styling * add theming to autocomplete (theme, dark, light) * improve autocomplete show/hide logic and editor selection * use blur tint color instead of chat tint color and use blur setting * cleanup and docs * use scope macros for QR args * add enter to select autocomplete * fix no executor found * cleanup and comment * fix alias list in help string * fallback to empty string piped value if null or undefined * fix typo * blur textarea on ctrl+enter execute (and refocus after) * stop executeSlashCommand if parser throws * move /let and /var callbacks into functions * switch textarea to monospace when value starts with slash * add double pipe a pipe breaker * fix /? slash * remove some logging * add "/:name" as shorthand for "/run name" after all * move shit around * fix error message * use testRunShorthandEnd * use parseQuotedValue and parseValue to determine name for "/:" QR labels and set names can include spaces * add some adjustments to make autocomplete work properly some hint in there about "/:" would still be nice * add autocomplete style selector * only strip quotes from subcommand if they are at both ends * fix JSDoc * escaping * allow open quotes on dry run * throwing shit at the wall for /: autocomplete * escapes only for symbols * clean up autocomplete * improve performance * fix scope macros * remove unescaping of pipes * fix macros in scope copy * fix "/? slash" * don't run parser for getNameAt if text has not changed * fix options filter * re-enable blur listener * restore selection on non-replace select * fix for escaping first character of value * add support for {{pipe}} and {{var::}} closures * add index support to var macro * add scoped var macro to macro help * more escape fixes * reduce autocomplete render debounce * cleanup * restore old escape handling and parser flag for strict escaping * fix "no match" autocomplete message * add dummy commands for comments and parser flag * fix type annotations * somewhat safer macro replacements * fix autocomplete select on blank / "no match" * fix cutting off handled part in substitution * add parser flag REPLACE_GETVAR Replaces all {{getvar::}} and {{getglobalvar::}} macros with {{var::}}. Inserts a series of command executors before the command with the macros that: - save {{pipe}} to a var - call /getvar or /getglobalvar to get the variable used in the macro - call /let to save the retrieved variable - return the saved {{pipe}} value This helps to avoid double-substitutions when the var values contain text that could be interpreted as macros. * remove old parser * fix send on enter when no match * deal with pipes in quoted values (loose escaping) * add default parser flags to user settings * allow quoted values in unnamed argument * set parser flag without explicit state to "on" * add click hint on parser error toast * dirty more detailed cmd defs * remove name from unnamed arg * move autocomplete into class and floating with details * replace jQuery's trigger('input') on #send_textarea with native events because jQuery does not dispatch the native event * fix ctrl+space * fix arrow navigation * add comments * fix pointer block * add static fromProps * fix up dummy commands * migrate all commands to addCommandObject * remove commented comment command * fix alias in details * add range as argument type * switch to addCommandObject * switch to addCommandObject * fix height * fix floating details position on left * re-enable blur event * use auto width for full details on floating autocomplete * auto-size floating full details * fix typo * re-enable blur listener * don't prevent enter when selected item is fully typed out * add autocomplete details tooltips * add language to slash command examples * move makeItem into option and command and fix click select * use autocomplete parts in /? slash * fix alias formatting * add language to slash command examples * fix details position on initial input history * small screen styles * replace registerSlashCommand with detailed declarations * put name on first line * add missing returns * fix missing comma * fix alias display in autocomplete list * remove args from help string * move parser settings to its own section * jsdoc * hljs stscript lang * add hljs to autocomplete help examples * add missing import * apply autocomplete colors to stscript codeblocks (hljs) * add fromProps * cache autocomplete elements * towards generic autocomplete * remove unused imports * fix blanks * add return types * re-enable blur * fix blank check * Caption messages by id * add aborting command execution * fix return type * fix chat input font reset * add slash command progress indicator * add missing return * mark registerSlashCommand deprecated * why?? * separate abort logic for commands * remove parsing of quoted values from unnamed arg * add adjustable autocomplete width * revert stop button pulse * add progress and pause/abort to QR editor * add resize event on autocomplete width change * add key= argument to all get vars * refactoring * introduce NamedArgumentAsignment * add TODOs * refactoring * record start and end of named arg assignment * refactoring * prevent duplicate calls to show * refactoring * remove macro ac * add secondary autocomplete and enum descriptions * add syntax highlighting to QR editor * add enum descriptions to /while * add /let key=... to scope variable names * add unnamed argument assignment class and unnamed argument splitting * fix QR editor style * remove dash before autocomplete help text * add autocomplete for unnamed enums * fix remaining dom after holding backslash * fix for unnamed enums * fix autocomplete for /parser-flag * add parser-flag enum help * fix type annotations * fix autocomplete result for /: * add colored autocomplete type icons * collapse second line autocomplete help if empty * mark optional named args in autocomplete * fix when what * remove duplicate debug buttons * dispatch input on autocomplete select * prevent grow from editor syntax layer * add auto-adjust qr editor caret color * remove text-shadow from autocomplete * join value strings in /let and /var * add /abort syntax highlight * fix attempting secondary result when there is none * rename settings headers and split autocomplete / stscript * add parser flag tooltips * add tooltips to chat width stops * fix typo * return clone of help item * fix enum string * don't make optional notice for autocomplete arguments smaller * avoid scrollbar in chat input * add rudimentary macro autocomplete * strip macro from helptext * finally remove closure delimiters around root * cleanup * fix index stuff for removed closure delimiters * fix type hint * add child commands to progress indicator * include sub-separator in macro autocomplete * remove all mentions of interruptsGeneration and purge * remove unused imports * fix syntax highlight with newline at end of input * cleanup select pointer events * coalesce onProgress call * add regex to STscript syntax highlighting * fix closure end * fix autocomplete type icon alignment * adjustments for small screens * fix removing wrong element * add missing "at=" arg to /sys, /comment, /sendas * add font scale setting for autocomplete * add target=_blank for parser flag links * fix for searching enums * remove REGEXP_MODE from hljs just causes trouble * fix autocomplete in closures * fix typo * fix type hint * Get rid of scroll bar on load * Add type hint for /send name argument. Fix 'at' types * Add 'negative' arg hint to /sd command * reenable blur event * Allow /summarize to process any text * Compact layout of script toggles * Expand CSS by default * fix double ranger indicator and adjust to narrow container * make custom css input fill available vertical space * reduce scroll lag * use default cursor on scrollbar * Clean-up module loading in index.html * fix tab indent with hljs --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2024-05-12 21:15:05 +02:00
],
unnamedArgumentList: [
new SlashCommandArgument(
'value', [ARGUMENT_TYPE.STRING], true,
),
],
helpString: `
<div>
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.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/setentryfield file=chatLore uid=123 field=key Shadowfang,sword,weapon</code></pre>
</li>
</ul>
</div>
`,
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'wi-set-timed-effect',
callback: setTimedEffectCallback,
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'file',
description: 'book name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.worlds,
}),
SlashCommandNamedArgument.fromProps({
name: 'uid',
description: 'record UID',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.wiUids,
}),
SlashCommandNamedArgument.fromProps({
name: 'effect',
description: 'effect name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.timedEffects,
}),
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'new state of the effect',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
acceptsMultiple: false,
enumList: commonEnumProviders.boolean('onOffToggle')(),
}),
],
helpString: `
<div>
Set a timed effect for the record with the UID from the specified book. The duration must be set in the entry itself.
Will only be applied for the current chat. Enabling an effect that was already active refreshes the duration.
If the last chat message is swiped or deleted, the effect will be removed.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<pre><code>/wi-set-timed-effect file=chatLore uid=123 effect=sticky on</code></pre>
</li>
</ul>
</div>
`,
}));
2024-06-23 20:35:31 +02:00
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'wi-get-timed-effect',
callback: getTimedEffectCallback,
helpString: `
<div>
Get the current state of the timed effect for the record with the UID from the specified book.
</div>
<div>
<strong>Example:</strong>
<ul>
<li>
<code>/wi-get-timed-effect file=chatLore format=bool effect=sticky 123</code> - returns true or false if the effect is active or not
</li>
<li>
<code>/wi-get-timed-effect file=chatLore format=number effect=sticky 123</code> - returns the remaining duration of the effect, or 0 if inactive
</li>
</ul>
</div>
`,
returns: 'state of the effect',
2024-06-23 20:35:31 +02:00
namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'file',
description: 'book name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: commonEnumProviders.worlds,
}),
SlashCommandNamedArgument.fromProps({
name: 'effect',
description: 'effect name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.timedEffects,
}),
SlashCommandNamedArgument.fromProps({
name: 'format',
description: 'output format',
isRequired: false,
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: ARGUMENT_TYPE.BOOLEAN,
enumList: [ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.NUMBER],
}),
2024-06-23 20:35:31 +02:00
],
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'record UID',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: true,
enumProvider: localEnumProviders.wiUids,
}),
],
}));
2023-07-20 19:32:15 +02:00
}
// 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.has(name)) {
return worldInfoCache.get(name);
2023-07-20 19:32:15 +02:00
}
2023-12-06 23:09:48 +01:00
const response = await fetch('/api/worldinfo/get', {
2023-12-02 19:04:51 +01:00
method: 'POST',
2023-07-20 19:32:15 +02:00
headers: getRequestHeaders(),
body: JSON.stringify({ name: name }),
cache: 'no-cache',
});
if (response.ok) {
const data = await response.json();
worldInfoCache.set(name, data);
2023-07-20 19:32:15 +02:00
return data;
}
return null;
}
async function updateWorldInfoList() {
2023-12-14 22:47:03 +01:00
const result = await fetch('/api/settings/get', {
2023-12-02 19:04:51 +01:00
method: 'POST',
2023-07-20 19:32:15 +02:00
headers: getRequestHeaders(),
body: JSON.stringify({}),
});
if (result.ok) {
var data = await result.json();
world_names = data.world_names?.length ? data.world_names : [];
2023-12-02 19:04:51 +01:00
$('#world_info').find('option[value!=""]').remove();
$('#world_editor_select').find('option[value!=""]').remove();
2023-07-20 19:32:15 +02:00
world_names.forEach((item, i) => {
2023-12-02 19:04:51 +01:00
$('#world_info').append(`<option value='${i}'${selected_world_info.includes(item) ? ' selected' : ''}>${item}</option>`);
$('#world_editor_select').append(`<option value='${i}'>${item}</option>`);
2023-07-20 19:32:15 +02:00
});
}
}
function hideWorldEditor() {
displayWorldEntries(null, null);
}
function getWIElement(name) {
2023-12-02 19:04:51 +01:00
const wiElement = $('#world_info').children().filter(function () {
2023-12-02 20:11:06 +01:00
return $(this).text().toLowerCase() === name.toLowerCase();
2023-07-20 19:32:15 +02:00
});
return wiElement;
}
/**
* @param {any[]} data WI entries
* @returns {any[]} Sorted data
*/
function sortEntries(data) {
2023-12-02 19:04:51 +01:00
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;
2024-04-30 01:39:47 +02:00
if (!data.length) return data;
// If we have a search term for WI, we are sorting by weighting scores
2024-04-30 02:27:44 +02:00
if (sortRule === 'search') {
2024-04-30 01:39:47 +02:00
data.sort((a, b) => {
const aScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, a.uid);
const bScore = worldInfoFilter.getScore(FILTER_TYPES.WORLD_INFO_SEARCH, b.uid);
return (aScore - bScore);
});
}
else if (sortRule === 'custom') {
2023-11-11 19:16:57 +01:00
// 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 {
2023-10-06 00:18:50 +02:00
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));
2023-10-06 00:18:50 +02:00
};
const secondarySort = (a, b) => a.order - b.order;
2023-10-06 00:18:50 +02:00
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;
}
2023-07-20 19:32:15 +02:00
function nullWorldInfo() {
2023-12-02 19:04:51 +01:00
toastr.info('Create or import a new World Info file first.', 'World Info is not set', { timeOut: 10000, preventDuplicates: true });
2023-07-20 19:32:15 +02:00
}
/** @type {Select2Option[]} Cache all keys as selectable dropdown option */
const worldEntryKeyOptionsCache = [];
/**
* Update the cache and all select options for the keys with new values to display
* @param {string[]|Select2Option[]} keyOptions - An array of options to update
* @param {object} options - Optional arguments
* @param {boolean?} [options.remove=false] - Whether the option was removed, so the count should be reduced - otherwise it'll be increased
* @param {boolean?} [options.reset=false] - Whether the cache should be reset. Reset will also not trigger update of the controls, as we expect them to be redrawn anyway
*/
function updateWorldEntryKeyOptionsCache(keyOptions, { remove = false, reset = false } = {}) {
if (!keyOptions.length) return;
/** @type {Select2Option[]} */
const options = keyOptions.map(x => typeof x === 'string' ? { id: getSelect2OptionId(x), text: x } : x);
if (reset) worldEntryKeyOptionsCache.length = 0;
options.forEach(option => {
// Update the cache list
let cachedEntry = worldEntryKeyOptionsCache.find(x => x.id == option.id);
if (cachedEntry) {
cachedEntry.count += !remove ? 1 : -1;
} else if (!remove) {
worldEntryKeyOptionsCache.push(option);
cachedEntry = option;
cachedEntry.count = 1;
}
});
// Sort by count DESC and then alphabetically
worldEntryKeyOptionsCache.sort((a, b) => b.count - a.count || a.text.localeCompare(b.text));
}
function displayWorldEntries(name, data, navigation = navigation_option.none, flashOnNav = true) {
updateEditor = (navigation, flashOnNav = true) => displayWorldEntries(name, data, navigation, flashOnNav);
2023-08-21 20:10:11 +02:00
const worldEntriesList = $('#world_popup_entries_list');
// 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
worldEntriesList.find('*').off();
worldEntriesList.empty().show();
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
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);
2024-01-09 15:24:26 +01:00
$('#world_duplicate').off('click').on('click', nullWorldInfo);
worldEntriesList.hide();
$('#world_info_pagination').html('');
2023-07-20 19:32:15 +02:00
return;
}
// Regardless of whether success is displayed or not. Make sure the delete button is available.
// Do not put this code behind.
$('#world_popup_delete').off('click').on('click', async () => {
2024-06-26 21:43:30 +02:00
const confirmation = await Popup.show.confirm(`Delete the World/Lorebook: "${name}"?`, 'This action is irreversible!');
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);
});
2024-04-30 01:39:47 +02:00
// Before printing the WI, we check if we should enable/disable search sorting
2024-04-30 02:27:44 +02:00
verifyWorldInfoSearchSortRule();
2024-04-30 01:39:47 +02:00
2023-08-21 20:10:11 +02:00
function getDataArray(callback) {
// Convert the data.entries object into an array
let entriesArray = Object.keys(data.entries).map(uid => {
2023-08-21 20:10:11 +02:00
const entry = data.entries[uid];
entry.displayIndex = entry.displayIndex ?? entry.uid;
return entry;
});
2024-04-30 01:39:47 +02:00
// Apply the filter and do the chosen sorting
entriesArray = worldInfoFilter.applyFilters(entriesArray);
2024-05-04 22:51:28 +02:00
entriesArray = sortEntries(entriesArray);
2024-04-30 01:39:47 +02:00
// Cache keys
const keys = entriesArray.flatMap(entry => [...entry.key, ...entry.keysecondary]);
updateWorldEntryKeyOptionsCache(keys, { reset: true });
2024-04-30 01:39:47 +02:00
// Run the callback for printing this
typeof callback === 'function' && callback(entriesArray);
return entriesArray;
2023-08-21 20:10:11 +02:00
}
const storageKey = 'WI_PerPage';
const perPageDefault = 25;
2023-08-21 20:10:11 +02:00
let startPage = 1;
if (navigation === navigation_option.previous) {
2023-12-02 19:04:51 +01:00
startPage = $('#world_info_pagination').pagination('getCurrentPageNum');
2023-08-21 20:10:11 +02:00
}
2023-07-20 19:32:15 +02:00
if (typeof navigation === 'number' && Number(navigation) >= 0) {
const data = getDataArray();
const uidIndex = data.findIndex(x => x.uid === navigation);
const perPage = Number(localStorage.getItem(storageKey)) || perPageDefault;
startPage = Math.floor(uidIndex / perPage) + 1;
}
2023-12-02 19:04:51 +01:00
$('#world_info_pagination').pagination({
2023-08-21 20:10:11 +02:00
dataSource: getDataArray,
pageSize: Number(localStorage.getItem(storageKey)) || perPageDefault,
sizeChangerOptions: [10, 25, 50, 100, 500, 1000],
showSizeChanger: true,
2023-08-21 20:10:11 +02:00
pageRange: 1,
pageNumber: startPage,
position: 'top',
showPageNumbers: false,
prevText: '<',
nextText: '>',
formatNavigator: PAGINATION_TEMPLATE,
showNavigator: true,
2023-11-11 19:16:57 +01:00
callback: 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
// This prevents jQuery from actually going through all registered events on the controls for each entry when removing it
worldEntriesList.find('*').off();
worldEntriesList.empty();
2023-09-25 11:03:10 +02:00
const keywordHeaders = `
2024-04-27 16:52:47 +02:00
<div id="WIEntryHeaderTitlesPC" class="flex-container wide100p spaceBetween justifyCenter textAlignCenter" style="padding:0 4.5em;">
<small class="flex1">
Title/Memo
</small>
2024-04-27 16:52:47 +02:00
<small style="width: calc(3.5em + 15px)">
Status
</small>
2024-04-27 16:52:47 +02:00
<small style="width: calc(3.5em + 30px)">
Position
</small>
2024-04-27 16:52:47 +02:00
<small style="width: calc(3.5em + 20px)">
Depth
</small>
2024-04-27 16:52:47 +02:00
<small style="width: calc(3.5em + 20px)">
Order
</small>
2023-11-04 16:44:43 +01:00
<small style="width: calc(3.5em + 15px)">
Trigger %
</small>
2023-12-02 20:11:06 +01:00
</div>`;
2023-11-04 19:02:38 +01:00
const blocks = page.map(entry => getWorldEntry(name, data, entry)).filter(x => x);
2023-11-11 19:16:57 +01:00
const isCustomOrder = $('#world_info_sort_order').find(':selected').data('rule') === 'custom';
if (!isCustomOrder) {
blocks.forEach(block => {
block.find('.drag-handle').remove();
});
}
worldEntriesList.append(keywordHeaders);
worldEntriesList.append(blocks);
2023-08-21 20:10:11 +02:00
},
afterSizeSelectorChange: function (e) {
localStorage.setItem(storageKey, e.target.value);
2023-12-02 21:06:57 +01:00
},
afterPaging: function () {
$('#world_popup_entries_list textarea[name="comment"]').each(function () {
initScrollHeight($(this));
});
},
2023-08-21 20:10:11 +02:00
});
2023-07-20 19:32:15 +02:00
if (typeof navigation === 'number' && Number(navigation) >= 0) {
const selector = `#world_popup_entries_list [uid="${navigation}"]`;
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);
if (flashOnNav) flashHighlight(element);
});
2023-07-20 19:32:15 +02:00
}
2023-12-02 19:04:51 +01:00
$('#world_popup_new').off('click').on('click', () => {
const entry = createWorldInfoEntry(name, data);
if (entry) updateEditor(entry.uid);
2023-07-20 19:32:15 +02:00
});
2023-12-02 19:04:51 +01:00
$('#world_popup_name_button').off('click').on('click', async () => {
2023-07-20 19:32:15 +02:00
await renameWorldInfo(name, data);
});
2023-12-02 19:04:51 +01:00
$('#world_backfill_memos').off('click').on('click', async () => {
2023-10-05 22:56:31 +02:00
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];
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, entry.uid, 'comment', entry.comment);
2023-10-05 22:56:31 +02:00
counter++;
}
}
if (counter > 0) {
toastr.info(`Backfilled ${counter} titles`);
await saveWorldInfo(name, data, true);
updateEditor(navigation_option.previous);
}
});
2023-12-02 19:04:51 +01:00
$('#world_popup_export').off('click').on('click', () => {
2023-07-20 19:32:15 +02:00
if (name && data) {
const jsonValue = JSON.stringify(data);
const fileName = `${name}.json`;
2023-12-02 19:04:51 +01:00
download(jsonValue, fileName, 'application/json');
2023-07-20 19:32:15 +02:00
}
});
2024-01-09 15:24:26 +01:00
$('#world_duplicate').off('click').on('click', async () => {
const tempName = getFreeWorldName();
const finalName = await Popup.show.input('Create a new World Info?', 'Enter a name for the new file:', tempName);
2024-01-09 15:24:26 +01:00
if (finalName) {
await saveWorldInfo(finalName, data, true);
await updateWorldInfoList();
const selectedIndex = world_names.indexOf(finalName);
if (selectedIndex !== -1) {
$('#world_editor_select').val(selectedIndex).trigger('change');
} else {
hideWorldEditor();
}
}
});
2023-07-20 19:32:15 +02:00
// Check if a sortable instance exists
if (worldEntriesList.sortable('instance') !== undefined) {
2023-07-20 19:32:15 +02:00
// Destroy the instance
worldEntriesList.sortable('destroy');
2023-07-20 19:32:15 +02:00
}
worldEntriesList.sortable({
items: '.world_entry',
delay: getSortableDelay(),
2023-12-02 19:04:51 +01:00
handle: '.drag-handle',
stop: async function (_event, _ui) {
2023-11-11 19:16:57 +01:00
const firstEntryUid = $('#world_popup_entries_list .world_entry').first().data('uid');
const minDisplayIndex = data?.entries[firstEntryUid]?.displayIndex ?? 0;
2023-07-20 19:32:15 +02:00
$('#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;
}
2023-11-11 19:16:57 +01:00
item.displayIndex = minDisplayIndex + index;
setOriginalDataValue(data, uid, 'extensions.display_index', item.displayIndex);
2023-07-20 19:32:15 +02:00
});
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);
2023-12-02 21:06:57 +01:00
},
2023-07-20 19:32:15 +02:00
});
//$("#world_popup_entries_list").disableSelection();
}
2023-12-01 20:51:49 +01:00
const originalDataKeyMap = {
'displayIndex': 'extensions.display_index',
'excludeRecursion': 'extensions.exclude_recursion',
'preventRecursion': 'extensions.prevent_recursion',
'delayUntilRecursion': 'extensions.delay_until_recursion',
2023-12-01 20:51:49 +01:00
'selectiveLogic': 'selectiveLogic',
'comment': 'comment',
'constant': 'constant',
'order': 'insertion_order',
'depth': 'extensions.depth',
'probability': 'extensions.probability',
'position': 'extensions.position',
'role': 'extensions.role',
2023-12-01 20:51:49 +01:00
'content': 'content',
'enabled': 'enabled',
'key': 'keys',
'keysecondary': 'secondary_keys',
'selective': 'selective',
'matchWholeWords': 'extensions.match_whole_words',
2024-05-04 23:42:33 +02:00
'useGroupScoring': 'extensions.use_group_scoring',
'caseSensitive': 'extensions.case_sensitive',
'scanDepth': 'extensions.scan_depth',
'automationId': 'extensions.automation_id',
'vectorized': 'extensions.vectorized',
'groupOverride': 'extensions.group_override',
2024-05-06 16:00:42 +02:00
'groupWeight': 'extensions.group_weight',
'sticky': 'extensions.sticky',
'cooldown': 'extensions.cooldown',
2024-06-26 21:43:30 +02:00
'delay': 'extensions.delay',
2023-12-02 20:11:06 +01:00
};
2023-12-01 20:51:49 +01:00
2024-04-30 01:39:47 +02:00
/** Checks the state of the current search, and adds/removes the search sorting option accordingly */
2024-04-30 02:27:44 +02:00
function verifyWorldInfoSearchSortRule() {
2024-04-30 01:39:47 +02:00
const searchTerm = worldInfoFilter.getFilterData(FILTER_TYPES.WORLD_INFO_SEARCH);
const searchOption = $('#world_info_sort_order option[data-rule="search"]');
const selector = $('#world_info_sort_order');
const isHidden = searchOption.attr('hidden') !== undefined;
2024-04-30 02:27:44 +02:00
// If we have a search term, we are displaying the sorting option for it
2024-04-30 01:39:47 +02:00
if (searchTerm && isHidden) {
searchOption.removeAttr('hidden');
selector.val(searchOption.attr('value') || '0');
flashHighlight(selector);
}
// If search got cleared, we make sure to hide the option and go back to the one before
if (!searchTerm && !isHidden) {
searchOption.attr('hidden', '');
selector.val(localStorage.getItem(SORT_ORDER_KEY) || '0');
}
}
2023-07-20 19:32:15 +02:00
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;
}
setValueByPath(originalEntry, key, value);
2023-07-20 19:32:15 +02:00
}
}
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);
}
}
}
/** @typedef {import('./utils.js').Select2Option} Select2Option */
/**
* Splits a given input string that contains one or more keywords or regexes, separated by commas.
*
* Each part can be a valid regex following the pattern `/myregex/flags` with optional flags. Commmas inside the regex are allowed, slashes have to be escaped like this: `\/`
* If a regex doesn't stand alone, it is not treated as a regex.
*
* @param {string} input - One or multiple keywords or regexes, separated by commas
* @returns {string[]} An array of keywords and regexes
*/
function splitKeywordsAndRegexes(input) {
/** @type {string[]} */
let keywordsAndRegexes = [];
// We can make this easy. Instead of writing another function to find and parse regexes,
// we gonna utilize the custom tokenizer that also handles the input.
// No need for validation here
const addFindCallback = (/** @type {Select2Option} */ item) => {
keywordsAndRegexes.push(item.text);
};
const { term } = customTokenizer({ _type: 'custom_call', term: input }, undefined, addFindCallback);
const finalTerm = term.trim();
if (finalTerm) {
addFindCallback({ id: getSelect2OptionId(finalTerm), text: finalTerm });
}
return keywordsAndRegexes;
}
/**
* Tokenizer parsing input and splitting it into keywords and regexes
*
* @param {{_type: string, term: string}} input - The typed input
* @param {{options: object}} _selection - The selection even object (?)
* @param {function(Select2Option):void} callback - The original callback function to call if an item should be inserted
* @returns {{term: string}} - The remaining part that is untokenized in the textbox
*/
function customTokenizer(input, _selection, callback) {
let current = input.term;
let insideRegex = false, regexClosed = false;
// Go over the input and check the current state, if we can get a token
for (let i = 0; i < current.length; i++) {
let char = current[i];
// If we find an unascaped slash, set the current regex state
if (char === '/' && (i === 0 || current[i - 1] !== '\\')) {
if (!insideRegex) insideRegex = true;
else if (!regexClosed) regexClosed = true;
}
// If a comma is typed, we tokenize the input.
// unless we are inside a possible regex, which would allow commas inside
if (char === ',') {
// We take everything up till now and consider this a token
const token = current.slice(0, i).trim();
// Now how we test if this is a regex? And not a finished one, but a half-finished one?
// We use the state remembered from above to check whether the delimiter was opened but not closed yet.
// We don't check validity here if we are inside a regex, because it might only get valid after its finished. (Closing brackets, etc)
// Validity will be finally checked when the next comma is typed.
if (insideRegex && !regexClosed) {
continue;
}
// So now the comma really means the token is done.
// We take the token up till now, and insert it. Empty will be skipped.
if (token) {
const isRegex = isValidRegex(token);
// Last chance to check for valid regex again. Because it might have been valid while typing, but now is not valid anymore and contains commas we need to split.
if (token.startsWith('/') && !isRegex) {
const tokens = token.split(',').map(x => x.trim());
tokens.forEach(x => callback({ id: getSelect2OptionId(x), text: x }));
} else {
callback({ id: getSelect2OptionId(token), text: token });
}
}
// Now remove the token from the current input, and the comma too
current = current.slice(i + 1);
2024-06-23 17:41:49 +02:00
insideRegex = false;
regexClosed = false;
i = 0;
}
}
// At the end, just return the left-over input
return { term: current };
}
/**
* Validates if a string is a valid slash-delimited regex, that can be parsed and executed
*
* This is a wrapper around `parseRegexFromString`
*
* @param {string} input - A delimited regex string
* @returns {boolean} Whether this would be a valid regex that can be parsed and executed
*/
function isValidRegex(input) {
return parseRegexFromString(input) !== null;
}
/**
* Gets a real regex object from a slash-delimited regex string
*
* This function works with `/` as delimiter, and each occurance of it inside the regex has to be escaped.
* Flags are optional, but can only be valid flags supported by JavaScript's `RegExp` (`g`, `i`, `m`, `s`, `u`, `y`).
*
* @param {string} input - A delimited regex string
* @returns {RegExp|null} The regex object, or null if not a valid regex
*/
function parseRegexFromString(input) {
// Extracting the regex pattern and flags
let match = input.match(/^\/([\w\W]+?)\/([gimsuy]*)$/);
if (!match) {
return null; // Not a valid regex format
}
let [, pattern, flags] = match;
// If we find any unescaped slash delimiter, we also exit out.
// JS doesn't care about delimiters inside regex patterns, but for this to be a valid regex outside of our implementation,
// we have to make sure that our delimiter is correctly escaped. Or every other engine would fail.
if (pattern.match(/(^|[^\\])\//)) {
return null;
}
// Now we need to actually unescape the slash delimiters, because JS doesn't care about delimiters
pattern = pattern.replace('\\/', '/');
// Then we return the regex. If it fails, it was invalid syntax.
try {
return new RegExp(pattern, flags);
} catch (e) {
return null;
}
}
2023-08-21 20:10:11 +02:00
function getWorldEntry(name, data, entry) {
2023-11-04 19:02:38 +01:00
if (!data.entries[entry.uid]) {
return;
}
const template = WI_ENTRY_EDIT_TEMPLATE.clone();
2023-12-02 19:04:51 +01:00
template.data('uid', entry.uid);
template.attr('uid', entry.uid);
2023-07-20 19:32:15 +02:00
2024-05-15 00:19:09 +02:00
// Init default state of WI Key toggle (=> true)
if (typeof power_user.wi_key_input_plaintext === 'undefined') power_user.wi_key_input_plaintext = true;
/** Function to build the keys input controls @param {string} entryPropName @param {string} originalDataValueName */
function enableKeysInput(entryPropName, originalDataValueName) {
const isFancyInput = !isMobile() && !power_user.wi_key_input_plaintext;
const input = isFancyInput ? template.find(`select[name="${entryPropName}"]`) : template.find(`textarea[name="${entryPropName}"]`);
input.data('uid', entry.uid);
input.on('click', function (event) {
// Prevent closing the drawer on clicking the input
event.stopPropagation();
});
2023-07-20 19:32:15 +02:00
function templateStyling(/** @type {Select2Option} */ item, { searchStyle = false } = {}) {
const content = $('<span>').addClass('item').text(item.text).attr('title', `${item.text}\n\nClick to edit`);
const isRegex = isValidRegex(item.text);
if (isRegex) {
2024-05-09 03:31:41 +02:00
content.html(highlightRegex(item.text));
content.addClass('regex_item').prepend($('<span>').addClass('regex_icon').text('•*').attr('title', 'Regex'));
}
if (searchStyle && item.count) {
// Build a wrapping element
const wrapper = $('<span>').addClass('result_block')
.append(content);
wrapper.append($('<span>').addClass('item_count').text(item.count).attr('title', `Used as a key ${item.count} ${item.count != 1 ? 'times' : 'time'} in this lorebook`));
return wrapper;
}
return content;
}
if (isFancyInput) {
input.select2({
2024-05-09 03:31:41 +02:00
ajax: dynamicSelect2DataViaAjax(() => worldEntryKeyOptionsCache),
tags: true,
tokenSeparators: [','],
tokenizer: customTokenizer,
placeholder: input.attr('placeholder'),
templateResult: item => templateStyling(item, { searchStyle: true }),
templateSelection: item => templateStyling(item),
});
input.on('change', function (_, { skipReset, noSave } = {}) {
const uid = $(this).data('uid');
/** @type {string[]} */
const keys = ($(this).select2('data')).map(x => x.text);
!skipReset && resetScrollHeight(this);
if (!noSave) {
data.entries[uid][entryPropName] = keys;
setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
saveWorldInfo(name, data);
}
});
input.on('select2:select', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data]));
input.on('select2:unselect', /** @type {function(*):void} */ event => updateWorldEntryKeyOptionsCache([event.params.data], { remove: true }));
2023-07-20 19:32:15 +02:00
select2ChoiceClickSubscribe(input, target => {
const key = $(target).text();
console.debug('Editing WI key', key);
// Remove the current key from the actual selection
const selected = input.val();
if (!Array.isArray(selected)) return;
var index = selected.indexOf(getSelect2OptionId(key));
if (index > -1) selected.splice(index, 1);
input.val(selected).trigger('change');
// Manually update the cache, that change event is not gonna trigger it
updateWorldEntryKeyOptionsCache([key], { remove: true });
// We need to "hack" the actual text input into the currently open textarea
input.next('span.select2-container').find('textarea')
.val(key).trigger('input');
}, { openDrawer: true });
select2ModifyOptions(input, entry[entryPropName], { select: true, changeEventArgs: { skipReset: true, noSave: true } });
}
else {
// Compatibility with mobile devices. On mobile we need a text input field, not a select option control, so we need its own event handlers
template.find(`select[name="${entryPropName}"]`).hide();
input.show();
input.on('input', function (_, { skipReset, noSave } = {}) {
const uid = $(this).data('uid');
const value = String($(this).val());
!skipReset && resetScrollHeight(this);
if (!noSave) {
data.entries[uid][entryPropName] = splitKeywordsAndRegexes(value);
setOriginalDataValue(data, uid, originalDataValueName, data.entries[uid][entryPropName]);
saveWorldInfo(name, data);
}
});
input.val(entry[entryPropName].join(', ')).trigger('input', { skipReset: true });
}
return { isFancy: isFancyInput, control: input };
}
// key
const keyInput = enableKeysInput('key', 'keys');
// keysecondary
const keySecondaryInput = enableKeysInput('keysecondary', 'secondary_keys');
2023-07-20 19:32:15 +02:00
// draw key input switch button
template.find('.switch_input_type_icon').on('click', function () {
power_user.wi_key_input_plaintext = !power_user.wi_key_input_plaintext;
saveSettingsDebounced();
// Just redraw the panel
const uid = ($(this).parents('.world_entry')).data('uid');
updateEditor(uid, false);
$(`.world_entry[uid="${uid}"] .inline-drawer-icon`).trigger('click');
// setTimeout(() => {
// }, debounce_timeout.standard);
}).each((_, icon) => {
$(icon).attr('title', $(icon).data(power_user.wi_key_input_plaintext ? 'tooltip-on' : 'tooltip-off'));
$(icon).text($(icon).data(power_user.wi_key_input_plaintext ? 'icon-on' : 'icon-off'));
});
2023-07-20 19:32:15 +02:00
// logic AND/NOT
const selectiveLogicDropdown = template.find('select[name="entryLogicType"]');
2023-12-02 19:04:51 +01:00
selectiveLogicDropdown.data('uid', entry.uid);
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
selectiveLogicDropdown.on('click', function (event) {
event.stopPropagation();
2023-12-02 20:11:06 +01:00
});
2023-12-02 19:04:51 +01:00
selectiveLogicDropdown.on('input', function () {
const uid = $(this).data('uid');
2023-07-20 19:32:15 +02:00
const value = Number($(this).val());
2023-12-05 11:04:27 +01:00
data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY;
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic);
2023-07-20 19:32:15 +02:00
saveWorldInfo(name, data);
});
2023-07-20 19:32:15 +02:00
template
.find(`select[name="entryLogicType"] option[value=${entry.selectiveLogic}]`)
2023-12-02 19:04:51 +01:00
.prop('selected', true)
.trigger('input');
2023-07-20 19:32:15 +02:00
// Character filter
2023-12-02 19:04:51 +01:00
const characterFilterLabel = template.find('label[for="characterFilter"] > small');
characterFilterLabel.text(entry.characterFilter?.isExclude ? 'Exclude Character(s)' : 'Filter to Character(s)');
// exclude characters checkbox
2023-12-02 19:04:51 +01:00
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) {
2023-11-14 22:54:08 +01:00
if (!value && data.entries[uid].characterFilter.names.length === 0 && data.entries[uid].characterFilter.tags.length === 0) {
delete data.entries[uid].characterFilter;
} else {
2023-12-02 20:11:06 +01:00
data.entries[uid].characterFilter.isExclude = value;
}
} else if (value) {
Object.assign(
data.entries[uid],
{
characterFilter: {
isExclude: true,
2023-11-14 22:54:08 +01:00
names: [],
tags: [],
2023-12-02 21:06:57 +01:00
},
},
);
}
// Verify names to exist in the system
if (data.entries[uid]?.characterFilter?.names?.length > 0) {
for (const name of [...data.entries[uid].characterFilter.names]) {
if (!getContext().characters.find(x => x.avatar.replace(/\.[^/.]+$/, '') === name)) {
console.warn(`World Info: Character ${name} not found. Removing from the entry filter.`, entry);
data.entries[uid].characterFilter.names = data.entries[uid].characterFilter.names.filter(x => x !== name);
}
}
}
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
saveWorldInfo(name, data);
});
2023-12-02 19:04:51 +01:00
characterExclusionInput.prop('checked', entry.characterFilter?.isExclude ?? false).trigger('input');
2023-12-02 19:04:51 +01:00
const characterFilter = template.find('select[name="characterFilter"]');
2023-12-02 20:11:06 +01:00
characterFilter.data('uid', entry.uid);
if (!isMobile()) {
$(characterFilter).select2({
width: '100%',
placeholder: 'All characters will pull from this entry.',
allowClear: true,
closeOnSelect: false,
});
}
2023-11-14 22:54:08 +01:00
const characters = getContext().characters;
characters.forEach((character) => {
const option = document.createElement('option');
2023-12-02 19:04:51 +01:00
const name = character.avatar.replace(/\.[^/.]+$/, '') ?? character.name;
2023-11-14 22:54:08 +01:00
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;
}
2023-12-02 19:04:51 +01:00
const uid = $(this).data('uid');
2023-11-14 22:54:08 +01:00
const selected = $(this).find(':selected');
if ((!selected || selected?.length === 0) && !data.entries[uid].characterFilter?.isExclude) {
delete data.entries[uid].characterFilter;
} else {
2023-11-14 22:54:08 +01:00
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,
2023-11-14 22:54:08 +01:00
names: names,
tags: tags,
2023-12-02 21:06:57 +01:00
},
},
);
}
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'character_filter', data.entries[uid].characterFilter);
saveWorldInfo(name, data);
});
2023-07-20 19:32:15 +02:00
// comment
const commentInput = template.find('textarea[name="comment"]');
const commentToggle = template.find('input[name="addMemo"]');
2023-12-02 19:04:51 +01:00
commentInput.data('uid', entry.uid);
commentInput.on('input', function (_, { skipReset } = {}) {
2023-12-02 19:04:51 +01:00
const uid = $(this).data('uid');
2023-07-20 19:32:15 +02:00
const value = $(this).val();
!skipReset && resetScrollHeight(this);
2023-07-20 19:32:15 +02:00
data.entries[uid].comment = value;
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment);
2023-07-20 19:32:15 +02:00
saveWorldInfo(name, data);
});
2023-12-02 19:04:51 +01:00
commentToggle.data('uid', entry.uid);
commentToggle.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).prop('checked');
2023-07-20 19:32:15 +02:00
//console.log(value)
const commentContainer = $(this)
2023-12-02 19:04:51 +01:00
.closest('.world_entry')
.find('.commentContainer');
2023-07-20 19:32:15 +02:00
data.entries[uid].addMemo = value;
saveWorldInfo(name, data);
value ? commentContainer.show() : commentContainer.hide();
});
commentInput.val(entry.comment).trigger('input', { skipReset: true });
//initScrollHeight(commentInput);
2023-12-02 19:04:51 +01:00
commentToggle.prop('checked', true /* entry.addMemo */).trigger('input');
2023-12-02 20:11:06 +01:00
commentToggle.parent().hide();
2023-07-20 19:32:15 +02:00
// content
2023-12-02 19:04:51 +01:00
const counter = template.find('.world_entry_form_token_counter');
2024-04-13 20:33:19 +02:00
const countTokensDebounced = debounce(async function (counter, value) {
const numberOfTokens = await getTokenCountAsync(value);
2023-08-21 20:10:11 +02:00
$(counter).text(numberOfTokens);
}, debounce_timeout.relaxed);
2023-07-20 19:32:15 +02:00
const contentInput = template.find('textarea[name="content"]');
2023-12-02 19:04:51 +01:00
contentInput.data('uid', entry.uid);
contentInput.on('input', function (_, { skipCount } = {}) {
const uid = $(this).data('uid');
2023-07-20 19:32:15 +02:00
const value = $(this).val();
data.entries[uid].content = value;
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'content', data.entries[uid].content);
2023-07-20 19:32:15 +02:00
saveWorldInfo(name, data);
if (skipCount) {
return;
}
2023-07-20 19:32:15 +02:00
// count tokens
countTokensDebounced(counter, value);
2023-07-20 19:32:15 +02:00
});
2023-12-02 19:04:51 +01:00
contentInput.val(entry.content).trigger('input', { skipCount: true });
2023-07-20 19:32:15 +02:00
//initScrollHeight(contentInput);
template.find('.inline-drawer-toggle').on('click', function () {
if (counter.data('first-run')) {
counter.data('first-run', false);
countTokensDebounced(counter, contentInput.val());
if (!keyInput.isFancy) initScrollHeight(keyInput.control);
2024-05-16 23:03:41 +02:00
if (!keySecondaryInput.isFancy) initScrollHeight(keySecondaryInput.control);
}
});
2023-07-20 19:32:15 +02:00
// selective
const selectiveInput = template.find('input[name="selective"]');
2023-12-02 19:04:51 +01:00
selectiveInput.data('uid', entry.uid);
selectiveInput.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).prop('checked');
2023-07-20 19:32:15 +02:00
data.entries[uid].selective = value;
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'selective', data.entries[uid].selective);
2023-07-20 19:32:15 +02:00
saveWorldInfo(name, data);
const keysecondary = $(this)
2023-12-02 19:04:51 +01:00
.closest('.world_entry')
.find('.keysecondary');
2023-07-20 19:32:15 +02:00
const keysecondarytextpole = $(this)
2023-12-02 19:04:51 +01:00
.closest('.world_entry')
.find('.keysecondarytextpole');
2023-07-20 19:32:15 +02:00
const keyprimaryselect = $(this)
2023-12-02 19:04:51 +01:00
.closest('.world_entry')
.find('.keyprimaryselect');
2023-07-20 19:32:15 +02:00
const keyprimaryHeight = keyprimaryselect.outerHeight();
2023-07-20 19:32:15 +02:00
keysecondarytextpole.css('height', keyprimaryHeight + 'px');
value ? keysecondary.show() : keysecondary.hide();
});
//forced on, ignored if empty
2023-12-02 19:04:51 +01:00
selectiveInput.prop('checked', true /* entry.selective */).trigger('input');
2023-07-20 19:32:15 +02:00
selectiveInput.parent().hide();
// constant
/*
2023-07-20 19:32:15 +02:00
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");
*/
2023-07-20 19:32:15 +02:00
// order
const orderInput = template.find('input[name="order"]');
2023-12-02 19:04:51 +01:00
orderInput.data('uid', entry.uid);
orderInput.on('input', function () {
const uid = $(this).data('uid');
2023-07-20 19:32:15 +02:00
const value = Number($(this).val());
data.entries[uid].order = !isNaN(value) ? value : 0;
2023-12-02 20:11:06 +01:00
updatePosOrdDisplay(uid);
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'insertion_order', data.entries[uid].order);
2023-07-20 19:32:15 +02:00
saveWorldInfo(name, data);
});
2023-12-02 19:04:51 +01:00
orderInput.val(entry.order).trigger('input');
2023-11-04 16:44:43 +01:00
orderInput.css('width', 'calc(3em + 15px)');
2023-07-20 19:32:15 +02:00
2023-12-07 19:06:06 +01:00
// group
const groupInput = template.find('input[name="group"]');
groupInput.data('uid', entry.uid);
groupInput.on('input', function () {
const uid = $(this).data('uid');
const value = String($(this).val()).trim();
data.entries[uid].group = value;
setOriginalDataValue(data, uid, 'extensions.group', data.entries[uid].group);
saveWorldInfo(name, data);
});
groupInput.val(entry.group ?? '').trigger('input');
setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);
2023-12-07 19:06:06 +01:00
2024-04-27 02:23:37 +02:00
// inclusion priority
const groupOverrideInput = template.find('input[name="groupOverride"]');
groupOverrideInput.data('uid', entry.uid);
groupOverrideInput.on('input', function () {
2024-04-27 02:23:37 +02:00
const uid = $(this).data('uid');
const value = $(this).prop('checked');
data.entries[uid].groupOverride = value;
2024-05-06 16:00:42 +02:00
setOriginalDataValue(data, uid, 'extensions.group_override', data.entries[uid].groupOverride);
2024-04-27 02:23:37 +02:00
saveWorldInfo(name, data);
});
groupOverrideInput.prop('checked', entry.groupOverride).trigger('input');
2024-04-27 02:23:37 +02:00
2024-05-06 16:00:42 +02:00
// group weight
const groupWeightInput = template.find('input[name="groupWeight"]');
groupWeightInput.data('uid', entry.uid);
groupWeightInput.on('input', function () {
const uid = $(this).data('uid');
2024-05-06 21:55:31 +02:00
let value = Number($(this).val());
const min = Number($(this).attr('min'));
const max = Number($(this).attr('max'));
// Clamp the value
if (value < min) {
value = min;
$(this).val(min);
} else if (value > max) {
value = max;
$(this).val(max);
}
2024-05-06 16:00:42 +02:00
2024-05-06 21:55:31 +02:00
data.entries[uid].groupWeight = !isNaN(value) ? Math.abs(value) : 1;
2024-05-06 16:00:42 +02:00
setOriginalDataValue(data, uid, 'extensions.group_weight', data.entries[uid].groupWeight);
saveWorldInfo(name, data);
});
2024-05-06 21:55:31 +02:00
groupWeightInput.val(entry.groupWeight ?? DEFAULT_WEIGHT).trigger('input');
2024-05-06 16:00:42 +02:00
// sticky
const sticky = template.find('input[name="sticky"]');
sticky.data('uid', entry.uid);
sticky.on('input', function () {
const uid = $(this).data('uid');
const value = Number($(this).val());
data.entries[uid].sticky = !isNaN(value) ? value : null;
setOriginalDataValue(data, uid, 'extensions.sticky', data.entries[uid].sticky);
saveWorldInfo(name, data);
});
sticky.val(entry.sticky > 0 ? entry.sticky : '').trigger('input');
// cooldown
const cooldown = template.find('input[name="cooldown"]');
cooldown.data('uid', entry.uid);
cooldown.on('input', function () {
const uid = $(this).data('uid');
const value = Number($(this).val());
data.entries[uid].cooldown = !isNaN(value) ? value : null;
setOriginalDataValue(data, uid, 'extensions.cooldown', data.entries[uid].cooldown);
saveWorldInfo(name, data);
});
cooldown.val(entry.cooldown > 0 ? entry.cooldown : '').trigger('input');
2024-06-26 21:43:30 +02:00
// delay
const delay = template.find('input[name="delay"]');
delay.data('uid', entry.uid);
delay.on('input', function () {
const uid = $(this).data('uid');
const value = Number($(this).val());
data.entries[uid].delay = !isNaN(value) ? value : null;
setOriginalDataValue(data, uid, 'extensions.delay', data.entries[uid].delay);
saveWorldInfo(name, data);
});
delay.val(entry.delay > 0 ? entry.delay : '').trigger('input');
2023-07-20 19:32:15 +02:00
// probability
if (entry.probability === undefined) {
entry.probability = null;
}
// depth
const depthInput = template.find('input[name="depth"]');
2023-12-02 19:04:51 +01:00
depthInput.data('uid', entry.uid);
2023-12-02 19:04:51 +01:00
depthInput.on('input', function () {
const uid = $(this).data('uid');
const value = Number($(this).val());
data.entries[uid].depth = !isNaN(value) ? value : 0;
2023-12-02 20:11:06 +01:00
updatePosOrdDisplay(uid);
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'extensions.depth', data.entries[uid].depth);
saveWorldInfo(name, data);
});
2023-12-02 19:04:51 +01:00
depthInput.val(entry.depth ?? DEFAULT_DEPTH).trigger('input');
2023-11-04 16:44:43 +01:00
depthInput.css('width', 'calc(3em + 15px)');
// Hide by default unless depth is specified
if (entry.position === world_info_position.atDepth) {
//depthInput.parent().hide();
}
2023-07-20 19:32:15 +02:00
const probabilityInput = template.find('input[name="probability"]');
2023-12-02 19:04:51 +01:00
probabilityInput.data('uid', entry.uid);
probabilityInput.on('input', function () {
const uid = $(this).data('uid');
const value = Number($(this).val());
2023-07-20 19:32:15 +02:00
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);
}
}
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'extensions.probability', data.entries[uid].probability);
2023-07-20 19:32:15 +02:00
saveWorldInfo(name, data);
});
2023-12-02 19:04:51 +01:00
probabilityInput.val(entry.probability).trigger('input');
2023-11-04 16:44:43 +01:00
probabilityInput.css('width', 'calc(3em + 15px)');
2023-07-20 19:32:15 +02:00
// probability toggle
if (entry.useProbability === undefined) {
entry.useProbability = false;
}
const probabilityToggle = template.find('input[name="useProbability"]');
2023-12-02 19:04:51 +01:00
probabilityToggle.data('uid', entry.uid);
probabilityToggle.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).prop('checked');
2023-07-20 19:32:15 +02:00
data.entries[uid].useProbability = value;
const probabilityContainer = $(this)
2023-12-02 19:04:51 +01:00
.closest('.world_entry')
.find('.probabilityContainer');
2023-07-20 19:32:15 +02:00
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;
}
2023-12-02 19:04:51 +01:00
probabilityInput.val(data.entries[uid].probability).trigger('input');
2023-07-20 19:32:15 +02:00
});
//forced on, 100% by default
2023-12-02 19:04:51 +01:00
probabilityToggle.prop('checked', true /* entry.useProbability */).trigger('input');
2023-07-20 19:32:15 +02:00
probabilityToggle.parent().hide();
// position
if (entry.position === undefined) {
entry.position = 0;
}
const positionInput = template.find('select[name="position"]');
//initScrollHeight(positionInput);
2023-12-02 19:04:51 +01:00
positionInput.data('uid', entry.uid);
positionInput.on('click', function (event) {
// Prevent closing the drawer on clicking the input
event.stopPropagation();
});
2023-12-02 19:04:51 +01:00
positionInput.on('input', function () {
const uid = $(this).data('uid');
2023-07-20 19:32:15 +02:00
const value = Number($(this).val());
data.entries[uid].position = !isNaN(value) ? value : 0;
if (value === world_info_position.atDepth) {
depthInput.prop('disabled', false);
2023-12-02 20:11:06 +01:00
depthInput.css('visibility', 'visible');
//depthInput.parent().show();
const role = Number($(this).find(':selected').data('role'));
data.entries[uid].role = role;
} else {
depthInput.prop('disabled', true);
2023-12-02 20:11:06 +01:00
depthInput.css('visibility', 'hidden');
data.entries[uid].role = null;
//depthInput.parent().hide();
}
2023-12-02 20:11:06 +01:00
updatePosOrdDisplay(uid);
2023-07-20 19:32:15 +02:00
// Spec v2 only supports before_char and after_char
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'position', data.entries[uid].position == 0 ? 'before_char' : 'after_char');
2023-07-20 19:32:15 +02:00
// Write the original value as extensions field
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'extensions.position', data.entries[uid].position);
setOriginalDataValue(data, uid, 'extensions.role', data.entries[uid].role);
2023-07-20 19:32:15 +02:00
saveWorldInfo(name, data);
});
const roleValue = entry.position === world_info_position.atDepth ? String(entry.role ?? extension_prompt_roles.SYSTEM) : '';
2023-07-20 19:32:15 +02:00
template
.find(`select[name="position"] option[value=${entry.position}][data-role="${roleValue}"]`)
2023-12-02 19:04:51 +01:00
.prop('selected', true)
.trigger('input');
2023-07-20 19:32:15 +02:00
2023-09-25 11:03:10 +02:00
//add UID above content box (less important doesn't need to be always visible)
2023-12-02 19:04:51 +01:00
template.find('.world_entry_form_uid_value').text(`(UID: ${entry.uid})`);
2023-07-20 19:32:15 +02:00
// disable
2023-10-04 21:41:10 +02:00
/*
2023-07-20 19:32:15 +02:00
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"]');
2023-12-02 19:04:51 +01:00
entryStateSelector.data('uid', entry.uid);
entryStateSelector.on('click', function (event) {
// Prevent closing the drawer on clicking the input
event.stopPropagation();
});
2023-12-02 19:04:51 +01:00
entryStateSelector.on('input', function () {
const uid = entry.uid;
const value = $(this).val();
switch (value) {
2023-12-02 19:04:51 +01:00
case 'constant':
data.entries[uid].constant = true;
data.entries[uid].disable = false;
data.entries[uid].vectorized = false;
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'enabled', true);
setOriginalDataValue(data, uid, 'constant', true);
setOriginalDataValue(data, uid, 'extensions.vectorized', false);
template.removeClass('disabledWIEntry');
2023-12-02 20:11:06 +01:00
break;
2023-12-02 19:04:51 +01:00
case 'normal':
data.entries[uid].constant = false;
data.entries[uid].disable = false;
data.entries[uid].vectorized = false;
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'enabled', true);
setOriginalDataValue(data, uid, 'constant', false);
setOriginalDataValue(data, uid, 'extensions.vectorized', false);
template.removeClass('disabledWIEntry');
break;
case 'vectorized':
data.entries[uid].constant = false;
data.entries[uid].disable = false;
data.entries[uid].vectorized = true;
setOriginalDataValue(data, uid, 'enabled', true);
setOriginalDataValue(data, uid, 'constant', false);
setOriginalDataValue(data, uid, 'extensions.vectorized', true);
template.removeClass('disabledWIEntry');
2023-12-02 20:11:06 +01:00
break;
2023-12-02 19:04:51 +01:00
case 'disabled':
data.entries[uid].constant = false;
data.entries[uid].disable = true;
data.entries[uid].vectorized = false;
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'enabled', false);
setOriginalDataValue(data, uid, 'constant', false);
setOriginalDataValue(data, uid, 'extensions.vectorized', false);
template.addClass('disabledWIEntry');
2023-12-02 20:11:06 +01:00
break;
}
saveWorldInfo(name, data);
2023-12-02 20:11:06 +01:00
});
const entryState = function () {
if (entry.constant === true) {
2023-12-02 20:11:06 +01:00
return 'constant';
} else if (entry.vectorized === true) {
return 'vectorized';
} else if (entry.disable === true) {
2023-12-02 20:11:06 +01:00
return 'disabled';
} else {
2023-12-02 20:11:06 +01:00
return 'normal';
}
2023-12-02 20:11:06 +01:00
};
template
.find(`select[name="entryStateSelector"] option[value=${entryState()}]`)
2023-12-02 19:04:51 +01:00
.prop('selected', true)
.trigger('input');
saveWorldInfo(name, data);
2023-07-20 19:32:15 +02:00
// exclude recursion
2023-07-20 19:32:15 +02:00
const excludeRecursionInput = template.find('input[name="exclude_recursion"]');
2023-12-02 19:04:51 +01:00
excludeRecursionInput.data('uid', entry.uid);
excludeRecursionInput.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).prop('checked');
2023-07-20 19:32:15 +02:00
data.entries[uid].excludeRecursion = value;
2023-12-02 19:04:51 +01:00
setOriginalDataValue(data, uid, 'extensions.exclude_recursion', data.entries[uid].excludeRecursion);
2023-07-20 19:32:15 +02:00
saveWorldInfo(name, data);
});
2023-12-02 19:04:51 +01:00
excludeRecursionInput.prop('checked', entry.excludeRecursion).trigger('input');
2023-07-20 19:32:15 +02:00
// prevent recursion
const preventRecursionInput = template.find('input[name="prevent_recursion"]');
preventRecursionInput.data('uid', entry.uid);
preventRecursionInput.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).prop('checked');
data.entries[uid].preventRecursion = value;
setOriginalDataValue(data, uid, 'extensions.prevent_recursion', data.entries[uid].preventRecursion);
saveWorldInfo(name, data);
});
preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
// delay until recursion
const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
delayUntilRecursionInput.data('uid', entry.uid);
delayUntilRecursionInput.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).prop('checked');
data.entries[uid].delayUntilRecursion = value;
setOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
saveWorldInfo(name, data);
});
delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
// duplicate button
const duplicateButton = template.find('.duplicate_entry_button');
duplicateButton.data('uid', entry.uid);
duplicateButton.on('click', function () {
const uid = $(this).data('uid');
const entry = duplicateWorldInfoEntry(data, uid);
if (entry) {
saveWorldInfo(name, data);
updateEditor(entry.uid);
}
});
2023-07-20 19:32:15 +02:00
// delete button
2023-12-02 19:04:51 +01:00
const deleteButton = template.find('.delete_entry_button');
deleteButton.data('uid', entry.uid);
deleteButton.on('click', function () {
const uid = $(this).data('uid');
2023-07-20 19:32:15 +02:00
deleteWorldInfoEntry(data, uid);
deleteOriginalDataValue(data, uid);
saveWorldInfo(name, data);
2023-08-21 20:10:11 +02:00
updateEditor(navigation_option.previous);
2023-07-20 19:32:15 +02:00
});
// scan depth
const scanDepthInput = template.find('input[name="scanDepth"]');
scanDepthInput.data('uid', entry.uid);
scanDepthInput.on('input', function () {
const uid = $(this).data('uid');
const isEmpty = $(this).val() === '';
const value = Number($(this).val());
// Clamp if necessary
if (value < 0) {
$(this).val(0).trigger('input');
toastr.warning('Scan depth cannot be negative');
return;
}
if (value > MAX_SCAN_DEPTH) {
$(this).val(MAX_SCAN_DEPTH).trigger('input');
toastr.warning(`Scan depth cannot exceed ${MAX_SCAN_DEPTH}`);
return;
}
2024-02-27 22:34:07 +01:00
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);
saveWorldInfo(name, data);
});
scanDepthInput.val(entry.scanDepth ?? null).trigger('input');
// case sensitive select
const caseSensitiveSelect = template.find('select[name="caseSensitive"]');
caseSensitiveSelect.data('uid', entry.uid);
caseSensitiveSelect.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).val();
data.entries[uid].caseSensitive = value === 'null' ? null : value === 'true';
setOriginalDataValue(data, uid, 'extensions.case_sensitive', data.entries[uid].caseSensitive);
saveWorldInfo(name, data);
});
2024-01-24 12:00:43 +01:00
caseSensitiveSelect.val((entry.caseSensitive === null || entry.caseSensitive === undefined) ? 'null' : entry.caseSensitive ? 'true' : 'false').trigger('input');
// match whole words select
const matchWholeWordsSelect = template.find('select[name="matchWholeWords"]');
matchWholeWordsSelect.data('uid', entry.uid);
matchWholeWordsSelect.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).val();
data.entries[uid].matchWholeWords = value === 'null' ? null : value === 'true';
setOriginalDataValue(data, uid, 'extensions.match_whole_words', data.entries[uid].matchWholeWords);
saveWorldInfo(name, data);
});
2024-01-24 12:00:43 +01:00
matchWholeWordsSelect.val((entry.matchWholeWords === null || entry.matchWholeWords === undefined) ? 'null' : entry.matchWholeWords ? 'true' : 'false').trigger('input');
2024-05-04 23:42:33 +02:00
// use group scoring select
const useGroupScoringSelect = template.find('select[name="useGroupScoring"]');
useGroupScoringSelect.data('uid', entry.uid);
useGroupScoringSelect.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).val();
data.entries[uid].useGroupScoring = value === 'null' ? null : value === 'true';
setOriginalDataValue(data, uid, 'extensions.use_group_scoring', data.entries[uid].useGroupScoring);
saveWorldInfo(name, data);
});
useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
// automation id
const automationIdInput = template.find('input[name="automationId"]');
automationIdInput.data('uid', entry.uid);
automationIdInput.on('input', function () {
const uid = $(this).data('uid');
const value = $(this).val();
data.entries[uid].automationId = value;
setOriginalDataValue(data, uid, 'extensions.automation_id', data.entries[uid].automationId);
saveWorldInfo(name, data);
});
automationIdInput.val(entry.automationId ?? '').trigger('input');
2024-02-25 02:54:40 +01:00
setTimeout(() => createEntryInputAutocomplete(automationIdInput, getAutomationIdCallback(data)), 1);
2023-07-20 19:32:15 +02:00
template.find('.inline-drawer-content').css('display', 'none'); //entries start collapsed
function updatePosOrdDisplay(uid) {
// display position/order info left of keyword box
2023-12-02 20:11:06 +01:00
let entry = data.entries[uid];
let posText = entry.position;
switch (entry.position) {
case 0:
posText = '↑CD';
2023-12-02 20:11:06 +01:00
break;
case 1:
posText = 'CD↓';
2023-12-02 20:11:06 +01:00
break;
case 2:
posText = '↑AN';
2023-12-02 20:11:06 +01:00
break;
case 3:
posText = 'AN↓';
2023-12-02 20:11:06 +01:00
break;
case 4:
posText = `@D${entry.depth}`;
2023-12-02 20:11:06 +01:00
break;
}
2023-12-02 19:04:51 +01:00
template.find('.world_entry_form_position_value').text(`(${posText} ${entry.order})`);
}
2023-07-20 19:32:15 +02:00
return template;
}
/**
2024-02-25 02:54:40 +01:00
* Get the inclusion groups for the autocomplete.
* @param {any} data WI data
2024-02-25 02:54:40 +01:00
* @returns {(input: any, output: any) => any} Callback function for the autocomplete
*/
2024-02-25 02:54:40 +01:00
function getInclusionGroupCallback(data) {
return function (control, input, output) {
const uid = $(control).data('uid');
const thisGroups = String($(control).val()).split(/,\s*/).filter(x => x).map(x => x.toLowerCase());
const groups = new Set();
for (const entry of Object.values(data.entries)) {
// Skip the groups of this entry, because auto-complete should only suggest the ones that are already available on other entries
if (entry.uid == uid) continue;
if (entry.group) {
entry.group.split(/,\s*/).filter(x => x).forEach(x => groups.add(x));
}
}
const haystack = Array.from(groups);
haystack.sort((a, b) => a.localeCompare(b));
const needle = input.term.toLowerCase();
const hasExactMatch = haystack.findIndex(x => x.toLowerCase() == needle) !== -1;
const result = haystack.filter(x => x.toLowerCase().includes(needle) && (!thisGroups.includes(x) || hasExactMatch && thisGroups.filter(g => g == x).length == 1));
output(result);
2024-02-25 02:54:40 +01:00
};
}
2024-02-25 02:54:40 +01:00
function getAutomationIdCallback(data) {
return function (control, input, output) {
const uid = $(control).data('uid');
2024-02-25 02:54:40 +01:00
const ids = new Set();
for (const entry of Object.values(data.entries)) {
// Skip automation id of this entry, because auto-complete should only suggest the ones that are already available on other entries
if (entry.uid == uid) continue;
2024-02-25 02:54:40 +01:00
if (entry.automationId) {
ids.add(String(entry.automationId));
}
}
if ('quickReplyApi' in window) {
// @ts-ignore
for (const automationId of window['quickReplyApi'].listAutomationIds()) {
ids.add(String(automationId));
}
}
const haystack = Array.from(ids);
haystack.sort((a, b) => a.localeCompare(b));
const needle = input.term.toLowerCase();
const result = haystack.filter(x => x.toLowerCase().includes(needle));
output(result);
};
}
/**
* Create an autocomplete for the inclusion group.
* @param {JQuery<HTMLElement>} input - Input element to attach the autocomplete to
* @param {(control: JQuery<HTMLElement>, input: any, output: any) => any} callback - Source data callbacks
* @param {object} [options={}] - Optional arguments
* @param {boolean} [options.allowMultiple=false] - Whether to allow multiple comma-separated values
2024-02-25 02:54:40 +01:00
*/
function createEntryInputAutocomplete(input, callback, { allowMultiple = false } = {}) {
const handleSelect = (event, ui) => {
// Prevent default autocomplete select, so we can manually set the value
event.preventDefault();
if (!allowMultiple) {
$(input).val(ui.item.value).trigger('input').trigger('blur');
} else {
var terms = String($(input).val()).split(/,\s*/);
terms.pop(); // remove the current input
terms.push(ui.item.value); // add the selected item
$(input).val(terms.filter(x => x).join(', ')).trigger('input').trigger('blur');
}
};
$(input).autocomplete({
minLength: 0,
source: function (request, response) {
if (!allowMultiple) {
callback(input, request, response);
} else {
const term = request.term.split(/,\s*/).pop();
request.term = term;
callback(input, request, response);
}
},
select: handleSelect,
});
2024-02-25 02:54:40 +01:00
$(input).on('focus click', function () {
$(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : $(input).val());
});
}
/**
* Duplicated a WI entry by copying all of its properties and assigning a new uid
* @param {*} data - The data of the book
* @param {number} uid - The uid of the entry to copy in this book
* @returns {*} The new WI duplicated entry
*/
function duplicateWorldInfoEntry(data, uid) {
if (!data || !('entries' in data) || !data.entries[uid]) {
return;
}
// Exclude uid and gather the rest of the properties
const { uid: _, ...originalData } = data.entries[uid];
// Create new entry and copy over data
const entry = createWorldInfoEntry(data.name, data);
Object.assign(entry, originalData);
return entry;
}
/**
* Deletes a WI entry, with a user confirmation dialog
* @param {*[]} data - The data of the book
* @param {number} uid - The uid of the entry to copy in this book
*/
function deleteWorldInfoEntry(data, uid) {
2023-12-02 19:04:51 +01:00
if (!data || !('entries' in data)) {
2023-07-20 19:32:15 +02:00
return;
}
2023-09-05 11:05:20 +02:00
if (!confirm(`Delete the entry with UID: ${uid}? This action is irreversible!`)) {
2023-12-02 19:04:51 +01:00
throw new Error('User cancelled deletion');
2023-09-05 11:05:20 +02:00
}
2023-07-20 19:32:15 +02:00
delete data.entries[uid];
}
/**
* Definitions of types for new WI entries
*
* Use `newEntryTemplate` if you just need the template that contains default values
*
* @type {{[key: string]: { default: any, type: string }}}
*/
const newEntryDefinition = {
key: { default: [], type: 'array' },
keysecondary: { default: [], type: 'array' },
comment: { default: '', type: 'string' },
content: { default: '', type: 'string' },
constant: { default: false, type: 'boolean' },
vectorized: { default: false, type: 'boolean' },
selective: { default: true, type: 'boolean' },
selectiveLogic: { default: world_info_logic.AND_ANY, type: 'enum' },
addMemo: { default: false, type: 'boolean' },
order: { default: 100, type: 'number' },
position: { default: 0, type: 'number' },
disable: { default: false, type: 'boolean' },
excludeRecursion: { default: false, type: 'boolean' },
preventRecursion: { default: false, type: 'boolean' },
delayUntilRecursion: { default: false, type: 'boolean' },
probability: { default: 100, type: 'number' },
useProbability: { default: true, type: 'boolean' },
depth: { default: DEFAULT_DEPTH, type: 'number' },
group: { default: '', type: 'string' },
groupOverride: { default: false, type: 'boolean' },
groupWeight: { default: DEFAULT_WEIGHT, type: 'number' },
scanDepth: { default: null, type: 'number?' },
caseSensitive: { default: null, type: 'boolean?' },
matchWholeWords: { default: null, type: 'boolean?' },
useGroupScoring: { default: null, type: 'boolean?' },
automationId: { default: '', type: 'string' },
role: { default: 0, type: 'enum' },
2024-06-23 17:31:40 +02:00
sticky: { default: null, type: 'number?' },
cooldown: { default: null, type: 'number?' },
2024-06-26 21:43:30 +02:00
delay: { default: null, type: 'number?' },
2023-12-01 20:51:49 +01:00
};
const newEntryTemplate = Object.fromEntries(
2024-06-23 15:24:33 +02:00
Object.entries(newEntryDefinition).map(([key, value]) => [key, value.default]),
);
2024-06-23 17:41:49 +02:00
/**
* Creates a new world info entry from template.
* @param {string} _name Name of the WI (unused)
* @param {any} data WI data
* @returns {object | undefined} New entry object or undefined if failed
*/
function createWorldInfoEntry(_name, data) {
2023-07-20 19:32:15 +02:00
const newUid = getFreeWorldEntryUid(data);
if (!Number.isInteger(newUid)) {
2023-12-02 19:04:51 +01:00
console.error('Couldn\'t assign UID to a new entry');
2023-07-20 19:32:15 +02:00
return;
}
2023-12-01 20:51:49 +01:00
const newEntry = { uid: newUid, ...structuredClone(newEntryTemplate) };
2023-07-20 19:32:15 +02:00
data.entries[newUid] = newEntry;
return newEntry;
2023-07-20 19:32:15 +02:00
}
async function _save(name, data) {
2023-12-06 23:09:48 +01:00
await fetch('/api/worldinfo/edit', {
2023-12-02 19:04:51 +01:00
method: 'POST',
2023-07-20 19:32:15 +02:00
headers: getRequestHeaders(),
body: JSON.stringify({ name: name, data: data }),
});
2024-01-01 15:34:09 +01:00
eventSource.emit(event_types.WORLDINFO_UPDATED, name, data);
2023-07-20 19:32:15 +02:00
}
async function saveWorldInfo(name, data, immediately) {
if (!name || !data) {
return;
}
worldInfoCache.delete(name);
2023-07-20 19:32:15 +02:00
if (immediately) {
return await _save(name, data);
}
saveWorldDebounced(name, data);
}
async function renameWorldInfo(name, data) {
const oldName = name;
const newName = await Popup.show.input('Rename World Info', 'Enter a new name:', oldName);
2023-07-20 19:32:15 +02:00
if (oldName === newName || !newName) {
2023-12-02 19:04:51 +01:00
console.debug('World info rename cancelled');
2023-07-20 19:32:15 +02:00
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);
2023-12-02 19:04:51 +01:00
wiElement.prop('selected', true);
$('#world_info').trigger('change');
2023-07-20 19:32:15 +02:00
}
const selectedIndex = world_names.indexOf(newName);
if (selectedIndex !== -1) {
$('#world_editor_select').val(selectedIndex).trigger('change');
}
}
/**
* Deletes a world info with the given name
*
* @param {string} worldInfoName - The name of the world info to delete
* @returns {Promise<boolean>} A promise that resolves to true if the world info was successfully deleted, false otherwise
*/
2023-07-20 19:32:15 +02:00
async function deleteWorldInfo(worldInfoName) {
if (!world_names.includes(worldInfoName)) {
return false;
2023-07-20 19:32:15 +02:00
}
2023-12-06 23:09:48 +01:00
const response = await fetch('/api/worldinfo/delete', {
2023-12-02 19:04:51 +01:00
method: 'POST',
2023-07-20 19:32:15 +02:00
headers: getRequestHeaders(),
body: JSON.stringify({ name: worldInfoName }),
});
if (!response.ok) {
return false;
}
2023-07-20 19:32:15 +02:00
const existingWorldIndex = selected_world_info.findIndex((e) => e === worldInfoName);
if (existingWorldIndex !== -1) {
selected_world_info.splice(existingWorldIndex, 1);
saveSettingsDebounced();
}
2023-07-20 19:32:15 +02:00
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();
2023-07-20 19:32:15 +02:00
}
}
return true;
2023-07-20 19:32:15 +02:00
}
function getFreeWorldEntryUid(data) {
2023-12-02 19:04:51 +01:00
if (!data || !('entries' in data)) {
2023-07-20 19:32:15 +02:00
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;
}
/**
* Creates a new world info/lorebook with the given name.
* Checks if a world with the same name already exists, providing a warning or optionally a user confirmation dialog.
*
* @param {string} worldName - The name of the new world info
* @param {Object} options - Optional parameters
* @param {boolean} [options.interactive=false] - Whether to show a confirmation dialog when overwriting an existing world
* @returns {Promise<boolean>} - True if the world info was successfully created, false otherwise
*/
async function createNewWorldInfo(worldName, { interactive = false } = {}) {
2023-07-20 19:32:15 +02:00
const worldInfoTemplate = { entries: {} };
if (!worldName) {
return false;
}
2024-05-23 00:39:49 +02:00
const sanitizedWorldName = await getSanitizedFilename(worldName);
const allowed = await checkOverwriteExistingData('World Info', world_names, sanitizedWorldName, { interactive: interactive, actionName: 'Create', deleteAction: (existingName) => deleteWorldInfo(existingName) });
if (!allowed) {
return false;
2023-07-20 19:32:15 +02:00
}
await saveWorldInfo(worldName, worldInfoTemplate, true);
2023-07-20 19:32:15 +02:00
await updateWorldInfoList();
const selectedIndex = world_names.indexOf(worldName);
2023-07-20 19:32:15 +02:00
if (selectedIndex !== -1) {
$('#world_editor_select').val(selectedIndex).trigger('change');
} else {
hideWorldEditor();
}
return true;
2023-07-20 19:32:15 +02:00
}
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 {
2023-12-02 20:11:06 +01:00
console.debug(`Character ${name}'s base world could not be found or is empty! Skipping...`);
2023-07-20 19:32:15 +02:00
}
// 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;
}
2023-10-16 22:13:32 +02:00
if (chat_metadata[METADATA_KEY] === worldName) {
console.debug(`Character ${name}'s world ${worldName} is already activated in chat lore! Skipping...`);
continue;
}
2023-07-20 19:32:15 +02:00
const data = await loadWorldInfoData(worldName);
const newEntries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(x => ({ ...x, world: worldName })) : [];
2023-07-20 19:32:15 +02:00
entries = entries.concat(newEntries);
}
console.debug(`Character ${name} lore (${Array.from(worldsToSearch)}) has ${entries.length} world info entries`);
2023-07-20 19:32:15 +02:00
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]).map(x => ({ ...x, world: worldName })) : [];
2023-07-20 19:32:15 +02:00
entries = entries.concat(newEntries);
}
console.debug(`Global world info has ${entries.length} entries`);
return entries;
}
2023-10-16 22:03:42 +02:00
async function getChatLore() {
const chatWorld = chat_metadata[METADATA_KEY];
if (!chatWorld) {
return [];
}
2023-10-16 22:13:32 +02:00
if (selected_world_info.includes(chatWorld)) {
console.debug(`Chat world ${chatWorld} is already activated in global world info! Skipping...`);
return [];
}
2023-10-16 22:03:42 +02:00
const data = await loadWorldInfoData(chatWorld);
const entries = data ? Object.keys(data.entries).map((x) => data.entries[x]).map(x => ({ ...x, world: chatWorld })) : [];
2023-10-16 22:03:42 +02:00
console.debug(`Chat lore has ${entries.length} entries`);
return entries;
}
export async function getSortedEntries() {
2023-07-20 19:32:15 +02:00
try {
const globalLore = await getGlobalLore();
const characterLore = await getCharacterLore();
2023-10-16 22:03:42 +02:00
const chatLore = await getChatLore();
2023-07-20 19:32:15 +02:00
let entries;
switch (Number(world_info_character_strategy)) {
case world_info_insertion_strategy.evenly:
entries = [...globalLore, ...characterLore].sort(sortFn);
break;
case world_info_insertion_strategy.character_first:
entries = [...characterLore.sort(sortFn), ...globalLore.sort(sortFn)];
break;
case world_info_insertion_strategy.global_first:
entries = [...globalLore.sort(sortFn), ...characterLore.sort(sortFn)];
break;
default:
2023-12-02 19:04:51 +01:00
console.error('Unknown WI insertion strategy: ', world_info_character_strategy, 'defaulting to evenly');
2023-07-20 19:32:15 +02:00
entries = [...globalLore, ...characterLore].sort(sortFn);
break;
}
2023-10-16 22:03:42 +02:00
// Chat lore always goes first
entries = [...chatLore.sort(sortFn), ...entries];
2023-07-20 19:32:15 +02:00
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
2023-09-26 08:53:04 +02:00
return structuredClone(entries);
2023-07-20 19:32:15 +02:00
}
catch (e) {
console.error(e);
return [];
}
}
2024-07-11 08:11:35 +02:00
/**
* Parse decorators from worldinfo content
* @param {string} content The content to parse
* @returns {[string[],string]} The decorators found in the content and the content without decorators
*/
2024-07-14 13:07:23 +02:00
function parseDecorators(content) {
2024-07-11 08:11:35 +02:00
/**
* Check if the decorator is known
* @param {string} data string to check
* @returns {boolean} true if the decorator is known
*/
const isKnownDecorator = (data) => {
2024-07-14 13:07:23 +02:00
if (data.startsWith('@@@')) {
data = data.substring(1);
2024-07-11 08:11:35 +02:00
}
2024-07-14 13:07:23 +02:00
for (let i = 0; i < KNOWN_DECORATORS.length; i++) {
if (data.startsWith(KNOWN_DECORATORS[i])) {
return true;
2024-07-11 08:11:35 +02:00
}
}
2024-07-14 13:07:23 +02:00
return false;
};
2024-07-11 08:11:35 +02:00
2024-07-14 13:07:23 +02:00
if (content.startsWith('@@')) {
2024-07-11 08:11:35 +02:00
let newContent = content;
const splited = content.split('\n');
2024-07-14 13:07:23 +02:00
let decorators = [];
2024-07-11 08:11:35 +02:00
let fallbacked = false;
for (let i = 0; i < splited.length; i++) {
2024-07-14 13:07:23 +02:00
if (splited[i].startsWith('@@')) {
if (splited[i].startsWith('@@@') && !fallbacked) {
continue;
2024-07-11 08:11:35 +02:00
}
2024-07-14 13:07:23 +02:00
if (isKnownDecorator(splited[i])) {
decorators.push(splited[i].startsWith('@@@') ? splited[i].substring(1) : splited[i]);
fallbacked = false;
2024-07-11 08:11:35 +02:00
}
2024-07-14 13:07:23 +02:00
else {
fallbacked = true;
2024-07-11 08:11:35 +02:00
}
} else {
newContent = splited.slice(i).join('\n');
break;
}
}
2024-07-14 13:07:23 +02:00
return [decorators, newContent];
2024-07-11 08:11:35 +02:00
}
2024-07-14 13:07:23 +02:00
return [[], content];
2024-07-11 08:11:35 +02:00
}
/**
* Performs a scan on the chat and returns the world info activated.
* @param {string[]} chat The chat messages to scan, in reverse order.
* @param {number} maxContext The maximum context size of the generation.
* @param {boolean} isDryRun Whether to perform a dry run.
* @typedef {{ worldInfoBefore: string, worldInfoAfter: string, EMEntries: any[], WIDepthEntries: any[], allActivatedEntries: Set<any> }} WIActivated
* @returns {Promise<WIActivated>} The world info activated.
*/
async function checkWorldInfo(chat, maxContext, isDryRun) {
2023-07-20 19:32:15 +02:00
const context = getContext();
const buffer = new WorldInfoBuffer(chat);
// Combine the chat
// Add the depth or AN if enabled
// Put this code here since otherwise, the chat reference is modified
2023-12-11 21:47:26 +01:00
for (const key of Object.keys(context.extensionPrompts)) {
if (context.extensionPrompts[key]?.scan) {
const prompt = getExtensionPromptByName(key);
if (prompt) {
buffer.addInject(prompt);
}
}
}
2024-07-03 23:18:46 +02:00
let scanState = scan_state.INITIAL;
let token_budget_overflowed = false;
2023-07-20 19:32:15 +02:00
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})`);
2023-07-20 19:32:15 +02:00
const sortedEntries = await getSortedEntries();
const timedEffects = new WorldInfoTimedEffects(chat, sortedEntries);
2023-07-20 19:32:15 +02:00
!isDryRun && timedEffects.checkTimedEffects();
2023-07-20 19:32:15 +02:00
if (sortedEntries.length === 0) {
2024-05-08 14:04:17 +02:00
return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
2023-07-20 19:32:15 +02:00
}
while (scanState) {
2024-07-03 23:18:46 +02:00
// Track how many times the loop has run. May be useful for debugging.
// eslint-disable-next-line no-unused-vars
2023-07-20 19:32:15 +02:00
count++;
let activatedNow = new Set();
for (let entry of sortedEntries) {
2024-07-11 08:11:35 +02:00
//oarse decorators
2024-07-11 23:42:06 +02:00
const [decorators, content] = parseDecorators(entry.content);
entry.decorators = decorators;
entry.content = content;
// Check if this entry applies to the character or if it's excluded
2023-11-14 22:54:08 +01:00
if (entry.characterFilter && entry.characterFilter?.names?.length > 0) {
const nameIncluded = entry.characterFilter.names.includes(getCharaFilename());
2023-12-02 20:11:06 +01:00
const filtered = entry.characterFilter.isExclude ? nameIncluded : !nameIncluded;
if (filtered) {
2023-11-14 22:54:08 +01:00
console.debug(`WI entry ${entry.uid} filtered out by character`);
continue;
}
}
2023-11-14 22:54:08 +01:00
if (entry.characterFilter && entry.characterFilter?.tags?.length > 0) {
const tagKey = getTagKeyForEntity(this_chid);
2023-11-14 22:54:08 +01:00
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;
}
}
}
}
const isSticky = timedEffects.isEffectActive('sticky', entry);
const isCooldown = timedEffects.isEffectActive('cooldown', entry);
2024-06-26 21:43:30 +02:00
const isDelay = timedEffects.isEffectActive('delay', entry);
if (isDelay) {
console.debug(`WI entry ${entry.uid} suppressed by delay`, entry);
continue;
}
if (isCooldown && !isSticky) {
2024-06-26 21:43:30 +02:00
console.debug(`WI entry ${entry.uid} suppressed by cooldown`, entry);
continue;
}
2023-07-20 19:32:15 +02:00
if (failedProbabilityChecks.has(entry)) {
continue;
}
if (allActivatedEntries.has(entry) || entry.disable == true) {
continue;
}
// Only use checks for recursion flags if the scan step was activated by recursion
2024-07-03 23:18:46 +02:00
if (scanState !== scan_state.RECURSION && entry.delayUntilRecursion) {
console.debug(`WI entry ${entry.uid} suppressed by delay until recursion`, entry);
continue;
}
if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion) {
console.debug(`WI entry ${entry.uid} suppressed by exclude recursion`, entry);
2023-07-20 19:32:15 +02:00
continue;
}
2024-07-14 13:07:23 +02:00
if (decorators.includes('@@activate')) {
//activate in any case
activatedNow.add(entry);
continue;
}
2024-07-14 13:07:23 +02:00
if (decorators.includes('@@dont_activate')) {
//deactivate in any case if @@activate is not present
continue;
}
2024-07-14 13:07:23 +02:00
if (entry.constant || buffer.isExternallyActivated(entry) || isSticky) {
2023-07-20 19:32:15 +02:00
activatedNow.add(entry);
continue;
}
2024-07-11 08:11:35 +02:00
2023-07-20 19:32:15 +02:00
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;
2023-12-04 22:57:04 +01:00
2023-07-20 19:32:15 +02:00
primary: for (let key of entry.key) {
const substituted = substituteParams(key);
const textToScan = buffer.get(entry, scanState);
2023-12-04 22:57:04 +01:00
if (substituted && buffer.matchKeys(textToScan, substituted.trim(), entry)) {
2023-12-04 22:57:04 +01:00
console.debug(`WI UID ${entry.uid} found by primary match: ${substituted}.`);
2023-07-20 19:32:15 +02:00
//selective logic begins
if (
entry.selective && //all entries are selective now
Array.isArray(entry.keysecondary) && //always true
entry.keysecondary.length //ignore empties
) {
2023-12-04 22:57:04 +01:00
console.debug(`WI UID:${entry.uid} found. Checking logic: ${entry.selectiveLogic}`);
2023-12-05 09:56:52 +01:00
let hasAnyMatch = false;
let hasAllMatch = true;
2023-07-20 19:32:15 +02:00
secondary: for (let keysecondary of entry.keysecondary) {
const secondarySubstituted = substituteParams(keysecondary);
const hasSecondaryMatch = secondarySubstituted && buffer.matchKeys(textToScan, secondarySubstituted.trim(), entry);
2023-12-04 22:57:04 +01:00
console.debug(`WI UID:${entry.uid}: Filtering for secondary keyword - "${secondarySubstituted}".`);
2023-12-05 09:56:52 +01:00
if (hasSecondaryMatch) {
hasAnyMatch = true;
}
if (!hasSecondaryMatch) {
hasAllMatch = false;
}
2023-12-05 11:04:27 +01:00
// Simplified AND ANY / NOT ALL if statement. (Proper fix for PR#1356 by Bronya)
// If AND ANY logic and the main checks pass OR if NOT ALL logic and the main checks do not pass
if ((selectiveLogic === world_info_logic.AND_ANY && hasSecondaryMatch) || (selectiveLogic === world_info_logic.NOT_ALL && !hasSecondaryMatch)) {
2023-12-04 23:05:05 +01:00
// Differ both logic statements in the debugger
2023-12-05 11:04:27 +01:00
if (selectiveLogic === world_info_logic.AND_ANY) {
console.debug(`(AND ANY Check) Activating WI Entry ${entry.uid}. Found match for word: ${substituted} ${secondarySubstituted}`);
2023-12-04 22:57:04 +01:00
} else {
2023-12-05 10:00:26 +01:00
console.debug(`(NOT ALL Check) Activating WI Entry ${entry.uid}. Found match for word "${substituted}" without secondary keyword: ${secondarySubstituted}`);
2023-07-20 19:32:15 +02:00
}
2023-12-04 22:57:04 +01:00
activatedNow.add(entry);
break secondary;
2023-07-20 19:32:15 +02:00
}
}
2023-12-05 09:56:52 +01:00
2023-12-05 11:04:27 +01:00
// Handle NOT ANY logic
if (selectiveLogic === world_info_logic.NOT_ANY && !hasAnyMatch) {
console.debug(`(NOT ANY Check) Activating WI Entry ${entry.uid}, no secondary keywords found.`);
2023-12-05 09:56:52 +01:00
activatedNow.add(entry);
}
// Handle AND ALL logic
if (selectiveLogic === world_info_logic.AND_ALL && hasAllMatch) {
console.debug(`(AND ALL Check) Activating WI Entry ${entry.uid}, all secondary keywords found.`);
activatedNow.add(entry);
}
2023-07-20 19:32:15 +02:00
} else {
2023-12-07 19:06:06 +01:00
// Handle cases where secondary is empty
2023-12-04 22:57:04 +01:00
console.debug(`WI UID ${entry.uid}: Activated without filter logic.`);
2023-07-20 19:32:15 +02:00
activatedNow.add(entry);
break primary;
}
2024-04-04 08:08:17 +02:00
}
}
2023-07-20 19:32:15 +02:00
}
}
scanState = world_info_recursive && activatedNow.size > 0 ? scan_state.RECURSION : scan_state.NONE;
2023-07-20 19:32:15 +02:00
const newEntries = [...activatedNow]
.sort((a, b) => sortedEntries.indexOf(a) - sortedEntries.indexOf(b));
2023-12-02 19:04:51 +01:00
let newContent = '';
2024-04-13 20:33:19 +02:00
const textToScanTokens = await getTokenCountAsync(allActivatedText);
2023-07-20 19:32:15 +02:00
const probabilityChecksBefore = failedProbabilityChecks.size;
2023-12-07 19:06:06 +01:00
filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState);
2023-12-07 19:06:06 +01:00
2023-12-02 20:11:06 +01:00
console.debug('-- PROBABILITY CHECKS BEGIN --');
2023-07-20 19:32:15 +02:00
for (const entry of newEntries) {
const rollValue = Math.random() * 100;
2024-05-06 16:00:42 +02:00
if (entry.useProbability && rollValue > entry.probability) {
const isSticky = timedEffects.isEffectActive('sticky', entry);
2024-06-22 14:04:49 +02:00
if (!isSticky) {
console.debug(`WI entry ${entry.uid} ${entry.key} failed probability check, skipping`);
failedProbabilityChecks.add(entry);
continue;
}
2023-12-02 20:11:06 +01:00
} else { console.debug(`uid:${entry.uid} passed probability check, inserting to prompt`); }
2023-07-20 19:32:15 +02:00
// Substitute macros inline, for both this checking and also future processing
2024-07-11 23:42:06 +02:00
entry.content = substituteParams(entry.content);
newContent += `${entry.content}\n`;
2023-07-20 19:32:15 +02:00
2024-04-13 20:33:19 +02:00
if ((textToScanTokens + (await getTokenCountAsync(newContent))) >= budget) {
2023-12-02 19:04:51 +01:00
console.debug('WI budget reached, stopping');
if (world_info_overflow_alert) {
2023-12-02 19:04:51 +01:00
console.log('Alerting');
2023-09-01 22:14:01 +02:00
toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');
}
scanState = scan_state.NONE;
token_budget_overflowed = true;
2023-07-20 19:32:15 +02:00
break;
}
allActivatedEntries.add(entry);
console.debug('WI entry activated:', entry);
}
const probabilityChecksAfter = failedProbabilityChecks.size;
if ((probabilityChecksAfter - probabilityChecksBefore) === activatedNow.size) {
2023-12-02 19:04:51 +01:00
console.debug('WI probability checks failed for all activated entries, stopping');
scanState = scan_state.NONE;
2023-07-20 19:32:15 +02:00
}
if (newEntries.length === 0) {
console.debug('No new entries activated, stopping');
scanState = scan_state.NONE;
}
if (scanState) {
2023-07-20 19:32:15 +02:00
const text = newEntries
.filter(x => !failedProbabilityChecks.has(x))
.filter(x => !x.preventRecursion)
.map(x => x.content).join('\n');
buffer.addRecurse(text);
allActivatedText = (text + '\n' + allActivatedText);
2023-07-20 19:32:15 +02:00
}
2023-10-30 22:55:32 +01:00
// world_info_min_activations
if (!scanState && !token_budget_overflowed) {
2023-10-30 22:55:32 +01:00
if (world_info_min_activations > 0 && (allActivatedEntries.size < world_info_min_activations)) {
let over_max = (
world_info_min_activations_depth_max > 0 &&
buffer.getDepth() > world_info_min_activations_depth_max
) || (buffer.getDepth() > chat.length);
if (!over_max) {
scanState = scan_state.MIN_ACTIVATIONS; // loop
buffer.advanceScanPosition();
2023-10-30 22:55:32 +01:00
}
}
}
2023-07-20 19:32:15 +02:00
}
// Forward-sorted list of entries for joining
const WIBeforeEntries = [];
const WIAfterEntries = [];
const EMEntries = [];
2023-07-20 19:32:15 +02:00
const ANTopEntries = [];
const ANBottomEntries = [];
const WIDepthEntries = [];
2023-07-20 19:32:15 +02:00
// Appends from insertion order 999 to 1. Use unshift for this purpose
// TODO (kingbri): Change to use WI Anchor positioning instead of separate top/bottom arrays
2023-07-20 19:32:15 +02:00
[...allActivatedEntries].sort(sortFn).forEach((entry) => {
const regexDepth = entry.position === world_info_position.atDepth ? (entry.depth ?? DEFAULT_DEPTH) : null;
const content = getRegexedString(entry.content, regex_placement.WORLD_INFO, { depth: regexDepth, isMarkdown: false, isPrompt: true });
2024-05-09 13:53:17 +02:00
if (!content) {
console.debug('Skipping adding WI entry to prompt due to empty content:', entry);
return;
}
2023-07-20 19:32:15 +02:00
switch (entry.position) {
case world_info_position.before:
WIBeforeEntries.unshift(substituteParams(content));
2023-07-20 19:32:15 +02:00
break;
case world_info_position.after:
WIAfterEntries.unshift(substituteParams(content));
2023-07-20 19:32:15 +02:00
break;
case world_info_position.EMTop:
EMEntries.unshift(
{ position: wi_anchor_position.before, content: content },
);
break;
case world_info_position.EMBottom:
EMEntries.unshift(
{ position: wi_anchor_position.after, content: content },
);
2023-07-20 19:32:15 +02:00
break;
case world_info_position.ANTop:
ANTopEntries.unshift(content);
2023-07-20 19:32:15 +02:00
break;
case world_info_position.ANBottom:
ANBottomEntries.unshift(content);
2023-07-20 19:32:15 +02:00
break;
2023-12-02 16:14:06 +01:00
case world_info_position.atDepth: {
const existingDepthIndex = WIDepthEntries.findIndex((e) => e.depth === (entry.depth ?? DEFAULT_DEPTH) && e.role === (entry.role ?? extension_prompt_roles.SYSTEM));
if (existingDepthIndex !== -1) {
WIDepthEntries[existingDepthIndex].entries.unshift(content);
} else {
WIDepthEntries.push({
depth: entry.depth,
entries: [content],
role: entry.role ?? extension_prompt_roles.SYSTEM,
});
}
2023-12-02 15:07:08 +01:00
break;
2023-12-02 16:14:06 +01:00
}
2023-07-20 19:32:15 +02:00
default:
break;
}
});
2023-12-02 19:04:51 +01:00
const worldInfoBefore = WIBeforeEntries.length ? WIBeforeEntries.join('\n') : '';
const worldInfoAfter = WIAfterEntries.length ? WIAfterEntries.join('\n') : '';
2023-07-20 19:32:15 +02:00
if (shouldWIAddPrompt) {
const originalAN = context.extensionPrompts[NOTE_MODULE_NAME].value;
const ANWithWI = `${ANTopEntries.join('\n')}\n${originalAN}\n${ANBottomEntries.join('\n')}`.replace(/(^\n)|(\n$)/g, '');
2024-03-23 17:45:37 +01:00
context.setExtensionPrompt(NOTE_MODULE_NAME, ANWithWI, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth], extension_settings.note.allowWIScan, chat_metadata[metadata_keys.role]);
2023-07-20 19:32:15 +02:00
}
!isDryRun && timedEffects.setTimedEffects(Array.from(allActivatedEntries));
buffer.resetExternalEffects();
timedEffects.cleanUp();
return { worldInfoBefore, worldInfoAfter, EMEntries, WIDepthEntries, allActivatedEntries };
2023-07-20 19:32:15 +02:00
}
2024-05-04 22:51:28 +02:00
/**
* Only leaves entries with the highest key matching score in each group.
* @param {Record<string, WIScanEntry[]>} groups The groups to filter
* @param {WorldInfoBuffer} buffer The buffer to use for scoring
2024-05-04 23:42:33 +02:00
* @param {(entry: WIScanEntry) => void} removeEntry The function to remove an entry
* @param {number} scanState The current scan state
2024-05-04 22:51:28 +02:00
*/
function filterGroupsByScoring(groups, buffer, removeEntry, scanState) {
2024-05-04 22:51:28 +02:00
for (const [key, group] of Object.entries(groups)) {
2024-05-04 23:42:33 +02:00
// Group scoring is disabled both globally and for the group entries
if (!world_info_use_group_scoring && !group.some(x => x.useGroupScoring)) {
console.debug(`Skipping group scoring for group '${key}'`);
continue;
}
const scores = group.map(entry => buffer.getScore(entry, scanState));
2024-05-04 22:51:28 +02:00
const maxScore = Math.max(...scores);
console.debug(`Group '${key}' max score: ${maxScore}`);
//console.table(group.map((entry, i) => ({ uid: entry.uid, key: JSON.stringify(entry.key), score: scores[i] })));
for (let i = 0; i < group.length; i++) {
2024-05-04 23:42:33 +02:00
const isScored = group[i].useGroupScoring ?? world_info_use_group_scoring;
if (!isScored) {
continue;
}
2024-05-04 22:51:28 +02:00
if (scores[i] < maxScore) {
console.debug(`Removing score loser from inclusion group '${key}' entry '${group[i].uid}'`, group[i]);
2024-05-05 00:08:49 +02:00
removeEntry(group[i]);
2024-05-04 22:51:28 +02:00
group.splice(i, 1);
scores.splice(i, 1);
i--;
}
}
}
}
2023-12-07 19:06:06 +01:00
/**
* Filters entries by inclusion groups.
* @param {object[]} newEntries Entries activated on current recursion level
* @param {Set<object>} allActivatedEntries Set of all activated entries
2024-05-04 22:51:28 +02:00
* @param {WorldInfoBuffer} buffer The buffer to use for scanning
* @param {number} scanState The current scan state
2023-12-07 19:06:06 +01:00
*/
function filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState) {
2023-12-07 19:06:06 +01:00
console.debug('-- INCLUSION GROUP CHECKS BEGIN --');
const grouped = newEntries.filter(x => x.group).reduce((acc, item) => {
item.group.split(/,\s*/).filter(x => x).forEach(group => {
if (!acc[group]) {
acc[group] = [];
}
acc[group].push(item);
});
2023-12-07 19:06:06 +01:00
return acc;
}, {});
if (Object.keys(grouped).length === 0) {
console.debug('No inclusion groups found');
return;
}
2024-04-27 02:23:37 +02:00
const removeEntry = (entry) => newEntries.splice(newEntries.indexOf(entry), 1);
function removeAllBut(group, chosen, logging = true) {
for (const entry of group) {
if (entry === chosen) {
continue;
}
if (logging) console.debug(`Removing loser from inclusion group '${entry.group}' entry '${entry.uid}'`, entry);
removeEntry(entry);
2024-04-27 02:23:37 +02:00
}
}
filterGroupsByScoring(grouped, buffer, removeEntry, scanState);
2024-05-04 23:42:33 +02:00
2023-12-07 19:06:06 +01:00
for (const [key, group] of Object.entries(grouped)) {
console.debug(`Checking inclusion group '${key}' with ${group.length} entries`, group);
if (Array.from(allActivatedEntries).some(x => x.group === key)) {
console.debug(`Skipping inclusion group check, group already activated '${key}'`);
// We need to forcefully deactivate all other entries in the group
2024-04-27 02:23:37 +02:00
removeAllBut(group, null, false);
2023-12-07 19:06:06 +01:00
continue;
}
if (!Array.isArray(group) || group.length <= 1) {
console.debug('Skipping inclusion group check, only one entry');
2023-12-07 19:06:06 +01:00
continue;
}
2024-04-27 02:23:37 +02:00
// Check for group prio
const prios = group.filter(x => x.groupOverride).sort(sortFn);
2024-04-27 02:23:37 +02:00
if (prios.length) {
console.debug(`Activated inclusion group '${key}' with by prio winner entry '${prios[0].uid}'`, prios[0]);
removeAllBut(group, prios[0]);
continue;
}
2024-05-06 16:00:42 +02:00
// Do weighted random using entry's weight
const totalWeight = group.reduce((acc, item) => acc + (item.groupWeight ?? DEFAULT_WEIGHT), 0);
2023-12-07 19:06:06 +01:00
const rollValue = Math.random() * totalWeight;
let currentWeight = 0;
let winner = null;
for (const entry of group) {
2024-05-06 16:27:43 +02:00
currentWeight += (entry.groupWeight ?? DEFAULT_WEIGHT);
2023-12-07 19:06:06 +01:00
if (rollValue <= currentWeight) {
2024-04-27 02:23:37 +02:00
console.debug(`Activated inclusion group '${key}' with roll winner entry '${entry.uid}'`, entry);
2023-12-07 19:06:06 +01:00
winner = entry;
break;
}
}
if (!winner) {
console.debug(`Failed to activate inclusion group '${key}', no winner found`);
continue;
}
// Remove every group item from newEntries but the winner
2024-04-27 02:23:37 +02:00
removeAllBut(group, winner);
2023-12-07 19:06:06 +01:00
}
}
2023-07-20 19:32:15 +02:00
function convertAgnaiMemoryBook(inputObj) {
const outputObj = { entries: {} };
inputObj.entries.forEach((entry, index) => {
outputObj.entries[index] = {
...newEntryTemplate,
2023-07-20 19:32:15 +02:00
uid: index,
key: entry.keywords,
keysecondary: [],
comment: entry.name,
content: entry.entry,
constant: false,
selective: false,
vectorized: false,
2023-12-05 11:04:27 +01:00
selectiveLogic: world_info_logic.AND_ANY,
2023-07-20 19:32:15 +02:00
order: entry.weight,
position: 0,
disable: !entry.enabled,
addMemo: !!entry.name,
excludeRecursion: false,
delayUntilRecursion: false,
2023-07-20 19:32:15 +02:00
displayIndex: index,
probability: 100,
useProbability: true,
group: '',
groupOverride: false,
2024-05-06 16:00:42 +02:00
groupWeight: DEFAULT_WEIGHT,
scanDepth: null,
caseSensitive: null,
matchWholeWords: null,
2024-05-04 23:42:33 +02:00
useGroupScoring: null,
automationId: '',
role: extension_prompt_roles.SYSTEM,
sticky: null,
cooldown: null,
2024-06-26 21:43:30 +02:00
delay: null,
2023-07-20 19:32:15 +02:00
};
});
return outputObj;
}
function convertRisuLorebook(inputObj) {
const outputObj = { entries: {} };
inputObj.data.forEach((entry, index) => {
outputObj.entries[index] = {
...newEntryTemplate,
2023-07-20 19:32:15 +02:00
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,
vectorized: false,
2023-12-05 11:04:27 +01:00
selectiveLogic: world_info_logic.AND_ANY,
2023-07-20 19:32:15 +02:00
order: entry.insertorder,
position: world_info_position.before,
disable: false,
addMemo: true,
excludeRecursion: false,
delayUntilRecursion: false,
2023-07-20 19:32:15 +02:00
displayIndex: index,
probability: entry.activationPercent ?? 100,
useProbability: entry.activationPercent ?? true,
group: '',
groupOverride: false,
2024-05-06 16:00:42 +02:00
groupWeight: DEFAULT_WEIGHT,
scanDepth: null,
caseSensitive: null,
matchWholeWords: null,
2024-05-04 23:42:33 +02:00
useGroupScoring: null,
automationId: '',
role: extension_prompt_roles.SYSTEM,
sticky: null,
cooldown: null,
2024-06-26 21:43:30 +02:00
delay: null,
2023-07-20 19:32:15 +02:00
};
});
return outputObj;
}
function convertNovelLorebook(inputObj) {
const outputObj = {
2023-12-02 21:06:57 +01:00
entries: {},
2023-07-20 19:32:15 +02:00
};
inputObj.entries.forEach((entry, index) => {
const displayName = entry.displayName;
const addMemo = displayName !== undefined && displayName.trim() !== '';
outputObj.entries[index] = {
...newEntryTemplate,
2023-07-20 19:32:15 +02:00
uid: index,
key: entry.keys,
keysecondary: [],
comment: displayName || '',
content: entry.text,
constant: false,
selective: false,
vectorized: false,
2023-12-05 11:04:27 +01:00
selectiveLogic: world_info_logic.AND_ANY,
2023-07-20 19:32:15 +02:00
order: entry.contextConfig?.budgetPriority ?? 0,
position: 0,
disable: !entry.enabled,
addMemo: addMemo,
excludeRecursion: false,
delayUntilRecursion: false,
2023-07-20 19:32:15 +02:00
displayIndex: index,
probability: 100,
useProbability: true,
group: '',
groupOverride: false,
2024-05-06 16:00:42 +02:00
groupWeight: DEFAULT_WEIGHT,
scanDepth: null,
caseSensitive: null,
matchWholeWords: null,
2024-05-04 23:42:33 +02:00
useGroupScoring: null,
automationId: '',
role: extension_prompt_roles.SYSTEM,
sticky: null,
cooldown: null,
2024-06-26 21:43:30 +02:00
delay: null,
2023-07-20 19:32:15 +02:00
};
});
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] = {
...newEntryTemplate,
2023-07-20 19:32:15 +02:00
uid: entry.id,
key: entry.keys,
keysecondary: entry.secondary_keys || [],
2023-12-02 19:04:51 +01:00
comment: entry.comment || '',
2023-07-20 19:32:15 +02:00
content: entry.content,
constant: entry.constant || false,
selective: entry.selective || false,
order: entry.insertion_order,
2023-12-02 19:04:51 +01:00
position: entry.extensions?.position ?? (entry.position === 'before_char' ? world_info_position.before : world_info_position.after),
2023-07-20 19:32:15 +02:00
excludeRecursion: entry.extensions?.exclude_recursion ?? false,
preventRecursion: entry.extensions?.prevent_recursion ?? false,
delayUntilRecursion: entry.extensions?.delay_until_recursion ?? false,
2023-07-20 19:32:15 +02:00
disable: !entry.enabled,
addMemo: entry.comment ? true : false,
displayIndex: entry.extensions?.display_index ?? index,
probability: entry.extensions?.probability ?? 100,
useProbability: entry.extensions?.useProbability ?? true,
2023-09-25 19:11:16 +02:00
depth: entry.extensions?.depth ?? DEFAULT_DEPTH,
2023-12-05 11:04:27 +01:00
selectiveLogic: entry.extensions?.selectiveLogic ?? world_info_logic.AND_ANY,
group: entry.extensions?.group ?? '',
groupOverride: entry.extensions?.group_override ?? false,
2024-05-06 16:00:42 +02:00
groupWeight: entry.extensions?.group_weight ?? DEFAULT_WEIGHT,
scanDepth: entry.extensions?.scan_depth ?? null,
caseSensitive: entry.extensions?.case_sensitive ?? null,
matchWholeWords: entry.extensions?.match_whole_words ?? null,
2024-05-04 23:42:33 +02:00
useGroupScoring: entry.extensions?.use_group_scoring ?? null,
automationId: entry.extensions?.automation_id ?? '',
role: entry.extensions?.role ?? extension_prompt_roles.SYSTEM,
vectorized: entry.extensions?.vectorized ?? false,
sticky: entry.extensions?.sticky ?? null,
cooldown: entry.extensions?.cooldown ?? null,
2024-06-26 21:43:30 +02:00
delay: entry.extensions?.delay ?? null,
2023-07-20 19:32:15 +02:00
};
});
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, 'true');
2023-10-04 21:41:10 +02:00
2023-10-10 19:47:54 +02:00
if (power_user.world_import_dialog) {
2023-12-02 20:56:16 +01:00
const html = `<h3>This character has an embedded World/Lorebook.</h3>
<h3>Would you like to import it now?</h3>
<div class="m-b-1">If you want to import it later, select "Import Card Lore" in the "More..." dropdown menu on the character panel.</div>`;
const checkResult = (result) => {
if (result) {
importEmbeddedWorldInfo(true);
}
};
callGenericPopup(html, POPUP_TYPE.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' },
);
}
2023-07-20 19:32:15 +02:00
}
return true;
}
return false;
}
2023-10-04 21:41:10 +02:00
export async function importEmbeddedWorldInfo(skipPopup = false) {
2023-07-20 19:32:15 +02:00
const chid = $('#import_character_info').data('chid');
if (chid === undefined) {
return;
}
const bookName = characters[chid]?.data?.character_book?.name || `${characters[chid]?.name}'s Lorebook`;
2023-10-04 21:41:10 +02:00
if (!skipPopup) {
const confirmation = await Popup.show.confirm(`Are you sure you want to import "${bookName}"?`, world_names.includes(bookName) ? 'It will overwrite the World/Lorebook with the same name.' : '');
2023-10-04 21:41:10 +02:00
if (!confirmation) {
return;
}
2023-07-20 19:32:15 +02:00
}
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..
2023-12-02 19:04:51 +01:00
$('#WIDrawerIcon').trigger('click');
//..auto-opening the new imported WI
2023-12-02 19:04:51 +01:00
$('#world_editor_select').val(newIndex).trigger('change');
2023-07-20 19:32:15 +02:00
}
setWorldInfoButtonClass(chid, true);
}
function onWorldInfoChange(args, text) {
if (args !== '__notSlashCommand__') { // if it's a slash command
2024-01-07 17:58:30 +01:00
const silent = isTrueBoolean(args.silent);
2023-11-25 21:26:41 +01:00
if (text.trim() !== '') { // and args are provided
2023-12-02 19:04:51 +01:00
const slashInputSplitText = text.trim().toLowerCase().split(',');
2023-07-20 19:32:15 +02:00
slashInputSplitText.forEach((worldName) => {
const wiElement = getWIElement(worldName);
if (wiElement.length > 0) {
const name = wiElement.text();
switch (args.state) {
case 'off': {
if (selected_world_info.includes(name)) {
selected_world_info.splice(selected_world_info.indexOf(name), 1);
wiElement.prop('selected', false);
if (!silent) toastr.success(`Deactivated world: ${name}`);
} else {
if (!silent) toastr.error(`World was not active: ${name}`);
}
break;
}
case 'toggle': {
if (selected_world_info.includes(name)) {
selected_world_info.splice(selected_world_info.indexOf(name), 1);
wiElement.prop('selected', false);
2024-01-07 20:19:46 +01:00
if (!silent) toastr.success(`Deactivated world: ${name}`);
} else {
selected_world_info.push(name);
wiElement.prop('selected', true);
2024-01-07 19:34:16 +01:00
if (!silent) toastr.success(`Activated world: ${name}`);
}
break;
}
case 'on':
default: {
selected_world_info.push(name);
wiElement.prop('selected', true);
if (!silent) toastr.success(`Activated world: ${name}`);
}
}
2023-07-20 19:32:15 +02:00
} else {
if (!silent) toastr.error(`No world found named: ${worldName}`);
2023-07-20 19:32:15 +02:00
}
});
2023-12-02 19:04:51 +01:00
$('#world_info').trigger('change');
2023-07-20 19:32:15 +02:00
} else { // if no args, unset all worlds
2024-01-07 18:00:16 +01:00
if (!silent) toastr.success('Deactivated all worlds');
2023-07-20 19:32:15 +02:00
selected_world_info = [];
2023-12-02 19:04:51 +01:00
$('#world_info').val(null).trigger('change');
2023-07-20 19:32:15 +02:00
}
} else { //if it's a pointer selection
let tempWorldInfo = [];
2023-12-02 19:04:51 +01:00
let selectedWorlds = $('#world_info').val().map((e) => Number(e)).filter((e) => !isNaN(e));
2023-07-20 19:32:15 +02:00
if (selectedWorlds.length > 0) {
selectedWorlds.forEach((worldIndex) => {
const existingWorldName = world_names[worldIndex];
if (existingWorldName) {
tempWorldInfo.push(existingWorldName);
} else {
const wiElement = getWIElement(existingWorldName);
2023-12-02 19:04:51 +01:00
wiElement.prop('selected', false);
2023-07-20 19:32:15 +02:00
toastr.error(`The world with ${existingWorldName} is invalid or corrupted.`);
}
});
}
selected_world_info = tempWorldInfo;
}
saveSettingsDebounced();
eventSource.emit(event_types.WORLDINFO_SETTINGS_UPDATED);
return '';
2023-07-20 19:32:15 +02:00
}
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;
}
const worldName = file.name.substr(0, file.name.lastIndexOf('.'));
const sanitizedWorldName = await getSanitizedFilename(worldName);
const allowed = await checkOverwriteExistingData('World Info', world_names, sanitizedWorldName, { interactive: true, actionName: 'Import', deleteAction: (existingName) => deleteWorldInfo(existingName) });
if (!allowed) {
return false;
}
2023-07-20 19:32:15 +02:00
jQuery.ajax({
2023-12-02 19:04:51 +01:00
type: 'POST',
2023-12-06 23:09:48 +01:00
url: '/api/worldinfo/import',
2023-07-20 19:32:15 +02:00
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) {
2023-12-02 19:04:51 +01:00
$('#world_editor_select').val(newIndex).trigger('change');
2023-07-20 19:32:15 +02:00
}
toastr.success(`World Info "${data.name}" imported successfully!`);
2023-07-20 19:32:15 +02:00
}
},
error: (_jqXHR, _exception) => { },
2023-07-20 19:32:15 +02:00
});
}
2023-10-16 22:03:42 +02:00
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');
}
2023-07-20 19:32:15 +02:00
jQuery(() => {
2023-12-02 19:04:51 +01:00
$('#world_info').on('mousedown change', async function (e) {
2023-07-20 19:32:15 +02:00
// If there's no world names, don't do anything
if (world_names.length === 0) {
e.preventDefault();
return;
}
onWorldInfoChange('__notSlashCommand__');
});
//**************************WORLD INFO IMPORT EXPORT*************************//
2023-12-02 19:04:51 +01:00
$('#world_import_button').on('click', function () {
$('#world_import_file').trigger('click');
2023-07-20 19:32:15 +02:00
});
2023-12-02 19:04:51 +01:00
$('#world_import_file').on('change', async function (e) {
2024-05-08 14:04:17 +02:00
if (!(e.target instanceof HTMLInputElement)) {
return;
}
2023-07-20 19:32:15 +02:00
const file = e.target.files[0];
await importWorldInfo(file);
// Will allow to select the same file twice in a row
e.target.value = '';
2023-07-20 19:32:15 +02:00
});
2023-12-02 19:04:51 +01:00
$('#world_create_button').on('click', async () => {
2023-07-20 19:32:15 +02:00
const tempName = getFreeWorldName();
const finalName = await Popup.show.input('Create a new World Info', 'Enter a name for the new file:', tempName);
2023-07-20 19:32:15 +02:00
if (finalName) {
await createNewWorldInfo(finalName, { interactive: true });
2023-07-20 19:32:15 +02:00
}
});
2023-12-02 19:04:51 +01:00
$('#world_editor_select').on('change', async () => {
$('#world_info_search').val('');
worldInfoFilter.setFilterData(FILTER_TYPES.WORLD_INFO_SEARCH, '', true);
2023-12-02 19:04:51 +01:00
const selectedIndex = String($('#world_editor_select').find(':selected').val());
2023-07-20 19:32:15 +02:00
2023-12-02 19:04:51 +01:00
if (selectedIndex === '') {
2023-07-20 19:32:15 +02:00
hideWorldEditor();
} else {
const worldName = world_names[selectedIndex];
showWorldEditor(worldName);
}
});
const saveSettings = () => {
2023-12-02 20:11:06 +01:00
saveSettingsDebounced();
eventSource.emit(event_types.WORLDINFO_SETTINGS_UPDATED);
2023-12-02 20:11:06 +01:00
};
2023-12-02 19:04:51 +01:00
$('#world_info_depth').on('input', function () {
2023-07-20 19:32:15 +02:00
world_info_depth = Number($(this).val());
2023-12-02 19:04:51 +01:00
$('#world_info_depth_counter').val($(this).val());
saveSettings();
2023-07-20 19:32:15 +02:00
});
2023-12-02 19:04:51 +01:00
$('#world_info_min_activations').on('input', function () {
world_info_min_activations = Number($(this).val());
2023-12-02 19:04:51 +01:00
$('#world_info_min_activations_counter').val($(this).val());
saveSettings();
});
2023-12-02 19:04:51 +01:00
$('#world_info_min_activations_depth_max').on('input', function () {
world_info_min_activations_depth_max = Number($(this).val());
2023-12-02 19:04:51 +01:00
$('#world_info_min_activations_depth_max_counter').val($(this).val());
saveSettings();
});
2023-12-02 19:04:51 +01:00
$('#world_info_budget').on('input', function () {
2023-07-20 19:32:15 +02:00
world_info_budget = Number($(this).val());
2023-12-02 19:04:51 +01:00
$('#world_info_budget_counter').val($(this).val());
saveSettings();
2023-07-20 19:32:15 +02:00
});
$('#world_info_include_names').on('input', function () {
world_info_include_names = !!$(this).prop('checked');
saveSettings();
});
2023-12-02 19:04:51 +01:00
$('#world_info_recursive').on('input', function () {
2023-07-20 19:32:15 +02:00
world_info_recursive = !!$(this).prop('checked');
saveSettings();
2023-11-04 16:44:43 +01:00
});
2023-07-20 19:32:15 +02:00
$('#world_info_case_sensitive').on('input', function () {
world_info_case_sensitive = !!$(this).prop('checked');
saveSettings();
2023-11-04 16:44:43 +01:00
});
2023-07-20 19:32:15 +02:00
$('#world_info_match_whole_words').on('input', function () {
world_info_match_whole_words = !!$(this).prop('checked');
saveSettings();
2023-07-20 19:32:15 +02:00
});
$('#world_info_character_strategy').on('change', function () {
2023-10-17 12:55:02 +02:00
world_info_character_strategy = Number($(this).val());
saveSettings();
2023-07-20 19:32:15 +02:00
});
$('#world_info_overflow_alert').on('change', function () {
2023-08-07 21:12:50 +02:00
world_info_overflow_alert = !!$(this).prop('checked');
saveSettingsDebounced();
});
2024-05-04 22:51:28 +02:00
$('#world_info_use_group_scoring').on('change', function () {
world_info_use_group_scoring = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#world_info_budget_cap').on('input', function () {
world_info_budget_cap = Number($(this).val());
2023-12-02 19:04:51 +01:00
$('#world_info_budget_cap_counter').val(world_info_budget_cap);
saveSettings();
});
2023-07-20 19:32:15 +02:00
2023-10-17 12:55:02 +02:00
$('#world_button').on('click', async function (event) {
2023-07-20 19:32:15 +02:00
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) {
2023-07-20 19:32:15 +02:00
if (!$('#WorldInfo').is(':visible')) {
$('#WIDrawerIcon').trigger('click');
}
const index = world_names.indexOf(worldName);
2023-12-02 19:04:51 +01:00
$('#world_editor_select').val(index).trigger('change');
} else if (hasEmbed && !event.shiftKey) {
2023-07-20 19:32:15 +02:00
await importEmbeddedWorldInfo();
saveCharacterDebounced();
}
else {
$('#char-management-dropdown').val($('#set_character_world').val()).trigger('change');
}
}
});
2024-04-28 06:27:55 +02:00
const debouncedWorldInfoSearch = debounce((searchQuery) => {
2024-04-28 05:42:15 +02:00
worldInfoFilter.setFilterData(FILTER_TYPES.WORLD_INFO_SEARCH, searchQuery);
});
$('#world_info_search').on('input', function () {
2024-04-28 05:42:15 +02:00
const searchQuery = $(this).val();
2024-04-28 06:27:55 +02:00
debouncedWorldInfoSearch(searchQuery);
2023-07-20 19:32:15 +02:00
});
2023-10-08 11:30:12 +02:00
$('#world_refresh').on('click', () => {
updateEditor(navigation_option.previous);
});
2023-11-11 19:16:57 +01:00
$('#world_info_sort_order').on('change', function () {
2023-12-02 19:04:51 +01:00
const value = String($(this).find(':selected').val());
2024-04-30 01:39:47 +02:00
// Save sort order, but do not save search sorting, as this is a temporary sorting option
2024-04-30 02:27:44 +02:00
if (value !== 'search') localStorage.setItem(SORT_ORDER_KEY, value);
updateEditor(navigation_option.none);
2023-12-02 20:11:06 +01:00
});
2023-10-16 22:03:42 +02:00
$(document).on('click', '.chat_lorebook_button', assignLorebookToChat);
2023-07-20 19:32:15 +02:00
// Not needed on mobile
if (!isMobile()) {
2023-07-20 19:32:15 +02:00
$('#world_info').select2({
width: '100%',
placeholder: 'No Worlds active. Click here to select.',
allowClear: true,
closeOnSelect: false,
});
2024-05-02 20:04:24 +02:00
// Subscribe world loading to the select2 multiselect items (We need to target the specific select2 control)
select2ChoiceClickSubscribe($('#world_info'), target => {
const name = $(target).text();
const selectedIndex = world_names.indexOf(name);
if (selectedIndex !== -1) {
$('#world_editor_select').val(selectedIndex).trigger('change');
console.log('Quick selection of world', name);
2024-05-02 20:04:24 +02:00
}
}, { buttonStyle: true, closeDrawer: true });
2023-07-20 19:32:15 +02:00
}
$('#WorldInfo').on('scroll', () => {
2024-02-25 02:54:40 +01:00
$('.world_entry input[name="group"], .world_entry input[name="automationId"]').each((_, el) => {
const instance = $(el).autocomplete('instance');
if (instance !== undefined) {
$(el).autocomplete('close');
}
});
});
2023-12-05 11:04:27 +01:00
});