From 51d7ba728f6ea8543ae97c0f046d7c9e25d98005 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 16 Jun 2024 02:59:37 +0200 Subject: [PATCH 01/58] Tag commands enum providers --- public/script.js | 4 + .../slash-commands/SlashCommandParser.js | 5 +- public/scripts/tags.js | 87 +++++++++++-------- 3 files changed, 57 insertions(+), 39 deletions(-) diff --git a/public/script.js b/public/script.js index 7ddede025..f8d300003 100644 --- a/public/script.js +++ b/public/script.js @@ -2339,6 +2339,10 @@ export function substituteParamsExtended(content, additionalMacro = {}) { * @returns {string} The string with substituted parameters. */ export function substituteParams(content, _name1, _name2, _original, _group, _replaceCharacterCard = true, additionalMacro = {}) { + if (!content) { + return ''; + } + const environment = {}; if (typeof _original === 'string') { diff --git a/public/scripts/slash-commands/SlashCommandParser.js b/public/scripts/slash-commands/SlashCommandParser.js index 8dffa666e..8c2d7d5d9 100644 --- a/public/scripts/slash-commands/SlashCommandParser.js +++ b/public/scripts/slash-commands/SlashCommandParser.js @@ -18,6 +18,9 @@ import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgu import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js'; +/** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */ +/** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */ + /**@readonly*/ /**@enum {Number}*/ export const PARSER_FLAG = { @@ -32,7 +35,7 @@ export class SlashCommandParser { /** * @deprecated Use SlashCommandParser.addCommandObject() instead. * @param {string} command Command name - * @param {(namedArguments:Object., unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|void|Promise} callback The function to execute when the command is called + * @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise} callback callback The function to execute when the command is called * @param {string[]} aliases List of alternative command names * @param {string} helpString Help text shown in autocomplete and command browser */ diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 05015c686..0a31c9914 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -10,6 +10,8 @@ import { buildAvatarList, eventSource, event_types, + substituteParams, + printCharacters, } from '../script.js'; // eslint-disable-next-line no-unused-vars import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js'; @@ -1546,17 +1548,18 @@ function registerTagsSlashCommands() { * @param {string?} [charName] The optionally provided char name * @returns {string?} - The char/group key, or null if none found */ - function paraGetCharKey(charName) { + function paraGetCharKey(charName, { suppressLogging = false } = {}) { const entity = charName ? (characters.find(x => x.name === charName) || groups.find(x => x.name == charName)) : (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]); const key = getTagKeyForEntity(entity); if (!key) { - toastr.warning(`Character ${charName} not found.`); + if (!suppressLogging) toastr.warning(`Character ${charName} not found.`); return null; } return key; } + /** * Gets a tag by its name. Optionally can create the tag if it does not exist. * @param {string} tagName - The name of the tag @@ -1580,18 +1583,23 @@ function registerTagsSlashCommands() { return tag; } - function updateTagsList() { - switch (menu_type) { - case 'characters': - printTagFilters(tag_filter_types.character); - printTagFilters(tag_filter_types.group_member); - break; - case 'character_edit': - applyTagsOnCharacterSelect(); - break; - case 'group_edit': - select_group_chats(selected_group, true); - break; + /** A collection of enum providers used for the tag slash commands */ + const enumProviders = { + /** Get a list of all possible character and group names */ + charName: () => [ + ...characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')), + ...groups.map(it => new SlashCommandEnumValue(it.name, null, 'variable', 'G')), + ], + /** + * Get A list of all possible tags for the given char/group entity + * @param {'all' | 'existing' | 'not-existing'} mode - Which types of tags to + */ + tagForChar: (mode) => (/** @type {SlashCommandExecutor} */ executor) => { + // Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags. + const key = paraGetCharKey(substituteParams(/**@type {string?}*/(executor.namedArgumentList.find(it => it.name == 'name')?.value)), { suppressLogging: true }); + const assigned = key ? getTagsList(key) : []; + return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it)) + .map(it => new SlashCommandEnumValue(it.name, it.title)); } } @@ -1605,7 +1613,7 @@ function registerTagsSlashCommands() { const tag = paraGetTag(tagName, { allowCreate: true }); if (!tag) return 'false'; const result = addTagToEntity(tag, key); - updateTagsList(); + printCharacters(); return String(result); }, namedArgumentList: [ @@ -1613,22 +1621,14 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: ()=>[ - ...characters.map(it=>new SlashCommandEnumValue(it.name, null, 'qr', 'C')), - ...groups.map(it=>new SlashCommandEnumValue(it.name, null, 'variable', 'G')), - ], + enumProvider: enumProviders.charName, }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'tag name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: (executor)=>{ - const key = paraGetCharKey(/**@type {string}*/(executor.namedArgumentList.find(it=>it.name == 'name')?.value)); - if (!key) return tags.map(it=>new SlashCommandEnumValue(it.name, it.title)); - const assigned = getTagsList(key); - return tags.filter(it=>!assigned.includes(it)).map(it=>new SlashCommandEnumValue(it.name, it.title)); - }, + enumProvider: enumProviders.tagForChar('not-existing'), forceEnum: false, }), ], @@ -1658,7 +1658,7 @@ function registerTagsSlashCommands() { const tag = paraGetTag(tagName); if (!tag) return 'false'; const result = removeTagFromEntity(tag, key); - updateTagsList(); + printCharacters(); return String(result); }, namedArgumentList: [ @@ -1666,10 +1666,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: ()=>[ - ...characters.map(it=>new SlashCommandEnumValue(it.name, null, 'qr', 'C')), - ...groups.map(it=>new SlashCommandEnumValue(it.name, null, 'variable', 'G')), - ], + enumProvider: enumProviders.charName, }), ], unnamedArgumentList: [ @@ -1677,11 +1674,7 @@ function registerTagsSlashCommands() { typeList: [ARGUMENT_TYPE.STRING], isRequired: true, /**@param {SlashCommandExecutor} executor */ - enumProvider: (executor)=>{ - const key = paraGetCharKey(/**@type {string}*/(executor.namedArgumentList.find(it=>it.name == 'name')?.value)); - if (!key) return tags.map(it=>new SlashCommandEnumValue(it.name, it.title)); - return getTagsList(key).map(it=>new SlashCommandEnumValue(it.name, it.title)); - }, + enumProvider: enumProviders.tagForChar('existing'), }), ], helpString: ` @@ -1711,10 +1704,22 @@ function registerTagsSlashCommands() { return String(tag_map[key].includes(tag.id)); }, namedArgumentList: [ - new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'Character name', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: '{{char}}', + enumProvider: enumProviders.charName, + }), ], unnamedArgumentList: [ - new SlashCommandArgument('tag name', [ARGUMENT_TYPE.STRING], true), + SlashCommandArgument.fromProps({ + description: 'tag name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + /**@param {SlashCommandExecutor} executor */ + enumProvider: enumProviders.tagForChar('all'), + }), ], helpString: `
@@ -1742,7 +1747,13 @@ function registerTagsSlashCommands() { return tags.map(x => x.name).join(', '); }, namedArgumentList: [ - new SlashCommandNamedArgument('name', 'Character name', [ARGUMENT_TYPE.STRING], false, false, '{{char}}'), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'Character name', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: '{{char}}', + enumProvider: enumProviders.charName, + }), ], helpString: `
From 34e8cf476a0b012db39f71024141774cef402db4 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 16 Jun 2024 03:14:43 +0200 Subject: [PATCH 02/58] Default enum values for STScript boolean argument --- public/scripts/slash-commands/SlashCommandArgument.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/scripts/slash-commands/SlashCommandArgument.js b/public/scripts/slash-commands/SlashCommandArgument.js index 324d5b9d6..4fc566369 100644 --- a/public/scripts/slash-commands/SlashCommandArgument.js +++ b/public/scripts/slash-commands/SlashCommandArgument.js @@ -78,6 +78,9 @@ export class SlashCommandArgument { }); this.enumProvider = enumProvider; this.forceEnum = forceEnum; + + // If no enums were set explictly and the type is one where we know possible enum values, we set them here + if (!this.enumList.length && types == ARGUMENT_TYPE.BOOLEAN) this.enumList = [new SlashCommandEnumValue('true'), new SlashCommandEnumValue('false')]; } } From 6f7ef2536974868b62b4ec3168763b115a1eda25 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Mon, 17 Jun 2024 03:30:52 +0200 Subject: [PATCH 03/58] Rework slash command enum values pt.2 - Fix jsconfig module resolution for imports in frontend scripts - Add file with common slash command enum values --- public/jsconfig.json | 1 + public/scripts/authors-note.js | 4 + public/scripts/backgrounds.js | 9 +- public/scripts/openai.js | 10 +- public/scripts/power-user.js | 10 +- public/scripts/preset-manager.js | 9 +- public/scripts/slash-commands.js | 2 +- .../slash-commands/SlashCommandArgument.js | 55 +- .../slash-commands/SlashCommandClosure.js | 2 +- .../SlashCommandCommonEnumsProvider.js | 84 +++ public/scripts/tags.js | 82 +-- public/scripts/variables.js | 590 ++++++++++++------ public/scripts/world-info.js | 154 +++-- 13 files changed, 661 insertions(+), 351 deletions(-) create mode 100644 public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js diff --git a/public/jsconfig.json b/public/jsconfig.json index aad133bc6..f362b9731 100644 --- a/public/jsconfig.json +++ b/public/jsconfig.json @@ -3,6 +3,7 @@ "checkJs": true, "target": "ESNext", "module": "ESNext", + "moduleResolution": "node", "allowUmdGlobalAccess": true, "allowSyntheticDefaultImports": true }, diff --git a/public/scripts/authors-note.js b/public/scripts/authors-note.js index f9643a8d6..31a5719e1 100644 --- a/public/scripts/authors-note.js +++ b/public/scripts/authors-note.js @@ -38,6 +38,7 @@ const chara_note_position = { function setNoteTextCommand(_, text) { $('#extension_floating_prompt').val(text).trigger('input'); toastr.success('Author\'s Note text updated'); + return ''; } function setNoteDepthCommand(_, text) { @@ -50,6 +51,7 @@ function setNoteDepthCommand(_, text) { $('#extension_floating_depth').val(Math.abs(value)).trigger('input'); toastr.success('Author\'s Note depth updated'); + return ''; } function setNoteIntervalCommand(_, text) { @@ -62,6 +64,7 @@ function setNoteIntervalCommand(_, text) { $('#extension_floating_interval').val(Math.abs(value)).trigger('input'); toastr.success('Author\'s Note frequency updated'); + return ''; } function setNotePositionCommand(_, text) { @@ -79,6 +82,7 @@ function setNotePositionCommand(_, text) { $(`input[name="extension_floating_position"][value="${position}"]`).prop('checked', true).trigger('input'); toastr.info('Author\'s Note position updated'); + return ''; } function updateSettings() { diff --git a/public/scripts/backgrounds.js b/public/scripts/backgrounds.js index 8a3979c45..fcd603789 100644 --- a/public/scripts/backgrounds.js +++ b/public/scripts/backgrounds.js @@ -95,7 +95,7 @@ function onLockBackgroundClick(e) { if (!chatName) { toastr.warning('Select a chat to lock the background for it'); - return; + return ''; } const relativeBgImage = getUrlParameter(this); @@ -103,6 +103,7 @@ function onLockBackgroundClick(e) { saveBackgroundMetadata(relativeBgImage); setCustomBackground(); highlightLockedBackground(); + return ''; } function onUnlockBackgroundClick(e) { @@ -110,6 +111,7 @@ function onUnlockBackgroundClick(e) { removeBackgroundMetadata(); unsetCustomBackground(); highlightLockedBackground(); + return ''; } function hasCustomBackground() { @@ -319,7 +321,7 @@ async function autoBackgroundCommand() { const options = bgTitles.map(x => ({ element: x, text: x.innerText.trim() })).filter(x => x.text.length > 0); if (options.length == 0) { toastr.warning('No backgrounds to choose from. Please upload some images to the "backgrounds" folder.'); - return; + return ''; } const list = options.map(option => `- ${option.text}`).join('\n'); @@ -330,11 +332,12 @@ async function autoBackgroundCommand() { if (bestMatch.length == 0) { toastr.warning('No match found. Please try again.'); - return; + return ''; } console.debug('Automatically choosing background:', bestMatch); bestMatch[0].item.element.click(); + return ''; } export async function getBackgrounds() { diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 33ddf87fa..69c65eab8 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -70,6 +70,7 @@ import { saveLogprobsForActiveMessage } from './logprobs.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; +import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; export { openai_messages_count, @@ -4616,9 +4617,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ returns: 'current proxy', namedArgumentList: [], unnamedArgumentList: [ - new SlashCommandArgument( - 'name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => proxies.map(preset => new SlashCommandEnumValue(preset.name)), + }), ], helpString: 'Sets a proxy preset by name.', })); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 435b5f8d1..efff375db 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -2883,7 +2883,7 @@ function setAvgBG() { return `rgba(${rNew.toFixed(0)}, ${gNew.toFixed(0)}, ${bNew.toFixed(0)}, 1)`; } - + return ''; } async function setThemeCallback(_, text) { @@ -3887,9 +3887,11 @@ $(document).ready(() => { name: 'random', callback: doRandomChat, unnamedArgumentList: [ - new SlashCommandArgument( - 'optional tag name', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandArgument.fromProps({ + description: 'optional tag name', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: () => tags.filter(tag => Object.values(tag_map).some(x => x.includes(tag.id))).map(tag => new SlashCommandEnumValue(tag.name)), + }), ], helpString: 'Start a new chat with a random character. If an argument is provided, only considers characters that have the specified tag.', })); diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index 42cc7585a..6b3b2f0ab 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -22,6 +22,7 @@ import { kai_settings } from './kai-settings.js'; import { context_presets, getContextSettings, power_user } from './power-user.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; +import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { textgenerationwebui_preset_names, @@ -481,9 +482,11 @@ export async function initPresetManager() { returns: 'current preset', namedArgumentList: [], unnamedArgumentList: [ - new SlashCommandArgument( - 'name', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandArgument.fromProps({ + description: 'name', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: () => getPresetManager().getAllPresets().map(preset => new SlashCommandEnumValue(preset)), + }), ], helpString: `
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index f5a89eb45..99381c1e8 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -42,7 +42,7 @@ import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandPa import { SlashCommandParserError } from './slash-commands/SlashCommandParserError.js'; import { getMessageTimeStamp } from './RossAscends-mods.js'; import { hideChatMessageRange } from './chats.js'; -import { getContext, saveMetadataDebounced } from './extensions.js'; +import { extension_settings, getContext, saveMetadataDebounced } from './extensions.js'; import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js'; import { chat_completion_sources, oai_settings } from './openai.js'; diff --git a/public/scripts/slash-commands/SlashCommandArgument.js b/public/scripts/slash-commands/SlashCommandArgument.js index 4fc566369..2355b0020 100644 --- a/public/scripts/slash-commands/SlashCommandArgument.js +++ b/public/scripts/slash-commands/SlashCommandArgument.js @@ -2,8 +2,6 @@ import { SlashCommandClosure } from './SlashCommandClosure.js'; import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; import { SlashCommandExecutor } from './SlashCommandExecutor.js'; - - /**@readonly*/ /**@enum {string}*/ export const ARGUMENT_TYPE = { @@ -18,20 +16,18 @@ export const ARGUMENT_TYPE = { 'DICTIONARY': 'dictionary', }; - - export class SlashCommandArgument { /** * Creates an unnamed argument from a properties object. * @param {Object} props * @param {string} props.description description of the argument - * @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} props.typeList default: ARGUMENT_TYPE.STRING - list of accepted types (from ARGUMENT_TYPE) - * @param {boolean} [props.isRequired] default: false - whether the argument is required (false = optional argument) - * @param {boolean} [props.acceptsMultiple] default: false - whether argument accepts multiple values - * @param {string|SlashCommandClosure} [props.defaultValue] default value if no value is provided - * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList] list of accepted values - * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider] function that returns auto complete options - * @param {boolean} [props.forceEnum] default: true - whether the input must match one of the enum values + * @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} [props.typeList=[ARGUMENT_TYPE.STRING]] default: ARGUMENT_TYPE.STRING - list of accepted types (from ARGUMENT_TYPE) + * @param {boolean} [props.isRequired=false] default: false - whether the argument is required (false = optional argument) + * @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values + * @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided + * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values + * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options + * @param {boolean} [props.forceEnum=true] default: true - whether the input must match one of the enum values */ static fromProps(props) { return new SlashCommandArgument( @@ -46,9 +42,6 @@ export class SlashCommandArgument { ); } - - - /**@type {string}*/ description; /**@type {ARGUMENT_TYPE[]}*/ typeList = []; /**@type {boolean}*/ isRequired = false; @@ -58,7 +51,6 @@ export class SlashCommandArgument { /**@type {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]}*/ enumProvider = null; /**@type {boolean}*/ forceEnum = true; - /** * @param {string} description * @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types @@ -84,22 +76,20 @@ export class SlashCommandArgument { } } - - export class SlashCommandNamedArgument extends SlashCommandArgument { /** * Creates an unnamed argument from a properties object. * @param {Object} props * @param {string} props.name the argument's name - * @param {string[]} [props.aliasList] list of aliases * @param {string} props.description description of the argument - * @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} props.typeList default: ARGUMENT_TYPE.STRING - list of accepted types (from ARGUMENT_TYPE) - * @param {boolean} [props.isRequired] default: false - whether the argument is required (false = optional argument) - * @param {boolean} [props.acceptsMultiple] default: false - whether argument accepts multiple values - * @param {string|SlashCommandClosure} [props.defaultValue] default value if no value is provided - * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList] list of accepted values - * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider] function that returns auto complete options - * @param {boolean} [props.forceEnum] default: true - whether the input must match one of the enum values + * @param {string[]} [props.aliasList=[]] list of aliases + * @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} [props.typeList=[ARGUMENT_TYPE.STRING]] default: ARGUMENT_TYPE.STRING - list of accepted types (from ARGUMENT_TYPE) + * @param {boolean} [props.isRequired=false] default: false - whether the argument is required (false = optional argument) + * @param {boolean} [props.acceptsMultiple=false] default: false - whether argument accepts multiple values + * @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided + * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values + * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options + * @param {boolean} [props.forceEnum=true] default: true - whether the input must match one of the enum values */ static fromProps(props) { return new SlashCommandNamedArgument( @@ -116,21 +106,20 @@ export class SlashCommandNamedArgument extends SlashCommandArgument { ); } - - - /**@type {string}*/ name; /**@type {string[]}*/ aliasList = []; - /** * @param {string} name * @param {string} description * @param {ARGUMENT_TYPE|ARGUMENT_TYPE[]} types - * @param {string|SlashCommandClosure} defaultValue - * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} enums - * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} enumProvider function that returns auto complete options - * @param {boolean} forceEnum + * @param {boolean} [isRequired=false] + * @param {boolean} [acceptsMultiple=false] + * @param {string|SlashCommandClosure} [defaultValue=null] + * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [enums=[]] + * @param {string[]} [aliases=[]] + * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [enumProvider=null] function that returns auto complete options + * @param {boolean} [forceEnum=true] */ constructor(name, description, types, isRequired = false, acceptsMultiple = false, defaultValue = null, enums = [], aliases = [], enumProvider = null, forceEnum = true) { super(description, types, isRequired, acceptsMultiple, defaultValue, enums, enumProvider, forceEnum); diff --git a/public/scripts/slash-commands/SlashCommandClosure.js b/public/scripts/slash-commands/SlashCommandClosure.js index 89eda369a..6f62cc69b 100644 --- a/public/scripts/slash-commands/SlashCommandClosure.js +++ b/public/scripts/slash-commands/SlashCommandClosure.js @@ -93,7 +93,7 @@ export class SlashCommandClosure { /** * - * @returns Promise + * @returns {Promise} */ async execute() { const closure = this.getCopy(); diff --git a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js new file mode 100644 index 000000000..d38b5a68a --- /dev/null +++ b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js @@ -0,0 +1,84 @@ +import { chat_metadata, characters, substituteParams } from "../../script.js"; +import { extension_settings } from "../extensions.js"; +import { groups } from "../group-chats.js"; +import { searchCharByName, getTagsList, tags } from "../tags.js"; +import { SlashCommandEnumValue } from "./SlashCommandEnumValue.js"; +import { SlashCommandExecutor } from "./SlashCommandExecutor.js"; + +/** + * A collection of common enum providers + * + * Can be used on `SlashCommandNamedArgument` and `SlashCommandArgument` and their `enumProvider` property. + */ +export const commonEnumProviders = { + /** + * All possible variable names + * + * Can be filtered by `type` to only show global or local variables + * + * @param {...('global'|'local'|'scope'|'all')} type - The type of variables to include in the array. Can be 'all', 'global', or 'local'. + * @returns {() => SlashCommandEnumValue[]} + */ + variables: (...type) => () => { + const types = type.flat(); + const isAll = types.includes('all'); + return [ + ...isAll || types.includes('global') ? Object.keys(chat_metadata.variables).map(x => new SlashCommandEnumValue(x, null, 'variable', 'L')) : [], + ...isAll || types.includes('local') ? Object.keys(extension_settings.variables.global).map(x => new SlashCommandEnumValue(x, null, 'variable', 'G')) : [], + ...isAll || types.includes('scope') ? [].map(x => new SlashCommandEnumValue(x, null, 'variable', 'S')) : [], // TODO: Add scoped variables here, Lenny + ] + }, + + /** + * All possible character and group names + * + * @returns {SlashCommandEnumValue[]} + */ + charName: () => [ + ...characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')), + ...groups.map(it => new SlashCommandEnumValue(it.name, null, 'variable', 'G')), + ], + + /** + * All possible tags for a given char/group entity + * + * @param {'all' | 'existing' | 'not-existing'} mode - Which types of tags to show + * @returns {() => SlashCommandEnumValue[]} + */ + tagsForChar: (mode) => (/** @type {SlashCommandExecutor} */ executor) => { + // Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags. + const key = searchCharByName(substituteParams(/**@type {string?}*/(executor.namedArgumentList.find(it => it.name == 'name')?.value)), { suppressLogging: true }); + const assigned = key ? getTagsList(key) : []; + return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it)) + .map(it => new SlashCommandEnumValue(it.name, it.title)); + }, + + /** + * All existing worlds / lorebooks + * + * @returns {SlashCommandEnumValue[]} + */ + worlds: () => $('#world_info').children().toArray().map(x => new SlashCommandEnumValue(x.textContent)), +}; + + +/** + * Get the unicode icon for the given enum value type + * @param {string} type The type of the enum value + * @returns {string} the unicode icon + */ +export function getEnumIconByValueType(type) { + // Remove nullable types definition to match type icon + type = type.replace(/\?$/, ''); + + switch (type) { + case 'boolean': return '🔲'; + case 'string': return '📝'; + case 'number': return '1️⃣'; + case 'array': return '📦'; + case 'enum': return '📚'; + case 'dictionary': return '📖'; + case 'closure': return '🧩'; + default: return '◊'; + } +} diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 0a31c9914..62981e4ed 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -24,6 +24,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js'; +import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; export { TAG_FOLDER_TYPES, @@ -469,6 +470,28 @@ export function getTagKeyForEntityElement(element) { return undefined; } + +/** + * Gets the key for char/group by searching based on the name or avatar. If none can be found, a toastr will be shown and null returned. + * This function is mostly used in slash commands. + * + * @param {string?} [charName] The optionally provided char name + * @param {object} [options] - Optional arguments + * @param {boolean} [options.suppressLogging=false] - Whether to suppress the toastr warning + * @returns {string?} - The char/group key, or null if none found + */ +export function searchCharByName(charName, { suppressLogging = false } = {}) { + const entity = charName + ? (characters.find(x => x.name === charName) || groups.find(x => x.name == charName)) + : (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]); + const key = getTagKeyForEntity(entity); + if (!key) { + if (!suppressLogging) toastr.warning(`Character ${charName} not found.`); + return null; + } + return key; +} + /** * Adds a tag to a given entity * @param {Tag} tag - The tag to add @@ -1543,23 +1566,6 @@ function printViewTagList(empty = true) { } function registerTagsSlashCommands() { - /** - * Gets the key for char/group for a slash command. If none can be found, a toastr will be shown and null returned. - * @param {string?} [charName] The optionally provided char name - * @returns {string?} - The char/group key, or null if none found - */ - function paraGetCharKey(charName, { suppressLogging = false } = {}) { - const entity = charName - ? (characters.find(x => x.name === charName) || groups.find(x => x.name == charName)) - : (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]); - const key = getTagKeyForEntity(entity); - if (!key) { - if (!suppressLogging) toastr.warning(`Character ${charName} not found.`); - return null; - } - return key; - } - /** * Gets a tag by its name. Optionally can create the tag if it does not exist. * @param {string} tagName - The name of the tag @@ -1583,32 +1589,12 @@ function registerTagsSlashCommands() { return tag; } - /** A collection of enum providers used for the tag slash commands */ - const enumProviders = { - /** Get a list of all possible character and group names */ - charName: () => [ - ...characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')), - ...groups.map(it => new SlashCommandEnumValue(it.name, null, 'variable', 'G')), - ], - /** - * Get A list of all possible tags for the given char/group entity - * @param {'all' | 'existing' | 'not-existing'} mode - Which types of tags to - */ - tagForChar: (mode) => (/** @type {SlashCommandExecutor} */ executor) => { - // Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags. - const key = paraGetCharKey(substituteParams(/**@type {string?}*/(executor.namedArgumentList.find(it => it.name == 'name')?.value)), { suppressLogging: true }); - const assigned = key ? getTagsList(key) : []; - return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it)) - .map(it => new SlashCommandEnumValue(it.name, it.title)); - } - } - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'tag-add', returns: 'true/false - Whether the tag was added or was assigned already', /** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */ callback: ({ name }, tagName) => { - const key = paraGetCharKey(name); + const key = searchCharByName(name); if (!key) return 'false'; const tag = paraGetTag(tagName, { allowCreate: true }); if (!tag) return 'false'; @@ -1621,14 +1607,14 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: enumProviders.charName, + enumProvider: commonEnumProviders.charName, }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'tag name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: enumProviders.tagForChar('not-existing'), + enumProvider: commonEnumProviders.tagsForChar('not-existing'), forceEnum: false, }), ], @@ -1653,7 +1639,7 @@ function registerTagsSlashCommands() { returns: 'true/false - Whether the tag was removed or wasn\'t assigned already', /** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */ callback: ({ name }, tagName) => { - const key = paraGetCharKey(name); + const key = searchCharByName(name); if (!key) return 'false'; const tag = paraGetTag(tagName); if (!tag) return 'false'; @@ -1666,7 +1652,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: enumProviders.charName, + enumProvider: commonEnumProviders.charName, }), ], unnamedArgumentList: [ @@ -1674,7 +1660,7 @@ function registerTagsSlashCommands() { typeList: [ARGUMENT_TYPE.STRING], isRequired: true, /**@param {SlashCommandExecutor} executor */ - enumProvider: enumProviders.tagForChar('existing'), + enumProvider: commonEnumProviders.tagsForChar('existing'), }), ], helpString: ` @@ -1697,7 +1683,7 @@ function registerTagsSlashCommands() { returns: 'true/false - Whether the given tag name is assigned to the character', /** @param {{name: string}} namedArgs @param {string} tagName @returns {string} */ callback: ({ name }, tagName) => { - const key = paraGetCharKey(name); + const key = searchCharByName(name); if (!key) return 'false'; const tag = paraGetTag(tagName); if (!tag) return 'false'; @@ -1709,7 +1695,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: enumProviders.charName, + enumProvider: commonEnumProviders.charName, }), ], unnamedArgumentList: [ @@ -1718,7 +1704,7 @@ function registerTagsSlashCommands() { typeList: [ARGUMENT_TYPE.STRING], isRequired: true, /**@param {SlashCommandExecutor} executor */ - enumProvider: enumProviders.tagForChar('all'), + enumProvider: commonEnumProviders.tagsForChar('all'), }), ], helpString: ` @@ -1741,7 +1727,7 @@ function registerTagsSlashCommands() { returns: 'Comma-separated list of all assigned tags', /** @param {{name: string}} namedArgs @returns {string} */ callback: ({ name }) => { - const key = paraGetCharKey(name); + const key = searchCharByName(name); if (!key) return ''; const tags = getTagsList(key); return tags.map(x => x.name).join(', '); @@ -1752,7 +1738,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: enumProviders.charName, + enumProvider: commonEnumProviders.charName, }), ], helpString: ` diff --git a/public/scripts/variables.js b/public/scripts/variables.js index de8ff390b..05e812bca 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -1,16 +1,20 @@ import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js'; import { extension_settings, saveMetadataDebounced } from './extensions.js'; -import { executeSlashCommands, executeSlashCommandsWithOptions } from './slash-commands.js'; +import { executeSlashCommandsWithOptions } from './slash-commands.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js'; +import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; import { isFalseBoolean } from './utils.js'; +/** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */ +/** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ + const MAX_LOOPS = 100; function getLocalVariable(name, args = {}) { @@ -116,6 +120,7 @@ function setGlobalVariable(name, value, args = {}) { extension_settings.variables.global[name] = value; } saveSettingsDebounced(); + return value; } function addLocalVariable(name, value) { @@ -314,11 +319,12 @@ function listVariablesCallback() { const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message)); sendSystemMessage(system_message_types.GENERIC, htmlMessage); + return ''; } /** * - * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args + * @param {NamedArguments} args * @param {(string|SlashCommandClosure)[]} value */ async function whileCallback(args, value) { @@ -360,8 +366,8 @@ async function whileCallback(args, value) { /** * - * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args - * @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value + * @param {NamedArguments} args + * @param {UnnamedArguments} value * @returns */ async function timesCallback(args, value) { @@ -398,7 +404,7 @@ async function timesCallback(args, value) { /** * - * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args + * @param {NamedArguments} args * @param {(string|SlashCommandClosure)[]} value */ async function ifCallback(args, value) { @@ -765,13 +771,13 @@ function randValuesCallback(from, to, args) { if (args.round == 'floor') { return Math.floor(value); } - return String(value); + return value; } /** * Declare a new variable in the current scope. - * @param {{_scope:SlashCommandScope, key?:string}} args Named arguments. - * @param {String|[String, SlashCommandClosure]} value Name and optional value for the variable. + * @param {NamedArguments} args Named arguments. + * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]} value Name and optional value for the variable. * @returns The variable's value */ function letCallback(args, value) { @@ -784,7 +790,9 @@ function letCallback(args, value) { const val = value; args._scope.letVariable(key, val); return val; - } else if (value.includes(' ')) { + } + if (value instanceof SlashCommandClosure) throw new Error('/let unnamed argument does not support closures if no key is provided'); + if (value.includes(' ')) { const key = value.split(' ')[0]; const val = value.split(' ').slice(1).join(' '); args._scope.letVariable(key, val); @@ -795,7 +803,7 @@ function letCallback(args, value) { /** * Set or retrieve a variable in the current scope or nearest ancestor scope. - * @param {{_hasUnnamedArgument:boolean, _scope:SlashCommandScope, key?:string, index?:string|number}} args Named arguments. + * @param {NamedArguments} args Named arguments. * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]} value Name and optional value for the variable. * @returns The variable's value */ @@ -822,7 +830,7 @@ function varCallback(args, value) { } /** - * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args + * @param {NamedArguments} args * @param {SlashCommandClosure} value * @returns {string} */ @@ -834,8 +842,8 @@ function closureSerializeCallback(args, value) { } /** - * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args - * @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value + * @param {NamedArguments} args + * @param {UnnamedArguments} value * @returns {SlashCommandClosure} */ function closureDeserializeCallback(args, value) { @@ -846,17 +854,24 @@ function closureDeserializeCallback(args, value) { } export function registerVariableCommands() { - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'listvar', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'listvar', callback: listVariablesCallback, helpString: 'List registered chat variables.', })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'setvar', - callback: (args, value) => setLocalVariable(args.key || args.name, value, args), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'setvar', + callback: (args, value) => String(setLocalVariable(args.key || args.name, value, args)), returns: 'the set variable value', namedArgumentList: [ - new SlashCommandNamedArgument( - 'key', 'variable name', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('local'), + forceEnum: false, + }), new SlashCommandNamedArgument( 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, ), @@ -880,21 +895,28 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'getvar', - callback: (args, value) => getLocalVariable(value, args), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'getvar', + callback: (args, value) => String(getLocalVariable(value, args)), returns: 'the variable value', namedArgumentList: [ - new SlashCommandNamedArgument( - 'key', 'variable name', [ARGUMENT_TYPE.VARIABLE_NAME], false, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('local'), + }), new SlashCommandNamedArgument( 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'key', [ARGUMENT_TYPE.VARIABLE_NAME], false, - ), + SlashCommandArgument.fromProps({ + description: 'key', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: false, + enumProvider: commonEnumProviders.variables('local'), + }), ], helpString: `
@@ -916,13 +938,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'addvar', - callback: (args, value) => addLocalVariable(args.key || args.name, value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'addvar', + callback: (args, value) => String(addLocalVariable(args.key || args.name, value)), returns: 'the new variable value', namedArgumentList: [ - new SlashCommandNamedArgument( - 'key', 'variable name', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('local'), + forceEnum: false, + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -943,13 +971,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'setglobalvar', - callback: (args, value) => setGlobalVariable(args.key || args.name, value, args), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'setglobalvar', + callback: (args, value) => String(setGlobalVariable(args.key || args.name, value, args)), returns: 'the set global variable value', namedArgumentList: [ - new SlashCommandNamedArgument( - 'key', 'variable name', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('global'), + forceEnum: false, + }), new SlashCommandNamedArgument( 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, ), @@ -973,21 +1007,27 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'getglobalvar', - callback: (args, value) => getGlobalVariable(value, args), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'getglobalvar', + callback: (args, value) => String(getGlobalVariable(value, args)), returns: 'global variable value', namedArgumentList: [ - new SlashCommandNamedArgument( - 'key', 'variable name', [ARGUMENT_TYPE.VARIABLE_NAME], false, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('global'), + }), new SlashCommandNamedArgument( 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'key', [ARGUMENT_TYPE.VARIABLE_NAME], false, - ), + SlashCommandArgument.fromProps({ + description: 'key', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('global'), + }), ], helpString: `
@@ -1009,13 +1049,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'addglobalvar', - callback: (args, value) => addGlobalVariable(args.key || args.name, value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'addglobalvar', + callback: (args, value) => String(addGlobalVariable(args.key || args.name, value)), returns: 'the new variable value', namedArgumentList: [ - new SlashCommandNamedArgument( - 'key', 'variable name', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('global'), + forceEnum: false, + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -1036,13 +1082,19 @@ export function registerVariableCommands() { `, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'incvar', - callback: (_, value) => incrementLocalVariable(value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'incvar', + callback: (_, value) => String(incrementLocalVariable(value)), returns: 'the new variable value', unnamedArgumentList: [ - new SlashCommandArgument( - 'key', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('local'), + forceEnum: false, + }), ], helpString: `
@@ -1058,13 +1110,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'decvar', - callback: (_, value) => decrementLocalVariable(value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'decvar', + callback: (_, value) => String(decrementLocalVariable(value)), returns: 'the new variable value', unnamedArgumentList: [ - new SlashCommandArgument( - 'key', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('local'), + forceEnum: false, + }), ], helpString: `
@@ -1080,13 +1138,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'incglobalvar', - callback: (_, value) => incrementGlobalVariable(value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'incglobalvar', + callback: (_, value) => String(incrementGlobalVariable(value)), returns: 'the new variable value', unnamedArgumentList: [ - new SlashCommandArgument( - 'key', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('global'), + forceEnum: false, + }), ], helpString: `
@@ -1102,13 +1166,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'decglobalvar', - callback: (_, value) => decrementGlobalVariable(value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'decglobalvar', + callback: (_, value) => String(decrementGlobalVariable(value)), returns: 'the new variable value', unnamedArgumentList: [ - new SlashCommandArgument( - 'key', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('global'), + forceEnum: false, + }), ], helpString: `
@@ -1124,7 +1194,8 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'if', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'if', callback: ifCallback, returns: 'result of the executed command ("then" or "else")', namedArgumentList: [ @@ -1136,16 +1207,16 @@ export function registerVariableCommands() { ), new SlashCommandNamedArgument( 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ - new SlashCommandEnumValue('gt', 'a > b'), + new SlashCommandEnumValue('gt', 'a > b'), new SlashCommandEnumValue('gte', 'a >= b'), - new SlashCommandEnumValue('lt', 'a < b'), + new SlashCommandEnumValue('lt', 'a < b'), new SlashCommandEnumValue('lte', 'a <= b'), - new SlashCommandEnumValue('eq', 'a == b'), + new SlashCommandEnumValue('eq', 'a == b'), new SlashCommandEnumValue('neq', 'a !== b'), new SlashCommandEnumValue('not', '!a'), - new SlashCommandEnumValue('in', 'a includes b'), - new SlashCommandEnumValue('nin', 'a not includes b'), - ], + new SlashCommandEnumValue('in', 'a includes b'), + new SlashCommandEnumValue('nin', 'a not includes b'), + ], ), new SlashCommandNamedArgument( 'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false, @@ -1191,7 +1262,8 @@ export function registerVariableCommands() { `, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'while', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'while', callback: whileCallback, returns: 'result of the last executed command', namedArgumentList: [ @@ -1203,16 +1275,16 @@ export function registerVariableCommands() { ), new SlashCommandNamedArgument( 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ - new SlashCommandEnumValue('gt', 'a > b'), + new SlashCommandEnumValue('gt', 'a > b'), new SlashCommandEnumValue('gte', 'a >= b'), - new SlashCommandEnumValue('lt', 'a < b'), + new SlashCommandEnumValue('lt', 'a < b'), new SlashCommandEnumValue('lte', 'a <= b'), - new SlashCommandEnumValue('eq', 'a == b'), + new SlashCommandEnumValue('eq', 'a == b'), new SlashCommandEnumValue('neq', 'a !== b'), new SlashCommandEnumValue('not', '!a'), - new SlashCommandEnumValue('in', 'a includes b'), - new SlashCommandEnumValue('nin', 'a not includes b'), - ], + new SlashCommandEnumValue('in', 'a includes b'), + new SlashCommandEnumValue('nin', 'a not includes b'), + ], ), new SlashCommandNamedArgument( 'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, ['off'], @@ -1260,7 +1332,8 @@ export function registerVariableCommands() { `, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'times', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'times', callback: timesCallback, returns: 'result of the last executed command', namedArgumentList: [], @@ -1299,13 +1372,16 @@ export function registerVariableCommands() { `, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'flushvar', - callback: (_, value) => deleteLocalVariable(value), - namedArgumentList: [], + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'flushvar', + callback: async (_, value) => deleteLocalVariable(value instanceof SlashCommandClosure ? (await value.execute())?.pipe : String(value)), unnamedArgumentList: [ - new SlashCommandArgument( - 'key', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('local'), + }), ], helpString: `
@@ -1321,13 +1397,17 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'flushglobalvar', - callback: (_, value) => deleteGlobalVariable(value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'flushglobalvar', + callback: async (_, value) => deleteGlobalVariable(value instanceof SlashCommandClosure ? (await value.execute())?.pipe : String(value)), namedArgumentList: [], unnamedArgumentList: [ - new SlashCommandArgument( - 'key', [ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('global'), + }), ], helpString: `
@@ -1344,13 +1424,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'add', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'add', callback: addValuesCallback, returns: 'sum of the provided values', unnamedArgumentList: [ - new SlashCommandArgument( - 'values', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, true, - ), + SlashCommandArgument.fromProps({ + description: 'values to sum', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + acceptsMultiple: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1367,16 +1453,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'mul', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'mul', callback: (args, value) => mulValuesCallback(args, value), - result: 'product of the provided values', + returns: 'product of the provided values', unnamedArgumentList: [ - new SlashCommandArgument( - 'values to multiply', - [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - true, - true, - ), + SlashCommandArgument.fromProps({ + description: 'values to multiply', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + acceptsMultiple: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1392,16 +1481,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'max', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'max', callback: maxValuesCallback, returns: 'maximum value of the set of values', unnamedArgumentList: [ - new SlashCommandArgument( - 'values', - [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - true, - true, - ), + SlashCommandArgument.fromProps({ + description: 'values to find the max', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + acceptsMultiple: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1417,13 +1509,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'min', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'min', callback: minValuesCallback, returns: 'minimum value of the set of values', unnamedArgumentList: [ - new SlashCommandArgument( - 'values', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, true, - ), + SlashCommandArgument.fromProps({ + description: 'values to find the min', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + acceptsMultiple: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1440,13 +1538,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sub', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'sub', callback: subValuesCallback, returns: 'difference of the provided values', unnamedArgumentList: [ - new SlashCommandArgument( - 'values', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, true, - ), + SlashCommandArgument.fromProps({ + description: 'values to find the difference', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + acceptsMultiple: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1463,16 +1567,25 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'div', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'div', callback: divValuesCallback, returns: 'result of division', unnamedArgumentList: [ - new SlashCommandArgument( - 'dividend', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), - new SlashCommandArgument( - 'divisor', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'dividend', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), + SlashCommandArgument.fromProps({ + description: 'divisor', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1489,16 +1602,25 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'mod', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'mod', callback: modValuesCallback, returns: 'result of modulo operation', unnamedArgumentList: [ - new SlashCommandArgument( - 'dividend', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), - new SlashCommandArgument( - 'divisor', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'dividend', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), + SlashCommandArgument.fromProps({ + description: 'divisor', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1515,16 +1637,25 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pow', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'pow', callback: powValuesCallback, returns: 'result of power operation', unnamedArgumentList: [ - new SlashCommandArgument( - 'base', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), - new SlashCommandArgument( - 'exponent', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'base', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), + SlashCommandArgument.fromProps({ + description: 'exponent', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1541,13 +1672,18 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sin', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'sin', callback: sinValuesCallback, returns: 'sine of the provided value', unnamedArgumentList: [ - new SlashCommandArgument( - 'value', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'value', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1564,13 +1700,18 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'cos', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'cos', callback: cosValuesCallback, returns: 'cosine of the provided value', unnamedArgumentList: [ - new SlashCommandArgument( - 'value', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'value', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1587,14 +1728,19 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'log', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'log', callback: logValuesCallback, returns: 'log of the provided value', namedArgumentList: [], unnamedArgumentList: [ - new SlashCommandArgument( - 'value', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'value', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1611,13 +1757,18 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'abs', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'abs', callback: absValuesCallback, returns: 'absolute value of the provided value', unnamedArgumentList: [ - new SlashCommandArgument( - 'value', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'value', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1634,13 +1785,18 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sqrt', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'sqrt', callback: sqrtValuesCallback, returns: 'square root of the provided value', unnamedArgumentList: [ - new SlashCommandArgument( - 'value', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'value', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1657,13 +1813,18 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'round', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'round', callback: roundValuesCallback, returns: 'rounded value', unnamedArgumentList: [ - new SlashCommandArgument( - 'value', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'value', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1680,13 +1841,18 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'len', - callback: (_, value) => lenValuesCallback(value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'len', + callback: (_, value) => String(lenValuesCallback(value)), returns: 'length of the provided value', unnamedArgumentList: [ - new SlashCommandArgument( - 'value', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], true, - ), + SlashCommandArgument.fromProps({ + description: 'value', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], helpString: `
@@ -1702,8 +1868,9 @@ export function registerVariableCommands() {
`, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'rand', - callback: (args, value) => randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value.length ? value : 1)), args), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'rand', + callback: (args, value) => String(randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value ? value : 1)), args)), returns: 'random number', namedArgumentList: [ new SlashCommandNamedArgument( @@ -1755,13 +1922,18 @@ export function registerVariableCommands() { `, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'var', - callback: (args, value) => varCallback(args, value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'var', + callback: (/** @type {NamedArguments} */ args, value) => varCallback(args, value), returns: 'the variable value', namedArgumentList: [ - new SlashCommandNamedArgument( - 'key', 'variable name; forces setting the variable, even if no value is provided', [ARGUMENT_TYPE.VARIABLE_NAME], false, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name; forces setting the variable, even if no value is provided', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('scope'), + forceEnum: false, + }), new SlashCommandNamedArgument( 'index', 'optional index for list or dictionary', @@ -1771,12 +1943,12 @@ export function registerVariableCommands() { ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'variable name', - [ARGUMENT_TYPE.VARIABLE_NAME], - false, // isRequired - false, // acceptsMultiple - ), + SlashCommandArgument.fromProps({ + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('scope'), + forceEnum: false, + }), new SlashCommandArgument( 'variable value', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY, ARGUMENT_TYPE.CLOSURE], @@ -1802,18 +1974,26 @@ export function registerVariableCommands() { `, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'let', - callback: (args, value) => letCallback(args, value), + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'let', + callback: (/** @type {NamedArguments} */ args, value) => letCallback(args, value), returns: 'the variable value', namedArgumentList: [ - new SlashCommandNamedArgument( - 'key', 'variable name', [ARGUMENT_TYPE.VARIABLE_NAME], false, - ), + SlashCommandNamedArgument.fromProps({ + name: 'key', + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('scope'), + forceEnum: false, + }), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'variable name', [ARGUMENT_TYPE.VARIABLE_NAME], false, - ), + SlashCommandArgument.fromProps({ + description: 'variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('scope'), + forceEnum: false, + }), new SlashCommandArgument( 'variable value', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY, ARGUMENT_TYPE.CLOSURE], ), @@ -1839,16 +2019,18 @@ export function registerVariableCommands() { `, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'closure-serialize', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'closure-serialize', /** * - * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args + * @param {NamedArguments} args * @param {SlashCommandClosure} value * @returns {string} */ - callback: (args, value)=>closureSerializeCallback(args, value), + callback: (args, value) => closureSerializeCallback(args, value), unnamedArgumentList: [ - SlashCommandArgument.fromProps({ description: 'the closure to serialize', + SlashCommandArgument.fromProps({ + description: 'the closure to serialize', typeList: [ARGUMENT_TYPE.CLOSURE], isRequired: true, }), @@ -1868,15 +2050,17 @@ export function registerVariableCommands() { `, })); - SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'closure-deserialize', + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'closure-deserialize', /** - * @param {import('./slash-commands/SlashCommand.js').NamedArguments} args - * @param {import('./slash-commands/SlashCommand.js').UnnamedArguments} value + * @param {NamedArguments} args + * @param {UnnamedArguments} value * @returns {SlashCommandClosure} */ - callback: (args, value)=>closureDeserializeCallback(args, value), + callback: (args, value) => closureDeserializeCallback(args, value), unnamedArgumentList: [ - SlashCommandArgument.fromProps({ description: 'serialized closure', + SlashCommandArgument.fromProps({ + description: 'serialized closure', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, }), diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 76bb3ab3f..d2d9565a8 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -13,6 +13,8 @@ import { getRegexedString, regex_placement } from './extensions/regex/engine.js' 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 } from './slash-commands/SlashCommandEnumValue.js'; +import { commonEnumProviders, getEnumIconByValueType } from './slash-commands/SlashCommandCommonEnumsProvider.js'; export { world_info, @@ -698,6 +700,12 @@ function registerWorldInfoSlashCommands() { 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)}`, 'property', getEnumIconByValueType(value.type))), + }; + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'world', callback: onWorldInfoChange, namedArgumentList: [ @@ -709,9 +717,11 @@ function registerWorldInfoSlashCommands() { ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'name', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandArgument.fromProps({ + description: 'world name', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: commonEnumProviders.worlds, + }), ], helpString: `
@@ -726,17 +736,26 @@ function registerWorldInfoSlashCommands() { 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', aliases: ['findlore', 'findwi'], returns: 'UID', callback: findBookEntryCallback, namedArgumentList: [ - new SlashCommandNamedArgument( - 'file', 'bookName', ARGUMENT_TYPE.STRING, true, - ), - new SlashCommandNamedArgument( - 'field', 'field value for fuzzy match (default: key)', ARGUMENT_TYPE.STRING, false, false, 'key', - ), + SlashCommandNamedArgument.fromProps({ + name: 'file', + description: 'book name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.loreBooks, + }), + SlashCommandNamedArgument.fromProps({ + name: 'field', + description: 'field value for fuzzy match (default: key)', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: 'key', + enumList: localEnumProviders.wiEntryFields(), + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -762,12 +781,20 @@ function registerWorldInfoSlashCommands() { callback: getEntryFieldCallback, returns: 'field value', namedArgumentList: [ - new SlashCommandNamedArgument( - 'file', 'bookName', ARGUMENT_TYPE.STRING, true, - ), - new SlashCommandNamedArgument( - 'field', 'field to retrieve (default: content)', ARGUMENT_TYPE.STRING, false, false, 'content', - ), + SlashCommandNamedArgument.fromProps({ + name: 'file', + description: 'book name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.loreBooks, + }), + SlashCommandNamedArgument.fromProps({ + name: 'field', + description: 'field to retrieve (default: content)', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: 'content', + enumList: localEnumProviders.wiEntryFields(), + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -793,9 +820,13 @@ function registerWorldInfoSlashCommands() { aliases: ['createlore', 'createwi'], returns: 'UID of the new record', namedArgumentList: [ - new SlashCommandNamedArgument( - 'file', 'book name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'file', + description: 'book name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.loreBooks, + }), new SlashCommandNamedArgument( 'key', 'record key', [ARGUMENT_TYPE.STRING], false, ), @@ -823,15 +854,23 @@ function registerWorldInfoSlashCommands() { callback: setEntryFieldCallback, aliases: ['setlorefield', 'setwifield'], namedArgumentList: [ - new SlashCommandNamedArgument( - 'file', 'book name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'file', + description: 'book name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.loreBooks, + }), new SlashCommandNamedArgument( 'uid', 'record UID', [ARGUMENT_TYPE.STRING], true, ), - new SlashCommandNamedArgument( - 'field', 'field name', [ARGUMENT_TYPE.STRING], true, false, 'content', - ), + SlashCommandNamedArgument.fromProps({ + name: 'field', + description: 'field name (default: content)', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: 'content', + enumList: localEnumProviders.wiEntryFields(), + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -2450,36 +2489,47 @@ function deleteWorldInfoEntry(data, uid) { delete data.entries[uid]; } -const newEntryTemplate = { - key: [], - keysecondary: [], - comment: '', - content: '', - constant: false, - vectorized: false, - selective: true, - selectiveLogic: world_info_logic.AND_ANY, - addMemo: false, - order: 100, - position: 0, - disable: false, - excludeRecursion: false, - preventRecursion: false, - delayUntilRecursion: false, - probability: 100, - useProbability: true, - depth: DEFAULT_DEPTH, - group: '', - groupOverride: false, - groupWeight: DEFAULT_WEIGHT, - scanDepth: null, - caseSensitive: null, - matchWholeWords: null, - useGroupScoring: null, - automationId: '', - role: 0, +/** + * 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' }, }; +const newEntryTemplate = Object.fromEntries( + Object.entries(newEntryDefinition).map(([key, value]) => [key, value.default]) +); + function createWorldInfoEntry(_name, data) { const newUid = getFreeWorldEntryUid(data); From 66d609c35f58a4a1892f87c6a9a1b4d59daa8e0e Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Mon, 17 Jun 2024 07:04:10 +0200 Subject: [PATCH 04/58] Several million refactoring of existing slash commands with enums (really) --- .../scripts/extensions/attachments/index.js | 5 +- public/scripts/extensions/caption/index.js | 12 +- .../scripts/extensions/expressions/index.js | 10 +- public/scripts/extensions/gallery/index.js | 29 +- public/scripts/extensions/memory/index.js | 10 +- .../quick-reply/src/SlashCommandHandler.js | 274 +++++++++++++----- public/scripts/extensions/regex/engine.js | 2 +- public/scripts/extensions/regex/index.js | 32 +- .../extensions/stable-diffusion/index.js | 22 +- .../scripts/extensions/token-counter/index.js | 3 +- public/scripts/extensions/tts/index.js | 5 +- public/scripts/openai.js | 22 +- .../slash-commands/SlashCommandArgument.js | 3 +- .../SlashCommandCommonEnumsProvider.js | 27 +- public/scripts/tags.js | 8 +- 15 files changed, 332 insertions(+), 132 deletions(-) diff --git a/public/scripts/extensions/attachments/index.js b/public/scripts/extensions/attachments/index.js index d43d1d0a7..fcdc4c3a0 100644 --- a/public/scripts/extensions/attachments/index.js +++ b/public/scripts/extensions/attachments/index.js @@ -198,7 +198,10 @@ jQuery(async () => { SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'db', - callback: () => document.getElementById('manageAttachments')?.click(), + callback: () => { + document.getElementById('manageAttachments')?.click(); + return ''; + }, aliases: ['databank', 'data-bank'], helpString: 'Open the data bank', })); diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index d981c15a6..3bae2dadf 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -8,6 +8,7 @@ import { textgen_types, textgenerationwebui_settings } from '../../textgen-setti 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 } from '../../slash-commands/SlashCommandEnumValue.js'; export { MODULE_NAME }; const MODULE_NAME = 'caption'; @@ -445,11 +446,14 @@ jQuery(async function () { returns: 'caption', namedArgumentList: [ new SlashCommandNamedArgument( - 'quiet', 'suppress sending a captioned message', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'], - ), - new SlashCommandNamedArgument( - 'id', 'get image from a message with this ID', [ARGUMENT_TYPE.NUMBER], false, false, + 'quiet', 'suppress sending a captioned message', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), + SlashCommandNamedArgument.fromProps({ + name: 'id', + description: 'get image from a message with this ID', + typeList: [ARGUMENT_TYPE.NUMBER], + enumProvider: () => getContext().chat.map((_, i) => new SlashCommandEnumValue(String(i), null, 'number', '1️⃣')), + }), ], unnamedArgumentList: [ new SlashCommandArgument( diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index 7de3f7717..41df27538 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -11,6 +11,7 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from '../../slash-commands/SlashCommandArgument.js'; import { isFunctionCallingSupported } from '../../openai.js'; import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; +import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; export { MODULE_NAME }; const MODULE_NAME = 'expressions'; @@ -2080,9 +2081,12 @@ function migrateSettings() { callback: (_, value) => lastExpression[String(value).trim()] ?? '', returns: 'sprite', unnamedArgumentList: [ - new SlashCommandArgument( - 'charName', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'character name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.charName(), + }), ], helpString: 'Returns the last set sprite / expression for the named character.', })); diff --git a/public/scripts/extensions/gallery/index.js b/public/scripts/extensions/gallery/index.js index dbb40363a..b371c4bf0 100644 --- a/public/scripts/extensions/gallery/index.js +++ b/public/scripts/extensions/gallery/index.js @@ -11,6 +11,7 @@ import { dragElement } from '../../RossAscends-mods.js'; import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; +import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; const extensionName = 'gallery'; const extensionFolderPath = `scripts/extensions/${extensionName}/`; @@ -419,7 +420,10 @@ function viewWithDragbox(items) { // Registers a simple command for opening the char gallery. SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'show-gallery', aliases: ['sg'], - callback: showGalleryCommand, + callback: () => { + showCharGallery(); + return ''; + }, helpString: 'Shows the gallery.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery', @@ -427,21 +431,22 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery callback: listGalleryCommand, returns: 'list of images', namedArgumentList: [ - new SlashCommandNamedArgument( - 'char', 'character name', [ARGUMENT_TYPE.STRING], false, - ), - new SlashCommandNamedArgument( - 'group', 'group name', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandNamedArgument.fromProps({ + name: 'char', + description: 'character name', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: commonEnumProviders.charName('character'), + }), + SlashCommandNamedArgument.fromProps({ + name: 'group', + description: 'group name', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: commonEnumProviders.charName('group'), + }), ], helpString: 'List images in the gallery of the current char / group or a specified char / group.', })); - -function showGalleryCommand(args) { - showCharGallery(); -} - async function listGalleryCommand(args) { try { let url = args.char ?? (args.group ? groups.find(it=>it.name == args.group)?.id : null) ?? (selected_group || this_chid); diff --git a/public/scripts/extensions/memory/index.js b/public/scripts/extensions/memory/index.js index 1cc62ed02..5d61417e3 100644 --- a/public/scripts/extensions/memory/index.js +++ b/public/scripts/extensions/memory/index.js @@ -24,6 +24,7 @@ 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 { resolveVariable } from '../../variables.js'; +import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; export { MODULE_NAME }; const MODULE_NAME = '1_memory'; @@ -919,7 +920,14 @@ jQuery(async function () { callback: summarizeCallback, namedArgumentList: [ new SlashCommandNamedArgument('source', 'API to use for summarization', [ARGUMENT_TYPE.STRING], false, false, '', ['main', 'extras']), - new SlashCommandNamedArgument('prompt', 'prompt to use for summarization', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], false, false, ''), + SlashCommandNamedArgument.fromProps({ + name: 'prompt', + description: 'prompt to use for summarization', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], + defaultValue: '', + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), ], unnamedArgumentList: [ new SlashCommandArgument('text to summarize', [ARGUMENT_TYPE.STRING], false, false, ''), diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index f5877086d..9a568a561 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -1,9 +1,12 @@ import { SlashCommand } from '../../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js'; +import { SlashCommandEnumValue } from '../../../slash-commands/SlashCommandEnumValue.js'; import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; import { isTrueBoolean } from '../../../utils.js'; // eslint-disable-next-line no-unused-vars import { QuickReplyApi } from '../api/QuickReplyApi.js'; +import { QuickReply } from './QuickReply.js'; +import { QuickReplySet } from './QuickReplySet.js'; export class SlashCommandHandler { /**@type {QuickReplyApi}*/ api; @@ -29,86 +32,125 @@ export class SlashCommandHandler { helpString: 'Activates the specified Quick Reply', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qrset', - callback: () => toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.'), + callback: () => { + toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.'); + return ''; + }, helpString: 'DEPRECATED – The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set', - callback: (args, value) => this.toggleGlobalSet(value, args), + callback: (args, value) => { + this.toggleGlobalSet(value, args); + return ''; + }, namedArgumentList: [ new SlashCommandNamedArgument( 'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true', ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'QR set name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: 'Toggle global QR set', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-on', - callback: (args, value) => this.addGlobalSet(value, args), + callback: (args, value) => { + this.addGlobalSet(value, args); + return ''; + }, namedArgumentList: [ new SlashCommandNamedArgument( 'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true', ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'QR set name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: 'Activate global QR set', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-off', - callback: (_, value) => this.removeGlobalSet(value), + callback: (_, value) => { + this.removeGlobalSet(value); + return ''; + }, unnamedArgumentList: [ - new SlashCommandArgument( - 'QR set name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: 'Deactivate global QR set', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set', - callback: (args, value) => this.toggleChatSet(value, args), + callback: (args, value) => { + this.toggleChatSet(value, args); + return ''; + }, namedArgumentList: [ new SlashCommandNamedArgument( 'visible', 'set visibility', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true', ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'QR set name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: 'Toggle chat QR set', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-on', - callback: (args, value) => this.addChatSet(value, args), + callback: (args, value) => { + this.addChatSet(value, args); + return ''; + }, namedArgumentList: [ new SlashCommandNamedArgument( 'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true', ['true', 'false'], ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'QR set name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: 'Activate chat QR set', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-chat-set-off', - callback: (_, value) => this.removeChatSet(value), + callback: (_, value) => { + this.removeChatSet(value); + return ''; + }, unnamedArgumentList: [ - new SlashCommandArgument( - 'QR set name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: 'Deactivate chat QR set', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-list', - callback: (_, value) => this.listSets(value ?? 'all'), + callback: (_, value) => JSON.stringify(this.listSets(value ?? 'all')), returns: 'list of QR sets', namedArgumentList: [], unnamedArgumentList: [ @@ -119,13 +161,18 @@ export class SlashCommandHandler { helpString: 'Gets a list of the names of all quick reply sets.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-list', - callback: (_, value) => this.listQuickReplies(value), + callback: (_, value) => { + return JSON.stringify(this.listQuickReplies(value)); + }, returns: 'list of QRs', namedArgumentList: [], unnamedArgumentList: [ - new SlashCommandArgument( - 'set name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: 'Gets a list of the names of all quick replies in this quick reply set.', })); @@ -144,8 +191,12 @@ export class SlashCommandHandler { const qrUpdateArgs = [ new SlashCommandNamedArgument('newlabel', 'new text for the button', [ARGUMENT_TYPE.STRING], false), ]; + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-create', - callback: (args, message) => this.createQuickReply(args, message), + callback: (args, message) => { + this.createQuickReply(args, message); + return ''; + }, namedArgumentList: qrArgs, unnamedArgumentList: [ new SlashCommandArgument( @@ -165,9 +216,15 @@ export class SlashCommandHandler { `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-update', - callback: (args, message) => this.updateQuickReply(args, message), + callback: (args, message) => { + this.updateQuickReply(args, message); + return ''; + }, returns: 'updated quick reply', namedArgumentList: [...qrUpdateArgs, ...qrArgs], + unnamedArgumentList: [ + new SlashCommandArgument('message', [ARGUMENT_TYPE.STRING]), + ], helpString: `
Updates Quick Reply. @@ -183,34 +240,57 @@ export class SlashCommandHandler { `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-delete', - callback: (args, name) => this.deleteQuickReply(args, name), + callback: (args, name) => { + this.deleteQuickReply(args, name); + return ''; + }, namedArgumentList: [ - new SlashCommandNamedArgument( - 'set', 'Quick Reply set', [ARGUMENT_TYPE.STRING], true, - ), - new SlashCommandNamedArgument( - 'label', 'Quick Reply label', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandNamedArgument.fromProps({ + name: 'set', + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), + SlashCommandNamedArgument.fromProps({ + name: 'label', + description: 'Quick Reply label', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(x => new SlashCommandEnumValue(x.label, null, 'qr-entry')), + }), ], helpString: 'Deletes a Quick Reply from the specified set. If no label is provided, the entire set is deleted.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextadd', - callback: (args, name) => this.createContextItem(args, name), + callback: (args, name) => { + this.createContextItem(args, name); + return ''; + }, namedArgumentList: [ - new SlashCommandNamedArgument( - 'set', 'string', [ARGUMENT_TYPE.STRING], true, - ), - new SlashCommandNamedArgument( - 'label', 'string', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'set', + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), + SlashCommandNamedArgument.fromProps({ + name: 'label', + description: 'Quick Reply label', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(x => new SlashCommandEnumValue(x.label, null, 'qr-entry')), + }), new SlashCommandNamedArgument( 'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'preset name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: `
@@ -227,19 +307,32 @@ export class SlashCommandHandler { `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextdel', - callback: (args, name) => this.deleteContextItem(args, name), + callback: (args, name) => { + this.deleteContextItem(args, name); + return ''; + }, namedArgumentList: [ - new SlashCommandNamedArgument( - 'set', 'string', [ARGUMENT_TYPE.STRING], true, - ), - new SlashCommandNamedArgument( - 'label', 'string', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'set', + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), + SlashCommandNamedArgument.fromProps({ + name: 'label', + description: 'Quick Reply label', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(x => new SlashCommandEnumValue(x.label, null, 'qr-entry')), + }), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'preset name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: `
@@ -256,16 +349,25 @@ export class SlashCommandHandler { `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-contextclear', - callback: (args, label) => this.clearContextMenu(args, label), + callback: (args, label) => { + this.clearContextMenu(args, label); + return ''; + }, namedArgumentList: [ - new SlashCommandNamedArgument( - 'set', 'context menu preset name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'set', + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'label', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'Quick Reply label', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(x => new SlashCommandEnumValue(x.label, null, 'qr-entry')), + }), ], helpString: `
@@ -288,13 +390,19 @@ export class SlashCommandHandler { new SlashCommandNamedArgument('inject', 'inject user input automatically (if disabled use {{input}})', [ARGUMENT_TYPE.BOOLEAN], false), ]; SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-create', - callback: (args, name) => this.createSet(name, args), + callback: (args, name) => { + this.createSet(name, args); + return ''; + }, aliases: ['qr-presetadd'], namedArgumentList: presetArgs, unnamedArgumentList: [ - new SlashCommandArgument( - 'name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: `
@@ -312,11 +420,19 @@ export class SlashCommandHandler { })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-update', - callback: (args, name) => this.updateSet(name, args), + callback: (args, name) => { + this.updateSet(name, args); + return ''; + }, aliases: ['qr-presetupdate'], namedArgumentList: presetArgs, unnamedArgumentList: [ - new SlashCommandArgument('name', [ARGUMENT_TYPE.STRING], true), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: `
@@ -329,10 +445,18 @@ export class SlashCommandHandler { `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr-set-delete', - callback: (args, name) => this.deleteSet(name), + callback: (_, name) => { + this.deleteSet(name); + return ''; + }, aliases: ['qr-presetdelete'], unnamedArgumentList: [ - new SlashCommandArgument('name', [ARGUMENT_TYPE.STRING], true), + SlashCommandArgument.fromProps({ + description: 'QR set name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + }), ], helpString: `
@@ -468,7 +592,7 @@ export class SlashCommandHandler { } deleteQuickReply(args, label) { try { - this.api.deleteQuickReply(args.set, label); + this.api.deleteQuickReply(args.set, args.label ?? label); } catch (ex) { toastr.error(ex.message); } diff --git a/public/scripts/extensions/regex/engine.js b/public/scripts/extensions/regex/engine.js index 24aa18d49..f55e8bb0b 100644 --- a/public/scripts/extensions/regex/engine.js +++ b/public/scripts/extensions/regex/engine.js @@ -92,7 +92,7 @@ function getRegexedString(rawString, placement, { characterOverride, isMarkdown, /** * Runs the provided regex script on the given string - * @param {object} regexScript The regex script to run + * @param {import('./index.js').RegexScript} regexScript The regex script to run * @param {string} rawString The string to run the regex script on * @param {RegexScriptParams} params The parameters to use for the regex script * @returns {string} The new string diff --git a/public/scripts/extensions/regex/index.js b/public/scripts/extensions/regex/index.js index 374004e8b..81632c9e2 100644 --- a/public/scripts/extensions/regex/index.js +++ b/public/scripts/extensions/regex/index.js @@ -3,11 +3,31 @@ import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } import { selected_group } from '../../group-chats.js'; import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; +import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js'; import { resolveVariable } from '../../variables.js'; import { regex_placement, runRegexScript } from './engine.js'; +/** + * @typedef {object} RegexScript + * @property {string} scriptName - The name of the script + * @property {boolean} disabled - Whether the script is disabled + * @property {string} replaceString - The replace string + * @property {string[]} trimStrings - The trim strings + * @property {string?} findRegex - The find regex + * @property {string?} substituteRegex - The substitute regex + */ + +/** + * Retrieves the list of regex scripts by combining the scripts from the extension settings and the character data + * + * @return {RegexScript[]} An array of regex scripts, where each script is an object containing the necessary information. + */ +export function getRegexScripts() { + return [...(extension_settings.regex ?? []), ...(characters[this_chid]?.data?.extensions?.regex_scripts ?? [])]; +} + /** * Saves a regex script to the extension settings or character data. * @param {import('../../char-data.js').RegexScriptData} regexScript @@ -339,7 +359,7 @@ function runRegexCallback(args, value) { } const scriptName = String(resolveVariable(args.name)); - const scripts = [...(extension_settings.regex ?? []), ...(characters[this_chid]?.data?.extensions?.regex_scripts ?? [])]; + const scripts = getRegexScripts(); for (const script of scripts) { if (String(script.scriptName).toLowerCase() === String(scriptName).toLowerCase()) { @@ -556,9 +576,13 @@ jQuery(async () => { callback: runRegexCallback, returns: 'replaced text', namedArgumentList: [ - new SlashCommandNamedArgument( - 'name', 'script name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'script name', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + enumProvider: () => getRegexScripts().map(script => new SlashCommandEnumValue(script.scriptName, null, 'regex', '🔍')), + }), ], unnamedArgumentList: [ new SlashCommandArgument( diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index a55d5efd0..9e00c5690 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -31,6 +31,8 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; import { resolveVariable } from '../../variables.js'; import { debounce_timeout } from '../../constants.js'; +import { commonEnumProviders, getEnumBooleanValues } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; +import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; export { MODULE_NAME }; const MODULE_NAME = 'sd'; @@ -3335,11 +3337,14 @@ jQuery(async () => { aliases: ['sd', 'img', 'image'], namedArgumentList: [ new SlashCommandNamedArgument( - 'quiet', 'whether to post the generated image to chat', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['false', 'true'], - ), - new SlashCommandNamedArgument( - 'negative', 'negative prompt prefix', [ARGUMENT_TYPE.STRING], false, false, '', + 'quiet', 'whether to post the generated image to chat', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), + SlashCommandNamedArgument.fromProps({ + name: 'negative', + description: 'negative prompt prefix', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: () => [...getEnumBooleanValues(), ...commonEnumProviders.variables('all')()], + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -3361,9 +3366,12 @@ jQuery(async () => { callback: changeComfyWorkflow, aliases: ['icw'], unnamedArgumentList: [ - new SlashCommandArgument( - 'workflowName', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'workflow name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(x => new SlashCommandEnumValue(x, null, 'workflow', 'W')), + }), ], helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g.
/imagine-comfy-workflow MyWorkflow
', })); diff --git a/public/scripts/extensions/token-counter/index.js b/public/scripts/extensions/token-counter/index.js index f93a20e9b..b7f0adeb2 100644 --- a/public/scripts/extensions/token-counter/index.js +++ b/public/scripts/extensions/token-counter/index.js @@ -123,6 +123,7 @@ async function doCount() { //toastr success with the token count of the chat const count = await getTokenCountAsync(allMessages); toastr.success(`Token count: ${count}`); + return count; } jQuery(() => { @@ -134,7 +135,7 @@ jQuery(() => { $('#extensionsMenu').prepend(buttonHtml); $('#token_counter').on('click', doTokenCounter); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'count', - callback: doCount, + callback: async () => String(await doCount()), returns: 'number of tokens', helpString: 'Counts the number of tokens in the current chat.', })); diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index c54458efe..3c1363a67 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -1102,7 +1102,10 @@ $(document).ready(function () { eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onMessageEvent); eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, onMessageEvent); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'speak', - callback: onNarrateText, + callback: () => { + onNarrateText(); + return ''; + }, aliases: ['narrate', 'tts'], namedArgumentList: [ new SlashCommandNamedArgument( diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 69c65eab8..6331c924e 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -199,16 +199,18 @@ const custom_prompt_post_processing_types = { CLAUDE: 'claude', }; -const prefixMap = selected_group ? { - assistant: '', - user: '', - system: 'OOC: ', +function getPrefixMap() { + return selected_group ? { + assistant: '', + user: '', + system: 'OOC: ', + } + : { + assistant: '{{char}}:', + user: '{{user}}:', + system: '', + }; } - : { - assistant: '{{char}}:', - user: '{{user}}:', - system: '', - }; const default_settings = { preset_settings_openai: 'Default', @@ -1709,7 +1711,7 @@ async function sendOpenAIRequest(type, messages, signal) { if (isAI21) { const joinedMsgs = messages.reduce((acc, obj) => { - const prefix = prefixMap[obj.role]; + const prefix = getPrefixMap()[obj.role]; return acc + (prefix ? (selected_group ? '\n' : prefix + ' ') : '') + obj.content + '\n'; }, ''); messages = substituteParams(joinedMsgs) + (isImpersonate ? `${name1}:` : `${name2}:`); diff --git a/public/scripts/slash-commands/SlashCommandArgument.js b/public/scripts/slash-commands/SlashCommandArgument.js index 2355b0020..cb39aaa5b 100644 --- a/public/scripts/slash-commands/SlashCommandArgument.js +++ b/public/scripts/slash-commands/SlashCommandArgument.js @@ -1,4 +1,5 @@ import { SlashCommandClosure } from './SlashCommandClosure.js'; +import { getEnumBooleanValues, getEnumIconByValueType } from './SlashCommandCommonEnumsProvider.js'; import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; import { SlashCommandExecutor } from './SlashCommandExecutor.js'; @@ -72,7 +73,7 @@ export class SlashCommandArgument { this.forceEnum = forceEnum; // If no enums were set explictly and the type is one where we know possible enum values, we set them here - if (!this.enumList.length && types == ARGUMENT_TYPE.BOOLEAN) this.enumList = [new SlashCommandEnumValue('true'), new SlashCommandEnumValue('false')]; + if (!this.enumList.length && types == ARGUMENT_TYPE.BOOLEAN) this.enumList = getEnumBooleanValues(); } } diff --git a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js index d38b5a68a..45a2be844 100644 --- a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js +++ b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js @@ -30,22 +30,27 @@ export const commonEnumProviders = { }, /** - * All possible character and group names + * All possible char entities, like characters and groups. Can be filtered down to just one type. * - * @returns {SlashCommandEnumValue[]} + * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return + * @returns {() => SlashCommandEnumValue[]} */ - charName: () => [ - ...characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')), - ...groups.map(it => new SlashCommandEnumValue(it.name, null, 'variable', 'G')), - ], + charName: (mode) => () => { + mode = mode ?? 'all'; + return [ + ...['all', 'character'].includes(mode) ? characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')) : [], + ...['all', 'group'].includes(mode) ? groups.map(it => new SlashCommandEnumValue(it.name, null, 'variable', 'G')) : [], + ]; + }, /** * All possible tags for a given char/group entity * - * @param {'all' | 'existing' | 'not-existing'} mode - Which types of tags to show + * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show * @returns {() => SlashCommandEnumValue[]} */ tagsForChar: (mode) => (/** @type {SlashCommandExecutor} */ executor) => { + mode = mode ?? 'all'; // Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags. const key = searchCharByName(substituteParams(/**@type {string?}*/(executor.namedArgumentList.find(it => it.name == 'name')?.value)), { suppressLogging: true }); const assigned = key ? getTagsList(key) : []; @@ -61,6 +66,14 @@ export const commonEnumProviders = { worlds: () => $('#world_info').children().toArray().map(x => new SlashCommandEnumValue(x.textContent)), }; +/** + * Get the enum values for boolean type, with class and icon + * + * @return {Array} An array of SlashCommandEnumValue objects representing the boolean values 'true' and 'false'. + */ +export function getEnumBooleanValues() { + return [new SlashCommandEnumValue('true', null, 'boolean', getEnumIconByValueType('boolean')), new SlashCommandEnumValue('false', null, 'boolean', getEnumIconByValueType('boolean'))]; +} /** * Get the unicode icon for the given enum value type diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 62981e4ed..d6eb9be15 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -1607,7 +1607,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName, + enumProvider: commonEnumProviders.charName(), }), ], unnamedArgumentList: [ @@ -1652,7 +1652,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName, + enumProvider: commonEnumProviders.charName(), }), ], unnamedArgumentList: [ @@ -1695,7 +1695,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName, + enumProvider: commonEnumProviders.charName(), }), ], unnamedArgumentList: [ @@ -1738,7 +1738,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName, + enumProvider: commonEnumProviders.charName(), }), ], helpString: ` From 461b1a9d87bf967329e79326ca538637070fbbd1 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Thu, 20 Jun 2024 20:33:45 +0200 Subject: [PATCH 05/58] Even more enum refactorings (not done yet) - Add common enum icons - enum def for existing enum types, with color description --- public/script.js | 27 +++- .../scripts/extensions/attachments/index.js | 57 ++++++- public/scripts/extensions/caption/index.js | 3 +- .../quick-reply/src/SlashCommandHandler.js | 67 +++++--- .../extensions/stable-diffusion/index.js | 4 +- public/scripts/group-chats.js | 11 ++ public/scripts/openai.js | 2 +- public/scripts/power-user.js | 5 +- public/scripts/preset-manager.js | 5 +- public/scripts/scrapers.js | 19 ++- public/scripts/slash-commands.js | 51 ++++-- .../slash-commands/SlashCommandArgument.js | 4 +- .../SlashCommandCommonEnumsProvider.js | 151 +++++++++++++----- .../slash-commands/SlashCommandEnumValue.js | 54 ++++++- .../slash-commands/SlashCommandParser.js | 3 +- public/scripts/variables.js | 55 +++++-- public/scripts/world-info.js | 73 ++++++--- public/style.css | 8 + 18 files changed, 462 insertions(+), 137 deletions(-) diff --git a/public/script.js b/public/script.js index fbfe1bfb6..a080af2f8 100644 --- a/public/script.js +++ b/public/script.js @@ -235,6 +235,8 @@ import { SlashCommand } from './scripts/slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js'; import { SlashCommandBrowser } from './scripts/slash-commands/SlashCommandBrowser.js'; import { initCustomSelectedSamplers, validateDisabledSamplers } from './scripts/samplerSelect.js'; +import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js'; +import { enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js'; //exporting functions and vars for mods export { @@ -8774,6 +8776,9 @@ jQuery(async function () { return ''; } + // Collect all unique API names in an array + const uniqueAPIs = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))]; + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'dupe', callback: DupeChar, @@ -8790,7 +8795,9 @@ jQuery(async function () { true, false, null, - Object.keys(CONNECT_API_MAP), + Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => + new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(uniqueAPIs.findIndex(x => x === selected)), + selected[0].toUpperCase() ?? enumIcons.default)), ), ], helpString: ` @@ -8882,9 +8889,11 @@ jQuery(async function () { returns: 'current preset', namedArgumentList: [], unnamedArgumentList: [ - new SlashCommandArgument( - 'name', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandArgument.fromProps({ + description: 'instruct preset name', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: () => instruct_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)), + }), ], helpString: `
@@ -8915,9 +8924,11 @@ jQuery(async function () { callback: selectContextCallback, returns: 'template name', unnamedArgumentList: [ - new SlashCommandArgument( - 'name', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandArgument.fromProps({ + description: 'context preset name', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: () => context_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)), + }), ], helpString: 'Selects context template by name. Gets the current template if no name is provided', })); @@ -10773,4 +10784,4 @@ jQuery(async function () { }); initCustomSelectedSamplers(); -}); \ No newline at end of file +}); diff --git a/public/scripts/extensions/attachments/index.js b/public/scripts/extensions/attachments/index.js index fcdc4c3a0..061e83a51 100644 --- a/public/scripts/extensions/attachments/index.js +++ b/public/scripts/extensions/attachments/index.js @@ -2,6 +2,10 @@ import { deleteAttachment, getDataBankAttachments, getDataBankAttachmentsForSour import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js'; import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; +import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js'; +import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; +import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; +import { SlashCommandExecutor } from '../../slash-commands/SlashCommandExecutor.js'; import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; /** @@ -196,6 +200,24 @@ jQuery(async () => { const buttons = await renderExtensionTemplateAsync('attachments', 'buttons', {}); $('#extensionsMenu').prepend(buttons); + /** A collection of local enum providers for this context of data bank */ + const localEnumProviders = { + /** + * All attachements in the data bank based on the source argument. If not provided, defaults to 'chat'. + * @param {'name' | 'url'} returnField - Whether the enum should return the 'name' field or the 'url' + * */ + attachements: (returnField = 'name') => (/** @type {SlashCommandExecutor} */ executor) => { + const source = executor.namedArgumentList.find(it => it.name == 'source')?.value ?? 'chat'; + if (source instanceof SlashCommandClosure) throw new Error('Argument \'source\' does not support closures'); + const attachments = getAttachments(source); + + return attachments.map(attachment => new SlashCommandEnumValue( + returnField === 'name' ? attachment.name : attachment.url, + `${extension_settings.disabled_attachments.includes(attachment.url) ? enumIcons.false : enumIcons.true} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`, + enumTypes.enum, enumIcons.file)); + }, + }; + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'db', callback: () => { @@ -254,8 +276,18 @@ jQuery(async () => { helpString: 'Update an attachment in the Data Bank, preserving its name. Returns a new URL of the attachment.', namedArgumentList: [ new SlashCommandNamedArgument('source', 'The source for the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES), - new SlashCommandNamedArgument('name', 'The name of the attachment.', ARGUMENT_TYPE.STRING, false, false), - new SlashCommandNamedArgument('url', 'The URL of the attachment to update.', ARGUMENT_TYPE.STRING, false, false), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'The name of the attachment.', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: localEnumProviders.attachements('name'), + }), + SlashCommandNamedArgument.fromProps({ + name: 'url', + description: 'The URL of the attachment.', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: localEnumProviders.attachements('url'), + }), ], unnamedArgumentList: [ new SlashCommandArgument('The content of the file attachment.', ARGUMENT_TYPE.STRING, true, false), @@ -272,7 +304,12 @@ jQuery(async () => { new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES), ], unnamedArgumentList: [ - new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false), + SlashCommandArgument.fromProps({ + description: 'The name or URL of the attachment.', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: localEnumProviders.attachements(), + }), ], })); @@ -285,7 +322,12 @@ jQuery(async () => { new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES), ], unnamedArgumentList: [ - new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false), + SlashCommandArgument.fromProps({ + description: 'The name or URL of the attachment.', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: localEnumProviders.attachements(), + }), ], })); @@ -298,7 +340,12 @@ jQuery(async () => { new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES), ], unnamedArgumentList: [ - new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false), + SlashCommandArgument.fromProps({ + description: 'The name or URL of the attachment.', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: localEnumProviders.attachements(), + }), ], })); }); diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index 3bae2dadf..4bbc701ed 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -9,6 +9,7 @@ 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 } from '../../slash-commands/SlashCommandEnumValue.js'; +import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; export { MODULE_NAME }; const MODULE_NAME = 'caption'; @@ -452,7 +453,7 @@ jQuery(async function () { name: 'id', description: 'get image from a message with this ID', typeList: [ARGUMENT_TYPE.NUMBER], - enumProvider: () => getContext().chat.map((_, i) => new SlashCommandEnumValue(String(i), null, 'number', '1️⃣')), + enumProvider: commonEnumProviders.messages, }), ], unnamedArgumentList: [ diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index 9a568a561..86e6f9016 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -1,6 +1,7 @@ import { SlashCommand } from '../../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../../slash-commands/SlashCommandArgument.js'; -import { SlashCommandEnumValue } from '../../../slash-commands/SlashCommandEnumValue.js'; +import { enumIcons } from '../../../slash-commands/SlashCommandCommonEnumsProvider.js'; +import { SlashCommandEnumValue, enumTypes } from '../../../slash-commands/SlashCommandEnumValue.js'; import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; import { isTrueBoolean } from '../../../utils.js'; // eslint-disable-next-line no-unused-vars @@ -22,6 +23,28 @@ export class SlashCommandHandler { init() { + function getExecutionIcons(/**@type {QuickReply} */ qr) { + let icons = ''; + if (qr.preventAutoExecute) icons += '🚫'; + if (qr.isHidden) icons += '👁️'; + if (qr.executeOnStartup) icons += '🚀'; + if (qr.executeOnUser) icons += enumIcons.user; + if (qr.executeOnAi) icons += enumIcons.assistant; + if (qr.executeOnChatChange) icons += '💬'; + if (qr.executeOnGroupMemberDraft) icons += enumIcons.group; + return icons; + } + + const localEnumProviders = { + qrSets: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, enumTypes.enum, 'S')), + + qrEntries: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => { + const icons = getExecutionIcons(qr); + const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim(); + return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, 'QR'); + }) ?? [], + } + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr', callback: (_, value) => this.executeQuickReplyByIndex(Number(value)), unnamedArgumentList: [ @@ -53,7 +76,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: 'Toggle global QR set', @@ -73,7 +96,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: 'Activate global QR set', @@ -88,7 +111,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: 'Deactivate global QR set', @@ -108,7 +131,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: 'Toggle chat QR set', @@ -121,7 +144,7 @@ export class SlashCommandHandler { }, namedArgumentList: [ new SlashCommandNamedArgument( - 'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true', ['true', 'false'], + 'visible', 'whether the QR set should be visible', [ARGUMENT_TYPE.BOOLEAN], false, false, 'true', ), ], unnamedArgumentList: [ @@ -129,7 +152,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: 'Activate chat QR set', @@ -144,7 +167,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: 'Deactivate chat QR set', @@ -171,7 +194,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: 'Gets a list of the names of all quick replies in this quick reply set.', @@ -250,13 +273,13 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), SlashCommandNamedArgument.fromProps({ name: 'label', description: 'Quick Reply label', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(x => new SlashCommandEnumValue(x.label, null, 'qr-entry')), + enumProvider: localEnumProviders.qrEntries, }), ], helpString: 'Deletes a Quick Reply from the specified set. If no label is provided, the entire set is deleted.', @@ -272,13 +295,13 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), SlashCommandNamedArgument.fromProps({ name: 'label', description: 'Quick Reply label', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(x => new SlashCommandEnumValue(x.label, null, 'qr-entry')), + enumProvider: localEnumProviders.qrEntries, }), new SlashCommandNamedArgument( 'chain', 'boolean', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', @@ -289,7 +312,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: ` @@ -317,13 +340,13 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), SlashCommandNamedArgument.fromProps({ name: 'label', description: 'Quick Reply label', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(x => new SlashCommandEnumValue(x.label, null, 'qr-entry')), + enumProvider: localEnumProviders.qrEntries, }), ], unnamedArgumentList: [ @@ -331,7 +354,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: ` @@ -359,14 +382,14 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'Quick Reply label', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(x => new SlashCommandEnumValue(x.label, null, 'qr-entry')), + enumProvider: localEnumProviders.qrEntries, }), ], helpString: ` @@ -401,7 +424,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: ` @@ -431,7 +454,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: ` @@ -455,7 +478,7 @@ export class SlashCommandHandler { description: 'QR set name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, 'qr-set', 'QR')), + enumProvider: localEnumProviders.qrSets, }), ], helpString: ` diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index 401b9d589..ff450472a 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -31,7 +31,7 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; import { resolveVariable } from '../../variables.js'; import { debounce_timeout } from '../../constants.js'; -import { commonEnumProviders, getEnumBooleanValues } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; +import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; export { MODULE_NAME }; @@ -3347,7 +3347,7 @@ jQuery(async () => { name: 'negative', description: 'negative prompt prefix', typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: () => [...getEnumBooleanValues(), ...commonEnumProviders.variables('all')()], + enumProvider: commonEnumProviders.variables('all'), }), ], unnamedArgumentList: [ diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 97ccbcd89..783352363 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -230,6 +230,17 @@ export async function getGroupChat(groupId, reload = false) { await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId()); } +/** + * Retrieves the members of a group + * + * @param {string} [groupId=selected_group] - The ID of the group to retrieve members from. Defaults to the currently selected group. + * @returns {import('../script.js').Character[]} An array of character objects representing the members of the group. If the group is not found, an empty array is returned. + */ +export function getGroupMembers(groupId = selected_group) { + const group = groups.find((x) => x.id === groupId); + return group?.members.map(member => characters.find(x => x.avatar === member)) ?? []; +} + /** * Finds the character ID for a group member. * @param {string} arg 1-based member index or character name diff --git a/public/scripts/openai.js b/public/scripts/openai.js index e2f901f59..f8b61f89f 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -4609,7 +4609,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => proxies.map(preset => new SlashCommandEnumValue(preset.name)), + enumProvider: () => proxies.map(preset => new SlashCommandEnumValue(preset.name, preset.url)), }), ], helpString: 'Sets a proxy preset by name.', diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index efff375db..4e6a24b3d 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -46,7 +46,8 @@ import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandPa import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js'; -import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; +import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; +import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; export { loadPowerUserSettings, @@ -3890,7 +3891,7 @@ $(document).ready(() => { SlashCommandArgument.fromProps({ description: 'optional tag name', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: () => tags.filter(tag => Object.values(tag_map).some(x => x.includes(tag.id))).map(tag => new SlashCommandEnumValue(tag.name)), + enumProvider: () => tags.filter(tag => Object.values(tag_map).some(x => x.includes(tag.id))).map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.enum, enumIcons.tag)), }), ], helpString: 'Start a new chat with a random character. If an argument is provided, only considers characters that have the specified tag.', diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index 6b3b2f0ab..4c6ef88a5 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -22,7 +22,8 @@ import { kai_settings } from './kai-settings.js'; import { context_presets, getContextSettings, power_user } from './power-user.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; -import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; +import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; +import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { textgenerationwebui_preset_names, @@ -485,7 +486,7 @@ export async function initPresetManager() { SlashCommandArgument.fromProps({ description: 'name', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: () => getPresetManager().getAllPresets().map(preset => new SlashCommandEnumValue(preset)), + enumProvider: () => getPresetManager().getAllPresets().map(preset => new SlashCommandEnumValue(preset, null, enumTypes.enum, enumIcons.preset)), }), ], helpString: ` diff --git a/public/scripts/scrapers.js b/public/scripts/scrapers.js index 61b076d11..e49fa51bf 100644 --- a/public/scripts/scrapers.js +++ b/public/scripts/scrapers.js @@ -433,6 +433,23 @@ class FandomScraper { } } +const iso6391Codes = ["aa", "ab", "ae", "af", "ak", "am", "an", "ar", "as", "av", "ay", "az", + "ba", "be", "bg", "bh", "bi", "bm", "bn", "bo", "br", "bs", "ca", "ce", + "ch", "co", "cr", "cs", "cu", "cv", "cy", "da", "de", "dv", "dz", "ee", + "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi", "fj", "fo", "fr", + "fy", "ga", "gd", "gl", "gn", "gu", "gv", "ha", "he", "hi", "ho", "hr", + "ht", "hu", "hy", "hz", "ia", "id", "ie", "ig", "ii", "ik", "io", "is", + "it", "iu", "ja", "jv", "ka", "kg", "ki", "kj", "kk", "kl", "km", "kn", + "ko", "kr", "ks", "ku", "kv", "kw", "ky", "la", "lb", "lg", "li", "ln", + "lo", "lt", "lu", "lv", "mg", "mh", "mi", "mk", "ml", "mn", "mr", "ms", + "mt", "my", "na", "nb", "nd", "ne", "ng", "nl", "nn", "no", "nr", "nv", + "ny", "oc", "oj", "om", "or", "os", "pa", "pi", "pl", "ps", "pt", "qu", + "rm", "rn", "ro", "ru", "rw", "sa", "sc", "sd", "se", "sg", "si", "sk", + "sl", "sm", "sn", "so", "sq", "sr", "ss", "st", "su", "sv", "sw", "ta", + "te", "tg", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts", "tt", "tw", + "ty", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh", "yi", + "yo", "za", "zh", "zu"]; + /** * Scrape transcript from a YouTube video. * @implements {Scraper} @@ -464,7 +481,7 @@ class YouTubeScraper { helpString: 'Scrape a transcript from a YouTube video by ID or URL.', returns: ARGUMENT_TYPE.STRING, namedArgumentList: [ - new SlashCommandNamedArgument('lang', 'ISO 639-1 language code of the transcript, e.g. "en"', ARGUMENT_TYPE.STRING, false, false, ''), + new SlashCommandNamedArgument('lang', 'ISO 639-1 language code of the transcript, e.g. "en"', ARGUMENT_TYPE.STRING, false, false, '', iso6391Codes), ], unnamedArgumentList: [ new SlashCommandArgument('URL or ID of the YouTube video', ARGUMENT_TYPE.STRING, true, false), diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 00f7bd3d1..0e25e0200 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -44,7 +44,7 @@ import { getMessageTimeStamp } from './RossAscends-mods.js'; import { hideChatMessageRange } from './chats.js'; import { extension_settings, getContext, saveMetadataDebounced } from './extensions.js'; import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; -import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js'; +import { findGroupMemberId, getGroupMembers, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group } from './group-chats.js'; import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager } from './openai.js'; import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, user_avatar } from './personas.js'; import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js'; @@ -62,6 +62,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashCommandNamedArgumentAssignment.js'; import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; +import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; export { executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, }; @@ -148,12 +149,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ 'name', 'Character name', [ARGUMENT_TYPE.STRING], true, ), new SlashCommandNamedArgument( - 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'], + 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), SlashCommandNamedArgument.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, }), ], unnamedArgumentList: [ @@ -196,6 +199,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, }), ], unnamedArgumentList: [ @@ -249,6 +254,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, }), ], unnamedArgumentList: [ @@ -332,10 +339,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => [ - ...characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')), - ...groups.map(it => new SlashCommandEnumValue(it.name, null, 'variable', 'G')), - ], + enumProvider: commonEnumProviders.charName('all'), }), ], helpString: 'Opens up a chat with the character or group by its name', @@ -383,7 +387,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'character name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')), + enumProvider: commonEnumProviders.charName('all'), }), ], unnamedArgumentList: [ @@ -398,9 +402,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: deleteMessagesByNameCallback, namedArgumentList: [], unnamedArgumentList: [ - new SlashCommandArgument( - 'name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.charName('all'), + }), ], aliases: ['cancel'], helpString: ` @@ -433,6 +440,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, }), new SlashCommandNamedArgument( 'name', @@ -499,9 +508,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'hide', callback: hideMessageCallback, unnamedArgumentList: [ - new SlashCommandArgument( - 'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], true, - ), + SlashCommandArgument.fromProps({ + description: 'message index or range', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + isRequired: true, + enumProvider: () => chat.map((_, i) => new SlashCommandEnumValue(String(i), null, 'number', '1️⃣')), + }), ], helpString: 'Hides a chat message from the prompt.', })); @@ -520,9 +532,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: disableGroupMemberCallback, aliases: ['disable', 'disablemember', 'memberdisable'], unnamedArgumentList: [ - new SlashCommandArgument( - 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'member index or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => [ + ...getGroupMembers().map((character, i) => new SlashCommandEnumValue(String(i), character.name, 'name', '👤')), + ], + }), ], helpString: 'Disables a group member from being drafted for replies.', })); @@ -791,7 +808,6 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'Whether to suppress the toast message notifying about the /abort call.', typeList: [ARGUMENT_TYPE.BOOLEAN], defaultValue: 'true', - enumList: ['true', 'false'], }), ], unnamedArgumentList: [ @@ -1606,6 +1622,7 @@ async function runCallback(args, name) { */ function abortCallback({ _abortController, quiet }, reason) { _abortController.abort((reason ?? '').toString().length == 0 ? '/abort command executed' : reason, !isFalseBoolean(quiet ?? 'true')); + return ''; } async function delayCallback(_, amount) { diff --git a/public/scripts/slash-commands/SlashCommandArgument.js b/public/scripts/slash-commands/SlashCommandArgument.js index cb39aaa5b..77f396dca 100644 --- a/public/scripts/slash-commands/SlashCommandArgument.js +++ b/public/scripts/slash-commands/SlashCommandArgument.js @@ -1,5 +1,5 @@ import { SlashCommandClosure } from './SlashCommandClosure.js'; -import { getEnumBooleanValues, getEnumIconByValueType } from './SlashCommandCommonEnumsProvider.js'; +import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js'; import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; import { SlashCommandExecutor } from './SlashCommandExecutor.js'; @@ -73,7 +73,7 @@ export class SlashCommandArgument { this.forceEnum = forceEnum; // If no enums were set explictly and the type is one where we know possible enum values, we set them here - if (!this.enumList.length && types == ARGUMENT_TYPE.BOOLEAN) this.enumList = getEnumBooleanValues(); + if (!this.enumList.length && types == ARGUMENT_TYPE.BOOLEAN) this.enumList = commonEnumProviders.boolean()(); } } diff --git a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js index 45a2be844..6026f82e1 100644 --- a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js +++ b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js @@ -1,16 +1,105 @@ -import { chat_metadata, characters, substituteParams } from "../../script.js"; +import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles } from "../../script.js"; import { extension_settings } from "../extensions.js"; import { groups } from "../group-chats.js"; import { searchCharByName, getTagsList, tags } from "../tags.js"; -import { SlashCommandEnumValue } from "./SlashCommandEnumValue.js"; +import { SlashCommandClosure } from "./SlashCommandClosure.js"; +import { SlashCommandEnumValue, enumTypes } from "./SlashCommandEnumValue.js"; import { SlashCommandExecutor } from "./SlashCommandExecutor.js"; +/** + * A collection of regularly used enum icons + */ +export const enumIcons = { + default: '◊', + + // Variables + variable: 'V', + localVariable: 'L', + globalVariable: 'G', + scopeVariable: 'S', + + // Common types + character: '👤', + group: '🧑‍🤝‍🧑', + qr: '🤖', + tag: '🏷️', + world: '🌐', + preset: '⚙️', + file: '📄', + + true: '✔️', + false: '❌', + + // Value types + boolean: '🔲', + string: '📝', + number: '1️⃣', + array: '📦', + enum: '📚', + dictionary: '📖', + closure: '🧩', + + // Roles + system: '⚙️', + user: '👤', + assistant: '🤖', + + // WI Icons + constant: '🔵', + normal: '🟢', + disabled: '❌', + vectorized: '🔗', + + /** + * Returns the appropriate WI icon based on the entry + * + * @param {Object} entry - WI entry + * @returns {string} The corresponding WI icon + */ + getWiStatusIcon: (entry) => { + if (entry.constant) return enumIcons.constant; + if (entry.disable) return enumIcons.disabled; + if (entry.vectorized) return enumIcons.vectorized; + return enumIcons.normal; + }, + + /** + * Returns the appropriate icon based on the role + * + * @param {extension_prompt_roles} role - The role to get the icon for + * @returns {string} The corresponding icon + */ + getRoleIcon: (role) => { + switch (role) { + case extension_prompt_roles.SYSTEM: return enumIcons.system; + case extension_prompt_roles.USER: return enumIcons.user; + case extension_prompt_roles.ASSISTANT: return enumIcons.assistant; + default: return enumIcons.default; + } + }, +} + /** * A collection of common enum providers * * Can be used on `SlashCommandNamedArgument` and `SlashCommandArgument` and their `enumProvider` property. */ export const commonEnumProviders = { + /** + * Enum values for booleans. Either using true/false or on/off + * Optionally supports "toggle". + * @param {('onOff'|'onOffToggle'|'trueFalse')?} [mode='trueFalse'] - The mode to use. Default is 'trueFalse'. + * @returns {() => SlashCommandEnumValue[]} + */ + boolean: (mode = 'trueFalse') => () => { + switch (mode) { + case 'onOff': return [new SlashCommandEnumValue('on', null, 'macro', enumIcons.true), new SlashCommandEnumValue('off', null, 'macro', enumIcons.false)]; + case 'onOffToggle': return [new SlashCommandEnumValue('on', null, 'macro', enumIcons.true), new SlashCommandEnumValue('off', null, 'macro', enumIcons.false), new SlashCommandEnumValue('toggle', null, 'macro', enumIcons.boolean)]; + case 'trueFalse': return [new SlashCommandEnumValue('true', null, 'macro', enumIcons.true), new SlashCommandEnumValue('false', null, 'macro', enumIcons.false)]; + default: throw new Error(`Invalid boolean enum provider mode: ${mode}`); + } + }, + /** * All possible variable names * @@ -23,9 +112,9 @@ export const commonEnumProviders = { const types = type.flat(); const isAll = types.includes('all'); return [ - ...isAll || types.includes('global') ? Object.keys(chat_metadata.variables).map(x => new SlashCommandEnumValue(x, null, 'variable', 'L')) : [], - ...isAll || types.includes('local') ? Object.keys(extension_settings.variables.global).map(x => new SlashCommandEnumValue(x, null, 'variable', 'G')) : [], - ...isAll || types.includes('scope') ? [].map(x => new SlashCommandEnumValue(x, null, 'variable', 'S')) : [], // TODO: Add scoped variables here, Lenny + ...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(x => new SlashCommandEnumValue(x, null, enumTypes.macro, enumIcons.globalVariable)) : [], + ...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(x => new SlashCommandEnumValue(x, null, enumTypes.name, enumIcons.localVariable)) : [], + ...isAll || types.includes('scope') ? [].map(x => new SlashCommandEnumValue(x, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny ] }, @@ -35,11 +124,10 @@ export const commonEnumProviders = { * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return * @returns {() => SlashCommandEnumValue[]} */ - charName: (mode) => () => { - mode = mode ?? 'all'; + charName: (mode = 'all') => () => { return [ - ...['all', 'character'].includes(mode) ? characters.map(it => new SlashCommandEnumValue(it.name, null, 'qr', 'C')) : [], - ...['all', 'group'].includes(mode) ? groups.map(it => new SlashCommandEnumValue(it.name, null, 'variable', 'G')) : [], + ...['all', 'character'].includes(mode) ? characters.map(it => new SlashCommandEnumValue(it.name, null, enumTypes.name, enumIcons.character)) : [], + ...['all', 'group'].includes(mode) ? groups.map(it => new SlashCommandEnumValue(it.name, null, enumTypes.qr, enumIcons.group)) : [], ]; }, @@ -49,49 +137,40 @@ export const commonEnumProviders = { * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show * @returns {() => SlashCommandEnumValue[]} */ - tagsForChar: (mode) => (/** @type {SlashCommandExecutor} */ executor) => { - mode = mode ?? 'all'; + tagsForChar: (mode = 'all') => (/** @type {SlashCommandExecutor} */ executor) => { // Try to see if we can find the char during execution to filter down the tags list some more. Otherwise take all tags. - const key = searchCharByName(substituteParams(/**@type {string?}*/(executor.namedArgumentList.find(it => it.name == 'name')?.value)), { suppressLogging: true }); + const charName = executor.namedArgumentList.find(it => it.name == 'name')?.value; + if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures'); + const key = searchCharByName(substituteParams(charName), { suppressLogging: true }); const assigned = key ? getTagsList(key) : []; return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it)) - .map(it => new SlashCommandEnumValue(it.name, it.title)); + .map(it => new SlashCommandEnumValue(it.name, null, enumTypes.command, enumIcons.tag)); }, + /** + * All messages in the current chat, returning the message id + * @returns {SlashCommandEnumValue[]} + */ + messages: () => chat.map((mes, i) => new SlashCommandEnumValue(String(i), `${mes.name}: ${mes.mes}`, enumTypes.number, mes.is_user ? enumIcons.user : mes.is_system ? enumIcons.system : enumIcons.assistant)), + /** * All existing worlds / lorebooks * * @returns {SlashCommandEnumValue[]} */ - worlds: () => $('#world_info').children().toArray().map(x => new SlashCommandEnumValue(x.textContent)), + worlds: () => $('#world_info').children().toArray().map(x => new SlashCommandEnumValue(x.textContent, null, enumTypes.name, enumIcons.world)), }; -/** - * Get the enum values for boolean type, with class and icon - * - * @return {Array} An array of SlashCommandEnumValue objects representing the boolean values 'true' and 'false'. - */ -export function getEnumBooleanValues() { - return [new SlashCommandEnumValue('true', null, 'boolean', getEnumIconByValueType('boolean')), new SlashCommandEnumValue('false', null, 'boolean', getEnumIconByValueType('boolean'))]; -} - /** * Get the unicode icon for the given enum value type + * + * Can also confert nullable data types to their non-nullable counterparts + * * @param {string} type The type of the enum value * @returns {string} the unicode icon */ -export function getEnumIconByValueType(type) { - // Remove nullable types definition to match type icon +export function getEnumIcon(type) { + // Remove possible nullable types definition to match type icon type = type.replace(/\?$/, ''); - - switch (type) { - case 'boolean': return '🔲'; - case 'string': return '📝'; - case 'number': return '1️⃣'; - case 'array': return '📦'; - case 'enum': return '📚'; - case 'dictionary': return '📖'; - case 'closure': return '🧩'; - default: return '◊'; - } + return enumIcons[type] ?? enumIcons.default; } diff --git a/public/scripts/slash-commands/SlashCommandEnumValue.js b/public/scripts/slash-commands/SlashCommandEnumValue.js index 1fa610c56..973d1be99 100644 --- a/public/scripts/slash-commands/SlashCommandEnumValue.js +++ b/public/scripts/slash-commands/SlashCommandEnumValue.js @@ -1,13 +1,63 @@ + +/** + * @typedef {'enum' | 'command' | 'namedArgument' | 'variable' | 'qr' | 'macro' | 'number' | 'name'} EnumType + */ + +/** + * Collection of the enum types that can be used with `SlashCommandEnumValue` + * + * Contains documentation on which color this will result to + */ +export const enumTypes = { + /** 'enum' - [string] - light orange @type {EnumType} */ + enum: 'enum', + /** 'command' - [cmd] - light yellow @type {EnumType} */ + command: 'command', + /** 'namedArgument' - [argName] - sky blue @type {EnumType} */ + namedArgument: 'namedArgument', + /** 'variable' - [punctuationL1] - pink @type {EnumType} */ + variable: 'variable', + /** 'qr' - [variable] - light blue @type {EnumType} */ + qr: 'qr', + /** 'macro' - [variableLanguage] - blue @type {EnumType} */ + macro: 'macro', + /** 'number' - [number] - light green @type {EnumType} */ + number: 'number', + /** 'name' - [type] - forest green @type {EnumType} */ + name: 'name', + + /** + * Gets the value of the enum type based on the provided index + * + * Can be used to get differing colors or even random colors, by providing the index of a unique set + * + * @param {number?} index - The index used to retrieve the enum type + * @return {EnumType} The enum type corresponding to the index + */ + getBasedOnIndex(index) { + const keys = Object.keys(this); + return this[keys[(index ?? 0) % keys.length]]; + } +} + export class SlashCommandEnumValue { /**@type {string}*/ value; /**@type {string}*/ description; - /**@type {string}*/ type = 'enum'; + /**@type {EnumType}*/ type = 'enum'; /**@type {string}*/ typeIcon = '◊'; + /** + * A constructor for creating a SlashCommandEnumValue instance. + * + * @param {string} value - The value + * @param {string?} description - Optional description, displayed in a second line + * @param {EnumType?} type - type of the enum (defining its color) + * @param {string} typeIcon - The icon to display (Can be pulled from `enumIcons` for common ones) + */ constructor(value, description = null, type = 'enum', typeIcon = '◊') { this.value = value; this.description = description; - this.type = type; + this.type = type ?? 'enum'; this.typeIcon = typeIcon; } diff --git a/public/scripts/slash-commands/SlashCommandParser.js b/public/scripts/slash-commands/SlashCommandParser.js index 8c2d7d5d9..1b6f65761 100644 --- a/public/scripts/slash-commands/SlashCommandParser.js +++ b/public/scripts/slash-commands/SlashCommandParser.js @@ -17,6 +17,7 @@ import { SlashCommandAutoCompleteNameResult } from './SlashCommandAutoCompleteNa import { SlashCommandUnnamedArgumentAssignment } from './SlashCommandUnnamedArgumentAssignment.js'; import { SlashCommandEnumValue } from './SlashCommandEnumValue.js'; import { MacroAutoCompleteOption } from '../autocomplete/MacroAutoCompleteOption.js'; +import { commonEnumProviders } from './SlashCommandCommonEnumsProvider.js'; /** @typedef {import('./SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture */ /** @typedef {import('./SlashCommand.js').NamedArguments} NamedArguments */ @@ -134,7 +135,7 @@ export class SlashCommandParser { description: 'The state of the parser flag to set.', typeList: [ARGUMENT_TYPE.BOOLEAN], defaultValue: 'on', - enumList: ['on', 'off'], + enumList: commonEnumProviders.boolean('onOff')(), }), ], splitUnnamedArgument: true, diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 05e812bca..49537568b 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -328,6 +328,7 @@ function listVariablesCallback() { * @param {(string|SlashCommandClosure)[]} value */ async function whileCallback(args, value) { + if (args.guard instanceof SlashCommandClosure) throw new Error('argument \'guard\' cannot be a closure for command /while'); const isGuardOff = isFalseBoolean(args.guard); const iterations = isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS; /**@type {string|SlashCommandClosure} */ @@ -1199,12 +1200,22 @@ export function registerVariableCommands() { callback: ifCallback, returns: 'result of the executed command ("then" or "else")', namedArgumentList: [ - new SlashCommandNamedArgument( - 'left', 'left operand', [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], true, - ), - new SlashCommandNamedArgument( - 'right', 'right operand', [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'left', + description: 'left operand', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), + SlashCommandNamedArgument.fromProps({ + name: 'right', + description: 'right operand', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), new SlashCommandNamedArgument( 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ new SlashCommandEnumValue('gt', 'a > b'), @@ -1214,8 +1225,8 @@ export function registerVariableCommands() { new SlashCommandEnumValue('eq', 'a == b'), new SlashCommandEnumValue('neq', 'a !== b'), new SlashCommandEnumValue('not', '!a'), - new SlashCommandEnumValue('in', 'a includes b'), - new SlashCommandEnumValue('nin', 'a not includes b'), + new SlashCommandEnumValue('in', 'a includes b'), + new SlashCommandEnumValue('nin', 'a not includes b'), ], ), new SlashCommandNamedArgument( @@ -1267,12 +1278,22 @@ export function registerVariableCommands() { callback: whileCallback, returns: 'result of the last executed command', namedArgumentList: [ - new SlashCommandNamedArgument( - 'left', 'left operand', [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], true, - ), - new SlashCommandNamedArgument( - 'right', 'right operand', [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'left', + description: 'left operand', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), + SlashCommandNamedArgument.fromProps({ + name: 'right', + description: 'right operand', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], + isRequired: true, + enumProvider: commonEnumProviders.variables('all'), + forceEnum: false, + }), new SlashCommandNamedArgument( 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ new SlashCommandEnumValue('gt', 'a > b'), @@ -1282,12 +1303,12 @@ export function registerVariableCommands() { new SlashCommandEnumValue('eq', 'a == b'), new SlashCommandEnumValue('neq', 'a !== b'), new SlashCommandEnumValue('not', '!a'), - new SlashCommandEnumValue('in', 'a includes b'), - new SlashCommandEnumValue('nin', 'a not includes b'), + new SlashCommandEnumValue('in', 'a includes b'), + new SlashCommandEnumValue('nin', 'a not includes b'), ], ), new SlashCommandNamedArgument( - 'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, ['off'], + 'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(), ), ], unnamedArgumentList: [ diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index d2d9565a8..2ae8fefe6 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -13,8 +13,10 @@ import { getRegexedString, regex_placement } from './extensions/regex/engine.js' 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 } from './slash-commands/SlashCommandEnumValue.js'; -import { commonEnumProviders, getEnumIconByValueType } from './slash-commands/SlashCommandCommonEnumsProvider.js'; +import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; +import { commonEnumProviders, enumIcons, getEnumIcon } from './slash-commands/SlashCommandCommonEnumsProvider.js'; +import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js'; +import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; export { world_info, @@ -703,14 +705,41 @@ function registerWorldInfoSlashCommands() { /** 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)}`, 'property', getEnumIconByValueType(value.type))), + wiEntryFields: () => Object.entries(newEntryDefinition).map(([key, value]) => + new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : value.default)}`, + enumTypes.enum, getEnumIcon(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 + const world = worldInfoCache[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))); + }, }; + 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 ''; + } + } + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'world', callback: onWorldInfoChange, namedArgumentList: [ new SlashCommandNamedArgument( - 'state', 'set world state', [ARGUMENT_TYPE.STRING], false, false, null, ['off', 'toggle'], + 'state', 'set world state', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOffToggle')(), ), new SlashCommandNamedArgument( 'silent', 'suppress toast messages', [ARGUMENT_TYPE.BOOLEAN], false, @@ -747,7 +776,7 @@ function registerWorldInfoSlashCommands() { description: 'book name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.loreBooks, + enumProvider: commonEnumProviders.worlds, }), SlashCommandNamedArgument.fromProps({ name: 'field', @@ -786,7 +815,7 @@ function registerWorldInfoSlashCommands() { description: 'book name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.loreBooks, + enumProvider: commonEnumProviders.worlds, }), SlashCommandNamedArgument.fromProps({ name: 'field', @@ -797,9 +826,12 @@ function registerWorldInfoSlashCommands() { }), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'UID', ARGUMENT_TYPE.STRING, true, - ), + SlashCommandArgument.fromProps({ + description: 'record UID', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: localEnumProviders.wiUids, + }), ], helpString: `
@@ -825,7 +857,7 @@ function registerWorldInfoSlashCommands() { description: 'book name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.loreBooks, + enumProvider: commonEnumProviders.worlds, }), new SlashCommandNamedArgument( 'key', 'record key', [ARGUMENT_TYPE.STRING], false, @@ -859,11 +891,15 @@ function registerWorldInfoSlashCommands() { description: 'book name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.loreBooks, + enumProvider: commonEnumProviders.worlds, + }), + SlashCommandNamedArgument.fromProps({ + name: 'uid', + description: 'record UID', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: localEnumProviders.wiUids, }), - new SlashCommandNamedArgument( - 'uid', 'record UID', [ARGUMENT_TYPE.STRING], true, - ), SlashCommandNamedArgument.fromProps({ name: 'field', description: 'field name (default: content)', @@ -910,9 +946,9 @@ async function loadWorldInfoData(name) { return; } - if (worldInfoCache[name]) { - return worldInfoCache[name]; - } + // if (worldInfoCache[name]) { + // return worldInfoCache[name]; + // } const response = await fetch('/api/worldinfo/get', { method: 'POST', @@ -2558,7 +2594,7 @@ async function saveWorldInfo(name, data, immediately) { return; } - delete worldInfoCache[name]; + // delete worldInfoCache[name]; if (immediately) { return await _save(name, data); @@ -3555,6 +3591,7 @@ function onWorldInfoChange(args, text) { } break; } + case 'on': default: { selected_world_info.push(name); wiElement.prop('selected', true); diff --git a/public/style.css b/public/style.css index 7269fe784..547d08d8b 100644 --- a/public/style.css +++ b/public/style.css @@ -1487,6 +1487,14 @@ body[data-stscript-style] .autoComplete [data-option-type] { &[data-option-type="macro"] .type { color: var(--ac-color-variableLanguage); } + + &[data-option-type="number"] .type { + color: var(--ac-color-number); + } + + &[data-option-type="name"] .type { + color: var(--ac-color-type); + } } body[data-stscript-style] .hljs.language-stscript { From 2a1704add06d8bcd6621478ea18362fd33118793 Mon Sep 17 00:00:00 2001 From: splitclover Date: Fri, 21 Jun 2024 13:12:57 +0200 Subject: [PATCH 06/58] Added event emmiters when creating new chats --- public/script.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/script.js b/public/script.js index f476b438a..32236359a 100644 --- a/public/script.js +++ b/public/script.js @@ -431,7 +431,9 @@ export const event_types = { CHARACTER_MESSAGE_RENDERED: 'character_message_rendered', FORCE_SET_BACKGROUND: 'force_set_background', CHAT_DELETED: 'chat_deleted', + CHAT_CREATED: 'chat_created', GROUP_CHAT_DELETED: 'group_chat_deleted', + GROUP_CHAT_CREATED: 'group_chat_created', GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts', GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts', GROUP_MEMBER_DRAFTED: 'group_member_drafted', @@ -9214,6 +9216,7 @@ jQuery(async function () { if (selected_group) { await createNewGroupChat(selected_group); if (isDelChatCheckbox) await deleteGroupChat(selected_group, chat_file_for_del); + await eventSource.emit(event_types.GROUP_CHAT_DELETED); } else { //RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime; @@ -9223,6 +9226,7 @@ jQuery(async function () { await getChat(); await createOrEditCharacter(new CustomEvent('newChat')); if (isDelChatCheckbox) await delChat(chat_file_for_del + '.jsonl'); + await eventSource.emit(event_types.CHAT_CREATED); } } From 3092c68a05820c6b9ddeb801dc55748624ae6208 Mon Sep 17 00:00:00 2001 From: splitclover Date: Fri, 21 Jun 2024 13:19:36 +0200 Subject: [PATCH 07/58] Fixed typo --- public/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index 32236359a..7fab18d67 100644 --- a/public/script.js +++ b/public/script.js @@ -9216,7 +9216,7 @@ jQuery(async function () { if (selected_group) { await createNewGroupChat(selected_group); if (isDelChatCheckbox) await deleteGroupChat(selected_group, chat_file_for_del); - await eventSource.emit(event_types.GROUP_CHAT_DELETED); + await eventSource.emit(event_types.GROUP_CHAT_CREATED); } else { //RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime; From feb8321147fae21640ba68ebe0fa2462894914f3 Mon Sep 17 00:00:00 2001 From: splitclover Date: Fri, 21 Jun 2024 13:53:31 +0200 Subject: [PATCH 08/58] Fix emmiter for imported cards --- public/script.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/script.js b/public/script.js index 7fab18d67..ed0e4d9b0 100644 --- a/public/script.js +++ b/public/script.js @@ -5886,11 +5886,13 @@ export async function getChat() { async function getChatResult() { name2 = characters[this_chid].name; + let freshChat = false; if (chat.length === 0) { const message = getFirstMessage(); if (message.mes) { chat.push(message); await saveChatConditional(); + freshChat = true; } } await loadItemizedPrompts(getCurrentChatId()); @@ -5898,6 +5900,7 @@ async function getChatResult() { select_selected_character(this_chid); await eventSource.emit(event_types.CHAT_CHANGED, (getCurrentChatId())); + if (freshChat) await eventSource.emit(event_types.CHAT_CREATED); if (chat.length === 1) { const chat_id = (chat.length - 1); From 0c69b698b973cc63daeaf799e9d64bb1d1e30c22 Mon Sep 17 00:00:00 2001 From: splitclover Date: Fri, 21 Jun 2024 14:16:59 +0200 Subject: [PATCH 09/58] Removed redundant emmiter --- public/script.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/script.js b/public/script.js index ed0e4d9b0..ed4afd666 100644 --- a/public/script.js +++ b/public/script.js @@ -9229,7 +9229,6 @@ jQuery(async function () { await getChat(); await createOrEditCharacter(new CustomEvent('newChat')); if (isDelChatCheckbox) await delChat(chat_file_for_del + '.jsonl'); - await eventSource.emit(event_types.CHAT_CREATED); } } From 56710fee39d2cd51c31c4ae08b1e0ddd3870123b Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:59:18 +0000 Subject: [PATCH 10/58] Apply fix for group chats --- public/script.js | 1 - public/scripts/group-chats.js | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index ed4afd666..ca2765ef8 100644 --- a/public/script.js +++ b/public/script.js @@ -9219,7 +9219,6 @@ jQuery(async function () { if (selected_group) { await createNewGroupChat(selected_group); if (isDelChatCheckbox) await deleteGroupChat(selected_group, chat_file_for_del); - await eventSource.emit(event_types.GROUP_CHAT_CREATED); } else { //RossAscends: added character name to new chat filenames and replaced Date.now() with humanizedDateTime; diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 152074374..ad6c38905 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -183,6 +183,7 @@ export async function getGroupChat(groupId, reload = false) { const group = groups.find((x) => x.id === groupId); const chat_id = group.chat_id; const data = await loadGroupChat(chat_id); + let freshChat = false; await loadItemizedPrompts(getCurrentChatId()); @@ -216,6 +217,7 @@ export async function getGroupChat(groupId, reload = false) { } } await saveGroupChat(groupId, false); + freshChat = true; } if (group) { @@ -228,6 +230,7 @@ export async function getGroupChat(groupId, reload = false) { } await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId()); + if (freshChat) await eventSource.emit(event_types.GROUP_CHAT_CREATED); } /** From f2cc66d4148a5f86326856777ea6d5e66575d917 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 21 Jun 2024 17:27:28 +0300 Subject: [PATCH 11/58] Add console logs to search module --- src/endpoints/search.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/endpoints/search.js b/src/endpoints/search.js index 7efd004f7..7f08d89c8 100644 --- a/src/endpoints/search.js +++ b/src/endpoints/search.js @@ -34,6 +34,8 @@ router.post('/serpapi', jsonParser, async (request, response) => { const { query } = request.body; const result = await fetch(`https://serpapi.com/search.json?q=${encodeURIComponent(query)}&api_key=${key}`); + console.log('SerpApi query', query); + if (!result.ok) { const text = await result.text(); console.log('SerpApi request failed', result.statusText, text); @@ -143,6 +145,8 @@ router.post('/searxng', jsonParser, async (request, response) => { return response.sendStatus(400); } + console.log('SearXNG query', baseUrl, query); + const url = new URL(baseUrl); const params = new URLSearchParams(); params.append('q', query); @@ -205,6 +209,8 @@ router.post('/visit', jsonParser, async (request, response) => { return response.sendStatus(400); } + console.log('Visiting web URL', url); + const result = await fetch(url, { headers: visitHeaders }); if (!result.ok) { From 48077d200bacd9bfa9c440426cbb6df63d4f8dd4 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 21 Jun 2024 20:04:55 +0200 Subject: [PATCH 12/58] More slash command enums (nearly done) --- public/script.js | 16 +- .../scripts/extensions/attachments/index.js | 2 +- public/scripts/extensions/caption/index.js | 2 +- .../scripts/extensions/expressions/index.js | 14 +- public/scripts/extensions/gallery/index.js | 4 +- .../quick-reply/src/SlashCommandHandler.js | 47 ++- public/scripts/extensions/regex/index.js | 13 +- .../extensions/stable-diffusion/index.js | 2 +- public/scripts/extensions/tts/index.js | 11 +- public/scripts/power-user.js | 12 +- public/scripts/preset-manager.js | 1 - public/scripts/slash-commands.js | 354 ++++++++++++------ .../SlashCommandCommonEnumsProvider.js | 106 ++++-- public/scripts/tags.js | 8 +- public/scripts/variables.js | 1 + public/scripts/world-info.js | 4 +- 16 files changed, 415 insertions(+), 182 deletions(-) diff --git a/public/script.js b/public/script.js index bed9072c3..7069e855f 100644 --- a/public/script.js +++ b/public/script.js @@ -8818,18 +8818,15 @@ jQuery(async function () { SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'api', callback: connectAPISlash, - namedArgumentList: [], unnamedArgumentList: [ - new SlashCommandArgument( - 'API to connect to', - [ARGUMENT_TYPE.STRING], - true, - false, - null, - Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => + SlashCommandArgument.fromProps({ + description: 'API to connect to', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(uniqueAPIs.findIndex(x => x === selected)), selected[0].toUpperCase() ?? enumIcons.default)), - ), + }), ], helpString: `
@@ -8918,7 +8915,6 @@ jQuery(async function () { name: 'instruct', callback: selectInstructCallback, returns: 'current preset', - namedArgumentList: [], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'instruct preset name', diff --git a/public/scripts/extensions/attachments/index.js b/public/scripts/extensions/attachments/index.js index 061e83a51..f5296072d 100644 --- a/public/scripts/extensions/attachments/index.js +++ b/public/scripts/extensions/attachments/index.js @@ -213,7 +213,7 @@ jQuery(async () => { return attachments.map(attachment => new SlashCommandEnumValue( returnField === 'name' ? attachment.name : attachment.url, - `${extension_settings.disabled_attachments.includes(attachment.url) ? enumIcons.false : enumIcons.true} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`, + `${enumIcons.getStateIcon(!extension_settings.disabled_attachments.includes(attachment.url))} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`, enumTypes.enum, enumIcons.file)); }, }; diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index 4bbc701ed..b88926e10 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -453,7 +453,7 @@ jQuery(async function () { name: 'id', description: 'get image from a message with this ID', typeList: [ARGUMENT_TYPE.NUMBER], - enumProvider: commonEnumProviders.messages, + enumProvider: commonEnumProviders.messages(), }), ], unnamedArgumentList: [ diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index 146b7e571..eaed37355 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -10,7 +10,7 @@ import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from '../../slash-commands/SlashCommandArgument.js'; import { isFunctionCallingSupported } from '../../openai.js'; -import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; +import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; export { MODULE_NAME }; @@ -2045,6 +2045,14 @@ function migrateSettings() { }); eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced); eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced); + + const localEnumProviders = { + expressions: () => getCachedExpressions().map(expression => { + const isCustom = extension_settings.expressions.custom?.includes(expression); + return new SlashCommandEnumValue(expression, null, isCustom ? enumTypes.name : enumTypes.enum, isCustom ? 'C' : 'D'); + }), + } + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sprite', aliases: ['emote'], @@ -2054,7 +2062,7 @@ function migrateSettings() { description: 'spriteId', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => getCachedExpressions().map((x) => new SlashCommandEnumValue(x)), + enumProvider: localEnumProviders.expressions, }), ], helpString: 'Force sets the sprite for the current character.', @@ -2080,7 +2088,7 @@ function migrateSettings() { description: 'character name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], helpString: 'Returns the last set sprite / expression for the named character.', diff --git a/public/scripts/extensions/gallery/index.js b/public/scripts/extensions/gallery/index.js index cb275a4f8..ce9a12b32 100644 --- a/public/scripts/extensions/gallery/index.js +++ b/public/scripts/extensions/gallery/index.js @@ -417,13 +417,13 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'list-gallery name: 'char', description: 'character name', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: commonEnumProviders.charName('character'), + enumProvider: commonEnumProviders.characters('character'), }), SlashCommandNamedArgument.fromProps({ name: 'group', description: 'group name', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: commonEnumProviders.charName('group'), + enumProvider: commonEnumProviders.characters('group'), }), ], helpString: 'List images in the gallery of the current char / group or a specified char / group.', diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js index 86e6f9016..a2c54e9ee 100644 --- a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js +++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js @@ -36,15 +36,37 @@ export class SlashCommandHandler { } const localEnumProviders = { - qrSets: () => QuickReplySet.list.map(qrSet => new SlashCommandEnumValue(qrSet.name, null, enumTypes.enum, 'S')), + /** All quick reply sets, optionally filtering out sets that wer already used in the "set" named argument */ + qrSets: (executor) => QuickReplySet.list.filter(qrSet => qrSet.name != String(executor.namedArgumentList.find(x => x.name == 'set')?.value)) + .map(qrSet => new SlashCommandEnumValue(qrSet.name, null, enumTypes.enum, 'S')), + /** All QRs inside a set, utilizing the "set" named argument */ qrEntries: (executor) => QuickReplySet.get(String(executor.namedArgumentList.find(x => x.name == 'set')?.value))?.qrList.map(qr => { const icons = getExecutionIcons(qr); const message = `${qr.automationId ? `[${qr.automationId}]` : ''}${icons ? `[auto: ${icons}]` : ''} ${qr.title || qr.message}`.trim(); - return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, 'QR'); + return new SlashCommandEnumValue(qr.label, message, enumTypes.enum, enumIcons.qr); }) ?? [], + + /** All QRs as a set.name string, to be able to execute, for example via the /run command */ + qrExecutables: () => { + const globalSetList = this.api.settings.config.setList; + const chatSetList = this.api.settings.chatConfig?.setList; + + const globalQrs = globalSetList.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat(); + const chatQrs = chatSetList?.map(link => link.set.qrList.map(qr => ({ set: link.set, qr }))).flat() ?? []; + const otherQrs = QuickReplySet.list.filter(set => !globalSetList.some(link => link.set.name === set.name && !chatSetList?.some(link => link.set.name === set.name))) + .map(set => set.qrList.map(qr => ({ set, qr }))).flat(); + + return [ + ...globalQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[global] ${x.qr.title || x.qr.message}`, enumTypes.name, enumIcons.qr)), + ...chatQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `[chat] ${x.qr.title || x.qr.message}`, enumTypes.enum, enumIcons.qr)), + ...otherQrs.map(x => new SlashCommandEnumValue(`${x.set.name}.${x.qr.label}`, `${x.qr.title || x.qr.message}`, enumTypes.qr, enumIcons.qr)), + ]; + }, } + window['qrEnumProviderExecutables'] = localEnumProviders.qrExecutables; + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr', callback: (_, value) => this.executeQuickReplyByIndex(Number(value)), unnamedArgumentList: [ @@ -178,7 +200,7 @@ export class SlashCommandHandler { namedArgumentList: [], unnamedArgumentList: [ new SlashCommandArgument( - 'set type', [ARGUMENT_TYPE.STRING], false, false, null, ['all', 'global', 'chat'], + 'set type', [ARGUMENT_TYPE.STRING], false, false, 'all', ['all', 'global', 'chat'], ), ], helpString: 'Gets a list of the names of all quick reply sets.', @@ -201,8 +223,20 @@ export class SlashCommandHandler { })); const qrArgs = [ - new SlashCommandNamedArgument('label', 'text on the button, e.g., label=MyButton', [ARGUMENT_TYPE.STRING]), - new SlashCommandNamedArgument('set', 'name of the QR set, e.g., set=PresetName1', [ARGUMENT_TYPE.STRING]), + SlashCommandNamedArgument.fromProps({ + name: 'set', + description: 'name of the QR set, e.g., set=PresetName1', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: localEnumProviders.qrSets, + }), + SlashCommandNamedArgument.fromProps({ + name: 'label', + description: 'text on the button, e.g., label=MyButton', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: localEnumProviders.qrLabels, + }), new SlashCommandNamedArgument('hidden', 'whether the button should be hidden, e.g., hidden=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), new SlashCommandNamedArgument('startup', 'auto execute on app startup, e.g., startup=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), new SlashCommandNamedArgument('user', 'auto execute on user message, e.g., user=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), @@ -246,7 +280,7 @@ export class SlashCommandHandler { returns: 'updated quick reply', namedArgumentList: [...qrUpdateArgs, ...qrArgs], unnamedArgumentList: [ - new SlashCommandArgument('message', [ARGUMENT_TYPE.STRING]), + new SlashCommandArgument('command', [ARGUMENT_TYPE.STRING]), ], helpString: `
@@ -425,6 +459,7 @@ export class SlashCommandHandler { typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: localEnumProviders.qrSets, + forceEnum: false, }), ], helpString: ` diff --git a/public/scripts/extensions/regex/index.js b/public/scripts/extensions/regex/index.js index 81632c9e2..7108b9315 100644 --- a/public/scripts/extensions/regex/index.js +++ b/public/scripts/extensions/regex/index.js @@ -3,7 +3,8 @@ import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } import { selected_group } from '../../group-chats.js'; import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; -import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; +import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; +import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js'; import { resolveVariable } from '../../variables.js'; @@ -571,6 +572,14 @@ jQuery(async () => { await loadRegexScripts(); $('#saved_regex_scripts').sortable('enable'); + const localEnumProviders = { + regexScripts: () => getRegexScripts().map(script => { + const isGlobal = extension_settings.regex?.some(x => x.scriptName === script.scriptName); + return new SlashCommandEnumValue(script.scriptName, `${enumIcons.getStateIcon(!script.disabled)} [${isGlobal ? 'global' : 'scoped'}] ${script.findRegex}`, + isGlobal ? enumTypes.enum : enumTypes.name, isGlobal ? 'G' : 'S'); + }), + }; + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'regex', callback: runRegexCallback, @@ -581,7 +590,7 @@ jQuery(async () => { description: 'script name', typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: () => getRegexScripts().map(script => new SlashCommandEnumValue(script.scriptName, null, 'regex', '🔍')), + enumProvider: localEnumProviders.regexScripts, }), ], unnamedArgumentList: [ diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index 2bd8cc15c..d3da529c4 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -3384,7 +3384,7 @@ jQuery(async () => { description: 'workflow name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(x => new SlashCommandEnumValue(x, null, 'workflow', 'W')), + enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(workflow => new SlashCommandEnumValue(workflow)), }), ], helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g.
/imagine-comfy-workflow MyWorkflow
', diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 9eed3205a..2a9b75ffc 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -19,6 +19,7 @@ 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 { debounce_timeout } from '../../constants.js'; +import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; export { talkingAnimation }; const UPDATE_INTERVAL = 1000; @@ -1210,9 +1211,13 @@ $(document).ready(function () { }, aliases: ['narrate', 'tts'], namedArgumentList: [ - new SlashCommandNamedArgument( - 'voice', 'character voice name', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandNamedArgument.fromProps({ + name: 'voice', + description: 'character voice name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: false, + enumProvider: () => Object.keys(voiceMap).map(voiceName => new SlashCommandEnumValue(voiceName, null, enumTypes.enum, voiceName)), + }), ], unnamedArgumentList: [ new SlashCommandArgument( diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index cf4e2d747..2a34984d2 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -47,7 +47,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; import { AUTOCOMPLETE_WIDTH } from './autocomplete/AutoComplete.js'; import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; -import { enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; +import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; export { loadPowerUserSettings, @@ -3939,9 +3939,13 @@ $(document).ready(() => { callback: doMesCut, returns: 'the text of cut messages separated by a newline', unnamedArgumentList: [ - new SlashCommandArgument( - 'number or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], true, - ), + SlashCommandArgument.fromProps({ + description: 'number or range', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + isRequired: true, + acceptsMultiple: true, + enumProvider: commonEnumProviders.messages(), + }), ], helpString: `
diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index 4c6ef88a5..036bc1aa8 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -481,7 +481,6 @@ export async function initPresetManager() { SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'preset', callback: presetCommandCallback, returns: 'current preset', - namedArgumentList: [], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'name', diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index db37443c7..4c28fdec9 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -61,9 +61,9 @@ import { AutoComplete } from './autocomplete/AutoComplete.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashCommandNamedArgumentAssignment.js'; -import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; +import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; -import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js'; +import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; export { executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, }; @@ -79,9 +79,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: '?', callback: helpCommandCallback, aliases: ['help'], - unnamedArgumentList: [new SlashCommandArgument( - 'help topic', ARGUMENT_TYPE.STRING, false, false, null, ['slash', 'format', 'hotkeys', 'macros'], - )], + unnamedArgumentList: [SlashCommandArgument.fromProps({ + description: 'help topic', + typeList: [ARGUMENT_TYPE.STRING], + enumList: [ + new SlashCommandEnumValue('slash', 'slash commands (STscript)', enumTypes.command, '/'), + new SlashCommandEnumValue('macros', '{{macros}} (text replacement)', enumTypes.macro, '{{'), + new SlashCommandEnumValue('format', 'chat/text formatting', enumTypes.name, '★'), + new SlashCommandEnumValue('hotkeys', 'keyboard shortcuts', enumTypes.enum, '⏎'), + ], + })], helpString: 'Get help on macros, chat formatting and commands.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ @@ -94,9 +101,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'persona name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'persona name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.personas, + }), ], helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.', aliases: ['name'], @@ -123,9 +133,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: () => [...document.querySelectorAll('.bg_example')] - .map((it) => new SlashCommandEnumValue(it.getAttribute('bgfile'))) - .filter(it => it.value?.length) - , + .map(it => new SlashCommandEnumValue(it.getAttribute('bgfile'))) + .filter(it => it.value?.length), }), ], helpString: ` @@ -146,9 +155,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sendas', callback: sendMessageAs, namedArgumentList: [ - new SlashCommandNamedArgument( - 'name', 'Character name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'Character name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.characters('character'), + forceEnum: false, + }), new SlashCommandNamedArgument( 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), @@ -156,8 +170,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.variables('all'), - forceEnum: false, + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }), }), ], unnamedArgumentList: [ @@ -200,8 +213,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.variables('all'), - forceEnum: false, + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }) }), ], unnamedArgumentList: [ @@ -255,8 +267,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.variables('all'), - forceEnum: false, + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }) }), ], unnamedArgumentList: [ @@ -340,7 +351,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.charName('all'), + enumProvider: commonEnumProviders.characters('all'), }), ], helpString: 'Opens up a chat with the character or group by its name', @@ -388,7 +399,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'character name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.charName('all'), + enumProvider: commonEnumProviders.characters('all'), }), ], unnamedArgumentList: [ @@ -407,7 +418,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.charName('all'), + enumProvider: commonEnumProviders.characters('all'), }), ], aliases: ['cancel'], @@ -441,17 +452,18 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.variables('all'), - forceEnum: false, + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }) + }), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'display name', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], + defaultValue: '{{user}}', + enumProvider: () => [ + ...commonEnumProviders.characters('character')(), + ...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }) + ], }), - new SlashCommandNamedArgument( - 'name', - 'display name', - [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], - false, - false, - '{{user}}', - ), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -513,7 +525,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'message index or range', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], isRequired: true, - enumProvider: () => chat.map((_, i) => new SlashCommandEnumValue(String(i), null, 'number', '1️⃣')), + enumProvider: commonEnumProviders.messages(), }), ], helpString: 'Hides a chat message from the prompt.', @@ -522,9 +534,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unhide', callback: unhideMessageCallback, unnamedArgumentList: [ - new SlashCommandArgument( - 'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], true, - ), + SlashCommandArgument.fromProps({ + description: 'message index or range', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + isRequired: true, + enumProvider: commonEnumProviders.messages(), + }), ], helpString: 'Unhides a message from the prompt.', })); @@ -537,9 +552,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'member index or name', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: () => [ - ...getGroupMembers().map((character, i) => new SlashCommandEnumValue(String(i), character.name, 'name', '👤')), - ], + enumProvider: commonEnumProviders.groupMembers(), }), ], helpString: 'Disables a group member from being drafted for replies.', @@ -549,9 +562,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['enable', 'enablemember', 'memberenable'], callback: enableGroupMemberCallback, unnamedArgumentList: [ - new SlashCommandArgument( - 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'member index or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.groupMembers(), + }), ], helpString: 'Enables a group member to be drafted for replies.', })); @@ -560,9 +576,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: addGroupMemberCallback, aliases: ['addmember', 'memberadd'], unnamedArgumentList: [ - new SlashCommandArgument( - 'character name', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'character name', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [], + }), ], helpString: `
@@ -583,9 +602,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: removeGroupMemberCallback, aliases: ['removemember', 'memberremove'], unnamedArgumentList: [ - new SlashCommandArgument( - 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'member index or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.groupMembers(), + }), ], helpString: `
@@ -607,9 +629,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: moveGroupMemberUpCallback, aliases: ['upmember', 'memberup'], unnamedArgumentList: [ - new SlashCommandArgument( - 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'member index or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.groupMembers(), + }), ], helpString: 'Moves a group member up in the group chat list.', })); @@ -618,9 +643,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: moveGroupMemberDownCallback, aliases: ['downmember', 'memberdown'], unnamedArgumentList: [ - new SlashCommandArgument( - 'member index or name', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'member index or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: commonEnumProviders.groupMembers(), + }), ], helpString: 'Moves a group member down in the group chat list.', })); @@ -628,9 +656,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'peek', callback: peekCallback, unnamedArgumentList: [ - new SlashCommandArgument( - 'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], false, true, - ), + SlashCommandArgument.fromProps({ + description: 'message index or range', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + isRequired: true, + enumProvider: commonEnumProviders.messages(), + }), ], helpString: `
@@ -656,9 +687,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: deleteSwipeCallback, aliases: ['swipedel'], unnamedArgumentList: [ - new SlashCommandArgument( - '1-based swipe id', [ARGUMENT_TYPE.NUMBER], - ), + SlashCommandArgument.fromProps({ + description: '1-based swipe id', + typeList: [ARGUMENT_TYPE.NUMBER], + isRequired: true, + enumProvider: () => Array.isArray(chat[chat.length - 1]?.swipes) ? + chat[chat.length - 1].swipes.map((/** @type {string} */ swipe, /** @type {number} */ i) => new SlashCommandEnumValue(String(i + 1), swipe, enumTypes.enum, enumIcons.message)) + : [], + }), ], helpString: `
@@ -687,9 +723,18 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ new SlashCommandNamedArgument( 'title', 'title of the toast message', [ARGUMENT_TYPE.STRING], false, ), - new SlashCommandNamedArgument( - 'severity', 'severity level of the toast message', [ARGUMENT_TYPE.STRING], false, false, null, ['info', 'warning', 'error', 'success'], - ), + SlashCommandNamedArgument.fromProps({ + name: 'severity', + description: 'severity level of the toast message', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: 'info', + enumProvider: () => [ + new SlashCommandEnumValue('info', 'info', enumTypes.macro, 'ℹ️'), + new SlashCommandEnumValue('warning', 'warning', enumTypes.enum, '⚠️'), + new SlashCommandEnumValue('error', 'error', enumTypes.enum, '❗'), + new SlashCommandEnumValue('success', 'success', enumTypes.enum, '✅'), + ], + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -716,17 +761,28 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ returns: 'generated text', namedArgumentList: [ new SlashCommandNamedArgument( - 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'], - ), - new SlashCommandNamedArgument( - 'name', 'in-prompt name for instruct mode', [ARGUMENT_TYPE.STRING], false, false, 'System', + 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(), ), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'in-prompt name for instruct mode', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: 'System', + enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)], + forceEnum: false, + }), new SlashCommandNamedArgument( 'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false, ), - new SlashCommandNamedArgument( - 'as', 'role of the output prompt', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'char'], - ), + SlashCommandNamedArgument.fromProps({ + name: 'as', + description: 'role of the output prompt', + typeList: [ARGUMENT_TYPE.STRING], + enumList: [ + new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant), + new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character), + ], + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -748,17 +804,23 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ returns: 'generated text', namedArgumentList: [ new SlashCommandNamedArgument( - 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'], + 'lock', 'lock user input during generation', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( - 'instruct', 'use instruct mode', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', ['on', 'off'], + 'instruct', 'use instruct mode', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( 'stop', 'one-time custom stop strings', [ARGUMENT_TYPE.LIST], false, ), - new SlashCommandNamedArgument( - 'as', 'role of the output prompt', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'char'], - ), + SlashCommandNamedArgument.fromProps({ + name: 'as', + description: 'role of the output prompt', + typeList: [ARGUMENT_TYPE.STRING], + enumList: [ + new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.assistant), + new SlashCommandEnumValue('char', null, enumTypes.enum, enumIcons.character), + ], + }), new SlashCommandNamedArgument( 'system', 'system prompt at the start', [ARGUMENT_TYPE.STRING], false, ), @@ -861,7 +923,12 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pass', - callback: (_, arg) => arg, + callback: (_, arg) => { + // We do not support arrays of closures. Arrays of strings will be send as JSON + if (Array.isArray(arg) && arg.some(x => x instanceof SlashCommandClosure)) throw new Error('Command /pass does not support multiple closures'); + if (Array.isArray(arg)) return JSON.stringify(arg); + return arg; + }, returns: 'the provided value', unnamedArgumentList: [ new SlashCommandArgument( @@ -914,10 +981,10 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ 'default', 'default value of the input field', [ARGUMENT_TYPE.STRING], false, false, '"string"', ), new SlashCommandNamedArgument( - 'large', 'show large input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', ['on', 'off'], + 'large', 'show large input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( - 'wide', 'show wide input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', ['on', 'off'], + 'wide', 'show wide input field', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( 'okButton', 'text for the ok button', [ARGUMENT_TYPE.STRING], false, @@ -949,9 +1016,15 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ ), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'scoped variable or qr label', [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING], true, - ), + SlashCommandArgument.fromProps({ + description: 'scoped variable or qr label', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING], + isRequired: true, + enumProvider: () => [ + ...commonEnumProviders.variables('scope')(), + ...(typeof window['qrEnumProviderExecutables'] === 'function') ? window['qrEnumProviderExecutables']() : [], + ], + }), ], helpString: `
@@ -966,19 +1039,29 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['message'], namedArgumentList: [ new SlashCommandNamedArgument( - 'names', 'show message author names', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', ['off', 'on'], + 'names', 'show message author names', [ARGUMENT_TYPE.BOOLEAN], false, false, 'off', commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( - 'hidden', 'include hidden messages', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', ['off', 'on'], - ), - new SlashCommandNamedArgument( - 'role', 'filter messages by role', [ARGUMENT_TYPE.STRING], false, false, null, ['system', 'assistant', 'user'], + 'hidden', 'include hidden messages', [ARGUMENT_TYPE.BOOLEAN], false, false, 'on', commonEnumProviders.boolean('onOff')(), ), + SlashCommandNamedArgument.fromProps({ + name: 'role', + description: 'filter messages by role', + typeList: [ARGUMENT_TYPE.STRING], + enumList: [ + new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system), + new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant), + new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user), + ], + }), ], unnamedArgumentList: [ - new SlashCommandArgument( - 'message index or range', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], false, true, - ), + SlashCommandArgument.fromProps({ + description: 'message index or range', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + isRequired: true, + enumProvider: commonEnumProviders.messages(), + }), ], returns: 'the specified message or range of messages as a string', helpString: ` @@ -1034,10 +1117,10 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ returns: 'popup text', namedArgumentList: [ new SlashCommandNamedArgument( - 'large', 'show large popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'], + 'large', 'show large popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( - 'wide', 'show wide popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, ['on', 'off'], + 'wide', 'show wide popup', [ARGUMENT_TYPE.BOOLEAN], false, false, null, commonEnumProviders.boolean('onOff')(), ), new SlashCommandNamedArgument( 'okButton', 'text for the OK button', [ARGUMENT_TYPE.STRING], false, @@ -1100,9 +1183,16 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ new SlashCommandNamedArgument( 'limit', 'number of tokens to keep', [ARGUMENT_TYPE.NUMBER], true, ), - new SlashCommandNamedArgument( - 'direction', 'trim direction', [ARGUMENT_TYPE.STRING], true, false, null, ['start', 'end'], - ), + SlashCommandNamedArgument.fromProps({ + name: 'direction', + description: 'trim direction', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + enumList: [ + new SlashCommandEnumValue('start', null, enumTypes.enum, '⬅️'), + new SlashCommandEnumValue('end', null, enumTypes.enum, '➡️'), + ], + }), ], unnamedArgumentList: [ new SlashCommandArgument( @@ -1173,11 +1263,19 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ new SlashCommandNamedArgument( 'scan', 'include injection content into World Info scans', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), + SlashCommandNamedArgument.fromProps({ + name: 'role', + description: 'role for in-chat injections', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: false, + enumList: [ + new SlashCommandEnumValue('system', null, enumTypes.enum, enumIcons.system), + new SlashCommandEnumValue('assistant', null, enumTypes.enum, enumIcons.assistant), + new SlashCommandEnumValue('user', null, enumTypes.enum, enumIcons.user), + ], + }), new SlashCommandNamedArgument( - 'role', 'role for in-chat injections', [ARGUMENT_TYPE.STRING], false, false, 'system', ['system', 'user', 'assistant'], - ), - new SlashCommandNamedArgument( - 'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ['true', 'false'], + 'ephemeral', 'remove injection after generation', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', ), ], unnamedArgumentList: [ @@ -1196,16 +1294,25 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'flushinject', aliases: ['flushinjects'], unnamedArgumentList: [ - new SlashCommandArgument( - 'injection ID or a variable name pointing to ID', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], false, false, '', - ), + SlashCommandArgument.fromProps({ + description: 'injection ID or a variable name pointing to ID', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], + defaultValue: '', + enumProvider: () => [ + ...commonEnumProviders.injects(), + ...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }), + ], + }) ], callback: flushInjectsCallback, helpString: 'Removes a script injection for the current chat. If no ID is provided, removes all script injections.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'tokens', - callback: (_, text) => getTokenCountAsync(text), + callback: (_, text) => { + if (text instanceof SlashCommandClosure || Array.isArray(text)) throw new Error('Unnamed argument cannot be a closure for command /tokens') + return getTokenCountAsync(text).then(count => String(count)); + }, returns: 'number of tokens', unnamedArgumentList: [ new SlashCommandArgument( @@ -1230,12 +1337,28 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['setpromptentries'], callback: setPromptEntryCallback, namedArgumentList: [ - new SlashCommandNamedArgument( - 'identifier', 'Prompt entry identifier(s) to target', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], false, true, - ), - new SlashCommandNamedArgument( - 'name', 'Prompt entry name(s) to target', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], false, true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'identifier', + description: 'Prompt entry identifier(s) to target', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], + acceptsMultiple: true, + enumProvider: () => { + const promptManager = setupChatCompletionPromptManager(oai_settings); + const prompts = promptManager.serviceSettings.prompts; + return prompts.map(prompt => new SlashCommandEnumValue(prompt.identifier, prompt.name, enumTypes.enum)); + }, + }), + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'Prompt entry name(s) to target', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.LIST], + acceptsMultiple: true, + enumProvider: () => { + const promptManager = setupChatCompletionPromptManager(oai_settings); + const prompts = promptManager.serviceSettings.prompts; + return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum)); + } + }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ @@ -1244,7 +1367,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ isRequired: true, acceptsMultiple: false, defaultValue: 'toggle', // unnamed arguments don't support default values yet - enumList: ['on', 'off', 'toggle'], + enumList: commonEnumProviders.boolean('onOffToggle')(), }), ], helpString: 'Sets the specified prompt manager entry/entries on or off.', @@ -1521,7 +1644,7 @@ async function popupCallback(args, value) { await delay(1); await callGenericPopup(safeValue, POPUP_TYPE.TEXT, '', popupOptions); await delay(1); - return value; + return String(value); } function getMessagesCallback(args, value) { @@ -1625,12 +1748,11 @@ async function runCallback(args, name) { /** * - * @param {object} param0 - * @param {SlashCommandAbortController} param0._abortController - * @param {string} [param0.quiet] + * @param {import('./slash-commands/SlashCommand.js').NamedArguments} param0 * @param {string} [reason] */ function abortCallback({ _abortController, quiet }, reason) { + if (quiet instanceof SlashCommandClosure) throw new Error('argument \'quiet\' cannot be a closure for command /abort'); _abortController.abort((reason ?? '').toString().length == 0 ? '/abort command executed' : reason, !isFalseBoolean(quiet ?? 'true')); return ''; } @@ -1661,9 +1783,9 @@ async function inputCallback(args, prompt) { }; // Do not remove this delay, otherwise the prompt will not show up await delay(1); - const result = await callPopup(safeValue, 'input', defaultInput, popupOptions); + const result = await callGenericPopup(safeValue, POPUP_TYPE.INPUT, defaultInput, popupOptions); await delay(1); - return result || ''; + return String(result); } /** diff --git a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js index 6026f82e1..22f25a281 100644 --- a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js +++ b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js @@ -1,6 +1,7 @@ -import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles } from "../../script.js"; +import { chat_metadata, characters, substituteParams, chat, extension_prompt_roles, extension_prompt_types } from "../../script.js"; import { extension_settings } from "../extensions.js"; -import { groups } from "../group-chats.js"; +import { getGroupMembers, groups, selected_group } from "../group-chats.js"; +import { power_user } from "../power-user.js"; import { searchCharByName, getTagsList, tags } from "../tags.js"; import { SlashCommandClosure } from "./SlashCommandClosure.js"; import { SlashCommandEnumValue, enumTypes } from "./SlashCommandEnumValue.js"; @@ -21,11 +22,13 @@ export const enumIcons = { // Common types character: '👤', group: '🧑‍🤝‍🧑', - qr: '🤖', + persona: '🧙‍♂️', + qr: 'QR', tag: '🏷️', world: '🌐', preset: '⚙️', file: '📄', + message: '💬', true: '✔️', false: '❌', @@ -50,6 +53,16 @@ export const enumIcons = { disabled: '❌', vectorized: '🔗', + /** + * Returns the appropriate state icon based on a boolean + * + * @param {boolean} state - The state to determine the icon for + * @returns {string} The corresponding state icon + */ + getStateIcon: (state) => { + return state ? enumIcons.true : enumIcons.false; + }, + /** * Returns the appropriate WI icon based on the entry * @@ -77,6 +90,18 @@ export const enumIcons = { default: return enumIcons.default; } }, + + /** + * A function to get the data type icon + * + * @param {string} type - The type of the data + * @returns {string} The corresponding data type icon + */ + getDataTypeIcon: (type) => { + // Remove possible nullable types definition to match type icon + type = type.replace(/\?$/, ''); + return enumIcons[type] ?? enumIcons.default; + } } /** @@ -88,6 +113,7 @@ export const commonEnumProviders = { /** * Enum values for booleans. Either using true/false or on/off * Optionally supports "toggle". + * * @param {('onOff'|'onOffToggle'|'trueFalse')?} [mode='trueFalse'] - The mode to use. Default is 'trueFalse'. * @returns {() => SlashCommandEnumValue[]} */ @@ -112,9 +138,9 @@ export const commonEnumProviders = { const types = type.flat(); const isAll = types.includes('all'); return [ - ...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(x => new SlashCommandEnumValue(x, null, enumTypes.macro, enumIcons.globalVariable)) : [], - ...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(x => new SlashCommandEnumValue(x, null, enumTypes.name, enumIcons.localVariable)) : [], - ...isAll || types.includes('scope') ? [].map(x => new SlashCommandEnumValue(x, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny + ...isAll || types.includes('global') ? Object.keys(extension_settings.variables.global ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.macro, enumIcons.globalVariable)) : [], + ...isAll || types.includes('local') ? Object.keys(chat_metadata.variables ?? []).map(name => new SlashCommandEnumValue(name, null, enumTypes.name, enumIcons.localVariable)) : [], + ...isAll || types.includes('scope') ? [].map(name => new SlashCommandEnumValue(name, null, enumTypes.variable, enumIcons.scopeVariable)) : [], // TODO: Add scoped variables here, Lenny ] }, @@ -124,13 +150,28 @@ export const commonEnumProviders = { * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return * @returns {() => SlashCommandEnumValue[]} */ - charName: (mode = 'all') => () => { + characters: (mode = 'all') => () => { return [ - ...['all', 'character'].includes(mode) ? characters.map(it => new SlashCommandEnumValue(it.name, null, enumTypes.name, enumIcons.character)) : [], - ...['all', 'group'].includes(mode) ? groups.map(it => new SlashCommandEnumValue(it.name, null, enumTypes.qr, enumIcons.group)) : [], + ...['all', 'character'].includes(mode) ? characters.map(char => new SlashCommandEnumValue(char.name, null, enumTypes.name, enumIcons.character)) : [], + ...['all', 'group'].includes(mode) ? groups.map(group => new SlashCommandEnumValue(group.name, null, enumTypes.qr, enumIcons.group)) : [], ]; }, + /** + * All group members of the given group, or default the current active one + * + * @param {string?} groupId - The id of the group - pass in `undefined` to use the current active group + * @returns {() =>SlashCommandEnumValue[]} + */ + groupMembers: (groupId = undefined) => () => getGroupMembers(groupId).map((character, index) => new SlashCommandEnumValue(String(index), character.name, enumTypes.enum, enumIcons.character)), + + /** + * All possible personas + * + * @returns {SlashCommandEnumValue[]} + */ + personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)), + /** * All possible tags for a given char/group entity * @@ -144,14 +185,26 @@ export const commonEnumProviders = { const key = searchCharByName(substituteParams(charName), { suppressLogging: true }); const assigned = key ? getTagsList(key) : []; return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it)) - .map(it => new SlashCommandEnumValue(it.name, null, enumTypes.command, enumIcons.tag)); + .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag)); }, /** * All messages in the current chat, returning the message id - * @returns {SlashCommandEnumValue[]} + * + * Optionally supports variable names, and/or a placeholder for the last/new message id + * + * @param {object} [options={}] - Optional arguments + * @param {boolean} [options.allowIdAfter=false] - Whether to add an enum option for the new message id after the last message + * @param {boolean} [options.allowVars=false] - Whether to add enum option for variable names + * @returns {() => SlashCommandEnumValue[]} */ - messages: () => chat.map((mes, i) => new SlashCommandEnumValue(String(i), `${mes.name}: ${mes.mes}`, enumTypes.number, mes.is_user ? enumIcons.user : mes.is_system ? enumIcons.system : enumIcons.assistant)), + messages: ({ allowIdAfter = false, allowVars = false } = {}) => () => { + return [ + ...chat.map((message, index) => new SlashCommandEnumValue(String(index), `${message.name}: ${message.mes}`, enumTypes.number, message.is_user ? enumIcons.user : message.is_system ? enumIcons.system : enumIcons.assistant)), + ...allowIdAfter ? [new SlashCommandEnumValue(String(chat.length), '>> After Last Message >>', enumTypes.enum, '➕')] : [], + ...allowVars ? commonEnumProviders.variables('all')() : [], + ]; + }, /** * All existing worlds / lorebooks @@ -159,18 +212,19 @@ export const commonEnumProviders = { * @returns {SlashCommandEnumValue[]} */ worlds: () => $('#world_info').children().toArray().map(x => new SlashCommandEnumValue(x.textContent, null, enumTypes.name, enumIcons.world)), -}; -/** - * Get the unicode icon for the given enum value type - * - * Can also confert nullable data types to their non-nullable counterparts - * - * @param {string} type The type of the enum value - * @returns {string} the unicode icon - */ -export function getEnumIcon(type) { - // Remove possible nullable types definition to match type icon - type = type.replace(/\?$/, ''); - return enumIcons[type] ?? enumIcons.default; -} + /** + * All existing injects for the current chat + * + * @returns {SlashCommandEnumValue[]} + */ + injects: () => { + if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) return []; + return Object.entries(chat_metadata.script_injects) + .map(([id, inject]) => { + const positionName = (Object.entries(extension_prompt_types)).find(([_, value]) => value === inject.position)?.[0] ?? 'unknown'; + return new SlashCommandEnumValue(id, `${enumIcons.getRoleIcon(inject.role ?? extension_prompt_roles.SYSTEM)}[Inject](${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}) ${inject.value}`, + enumTypes.enum, '💉'); + }); + }, +}; diff --git a/public/scripts/tags.js b/public/scripts/tags.js index c8185d8d5..d8aae0617 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -1825,7 +1825,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], unnamedArgumentList: [ @@ -1870,7 +1870,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], unnamedArgumentList: [ @@ -1913,7 +1913,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], unnamedArgumentList: [ @@ -1956,7 +1956,7 @@ function registerTagsSlashCommands() { description: 'Character name', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', - enumProvider: commonEnumProviders.charName(), + enumProvider: commonEnumProviders.characters(), }), ], helpString: ` diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 49537568b..2a869fc6f 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -372,6 +372,7 @@ async function whileCallback(args, value) { * @returns */ async function timesCallback(args, value) { + if (args.guard instanceof SlashCommandClosure) throw new Error('argument \'guard\' cannot be a closure for command /while'); let repeats; let command; if (Array.isArray(value)) { diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 16343ccbd..4cb7bc6ca 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -14,7 +14,7 @@ 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'; -import { commonEnumProviders, enumIcons, getEnumIcon } from './slash-commands/SlashCommandCommonEnumsProvider.js'; +import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { SlashCommandExecutor } from './slash-commands/SlashCommandExecutor.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; @@ -707,7 +707,7 @@ function registerWorldInfoSlashCommands() { /** 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)}`, - enumTypes.enum, getEnumIcon(value.type))), + enumTypes.enum, enumIcons.getDataTypeIcon(value.type))), /** All existing UIDs based on the file argument as world name */ wiUids: (/** @type {SlashCommandExecutor} */ executor) => { From 824d0a9b633cc0198ce6a325b3f0784f7add01d6 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 21 Jun 2024 20:40:58 +0200 Subject: [PATCH 13/58] Small fix to boolean automatic enums --- public/scripts/slash-commands/SlashCommandArgument.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/slash-commands/SlashCommandArgument.js b/public/scripts/slash-commands/SlashCommandArgument.js index 77f396dca..8cf16e820 100644 --- a/public/scripts/slash-commands/SlashCommandArgument.js +++ b/public/scripts/slash-commands/SlashCommandArgument.js @@ -73,7 +73,7 @@ export class SlashCommandArgument { this.forceEnum = forceEnum; // If no enums were set explictly and the type is one where we know possible enum values, we set them here - if (!this.enumList.length && types == ARGUMENT_TYPE.BOOLEAN) this.enumList = commonEnumProviders.boolean()(); + if (!this.enumList.length && this.typeList.includes(ARGUMENT_TYPE.BOOLEAN)) this.enumList = commonEnumProviders.boolean()(); } } From da6d77cffdb1ef6a57aa93b002ef3e19878957cd Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 21 Jun 2024 21:24:37 +0200 Subject: [PATCH 14/58] Enum provider for /model --- public/scripts/slash-commands.js | 37 +++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 4c28fdec9..b6a9f2de0 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -1326,9 +1326,11 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: modelCallback, returns: 'current model', unnamedArgumentList: [ - new SlashCommandArgument( - 'model name', [ARGUMENT_TYPE.STRING], false, - ), + SlashCommandArgument.fromProps({ + description: 'model name', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: () => getModelOptions()?.options.map(option => new SlashCommandEnumValue(option.value, option.value !== option.text ? option.text : null)), + }), ], helpString: 'Sets the model for the current API. Gets the current model name if no argument is provided.', })); @@ -2905,12 +2907,11 @@ function setBackgroundCallback(_, bg) { } /** - * Sets a model for the current API. - * @param {object} _ Unused - * @param {string} model New model name - * @returns {string} New or existing model name + * Retrieves the available model options based on the currently selected main API and its subtype + * + * @returns {{control: HTMLSelectElement, options: HTMLOptionElement[]}?} An array of objects representing the available model options, or null if not supported */ -function modelCallback(_, model) { +function getModelOptions() { const modelSelectMap = [ { id: 'model_togetherai_select', api: 'textgenerationwebui', type: textgen_types.TOGETHERAI }, { id: 'openrouter_model', api: 'textgenerationwebui', type: textgen_types.OPENROUTER }, @@ -2951,17 +2952,33 @@ function modelCallback(_, model) { if (!modelSelectItem) { toastr.info('Setting a model for your API is not supported or not implemented yet.'); - return ''; + return null; } const modelSelectControl = document.getElementById(modelSelectItem); if (!(modelSelectControl instanceof HTMLSelectElement)) { toastr.error(`Model select control not found: ${main_api}[${apiSubType}]`); - return ''; + return null; } const options = Array.from(modelSelectControl.options); + return { control: modelSelectControl, options }; +} + +/** + * Sets a model for the current API. + * @param {object} _ Unused + * @param {string} model New model name + * @returns {string} New or existing model name + */ +function modelCallback(_, model) { + const { control: modelSelectControl, options } = getModelOptions(); + + // If no model was found, the reason was already logged, we just return here + if (options === null) { + return ''; + } if (!options.length) { toastr.warning('No model options found. Check your API settings.'); From e3714e9b6aaea7119a0de37d6b8f473054e3ec98 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 21 Jun 2024 22:31:34 +0300 Subject: [PATCH 15/58] Fix search provider --- src/endpoints/search.js | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/endpoints/search.js b/src/endpoints/search.js index 7f08d89c8..457124228 100644 --- a/src/endpoints/search.js +++ b/src/endpoints/search.js @@ -147,25 +147,36 @@ router.post('/searxng', jsonParser, async (request, response) => { console.log('SearXNG query', baseUrl, query); - const url = new URL(baseUrl); - const params = new URLSearchParams(); - params.append('q', query); - params.append('format', 'html'); - url.pathname = '/search'; - url.search = params.toString(); + const mainPageUrl = new URL(baseUrl); + const mainPageRequest = await fetch(mainPageUrl, { headers: visitHeaders }); - const result = await fetch(url, { - method: 'POST', - headers: visitHeaders, - }); - - if (!result.ok) { - const text = await result.text(); - console.log('SearXNG request failed', result.statusText, text); + if (!mainPageRequest.ok) { + console.log('SearXNG request failed', mainPageRequest.statusText); return response.sendStatus(500); } - const data = await result.text(); + const mainPageText = await mainPageRequest.text(); + const clientHref = mainPageText.match(/href="(\/client.+\.css)"/)?.[1]; + + if (clientHref) { + const clientUrl = new URL(clientHref, baseUrl); + await fetch(clientUrl, { headers: visitHeaders }); + } + + const searchUrl = new URL('/search', baseUrl); + const searchParams = new URLSearchParams(); + searchParams.append('q', query); + searchUrl.search = searchParams.toString(); + + const searchResult = await fetch(searchUrl, { headers: visitHeaders }); + + if (!searchResult.ok) { + const text = await searchResult.text(); + console.log('SearXNG request failed', searchResult.statusText, text); + return response.sendStatus(500); + } + + const data = await searchResult.text(); return response.send(data); } catch (error) { console.log('SearXNG request failed', error); From 62bc550d3af96205cf4cbd668aec01f10609668a Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 21 Jun 2024 23:32:17 +0300 Subject: [PATCH 16/58] Open enums with Alt+Space (also non-breaking) --- public/scripts/autocomplete/AutoComplete.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/scripts/autocomplete/AutoComplete.js b/public/scripts/autocomplete/AutoComplete.js index a7f8ceb0c..df3bcf75c 100644 --- a/public/scripts/autocomplete/AutoComplete.js +++ b/public/scripts/autocomplete/AutoComplete.js @@ -750,8 +750,10 @@ export class AutoComplete { } // autocomplete shown or not, cursor anywhere switch (evt.key) { + // The first is a non-breaking space, the second is a regular space. + case ' ': case ' ': { - if (evt.ctrlKey) { + if (evt.ctrlKey || evt.altKey) { if (this.isActive && this.isReplaceable) { // ctrl-space to toggle details for selected item this.toggleDetails(); @@ -759,6 +761,8 @@ export class AutoComplete { // ctrl-space to force show autocomplete this.show(false, true); } + evt.preventDefault(); + evt.stopPropagation(); return; } break; From 6380e0a062f82a420de18e2d22004ef673bd3e31 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 21 Jun 2024 23:48:11 +0300 Subject: [PATCH 17/58] Add fallback source for attachments. Fix typo --- .../scripts/extensions/attachments/index.js | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/public/scripts/extensions/attachments/index.js b/public/scripts/extensions/attachments/index.js index f5296072d..ca7ef7ba6 100644 --- a/public/scripts/extensions/attachments/index.js +++ b/public/scripts/extensions/attachments/index.js @@ -203,11 +203,12 @@ jQuery(async () => { /** A collection of local enum providers for this context of data bank */ const localEnumProviders = { /** - * All attachements in the data bank based on the source argument. If not provided, defaults to 'chat'. + * All attachments in the data bank based on the source argument. If not provided, defaults to 'chat'. * @param {'name' | 'url'} returnField - Whether the enum should return the 'name' field or the 'url' + * @param {'chat' | 'character' | 'global' | ''} fallbackSource - The source to use if the source argument is not provided. Empty string to use all sources. * */ - attachements: (returnField = 'name') => (/** @type {SlashCommandExecutor} */ executor) => { - const source = executor.namedArgumentList.find(it => it.name == 'source')?.value ?? 'chat'; + attachments: (returnField = 'name', fallbackSource = 'chat') => (/** @type {SlashCommandExecutor} */ executor) => { + const source = executor.namedArgumentList.find(it => it.name == 'source')?.value ?? fallbackSource; if (source instanceof SlashCommandClosure) throw new Error('Argument \'source\' does not support closures'); const attachments = getAttachments(source); @@ -249,7 +250,13 @@ jQuery(async () => { new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES), ], unnamedArgumentList: [ - new SlashCommandArgument('The name or URL of the attachment.', ARGUMENT_TYPE.STRING, true, false), + SlashCommandArgument.fromProps({ + description: 'The name or URL of the attachment.', + typeList: [ARGUMENT_TYPE.STRING], + isRequired: true, + acceptsMultiple: false, + enumProvider: localEnumProviders.attachments('name', ''), + }), ], returns: ARGUMENT_TYPE.STRING, })); @@ -280,13 +287,13 @@ jQuery(async () => { name: 'name', description: 'The name of the attachment.', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: localEnumProviders.attachements('name'), + enumProvider: localEnumProviders.attachments('name'), }), SlashCommandNamedArgument.fromProps({ name: 'url', description: 'The URL of the attachment.', typeList: [ARGUMENT_TYPE.STRING], - enumProvider: localEnumProviders.attachements('url'), + enumProvider: localEnumProviders.attachments('url'), }), ], unnamedArgumentList: [ @@ -308,7 +315,7 @@ jQuery(async () => { description: 'The name or URL of the attachment.', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: localEnumProviders.attachements(), + enumProvider: localEnumProviders.attachments('name', ''), }), ], })); @@ -326,7 +333,7 @@ jQuery(async () => { description: 'The name or URL of the attachment.', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: localEnumProviders.attachements(), + enumProvider: localEnumProviders.attachments('name', ''), }), ], })); @@ -344,7 +351,7 @@ jQuery(async () => { description: 'The name or URL of the attachment.', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: localEnumProviders.attachements(), + enumProvider: localEnumProviders.attachments(), }), ], })); From 791ce3da86f7ef2d07542db5fb3f665aad333895 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 00:11:08 +0300 Subject: [PATCH 18/58] Add stopgaps to fill the second row --- public/scripts/autocomplete/AutoCompleteOption.js | 5 +++++ public/scripts/slash-commands/SlashCommand.js | 5 +++++ public/style.css | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/public/scripts/autocomplete/AutoCompleteOption.js b/public/scripts/autocomplete/AutoCompleteOption.js index 7a12d74b2..abfcf3ff6 100644 --- a/public/scripts/autocomplete/AutoCompleteOption.js +++ b/public/scripts/autocomplete/AutoCompleteOption.js @@ -143,6 +143,11 @@ export class AutoCompleteOption { } li.append(specs); } + const stopgap = document.createElement('span'); { + stopgap.classList.add('stopgap'); + stopgap.textContent = ''; + li.append(stopgap); + } const help = document.createElement('span'); { help.classList.add('help'); const content = document.createElement('span'); { diff --git a/public/scripts/slash-commands/SlashCommand.js b/public/scripts/slash-commands/SlashCommand.js index d04934924..d0821d4d9 100644 --- a/public/scripts/slash-commands/SlashCommand.js +++ b/public/scripts/slash-commands/SlashCommand.js @@ -175,6 +175,11 @@ export class SlashCommand { } li.append(specs); } + const stopgap = document.createElement('span'); { + stopgap.classList.add('stopgap'); + stopgap.textContent = ''; + li.append(stopgap); + } const help = document.createElement('span'); { help.classList.add('help'); const content = document.createElement('span'); { diff --git a/public/style.css b/public/style.css index 45b11d37f..679edc1b4 100644 --- a/public/style.css +++ b/public/style.css @@ -1723,6 +1723,10 @@ body[data-stscript-style] .hljs.language-stscript { } } } + + >.stopgap { + opacity: 0.75; + } } } From a00560d2b379dddb7f7f4bd53bc0e842f6027f3e Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 00:36:29 +0300 Subject: [PATCH 19/58] Ensure format supported before captioning --- public/scripts/extensions/caption/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index d981c15a6..d7a167fd4 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -1,4 +1,4 @@ -import { getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; +import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; import { callPopup, getRequestHeaders, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; import { getMessageTimeStamp } from '../../RossAscends-mods.js'; @@ -272,7 +272,7 @@ async function getCaptionForFile(file, prompt, quiet) { try { setSpinnerIcon(); const context = getContext(); - const fileData = await getBase64Async(file); + const fileData = await getBase64Async(await ensureImageFormatSupported(file)); const base64Format = fileData.split(',')[0].split(';')[0].split('/')[1]; const base64Data = fileData.split(',')[1]; const { caption } = await doCaptionRequest(base64Data, fileData, prompt); From abb186db01e9244c67f678f482db4bf01708807a Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 00:46:14 +0300 Subject: [PATCH 20/58] He warned me. I didn't listen. --- public/style.css | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/public/style.css b/public/style.css index 679edc1b4..127ff829e 100644 --- a/public/style.css +++ b/public/style.css @@ -1633,6 +1633,11 @@ body[data-stscript-style] .hljs.language-stscript { gap: 0.5em; display: contents; + >.stopgap { + opacity: 0.75; + display: none; + } + @container (max-width: 80em) { .specs { grid-column: 2 / 4; @@ -1644,6 +1649,10 @@ body[data-stscript-style] .hljs.language-stscript { opacity: 0.75; height: auto; } + + >.stopgap { + display: inline-block; + } } &.blank { @@ -1723,10 +1732,6 @@ body[data-stscript-style] .hljs.language-stscript { } } } - - >.stopgap { - opacity: 0.75; - } } } From 9c2de78ad3d73c55d777fbef13f4282da38c5fc3 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 01:42:28 +0300 Subject: [PATCH 21/58] Fix OpenRouter caption headers --- src/endpoints/openai.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/endpoints/openai.js b/src/endpoints/openai.js index 9488d03e6..75de75b7b 100644 --- a/src/endpoints/openai.js +++ b/src/endpoints/openai.js @@ -6,6 +6,7 @@ const fs = require('fs'); const { jsonParser, urlencodedParser } = require('../express-common'); const { getConfigValue, mergeObjectWithYaml, excludeKeysByYaml, trimV1 } = require('../util'); const { setAdditionalHeaders } = require('../additional-headers'); +const { OPENROUTER_HEADERS } = require('../constants'); const router = express.Router(); @@ -80,7 +81,7 @@ router.post('/caption-image', jsonParser, async (request, response) => { if (request.body.api === 'openrouter') { apiUrl = 'https://openrouter.ai/api/v1/chat/completions'; - headers['HTTP-Referer'] = request.headers.referer; + Object.assign(headers, OPENROUTER_HEADERS); } if (request.body.api === 'openai') { From 473e11c773de10685022afc7a9ba17cc263407b0 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 02:03:39 +0300 Subject: [PATCH 22/58] New OpenRouter providers --- public/scripts/textgen-models.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/scripts/textgen-models.js b/public/scripts/textgen-models.js index d8f36cf45..5f663c816 100644 --- a/public/scripts/textgen-models.js +++ b/public/scripts/textgen-models.js @@ -39,6 +39,8 @@ const OPENROUTER_PROVIDERS = [ 'Novita', 'Lynn', 'Lynn 2', + 'DeepSeek', + 'Infermatic', ]; export async function loadOllamaModels(data) { From c6c8f91c999dcaacd0e3fe47f18fcf019ab6a0cf Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sat, 22 Jun 2024 01:04:03 +0200 Subject: [PATCH 23/58] forceEnum:false as default & enum icon changes - Set forceEnum to false, for now - Switch some icons around --- public/scripts/slash-commands.js | 2 +- public/scripts/slash-commands/SlashCommandArgument.js | 6 +++--- .../slash-commands/SlashCommandCommonEnumsProvider.js | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index b6a9f2de0..ef164883e 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -84,7 +84,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ typeList: [ARGUMENT_TYPE.STRING], enumList: [ new SlashCommandEnumValue('slash', 'slash commands (STscript)', enumTypes.command, '/'), - new SlashCommandEnumValue('macros', '{{macros}} (text replacement)', enumTypes.macro, '{{'), + new SlashCommandEnumValue('macros', '{{macros}} (text replacement)', enumTypes.macro, enumIcons.macro), new SlashCommandEnumValue('format', 'chat/text formatting', enumTypes.name, '★'), new SlashCommandEnumValue('hotkeys', 'keyboard shortcuts', enumTypes.enum, '⏎'), ], diff --git a/public/scripts/slash-commands/SlashCommandArgument.js b/public/scripts/slash-commands/SlashCommandArgument.js index 8cf16e820..e27831a0c 100644 --- a/public/scripts/slash-commands/SlashCommandArgument.js +++ b/public/scripts/slash-commands/SlashCommandArgument.js @@ -28,7 +28,7 @@ export class SlashCommandArgument { * @param {string|SlashCommandClosure} [props.defaultValue=null] default value if no value is provided * @param {string|SlashCommandEnumValue|(string|SlashCommandEnumValue)[]} [props.enumList=[]] list of accepted values * @param {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]} [props.enumProvider=null] function that returns auto complete options - * @param {boolean} [props.forceEnum=true] default: true - whether the input must match one of the enum values + * @param {boolean} [props.forceEnum=false] default: false - whether the input must match one of the enum values */ static fromProps(props) { return new SlashCommandArgument( @@ -39,7 +39,7 @@ export class SlashCommandArgument { props.defaultValue ?? null, props.enumList ?? [], props.enumProvider ?? null, - props.forceEnum ?? true, + props.forceEnum ?? false, ); } @@ -50,7 +50,7 @@ export class SlashCommandArgument { /**@type {string|SlashCommandClosure}*/ defaultValue; /**@type {SlashCommandEnumValue[]}*/ enumList = []; /**@type {(executor:SlashCommandExecutor)=>SlashCommandEnumValue[]}*/ enumProvider = null; - /**@type {boolean}*/ forceEnum = true; + /**@type {boolean}*/ forceEnum = false; /** * @param {string} description diff --git a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js index 22f25a281..87816db90 100644 --- a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js +++ b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js @@ -14,7 +14,7 @@ export const enumIcons = { default: '◊', // Variables - variable: 'V', + variable: '𝑥', localVariable: 'L', globalVariable: 'G', scopeVariable: 'S', @@ -24,6 +24,8 @@ export const enumIcons = { group: '🧑‍🤝‍🧑', persona: '🧙‍♂️', qr: 'QR', + closure: '𝑓', + macro: '{{', tag: '🏷️', world: '🌐', preset: '⚙️', @@ -37,10 +39,9 @@ export const enumIcons = { boolean: '🔲', string: '📝', number: '1️⃣', - array: '📦', + array: '[]', enum: '📚', - dictionary: '📖', - closure: '🧩', + dictionary: '{}', // Roles system: '⚙️', From 7c2b475e4653852a3b30902554bb7e4a6d3a2cd5 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sat, 22 Jun 2024 04:54:13 +0200 Subject: [PATCH 24/58] Improve popup class with close event handlers --- public/scripts/popup.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/public/scripts/popup.js b/public/scripts/popup.js index be460feb6..6e388839c 100644 --- a/public/scripts/popup.js +++ b/public/scripts/popup.js @@ -28,6 +28,8 @@ export const POPUP_RESULT = { * @property {boolean?} [allowVerticalScrolling] - Whether to allow vertical scrolling in the popup * @property {POPUP_RESULT|number?} [defaultResult] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`. * @property {CustomPopupButton[]|string[]?} [customButtons] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward. + * @property {(popup: Popup) => boolean?} [onClosing] - Handler called before the popup closes, return `false` to cancel the close + * @property {(popup: Popup) => void?} [onClose] - Handler called after the popup closes, but before the DOM is cleaned up */ /** @@ -78,6 +80,9 @@ export class Popup { /** @type {POPUP_RESULT|number?} */ defaultResult; /** @type {CustomPopupButton[]|string[]?} */ customButtons; + /** @type {(popup: Popup) => boolean?} */ onClosing; + /** @type {(popup: Popup) => void?} */ onClose; + /** @type {POPUP_RESULT|number} */ result; /** @type {any} */ value; @@ -94,13 +99,17 @@ export class Popup { * @param {string} [inputValue=''] - The initial value of the input field * @param {PopupOptions} [options={}] - Additional options for the popup */ - constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null } = {}) { + constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null } = {}) { Popup.util.popups.push(this); // Make this popup uniquely identifiable this.id = uuidv4(); this.type = type; + // Utilize event handlers being passed in + this.onClosing = onClosing; + this.onClose = onClose; + /**@type {HTMLTemplateElement}*/ const template = document.querySelector('#popup_template'); // @ts-ignore @@ -317,6 +326,12 @@ export class Popup { this.value = value; this.result = result; + + if (this.onClosing) { + const shouldClose = this.onClosing(this); + if (!shouldClose) return; + } + Popup.util.lastResult = { value, result }; this.hide(); } @@ -337,6 +352,11 @@ export class Popup { // Call the close on the dialog this.dlg.close(); + // Run a possible custom handler right before DOM removal + if (this.onClose) { + this.onClose(this); + } + // Remove it from the dom this.dlg.remove(); From d64b265a398073a7822aa718e001b44a2b0887e0 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sat, 22 Jun 2024 05:03:05 +0200 Subject: [PATCH 25/58] Tag import popup improvements - Save "remember" setting of tag import popup - Add user option to change the tag import setting - Improve tag import popup with adding drilled down bogus folders as auto-added tags - Extract tag import popup to template - Force-open popup no matter the setting on char dropdown button option --- public/index.html | 17 ++- public/script.js | 11 +- public/scripts/power-user.js | 19 ++- public/scripts/tags.js | 153 ++++++++++---------- public/scripts/templates/charTagImport.html | 35 +++++ 5 files changed, 143 insertions(+), 92 deletions(-) create mode 100644 public/scripts/templates/charTagImport.html diff --git a/public/index.html b/public/index.html index 97c2f3ec4..5096da04e 100644 --- a/public/index.html +++ b/public/index.html @@ -3923,13 +3923,22 @@

Character Handling

-
+
-
+
+ + +
-
-
+
@@ -5766,7 +5766,7 @@
-
+
@@ -5784,7 +5784,7 @@
-
+
diff --git a/public/script.js b/public/script.js index ca2765ef8..4b877c3d4 100644 --- a/public/script.js +++ b/public/script.js @@ -1368,7 +1368,7 @@ export async function printCharacters(fullRefresh = false) { nextText: '>', formatNavigator: PAGINATION_TEMPLATE, showNavigator: true, - callback: function (data) { + callback: function (/** @type {Entity[]} */ data) { $(listId).empty(); if (power_user.bogus_folders && isBogusFolderOpen()) { $(listId).append(getBackBlock()); @@ -1388,7 +1388,7 @@ export async function printCharacters(fullRefresh = false) { displayCount++; break; case 'tag': - $(listId).append(getTagBlock(i.item, i.entities, i.hidden)); + $(listId).append(getTagBlock(i.item, i.entities, i.hidden, i.isUseless)); break; } } @@ -1442,8 +1442,9 @@ function verifyCharactersSearchSortRule() { * @property {Character|Group|import('./scripts/tags.js').Tag|*} item - The item * @property {string|number} id - The id * @property {'character'|'group'|'tag'} type - The type of this entity (character, group, tag) - * @property {Entity[]} [entities] - An optional list of entities relevant for this item - * @property {number} [hidden] - An optional number representing how many hidden entities this entity contains + * @property {Entity[]?} [entities=null] - An optional list of entities relevant for this item + * @property {number?} [hidden=null] - An optional number representing how many hidden entities this entity contains + * @property {boolean?} [isUseless=null] - Specifies if the entity is useless (not relevant, but should still be displayed for consistency) and should be displayed greyed out */ /** @@ -1538,6 +1539,15 @@ export function getEntitiesList({ doFilter = false, doSort = true } = {}) { } } + // Final step, updating some properties after the last filter run + const nonTagEntitiesCount = entities.filter(entity => entity.type !== 'tag').length; + for (const entity of entities) { + if (entity.type === 'tag') { + if (entity.entities?.length == nonTagEntitiesCount) entity.isUseless = true; + } + } + + // Sort before returning if requested if (doSort) { sortEntitiesList(entities); } diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 519fc47eb..9175ee284 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -283,11 +283,12 @@ function chooseBogusFolder(source, tagId, remove = false) { * Builds the tag block for the specified item. * * @param {Tag} tag The tag item - * @param {*} entities The list ob sub items for this tag - * @param {*} hidden A count of how many sub items are hidden + * @param {any[]} entities The list ob sub items for this tag + * @param {number} hidden A count of how many sub items are hidden + * @param {boolean} isUseless Whether the tag is useless (should be displayed greyed out) * @returns The html for the tag block */ -function getTagBlock(tag, entities, hidden = 0) { +function getTagBlock(tag, entities, hidden = 0, isUseless = false) { let count = entities.length; const tagFolder = TAG_FOLDER_TYPES[tag.folder_type]; @@ -300,6 +301,7 @@ function getTagBlock(tag, entities, hidden = 0) { template.find('.bogus_folder_hidden_counter').text(hidden > 0 ? `${hidden} hidden` : ''); template.find('.bogus_folder_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`); template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon); + if (isUseless) template.addClass('useless'); // Fill inline character images buildAvatarList(template.find('.bogus_folder_avatars_block'), entities); diff --git a/public/style.css b/public/style.css index 75a77295d..6ca69bcf7 100644 --- a/public/style.css +++ b/public/style.css @@ -2363,6 +2363,10 @@ input[type="file"] { padding: 1px; } +#rm_print_characters_block .entity_block.useless { + opacity: 0.25; +} + #rm_ch_create_block { display: none; overflow-y: auto; From c79f1e4360019ce78efbed28face56b4de0a617c Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sat, 22 Jun 2024 08:52:13 +0200 Subject: [PATCH 27/58] Fix image enlarge popup image sizing --- public/style.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/style.css b/public/style.css index 75a77295d..896b9665f 100644 --- a/public/style.css +++ b/public/style.css @@ -368,6 +368,8 @@ input[type='checkbox']:focus-visible { .img_enlarged_container { padding: 10px; + height: 100%; + width: 100%; } .img_enlarged_container pre code, From 07da2461d00dee9b1acbe677963d6ce4c42e97c9 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sat, 22 Jun 2024 10:04:14 +0200 Subject: [PATCH 28/58] Fix vertical scaling of images in enlarge popup --- public/style.css | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/style.css b/public/style.css index 896b9665f..ad905cf2f 100644 --- a/public/style.css +++ b/public/style.css @@ -367,6 +367,9 @@ input[type='checkbox']:focus-visible { } .img_enlarged_container { + display: flex; + flex-direction: column; + justify-content: flex-end; padding: 10px; height: 100%; width: 100%; @@ -4481,7 +4484,7 @@ a { max-height: 100%; border-radius: 2px; border: 1px solid transparent; - outline: 1px solid var(--SmartThemeBorderColor); + object-fit: contain; } .cropper-container { From aa16ac446d90712ad9b112f21ba04da6e784d554 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 12:53:03 +0300 Subject: [PATCH 29/58] Migrate preference for existing users --- public/scripts/power-user.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index eb0438c15..bc43a8ca0 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -1564,7 +1564,10 @@ function loadPowerUserSettings(settings, data) { } // Clean up old/legacy settings - delete power_user.import_card_tags; + if (power_user.import_card_tags !== undefined) { + power_user.tag_import_setting = power_user.import_card_tags ? tag_import_setting.ASK : tag_import_setting.NONE; + delete power_user.import_card_tags; + } $('#single_line').prop('checked', power_user.single_line); $('#relaxed_api_urls').prop('checked', power_user.relaxed_api_urls); From 36ecf8a7174bd8c91a9f5c43a421e33d302adbf8 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 12:56:57 +0300 Subject: [PATCH 30/58] Update UI when remembering tag import setting --- public/scripts/tags.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 176c46960..24a57a9c0 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -778,6 +778,7 @@ async function showTagImportPopup(character, existingTags, newTags, folderTags) const setting = buttonSettingsMap[popup.result]; if (!setting) return; power_user.tag_import_setting = setting; + $('#tag_import_setting').val(power_user.tag_import_setting); saveSettingsDebounced(); console.log('Remembered tag import setting:', Object.entries(tag_import_setting).find(x => x[1] === setting)[0], setting); } From b448568aa34d4f4d6d5b50877be8a88d92875626 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:28:57 +0300 Subject: [PATCH 31/58] More ollama multimodal models --- public/scripts/extensions/caption/index.js | 6 ++++++ public/scripts/extensions/caption/settings.html | 10 ++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index d7a167fd4..f5cc0845d 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -377,6 +377,12 @@ jQuery(async function () { } function switchMultimodalBlocks() { const isMultimodal = extension_settings.caption.source === 'multimodal'; + $('#caption_ollama_pull').on('click', (e) => { + const presetModel = extension_settings.caption.multimodal_model !== 'ollama_current' ? extension_settings.caption.multimodal_model : ''; + e.preventDefault(); + $('#ollama_download_model').trigger('click'); + $('#dialogue_popup_input').val(presetModel); + }); $('#caption_multimodal_block').toggle(isMultimodal); $('#caption_prompt_block').toggle(isMultimodal); $('#caption_multimodal_api').val(extension_settings.caption.multimodal_api); diff --git a/public/scripts/extensions/caption/settings.html b/public/scripts/extensions/caption/settings.html index 1cdfbaefb..3e23cfbd5 100644 --- a/public/scripts/extensions/caption/settings.html +++ b/public/scripts/extensions/caption/settings.html @@ -58,14 +58,20 @@ - - + + + + +
+
+ The model must be downloaded first! Do it with the ollama pull command or click here. +
- - Hint: Download models and set the URL in the API connection settings. +
+ The model must be downloaded first! Do it with the ollama pull command or click here. +
+ + Hint: Set the URL in the API connection settings.
From 8564d6faa8050e8454c8d8a1e909531456bfc393 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 17:41:02 +0300 Subject: [PATCH 33/58] Debug function to purge all vectors --- public/scripts/extensions/vectors/index.js | 31 +++++++++++++++++++++- src/endpoints/vectors.js | 19 +++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/public/scripts/extensions/vectors/index.js b/public/scripts/extensions/vectors/index.js index 54c178b53..fc5fd8813 100644 --- a/public/scripts/extensions/vectors/index.js +++ b/public/scripts/extensions/vectors/index.js @@ -20,7 +20,7 @@ import { renderExtensionTemplateAsync, doExtrasFetch, getApiUrl, } from '../../extensions.js'; -import { collapseNewlines } from '../../power-user.js'; +import { collapseNewlines, registerDebugFunction } from '../../power-user.js'; import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js'; import { getDataBankAttachments, getDataBankAttachmentsForSource, getFileAttachment } from '../../chats.js'; import { debounce, getStringHash as calculateHash, waitUntilCondition, onlyUnique, splitRecursive, trimToStartSentence, trimToEndSentence } from '../../utils.js'; @@ -989,6 +989,28 @@ async function purgeVectorIndex(collectionId) { } } +/** + * Purges all vector indexes. + */ +async function purgeAllVectorIndexes() { + try { + const response = await fetch('/api/vector/purge-all', { + method: 'POST', + headers: getRequestHeaders(), + }); + + if (!response.ok) { + throw new Error('Failed to purge all vector indexes'); + } + + console.log('Vectors: Purged all vector indexes'); + toastr.success('All vector indexes purged', 'Purge successful'); + } catch (error) { + console.error('Vectors: Failed to purge all', error); + toastr.error('Failed to purge all vector indexes', 'Purge failed'); + } +} + function toggleSettings() { $('#vectors_files_settings').toggle(!!settings.enabled_files); $('#vectors_chats_settings').toggle(!!settings.enabled_chats); @@ -1578,4 +1600,11 @@ jQuery(async () => { ], returns: ARGUMENT_TYPE.LIST, })); + + registerDebugFunction('purge-everything', 'Purge all vector indices', 'Obliterate all stored vectors for all sources. No mercy.', async () => { + if (!confirm('Are you sure?')) { + return; + } + await purgeAllVectorIndexes(); + }); }); diff --git a/src/endpoints/vectors.js b/src/endpoints/vectors.js index 74666e584..38f74f7d8 100644 --- a/src/endpoints/vectors.js +++ b/src/endpoints/vectors.js @@ -1,5 +1,6 @@ const vectra = require('vectra'); const path = require('path'); +const fs = require('fs'); const express = require('express'); const sanitize = require('sanitize-filename'); const { jsonParser } = require('../express-common'); @@ -440,6 +441,24 @@ router.post('/delete', jsonParser, async (req, res) => { } }); +router.post('/purge-all', jsonParser, async (req, res) => { + try { + for (const source of SOURCES) { + const sourcePath = path.join(req.user.directories.vectors, sanitize(source)); + if (!fs.existsSync(sourcePath)) { + continue; + } + await fs.promises.rm(sourcePath, { recursive: true }); + console.log(`Deleted vector source store at ${sourcePath}`); + } + + return res.sendStatus(200); + } catch (error) { + console.error(error); + return res.sendStatus(500); + } +}); + router.post('/purge', jsonParser, async (req, res) => { try { if (!req.body.collectionId) { From d64647280a20da9507209d912e67122ad256a877 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 17:41:40 +0300 Subject: [PATCH 34/58] Fix method deprecation warning --- src/endpoints/extensions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/endpoints/extensions.js b/src/endpoints/extensions.js index d14ddb8cf..87c06da9c 100644 --- a/src/endpoints/extensions.js +++ b/src/endpoints/extensions.js @@ -191,7 +191,7 @@ router.post('/delete', jsonParser, async (request, response) => { return response.status(400).send('Bad Request: extensionName is required in the request body.'); } - // Sanatize the extension name to prevent directory traversal + // Sanitize the extension name to prevent directory traversal const extensionName = sanitize(request.body.extensionName); try { @@ -201,7 +201,7 @@ router.post('/delete', jsonParser, async (request, response) => { return response.status(404).send(`Directory does not exist at ${extensionPath}`); } - await fs.promises.rmdir(extensionPath, { recursive: true }); + await fs.promises.rm(extensionPath, { recursive: true }); console.log(`Extension has been deleted at ${extensionPath}`); return response.send(`Extension has been deleted at ${extensionPath}`); From a39a1a7cec7020cac3114d9ad571c8e7b54ffce9 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 22 Jun 2024 17:44:08 +0300 Subject: [PATCH 35/58] Fix odd-named preset selection via command --- public/scripts/preset-manager.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index 42cc7585a..d751c5803 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -140,7 +140,10 @@ class PresetManager { * @param {string} value Preset option value */ selectPreset(value) { - $(this.select).find(`option[value=${value}]`).prop('selected', true); + const option = $(this.select).filter(function() { + return $(this).val() === value; + }); + option.prop('selected', true); $(this.select).val(value).trigger('change'); } From de1910268a21da3acd50fed3658905cee2c90c54 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 01:26:25 +0300 Subject: [PATCH 36/58] Add missing macro reference --- public/scripts/templates/macros.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index 353dcc12e..8d2e4ebdd 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -21,9 +21,11 @@
  • {{char_version}}the Character's version number
  • {{group}}a comma-separated list of group member names or the character name in solo chats. Alias: {{charIfNotGroup}}
  • {{model}}a text generation model name for the currently selected API. Can be inaccurate!
  • -
  • {{lastMessage}} - the text of the latest chat message.
  • +
  • {{lastMessage}}the text of the latest chat message.
  • +
  • {{lastUserMessage}}the text of the latest user chat message.
  • +
  • {{lastCharMessage}}the text of the latest character chat message.
  • {{lastMessageId}}index # of the latest chat message. Useful for slash command batching.
  • -
  • {{firstIncludedMessageId}} - the ID of the first message included in the context. Requires generation to be ran at least once in the current session.
  • +
  • {{firstIncludedMessageId}}the ID of the first message included in the context. Requires generation to be ran at least once in the current session.
  • {{currentSwipeId}}the 1-based ID of the current swipe in the last chat message. Empty string if the last message is user or prompt-hidden.
  • {{lastSwipeId}}the number of swipes in the last chat message. Empty string if the last message is user or prompt-hidden.
  • {{// (note)}}you can leave a note here, and the macro will be replaced with blank content. Not visible for the AI.
  • From 323f34f5d473cb76a90e38be03246dae85158f7a Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 01:34:10 +0300 Subject: [PATCH 37/58] Fix QR breaking when no sets --- public/scripts/extensions/quick-reply/src/ui/SettingsUi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/quick-reply/src/ui/SettingsUi.js b/public/scripts/extensions/quick-reply/src/ui/SettingsUi.js index 48a5d059d..96d1208ce 100644 --- a/public/scripts/extensions/quick-reply/src/ui/SettingsUi.js +++ b/public/scripts/extensions/quick-reply/src/ui/SettingsUi.js @@ -148,7 +148,7 @@ export class SettingsUi { this.onQrSetChange(); } onQrSetChange() { - this.currentQrSet = QuickReplySet.get(this.currentSet.value); + this.currentQrSet = QuickReplySet.get(this.currentSet.value) ?? new QuickReplySet(); this.disableSend.checked = this.currentQrSet.disableSend; this.placeBeforeInput.checked = this.currentQrSet.placeBeforeInput; this.injectInput.checked = this.currentQrSet.injectInput; From 42766a715da043e1536e3d14e6a987ebd5ea5000 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 23 Jun 2024 02:32:06 +0200 Subject: [PATCH 38/58] Popup type "DISPLAY" & image enlarge changes - New popup type "DISPLAY", for showing content with an X in the corner, without buttons - Rework popup result controls to automatically support click (or other) events to close complete the popup - Fix inline image icons/actions being keyboard interactable - Switch inline image enlarge popup to new DISPLAY type --- public/css/popup.css | 11 ++++++- public/index.html | 5 +-- public/script.js | 2 +- public/scripts/chats.js | 2 +- public/scripts/popup.js | 70 ++++++++++++++++++++++++++--------------- public/style.css | 20 +++++++++--- 6 files changed, 75 insertions(+), 35 deletions(-) diff --git a/public/css/popup.css b/public/css/popup.css index 62c29fcf9..0ce4af1ce 100644 --- a/public/css/popup.css +++ b/public/css/popup.css @@ -109,7 +109,6 @@ dialog { .menu_button.popup-button-ok { background-color: var(--crimson70a); - cursor: pointer; } .menu_button.popup-button-ok:hover { @@ -132,3 +131,13 @@ dialog { filter: brightness(1.3) saturate(1.3); } +.popup .popup-button-close { + position: absolute; + top: -8px; + right: -8px; + width: 20px; + height: 20px; + font-size: 19px; + filter: brightness(0.4); +} + diff --git a/public/index.html b/public/index.html index b2fc3783d..0fc7f5b36 100644 --- a/public/index.html +++ b/public/index.html @@ -4855,10 +4855,11 @@
    +
    diff --git a/public/script.js b/public/script.js index 29df8a38e..c1e199885 100644 --- a/public/script.js +++ b/public/script.js @@ -2122,7 +2122,7 @@ export function addCopyToCodeBlocks(messageElement) { hljs.highlightElement(codeBlocks.get(i)); if (navigator.clipboard !== undefined) { const copyButton = document.createElement('i'); - copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy'); + copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy', 'interactable'); copyButton.title = 'Copy code'; codeBlocks.get(i).appendChild(copyButton); copyButton.addEventListener('pointerup', function (event) { diff --git a/public/scripts/chats.js b/public/scripts/chats.js index 32a188c20..3362189fa 100644 --- a/public/scripts/chats.js +++ b/public/scripts/chats.js @@ -587,7 +587,7 @@ function enlargeMessageImage() { const titleEmpty = !title || title.trim().length === 0; imgContainer.find('pre').toggle(!titleEmpty); addCopyToCodeBlocks(imgContainer); - callGenericPopup(imgContainer, POPUP_TYPE.TEXT, '', { wide: true, large: true }); + callGenericPopup(imgContainer, POPUP_TYPE.DISPLAY, '', { large: true }); } async function deleteMessageImage() { diff --git a/public/scripts/popup.js b/public/scripts/popup.js index 6e388839c..7222a9a28 100644 --- a/public/scripts/popup.js +++ b/public/scripts/popup.js @@ -3,17 +3,22 @@ import { removeFromArray, runAfterAnimation, uuidv4 } from './utils.js'; /** @readonly */ /** @enum {Number} */ export const POPUP_TYPE = { - 'TEXT': 1, - 'CONFIRM': 2, - 'INPUT': 3, + /** Main popup type. Containing any content displayed, with buttons below. Can also contain additional input controls. */ + TEXT: 1, + /** Popup mainly made to confirm something, answering with a simple Yes/No or similar. Focus on the button controls. */ + CONFIRM: 2, + /** Popup who's main focus is the input text field, which is displayed here. Can contain additional content above. Return value for this is the input string. */ + INPUT: 3, + /** Popup without any button controls. Used to simply display content, with a small X in the corner. */ + DISPLAY: 4, }; /** @readonly */ /** @enum {number?} */ export const POPUP_RESULT = { - 'AFFIRMATIVE': 1, - 'NEGATIVE': 0, - 'CANCELLED': null, + AFFIRMATIVE: 1, + NEGATIVE: 0, + CANCELLED: null, }; /** @@ -75,8 +80,9 @@ export class Popup { /** @type {HTMLElement} */ content; /** @type {HTMLTextAreaElement} */ input; /** @type {HTMLElement} */ controls; - /** @type {HTMLElement} */ ok; - /** @type {HTMLElement} */ cancel; + /** @type {HTMLElement} */ okButton; + /** @type {HTMLElement} */ cancelButton; + /** @type {HTMLElement} */ closeButton; /** @type {POPUP_RESULT|number?} */ defaultResult; /** @type {CustomPopupButton[]|string[]?} */ customButtons; @@ -118,8 +124,9 @@ export class Popup { this.content = this.dlg.querySelector('.popup-content'); this.input = this.dlg.querySelector('.popup-input'); this.controls = this.dlg.querySelector('.popup-controls'); - this.ok = this.dlg.querySelector('.popup-button-ok'); - this.cancel = this.dlg.querySelector('.popup-button-cancel'); + this.okButton = this.dlg.querySelector('.popup-button-ok'); + this.cancelButton = this.dlg.querySelector('.popup-button-cancel'); + this.closeButton = this.dlg.querySelector('.popup-button-close'); this.dlg.setAttribute('data-id', this.id); if (wide) this.dlg.classList.add('wide_dialogue_popup'); @@ -129,8 +136,8 @@ export class Popup { if (allowVerticalScrolling) this.dlg.classList.add('vertical_scrolling_dialogue_popup'); // If custom button captions are provided, we set them beforehand - this.ok.textContent = typeof okButton === 'string' ? okButton : 'OK'; - this.cancel.textContent = typeof cancelButton === 'string' ? cancelButton : template.getAttribute('popup-button-cancel'); + this.okButton.textContent = typeof okButton === 'string' ? okButton : 'OK'; + this.cancelButton.textContent = typeof cancelButton === 'string' ? cancelButton : template.getAttribute('popup-button-cancel'); this.defaultResult = defaultResult; this.customButtons = customButtons; @@ -141,17 +148,14 @@ export class Popup { const buttonElement = document.createElement('div'); buttonElement.classList.add('menu_button', 'popup-button-custom', 'result-control'); buttonElement.classList.add(...(button.classes ?? [])); - buttonElement.setAttribute('data-result', String(button.result ?? undefined)); + buttonElement.dataset.result = String(button.result ?? undefined); buttonElement.textContent = button.text; buttonElement.tabIndex = 0; - if (button.action) buttonElement.addEventListener('click', button.action); - if (button.result) buttonElement.addEventListener('click', () => this.complete(button.result)); - if (button.appendAtEnd) { this.controls.appendChild(buttonElement); } else { - this.controls.insertBefore(buttonElement, this.ok); + this.controls.insertBefore(buttonElement, this.okButton); } }); @@ -159,23 +163,30 @@ export class Popup { const defaultButton = this.controls.querySelector(`[data-result="${this.defaultResult}"]`); if (defaultButton) defaultButton.classList.add('menu_button_default'); + // Styling differences depending on the popup type + // General styling for all types first, that might be overriden for specific types below + this.input.style.display = 'none'; + this.closeButton.style.display = 'none'; + switch (type) { case POPUP_TYPE.TEXT: { - this.input.style.display = 'none'; - if (!cancelButton) this.cancel.style.display = 'none'; + if (!cancelButton) this.cancelButton.style.display = 'none'; break; } case POPUP_TYPE.CONFIRM: { - this.input.style.display = 'none'; - if (!okButton) this.ok.textContent = template.getAttribute('popup-button-yes'); - if (!cancelButton) this.cancel.textContent = template.getAttribute('popup-button-no'); + if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-yes'); + if (!cancelButton) this.cancelButton.textContent = template.getAttribute('popup-button-no'); break; } case POPUP_TYPE.INPUT: { this.input.style.display = 'block'; - if (!okButton) this.ok.textContent = template.getAttribute('popup-button-save'); + if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-save'); break; } + case POPUP_TYPE.DISPLAY: { + this.controls.style.display = 'none'; + this.closeButton.style.display = 'block'; + } default: { console.warn('Unknown popup type.', type); break; @@ -202,8 +213,14 @@ export class Popup { // Set focus event that remembers the focused element this.dlg.addEventListener('focusin', (evt) => { if (evt.target instanceof HTMLElement && evt.target != this.dlg) this.lastFocus = evt.target; }); - this.ok.addEventListener('click', () => this.complete(POPUP_RESULT.AFFIRMATIVE)); - this.cancel.addEventListener('click', () => this.complete(POPUP_RESULT.NEGATIVE)); + // Bind event listeners for all result controls to their defined event type + this.dlg.querySelectorAll(`[data-result]`).forEach(resultControl => { + if (!(resultControl instanceof HTMLElement)) return; + const result = Number(resultControl.dataset.result); + if (isNaN(result)) throw new Error('Invalid result control. Result must be a number. ' + resultControl.dataset.result); + const type = resultControl.dataset.resultEvent || 'click'; + resultControl.addEventListener(type, () => this.complete(result)); + }); // Bind dialog listeners manually, so we can be sure context is preserved const cancelListener = (evt) => { @@ -296,6 +313,9 @@ export class Popup { if (applyAutoFocus) { control.setAttribute('autofocus', ''); + // Manually enable tabindex too, as this might only be applied by the interactable functionality in the background, but too late for HTML autofocus + // interactable only gets applied when inserted into the DOM + control.tabIndex = 0; } else { control.focus(); } diff --git a/public/style.css b/public/style.css index 96a1ef69e..3df89b441 100644 --- a/public/style.css +++ b/public/style.css @@ -4457,26 +4457,36 @@ a { .mes_img_controls { position: absolute; - top: 0.5em; + top: 0.1em; left: 0; width: 100%; - display: none; + display: flex; + opacity: 0; flex-direction: row; justify-content: space-between; padding: 1em; } .mes_img_controls .right_menu_button { - padding: 0; filter: brightness(80%); + padding: 1px; + height: 1.25em; + width: 1.25em; +} + +.mes_img_controls .right_menu_button::before { + /* Fix weird alignment with this font-awesome icons on focus */ + position: relative; + top: 0.6125em; } .mes_img_controls .right_menu_button:hover { filter: brightness(150%); } -.mes_img_container:hover .mes_img_controls { - display: flex; +.mes_img_container:hover .mes_img_controls, +.mes_img_container:focus-within .mes_img_controls { + opacity: 1; } .mes .mes_img_container.img_extra { From 48621f1d50becc9343851afd8627e0b89969171b Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 23 Jun 2024 02:43:37 +0200 Subject: [PATCH 39/58] Fix scaling of enlarged popup image --- public/style.css | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/public/style.css b/public/style.css index 3df89b441..20dba9f6b 100644 --- a/public/style.css +++ b/public/style.css @@ -4494,11 +4494,9 @@ a { } .img_enlarged { - max-width: 100%; - max-height: 100%; - border-radius: 2px; - border: 1px solid transparent; object-fit: contain; + /* Scaling via flex-grow and object-fit only works if we have some kind of base-height set */ + min-height: 120px; } .cropper-container { From 7642b66a0e0e0253665ad9004c25cd86df0fed9b Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 23 Jun 2024 12:26:52 +0200 Subject: [PATCH 40/58] Improve enlarge inline image - Make enlarge inline image popup zoomable - Add optional popup class for transparent popups --- public/css/popup.css | 11 +++++--- public/scripts/chats.js | 22 +++++++++++++--- public/scripts/popup.js | 28 ++++++++++---------- public/style.css | 57 ++++++++++++++++++++++++++++++++--------- 4 files changed, 86 insertions(+), 32 deletions(-) diff --git a/public/css/popup.css b/public/css/popup.css index 0ce4af1ce..a35e7b96e 100644 --- a/public/css/popup.css +++ b/public/css/popup.css @@ -135,9 +135,14 @@ dialog { position: absolute; top: -8px; right: -8px; - width: 20px; - height: 20px; - font-size: 19px; + width: 21px; + height: 21px; + font-size: 18px; + padding: 2px 3px 3px 2px; + filter: brightness(0.4); + + /* Fix weird animation issue with font-scaling during popup open */ + backface-visibility: hidden; } diff --git a/public/scripts/chats.js b/public/scripts/chats.js index 3362189fa..83069aaab 100644 --- a/public/scripts/chats.js +++ b/public/scripts/chats.js @@ -35,7 +35,7 @@ import { extractTextFromOffice, } from './utils.js'; import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js'; -import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js'; +import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; import { ScraperManager } from './scrapers.js'; import { DragAndDropHandler } from './dragdrop.js'; @@ -566,7 +566,7 @@ export function isExternalMediaAllowed() { return !power_user.forbid_external_media; } -function enlargeMessageImage() { +async function enlargeMessageImage() { const mesBlock = $(this).closest('.mes'); const mesId = mesBlock.attr('mesid'); const message = chat[mesId]; @@ -580,14 +580,28 @@ function enlargeMessageImage() { const img = document.createElement('img'); img.classList.add('img_enlarged'); img.src = imgSrc; + const imgHolder = document.createElement('div'); + imgHolder.classList.add('img_enlarged_holder'); + imgHolder.append(img); const imgContainer = $('
    '); - imgContainer.prepend(img); + imgContainer.prepend(imgHolder); imgContainer.addClass('img_enlarged_container'); imgContainer.find('code').addClass('txt').text(title); const titleEmpty = !title || title.trim().length === 0; imgContainer.find('pre').toggle(!titleEmpty); addCopyToCodeBlocks(imgContainer); - callGenericPopup(imgContainer, POPUP_TYPE.DISPLAY, '', { large: true }); + + const popup = new Popup(imgContainer, POPUP_TYPE.DISPLAY, '', { large: true, transparent: true }); + + popup.dlg.style.width = 'unset'; + popup.dlg.style.height = 'unset'; + + img.addEventListener('click', () => { + const shouldZoom = !img.classList.contains('zoomed'); + img.classList.toggle('zoomed', shouldZoom); + }); + + await popup.show(); } async function deleteMessageImage() { diff --git a/public/scripts/popup.js b/public/scripts/popup.js index 7222a9a28..8e82994c5 100644 --- a/public/scripts/popup.js +++ b/public/scripts/popup.js @@ -23,18 +23,19 @@ export const POPUP_RESULT = { /** * @typedef {object} PopupOptions - * @property {string|boolean?} [okButton] - Custom text for the OK button, or `true` to use the default (If set, the button will always be displayed, no matter the type of popup) - * @property {string|boolean?} [cancelButton] - Custom text for the Cancel button, or `true` to use the default (If set, the button will always be displayed, no matter the type of popup) - * @property {number?} [rows] - The number of rows for the input field - * @property {boolean?} [wide] - Whether to display the popup in wide mode (wide screen, 1/1 aspect ratio) - * @property {boolean?} [wider] - Whether to display the popup in wider mode (just wider, no height scaling) - * @property {boolean?} [large] - Whether to display the popup in large mode (90% of screen) - * @property {boolean?} [allowHorizontalScrolling] - Whether to allow horizontal scrolling in the popup - * @property {boolean?} [allowVerticalScrolling] - Whether to allow vertical scrolling in the popup - * @property {POPUP_RESULT|number?} [defaultResult] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`. - * @property {CustomPopupButton[]|string[]?} [customButtons] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward. - * @property {(popup: Popup) => boolean?} [onClosing] - Handler called before the popup closes, return `false` to cancel the close - * @property {(popup: Popup) => void?} [onClose] - Handler called after the popup closes, but before the DOM is cleaned up + * @property {string|boolean?} [okButton=null] - Custom text for the OK button, or `true` to use the default (If set, the button will always be displayed, no matter the type of popup) + * @property {string|boolean?} [cancelButton=null] - Custom text for the Cancel button, or `true` to use the default (If set, the button will always be displayed, no matter the type of popup) + * @property {number?} [rows=1] - The number of rows for the input field + * @property {boolean?} [wide=false] - Whether to display the popup in wide mode (wide screen, 1/1 aspect ratio) + * @property {boolean?} [wider=false] - Whether to display the popup in wider mode (just wider, no height scaling) + * @property {boolean?} [large=false] - Whether to display the popup in large mode (90% of screen) + * @property {boolean?} [transparent=false] - Whether to display the popup in transparent mode (no background, border, shadow or anything, only its content) + * @property {boolean?} [allowHorizontalScrolling=false] - Whether to allow horizontal scrolling in the popup + * @property {boolean?} [allowVerticalScrolling=false] - Whether to allow vertical scrolling in the popup + * @property {POPUP_RESULT|number?} [defaultResult=POPUP_RESULT.AFFIRMATIVE] - The default result of this popup when Enter is pressed. Can be changed from `POPUP_RESULT.AFFIRMATIVE`. + * @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward. + * @property {(popup: Popup) => boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close + * @property {(popup: Popup) => void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up */ /** @@ -105,7 +106,7 @@ export class Popup { * @param {string} [inputValue=''] - The initial value of the input field * @param {PopupOptions} [options={}] - Additional options for the popup */ - constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null } = {}) { + constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null } = {}) { Popup.util.popups.push(this); // Make this popup uniquely identifiable @@ -132,6 +133,7 @@ export class Popup { if (wide) this.dlg.classList.add('wide_dialogue_popup'); if (wider) this.dlg.classList.add('wider_dialogue_popup'); if (large) this.dlg.classList.add('large_dialogue_popup'); + if (transparent) this.dlg.classList.add('transparent_dialogue_popup'); if (allowHorizontalScrolling) this.dlg.classList.add('horizontal_scrolling_dialogue_popup'); if (allowVerticalScrolling) this.dlg.classList.add('vertical_scrolling_dialogue_popup'); diff --git a/public/style.css b/public/style.css index 20dba9f6b..55aec739e 100644 --- a/public/style.css +++ b/public/style.css @@ -366,16 +366,6 @@ input[type='checkbox']:focus-visible { font-weight: bold; } -.img_enlarged_container { - display: flex; - flex-direction: column; - justify-content: flex-end; - padding: 10px; - height: 100%; - width: 100%; -} - -.img_enlarged_container pre code, .mes_text pre code { position: relative; display: block; @@ -3144,6 +3134,12 @@ grammarly-extension { min-width: 750px; } +.transparent_dialogue_popup { + background-color: transparent; + box-shadow: none; + border: none; +} + #dialogue_popup .horizontal_scrolling_dialogue_popup { overflow-x: unset !important; } @@ -4493,12 +4489,49 @@ a { display: flex; } -.img_enlarged { - object-fit: contain; +.img_enlarged_holder { /* Scaling via flex-grow and object-fit only works if we have some kind of base-height set */ min-height: 120px; } +.img_enlarged_holder:has(.zoomed) { + overflow: auto; +} + +.img_enlarged { + object-fit: contain; + width: 100%; + height: 100%; + cursor: zoom-in +} + +.img_enlarged.zoomed { + object-fit: cover; + width: auto; + height: auto; + cursor: zoom-out; +} + +.img_enlarged_container { + display: flex; + flex-direction: column; + justify-content: center; + padding: 10px; + height: 100%; + width: 100%; +} + +.img_enlarged_holder::-webkit-scrollbar-corner { + background-color: transparent; +} + +.img_enlarged_container pre code { + position: relative; + display: block; + overflow-x: auto; + padding: 1em; +} + .cropper-container { max-width: 100% !important; } From 58a85fa0c8a2719e06d0008c50dfc8baca161e72 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:11:00 +0300 Subject: [PATCH 41/58] Remove focus outline from transparent popups --- public/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/style.css b/public/style.css index 55aec739e..e6bc74ed3 100644 --- a/public/style.css +++ b/public/style.css @@ -3140,6 +3140,10 @@ grammarly-extension { border: none; } +.transparent_dialogue_popup:focus-visible { + outline: none; +} + #dialogue_popup .horizontal_scrolling_dialogue_popup { overflow-x: unset !important; } From a161ebfcaf1a345534e7f0af4bd15a17219ba349 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:53:01 +0300 Subject: [PATCH 42/58] Up visibility of close button --- public/css/popup.css | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/public/css/popup.css b/public/css/popup.css index a35e7b96e..bdd530a66 100644 --- a/public/css/popup.css +++ b/public/css/popup.css @@ -133,16 +133,15 @@ dialog { .popup .popup-button-close { position: absolute; - top: -8px; - right: -8px; - width: 21px; - height: 21px; - font-size: 18px; + top: -6px; + right: -6px; + width: 24px; + height: 24px; + font-size: 20px; padding: 2px 3px 3px 2px; - filter: brightness(0.4); + filter: brightness(0.8); /* Fix weird animation issue with font-scaling during popup open */ backface-visibility: hidden; } - From 03cfbca7cfeea2f6efca10c5c1edc8b21b5d2740 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 14:58:08 +0300 Subject: [PATCH 43/58] Distraction-free image zooming --- public/style.css | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/public/style.css b/public/style.css index e6bc74ed3..f00cb6c41 100644 --- a/public/style.css +++ b/public/style.css @@ -4536,6 +4536,29 @@ a { padding: 1em; } +.popup:has(.img_enlarged.zoomed).large_dialogue_popup { + height: 100vh !important; + height: 100svh !important; + max-height: 100vh !important; + max-height: 100svh !important; + max-width: 100vw !important; + max-width: 100svw !important; + padding: 0; +} + +.popup:has(.img_enlarged.zoomed).large_dialogue_popup .popup-content { + margin: 0; + padding: 0; +} + +.popup:has(.img_enlarged.zoomed).large_dialogue_popup .img_enlarged_container pre { + display: none; +} + +.popup:has(.img_enlarged.zoomed).large_dialogue_popup .popup-button-close { + display: none !important; +} + .cropper-container { max-width: 100% !important; } From d0b6243f7782dedb995d3916b6aec268cd588c1a Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:23:03 +0300 Subject: [PATCH 44/58] Add character filter to lastsprite --- public/scripts/extensions/expressions/index.js | 4 ++-- public/scripts/slash-commands.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index eaed37355..98615b6d8 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -2051,7 +2051,7 @@ function migrateSettings() { const isCustom = extension_settings.expressions.custom?.includes(expression); return new SlashCommandEnumValue(expression, null, isCustom ? enumTypes.name : enumTypes.enum, isCustom ? 'C' : 'D'); }), - } + }; SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sprite', @@ -2088,7 +2088,7 @@ function migrateSettings() { description: 'character name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.characters(), + enumProvider: commonEnumProviders.characters('character'), }), ], helpString: 'Returns the last set sprite / expression for the named character.', diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index ef164883e..ed9618721 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -213,7 +213,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }) + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }), }), ], unnamedArgumentList: [ @@ -267,7 +267,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }) + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }), }), ], unnamedArgumentList: [ @@ -452,7 +452,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'at', description: 'position to insert the message', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], - enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }) + enumProvider: commonEnumProviders.messages({ allowIdAfter: true, allowVars: true }), }), SlashCommandNamedArgument.fromProps({ name: 'name', @@ -461,7 +461,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ defaultValue: '{{user}}', enumProvider: () => [ ...commonEnumProviders.characters('character')(), - ...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }) + ...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }), ], }), ], @@ -1302,7 +1302,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ ...commonEnumProviders.injects(), ...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }), ], - }) + }), ], callback: flushInjectsCallback, helpString: 'Removes a script injection for the current chat. If no ID is provided, removes all script injections.', @@ -1310,7 +1310,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'tokens', callback: (_, text) => { - if (text instanceof SlashCommandClosure || Array.isArray(text)) throw new Error('Unnamed argument cannot be a closure for command /tokens') + if (text instanceof SlashCommandClosure || Array.isArray(text)) throw new Error('Unnamed argument cannot be a closure for command /tokens'); return getTokenCountAsync(text).then(count => String(count)); }, returns: 'number of tokens', @@ -1359,7 +1359,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ const promptManager = setupChatCompletionPromptManager(oai_settings); const prompts = promptManager.serviceSettings.prompts; return prompts.map(prompt => new SlashCommandEnumValue(prompt.name, prompt.identifier, enumTypes.enum)); - } + }, }), ], unnamedArgumentList: [ From 5a50ed97be7e90b2d56f61da349b5a718c6266d1 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:27:19 +0300 Subject: [PATCH 45/58] Clean-up /sd command help --- public/scripts/extensions/stable-diffusion/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index d3da529c4..2a759ac99 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -3367,7 +3367,7 @@ jQuery(async () => { ], helpString: `
    - Requests to generate an image and posts it to chat (unless quiet=true argument is specified). Supported arguments: ${Object.values(triggerWords).flat().join(', ')}. + Requests to generate an image and posts it to chat (unless quiet=true argument is specified)..
    Anything else would trigger a "free mode" to make generate whatever you prompted. Example: /imagine apple tree would generate a picture of an apple tree. Returns a link to the generated image. From 278b52689851750e1dc7be242e8f973d182a38e8 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:43:57 +0300 Subject: [PATCH 46/58] Add icon for voice command --- public/scripts/extensions/tts/index.js | 3 ++- .../scripts/slash-commands/SlashCommandCommonEnumsProvider.js | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 2a9b75ffc..32c5f39cb 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -20,6 +20,7 @@ import { SlashCommand } from '../../slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; import { debounce_timeout } from '../../constants.js'; import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; +import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; export { talkingAnimation }; const UPDATE_INTERVAL = 1000; @@ -1216,7 +1217,7 @@ $(document).ready(function () { description: 'character voice name', typeList: [ARGUMENT_TYPE.STRING], isRequired: false, - enumProvider: () => Object.keys(voiceMap).map(voiceName => new SlashCommandEnumValue(voiceName, null, enumTypes.enum, voiceName)), + enumProvider: () => Object.keys(voiceMap).map(voiceName => new SlashCommandEnumValue(voiceName, null, enumTypes.enum, enumIcons.voice)), }), ], unnamedArgumentList: [ diff --git a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js index 87816db90..683f28ffa 100644 --- a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js +++ b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js @@ -31,6 +31,7 @@ export const enumIcons = { preset: '⚙️', file: '📄', message: '💬', + voice: '🎤', true: '✔️', false: '❌', From e2593215bf85a1a9157b2e13a241d4caaa66ba6e Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:44:53 +0300 Subject: [PATCH 47/58] Fix speak command broken --- public/scripts/extensions/tts/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 32c5f39cb..da0d860b1 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -1206,8 +1206,8 @@ $(document).ready(function () { eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, onMessageEvent); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'speak', - callback: () => { - onNarrateText(); + callback: async (args, value) => { + await onNarrateText(args, value); return ''; }, aliases: ['narrate', 'tts'], From 38cc4f789b85991b76d391f11be59c929d5ca87e Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 15:57:07 +0300 Subject: [PATCH 48/58] Fix string quotes --- public/scripts/scrapers.js | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/public/scripts/scrapers.js b/public/scripts/scrapers.js index abf5188dc..86d2ba567 100644 --- a/public/scripts/scrapers.js +++ b/public/scripts/scrapers.js @@ -433,22 +433,23 @@ class FandomScraper { } } -const iso6391Codes = ["aa", "ab", "ae", "af", "ak", "am", "an", "ar", "as", "av", "ay", "az", - "ba", "be", "bg", "bh", "bi", "bm", "bn", "bo", "br", "bs", "ca", "ce", - "ch", "co", "cr", "cs", "cu", "cv", "cy", "da", "de", "dv", "dz", "ee", - "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi", "fj", "fo", "fr", - "fy", "ga", "gd", "gl", "gn", "gu", "gv", "ha", "he", "hi", "ho", "hr", - "ht", "hu", "hy", "hz", "ia", "id", "ie", "ig", "ii", "ik", "io", "is", - "it", "iu", "ja", "jv", "ka", "kg", "ki", "kj", "kk", "kl", "km", "kn", - "ko", "kr", "ks", "ku", "kv", "kw", "ky", "la", "lb", "lg", "li", "ln", - "lo", "lt", "lu", "lv", "mg", "mh", "mi", "mk", "ml", "mn", "mr", "ms", - "mt", "my", "na", "nb", "nd", "ne", "ng", "nl", "nn", "no", "nr", "nv", - "ny", "oc", "oj", "om", "or", "os", "pa", "pi", "pl", "ps", "pt", "qu", - "rm", "rn", "ro", "ru", "rw", "sa", "sc", "sd", "se", "sg", "si", "sk", - "sl", "sm", "sn", "so", "sq", "sr", "ss", "st", "su", "sv", "sw", "ta", - "te", "tg", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts", "tt", "tw", - "ty", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh", "yi", - "yo", "za", "zh", "zu"]; +const iso6391Codes = [ + 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', + 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce', + 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee', + 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', + 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr', + 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', + 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', + 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', + 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms', + 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv', + 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu', + 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk', + 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta', + 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', + 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi', + 'yo', 'za', 'zh', 'zu']; /** * Scrape transcript from a YouTube video. @@ -531,7 +532,7 @@ class YouTubeScraper { } const toast = toastr.info('Working, please wait...'); - const { transcript, id } = await this.getScript(videoUrl, lang); + const { transcript, id } = await this.getScript(String(videoUrl), lang); toastr.clear(toast); const file = new File([transcript], `YouTube - ${id} - ${Date.now()}.txt`, { type: 'text/plain' }); From e7362837858818be1ec808f37f6dccd2e2c6f720 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:24:33 +0300 Subject: [PATCH 49/58] Return cache to WI --- public/scripts/world-info.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 4cb7bc6ca..fbdbd69c1 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -946,9 +946,9 @@ async function loadWorldInfoData(name) { return; } - // if (worldInfoCache[name]) { - // return worldInfoCache[name]; - // } + if (worldInfoCache[name]) { + return worldInfoCache[name]; + } const response = await fetch('/api/worldinfo/get', { method: 'POST', @@ -2563,7 +2563,7 @@ const newEntryDefinition = { }; const newEntryTemplate = Object.fromEntries( - Object.entries(newEntryDefinition).map(([key, value]) => [key, value.default]) + Object.entries(newEntryDefinition).map(([key, value]) => [key, value.default]), ); function createWorldInfoEntry(_name, data) { @@ -2594,7 +2594,7 @@ async function saveWorldInfo(name, data, immediately) { return; } - // delete worldInfoCache[name]; + delete worldInfoCache[name]; if (immediately) { return await _save(name, data); From 2012bb49d288447db7ed998b76456dbd0f412928 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 16:47:07 +0300 Subject: [PATCH 50/58] Make /member command indices 0-based to match autocomplete --- public/scripts/group-chats.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index fbfbd6c31..6cb40914a 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -246,7 +246,7 @@ export function getGroupMembers(groupId = selected_group) { /** * Finds the character ID for a group member. - * @param {string} arg 1-based member index or character name + * @param {string} arg 0-based member index or character name * @returns {number} 0-based character ID */ export function findGroupMemberId(arg) { @@ -264,8 +264,7 @@ export function findGroupMemberId(arg) { return; } - // Index is 1-based - const index = parseInt(arg) - 1; + const index = parseInt(arg); const searchByName = isNaN(index); if (searchByName) { From 6594b3c7fa935dc3323dc4c7cc8265e42ca71532 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:21:55 +0300 Subject: [PATCH 51/58] Inject supports variables for ID --- public/scripts/slash-commands.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index ed9618721..959b931e4 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -1251,9 +1251,17 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'inject', callback: injectCallback, namedArgumentList: [ - new SlashCommandNamedArgument( - 'id', 'injection ID', [ARGUMENT_TYPE.STRING], true, - ), + SlashCommandNamedArgument.fromProps({ + name: 'id', + description: 'injection ID or variable name pointing to ID', + typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME], + isRequired: true, + + enumProvider: () => [ + ...commonEnumProviders.injects(), + ...commonEnumProviders.variables('all')().map(x => { x.description = 'Variable'; return x; }), + ], + }), new SlashCommandNamedArgument( 'position', 'injection position', [ARGUMENT_TYPE.STRING], false, false, 'after', ['before', 'after', 'chat'], ), From f3327c06ab088d0f30a62726295a2c9d0a1f358c Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:39:57 +0300 Subject: [PATCH 52/58] Update trim direction emoji --- public/scripts/slash-commands.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 959b931e4..af0ba4a8f 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -1189,8 +1189,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumList: [ - new SlashCommandEnumValue('start', null, enumTypes.enum, '⬅️'), - new SlashCommandEnumValue('end', null, enumTypes.enum, '➡️'), + new SlashCommandEnumValue('start', null, enumTypes.enum, '⏪'), + new SlashCommandEnumValue('end', null, enumTypes.enum, '⏩'), ], }), ], From eb8f4bebe0121bc0be7643fdd14c83a3f3672069 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:42:00 +0300 Subject: [PATCH 53/58] Argument for API is not required --- public/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index 2bf835cce..62c8eb23a 100644 --- a/public/script.js +++ b/public/script.js @@ -8838,7 +8838,7 @@ jQuery(async function () { SlashCommandArgument.fromProps({ description: 'API to connect to', typeList: [ARGUMENT_TYPE.STRING], - isRequired: true, + isRequired: false, enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(uniqueAPIs.findIndex(x => x === selected)), selected[0].toUpperCase() ?? enumIcons.default)), From a85ac96f8214ceef818229673ecf80f524f7e4f4 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:01:16 +0300 Subject: [PATCH 54/58] Fix /peek typings. Clarify hints for member indices --- public/scripts/slash-commands.js | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index af0ba4a8f..65e4b0bdf 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -522,7 +522,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: hideMessageCallback, unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'message index or range', + description: 'message index (starts with 0) or range', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], isRequired: true, enumProvider: commonEnumProviders.messages(), @@ -535,7 +535,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: unhideMessageCallback, unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'message index or range', + description: 'message index (starts with 0) or range', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], isRequired: true, enumProvider: commonEnumProviders.messages(), @@ -549,7 +549,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['disable', 'disablemember', 'memberdisable'], unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'member index or name', + description: 'member index (starts with 0) or name', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.groupMembers(), @@ -563,7 +563,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: enableGroupMemberCallback, unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'member index or name', + description: 'member index (starts with 0) or name', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.groupMembers(), @@ -603,7 +603,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['removemember', 'memberremove'], unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'member index or name', + description: 'member index (starts with 0) or name', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.groupMembers(), @@ -630,7 +630,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['upmember', 'memberup'], unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'member index or name', + description: 'member index (starts with 0) or name', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.groupMembers(), @@ -644,7 +644,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ aliases: ['downmember', 'memberdown'], unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'member index or name', + description: 'member index (starts with 0) or name', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.groupMembers(), @@ -657,10 +657,10 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ callback: peekCallback, unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'message index or range', - typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], + description: 'member index (starts with 0) or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.messages(), + enumProvider: commonEnumProviders.groupMembers(), }), ], helpString: ` @@ -671,12 +671,8 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ Examples:
    • -
      /peek 5
      - Shows the character card for the 5th message. -
    • -
    • -
      /peek 2-5
      - Shows the character cards for messages 2 through 5. +
      /peek Gloria
      + Shows the character card for the character named "Gloria".
    From b105a2ef24a1a33574443816146c16a463a7f88a Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:10:56 +0300 Subject: [PATCH 55/58] Add missing /trigger unnamed type, fix names for ask and delname --- public/scripts/slash-commands.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 65e4b0bdf..8e7ce0326 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -399,7 +399,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'character name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.characters('all'), + enumProvider: commonEnumProviders.characters('character'), }), ], unnamedArgumentList: [ @@ -418,7 +418,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'name', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, - enumProvider: commonEnumProviders.characters('all'), + enumProvider: commonEnumProviders.characters('character'), }), ], aliases: ['cancel'], @@ -508,6 +508,14 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ 'false', ), ], + unnamedArgumentList: [ + SlashCommandArgument.fromProps({ + description: 'group member index (starts with 0) or name', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], + isRequired: false, + enumProvider: commonEnumProviders.groupMembers(), + }), + ], helpString: `
    Triggers a message generation. If in group, can trigger a message for the specified group member index or name. From bd9c10c2ebc6a3a7557924ba38c69a0c2208e9c5 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:14:10 +0300 Subject: [PATCH 56/58] /messages text consistency --- public/scripts/slash-commands.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 8e7ce0326..f02a6ceeb 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -1061,7 +1061,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'message index or range', + description: 'message index (starts with 0) or range', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.RANGE], isRequired: true, enumProvider: commonEnumProviders.messages(), From 946994af22e4871a9dc0a605f2f8881dd4e0a270 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:16:15 +0300 Subject: [PATCH 57/58] /input should return empty string if canceled --- public/scripts/slash-commands.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index f02a6ceeb..a48805863 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -1799,7 +1799,7 @@ async function inputCallback(args, prompt) { await delay(1); const result = await callGenericPopup(safeValue, POPUP_TYPE.INPUT, defaultInput, popupOptions); await delay(1); - return String(result); + return String(result || ''); } /** From fa9ae4c979ed700076737f44fe3035fd93b77eae Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:28:22 +0300 Subject: [PATCH 58/58] Specify that flushvar supports closures --- public/scripts/variables.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 2a869fc6f..3117f07ec 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -1228,7 +1228,7 @@ export function registerVariableCommands() { new SlashCommandEnumValue('not', '!a'), new SlashCommandEnumValue('in', 'a includes b'), new SlashCommandEnumValue('nin', 'a not includes b'), - ], + ], ), new SlashCommandNamedArgument( 'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false, @@ -1306,7 +1306,7 @@ export function registerVariableCommands() { new SlashCommandEnumValue('not', '!a'), new SlashCommandEnumValue('in', 'a includes b'), new SlashCommandEnumValue('nin', 'a not includes b'), - ], + ], ), new SlashCommandNamedArgument( 'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(), @@ -1400,8 +1400,8 @@ export function registerVariableCommands() { unnamedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', - description: 'variable name', - typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + description: 'variable name or closure that returns a variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.CLOSURE], enumProvider: commonEnumProviders.variables('local'), }), ], @@ -1426,8 +1426,8 @@ export function registerVariableCommands() { unnamedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', - description: 'variable name', - typeList: [ARGUMENT_TYPE.VARIABLE_NAME], + description: 'variable name or closure that returns a variable name', + typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.CLOSURE], enumProvider: commonEnumProviders.variables('global'), }), ],