Merge branch 'staging' into swipe-nums-for-all-mes

77470502339c829640eeab9fe78ff66c7395acf5

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

16 files changed, +724 -286Showing whitespace changes
UpdateAndStart.bat+12 -3
@@ -2,17 +2,26 @@
22pushd %~dp0
33git --version > nul 2>&1
44if %errorlevel% neq 0 (
55 echo GitGit is not installed on this system. Skipping update.
6- echo If you installed with a zip file, you will need to download the new zip and install it manually.
6+ echo Install it from https://git-scm.com/downloads
7+ goto end
78) else (
9+ if not exist .git (
10+ echo Not running from a Git repository. Reinstall using an officially supported method to get updates.
11+ echo See: https://docs.sillytavern.app/installation/windows/
12+ goto end
13+ )
814 call git pull --rebase --autostash
915 if %errorlevel% neq 0 (
1016 REM incase there is still something wrong
1117 echo ThereThere were errors while updating. Please download the latest version manually.
18+ echo See the update FAQ at https://docs.sillytavern.app/usage/update/#common-update-problems
19+ goto end
1220 )
1321)
1422set NODE_ENV=production
1523call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
1624node server.js %*
25+:end
1726pause
1827popd
UpdateForkAndStart.bat+10 -3
@@ -5,8 +5,14 @@ pushd %~dp0
55echo Checking Git installation
66git --version > nul 2>&1
77if %errorlevel% neq 0 (
88 echo GitGit is not installed on this system. Skipping update.
9- echo If you installed with a zip file, you will need to download the new zip and install it manually.
9+ echo Install it from https://git-scm.com/downloads
10+ goto end
11+)
12+
13+if not exist .git (
14+ echo Not running from a Git repository. Reinstall using an officially supported method to get updates.
15+ echo See: https://docs.sillytavern.app/installation/windows/
1016 goto end
1117)
1218
@@ -89,7 +95,8 @@ git pull --rebase --autostash origin %TARGET_BRANCH%
8995
9096:install
9197if %errorlevel% neq 0 (
9298 echo ThereThere were errors while updating. Please check manually.
99+ echo See the update FAQ at https://docs.sillytavern.app/usage/update/#common-update-problems
93100 goto end
94101)
95102
default/config.yaml+0 -1
@@ -118,7 +118,6 @@ extras:
118118 classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx
119119 captioningModel: Xenova/vit-gpt2-image-captioning
120120 embeddingModel: Cohee/jina-embeddings-v2-base-en
121- promptExpansionModel: Cohee/fooocus_expansion-onnx
122121 speechToTextModel: Xenova/whisper-small
123122 textToSpeechModel: Xenova/speecht5_tts
124123# -- OPENAI CONFIGURATION --
public/script.js+3 -1
@@ -4797,7 +4797,7 @@ export function removeMacros(str) {
47974797 * @param {boolean} [compact] Send as a compact display message.
47984798 * @param {string} [name] Name of the user sending the message. Defaults to name1.
47994799 * @param {string} [avatar] Avatar of the user sending the message. Defaults to user_avatar.
48004800 * @returns {Promise<voidany>} A promise that resolves whento the message when it is inserted.
48014801 */
48024802export async function sendMessageAsUser(messageText, messageBias, insertAt = null, compact = false, name = name1, avatar = user_avatar) {
48034803 messageText = getRegexedString(messageText, regex_placement.USER_INPUT);
@@ -4844,6 +4844,8 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
48444844 await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id);
48454845 await saveChatConditional();
48464846 }
4847+
4848+ return message;
48474849}
48484850
48494851/**
public/scripts/extensions/expressions/index.js+31 -5
@@ -12,6 +12,8 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
1212import { isFunctionCallingSupported } from '../../openai.js';
1313import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
1414import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
15+import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js';
16+import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
1517export { MODULE_NAME };
1618
1719const MODULE_NAME = 'expressions';
@@ -2134,18 +2136,42 @@ function migrateSettings() {
21342136 name: 'classify-expressions',
21352137 aliases: ['expressions'],
21362138 callback: async (args) => {
2137- const list = await getExpressionsList();
2139+ /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
2138- switch (String(args.format).toLowerCase()) {
2140+ // @ts-ignore
2141+ let returnType = args.return;
2142+
2143+ // Old legacy return type handling
2144+ if (args.format) {
2145+ toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
2146+ const type = String(args?.format).toLowerCase().trim();
2147+ switch (type) {
21392148 case 'json':
2140- return JSON.stringify(list);
2149+ returnType = 'object';
2150+ break;
21412151 default:
2142- return list.join(', ');
2152+ returnType = 'pipe';
2153+ break;
2154+ }
21432155 }
2156+
2157+ // Now the actual new return type handling
2158+ const list = await getExpressionsList();
2159+
2160+ return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
21442161 },
21452162 namedArgumentList: [
21462163 SlashCommandNamedArgument.fromProps({
2164+ name: 'return',
2165+ description: 'The way how you want the return value to be provided',
2166+ typeList: [ARGUMENT_TYPE.STRING],
2167+ defaultValue: 'pipe',
2168+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
2169+ forceEnum: true,
2170+ }),
2171+ // TODO remove some day
2172+ SlashCommandNamedArgument.fromProps({
21472173 name: 'format',
21482174 description: '!!! DEPRECATED - use "return" instead !!! The format to return the list in: comma-separated plain text or JSON array. Default is plain text.',
21492175 typeList: [ARGUMENT_TYPE.STRING],
21502176 enumList: [
21512177 new SlashCommandEnumValue('plain', null, enumTypes.enum, ', '),
public/scripts/extensions/stable-diffusion/index.js+326 -59
@@ -20,7 +20,7 @@ import {
2020} from '../../../script.js';
2121import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
2222import { selected_group } from '../../group-chats.js';
2323import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce, isFalseBoolean } from '../../utils.js';
2424import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';
2525import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
2626import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';
@@ -31,6 +31,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
3131import { debounce_timeout } from '../../constants.js';
3232import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
3333import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
34+import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
3435export { MODULE_NAME };
3536
3637const MODULE_NAME = 'sd';
@@ -221,7 +222,6 @@ const defaultSettings = {
221222
222223 // Refine mode
223224 refine_mode: false,
224- expand: false,
225225 interactive_mode: false,
226226 multimodal_captioning: false,
227227 snap: false,
@@ -240,7 +240,7 @@ const defaultSettings = {
240240 drawthings_auth: '',
241241
242242 hr_upscaler: 'Latent',
243243 hr_scale: 21.0,
244244 hr_scale_min: 1.0,
245245 hr_scale_max: 4.0,
246246 hr_scale_step: 0.1,
@@ -260,10 +260,6 @@ const defaultSettings = {
260260 clip_skip: 1,
261261
262262 // NovelAI settings
263- novel_upscale_ratio_min: 1.0,
264- novel_upscale_ratio_max: 4.0,
265- novel_upscale_ratio_step: 0.1,
266- novel_upscale_ratio: 1.0,
267263 novel_anlas_guard: false,
268264 novel_sm: false,
269265 novel_sm_dyn: false,
@@ -416,7 +412,6 @@ async function loadSettings() {
416412 $('#sd_hr_scale').val(extension_settings.sd.hr_scale).trigger('input');
417413 $('#sd_denoising_strength').val(extension_settings.sd.denoising_strength).trigger('input');
418414 $('#sd_hr_second_pass_steps').val(extension_settings.sd.hr_second_pass_steps).trigger('input');
419- $('#sd_novel_upscale_ratio').val(extension_settings.sd.novel_upscale_ratio).trigger('input');
420415 $('#sd_novel_anlas_guard').prop('checked', extension_settings.sd.novel_anlas_guard);
421416 $('#sd_novel_sm').prop('checked', extension_settings.sd.novel_sm);
422417 $('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn);
@@ -430,7 +425,6 @@ async function loadSettings() {
430425 $('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
431426 $('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
432427 $('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
433- $('#sd_expand').prop('checked', extension_settings.sd.expand);
434428 $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);
435429 $('#sd_auto_url').val(extension_settings.sd.auto_url);
436430 $('#sd_auto_auth').val(extension_settings.sd.auto_auth);
@@ -644,37 +638,13 @@ async function onSaveStyleClick() {
644638 saveSettingsDebounced();
645639}
646640
647-async function expandPrompt(prompt) {
648- try {
649- const response = await fetch('/api/sd/expand', {
650- method: 'POST',
651- headers: getRequestHeaders(),
652- body: JSON.stringify({ prompt: prompt }),
653- });
654-
655- if (!response.ok) {
656- throw new Error('API returned an error.');
657- }
658-
659- const data = await response.json();
660- return data.prompt;
661- } catch {
662- return prompt;
663- }
664-}
665-
666641/**
667642 * Modifies prompt based on auto-expansion and user inputs.
668643 * @param {string} prompt Prompt to refine
669- * @param {boolean} allowExpand Whether to allow auto-expansion
670644 * @param {boolean} isNegative Whether the prompt is a negative one
671645 * @returns {Promise<string>} Refined prompt
672646 */
673647async function refinePrompt(prompt, allowExpand, isNegative = false) {
674- if (allowExpand && extension_settings.sd.expand) {
675- prompt = await expandPrompt(prompt);
676- }
677-
678648 if (extension_settings.sd.refine_mode) {
679649 const text = isNegative ? '<h3>Review and edit the <i>negative</i> prompt:</h3>' : '<h3>Review and edit the prompt:</h3>';
680650 const refinedPrompt = await callGenericPopup(text + 'Press "Cancel" to abort the image generation.', POPUP_TYPE.INPUT, prompt.trim(), { rows: 5, okButton: 'Continue' });
@@ -800,11 +770,6 @@ function combinePrefixes(str1, str2, macro = '') {
800770 return process(result);
801771}
802772
803-function onExpandInput() {
804- extension_settings.sd.expand = !!$(this).prop('checked');
805- saveSettingsDebounced();
806-}
807-
808773function onRefineModeInput() {
809774 extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked');
810775 saveSettingsDebounced();
@@ -969,12 +934,6 @@ async function onViewAnlasClick() {
969934 toastr.info(`Free image generation: ${unlimitedGeneration ? 'Yes' : 'No'}`, `Anlas: ${anlas}`);
970935}
971936
972-function onNovelUpscaleRatioInput() {
973- extension_settings.sd.novel_upscale_ratio = Number($('#sd_novel_upscale_ratio').val());
974- $('#sd_novel_upscale_ratio_value').val(extension_settings.sd.novel_upscale_ratio.toFixed(1));
975- saveSettingsDebounced();
976-}
977-
978937function onNovelAnlasGuardInput() {
979938 extension_settings.sd.novel_anlas_guard = !!$('#sd_novel_anlas_guard').prop('checked');
980939 saveSettingsDebounced();
@@ -2273,6 +2232,25 @@ function getRawLastMessage() {
22732232}
22742233
22752234/**
2235+ * Ensure that the selected option exists in the dropdown.
2236+ * @param {string} setting Setting key
2237+ * @param {string} selector Dropdown selector
2238+ * @returns {void}
2239+ */
2240+function ensureSelectionExists(setting, selector) {
2241+ /** @type {HTMLSelectElement} */
2242+ const selectElement = document.querySelector(selector);
2243+ if (!selectElement) {
2244+ return;
2245+ }
2246+ const options = Array.from(selectElement.options);
2247+ const value = extension_settings.sd[setting];
2248+ if (selectElement.selectedOptions.length && !options.some(option => option.value === value)) {
2249+ extension_settings.sd[setting] = selectElement.selectedOptions[0].value;
2250+ }
2251+}
2252+
2253+/**
22762254 * Generates an image based on the given trigger word.
22772255 * @param {string} initiator The initiator of the image generation
22782256 * @param {Record<string, object>} args Command arguments
@@ -2292,8 +2270,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
22922270 return;
22932271 }
22942272
2295- extension_settings.sd.sampler = $('#sd_sampler').find(':selected').val();
2273+ ensureSelectionExists('sampler', '#sd_sampler');
2296- extension_settings.sd.model = $('#sd_model').find(':selected').val();
2274+ ensureSelectionExists('model', '#sd_model');
22972275
22982276 trigger = trigger.trim();
22992277 const generationType = getGenerationType(trigger);
@@ -2441,7 +2419,7 @@ async function getPrompt(generationType, message, trigger, quietPrompt, combineN
24412419 }
24422420
24432421 if (generationType !== generationMode.FREE) {
24442422 prompt = await refinePrompt(prompt, truefalse);
24452423 }
24462424
24472425 return prompt;
@@ -2469,7 +2447,7 @@ function generateFreeModePrompt(trigger, combineNegatives) {
24692447 return message.original_avatar.replace(/\.[^/.]+$/, '');
24702448 }
24712449 }
2472- throw new Error('No usable messages found.');
2450+ return '';
24732451 };
24742452
24752453 const key = getLastCharacterKey();
@@ -3031,7 +3009,7 @@ async function generateNovelImage(prompt, negativePrompt, signal) {
30313009 width: width,
30323010 height: height,
30333011 negative_prompt: negativePrompt,
30343012 upscale_ratio: extension_settings.sd.novel_upscale_ratiohr_scale,
30353013 decrisper: extension_settings.sd.novel_decrisper,
30363014 sm: sm,
30373015 sm_dyn: sm_dyn,
@@ -3613,8 +3591,8 @@ async function sdMessageButton(e) {
36133591 try {
36143592 setBusyIcon(true);
36153593 if (hasSavedImage) {
36163594 const prompt = await refinePrompt(message.extra.title, false, false);
36173595 const negative = hasSavedNegative ? await refinePrompt(message.extra.negative, false, true) : '';
36183596 message.extra.title = prompt;
36193597
36203598 const generationType = message?.extra?.generationType ?? generationMode.FREE;
@@ -3756,8 +3734,8 @@ async function onImageSwiped({ message, element, direction }) {
37563734 eventSource.once(CUSTOM_STOP_EVENT, stopListener);
37573735 const callback = () => { };
37583736 const hasNegative = message.extra.negative;
37593737 const prompt = await refinePrompt(message.extra.title, false, false);
37603738 const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, false, true) : '';
37613739 const characterName = context.groupId
37623740 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
37633741 : context.characters[context.characterId]?.name;
@@ -3788,12 +3766,85 @@ async function onImageSwiped({ message, element, direction }) {
37883766 await context.saveChat();
37893767}
37903768
3769+/**
3770+ * Applies the command arguments to the extension settings.
3771+ * @typedef {import('../../slash-commands/SlashCommand.js').NamedArguments} NamedArguments
3772+ * @typedef {import('../../slash-commands/SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture
3773+ * @param {NamedArguments | NamedArgumentsCapture} args - Command arguments
3774+ * @returns {Record<string, any>} - Current settings before applying the command arguments
3775+ */
3776+function applyCommandArguments(args) {
3777+ const overrideSettings = {};
3778+ const currentSettings = {};
3779+ const settingMap = {
3780+ 'edit': 'refine_mode',
3781+ 'extend': 'free_extend',
3782+ 'multimodal': 'multimodal_captioning',
3783+ 'seed': 'seed',
3784+ 'width': 'width',
3785+ 'height': 'height',
3786+ 'steps': 'steps',
3787+ 'cfg': 'scale',
3788+ 'skip': 'clip_skip',
3789+ 'model': 'model',
3790+ 'sampler': 'sampler',
3791+ 'scheduler': 'scheduler',
3792+ 'vae': 'vae',
3793+ 'upscaler': 'hr_upscaler',
3794+ 'scale': 'hr_scale',
3795+ 'hires': 'enable_hr',
3796+ 'denoise': 'denoising_strength',
3797+ '2ndpass': 'hr_second_pass_steps',
3798+ 'faces': 'restore_faces',
3799+ };
3800+
3801+ for (const [param, setting] of Object.entries(settingMap)) {
3802+ if (args[param] === undefined || defaultSettings[setting] === undefined) {
3803+ continue;
3804+ }
3805+ currentSettings[setting] = extension_settings.sd[setting];
3806+ const value = String(args[param]);
3807+ const type = typeof defaultSettings[setting];
3808+ switch (type) {
3809+ case 'boolean':
3810+ overrideSettings[setting] = isTrueBoolean(value) || !isFalseBoolean(value);
3811+ break;
3812+ case 'number':
3813+ overrideSettings[setting] = Number(value);
3814+ break;
3815+ default:
3816+ overrideSettings[setting] = value;
3817+ break;
3818+ }
3819+ }
3820+
3821+ Object.assign(extension_settings.sd, overrideSettings);
3822+ return currentSettings;
3823+}
3824+
37913825jQuery(async () => {
37923826 await addSDGenButtons();
37933827
3828+ const getSelectEnumProvider = (id, text) => () => Array.from(document.querySelectorAll(`#${id} > [value]`)).map(x => new SlashCommandEnumValue(x.getAttribute('value'), text ? x.textContent : null));
3829+
37943830 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
37953831 name: 'imagine',
3796- callback: (args, trigger) => generatePicture(initiators.command, args, String(trigger)),
3832+ returns: 'URL of the generated image, or an empty string if the generation failed',
3833+ callback: async (args, trigger) => {
3834+ const currentSettings = applyCommandArguments(args);
3835+
3836+ try {
3837+ return await generatePicture(initiators.command, args, String(trigger));
3838+ } catch (error) {
3839+ console.error('Failed to generate image:', error);
3840+ return '';
3841+ } finally {
3842+ if (Object.keys(currentSettings).length) {
3843+ Object.assign(extension_settings.sd, currentSettings);
3844+ saveSettingsDebounced();
3845+ }
3846+ }
3847+ },
37973848 aliases: ['sd', 'img', 'image'],
37983849 namedArgumentList: [
37993850 new SlashCommandNamedArgument(
@@ -3803,6 +3854,164 @@ jQuery(async () => {
38033854 name: 'negative',
38043855 description: 'negative prompt prefix',
38053856 typeList: [ARGUMENT_TYPE.STRING],
3857+ isRequired: false,
3858+ acceptsMultiple: false,
3859+ }),
3860+ SlashCommandNamedArgument.fromProps({
3861+ name: 'extend',
3862+ description: 'auto-extend free mode prompts with the LLM',
3863+ typeList: [ARGUMENT_TYPE.BOOLEAN],
3864+ enumProvider: commonEnumProviders.boolean('trueFalse'),
3865+ isRequired: false,
3866+ acceptsMultiple: false,
3867+ }),
3868+ SlashCommandNamedArgument.fromProps({
3869+ name: 'edit',
3870+ description: 'edit the prompt before generation',
3871+ typeList: [ARGUMENT_TYPE.BOOLEAN],
3872+ enumProvider: commonEnumProviders.boolean('trueFalse'),
3873+ isRequired: false,
3874+ acceptsMultiple: false,
3875+ }),
3876+ SlashCommandNamedArgument.fromProps({
3877+ name: 'multimodal',
3878+ description: 'use multimodal captioning (for portraits only)',
3879+ typeList: [ARGUMENT_TYPE.BOOLEAN],
3880+ enumProvider: commonEnumProviders.boolean('trueFalse'),
3881+ isRequired: false,
3882+ acceptsMultiple: false,
3883+ }),
3884+ SlashCommandNamedArgument.fromProps({
3885+ name: 'snap',
3886+ description: 'snap auto-adjusted dimensions to the nearest known resolution (portraits and backgrounds only)',
3887+ typeList: [ARGUMENT_TYPE.BOOLEAN],
3888+ enumProvider: commonEnumProviders.boolean('trueFalse'),
3889+ isRequired: false,
3890+ acceptsMultiple: false,
3891+ }),
3892+ SlashCommandNamedArgument.fromProps({
3893+ name: 'seed',
3894+ description: 'random seed',
3895+ isRequired: false,
3896+ typeList: [ARGUMENT_TYPE.NUMBER],
3897+ acceptsMultiple: false,
3898+ }),
3899+ SlashCommandNamedArgument.fromProps({
3900+ name: 'width',
3901+ description: 'image width',
3902+ isRequired: false,
3903+ typeList: [ARGUMENT_TYPE.NUMBER],
3904+ acceptsMultiple: false,
3905+ }),
3906+ SlashCommandNamedArgument.fromProps({
3907+ name: 'height',
3908+ description: 'image height',
3909+ isRequired: false,
3910+ typeList: [ARGUMENT_TYPE.NUMBER],
3911+ acceptsMultiple: false,
3912+ }),
3913+ SlashCommandNamedArgument.fromProps({
3914+ name: 'steps',
3915+ description: 'number of steps',
3916+ isRequired: false,
3917+ typeList: [ARGUMENT_TYPE.NUMBER],
3918+ acceptsMultiple: false,
3919+ }),
3920+ SlashCommandNamedArgument.fromProps({
3921+ name: 'cfg',
3922+ description: 'CFG scale',
3923+ isRequired: false,
3924+ typeList: [ARGUMENT_TYPE.NUMBER],
3925+ acceptsMultiple: false,
3926+ }),
3927+ SlashCommandNamedArgument.fromProps({
3928+ name: 'skip',
3929+ description: 'CLIP skip layers',
3930+ isRequired: false,
3931+ typeList: [ARGUMENT_TYPE.NUMBER],
3932+ acceptsMultiple: false,
3933+ }),
3934+ SlashCommandNamedArgument.fromProps({
3935+ name: 'model',
3936+ description: 'model override',
3937+ isRequired: false,
3938+ typeList: [ARGUMENT_TYPE.STRING],
3939+ acceptsMultiple: false,
3940+ forceEnum: true,
3941+ enumProvider: getSelectEnumProvider('sd_model', true),
3942+ }),
3943+ SlashCommandNamedArgument.fromProps({
3944+ name: 'sampler',
3945+ description: 'sampler override',
3946+ isRequired: false,
3947+ typeList: [ARGUMENT_TYPE.STRING],
3948+ acceptsMultiple: false,
3949+ forceEnum: true,
3950+ enumProvider: getSelectEnumProvider('sd_sampler', false),
3951+ }),
3952+ SlashCommandNamedArgument.fromProps({
3953+ name: 'scheduler',
3954+ description: 'scheduler override',
3955+ isRequired: false,
3956+ typeList: [ARGUMENT_TYPE.STRING],
3957+ acceptsMultiple: false,
3958+ forceEnum: true,
3959+ enumProvider: getSelectEnumProvider('sd_scheduler', false),
3960+ }),
3961+ SlashCommandNamedArgument.fromProps({
3962+ name: 'vae',
3963+ description: 'VAE name override',
3964+ isRequired: false,
3965+ typeList: [ARGUMENT_TYPE.STRING],
3966+ acceptsMultiple: false,
3967+ forceEnum: true,
3968+ enumProvider: getSelectEnumProvider('sd_vae', false),
3969+ }),
3970+ SlashCommandNamedArgument.fromProps({
3971+ name: 'upscaler',
3972+ description: 'upscaler override',
3973+ isRequired: false,
3974+ typeList: [ARGUMENT_TYPE.STRING],
3975+ acceptsMultiple: false,
3976+ forceEnum: true,
3977+ enumProvider: getSelectEnumProvider('sd_hr_upscaler', false),
3978+ }),
3979+ SlashCommandNamedArgument.fromProps({
3980+ name: 'hires',
3981+ description: 'enable high-res fix',
3982+ isRequired: false,
3983+ typeList: [ARGUMENT_TYPE.BOOLEAN],
3984+ acceptsMultiple: false,
3985+ enumProvider: commonEnumProviders.boolean('trueFalse'),
3986+ }),
3987+ SlashCommandNamedArgument.fromProps({
3988+ name: 'scale',
3989+ description: 'upscale amount',
3990+ isRequired: false,
3991+ typeList: [ARGUMENT_TYPE.NUMBER],
3992+ acceptsMultiple: false,
3993+ }),
3994+ SlashCommandNamedArgument.fromProps({
3995+ name: 'denoise',
3996+ description: 'denoising strength',
3997+ isRequired: false,
3998+ typeList: [ARGUMENT_TYPE.NUMBER],
3999+ acceptsMultiple: false,
4000+ }),
4001+ SlashCommandNamedArgument.fromProps({
4002+ name: '2ndpass',
4003+ description: 'second pass steps',
4004+ isRequired: false,
4005+ typeList: [ARGUMENT_TYPE.NUMBER],
4006+ acceptsMultiple: false,
4007+ }),
4008+ SlashCommandNamedArgument.fromProps({
4009+ name: 'faces',
4010+ description: 'restore faces',
4011+ isRequired: false,
4012+ typeList: [ARGUMENT_TYPE.BOOLEAN],
4013+ acceptsMultiple: false,
4014+ enumProvider: commonEnumProviders.boolean('trueFalse'),
38064015 }),
38074016 ],
38084017 unnamedArgumentList: [
@@ -3824,6 +4033,66 @@ jQuery(async () => {
38244033 }));
38254034
38264035 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
4036+ name: 'imagine-source',
4037+ aliases: ['sd-source', 'img-source'],
4038+ returns: 'a name of the current generation source',
4039+ unnamedArgumentList: [
4040+ SlashCommandArgument.fromProps({
4041+ description: 'source name',
4042+ typeList: [ARGUMENT_TYPE.STRING],
4043+ isRequired: false,
4044+ forceEnum: true,
4045+ enumProvider: getSelectEnumProvider('sd_source', true),
4046+ }),
4047+ ],
4048+ helpString: 'If an argument is provided, change the source of the image generation, e.g. <code>/imagine-source comfy</code>. Returns the current source.',
4049+ callback: async (_args, name) => {
4050+ if (!name) {
4051+ return extension_settings.sd.source;
4052+ }
4053+ const isKnownSource = Object.keys(sources).includes(String(name));
4054+ if (!isKnownSource) {
4055+ throw new Error('The value provided is not a valid image generation source.');
4056+ }
4057+ const option = document.querySelector(`#sd_source [value="${name}"]`);
4058+ if (!(option instanceof HTMLOptionElement)) {
4059+ throw new Error('Could not find the source option in the dropdown.');
4060+ }
4061+ option.selected = true;
4062+ await onSourceChange();
4063+ return extension_settings.sd.source;
4064+ },
4065+ }));
4066+
4067+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
4068+ name: 'imagine-style',
4069+ aliases: ['sd-style', 'img-style'],
4070+ returns: 'a name of the current style',
4071+ unnamedArgumentList: [
4072+ SlashCommandArgument.fromProps({
4073+ description: 'style name',
4074+ typeList: [ARGUMENT_TYPE.STRING],
4075+ isRequired: false,
4076+ forceEnum: true,
4077+ enumProvider: getSelectEnumProvider('sd_style', false),
4078+ }),
4079+ ],
4080+ helpString: 'If an argument is provided, change the style of the image generation, e.g. <code>/imagine-style MyStyle</code>. Returns the current style.',
4081+ callback: async (_args, name) => {
4082+ if (!name) {
4083+ return extension_settings.sd.style;
4084+ }
4085+ const option = document.querySelector(`#sd_style [value="${name}"]`);
4086+ if (!(option instanceof HTMLOptionElement)) {
4087+ throw new Error('Could not find the style option in the dropdown.');
4088+ }
4089+ option.selected = true;
4090+ onStyleSelect();
4091+ return extension_settings.sd.style;
4092+ },
4093+ }));
4094+
4095+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
38274096 name: 'imagine-comfy-workflow',
38284097 callback: changeComfyWorkflow,
38294098 aliases: ['icw'],
@@ -3832,7 +4101,7 @@ jQuery(async () => {
38324101 description: 'workflow name',
38334102 typeList: [ARGUMENT_TYPE.STRING],
38344103 isRequired: true,
3835- enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(workflow => new SlashCommandEnumValue(workflow)),
4104+ enumProvider: getSelectEnumProvider('sd_comfy_workflow', false),
38364105 }),
38374106 ],
38384107 helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g. <pre><code>/imagine-comfy-workflow MyWorkflow</code></pre>',
@@ -3874,7 +4143,6 @@ jQuery(async () => {
38744143 $('#sd_hr_scale').on('input', onHrScaleInput);
38754144 $('#sd_denoising_strength').on('input', onDenoisingStrengthInput);
38764145 $('#sd_hr_second_pass_steps').on('input', onHrSecondPassStepsInput);
3877- $('#sd_novel_upscale_ratio').on('input', onNovelUpscaleRatioInput);
38784146 $('#sd_novel_anlas_guard').on('input', onNovelAnlasGuardInput);
38794147 $('#sd_novel_view_anlas').on('click', onViewAnlasClick);
38804148 $('#sd_novel_sm').on('input', onNovelSmInput);
@@ -3887,7 +4155,6 @@ jQuery(async () => {
38874155 $('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick);
38884156 $('#sd_comfy_new_workflow').on('click', onComfyNewWorkflowClick);
38894157 $('#sd_comfy_delete_workflow').on('click', onComfyDeleteWorkflowClick);
3890- $('#sd_expand').on('input', onExpandInput);
38914158 $('#sd_style').on('change', onStyleSelect);
38924159 $('#sd_save_style').on('click', onSaveStyleClick);
38934160 $('#sd_delete_style').on('click', onDeleteStyleClick);
public/scripts/extensions/stable-diffusion/settings.html+1 -14
@@ -27,11 +27,6 @@
2727 <span data-i18n="sd_free_extend_txt">Extend free mode prompts</span>
2828 <small data-i18n="sd_free_extend_small">(interactive/commands)</small>
2929 </label>
30- <label for="sd_expand" class="checkbox_label" data-i18n="[title]sd_expand" title="Automatically extend prompts using text generation model">
31- <input id="sd_expand" type="checkbox" />
32- <span data-i18n="sd_expand_txt">Auto-extend prompts</span>
33- <span class="right_menu_button fa-solid fa-triangle-exclamation" data-i18n="[title]sd_expand_warning" title="May produce unexpected results. Manual prompt editing is recommended."></span>
34- </label>
3530 <label for="sd_snap" class="checkbox_label" data-i18n="[title]sd_snap" title="Snap generation requests with a forced aspect ratio (portraits, backgrounds) to the nearest known resolution, while trying to preserve the absolute pixel counts (recommended for SDXL).">
3631 <input id="sd_snap" type="checkbox" />
3732 <span data-i18n="sd_snap_txt">Snap auto-adjusted resolutions</span>
@@ -308,7 +303,7 @@
308303 </div>
309304
310305 <div class="flex-container">
311306 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,drawthings,novel">
312307 <small>
313308 <span data-i18n="Upscale by">Upscale by</span>
314309 </small>
@@ -332,14 +327,6 @@
332327 <input class="neo-range-input" type="number" id="sd_hr_second_pass_steps_value" data-for="sd_hr_second_pass_steps" max="{{hr_second_pass_steps_max}}" step="{{hr_second_pass_steps_step}}" value="{{hr_second_pass_steps}}" >
333328 </div>
334329
335- <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="novel">
336- <small>
337- <span data-i18n="Upscale by">Upscale by</span>
338- </small>
339- <input class="neo-range-slider" type="range" id="sd_novel_upscale_ratio" name="sd_novel_upscale_ratio" min="{{novel_upscale_ratio_min}}" max="{{novel_upscale_ratio_max}}" step="{{novel_upscale_ratio_step}}" value="{{novel_upscale_ratio}}" >
340- <input class="neo-range-input" type="number" id="sd_novel_upscale_ratio_value" data-for="sd_novel_upscale_ratio" min="{{novel_upscale_ratio_min}}" max="{{novel_upscale_ratio_max}}" step="{{novel_upscale_ratio_step}}" value="{{novel_upscale_ratio}}" >
341- </div>
342-
343330 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras">
344331 <small>
345332 <span data-i18n="CLIP Skip">CLIP Skip</span>
public/scripts/instruct-mode.js+4 -0
@@ -384,6 +384,10 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
384384 * @returns {string} Formatted instruct mode system prompt.
385385 */
386386export function formatInstructModeSystemPrompt(systemPrompt) {
387+ if (!systemPrompt) {
388+ return '';
389+ }
390+
387391 const separator = power_user.instruct.wrap ? '\n' : '';
388392
389393 if (power_user.instruct.system_sequence_prefix) {
public/scripts/slash-commands.js+99 -44
@@ -70,6 +70,7 @@ import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
7070import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
7171import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
7272import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
73+import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
7374export {
7475 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
7576};
@@ -242,6 +243,7 @@ export function initDefaultSlashCommands() {
242243 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
243244 name: 'sendas',
244245 callback: sendMessageAs,
246+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
245247 namedArgumentList: [
246248 SlashCommandNamedArgument.fromProps({
247249 name: 'name',
@@ -269,6 +271,14 @@ export function initDefaultSlashCommands() {
269271 typeList: [ARGUMENT_TYPE.NUMBER],
270272 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
271273 }),
274+ SlashCommandNamedArgument.fromProps({
275+ name: 'return',
276+ description: 'The way how you want the return value to be provided',
277+ typeList: [ARGUMENT_TYPE.STRING],
278+ defaultValue: 'none',
279+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
280+ forceEnum: true,
281+ }),
272282 ],
273283 unnamedArgumentList: [
274284 new SlashCommandArgument(
@@ -301,6 +311,7 @@ export function initDefaultSlashCommands() {
301311 name: 'sys',
302312 callback: sendNarratorMessage,
303313 aliases: ['nar'],
314+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
304315 namedArgumentList: [
305316 new SlashCommandNamedArgument(
306317 'compact',
@@ -316,6 +327,14 @@ export function initDefaultSlashCommands() {
316327 typeList: [ARGUMENT_TYPE.NUMBER],
317328 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
318329 }),
330+ SlashCommandNamedArgument.fromProps({
331+ name: 'return',
332+ description: 'The way how you want the return value to be provided',
333+ typeList: [ARGUMENT_TYPE.STRING],
334+ defaultValue: 'none',
335+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
336+ forceEnum: true,
337+ }),
319338 ],
320339 unnamedArgumentList: [
321340 new SlashCommandArgument(
@@ -355,6 +374,7 @@ export function initDefaultSlashCommands() {
355374 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
356375 name: 'comment',
357376 callback: sendCommentMessage,
377+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
358378 namedArgumentList: [
359379 new SlashCommandNamedArgument(
360380 'compact',
@@ -370,6 +390,14 @@ export function initDefaultSlashCommands() {
370390 typeList: [ARGUMENT_TYPE.NUMBER],
371391 enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
372392 }),
393+ SlashCommandNamedArgument.fromProps({
394+ name: 'return',
395+ description: 'The way how you want the return value to be provided',
396+ typeList: [ARGUMENT_TYPE.STRING],
397+ defaultValue: 'none',
398+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
399+ forceEnum: true,
400+ }),
373401 ],
374402 unnamedArgumentList: [
375403 new SlashCommandArgument(
@@ -509,7 +537,7 @@ export function initDefaultSlashCommands() {
509537 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
510538 name: 'ask',
511539 callback: askCharacter,
512- returns: 'the generated text',
540+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
513541 namedArgumentList: [
514542 SlashCommandNamedArgument.fromProps({
515543 name: 'name',
@@ -518,6 +546,14 @@ export function initDefaultSlashCommands() {
518546 isRequired: true,
519547 enumProvider: commonEnumProviders.characters('character'),
520548 }),
549+ SlashCommandNamedArgument.fromProps({
550+ name: 'return',
551+ description: 'The way how you want the return value to be provided',
552+ typeList: [ARGUMENT_TYPE.STRING],
553+ defaultValue: 'pipe',
554+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
555+ forceEnum: true,
556+ }),
521557 ],
522558 unnamedArgumentList: [
523559 new SlashCommandArgument(
@@ -556,6 +592,7 @@ export function initDefaultSlashCommands() {
556592 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
557593 name: 'send',
558594 callback: sendUserMessageCallback,
595+ returns: 'Optionally the text of the sent message, if specified in the "return" argument',
559596 namedArgumentList: [
560597 new SlashCommandNamedArgument(
561598 'compact',
@@ -578,6 +615,14 @@ export function initDefaultSlashCommands() {
578615 defaultValue: '{{user}}',
579616 enumProvider: commonEnumProviders.personas,
580617 }),
618+ SlashCommandNamedArgument.fromProps({
619+ name: 'return',
620+ description: 'The way how you want the return value to be provided',
621+ typeList: [ARGUMENT_TYPE.STRING],
622+ defaultValue: 'none',
623+ enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
624+ forceEnum: true,
625+ }),
581626 ],
582627 unnamedArgumentList: [
583628 new SlashCommandArgument(
@@ -1568,12 +1613,21 @@ export function initDefaultSlashCommands() {
15681613 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
15691614 name: 'listinjects',
15701615 callback: listInjectsCallback,
15711616 helpString: 'Lists all script injections for the current chat. Displays injects in a popup by default. Use the <code>formatreturn</code> argument to change the outputreturn formattype.',
15721617 returns: 'Optionalls the JSON object of script injections',
15731618 namedArgumentList: [
15741619 SlashCommandNamedArgument.fromProps({
1620+ name: 'return',
1621+ description: 'The way how you want the return value to be provided',
1622+ typeList: [ARGUMENT_TYPE.STRING],
1623+ defaultValue: 'popup-html',
1624+ enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
1625+ forceEnum: true,
1626+ }),
1627+ // TODO remove some day
1628+ SlashCommandNamedArgument.fromProps({
15751629 name: 'format',
15761630 description: '!!! DEPRECATED - use "return" instead !!! output format',
15771631 typeList: [ARGUMENT_TYPE.STRING],
15781632 isRequired: true,
15791633 forceEnum: true,
@@ -1842,37 +1896,43 @@ function injectCallback(args, value) {
18421896}
18431897
18441898async function listInjectsCallback(args) {
1899+ /** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
1900+ let returnType = args.return;
1901+
1902+ // Old legacy return type handling
1903+ if (args.format) {
1904+ toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
18451905 const type = String(args?.format).toLowerCase().trim();
18461906 if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) {
18471907 type !== 'none' && toastr.info('No script injections for the current chat');
1848- return JSON.stringify({});
18491908 }
1850-
1851- const injects = Object.entries(chat_metadata.script_injects)
1852- .map(([id, inject]) => {
1853- const position = Object.entries(extension_prompt_types);
1854- const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';
1855- return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;
1856- })
1857- .join('\n');
1858-
1859- const converter = new showdown.Converter();
1860- const messageText = `### Script injections:\n${injects}`;
1861- const htmlMessage = DOMPurify.sanitize(converter.makeHtml(messageText));
1862-
18631909 switch (type) {
18641910 case 'none':
1911+ returnType = 'none';
18651912 break;
18661913 case 'chat':
1867- sendSystemMessage(system_message_types.GENERIC, htmlMessage);
1914+ returnType = 'chat-html';
18681915 break;
18691916 case 'popup':
18701917 default:
1871- await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
1918+ returnType = 'popup-html';
18721919 break;
18731920 }
1921+ }
18741922
1875- return JSON.stringify(chat_metadata.script_injects);
1923+ // Now the actual new return type handling
1924+ const buildTextValue = (injects) => {
1925+ const injectsStr = Object.entries(injects)
1926+ .map(([id, inject]) => {
1927+ const position = Object.entries(extension_prompt_types);
1928+ const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';
1929+ return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;
1930+ })
1931+ .join('\n');
1932+ return `### Script injections:\n${injectsStr || 'No script injections for the current chat'}`;
1933+ };
1934+
1935+ return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', chat_metadata.script_injects ?? {}, { objectToStringFunc: buildTextValue });
18761936}
18771937
18781938/**
@@ -2559,7 +2619,7 @@ async function askCharacter(args, text) {
25592619 // Not supported in group chats
25602620 // TODO: Maybe support group chats?
25612621 if (selected_group) {
25622622 toastr.errorwarning('Cannot run /ask command in a group chat!');
25632623 return '';
25642624 }
25652625
@@ -2633,7 +2693,9 @@ async function askCharacter(args, text) {
26332693 }
26342694 }
26352695
2636- return askResult;
2696+ const message = askResult ? chat[chat.length - 1] : null;
2697+
2698+ return await slashCommandReturnHelper.doReturn(args.return ?? 'pipe', message, { objectToStringFunc: x => x.mes });
26372699}
26382700
26392701async function hideMessageCallback(_, arg) {
@@ -2908,7 +2970,7 @@ function findPersonaByName(name) {
29082970
29092971async function sendUserMessageCallback(args, text) {
29102972 if (!text) {
29112973 consoletoastr.warnwarning('WARN:You Nomust textspecify providedtext forto /send command');
29122974 return;
29132975 }
29142976
@@ -2924,16 +2986,17 @@ async function sendUserMessageCallback(args, text) {
29242986 insertAt = chat.length + insertAt;
29252987 }
29262988
2989+ let message;
29272990 if ('name' in args) {
29282991 const name = args.name || '';
29292992 const avatar = findPersonaByName(name) || user_avatar;
29302993 message = await sendMessageAsUser(text, bias, insertAt, compact, name, avatar);
29312994 }
29322995 else {
29332996 message = await sendMessageAsUser(text, bias, insertAt, compact);
29342997 }
29352998
2936- return '';
2999+ return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
29373000}
29383001
29393002async function deleteMessagesByNameCallback(_, name) {
@@ -3027,7 +3090,7 @@ async function continueChatCallback(args, prompt) {
30273090 resolve();
30283091 } catch (error) {
30293092 console.error('Error running /continue command:', error);
30303093 reject(error);
30313094 }
30323095 });
30333096
@@ -3221,30 +3284,20 @@ export function getNameAndAvatarForMessage(character, name = null) {
32213284
32223285export async function sendMessageAs(args, text) {
32233286 if (!text) {
3287+ toastr.warning('You must specify text to send as');
32243288 return '';
32253289 }
32263290
3227- let name;
3291+ let name = args.name?.trim();
32283292 let mesText;
32293293
32303294 if (args.!name) {
3231- name = args.name.trim();
3232-
3233- if (!name && !text) {
3234- toastr.warning('You must specify a name and text to send as');
3235- return '';
3236- }
3237- } else {
32383295 const namelessWarningKey = 'sendAsNamelessWarningShown';
32393296 if (localStorage.getItem(namelessWarningKey) !== 'true') {
32403297 toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 });
32413298 localStorage.setItem(namelessWarningKey, 'true');
32423299 }
32433300 name = name2;
3244- if (!text) {
3245- toastr.warning('You must specify text to send as');
3246- return '';
3247- }
32483301 }
32493302
32503303 mesText = text.trim();
@@ -3321,11 +3374,12 @@ export async function sendMessageAs(args, text) {
33213374 await saveChatConditional();
33223375 }
33233376
3324- return '';
3377+ return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
33253378}
33263379
33273380export async function sendNarratorMessage(args, text) {
33283381 if (!text) {
3382+ toastr.warning('You must specify text to send');
33293383 return '';
33303384 }
33313385
@@ -3374,7 +3428,7 @@ export async function sendNarratorMessage(args, text) {
33743428 await saveChatConditional();
33753429 }
33763430
3377- return '';
3431+ return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
33783432}
33793433
33803434export async function promptQuietForLoudResponse(who, text) {
@@ -3420,6 +3474,7 @@ export async function promptQuietForLoudResponse(who, text) {
34203474
34213475async function sendCommentMessage(args, text) {
34223476 if (!text) {
3477+ toastr.warning('You must specify text to send');
34233478 return '';
34243479 }
34253480
@@ -3462,7 +3517,7 @@ async function sendCommentMessage(args, text) {
34623517 await saveChatConditional();
34633518 }
34643519
3465- return '';
3520+ return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
34663521}
34673522
34683523/**
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+30 -0
@@ -36,6 +36,7 @@ export const enumIcons = {
3636 message: '💬',
3737 voice: '🎤',
3838 server: '🖥️',
39+ popup: '🗔',
3940
4041 true: '✔️',
4142 false: '❌',
@@ -153,6 +154,35 @@ export const commonEnumProviders = {
153154 },
154155
155156 /**
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+ /**
156186 * All possible char entities, like characters and groups. Can be filtered down to just one type.
157187 *
158188 * @param {('all' | 'character' | 'group')?} [mode='all'] - Which type to return
public/scripts/slash-commands/SlashCommandReturnHelper.js+80 -0
@@ -0,0 +1,80 @@
1+import { sendSystemMessage, system_message_types } from '../../script.js';
2+import { callGenericPopup, POPUP_TYPE } from '../popup.js';
3+import { escapeHtml } from '../utils.js';
4+import { enumIcons } from './SlashCommandCommonEnumsProvider.js';
5+import { enumTypes, SlashCommandEnumValue } from './SlashCommandEnumValue.js';
6+
7+/** @typedef {'pipe'|'object'|'chat-html'|'chat-text'|'popup-html'|'popup-text'|'toast-html'|'toast-text'|'console'|'none'} SlashCommandReturnType */
8+
9+export const slashCommandReturnHelper = {
10+ // Without this, VSCode formatter fucks up JS docs. Don't ask me why.
11+ _: false,
12+
13+ /**
14+ * Gets/creates the enum list of types of return relevant for a slash command
15+ *
16+ * @param {object} [options={}] Options
17+ * @param {boolean} [options.allowPipe=true] Allow option to pipe the return value
18+ * @param {boolean} [options.allowObject=false] Allow option to return the value as an object
19+ * @param {boolean} [options.allowChat=false] Allow option to return the value as a chat message
20+ * @param {boolean} [options.allowPopup=false] Allow option to return the value as a popup
21+ * @param {boolean}[options.allowTextVersion=true] Used in combination with chat/popup/toast, some of them do not make sense for text versions, e.g.if you are building a HTML string anyway
22+ * @returns {SlashCommandEnumValue[]} The enum list
23+ */
24+ enumList: ({ allowPipe = true, allowObject = false, allowChat = false, allowPopup = false, allowTextVersion = true } = {}) => [
25+ allowPipe && new SlashCommandEnumValue('pipe', 'Return to the pipe for the next command', enumTypes.name, '|'),
26+ allowObject && new SlashCommandEnumValue('object', 'Return as an object (or array) to the pipe for the next command', enumTypes.variable, enumIcons.dictionary),
27+ allowChat && new SlashCommandEnumValue('chat-html', 'Sending a chat message with the return value - Can display HTML', enumTypes.command, enumIcons.message),
28+ allowChat && allowTextVersion && new SlashCommandEnumValue('chat-text', 'Sending a chat message with the return value - Will only display as text', enumTypes.qr, enumIcons.message),
29+ allowPopup && new SlashCommandEnumValue('popup-html', 'Showing as a popup with the return value - Can display HTML', enumTypes.command, enumIcons.popup),
30+ allowPopup && allowTextVersion && new SlashCommandEnumValue('popup-text', 'Showing as a popup with the return value - Will only display as text', enumTypes.qr, enumIcons.popup),
31+ new SlashCommandEnumValue('toast-html', 'Show the return value as a toast notification - Can display HTML', enumTypes.command, 'ℹ️'),
32+ allowTextVersion && new SlashCommandEnumValue('toast-text', 'Show the return value as a toast notification - Will only display as text', enumTypes.qr, 'ℹ️'),
33+ new SlashCommandEnumValue('console', 'Log the return value (object, if it can be one) to the console', enumTypes.enum, '>'),
34+ new SlashCommandEnumValue('none', 'No return value'),
35+ ].filter(x => !!x),
36+
37+ /**
38+ * Handles the return value based on the specified type
39+ *
40+ * @param {SlashCommandReturnType} type The type of return
41+ * @param {object|number|string} value The value to return
42+ * @param {object} [options={}] Options
43+ * @param {(o: object) => string} [options.objectToStringFunc=null] Function to convert the object to a string, if object was provided and 'object' was not the chosen return type
44+ * @param {(o: object) => string} [options.objectToHtmlFunc=null] Analog to 'objectToStringFunc', which will be used here if not provided - but can do a different string layout if HTML is requested
45+ * @returns {Promise<*>} The processed return value
46+ */
47+ async doReturn(type, value, { objectToStringFunc = o => o?.toString(), objectToHtmlFunc = null } = {}) {
48+ const shouldHtml = type.endsWith('html');
49+ const actualConverterFunc = shouldHtml && objectToHtmlFunc ? objectToHtmlFunc : objectToStringFunc;
50+ const stringValue = typeof value !== 'string' ? actualConverterFunc(value) : value;
51+
52+ switch (type) {
53+ case 'popup-html':
54+ case 'popup-text':
55+ case 'chat-text':
56+ case 'chat-html':
57+ case 'toast-text':
58+ case 'toast-html': {
59+ const htmlOrNotHtml = shouldHtml ? DOMPurify.sanitize((new showdown.Converter()).makeHtml(stringValue)) : escapeHtml(stringValue);
60+
61+ if (type.startsWith('popup')) await callGenericPopup(htmlOrNotHtml, POPUP_TYPE.TEXT);
62+ if (type.startsWith('chat')) sendSystemMessage(system_message_types.GENERIC, htmlOrNotHtml);
63+ if (type.startsWith('toast')) toastr.info(htmlOrNotHtml, null, { escapeHtml: !shouldHtml });
64+
65+ return '';
66+ }
67+ case 'pipe':
68+ return stringValue ?? '';
69+ case 'object':
70+ return JSON.stringify(value);
71+ case 'console':
72+ console.info(value);
73+ return '';
74+ case 'none':
75+ return '';
76+ default:
77+ throw new Error(`Unknown return type: ${type}`);
78+ }
79+ },
80+};
public/scripts/variables.js+119 -80
@@ -11,6 +11,7 @@ import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureR
1111import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
1212import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
1313import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
14+import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
1415import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
1516import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
1617
@@ -305,7 +306,28 @@ export function replaceVariableMacros(input) {
305306}
306307
307308async function listVariablesCallback(args) {
308- const type = String(args?.format || '').toLowerCase().trim() || 'popup';
309+ /** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
310+ let returnType = args.return;
311+
312+ // Old legacy return type handling
313+ if (args.format) {
314+ toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
315+ const type = String(args?.format).toLowerCase().trim();
316+ switch (type) {
317+ case 'none':
318+ returnType = 'none';
319+ break;
320+ case 'chat':
321+ returnType = 'chat-html';
322+ break;
323+ case 'popup':
324+ default:
325+ returnType = 'popup-html';
326+ break;
327+ }
328+ }
329+
330+ // Now the actual new return type handling
309331 const scope = String(args?.scope || '').toLowerCase().trim() || 'all';
310332 if (!chat_metadata.variables) {
311333 chat_metadata.variables = {};
@@ -317,35 +339,24 @@ async function listVariablesCallback(args) {
317339 const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : [];
318340 const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : [];
319341
320342 const jsonVariablesbuildTextValue = [(_) => {
321- ...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })),
322- ...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })),
323- ];
324-
325343 const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';
326344 const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
327345 const chatName = getCurrentChatId();
328346
329- const converter = new showdown.Converter();
330347 const message = [
331348 includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
332349 includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
333350 ].filter(x => x).join('\n\n');
334- const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message));
351+ return message;
352+ };
335353
336- switch (type) {
354+ const jsonVariables = [
337- case 'none':
355+ ...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })),
338- break;
356+ ...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })),
339- case 'chat':
357+ ];
340- sendSystemMessage(system_message_types.GENERIC, htmlMessage);
341- break;
342- case 'popup':
343- default:
344- await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
345- break;
346- }
347358
348- return JSON.stringify(jsonVariables);
359+ return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', jsonVariables, { objectToStringFunc: buildTextValue });
349360}
350361
351362/**
@@ -669,8 +680,8 @@ function deleteGlobalVariable(name) {
669680}
670681
671682/**
672683 * Parses a series of numeric values from a string or a string array.
673684 * @param {string|string[]} value A space-separated list of numeric values or variable names
674685 * @param {SlashCommandScope} scope Scope
675686 * @returns {number[]} An array of numeric values
676687 */
@@ -679,11 +690,17 @@ function parseNumericSeries(value, scope = null) {
679690 return [value];
680691 }
681692
682- const array = value
693+ /** @type {(string|number)[]} */
683- .split(' ')
694+ let values = Array.isArray(value) ? value : value.split(' ');
684- .map(i => i.trim())
695+
696+ // If a JSON array was provided as the only value, convert it to an array
697+ if (values.length === 1 && typeof values[0] === 'string' && values[0].startsWith('[')) {
698+ values = convertValueType(values[0], 'array');
699+ }
700+
701+ const array = values.map(i => typeof i === 'string' ? i.trim() : i)
685702 .filter(i => i !== '')
686703 .map(i => isNaN(Number(i)) ? Number(resolveVariable(String(i), scope)) : Number(i))
687704 .filter(i => !isNaN(i));
688705
689706 return array;
@@ -703,7 +720,7 @@ function performOperation(value, operation, singleOperand = false, scope = null)
703720
704721 const result = singleOperand ? operation(array[0]) : operation(array);
705722
706723 if (isNaN(result) || !isFinite(result)) {
707724 return 0;
708725 }
709726
@@ -731,7 +748,7 @@ function maxValuesCallback(args, value) {
731748}
732749
733750function subValuesCallback(args, value) {
734751 return performOperation(value, (array) => array[0].reduce((a, b) => a - b, array[1].shift() ?? 0), false, args._scope);
735752}
736753
737754function divValuesCallback(args, value) {
@@ -910,7 +927,7 @@ export function registerVariableCommands() {
910927 name: 'listvar',
911928 callback: listVariablesCallback,
912929 aliases: ['listchatvar'],
913930 helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>formatreturn</code> argument to change the outputreturn formattype.',
914931 returns: 'JSON list of local variables',
915932 namedArgumentList: [
916933 SlashCommandNamedArgument.fromProps({
@@ -927,8 +944,17 @@ export function registerVariableCommands() {
927944 ],
928945 }),
929946 SlashCommandNamedArgument.fromProps({
947+ name: 'return',
948+ description: 'The way how you want the return value to be provided',
949+ typeList: [ARGUMENT_TYPE.STRING],
950+ defaultValue: 'popup-html',
951+ enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
952+ forceEnum: true,
953+ }),
954+ // TODO remove some day
955+ SlashCommandNamedArgument.fromProps({
930956 name: 'format',
931957 description: '!!! DEPRECATED - use "return" instead !!! output format',
932958 typeList: [ARGUMENT_TYPE.STRING],
933959 isRequired: true,
934960 forceEnum: true,
@@ -1595,36 +1621,15 @@ export function registerVariableCommands() {
15951621 }));
15961622 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
15971623 name: 'add',
15981624 callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')),
15991625 returns: 'sum of the provided values',
16001626 unnamedArgumentList: [
16011627 SlashCommandArgument.fromProps({
16021628 description: 'values to sum',
16031629 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
16041630 isRequired: true,
16051631 acceptsMultiple: true,
1606- enumProvider: (executor, scope) => {
1632+ enumProvider: commonEnumProviders.numbersAndVariables,
1607- const vars = commonEnumProviders.variables('all')(executor, scope);
1608- vars.push(
1609- new SlashCommandEnumValue(
1610- 'any variable name',
1611- null,
1612- enumTypes.variable,
1613- enumIcons.variable,
1614- (input) => /^\w*$/.test(input),
1615- (input) => input,
1616- ),
1617- new SlashCommandEnumValue(
1618- 'any number',
1619- null,
1620- enumTypes.number,
1621- enumIcons.number,
1622- (input) => input == '' || !Number.isNaN(Number(input)),
1623- (input) => input,
1624- ),
1625- );
1626- return vars;
1627- },
16281633 forceEnum: false,
16291634 }),
16301635 ],
@@ -1632,7 +1637,9 @@ export function registerVariableCommands() {
16321637 helpString: `
16331638 <div>
16341639 Performs an addition of the set of values and passes the result down the pipe.
1635- Can use variable names.
1640+ </div>
1641+ <div>
1642+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
16361643 </div>
16371644 <div>
16381645 <strong>Example:</strong>
@@ -1640,6 +1647,9 @@ export function registerVariableCommands() {
16401647 <li>
16411648 <pre><code class="language-stscript">/add 10 i 30 j</code></pre>
16421649 </li>
1650+ <li>
1651+ <pre><code class="language-stscript">/add ["count", 15, 2, "i"]</code></pre>
1652+ </li>
16431653 </ul>
16441654 </div>
16451655 `,
@@ -1651,16 +1661,20 @@ export function registerVariableCommands() {
16511661 unnamedArgumentList: [
16521662 SlashCommandArgument.fromProps({
16531663 description: 'values to multiply',
16541664 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
16551665 isRequired: true,
16561666 acceptsMultiple: true,
16571667 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
16581668 forceEnum: false,
16591669 }),
16601670 ],
1671+ splitUnnamedArgument: true,
16611672 helpString: `
16621673 <div>
16631674 Performs a multiplication of the set of values and passes the result down the pipe. Can use variable names.
1675+ </div>
1676+ <div>
1677+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
16641678 </div>
16651679 <div>
16661680 <strong>Examples:</strong>
@@ -1668,6 +1682,9 @@ export function registerVariableCommands() {
16681682 <li>
16691683 <pre><code class="language-stscript">/mul 10 i 30 j</code></pre>
16701684 </li>
1685+ <li>
1686+ <pre><code class="language-stscript">/mul ["count", 15, 2, "i"]</code></pre>
1687+ </li>
16711688 </ul>
16721689 </div>
16731690 `,
@@ -1679,16 +1696,20 @@ export function registerVariableCommands() {
16791696 unnamedArgumentList: [
16801697 SlashCommandArgument.fromProps({
16811698 description: 'values to find the max',
16821699 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
16831700 isRequired: true,
16841701 acceptsMultiple: true,
16851702 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
16861703 forceEnum: false,
16871704 }),
16881705 ],
1706+ splitUnnamedArgument: true,
16891707 helpString: `
16901708 <div>
16911709 Returns the maximum value of the set of values and passes the result down the pipe. Can use variable names.
1710+ </div>
1711+ <div>
1712+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
16921713 </div>
16931714 <div>
16941715 <strong>Examples:</strong>
@@ -1696,6 +1717,9 @@ export function registerVariableCommands() {
16961717 <li>
16971718 <pre><code class="language-stscript">/max 10 i 30 j</code></pre>
16981719 </li>
1720+ <li>
1721+ <pre><code class="language-stscript">/max ["count", 15, 2, "i"]</code></pre>
1722+ </li>
16991723 </ul>
17001724 </div>
17011725 `,
@@ -1707,17 +1731,20 @@ export function registerVariableCommands() {
17071731 unnamedArgumentList: [
17081732 SlashCommandArgument.fromProps({
17091733 description: 'values to find the min',
17101734 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
17111735 isRequired: true,
17121736 acceptsMultiple: true,
17131737 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
17141738 forceEnum: false,
17151739 }),
17161740 ],
1741+ splitUnnamedArgument: true,
17171742 helpString: `
17181743 <div>
17191744 Returns the minimum value of the set of values and passes the result down the pipe.
1720- Can use variable names.
1745+ </div>
1746+ <div>
1747+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
17211748 </div>
17221749 <div>
17231750 <strong>Example:</strong>
@@ -1725,6 +1752,9 @@ export function registerVariableCommands() {
17251752 <li>
17261753 <pre><code class="language-stscript">/min 10 i 30 j</code></pre>
17271754 </li>
1755+ <li>
1756+ <pre><code class="language-stscript">/min ["count", 15, 2, "i"]</code></pre>
1757+ </li>
17281758 </ul>
17291759 </div>
17301760 `,
@@ -1735,18 +1765,21 @@ export function registerVariableCommands() {
17351765 returns: 'difference of the provided values',
17361766 unnamedArgumentList: [
17371767 SlashCommandArgument.fromProps({
17381768 description: 'values to findsubtract, starting form the differencefirst provided value',
17391769 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
17401770 isRequired: true,
17411771 acceptsMultiple: true,
17421772 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
17431773 forceEnum: false,
17441774 }),
17451775 ],
1776+ splitUnnamedArgument: true,
17461777 helpString: `
17471778 <div>
17481779 Performs a subtraction of the set of values and passes the result down the pipe.
1749- Can use variable names.
1780+ </div>
1781+ <div>
1782+ Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
17501783 </div>
17511784 <div>
17521785 <strong>Example:</strong>
@@ -1754,6 +1787,9 @@ export function registerVariableCommands() {
17541787 <li>
17551788 <pre><code class="language-stscript">/sub i 5</code></pre>
17561789 </li>
1790+ <li>
1791+ <pre><code class="language-stscript">/sub ["count", 4, "i"]</code></pre>
1792+ </li>
17571793 </ul>
17581794 </div>
17591795 `,
@@ -1767,17 +1803,18 @@ export function registerVariableCommands() {
17671803 description: 'dividend',
17681804 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
17691805 isRequired: true,
17701806 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
17711807 forceEnum: false,
17721808 }),
17731809 SlashCommandArgument.fromProps({
17741810 description: 'divisor',
17751811 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
17761812 isRequired: true,
17771813 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
17781814 forceEnum: false,
17791815 }),
17801816 ],
1817+ splitUnnamedArgument: true,
17811818 helpString: `
17821819 <div>
17831820 Performs a division of two values and passes the result down the pipe.
@@ -1802,17 +1839,18 @@ export function registerVariableCommands() {
18021839 description: 'dividend',
18031840 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18041841 isRequired: true,
18051842 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18061843 forceEnum: false,
18071844 }),
18081845 SlashCommandArgument.fromProps({
18091846 description: 'divisor',
18101847 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18111848 isRequired: true,
18121849 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18131850 forceEnum: false,
18141851 }),
18151852 ],
1853+ splitUnnamedArgument: true,
18161854 helpString: `
18171855 <div>
18181856 Performs a modulo operation of two values and passes the result down the pipe.
@@ -1837,17 +1875,18 @@ export function registerVariableCommands() {
18371875 description: 'base',
18381876 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18391877 isRequired: true,
18401878 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18411879 forceEnum: false,
18421880 }),
18431881 SlashCommandArgument.fromProps({
18441882 description: 'exponent',
18451883 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18461884 isRequired: true,
18471885 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18481886 forceEnum: false,
18491887 }),
18501888 ],
1889+ splitUnnamedArgument: true,
18511890 helpString: `
18521891 <div>
18531892 Performs a power operation of two values and passes the result down the pipe.
@@ -1872,7 +1911,7 @@ export function registerVariableCommands() {
18721911 description: 'value',
18731912 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
18741913 isRequired: true,
18751914 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
18761915 forceEnum: false,
18771916 }),
18781917 ],
@@ -1900,7 +1939,7 @@ export function registerVariableCommands() {
19001939 description: 'value',
19011940 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
19021941 isRequired: true,
19031942 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
19041943 forceEnum: false,
19051944 }),
19061945 ],
@@ -1929,7 +1968,7 @@ export function registerVariableCommands() {
19291968 description: 'value',
19301969 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
19311970 isRequired: true,
19321971 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
19331972 forceEnum: false,
19341973 }),
19351974 ],
@@ -1957,7 +1996,7 @@ export function registerVariableCommands() {
19571996 description: 'value',
19581997 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
19591998 isRequired: true,
19601999 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
19612000 forceEnum: false,
19622001 }),
19632002 ],
@@ -1985,7 +2024,7 @@ export function registerVariableCommands() {
19852024 description: 'value',
19862025 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
19872026 isRequired: true,
19882027 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
19892028 forceEnum: false,
19902029 }),
19912030 ],
@@ -2013,7 +2052,7 @@ export function registerVariableCommands() {
20132052 description: 'value',
20142053 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
20152054 isRequired: true,
20162055 enumProvider: commonEnumProviders.variables('all')numbersAndVariables,
20172056 forceEnum: false,
20182057 }),
20192058 ],
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: {
src/endpoints/stable-diffusion.js+0 -69
@@ -10,41 +10,6 @@ const { readSecret, SECRET_KEYS } = require('./secrets.js');
1010const FormData = require('form-data');
1111
1212/**
13- * Sanitizes a string.
14- * @param {string} x String to sanitize
15- * @returns {string} Sanitized string
16- */
17-function safeStr(x) {
18- x = String(x);
19- x = x.replace(/ +/g, ' ');
20- x = x.trim();
21- x = x.replace(/^[\s,.]+|[\s,.]+$/g, '');
22- return x;
23-}
24-
25-const splitStrings = [
26- ', extremely',
27- ', intricate,',
28-];
29-
30-const dangerousPatterns = '[]【】()()|::';
31-
32-/**
33- * Removes patterns from a string.
34- * @param {string} x String to sanitize
35- * @param {string} pattern Pattern to remove
36- * @returns {string} Sanitized string
37- */
38-function removePattern(x, pattern) {
39- for (let i = 0; i < pattern.length; i++) {
40- let p = pattern[i];
41- let regex = new RegExp('\\' + p, 'g');
42- x = x.replace(regex, '');
43- }
44- return x;
45-}
46-
47-/**
4813 * Gets the comfy workflows.
4914 * @param {import('../users.js').UserDirectoryList} directories
5015 * @returns {string[]} List of comfy workflows
@@ -391,40 +356,6 @@ router.post('/sd-next/upscalers', jsonParser, async (request, response) => {
391356 }
392357});
393358
394-/**
395- * SD prompt expansion using GPT-2 text generation model.
396- * Adapted from: https://github.com/lllyasviel/Fooocus/blob/main/modules/expansion.py
397- */
398-router.post('/expand', jsonParser, async (request, response) => {
399- const originalPrompt = request.body.prompt;
400-
401- if (!originalPrompt) {
402- console.warn('No prompt provided for SD expansion.');
403- return response.send({ prompt: '' });
404- }
405-
406- console.log('Refine prompt input:', originalPrompt);
407- const splitString = splitStrings[Math.floor(Math.random() * splitStrings.length)];
408- let prompt = safeStr(originalPrompt) + splitString;
409-
410- try {
411- const task = 'text-generation';
412- const module = await import('../transformers.mjs');
413- const pipe = await module.default.getPipeline(task);
414-
415- const result = await pipe(prompt, { num_beams: 1, max_new_tokens: 256, do_sample: true });
416-
417- const newText = result[0].generated_text;
418- const newPrompt = safeStr(removePattern(newText, dangerousPatterns));
419- console.log('Refine prompt output:', newPrompt);
420-
421- return response.send({ prompt: newPrompt });
422- } catch {
423- console.warn('Failed to load transformers.js pipeline.');
424- return response.send({ prompt: originalPrompt });
425- }
426-});
427-
428359const comfy = express.Router();
429360
430361comfy.post('/ping', jsonParser, async (request, response) => {
src/transformers.mjs+0 -6
@@ -31,12 +31,6 @@ const tasks = {
3131 configField: 'extras.embeddingModel',
3232 quantized: true,
3333 },
34- 'text-generation': {
35- defaultModel: 'Cohee/fooocus_expansion-onnx',
36- pipeline: null,
37- configField: 'extras.promptExpansionModel',
38- quantized: false,
39- },
4034 'automatic-speech-recognition': {
4135 defaultModel: 'Xenova/whisper-small',
4236 pipeline: null,