diff --git a/public/script.js b/public/script.js index cda7adea3..8d085b6d2 100644 --- a/public/script.js +++ b/public/script.js @@ -230,7 +230,7 @@ import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros import { currentUser, setUserControls } from './scripts/user.js'; import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js'; import { renderTemplate, renderTemplateAsync } from './scripts/templates.js'; -import { ScraperManager } from './scripts/scrapers.js'; +import { initScrapers, ScraperManager } from './scripts/scrapers.js'; import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js'; import { SlashCommand } from './scripts/slash-commands/SlashCommand.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js'; @@ -959,6 +959,7 @@ async function firstLoadInit() { initCfg(); initLogprobs(); initInputMarkdown(); + await initScrapers(); doDailyExtensionUpdatesCheck(); await hideLoader(); await fixViewport(); diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index 71be1e422..edb19ff05 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -2,7 +2,7 @@ import { callPopup, eventSource, event_types, generateRaw, getRequestHeaders, ma import { dragElement, isMobile } from '../../RossAscends-mods.js'; import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js'; import { loadMovingUIState, power_user } from '../../power-user.js'; -import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition } from '../../utils.js'; +import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar } from '../../utils.js'; import { hideMutedSprites } from '../../group-chats.js'; import { isJsonSchemaSupported } from '../../textgen-settings.js'; import { debounce_timeout } from '../../constants.js'; @@ -2105,14 +2105,20 @@ function migrateSettings() { })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lastsprite', - callback: (_, value) => lastExpression[String(value).trim()] ?? '', + callback: (_, name) => { + if (typeof name !== 'string') throw new Error('name must be a string'); + const char = findChar({ name: name }); + const sprite = lastExpression[char?.name ?? name] ?? ''; + return sprite; + }, returns: 'the last set sprite / expression for the named character.', unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'character name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.characters('character'), + forceEnum: true, }), ], 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 da617c683..a39634603 100644 --- a/public/scripts/extensions/gallery/index.js +++ b/public/scripts/extensions/gallery/index.js @@ -441,6 +441,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ description: 'character name', typeList: [ARGUMENT_TYPE.STRING], enumProvider: commonEnumProviders.characters('character'), + forceEnum: true, }), SlashCommandNamedArgument.fromProps({ name: 'group', diff --git a/public/scripts/scrapers.js b/public/scripts/scrapers.js index 86d2ba567..5a21dedd0 100644 --- a/public/scripts/scrapers.js +++ b/public/scripts/scrapers.js @@ -13,6 +13,7 @@ import { isValidUrl } from './utils.js'; * @property {string} description * @property {string} iconClass * @property {boolean} iconAvailable + * @property {() => Promise} [init=null] * @property {() => Promise} isAvailable * @property {() => Promise} scrape */ @@ -36,12 +37,16 @@ export class ScraperManager { * Register a scraper to be used by the Data Bank. * @param {Scraper} scraper Instance of a scraper to register */ - static registerDataBankScraper(scraper) { + static async registerDataBankScraper(scraper) { if (ScraperManager.#scrapers.some(s => s.id === scraper.id)) { console.warn(`Scraper with ID ${scraper.id} already registered`); return; } + if (scraper.init) { + await scraper.init(); + } + ScraperManager.#scrapers.push(scraper); } @@ -462,7 +467,9 @@ class YouTubeScraper { this.description = 'Download a transcript from a YouTube video.'; this.iconClass = 'fa-brands fa-youtube'; this.iconAvailable = true; + } + async init() { SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'yt-script', callback: async (args, url) => { @@ -564,9 +571,11 @@ class YouTubeScraper { } } -ScraperManager.registerDataBankScraper(new FileScraper()); -ScraperManager.registerDataBankScraper(new Notepad()); -ScraperManager.registerDataBankScraper(new WebScraper()); -ScraperManager.registerDataBankScraper(new MediaWikiScraper()); -ScraperManager.registerDataBankScraper(new FandomScraper()); -ScraperManager.registerDataBankScraper(new YouTubeScraper()); +export async function initScrapers() { + await ScraperManager.registerDataBankScraper(new FileScraper()); + await ScraperManager.registerDataBankScraper(new Notepad()); + await ScraperManager.registerDataBankScraper(new WebScraper()); + await ScraperManager.registerDataBankScraper(new MediaWikiScraper()); + await ScraperManager.registerDataBankScraper(new FandomScraper()); + await ScraperManager.registerDataBankScraper(new YouTubeScraper()); +} diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index eb81a0218..22531eb15 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -55,7 +55,7 @@ import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockStat import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js'; import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js'; -import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js'; +import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js'; import { registerVariableCommands, resolveVariable } from './variables.js'; import { background_settings } from './backgrounds.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; @@ -68,7 +68,6 @@ import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashComma import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; -import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js'; import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js'; export { @@ -173,21 +172,97 @@ export function initDefaultSlashCommands() { `, })); + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'char-find', + aliases: ['findchar'], + callback: (args, name) => { + if (typeof name !== 'string') throw new Error('name must be a string'); + if (args.preferCurrent instanceof SlashCommandClosure || Array.isArray(args.preferCurrent)) throw new Error('preferCurrent cannot be a closure or array'); + if (args.quiet instanceof SlashCommandClosure || Array.isArray(args.quiet)) throw new Error('quiet cannot be a closure or array'); + + const char = findChar({ name: name, filteredByTags: validateArrayArgString(args.tag, 'tag'), preferCurrentChar: !isFalseBoolean(args.preferCurrent), quiet: isTrueBoolean(args.quiet) }); + return char?.avatar ?? ''; + }, + returns: 'the avatar key (unique identifier) of the character', + namedArgumentList: [ + SlashCommandNamedArgument.fromProps({ + name: 'tag', + description: 'Supply one or more tags to filter down to the correct character for the provided name, if multiple characters have the same name.', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: commonEnumProviders.tags('assigned'), + acceptsMultiple: true, + }), + SlashCommandNamedArgument.fromProps({ + name: 'preferCurrent', + description: 'Prefer current character or characters in a group, if multiple characters match', + typeList: [ARGUMENT_TYPE.BOOLEAN], + defaultValue: 'true', + }), + SlashCommandNamedArgument.fromProps({ + name: 'quiet', + description: 'Do not show warning if multiple charactrers are found', + typeList: [ARGUMENT_TYPE.BOOLEAN], + defaultValue: 'false', + enumProvider: commonEnumProviders.boolean('trueFalse'), + }), + ], + unnamedArgumentList: [ + SlashCommandArgument.fromProps({ + description: 'Character name - or unique character identifier (avatar key)', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: commonEnumProviders.characters('character'), + forceEnum: false, + }), + ], + helpString: ` +
+ Searches for a character and returns its avatar key. +
+
+ This can be used to choose the correct character for something like /sendas or other commands in need of a character name + if you have multiple characters with the same name. +
+
+ Example: +
    +
  • +
    /char-find name="Chloe"
    + Returns the avatar key for "Chloe". +
  • +
  • +
    /search name="Chloe" tag="friend"
    + Returns the avatar key for the character "Chloe" that is tagged with "friend". + This is useful if you for example have multiple characters named "Chloe", and the others are "foe", "goddess", or anything else, + so you can actually select the character you are looking for. +
  • +
+
+ `, + })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sendas', callback: sendMessageAs, namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'name', - description: 'Character name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.characters('character'), forceEnum: false, }), - new SlashCommandNamedArgument( - 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false', - ), + SlashCommandNamedArgument.fromProps({ + name: 'avatar', + description: 'Character avatar override (Can be either avatar key or just the character name to pull the avatar from)', + typeList: [ARGUMENT_TYPE.STRING], + enumProvider: commonEnumProviders.characters('character'), + }), + SlashCommandNamedArgument.fromProps({ + name: 'compact', + description: 'Use compact layout', + typeList: [ARGUMENT_TYPE.BOOLEAN], + defaultValue: 'false', + }), SlashCommandNamedArgument.fromProps({ name: 'at', description: 'position to insert the message (index-based, corresponding to message id). If not set, the message will be inserted at the end of the chat.\nNegative values are accepted and will work similarly to how \'depth\' usually works. For example, -1 will insert the message right before the last message in chat.', @@ -211,6 +286,10 @@ export function initDefaultSlashCommands() {
/sendas name="Chloe" Hello, guys!
will send "Hello, guys!" from "Chloe". +
  • +
    /sendas name="Chloe" avatar="BigBadBoss" Hehehe, I am the big bad evil, fear me.
    + will send a message as the character "Chloe", but utilizing the avatar from a character named "BigBadBoss". +
  • @@ -381,12 +460,14 @@ export function initDefaultSlashCommands() { SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'go', callback: goToCharacterCallback, + returns: 'The character/group name', unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.characters('all'), + forceEnum: true, }), ], helpString: 'Opens up a chat with the character or group by its name', @@ -432,7 +513,7 @@ export function initDefaultSlashCommands() { namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'name', - description: 'character name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.characters('character'), @@ -451,7 +532,7 @@ export function initDefaultSlashCommands() { namedArgumentList: [], unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: commonEnumProviders.characters('character'), @@ -618,7 +699,7 @@ export function initDefaultSlashCommands() { aliases: ['addmember', 'memberadd'], unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'character name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [], @@ -856,7 +937,7 @@ export function initDefaultSlashCommands() { ), SlashCommandNamedArgument.fromProps({ name: 'name', - description: 'in-prompt name for instruct mode', + description: 'in-prompt character name for instruct mode (or unique character identifier (avatar key), which will be used as name)', typeList: [ARGUMENT_TYPE.STRING], defaultValue: 'System', enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)], @@ -2293,7 +2374,8 @@ async function generateCallback(args, value) { setEphemeralStopStrings(resolveVariable(args?.stop)); const name = args?.name; - const result = await generateQuietPrompt(value, quietToLoud, false, '', name, length); + const char = findChar({ name: name }); + const result = await generateQuietPrompt(value, quietToLoud, false, '', char?.name ?? name, length); return result; } catch (err) { console.error('Error on /gen generation', err); @@ -2481,26 +2563,22 @@ async function askCharacter(args, text) { return ''; } - let name = ''; - - if (args?.name) { - name = args.name.trim(); - - if (!name) { - toastr.warning('You must specify a name of the character to ask.'); - return ''; - } + if (!args.name) { + toastr.warning('You must specify a name of the character to ask.'); + return ''; } const prevChId = this_chid; // Find the character - const chId = characters.findIndex((e) => e.name === name || e.avatar === name); - if (!characters[chId] || chId === -1) { + const character = findChar({ name: args?.name }); + if (!character) { toastr.error('Character not found.'); return ''; } + const chId = getCharIndex(character); + if (text) { const mesText = getRegexedString(text.trim(), regex_placement.SLASH_COMMAND); // Sending a message implicitly saves the chat, so this needs to be done before changing the character @@ -2511,19 +2589,9 @@ async function askCharacter(args, text) { // Override character and send a user message setCharacterId(String(chId)); - const character = characters[chId]; - let force_avatar, original_avatar; + const { name, force_avatar, original_avatar } = getNameAndAvatarForMessage(character, args?.name); - if (character && character.avatar !== 'none') { - force_avatar = getThumbnailUrl('avatar', character.avatar); - original_avatar = character.avatar; - } - else { - force_avatar = default_avatar; - original_avatar = default_avatar; - } - - setCharacterName(character.name); + setCharacterName(name); const restoreCharacter = () => { if (String(this_chid) !== String(chId)) { @@ -2541,7 +2609,7 @@ async function askCharacter(args, text) { // Only force the new avatar if the character name is the same // This skips if an error was fired const lastMessage = chat[chat.length - 1]; - if (lastMessage && lastMessage?.name === character.name) { + if (lastMessage && lastMessage?.name === name) { lastMessage.force_avatar = force_avatar; lastMessage.original_avatar = original_avatar; } @@ -2552,7 +2620,7 @@ async function askCharacter(args, text) { // Run generate and restore previous character try { eventSource.once(event_types.MESSAGE_RECEIVED, restoreCharacter); - toastr.info(`Asking ${character.name} something...`); + toastr.info(`Asking ${name} something...`); askResult = await Generate('ask_command'); } catch (error) { restoreCharacter(); @@ -2746,26 +2814,23 @@ async function removeGroupMemberCallback(_, arg) { return ''; } -async function addGroupMemberCallback(_, arg) { +async function addGroupMemberCallback(_, name) { if (!selected_group) { toastr.warning('Cannot run /memberadd command outside of a group chat.'); return ''; } - if (!arg) { + if (!name) { console.warn('WARN: No argument provided for /memberadd command'); return ''; } - arg = arg.trim(); - const chid = findCharacterIndex(arg); - - if (chid === -1) { - console.warn(`WARN: No character found for argument ${arg}`); + const character = findChar({ name: name, preferCurrentChar: false }); + if (!character) { + console.warn(`WARN: No character found for argument ${name}`); return ''; } - const character = characters[chid]; const group = groups.find(x => x.id === selected_group); if (!group || !Array.isArray(group.members)) { @@ -2834,7 +2899,7 @@ function findPersonaByName(name) { } for (const persona of Object.entries(power_user.personas)) { - if (persona[1].toLowerCase() === name.toLowerCase()) { + if (equalsIgnoreCaseAndAccents(persona[1], name)) { return persona[0]; } } @@ -2877,7 +2942,9 @@ async function deleteMessagesByNameCallback(_, name) { return; } - name = name.trim(); + // Search for a matching character to get the real name, or take the name provided + const character = findChar({ name: name }); + name = character?.name || name; const messagesToDelete = []; chat.forEach((value) => { @@ -2906,60 +2973,34 @@ async function deleteMessagesByNameCallback(_, name) { return ''; } -function findCharacterIndex(name) { - const matchTypes = [ - (a, b) => a === b, - (a, b) => a.startsWith(b), - (a, b) => a.includes(b), - ]; - - const exactAvatarMatch = characters.findIndex(x => x.avatar === name); - - if (exactAvatarMatch !== -1) { - return exactAvatarMatch; - } - - for (const matchType of matchTypes) { - const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase())); - if (index !== -1) { - return index; - } - } - - return -1; -} - async function goToCharacterCallback(_, name) { if (!name) { console.warn('WARN: No character name provided for /go command'); return; } - name = name.trim(); - const characterIndex = findCharacterIndex(name); - - if (characterIndex !== -1) { - await openChat(new String(characterIndex)); - setActiveCharacter(characters[characterIndex]?.avatar); + const character = findChar({ name: name }); + if (character) { + const chid = getCharIndex(character); + await openChat(new String(chid)); + setActiveCharacter(character.avatar); setActiveGroup(null); - return characters[characterIndex]?.name; - } else { - const group = groups.find(it => it.name.toLowerCase() == name.toLowerCase()); - if (group) { - await openGroupById(group.id); - setActiveCharacter(null); - setActiveGroup(group.id); - return group.name; - } else { - console.warn(`No matches found for name "${name}"`); - return ''; - } + return character.name; } + const group = groups.find(it => equalsIgnoreCaseAndAccents(it.name, name)); + if (group) { + await openGroupById(group.id); + setActiveCharacter(null); + setActiveGroup(group.id); + return group.name; + } + console.warn(`No matches found for name "${name}"`); + return ''; } -async function openChat(id) { +async function openChat(chid) { resetSelectedGroup(); - setCharacterId(id); + setCharacterId(chid); await delay(1); await reloadCurrentChat(); } @@ -3105,6 +3146,79 @@ async function setNarratorName(_, text) { return ''; } +/** + * Checks if an argument is a string array (or undefined), and if not, throws an error + * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined} arg The named argument to check + * @param {string} name The name of the argument for the error message + * @param {object} [options={}] - The optional arguments + * @param {boolean} [options.allowUndefined=false] - Whether the argument can be undefined + * @throws {Error} If the argument is not an array + * @returns {string[]} + */ +export function validateArrayArgString(arg, name, { allowUndefined = true } = {}) { + if (arg === undefined) { + if (allowUndefined) return undefined; + throw new Error(`Argument "${name}" is undefined, but must be a string array`); + } + if (!Array.isArray(arg)) throw new Error(`Argument "${name}" must be an array`); + if (!arg.every(x => typeof x === 'string')) throw new Error(`Argument "${name}" must be an array of strings`); + return arg; +} + +/** + * Checks if an argument is a string or closure array (or undefined), and if not, throws an error + * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined} arg The named argument to check + * @param {string} name The name of the argument for the error message + * @param {object} [options={}] - The optional arguments + * @param {boolean} [options.allowUndefined=false] - Whether the argument can be undefined + * @throws {Error} If the argument is not an array of strings or closures + * @returns {(string|SlashCommandClosure)[]} + */ +export function validateArrayArg(arg, name, { allowUndefined = true } = {}) { + if (arg === undefined) { + if (allowUndefined) return []; + throw new Error(`Argument "${name}" is undefined, but must be an array of strings or closures`); + } + if (!Array.isArray(arg)) throw new Error(`Argument "${name}" must be an array`); + if (!arg.every(x => typeof x === 'string' || x instanceof SlashCommandClosure)) throw new Error(`Argument "${name}" must be an array of strings or closures`); + return arg; +} + + +/** + * Retrieves the name and avatar information for a message + * + * The name of the character will always have precendence over the one given as argument. If you want to specify a different name for the message, + * explicitly implement this in the code using this. + * + * @param {object?} character - The character object to get the avatar data for + * @param {string?} name - The name to get the avatar data for + * @returns {{name: string, force_avatar: string, original_avatar: string}} An object containing the name for the message, forced avatar URL, and original avatar + */ +export function getNameAndAvatarForMessage(character, name = null) { + const isNeutralCharacter = !character && name2 === neutralCharacterName && name === neutralCharacterName; + const currentChar = characters[this_chid]; + + let force_avatar, original_avatar; + if (character?.avatar === currentChar?.avatar || isNeutralCharacter) { + // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars + } + else if (character && character.avatar !== 'none') { + force_avatar = getThumbnailUrl('avatar', character.avatar); + original_avatar = character.avatar; + } + else { + force_avatar = default_avatar; + original_avatar = default_avatar; + } + + return { + name: character?.name || name, + force_avatar: force_avatar, + original_avatar: original_avatar, + }; +} + export async function sendMessageAs(args, text) { if (!text) { return ''; @@ -3143,26 +3257,18 @@ export async function sendMessageAs(args, text) { const isSystem = bias && !removeMacros(mesText).length; const compact = isTrueBoolean(args?.compact); - const character = characters.find(x => x.avatar === name) ?? characters.find(x => x.name === name); - let force_avatar, original_avatar; + const character = findChar({ name: name }); - const chatCharacter = this_chid !== undefined ? characters[this_chid] : null; - const isNeutralCharacter = !chatCharacter && name2 === neutralCharacterName && name === neutralCharacterName; + const avatarCharacter = args.avatar ? findChar({ name: args.avatar }) : character; + if (args.avatar && !avatarCharacter) { + toastr.warning(`Character for avatar ${args.avatar} not found`); + return ''; + } - if (chatCharacter === character || isNeutralCharacter) { - // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars - } - else if (character && character.avatar !== 'none') { - force_avatar = getThumbnailUrl('avatar', character.avatar); - original_avatar = character.avatar; - } - else { - force_avatar = default_avatar; - original_avatar = default_avatar; - } + const { name: avatarCharName, force_avatar, original_avatar } = getNameAndAvatarForMessage(avatarCharacter, name); const message = { - name: name, + name: character?.name || name || avatarCharName, is_user: false, is_system: isSystem, send_date: getMessageTimeStamp(), diff --git a/public/scripts/slash-commands/SlashCommand.js b/public/scripts/slash-commands/SlashCommand.js index 5f8726ba0..c56803249 100644 --- a/public/scripts/slash-commands/SlashCommand.js +++ b/public/scripts/slash-commands/SlashCommand.js @@ -15,13 +15,13 @@ import { SlashCommandScope } from './SlashCommandScope.js'; * _abortController:SlashCommandAbortController, * _debugController:SlashCommandDebugController, * _hasUnnamedArgument:boolean, - * [id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[], + * [id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined, * }} NamedArguments */ /** * Alternative object for local JSDocs, where you don't need existing pipe, scope, etc. arguments - * @typedef {{[id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]}} NamedArgumentsCapture + * @typedef {{[id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined}} NamedArgumentsCapture */ /** diff --git a/public/scripts/slash-commands/SlashCommandClosure.js b/public/scripts/slash-commands/SlashCommandClosure.js index 45c4d48ba..0ba1726b5 100644 --- a/public/scripts/slash-commands/SlashCommandClosure.js +++ b/public/scripts/slash-commands/SlashCommandClosure.js @@ -508,6 +508,14 @@ export class SlashCommandClosure { return v; }); } + + value ??= ''; + + // Make sure that if unnamed args are split, it should always return an array + if (executor.command.splitUnnamedArgument && !Array.isArray(value)) { + value = [value]; + } + return value; } diff --git a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js index 5612f47b5..5a40965c1 100644 --- a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js +++ b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js @@ -2,7 +2,7 @@ import { chat_metadata, characters, substituteParams, chat, extension_prompt_rol import { extension_settings } from '../extensions.js'; import { getGroupMembers, groups } from '../group-chats.js'; import { power_user } from '../power-user.js'; -import { searchCharByName, getTagsList, tags } from '../tags.js'; +import { searchCharByName, getTagsList, tags, tag_map } from '../tags.js'; import { world_names } from '../world-info.js'; import { SlashCommandClosure } from './SlashCommandClosure.js'; import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js'; @@ -152,6 +152,35 @@ export const commonEnumProviders = { ].filter((item, idx, list)=>idx == list.findIndex(it=>it.value == item.value)); }, + /** + * Enum values for numbers and variable names + * + * Includes all variable names and the ability to specify any number + * + * @param {SlashCommandExecutor} executor - The executor of the slash command + * @param {SlashCommandScope} scope - The scope of the slash command + * @returns {SlashCommandEnumValue[]} The enum values + */ + numbersAndVariables: (executor, scope) => [ + ...commonEnumProviders.variables('all')(executor, scope), + new SlashCommandEnumValue( + 'any variable name', + null, + enumTypes.variable, + enumIcons.variable, + (input) => /^\w*$/.test(input), + (input) => input, + ), + new SlashCommandEnumValue( + 'any number', + null, + enumTypes.number, + enumIcons.number, + (input) => input == '' || !Number.isNaN(Number(input)), + (input) => input, + ), + ], + /** * All possible char entities, like characters and groups. Can be filtered down to just one type. * @@ -181,6 +210,18 @@ export const commonEnumProviders = { */ personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)), + /** + * All possible tags, or only those that have been assigned + * + * @param {('all' | 'assigned')} [mode='all'] - Which types of tags to show + * @returns {() => SlashCommandEnumValue[]} + */ + tags: (mode = 'all') => () => { + let assignedTags = mode === 'assigned' ? new Set(Object.values(tag_map).flat()) : new Set(); + return tags.filter(tag => mode === 'all' || (mode === 'assigned' && assignedTags.has(tag.id))) + .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag)); + }, + /** * All possible tags for a given char/group entity * @@ -193,7 +234,7 @@ export const commonEnumProviders = { 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)) + return tags.filter(it => mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it)) .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag)); }, diff --git a/public/scripts/tags.js b/public/scripts/tags.js index c56e0d1cb..9a1950d0a 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -15,7 +15,7 @@ import { import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js'; import { groupCandidatesFilter, groups, selected_group } from './group-chats.js'; -import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight, equalsIgnoreCaseAndAccents, includesIgnoreCaseAndAccents, removeFromArray, getFreeName, debounce } from './utils.js'; +import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight, equalsIgnoreCaseAndAccents, includesIgnoreCaseAndAccents, removeFromArray, getFreeName, debounce, findChar } from './utils.js'; import { power_user } from './power-user.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; @@ -50,7 +50,6 @@ export { removeTagFromMap, }; -/** @typedef {import('../scripts/popup.js').Popup} Popup */ /** @typedef {import('../script.js').Character} Character */ const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter'; @@ -507,7 +506,7 @@ export function getTagKeyForEntityElement(element) { */ export function searchCharByName(charName, { suppressLogging = false } = {}) { const entity = charName - ? (characters.find(x => x.name === charName) || groups.find(x => x.name == charName)) + ? (findChar({ name: charName }) || groups.find(x => equalsIgnoreCaseAndAccents(x.name, charName))) : (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]); const key = getTagKeyForEntity(entity); if (!key) { @@ -1861,8 +1860,9 @@ function registerTagsSlashCommands() { return String(result); }, namedArgumentList: [ - SlashCommandNamedArgument.fromProps({ name: 'name', - description: 'Character name', + SlashCommandNamedArgument.fromProps({ + name: 'name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', enumProvider: commonEnumProviders.characters(), @@ -1907,7 +1907,7 @@ function registerTagsSlashCommands() { }, namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'name', - description: 'Character name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', enumProvider: commonEnumProviders.characters(), @@ -1950,7 +1950,7 @@ function registerTagsSlashCommands() { namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'name', - description: 'Character name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', enumProvider: commonEnumProviders.characters(), @@ -1993,7 +1993,7 @@ function registerTagsSlashCommands() { namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'name', - description: 'Character name', + description: 'Character name - or unique character identifier (avatar key)', typeList: [ARGUMENT_TYPE.STRING], defaultValue: '{{char}}', enumProvider: commonEnumProviders.characters(), diff --git a/public/scripts/utils.js b/public/scripts/utils.js index 9fc1e1e99..6cf92f5c0 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -1,10 +1,12 @@ import { getContext } from './extensions.js'; -import { getRequestHeaders } from '../script.js'; +import { characters, getRequestHeaders, this_chid } from '../script.js'; import { isMobile } from './RossAscends-mods.js'; import { collapseNewlines } from './power-user.js'; import { debounce_timeout } from './constants.js'; import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; +import { getTagsList } from './tags.js'; +import { groups, selected_group } from './group-chats.js'; /** * Pagination status string template. @@ -2110,3 +2112,74 @@ export async function showFontAwesomePicker(customList = null) { } return null; } + +/** + * Finds a character by name, with optional filtering and precedence for avatars + * @param {object} [options={}] - The options for the search + * @param {string?} [options.name=null] - The name to search for + * @param {boolean} [options.allowAvatar=true] - Whether to allow searching by avatar + * @param {boolean} [options.insensitive=true] - Whether the search should be case insensitive + * @param {string[]?} [options.filteredByTags=null] - Tags to filter characters by + * @param {boolean} [options.preferCurrentChar=true] - Whether to prefer the current character(s) + * @param {boolean} [options.quiet=false] - Whether to suppress warnings + * @returns {any?} - The found character or null if not found + */ +export function findChar({ name = null, allowAvatar = true, insensitive = true, filteredByTags = null, preferCurrentChar = true, quiet = false } = {}) { + const matches = (char) => !name || (allowAvatar && char.avatar === name) || (insensitive ? equalsIgnoreCaseAndAccents(char.name, name) : char.name === name); + + // Filter characters by tags if provided + let filteredCharacters = characters; + if (filteredByTags) { + filteredCharacters = characters.filter(char => { + const charTags = getTagsList(char.avatar, false); + return filteredByTags.every(tagName => charTags.some(x => x.name == tagName)); + }); + } + + // Get the current character(s) + /** @type {any[]} */ + const currentChars = selected_group ? groups.find(group => group.id === selected_group)?.members.map(member => filteredCharacters.find(char => char.avatar === member)) + : filteredCharacters.filter(char => characters[this_chid]?.avatar === char.avatar); + + // If we have a current char and prefer it, return that if it matches + if (preferCurrentChar) { + const preferredCharSearch = currentChars.filter(matches); + if (preferredCharSearch.length > 1) { + if (!quiet) toastr.warning('Multiple characters found for given conditions.'); + else console.warn('Multiple characters found for given conditions. Returning the first match.'); + } + if (preferredCharSearch.length) { + return preferredCharSearch[0]; + } + } + + // If allowAvatar is true, search by avatar first + if (allowAvatar && name) { + const characterByAvatar = filteredCharacters.find(char => char.avatar === name); + if (characterByAvatar) { + return characterByAvatar; + } + } + + // Search for matching characters by name + const matchingCharacters = name ? filteredCharacters.filter(matches) : filteredCharacters; + if (matchingCharacters.length > 1) { + if (!quiet) toastr.warning('Multiple characters found for given conditions.'); + else console.warn('Multiple characters found for given conditions. Returning the first match.'); + } + + return matchingCharacters[0] || null; +} + +/** + * Gets the index of a character based on the character object + * @param {object} char - The character object to find the index for + * @throws {Error} If the character is not found + * @returns {number} The index of the character in the characters array + */ +export function getCharIndex(char) { + if (!char) throw new Error('Character is undefined'); + const index = characters.findIndex(c => c.avatar === char.avatar); + if (index === -1) throw new Error(`Character not found: ${char.avatar}`); + return index; +} diff --git a/public/scripts/variables.js b/public/scripts/variables.js index e9cd8c653..7c9830a36 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -669,8 +669,8 @@ function deleteGlobalVariable(name) { } /** - * Parses a series of numeric values from a string. - * @param {string} value A space-separated list of numeric values or variable names + * Parses a series of numeric values from a string or a string array. + * @param {string|string[]} value A space-separated list of numeric values or variable names * @param {SlashCommandScope} scope Scope * @returns {number[]} An array of numeric values */ @@ -679,11 +679,17 @@ function parseNumericSeries(value, scope = null) { return [value]; } - const array = value - .split(' ') - .map(i => i.trim()) + /** @type {(string|number)[]} */ + let values = Array.isArray(value) ? value : value.split(' '); + + // If a JSON array was provided as the only value, convert it to an array + if (values.length === 1 && typeof values[0] === 'string' && values[0].startsWith('[')) { + values = convertValueType(values[0], 'array'); + } + + const array = values.map(i => typeof i === 'string' ? i.trim() : i) .filter(i => i !== '') - .map(i => isNaN(Number(i)) ? Number(resolveVariable(i, scope)) : Number(i)) + .map(i => isNaN(Number(i)) ? Number(resolveVariable(String(i), scope)) : Number(i)) .filter(i => !isNaN(i)); return array; @@ -703,7 +709,7 @@ function performOperation(value, operation, singleOperand = false, scope = null) const result = singleOperand ? operation(array[0]) : operation(array); - if (isNaN(result) || !isFinite(result)) { + if (isNaN(result)) { return 0; } @@ -731,7 +737,7 @@ function maxValuesCallback(args, value) { } function subValuesCallback(args, value) { - return performOperation(value, (array) => array[0] - array[1], false, args._scope); + return performOperation(value, (array) => array.reduce((a, b) => a - b, array.shift() ?? 0), false, args._scope); } function divValuesCallback(args, value) { @@ -1595,36 +1601,15 @@ export function registerVariableCommands() { })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'add', - callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')), + callback: (args, value) => addValuesCallback(args, value), returns: 'sum of the provided values', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'values to sum', - typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST], isRequired: true, acceptsMultiple: true, - enumProvider: (executor, scope) => { - const vars = commonEnumProviders.variables('all')(executor, scope); - vars.push( - new SlashCommandEnumValue( - 'any variable name', - null, - enumTypes.variable, - enumIcons.variable, - (input) => /^\w*$/.test(input), - (input) => input, - ), - new SlashCommandEnumValue( - 'any number', - null, - enumTypes.number, - enumIcons.number, - (input) => input == '' || !Number.isNaN(Number(input)), - (input) => input, - ), - ); - return vars; - }, + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], @@ -1632,7 +1617,9 @@ export function registerVariableCommands() { helpString: `
    Performs an addition of the set of values and passes the result down the pipe. - Can use variable names. +
    +
    + Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
    Example: @@ -1640,6 +1627,9 @@ export function registerVariableCommands() {
  • /add 10 i 30 j
  • +
  • +
    /add ["count", 15, 2, "i"]
    +
  • `, @@ -1651,16 +1641,20 @@ export function registerVariableCommands() { unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'values to multiply', - typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST], isRequired: true, acceptsMultiple: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], + splitUnnamedArgument: true, helpString: `
    - Performs a multiplication of the set of values and passes the result down the pipe. Can use variable names. + Performs a multiplication of the set of values and passes the result down the pipe. +
    +
    + Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
    Examples: @@ -1668,6 +1662,9 @@ export function registerVariableCommands() {
  • /mul 10 i 30 j
  • +
  • +
    /mul ["count", 15, 2, "i"]
    +
  • `, @@ -1679,16 +1676,20 @@ export function registerVariableCommands() { unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'values to find the max', - typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST], isRequired: true, acceptsMultiple: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], + splitUnnamedArgument: true, helpString: `
    - Returns the maximum value of the set of values and passes the result down the pipe. Can use variable names. + Returns the maximum value of the set of values and passes the result down the pipe. +
    +
    + Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
    Examples: @@ -1696,6 +1697,9 @@ export function registerVariableCommands() {
  • /max 10 i 30 j
  • +
  • +
    /max ["count", 15, 2, "i"]
    +
  • `, @@ -1707,17 +1711,20 @@ export function registerVariableCommands() { unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'values to find the min', - typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST], isRequired: true, acceptsMultiple: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], + splitUnnamedArgument: true, helpString: `
    Returns the minimum value of the set of values and passes the result down the pipe. - Can use variable names. +
    +
    + Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
    Example: @@ -1725,6 +1732,9 @@ export function registerVariableCommands() {
  • /min 10 i 30 j
  • +
  • +
    /min ["count", 15, 2, "i"]
    +
  • `, @@ -1735,18 +1745,21 @@ export function registerVariableCommands() { returns: 'difference of the provided values', unnamedArgumentList: [ SlashCommandArgument.fromProps({ - description: 'values to find the difference', - typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], + description: 'values to subtract, starting form the first provided value', + typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST], isRequired: true, acceptsMultiple: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], + splitUnnamedArgument: true, helpString: `
    Performs a subtraction of the set of values and passes the result down the pipe. - Can use variable names. +
    +
    + Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
    Example: @@ -1754,6 +1767,9 @@ export function registerVariableCommands() {
  • /sub i 5
  • +
  • +
    /sub ["count", 4, "i"]
    +
  • `, @@ -1767,17 +1783,18 @@ export function registerVariableCommands() { description: 'dividend', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), SlashCommandArgument.fromProps({ description: 'divisor', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], + splitUnnamedArgument: true, helpString: `
    Performs a division of two values and passes the result down the pipe. @@ -1802,17 +1819,18 @@ export function registerVariableCommands() { description: 'dividend', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), SlashCommandArgument.fromProps({ description: 'divisor', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], + splitUnnamedArgument: true, helpString: `
    Performs a modulo operation of two values and passes the result down the pipe. @@ -1837,17 +1855,18 @@ export function registerVariableCommands() { description: 'base', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), SlashCommandArgument.fromProps({ description: 'exponent', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], + splitUnnamedArgument: true, helpString: `
    Performs a power operation of two values and passes the result down the pipe. @@ -1872,7 +1891,7 @@ export function registerVariableCommands() { description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], @@ -1900,7 +1919,7 @@ export function registerVariableCommands() { description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], @@ -1929,7 +1948,7 @@ export function registerVariableCommands() { description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], @@ -1957,7 +1976,7 @@ export function registerVariableCommands() { description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], @@ -1985,7 +2004,7 @@ export function registerVariableCommands() { description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], @@ -2013,7 +2032,7 @@ export function registerVariableCommands() { description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, - enumProvider: commonEnumProviders.variables('all'), + enumProvider: commonEnumProviders.numbersAndVariables, forceEnum: false, }), ], diff --git a/src/endpoints/backends/chat-completions.js b/src/endpoints/backends/chat-completions.js index a551f8fbc..ca86db051 100644 --- a/src/endpoints/backends/chat-completions.js +++ b/src/endpoints/backends/chat-completions.js @@ -323,7 +323,7 @@ async function sendMakerSuiteRequest(request, response) { ? (stream ? 'streamGenerateContent' : 'generateContent') : (isText ? 'generateText' : 'generateMessage'); - const generateResponse = await fetch(`${apiUrl.origin}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, { + const generateResponse = await fetch(`${apiUrl}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, { body: JSON.stringify(body), method: 'POST', headers: {