Merge branch 'staging' into send-commands-return-value

d82dc4952bca39b7eb88a5a64c5e3d7a3095fe61

Cohee <18619528+Cohee1207@users.noreply.github.com>

12 files changed, +446 -182Showing whitespace changes
public/script.js+2 -1
@@ -230,7 +230,7 @@ import { MacrosParser, evaluateMacros, getLastMessageId } from './scripts/macros
230230import { currentUser, setUserControls } from './scripts/user.js';
231231import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js';
232232import { renderTemplate, renderTemplateAsync } from './scripts/templates.js';
233233import { initScrapers, ScraperManager } from './scripts/scrapers.js';
234234import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js';
235235import { SlashCommand } from './scripts/slash-commands/SlashCommand.js';
236236import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js';
@@ -959,6 +959,7 @@ async function firstLoadInit() {
959959 initCfg();
960960 initLogprobs();
961961 initInputMarkdown();
962+ await initScrapers();
962963 doDailyExtensionUpdatesCheck();
963964 await hideLoader();
964965 await fixViewport();
public/scripts/extensions/expressions/index.js+9 -3
@@ -2,7 +2,7 @@ import { callPopup, eventSource, event_types, generateRaw, getRequestHeaders, ma
22import { dragElement, isMobile } from '../../RossAscends-mods.js';
33import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
44import { loadMovingUIState, power_user } from '../../power-user.js';
55import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar } from '../../utils.js';
66import { hideMutedSprites } from '../../group-chats.js';
77import { isJsonSchemaSupported } from '../../textgen-settings.js';
88import { debounce_timeout } from '../../constants.js';
@@ -2107,14 +2107,20 @@ function migrateSettings() {
21072107 }));
21082108 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
21092109 name: 'lastsprite',
21102110 callback: (_, valuename) => lastExpression[String(value).trim()] ?? '',{
2111+ if (typeof name !== 'string') throw new Error('name must be a string');
2112+ const char = findChar({ name: name });
2113+ const sprite = lastExpression[char?.name ?? name] ?? '';
2114+ return sprite;
2115+ },
21112116 returns: 'the last set sprite / expression for the named character.',
21122117 unnamedArgumentList: [
21132118 SlashCommandArgument.fromProps({
21142119 description: 'characterCharacter name - or unique character identifier (avatar key)',
21152120 typeList: [ARGUMENT_TYPE.STRING],
21162121 isRequired: true,
21172122 enumProvider: commonEnumProviders.characters('character'),
2123+ forceEnum: true,
21182124 }),
21192125 ],
21202126 helpString: 'Returns the last set sprite / expression for the named character.',
public/scripts/extensions/gallery/index.js+1 -0
@@ -441,6 +441,7 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
441441 description: 'character name',
442442 typeList: [ARGUMENT_TYPE.STRING],
443443 enumProvider: commonEnumProviders.characters('character'),
444+ forceEnum: true,
444445 }),
445446 SlashCommandNamedArgument.fromProps({
446447 name: 'group',
public/scripts/scrapers.js+16 -7
@@ -13,6 +13,7 @@ import { isValidUrl } from './utils.js';
1313 * @property {string} description
1414 * @property {string} iconClass
1515 * @property {boolean} iconAvailable
16+ * @property {() => Promise<void>} [init=null]
1617 * @property {() => Promise<boolean>} isAvailable
1718 * @property {() => Promise<File[]>} scrape
1819 */
@@ -36,12 +37,16 @@ export class ScraperManager {
3637 * Register a scraper to be used by the Data Bank.
3738 * @param {Scraper} scraper Instance of a scraper to register
3839 */
3940 static async registerDataBankScraper(scraper) {
4041 if (ScraperManager.#scrapers.some(s => s.id === scraper.id)) {
4142 console.warn(`Scraper with ID ${scraper.id} already registered`);
4243 return;
4344 }
4445
46+ if (scraper.init) {
47+ await scraper.init();
48+ }
49+
4550 ScraperManager.#scrapers.push(scraper);
4651 }
4752
@@ -462,7 +467,9 @@ class YouTubeScraper {
462467 this.description = 'Download a transcript from a YouTube video.';
463468 this.iconClass = 'fa-brands fa-youtube';
464469 this.iconAvailable = true;
470+ }
465471
472+ async init() {
466473 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
467474 name: 'yt-script',
468475 callback: async (args, url) => {
@@ -564,9 +571,11 @@ class YouTubeScraper {
564571 }
565572}
566573
567-ScraperManager.registerDataBankScraper(new FileScraper());
574+export async function initScrapers() {
568575 await ScraperManager.registerDataBankScraper(new NotepadFileScraper());
569576 await ScraperManager.registerDataBankScraper(new WebScraperNotepad());
570577 await ScraperManager.registerDataBankScraper(new MediaWikiScraperWebScraper());
571578 await ScraperManager.registerDataBankScraper(new FandomScraperMediaWikiScraper());
572579 await ScraperManager.registerDataBankScraper(new YouTubeScraperFandomScraper());
580+ await ScraperManager.registerDataBankScraper(new YouTubeScraper());
581+}
public/scripts/slash-commands.js+205 -99
@@ -55,7 +55,7 @@ import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockStat
5555import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
5656import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
5757import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';
5858import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
5959import { registerVariableCommands, resolveVariable } from './variables.js';
6060import { background_settings } from './backgrounds.js';
6161import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
@@ -68,7 +68,6 @@ import { SlashCommandNamedArgumentAssignment } from './slash-commands/SlashComma
6868import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
6969import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
7070import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
71-import { SlashCommandDebugController } from './slash-commands/SlashCommandDebugController.js';
7271import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
7372import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
7473import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
@@ -175,21 +174,97 @@ export function initDefaultSlashCommands() {
175174 `,
176175 }));
177176 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
177+ name: 'char-find',
178+ aliases: ['findchar'],
179+ callback: (args, name) => {
180+ if (typeof name !== 'string') throw new Error('name must be a string');
181+ if (args.preferCurrent instanceof SlashCommandClosure || Array.isArray(args.preferCurrent)) throw new Error('preferCurrent cannot be a closure or array');
182+ if (args.quiet instanceof SlashCommandClosure || Array.isArray(args.quiet)) throw new Error('quiet cannot be a closure or array');
183+
184+ const char = findChar({ name: name, filteredByTags: validateArrayArgString(args.tag, 'tag'), preferCurrentChar: !isFalseBoolean(args.preferCurrent), quiet: isTrueBoolean(args.quiet) });
185+ return char?.avatar ?? '';
186+ },
187+ returns: 'the avatar key (unique identifier) of the character',
188+ namedArgumentList: [
189+ SlashCommandNamedArgument.fromProps({
190+ name: 'tag',
191+ description: 'Supply one or more tags to filter down to the correct character for the provided name, if multiple characters have the same name.',
192+ typeList: [ARGUMENT_TYPE.STRING],
193+ enumProvider: commonEnumProviders.tags('assigned'),
194+ acceptsMultiple: true,
195+ }),
196+ SlashCommandNamedArgument.fromProps({
197+ name: 'preferCurrent',
198+ description: 'Prefer current character or characters in a group, if multiple characters match',
199+ typeList: [ARGUMENT_TYPE.BOOLEAN],
200+ defaultValue: 'true',
201+ }),
202+ SlashCommandNamedArgument.fromProps({
203+ name: 'quiet',
204+ description: 'Do not show warning if multiple charactrers are found',
205+ typeList: [ARGUMENT_TYPE.BOOLEAN],
206+ defaultValue: 'false',
207+ enumProvider: commonEnumProviders.boolean('trueFalse'),
208+ }),
209+ ],
210+ unnamedArgumentList: [
211+ SlashCommandArgument.fromProps({
212+ description: 'Character name - or unique character identifier (avatar key)',
213+ typeList: [ARGUMENT_TYPE.STRING],
214+ enumProvider: commonEnumProviders.characters('character'),
215+ forceEnum: false,
216+ }),
217+ ],
218+ helpString: `
219+ <div>
220+ Searches for a character and returns its avatar key.
221+ </div>
222+ <div>
223+ This can be used to choose the correct character for something like <code>/sendas</code> or other commands in need of a character name
224+ if you have multiple characters with the same name.
225+ </div>
226+ <div>
227+ <strong>Example:</strong>
228+ <ul>
229+ <li>
230+ <pre><code>/char-find name="Chloe"</code></pre>
231+ Returns the avatar key for "Chloe".
232+ </li>
233+ <li>
234+ <pre><code>/search name="Chloe" tag="friend"</code></pre>
235+ Returns the avatar key for the character "Chloe" that is tagged with "friend".
236+ This is useful if you for example have multiple characters named "Chloe", and the others are "foe", "goddess", or anything else,
237+ so you can actually select the character you are looking for.
238+ </li>
239+ </ul>
240+ </div>
241+ `,
242+ }));
243+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
178244 name: 'sendas',
179245 callback: sendMessageAs,
180246 returns: 'Optionally the text of the sent message, if specified in the "return" argument',
181247 namedArgumentList: [
182248 SlashCommandNamedArgument.fromProps({
183249 name: 'name',
184250 description: 'Character name - or unique character identifier (avatar key)',
185251 typeList: [ARGUMENT_TYPE.STRING],
186252 isRequired: true,
187253 enumProvider: commonEnumProviders.characters('character'),
188254 forceEnum: false,
189255 }),
190256 new SlashCommandNamedArgument.fromProps({
191- 'compact', 'Use compact layout', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false',
257+ name: 'avatar',
192- ),
258+ description: 'Character avatar override (Can be either avatar key or just the character name to pull the avatar from)',
259+ typeList: [ARGUMENT_TYPE.STRING],
260+ enumProvider: commonEnumProviders.characters('character'),
261+ }),
262+ SlashCommandNamedArgument.fromProps({
263+ name: 'compact',
264+ description: 'Use compact layout',
265+ typeList: [ARGUMENT_TYPE.BOOLEAN],
266+ defaultValue: 'false',
267+ }),
193268 SlashCommandNamedArgument.fromProps({
194269 name: 'at',
195270 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.',
@@ -221,6 +296,10 @@ export function initDefaultSlashCommands() {
221296 <pre><code>/sendas name="Chloe" Hello, guys!</code></pre>
222297 will send "Hello, guys!" from "Chloe".
223298 </li>
299+ <li>
300+ <pre><code>/sendas name="Chloe" avatar="BigBadBoss" Hehehe, I am the big bad evil, fear me.</code></pre>
301+ will send a message as the character "Chloe", but utilizing the avatar from a character named "BigBadBoss".
302+ </li>
224303 </ul>
225304 </div>
226305 <div>
@@ -409,12 +488,14 @@ export function initDefaultSlashCommands() {
409488 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
410489 name: 'go',
411490 callback: goToCharacterCallback,
491+ returns: 'The character/group name',
412492 unnamedArgumentList: [
413493 SlashCommandArgument.fromProps({
414- description: 'name',
494+ description: 'Character name - or unique character identifier (avatar key)',
415495 typeList: [ARGUMENT_TYPE.STRING],
416496 isRequired: true,
417497 enumProvider: commonEnumProviders.characters('all'),
498+ forceEnum: true,
418499 }),
419500 ],
420501 helpString: 'Opens up a chat with the character or group by its name',
@@ -460,7 +541,7 @@ export function initDefaultSlashCommands() {
460541 namedArgumentList: [
461542 SlashCommandNamedArgument.fromProps({
462543 name: 'name',
463544 description: 'characterCharacter name - or unique character identifier (avatar key)',
464545 typeList: [ARGUMENT_TYPE.STRING],
465546 isRequired: true,
466547 enumProvider: commonEnumProviders.characters('character'),
@@ -487,7 +568,7 @@ export function initDefaultSlashCommands() {
487568 namedArgumentList: [],
488569 unnamedArgumentList: [
489570 SlashCommandArgument.fromProps({
490- description: 'name',
571+ description: 'Character name - or unique character identifier (avatar key)',
491572 typeList: [ARGUMENT_TYPE.STRING],
492573 isRequired: true,
493574 enumProvider: commonEnumProviders.characters('character'),
@@ -663,7 +744,7 @@ export function initDefaultSlashCommands() {
663744 aliases: ['addmember', 'memberadd'],
664745 unnamedArgumentList: [
665746 SlashCommandArgument.fromProps({
666747 description: 'characterCharacter name - or unique character identifier (avatar key)',
667748 typeList: [ARGUMENT_TYPE.STRING],
668749 isRequired: true,
669750 enumProvider: () => selected_group ? commonEnumProviders.characters('character')() : [],
@@ -901,7 +982,7 @@ export function initDefaultSlashCommands() {
901982 ),
902983 SlashCommandNamedArgument.fromProps({
903984 name: 'name',
904985 description: 'in-prompt character name for instruct mode (or unique character identifier (avatar key), which will be used as name)',
905986 typeList: [ARGUMENT_TYPE.STRING],
906987 defaultValue: 'System',
907988 enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],
@@ -2353,7 +2434,8 @@ async function generateCallback(args, value) {
23532434
23542435 setEphemeralStopStrings(resolveVariable(args?.stop));
23552436 const name = args?.name;
23562437 const resultchar = await generateQuietPromptfindChar(value, quietToLoud, false,{ '',name: name, length});
2438+ const result = await generateQuietPrompt(value, quietToLoud, false, '', char?.name ?? name, length);
23572439 return result;
23582440 } catch (err) {
23592441 console.error('Error on /gen generation', err);
@@ -2541,26 +2623,22 @@ async function askCharacter(args, text) {
25412623 return '';
25422624 }
25432625
2544- let name = '';
2626+ if (!args.name) {
2545-
2546- if (args?.name) {
2547- name = args.name.trim();
2548-
2549- if (!name) {
25502627 toastr.warning('You must specify a name of the character to ask.');
25512628 return '';
25522629 }
2553- }
25542630
25552631 const prevChId = this_chid;
25562632
25572633 // Find the character
2558- const chId = characters.findIndex((e) => e.name === name || e.avatar === name);
2634+ const character = findChar({ name: args?.name });
2559- if (!characters[chId] || chId === -1) {
2635+ if (!character) {
25602636 toastr.warningerror('Character not found.');
25612637 return '';
25622638 }
25632639
2640+ const chId = getCharIndex(character);
2641+
25642642 if (text) {
25652643 const mesText = getRegexedString(text.trim(), regex_placement.SLASH_COMMAND);
25662644 // Sending a message implicitly saves the chat, so this needs to be done before changing the character
@@ -2571,19 +2649,9 @@ async function askCharacter(args, text) {
25712649 // Override character and send a user message
25722650 setCharacterId(String(chId));
25732651
2574- const character = characters[chId];
2652+ const { name, force_avatar, original_avatar } = getNameAndAvatarForMessage(character, args?.name);
2575- let force_avatar, original_avatar;
25762653
2577- if (character && character.avatar !== 'none') {
2654+ setCharacterName(name);
2578- force_avatar = getThumbnailUrl('avatar', character.avatar);
2579- original_avatar = character.avatar;
2580- }
2581- else {
2582- force_avatar = default_avatar;
2583- original_avatar = default_avatar;
2584- }
2585-
2586- setCharacterName(character.name);
25872655
25882656 const restoreCharacter = () => {
25892657 if (String(this_chid) !== String(chId)) {
@@ -2601,7 +2669,7 @@ async function askCharacter(args, text) {
26012669 // Only force the new avatar if the character name is the same
26022670 // This skips if an error was fired
26032671 const lastMessage = chat[chat.length - 1];
26042672 if (lastMessage && lastMessage?.name === character.name) {
26052673 lastMessage.force_avatar = force_avatar;
26062674 lastMessage.original_avatar = original_avatar;
26072675 }
@@ -2612,7 +2680,7 @@ async function askCharacter(args, text) {
26122680 // Run generate and restore previous character
26132681 try {
26142682 eventSource.once(event_types.MESSAGE_RECEIVED, restoreCharacter);
26152683 toastr.info(`Asking ${character.name} something...`);
26162684 askResult = await Generate('ask_command');
26172685 } catch (error) {
26182686 restoreCharacter();
@@ -2808,26 +2876,23 @@ async function removeGroupMemberCallback(_, arg) {
28082876 return '';
28092877}
28102878
28112879async function addGroupMemberCallback(_, argname) {
28122880 if (!selected_group) {
28132881 toastr.warning('Cannot run /memberadd command outside of a group chat.');
28142882 return '';
28152883 }
28162884
28172885 if (!argname) {
28182886 console.warn('WARN: No argument provided for /memberadd command');
28192887 return '';
28202888 }
28212889
2822- arg = arg.trim();
2890+ const character = findChar({ name: name, preferCurrentChar: false });
2823- const chid = findCharacterIndex(arg);
2891+ if (!character) {
2824-
2892+ console.warn(`WARN: No character found for argument ${name}`);
2825- if (chid === -1) {
2826- console.warn(`WARN: No character found for argument ${arg}`);
28272893 return '';
28282894 }
28292895
2830- const character = characters[chid];
28312896 const group = groups.find(x => x.id === selected_group);
28322897
28332898 if (!group || !Array.isArray(group.members)) {
@@ -2896,7 +2961,7 @@ function findPersonaByName(name) {
28962961 }
28972962
28982963 for (const persona of Object.entries(power_user.personas)) {
28992964 if (equalsIgnoreCaseAndAccents(persona[1].toLowerCase() ===, name.toLowerCase()) {
29002965 return persona[0];
29012966 }
29022967 }
@@ -2940,7 +3005,9 @@ async function deleteMessagesByNameCallback(_, name) {
29403005 return;
29413006 }
29423007
2943- name = name.trim();
3008+ // Search for a matching character to get the real name, or take the name provided
3009+ const character = findChar({ name: name });
3010+ name = character?.name || name;
29443011
29453012 const messagesToDelete = [];
29463013 chat.forEach((value) => {
@@ -2969,60 +3036,34 @@ async function deleteMessagesByNameCallback(_, name) {
29693036 return '';
29703037}
29713038
2972-function findCharacterIndex(name) {
2973- const matchTypes = [
2974- (a, b) => a === b,
2975- (a, b) => a.startsWith(b),
2976- (a, b) => a.includes(b),
2977- ];
2978-
2979- const exactAvatarMatch = characters.findIndex(x => x.avatar === name);
2980-
2981- if (exactAvatarMatch !== -1) {
2982- return exactAvatarMatch;
2983- }
2984-
2985- for (const matchType of matchTypes) {
2986- const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase()));
2987- if (index !== -1) {
2988- return index;
2989- }
2990- }
2991-
2992- return -1;
2993-}
2994-
29953039async function goToCharacterCallback(_, name) {
29963040 if (!name) {
29973041 console.warn('WARN: No character name provided for /go command');
29983042 return;
29993043 }
30003044
3001- name = name.trim();
3045+ const character = findChar({ name: name });
3002- const characterIndex = findCharacterIndex(name);
3046+ if (character) {
3003-
3047+ const chid = getCharIndex(character);
3004- if (characterIndex !== -1) {
3048+ await openChat(new String(chid));
3005- await openChat(new String(characterIndex));
3049+ setActiveCharacter(character.avatar);
3006- setActiveCharacter(characters[characterIndex]?.avatar);
30073050 setActiveGroup(null);
30083051 return characters[characterIndex]?character.name;
30093052 } else {
30103053 const group = groups.find(it => equalsIgnoreCaseAndAccents(it.name.toLowerCase() ==, name.toLowerCase());
30113054 if (group) {
30123055 await openGroupById(group.id);
30133056 setActiveCharacter(null);
30143057 setActiveGroup(group.id);
30153058 return group.name;
3016- } else {
3059+ }
30173060 console.warn(`No matches found for name "${name}"`);
30183061 return '';
30193062}
3020- }
3021-}
30223063
30233064async function openChat(idchid) {
30243065 resetSelectedGroup();
30253066 setCharacterId(idchid);
30263067 await delay(1);
30273068 await reloadCurrentChat();
30283069}
@@ -3168,6 +3209,79 @@ async function setNarratorName(_, text) {
31683209 return '';
31693210}
31703211
3212+/**
3213+ * Checks if an argument is a string array (or undefined), and if not, throws an error
3214+ * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined} arg The named argument to check
3215+ * @param {string} name The name of the argument for the error message
3216+ * @param {object} [options={}] - The optional arguments
3217+ * @param {boolean} [options.allowUndefined=false] - Whether the argument can be undefined
3218+ * @throws {Error} If the argument is not an array
3219+ * @returns {string[]}
3220+ */
3221+export function validateArrayArgString(arg, name, { allowUndefined = true } = {}) {
3222+ if (arg === undefined) {
3223+ if (allowUndefined) return undefined;
3224+ throw new Error(`Argument "${name}" is undefined, but must be a string array`);
3225+ }
3226+ if (!Array.isArray(arg)) throw new Error(`Argument "${name}" must be an array`);
3227+ if (!arg.every(x => typeof x === 'string')) throw new Error(`Argument "${name}" must be an array of strings`);
3228+ return arg;
3229+}
3230+
3231+/**
3232+ * Checks if an argument is a string or closure array (or undefined), and if not, throws an error
3233+ * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined} arg The named argument to check
3234+ * @param {string} name The name of the argument for the error message
3235+ * @param {object} [options={}] - The optional arguments
3236+ * @param {boolean} [options.allowUndefined=false] - Whether the argument can be undefined
3237+ * @throws {Error} If the argument is not an array of strings or closures
3238+ * @returns {(string|SlashCommandClosure)[]}
3239+ */
3240+export function validateArrayArg(arg, name, { allowUndefined = true } = {}) {
3241+ if (arg === undefined) {
3242+ if (allowUndefined) return [];
3243+ throw new Error(`Argument "${name}" is undefined, but must be an array of strings or closures`);
3244+ }
3245+ if (!Array.isArray(arg)) throw new Error(`Argument "${name}" must be an array`);
3246+ if (!arg.every(x => typeof x === 'string' || x instanceof SlashCommandClosure)) throw new Error(`Argument "${name}" must be an array of strings or closures`);
3247+ return arg;
3248+}
3249+
3250+
3251+/**
3252+ * Retrieves the name and avatar information for a message
3253+ *
3254+ * 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,
3255+ * explicitly implement this in the code using this.
3256+ *
3257+ * @param {object?} character - The character object to get the avatar data for
3258+ * @param {string?} name - The name to get the avatar data for
3259+ * @returns {{name: string, force_avatar: string, original_avatar: string}} An object containing the name for the message, forced avatar URL, and original avatar
3260+ */
3261+export function getNameAndAvatarForMessage(character, name = null) {
3262+ const isNeutralCharacter = !character && name2 === neutralCharacterName && name === neutralCharacterName;
3263+ const currentChar = characters[this_chid];
3264+
3265+ let force_avatar, original_avatar;
3266+ if (character?.avatar === currentChar?.avatar || isNeutralCharacter) {
3267+ // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars
3268+ }
3269+ else if (character && character.avatar !== 'none') {
3270+ force_avatar = getThumbnailUrl('avatar', character.avatar);
3271+ original_avatar = character.avatar;
3272+ }
3273+ else {
3274+ force_avatar = default_avatar;
3275+ original_avatar = default_avatar;
3276+ }
3277+
3278+ return {
3279+ name: character?.name || name,
3280+ force_avatar: force_avatar,
3281+ original_avatar: original_avatar,
3282+ };
3283+}
3284+
31713285export async function sendMessageAs(args, text) {
31723286 if (!text) {
31733287 toastr.warning('You must specify text to send as');
@@ -3196,26 +3310,18 @@ export async function sendMessageAs(args, text) {
31963310 const isSystem = bias && !removeMacros(mesText).length;
31973311 const compact = isTrueBoolean(args?.compact);
31983312
3199- const character = characters.find(x => x.avatar === name) ?? characters.find(x => x.name === name);
3313+ const character = findChar({ name: name });
3200- let force_avatar, original_avatar;
3201-
3202- const chatCharacter = this_chid !== undefined ? characters[this_chid] : null;
3203- const isNeutralCharacter = !chatCharacter && name2 === neutralCharacterName && name === neutralCharacterName;
32043314
3205- if (chatCharacter === character || isNeutralCharacter) {
3315+ const avatarCharacter = args.avatar ? findChar({ name: args.avatar }) : character;
3206- // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars
3316+ if (args.avatar && !avatarCharacter) {
3207- }
3317+ toastr.warning(`Character for avatar ${args.avatar} not found`);
3208- else if (character && character.avatar !== 'none') {
3318+ return '';
3209- force_avatar = getThumbnailUrl('avatar', character.avatar);
3210- original_avatar = character.avatar;
3211- }
3212- else {
3213- force_avatar = default_avatar;
3214- original_avatar = default_avatar;
32153319 }
32163320
3321+ const { name: avatarCharName, force_avatar, original_avatar } = getNameAndAvatarForMessage(avatarCharacter, name);
3322+
32173323 const message = {
3218- name: name,
3324+ name: character?.name || name || avatarCharName,
32193325 is_user: false,
32203326 is_system: isSystem,
32213327 send_date: getMessageTimeStamp(),
public/scripts/slash-commands/SlashCommand.js+2 -2
@@ -15,13 +15,13 @@ import { SlashCommandScope } from './SlashCommandScope.js';
1515 * _abortController:SlashCommandAbortController,
1616 * _debugController:SlashCommandDebugController,
1717 * _hasUnnamedArgument:boolean,
1818 * [id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined,
1919 * }} NamedArguments
2020 */
2121
2222/**
2323 * Alternative object for local JSDocs, where you don't need existing pipe, scope, etc. arguments
2424 * @typedef {{[id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined}} NamedArgumentsCapture
2525 */
2626
2727/**
public/scripts/slash-commands/SlashCommandClosure.js+8 -0
@@ -508,6 +508,14 @@ export class SlashCommandClosure {
508508 return v;
509509 });
510510 }
511+
512+ value ??= '';
513+
514+ // Make sure that if unnamed args are split, it should always return an array
515+ if (executor.command.splitUnnamedArgument && !Array.isArray(value)) {
516+ value = [value];
517+ }
518+
511519 return value;
512520 }
513521
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+43 -2
@@ -2,7 +2,7 @@ import { chat_metadata, characters, substituteParams, chat, extension_prompt_rol
22import { extension_settings } from '../extensions.js';
33import { getGroupMembers, groups } from '../group-chats.js';
44import { power_user } from '../power-user.js';
55import { searchCharByName, getTagsList, tags, tag_map } from '../tags.js';
66import { world_names } from '../world-info.js';
77import { SlashCommandClosure } from './SlashCommandClosure.js';
88import { SlashCommandEnumValue, enumTypes } from './SlashCommandEnumValue.js';
@@ -154,6 +154,35 @@ export const commonEnumProviders = {
154154 },
155155
156156 /**
157+ * Enum values for numbers and variable names
158+ *
159+ * Includes all variable names and the ability to specify any number
160+ *
161+ * @param {SlashCommandExecutor} executor - The executor of the slash command
162+ * @param {SlashCommandScope} scope - The scope of the slash command
163+ * @returns {SlashCommandEnumValue[]} The enum values
164+ */
165+ numbersAndVariables: (executor, scope) => [
166+ ...commonEnumProviders.variables('all')(executor, scope),
167+ new SlashCommandEnumValue(
168+ 'any variable name',
169+ null,
170+ enumTypes.variable,
171+ enumIcons.variable,
172+ (input) => /^\w*$/.test(input),
173+ (input) => input,
174+ ),
175+ new SlashCommandEnumValue(
176+ 'any number',
177+ null,
178+ enumTypes.number,
179+ enumIcons.number,
180+ (input) => input == '' || !Number.isNaN(Number(input)),
181+ (input) => input,
182+ ),
183+ ],
184+
185+ /**
157186 * All possible char entities, like characters and groups. Can be filtered down to just one type.
158187 *
159188 * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return
@@ -183,6 +212,18 @@ export const commonEnumProviders = {
183212 personas: () => Object.values(power_user.personas).map(persona => new SlashCommandEnumValue(persona, null, enumTypes.name, enumIcons.persona)),
184213
185214 /**
215+ * All possible tags, or only those that have been assigned
216+ *
217+ * @param {('all' | 'assigned')} [mode='all'] - Which types of tags to show
218+ * @returns {() => SlashCommandEnumValue[]}
219+ */
220+ tags: (mode = 'all') => () => {
221+ let assignedTags = mode === 'assigned' ? new Set(Object.values(tag_map).flat()) : new Set();
222+ return tags.filter(tag => mode === 'all' || (mode === 'assigned' && assignedTags.has(tag.id)))
223+ .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));
224+ },
225+
226+ /**
186227 * All possible tags for a given char/group entity
187228 *
188229 * @param {('all' | 'existing' | 'not-existing')?} [mode='all'] - Which types of tags to show
@@ -194,7 +235,7 @@ export const commonEnumProviders = {
194235 if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');
195236 const key = searchCharByName(substituteParams(charName), { suppressLogging: true });
196237 const assigned = key ? getTagsList(key) : [];
197238 return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it))
198239 .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));
199240 },
200241
public/scripts/tags.js+8 -8
@@ -15,7 +15,7 @@ import {
1515import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
1616
1717import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
1818import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, flashHighlight, equalsIgnoreCaseAndAccents, includesIgnoreCaseAndAccents, removeFromArray, getFreeName, debounce, findChar } from './utils.js';
1919import { power_user } from './power-user.js';
2020import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
2121import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -50,7 +50,6 @@ export {
5050 removeTagFromMap,
5151};
5252
53-/** @typedef {import('../scripts/popup.js').Popup} Popup */
5453/** @typedef {import('../script.js').Character} Character */
5554
5655const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
@@ -507,7 +506,7 @@ export function getTagKeyForEntityElement(element) {
507506 */
508507export function searchCharByName(charName, { suppressLogging = false } = {}) {
509508 const entity = charName
510509 ? (characters.findfindChar(x =>{ x.name ===: charName }) || groups.find(x => equalsIgnoreCaseAndAccents(x.name ==, charName)))
511510 : (selected_group ? groups.find(x => x.id == selected_group) : characters[this_chid]);
512511 const key = getTagKeyForEntity(entity);
513512 if (!key) {
@@ -1861,8 +1860,9 @@ function registerTagsSlashCommands() {
18611860 return String(result);
18621861 },
18631862 namedArgumentList: [
18641863 SlashCommandNamedArgument.fromProps({ name: 'name',
18651864 descriptionname: 'Character name',
1865+ description: 'Character name - or unique character identifier (avatar key)',
18661866 typeList: [ARGUMENT_TYPE.STRING],
18671867 defaultValue: '{{char}}',
18681868 enumProvider: commonEnumProviders.characters(),
@@ -1907,7 +1907,7 @@ function registerTagsSlashCommands() {
19071907 },
19081908 namedArgumentList: [
19091909 SlashCommandNamedArgument.fromProps({ name: 'name',
19101910 description: 'Character name - or unique character identifier (avatar key)',
19111911 typeList: [ARGUMENT_TYPE.STRING],
19121912 defaultValue: '{{char}}',
19131913 enumProvider: commonEnumProviders.characters(),
@@ -1950,7 +1950,7 @@ function registerTagsSlashCommands() {
19501950 namedArgumentList: [
19511951 SlashCommandNamedArgument.fromProps({
19521952 name: 'name',
19531953 description: 'Character name - or unique character identifier (avatar key)',
19541954 typeList: [ARGUMENT_TYPE.STRING],
19551955 defaultValue: '{{char}}',
19561956 enumProvider: commonEnumProviders.characters(),
@@ -1993,7 +1993,7 @@ function registerTagsSlashCommands() {
19931993 namedArgumentList: [
19941994 SlashCommandNamedArgument.fromProps({
19951995 name: 'name',
19961996 description: 'Character name - or unique character identifier (avatar key)',
19971997 typeList: [ARGUMENT_TYPE.STRING],
19981998 defaultValue: '{{char}}',
19991999 enumProvider: commonEnumProviders.characters(),
public/scripts/utils.js+74 -1
@@ -1,10 +1,12 @@
11import { getContext } from './extensions.js';
22import { characters, getRequestHeaders, this_chid } from '../script.js';
33import { isMobile } from './RossAscends-mods.js';
44import { collapseNewlines } from './power-user.js';
55import { debounce_timeout } from './constants.js';
66import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
77import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
8+import { getTagsList } from './tags.js';
9+import { groups, selected_group } from './group-chats.js';
810
911/**
1012 * Pagination status string template.
@@ -2110,3 +2112,74 @@ export async function showFontAwesomePicker(customList = null) {
21102112 }
21112113 return null;
21122114}
2115+
2116+/**
2117+ * Finds a character by name, with optional filtering and precedence for avatars
2118+ * @param {object} [options={}] - The options for the search
2119+ * @param {string?} [options.name=null] - The name to search for
2120+ * @param {boolean} [options.allowAvatar=true] - Whether to allow searching by avatar
2121+ * @param {boolean} [options.insensitive=true] - Whether the search should be case insensitive
2122+ * @param {string[]?} [options.filteredByTags=null] - Tags to filter characters by
2123+ * @param {boolean} [options.preferCurrentChar=true] - Whether to prefer the current character(s)
2124+ * @param {boolean} [options.quiet=false] - Whether to suppress warnings
2125+ * @returns {any?} - The found character or null if not found
2126+ */
2127+export function findChar({ name = null, allowAvatar = true, insensitive = true, filteredByTags = null, preferCurrentChar = true, quiet = false } = {}) {
2128+ const matches = (char) => !name || (allowAvatar && char.avatar === name) || (insensitive ? equalsIgnoreCaseAndAccents(char.name, name) : char.name === name);
2129+
2130+ // Filter characters by tags if provided
2131+ let filteredCharacters = characters;
2132+ if (filteredByTags) {
2133+ filteredCharacters = characters.filter(char => {
2134+ const charTags = getTagsList(char.avatar, false);
2135+ return filteredByTags.every(tagName => charTags.some(x => x.name == tagName));
2136+ });
2137+ }
2138+
2139+ // Get the current character(s)
2140+ /** @type {any[]} */
2141+ const currentChars = selected_group ? groups.find(group => group.id === selected_group)?.members.map(member => filteredCharacters.find(char => char.avatar === member))
2142+ : filteredCharacters.filter(char => characters[this_chid]?.avatar === char.avatar);
2143+
2144+ // If we have a current char and prefer it, return that if it matches
2145+ if (preferCurrentChar) {
2146+ const preferredCharSearch = currentChars.filter(matches);
2147+ if (preferredCharSearch.length > 1) {
2148+ if (!quiet) toastr.warning('Multiple characters found for given conditions.');
2149+ else console.warn('Multiple characters found for given conditions. Returning the first match.');
2150+ }
2151+ if (preferredCharSearch.length) {
2152+ return preferredCharSearch[0];
2153+ }
2154+ }
2155+
2156+ // If allowAvatar is true, search by avatar first
2157+ if (allowAvatar && name) {
2158+ const characterByAvatar = filteredCharacters.find(char => char.avatar === name);
2159+ if (characterByAvatar) {
2160+ return characterByAvatar;
2161+ }
2162+ }
2163+
2164+ // Search for matching characters by name
2165+ const matchingCharacters = name ? filteredCharacters.filter(matches) : filteredCharacters;
2166+ if (matchingCharacters.length > 1) {
2167+ if (!quiet) toastr.warning('Multiple characters found for given conditions.');
2168+ else console.warn('Multiple characters found for given conditions. Returning the first match.');
2169+ }
2170+
2171+ return matchingCharacters[0] || null;
2172+}
2173+
2174+/**
2175+ * Gets the index of a character based on the character object
2176+ * @param {object} char - The character object to find the index for
2177+ * @throws {Error} If the character is not found
2178+ * @returns {number} The index of the character in the characters array
2179+ */
2180+export function getCharIndex(char) {
2181+ if (!char) throw new Error('Character is undefined');
2182+ const index = characters.findIndex(c => c.avatar === char.avatar);
2183+ if (index === -1) throw new Error(`Character not found: ${char.avatar}`);
2184+ return index;
2185+}
public/scripts/variables.js+77 -58
@@ -683,8 +683,8 @@ function deleteGlobalVariable(name) {
683683}
684684
685685/**
686686 * Parses a series of numeric values from a string or a string array.
687687 * @param {string|string[]} value A space-separated list of numeric values or variable names
688688 * @param {SlashCommandScope} scope Scope
689689 * @returns {number[]} An array of numeric values
690690 */
@@ -693,11 +693,17 @@ function parseNumericSeries(value, scope = null) {
693693 return [value];
694694 }
695695
696- const array = value
696+ /** @type {(string|number)[]} */
697- .split(' ')
697+ let values = Array.isArray(value) ? value : value.split(' ');
698- .map(i => i.trim())
698+
699+ // If a JSON array was provided as the only value, convert it to an array
700+ if (values.length === 1 && typeof values[0] === 'string' && values[0].startsWith('[')) {
701+ values = convertValueType(values[0], 'array');
702+ }
703+
704+ const array = values.map(i => typeof i === 'string' ? i.trim() : i)
699705 .filter(i => i !== '')
700706 .map(i => isNaN(Number(i)) ? Number(resolveVariable(String(i), scope)) : Number(i))
701707 .filter(i => !isNaN(i));
702708
703709 return array;
@@ -717,7 +723,7 @@ function performOperation(value, operation, singleOperand = false, scope = null)
717723
718724 const result = singleOperand ? operation(array[0]) : operation(array);
719725
720726 if (isNaN(result) || !isFinite(result)) {
721727 return 0;
722728 }
723729
@@ -745,7 +751,7 @@ function maxValuesCallback(args, value) {
745751}
746752
747753function subValuesCallback(args, value) {
748754 return performOperation(value, (array) => array[0].reduce((a, b) => a - b, array[1].shift() ?? 0), false, args._scope);
749755}
750756
751757function divValuesCallback(args, value) {
@@ -1618,36 +1624,15 @@ export function registerVariableCommands() {
16181624 }));
16191625 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
16201626 name: 'add',
16211627 callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')),
16221628 returns: 'sum of the provided values',
16231629 unnamedArgumentList: [
16241630 SlashCommandArgument.fromProps({
16251631 description: 'values to sum',
16261632 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
16271633 isRequired: true,
16281634 acceptsMultiple: true,
1629- enumProvider: (executor, scope) => {
1635+ enumProvider: commonEnumProviders.numbersAndVariables,
1630- const vars = commonEnumProviders.variables('all')(executor, scope);
1631- vars.push(
1632- new SlashCommandEnumValue(
1633- 'any variable name',
1634- null,
1635- enumTypes.variable,
1636- enumIcons.variable,
1637- (input) => /^\w*$/.test(input),
1638- (input) => input,
1639- ),
1640- new SlashCommandEnumValue(
1641- 'any number',
1642- null,
1643- enumTypes.number,
1644- enumIcons.number,
1645- (input) => input == '' || !Number.isNaN(Number(input)),
1646- (input) => input,
1647- ),
1648- );
1649- return vars;
1650- },
16511636 forceEnum: false,
16521637 }),
16531638 ],
@@ -1655,7 +1640,9 @@ export function registerVariableCommands() {
16551640 helpString: `
16561641 <div>
16571642 Performs an addition of the set of values and passes the result down the pipe.
1658- Can use variable names.
1643+ </div>
1644+ <div>
1645+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
16591646 </div>
16601647 <div>
16611648 <strong>Example:</strong>
@@ -1663,6 +1650,9 @@ export function registerVariableCommands() {
16631650 <li>
16641651 <pre><code class="language-stscript">/add 10 i 30 j</code></pre>
16651652 </li>
1653+ <li>
1654+ <pre><code class="language-stscript">/add ["count", 15, 2, "i"]</code></pre>
1655+ </li>
16661656 </ul>
16671657 </div>
16681658 `,
@@ -1674,16 +1664,20 @@ export function registerVariableCommands() {
16741664 unnamedArgumentList: [
16751665 SlashCommandArgument.fromProps({
16761666 description: 'values to multiply',
16771667 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
16781668 isRequired: true,
16791669 acceptsMultiple: true,
16801670 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
16811671 forceEnum: false,
16821672 }),
16831673 ],
1674+ splitUnnamedArgument: true,
16841675 helpString: `
16851676 <div>
16861677 Performs a multiplication of the set of values and passes the result down the pipe. Can use variable names.
1678+ </div>
1679+ <div>
1680+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
16871681 </div>
16881682 <div>
16891683 <strong>Examples:</strong>
@@ -1691,6 +1685,9 @@ export function registerVariableCommands() {
16911685 <li>
16921686 <pre><code class="language-stscript">/mul 10 i 30 j</code></pre>
16931687 </li>
1688+ <li>
1689+ <pre><code class="language-stscript">/mul ["count", 15, 2, "i"]</code></pre>
1690+ </li>
16941691 </ul>
16951692 </div>
16961693 `,
@@ -1702,16 +1699,20 @@ export function registerVariableCommands() {
17021699 unnamedArgumentList: [
17031700 SlashCommandArgument.fromProps({
17041701 description: 'values to find the max',
17051702 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
17061703 isRequired: true,
17071704 acceptsMultiple: true,
17081705 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
17091706 forceEnum: false,
17101707 }),
17111708 ],
1709+ splitUnnamedArgument: true,
17121710 helpString: `
17131711 <div>
17141712 Returns the maximum value of the set of values and passes the result down the pipe. Can use variable names.
1713+ </div>
1714+ <div>
1715+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
17151716 </div>
17161717 <div>
17171718 <strong>Examples:</strong>
@@ -1719,6 +1720,9 @@ export function registerVariableCommands() {
17191720 <li>
17201721 <pre><code class="language-stscript">/max 10 i 30 j</code></pre>
17211722 </li>
1723+ <li>
1724+ <pre><code class="language-stscript">/max ["count", 15, 2, "i"]</code></pre>
1725+ </li>
17221726 </ul>
17231727 </div>
17241728 `,
@@ -1730,17 +1734,20 @@ export function registerVariableCommands() {
17301734 unnamedArgumentList: [
17311735 SlashCommandArgument.fromProps({
17321736 description: 'values to find the min',
17331737 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
17341738 isRequired: true,
17351739 acceptsMultiple: true,
17361740 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
17371741 forceEnum: false,
17381742 }),
17391743 ],
1744+ splitUnnamedArgument: true,
17401745 helpString: `
17411746 <div>
17421747 Returns the minimum value of the set of values and passes the result down the pipe.
1743- Can use variable names.
1748+ </div>
1749+ <div>
1750+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
17441751 </div>
17451752 <div>
17461753 <strong>Example:</strong>
@@ -1748,6 +1755,9 @@ export function registerVariableCommands() {
17481755 <li>
17491756 <pre><code class="language-stscript">/min 10 i 30 j</code></pre>
17501757 </li>
1758+ <li>
1759+ <pre><code class="language-stscript">/min ["count", 15, 2, "i"]</code></pre>
1760+ </li>
17511761 </ul>
17521762 </div>
17531763 `,
@@ -1758,18 +1768,21 @@ export function registerVariableCommands() {
17581768 returns: 'difference of the provided values',
17591769 unnamedArgumentList: [
17601770 SlashCommandArgument.fromProps({
17611771 description: 'values to findsubtract, starting form the differencefirst provided value',
17621772 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
17631773 isRequired: true,
17641774 acceptsMultiple: true,
17651775 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
17661776 forceEnum: false,
17671777 }),
17681778 ],
1779+ splitUnnamedArgument: true,
17691780 helpString: `
17701781 <div>
17711782 Performs a subtraction of the set of values and passes the result down the pipe.
1772- Can use variable names.
1783+ </div>
1784+ <div>
1785+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
17731786 </div>
17741787 <div>
17751788 <strong>Example:</strong>
@@ -1777,6 +1790,9 @@ export function registerVariableCommands() {
17771790 <li>
17781791 <pre><code class="language-stscript">/sub i 5</code></pre>
17791792 </li>
1793+ <li>
1794+ <pre><code class="language-stscript">/sub ["count", 4, "i"]</code></pre>
1795+ </li>
17801796 </ul>
17811797 </div>
17821798 `,
@@ -1790,17 +1806,18 @@ export function registerVariableCommands() {
17901806 description: 'dividend',
17911807 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
17921808 isRequired: true,
17931809 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
17941810 forceEnum: false,
17951811 }),
17961812 SlashCommandArgument.fromProps({
17971813 description: 'divisor',
17981814 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
17991815 isRequired: true,
18001816 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18011817 forceEnum: false,
18021818 }),
18031819 ],
1820+ splitUnnamedArgument: true,
18041821 helpString: `
18051822 <div>
18061823 Performs a division of two values and passes the result down the pipe.
@@ -1825,17 +1842,18 @@ export function registerVariableCommands() {
18251842 description: 'dividend',
18261843 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18271844 isRequired: true,
18281845 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18291846 forceEnum: false,
18301847 }),
18311848 SlashCommandArgument.fromProps({
18321849 description: 'divisor',
18331850 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18341851 isRequired: true,
18351852 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18361853 forceEnum: false,
18371854 }),
18381855 ],
1856+ splitUnnamedArgument: true,
18391857 helpString: `
18401858 <div>
18411859 Performs a modulo operation of two values and passes the result down the pipe.
@@ -1860,17 +1878,18 @@ export function registerVariableCommands() {
18601878 description: 'base',
18611879 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18621880 isRequired: true,
18631881 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18641882 forceEnum: false,
18651883 }),
18661884 SlashCommandArgument.fromProps({
18671885 description: 'exponent',
18681886 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18691887 isRequired: true,
18701888 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18711889 forceEnum: false,
18721890 }),
18731891 ],
1892+ splitUnnamedArgument: true,
18741893 helpString: `
18751894 <div>
18761895 Performs a power operation of two values and passes the result down the pipe.
@@ -1895,7 +1914,7 @@ export function registerVariableCommands() {
18951914 description: 'value',
18961915 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18971916 isRequired: true,
18981917 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18991918 forceEnum: false,
19001919 }),
19011920 ],
@@ -1923,7 +1942,7 @@ export function registerVariableCommands() {
19231942 description: 'value',
19241943 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
19251944 isRequired: true,
19261945 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
19271946 forceEnum: false,
19281947 }),
19291948 ],
@@ -1952,7 +1971,7 @@ export function registerVariableCommands() {
19521971 description: 'value',
19531972 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
19541973 isRequired: true,
19551974 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
19561975 forceEnum: false,
19571976 }),
19581977 ],
@@ -1980,7 +1999,7 @@ export function registerVariableCommands() {
19801999 description: 'value',
19812000 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
19822001 isRequired: true,
19832002 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
19842003 forceEnum: false,
19852004 }),
19862005 ],
@@ -2008,7 +2027,7 @@ export function registerVariableCommands() {
20082027 description: 'value',
20092028 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
20102029 isRequired: true,
20112030 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
20122031 forceEnum: false,
20132032 }),
20142033 ],
@@ -2036,7 +2055,7 @@ export function registerVariableCommands() {
20362055 description: 'value',
20372056 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
20382057 isRequired: true,
20392058 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
20402059 forceEnum: false,
20412060 }),
20422061 ],
src/endpoints/backends/chat-completions.js+1 -1
@@ -323,7 +323,7 @@ async function sendMakerSuiteRequest(request, response) {
323323 ? (stream ? 'streamGenerateContent' : 'generateContent')
324324 : (isText ? 'generateText' : 'generateMessage');
325325
326326 const generateResponse = await fetch(`${apiUrl.origin}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {
327327 body: JSON.stringify(body),
328328 method: 'POST',
329329 headers: {