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

This commit is contained in:
Cohee
2024-10-01 19:27:20 +03:00
16 changed files with 742 additions and 304 deletions

View File

@@ -2,17 +2,26 @@
pushd %~dp0 pushd %~dp0
git --version > nul 2>&1 git --version > nul 2>&1
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
echo Git is not installed on this system. Skipping update. echo Git is not installed on this system.
echo If you installed with a zip file, you will need to download the new zip and install it manually. echo Install it from https://git-scm.com/downloads
goto end
) else ( ) else (
if not exist .git (
echo Not running from a Git repository. Reinstall using an officially supported method to get updates.
echo See: https://docs.sillytavern.app/installation/windows/
goto end
)
call git pull --rebase --autostash call git pull --rebase --autostash
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
REM incase there is still something wrong REM incase there is still something wrong
echo There were errors while updating. Please download the latest version manually. echo There were errors while updating.
echo See the update FAQ at https://docs.sillytavern.app/usage/update/#common-update-problems
goto end
) )
) )
set NODE_ENV=production set NODE_ENV=production
call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
node server.js %* node server.js %*
:end
pause pause
popd popd

View File

@@ -5,8 +5,14 @@ pushd %~dp0
echo Checking Git installation echo Checking Git installation
git --version > nul 2>&1 git --version > nul 2>&1
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
echo Git is not installed on this system. Skipping update. echo Git is not installed on this system.
echo If you installed with a zip file, you will need to download the new zip and install it manually. echo Install it from https://git-scm.com/downloads
goto end
)
if not exist .git (
echo Not running from a Git repository. Reinstall using an officially supported method to get updates.
echo See: https://docs.sillytavern.app/installation/windows/
goto end goto end
) )
@@ -89,7 +95,8 @@ git pull --rebase --autostash origin %TARGET_BRANCH%
:install :install
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (
echo There were errors while updating. Please check manually. echo There were errors while updating.
echo See the update FAQ at https://docs.sillytavern.app/usage/update/#common-update-problems
goto end goto end
) )

View File

@@ -118,7 +118,6 @@ extras:
classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx
captioningModel: Xenova/vit-gpt2-image-captioning captioningModel: Xenova/vit-gpt2-image-captioning
embeddingModel: Cohee/jina-embeddings-v2-base-en embeddingModel: Cohee/jina-embeddings-v2-base-en
promptExpansionModel: Cohee/fooocus_expansion-onnx
speechToTextModel: Xenova/whisper-small speechToTextModel: Xenova/whisper-small
textToSpeechModel: Xenova/speecht5_tts textToSpeechModel: Xenova/speecht5_tts
# -- OPENAI CONFIGURATION -- # -- OPENAI CONFIGURATION --

View File

@@ -4797,7 +4797,7 @@ export function removeMacros(str) {
* @param {boolean} [compact] Send as a compact display message. * @param {boolean} [compact] Send as a compact display message.
* @param {string} [name] Name of the user sending the message. Defaults to name1. * @param {string} [name] Name of the user sending the message. Defaults to name1.
* @param {string} [avatar] Avatar of the user sending the message. Defaults to user_avatar. * @param {string} [avatar] Avatar of the user sending the message. Defaults to user_avatar.
* @returns {Promise<void>} A promise that resolves when the message is inserted. * @returns {Promise<any>} A promise that resolves to the message when it is inserted.
*/ */
export async function sendMessageAsUser(messageText, messageBias, insertAt = null, compact = false, name = name1, avatar = user_avatar) { export async function sendMessageAsUser(messageText, messageBias, insertAt = null, compact = false, name = name1, avatar = user_avatar) {
messageText = getRegexedString(messageText, regex_placement.USER_INPUT); messageText = getRegexedString(messageText, regex_placement.USER_INPUT);
@@ -4844,6 +4844,8 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id); await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id);
await saveChatConditional(); await saveChatConditional();
} }
return message;
} }
/** /**

View File

@@ -12,6 +12,8 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
import { isFunctionCallingSupported } from '../../openai.js'; import { isFunctionCallingSupported } from '../../openai.js';
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js'; import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandReturnHelper.js';
import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
export { MODULE_NAME }; export { MODULE_NAME };
const MODULE_NAME = 'expressions'; const MODULE_NAME = 'expressions';
@@ -2134,18 +2136,42 @@ function migrateSettings() {
name: 'classify-expressions', name: 'classify-expressions',
aliases: ['expressions'], aliases: ['expressions'],
callback: async (args) => { callback: async (args) => {
const list = await getExpressionsList(); /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
switch (String(args.format).toLowerCase()) { // @ts-ignore
let returnType = args.return;
// Old legacy return type handling
if (args.format) {
toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
const type = String(args?.format).toLowerCase().trim();
switch (type) {
case 'json': case 'json':
return JSON.stringify(list); returnType = 'object';
break;
default: default:
return list.join(', '); returnType = 'pipe';
break;
} }
}
// Now the actual new return type handling
const list = await getExpressionsList();
return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
}, },
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'pipe',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
// TODO remove some day
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'format', name: 'format',
description: 'The format to return the list in: comma-separated plain text or JSON array. Default is plain text.', description: '!!! DEPRECATED - use "return" instead !!! The format to return the list in: comma-separated plain text or JSON array. Default is plain text.',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
enumList: [ enumList: [
new SlashCommandEnumValue('plain', null, enumTypes.enum, ', '), new SlashCommandEnumValue('plain', null, enumTypes.enum, ', '),

View File

@@ -20,7 +20,7 @@ import {
} from '../../../script.js'; } from '../../../script.js';
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js'; import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
import { selected_group } from '../../group-chats.js'; import { selected_group } from '../../group-chats.js';
import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce } from '../../utils.js'; import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce, isFalseBoolean } from '../../utils.js';
import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js'; import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';
import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js'; import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js'; import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';
@@ -31,6 +31,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
import { debounce_timeout } from '../../constants.js'; import { debounce_timeout } from '../../constants.js';
import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
export { MODULE_NAME }; export { MODULE_NAME };
const MODULE_NAME = 'sd'; const MODULE_NAME = 'sd';
@@ -221,7 +222,6 @@ const defaultSettings = {
// Refine mode // Refine mode
refine_mode: false, refine_mode: false,
expand: false,
interactive_mode: false, interactive_mode: false,
multimodal_captioning: false, multimodal_captioning: false,
snap: false, snap: false,
@@ -240,7 +240,7 @@ const defaultSettings = {
drawthings_auth: '', drawthings_auth: '',
hr_upscaler: 'Latent', hr_upscaler: 'Latent',
hr_scale: 2.0, hr_scale: 1.0,
hr_scale_min: 1.0, hr_scale_min: 1.0,
hr_scale_max: 4.0, hr_scale_max: 4.0,
hr_scale_step: 0.1, hr_scale_step: 0.1,
@@ -260,10 +260,6 @@ const defaultSettings = {
clip_skip: 1, clip_skip: 1,
// NovelAI settings // NovelAI settings
novel_upscale_ratio_min: 1.0,
novel_upscale_ratio_max: 4.0,
novel_upscale_ratio_step: 0.1,
novel_upscale_ratio: 1.0,
novel_anlas_guard: false, novel_anlas_guard: false,
novel_sm: false, novel_sm: false,
novel_sm_dyn: false, novel_sm_dyn: false,
@@ -416,7 +412,6 @@ async function loadSettings() {
$('#sd_hr_scale').val(extension_settings.sd.hr_scale).trigger('input'); $('#sd_hr_scale').val(extension_settings.sd.hr_scale).trigger('input');
$('#sd_denoising_strength').val(extension_settings.sd.denoising_strength).trigger('input'); $('#sd_denoising_strength').val(extension_settings.sd.denoising_strength).trigger('input');
$('#sd_hr_second_pass_steps').val(extension_settings.sd.hr_second_pass_steps).trigger('input'); $('#sd_hr_second_pass_steps').val(extension_settings.sd.hr_second_pass_steps).trigger('input');
$('#sd_novel_upscale_ratio').val(extension_settings.sd.novel_upscale_ratio).trigger('input');
$('#sd_novel_anlas_guard').prop('checked', extension_settings.sd.novel_anlas_guard); $('#sd_novel_anlas_guard').prop('checked', extension_settings.sd.novel_anlas_guard);
$('#sd_novel_sm').prop('checked', extension_settings.sd.novel_sm); $('#sd_novel_sm').prop('checked', extension_settings.sd.novel_sm);
$('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn); $('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn);
@@ -430,7 +425,6 @@ async function loadSettings() {
$('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces); $('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
$('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr); $('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
$('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode); $('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
$('#sd_expand').prop('checked', extension_settings.sd.expand);
$('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning); $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);
$('#sd_auto_url').val(extension_settings.sd.auto_url); $('#sd_auto_url').val(extension_settings.sd.auto_url);
$('#sd_auto_auth').val(extension_settings.sd.auto_auth); $('#sd_auto_auth').val(extension_settings.sd.auto_auth);
@@ -644,37 +638,13 @@ async function onSaveStyleClick() {
saveSettingsDebounced(); saveSettingsDebounced();
} }
async function expandPrompt(prompt) {
try {
const response = await fetch('/api/sd/expand', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ prompt: prompt }),
});
if (!response.ok) {
throw new Error('API returned an error.');
}
const data = await response.json();
return data.prompt;
} catch {
return prompt;
}
}
/** /**
* Modifies prompt based on auto-expansion and user inputs. * Modifies prompt based on user inputs.
* @param {string} prompt Prompt to refine * @param {string} prompt Prompt to refine
* @param {boolean} allowExpand Whether to allow auto-expansion
* @param {boolean} isNegative Whether the prompt is a negative one * @param {boolean} isNegative Whether the prompt is a negative one
* @returns {Promise<string>} Refined prompt * @returns {Promise<string>} Refined prompt
*/ */
async function refinePrompt(prompt, allowExpand, isNegative = false) { async function refinePrompt(prompt, isNegative) {
if (allowExpand && extension_settings.sd.expand) {
prompt = await expandPrompt(prompt);
}
if (extension_settings.sd.refine_mode) { if (extension_settings.sd.refine_mode) {
const text = isNegative ? '<h3>Review and edit the <i>negative</i> prompt:</h3>' : '<h3>Review and edit the prompt:</h3>'; const text = isNegative ? '<h3>Review and edit the <i>negative</i> prompt:</h3>' : '<h3>Review and edit the prompt:</h3>';
const refinedPrompt = await callGenericPopup(text + 'Press "Cancel" to abort the image generation.', POPUP_TYPE.INPUT, prompt.trim(), { rows: 5, okButton: 'Continue' }); 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 = '') {
return process(result); return process(result);
} }
function onExpandInput() {
extension_settings.sd.expand = !!$(this).prop('checked');
saveSettingsDebounced();
}
function onRefineModeInput() { function onRefineModeInput() {
extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked'); extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked');
saveSettingsDebounced(); saveSettingsDebounced();
@@ -969,12 +934,6 @@ async function onViewAnlasClick() {
toastr.info(`Free image generation: ${unlimitedGeneration ? 'Yes' : 'No'}`, `Anlas: ${anlas}`); toastr.info(`Free image generation: ${unlimitedGeneration ? 'Yes' : 'No'}`, `Anlas: ${anlas}`);
} }
function onNovelUpscaleRatioInput() {
extension_settings.sd.novel_upscale_ratio = Number($('#sd_novel_upscale_ratio').val());
$('#sd_novel_upscale_ratio_value').val(extension_settings.sd.novel_upscale_ratio.toFixed(1));
saveSettingsDebounced();
}
function onNovelAnlasGuardInput() { function onNovelAnlasGuardInput() {
extension_settings.sd.novel_anlas_guard = !!$('#sd_novel_anlas_guard').prop('checked'); extension_settings.sd.novel_anlas_guard = !!$('#sd_novel_anlas_guard').prop('checked');
saveSettingsDebounced(); saveSettingsDebounced();
@@ -2272,6 +2231,25 @@ function getRawLastMessage() {
return `((${processReply(lastMessage.mes)})), (${processReply(character.scenario)}:0.7), (${processReply(character.description)}:0.5)`; return `((${processReply(lastMessage.mes)})), (${processReply(character.scenario)}:0.7), (${processReply(character.description)}:0.5)`;
} }
/**
* Ensure that the selected option exists in the dropdown.
* @param {string} setting Setting key
* @param {string} selector Dropdown selector
* @returns {void}
*/
function ensureSelectionExists(setting, selector) {
/** @type {HTMLSelectElement} */
const selectElement = document.querySelector(selector);
if (!selectElement) {
return;
}
const options = Array.from(selectElement.options);
const value = extension_settings.sd[setting];
if (selectElement.selectedOptions.length && !options.some(option => option.value === value)) {
extension_settings.sd[setting] = selectElement.selectedOptions[0].value;
}
}
/** /**
* Generates an image based on the given trigger word. * Generates an image based on the given trigger word.
* @param {string} initiator The initiator of the image generation * @param {string} initiator The initiator of the image generation
@@ -2292,8 +2270,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
return; return;
} }
extension_settings.sd.sampler = $('#sd_sampler').find(':selected').val(); ensureSelectionExists('sampler', '#sd_sampler');
extension_settings.sd.model = $('#sd_model').find(':selected').val(); ensureSelectionExists('model', '#sd_model');
trigger = trigger.trim(); trigger = trigger.trim();
const generationType = getGenerationType(trigger); const generationType = getGenerationType(trigger);
@@ -2441,7 +2419,7 @@ async function getPrompt(generationType, message, trigger, quietPrompt, combineN
} }
if (generationType !== generationMode.FREE) { if (generationType !== generationMode.FREE) {
prompt = await refinePrompt(prompt, true); prompt = await refinePrompt(prompt, false);
} }
return prompt; return prompt;
@@ -2469,7 +2447,7 @@ function generateFreeModePrompt(trigger, combineNegatives) {
return message.original_avatar.replace(/\.[^/.]+$/, ''); return message.original_avatar.replace(/\.[^/.]+$/, '');
} }
} }
throw new Error('No usable messages found.'); return '';
}; };
const key = getLastCharacterKey(); const key = getLastCharacterKey();
@@ -3031,7 +3009,7 @@ async function generateNovelImage(prompt, negativePrompt, signal) {
width: width, width: width,
height: height, height: height,
negative_prompt: negativePrompt, negative_prompt: negativePrompt,
upscale_ratio: extension_settings.sd.novel_upscale_ratio, upscale_ratio: extension_settings.sd.hr_scale,
decrisper: extension_settings.sd.novel_decrisper, decrisper: extension_settings.sd.novel_decrisper,
sm: sm, sm: sm,
sm_dyn: sm_dyn, sm_dyn: sm_dyn,
@@ -3613,8 +3591,8 @@ async function sdMessageButton(e) {
try { try {
setBusyIcon(true); setBusyIcon(true);
if (hasSavedImage) { if (hasSavedImage) {
const prompt = await refinePrompt(message.extra.title, false, false); const prompt = await refinePrompt(message.extra.title, false);
const negative = hasSavedNegative ? await refinePrompt(message.extra.negative, false, true) : ''; const negative = hasSavedNegative ? await refinePrompt(message.extra.negative, true) : '';
message.extra.title = prompt; message.extra.title = prompt;
const generationType = message?.extra?.generationType ?? generationMode.FREE; const generationType = message?.extra?.generationType ?? generationMode.FREE;
@@ -3756,8 +3734,8 @@ async function onImageSwiped({ message, element, direction }) {
eventSource.once(CUSTOM_STOP_EVENT, stopListener); eventSource.once(CUSTOM_STOP_EVENT, stopListener);
const callback = () => { }; const callback = () => { };
const hasNegative = message.extra.negative; const hasNegative = message.extra.negative;
const prompt = await refinePrompt(message.extra.title, false, false); const prompt = await refinePrompt(message.extra.title, false);
const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, false, true) : ''; const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, true) : '';
const characterName = context.groupId const characterName = context.groupId
? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString() ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
: context.characters[context.characterId]?.name; : context.characters[context.characterId]?.name;
@@ -3788,12 +3766,85 @@ async function onImageSwiped({ message, element, direction }) {
await context.saveChat(); await context.saveChat();
} }
/**
* Applies the command arguments to the extension settings.
* @typedef {import('../../slash-commands/SlashCommand.js').NamedArguments} NamedArguments
* @typedef {import('../../slash-commands/SlashCommand.js').NamedArgumentsCapture} NamedArgumentsCapture
* @param {NamedArguments | NamedArgumentsCapture} args - Command arguments
* @returns {Record<string, any>} - Current settings before applying the command arguments
*/
function applyCommandArguments(args) {
const overrideSettings = {};
const currentSettings = {};
const settingMap = {
'edit': 'refine_mode',
'extend': 'free_extend',
'multimodal': 'multimodal_captioning',
'seed': 'seed',
'width': 'width',
'height': 'height',
'steps': 'steps',
'cfg': 'scale',
'skip': 'clip_skip',
'model': 'model',
'sampler': 'sampler',
'scheduler': 'scheduler',
'vae': 'vae',
'upscaler': 'hr_upscaler',
'scale': 'hr_scale',
'hires': 'enable_hr',
'denoise': 'denoising_strength',
'2ndpass': 'hr_second_pass_steps',
'faces': 'restore_faces',
};
for (const [param, setting] of Object.entries(settingMap)) {
if (args[param] === undefined || defaultSettings[setting] === undefined) {
continue;
}
currentSettings[setting] = extension_settings.sd[setting];
const value = String(args[param]);
const type = typeof defaultSettings[setting];
switch (type) {
case 'boolean':
overrideSettings[setting] = isTrueBoolean(value) || !isFalseBoolean(value);
break;
case 'number':
overrideSettings[setting] = Number(value);
break;
default:
overrideSettings[setting] = value;
break;
}
}
Object.assign(extension_settings.sd, overrideSettings);
return currentSettings;
}
jQuery(async () => { jQuery(async () => {
await addSDGenButtons(); await addSDGenButtons();
const getSelectEnumProvider = (id, text) => () => Array.from(document.querySelectorAll(`#${id} > [value]`)).map(x => new SlashCommandEnumValue(x.getAttribute('value'), text ? x.textContent : null));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'imagine', name: 'imagine',
callback: (args, trigger) => generatePicture(initiators.command, args, String(trigger)), returns: 'URL of the generated image, or an empty string if the generation failed',
callback: async (args, trigger) => {
const currentSettings = applyCommandArguments(args);
try {
return await generatePicture(initiators.command, args, String(trigger));
} catch (error) {
console.error('Failed to generate image:', error);
return '';
} finally {
if (Object.keys(currentSettings).length) {
Object.assign(extension_settings.sd, currentSettings);
saveSettingsDebounced();
}
}
},
aliases: ['sd', 'img', 'image'], aliases: ['sd', 'img', 'image'],
namedArgumentList: [ namedArgumentList: [
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
@@ -3803,6 +3854,164 @@ jQuery(async () => {
name: 'negative', name: 'negative',
description: 'negative prompt prefix', description: 'negative prompt prefix',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
isRequired: false,
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'extend',
description: 'auto-extend free mode prompts with the LLM',
typeList: [ARGUMENT_TYPE.BOOLEAN],
enumProvider: commonEnumProviders.boolean('trueFalse'),
isRequired: false,
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'edit',
description: 'edit the prompt before generation',
typeList: [ARGUMENT_TYPE.BOOLEAN],
enumProvider: commonEnumProviders.boolean('trueFalse'),
isRequired: false,
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'multimodal',
description: 'use multimodal captioning (for portraits only)',
typeList: [ARGUMENT_TYPE.BOOLEAN],
enumProvider: commonEnumProviders.boolean('trueFalse'),
isRequired: false,
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'snap',
description: 'snap auto-adjusted dimensions to the nearest known resolution (portraits and backgrounds only)',
typeList: [ARGUMENT_TYPE.BOOLEAN],
enumProvider: commonEnumProviders.boolean('trueFalse'),
isRequired: false,
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'seed',
description: 'random seed',
isRequired: false,
typeList: [ARGUMENT_TYPE.NUMBER],
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'width',
description: 'image width',
isRequired: false,
typeList: [ARGUMENT_TYPE.NUMBER],
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'height',
description: 'image height',
isRequired: false,
typeList: [ARGUMENT_TYPE.NUMBER],
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'steps',
description: 'number of steps',
isRequired: false,
typeList: [ARGUMENT_TYPE.NUMBER],
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'cfg',
description: 'CFG scale',
isRequired: false,
typeList: [ARGUMENT_TYPE.NUMBER],
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'skip',
description: 'CLIP skip layers',
isRequired: false,
typeList: [ARGUMENT_TYPE.NUMBER],
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'model',
description: 'model override',
isRequired: false,
typeList: [ARGUMENT_TYPE.STRING],
acceptsMultiple: false,
forceEnum: true,
enumProvider: getSelectEnumProvider('sd_model', true),
}),
SlashCommandNamedArgument.fromProps({
name: 'sampler',
description: 'sampler override',
isRequired: false,
typeList: [ARGUMENT_TYPE.STRING],
acceptsMultiple: false,
forceEnum: true,
enumProvider: getSelectEnumProvider('sd_sampler', false),
}),
SlashCommandNamedArgument.fromProps({
name: 'scheduler',
description: 'scheduler override',
isRequired: false,
typeList: [ARGUMENT_TYPE.STRING],
acceptsMultiple: false,
forceEnum: true,
enumProvider: getSelectEnumProvider('sd_scheduler', false),
}),
SlashCommandNamedArgument.fromProps({
name: 'vae',
description: 'VAE name override',
isRequired: false,
typeList: [ARGUMENT_TYPE.STRING],
acceptsMultiple: false,
forceEnum: true,
enumProvider: getSelectEnumProvider('sd_vae', false),
}),
SlashCommandNamedArgument.fromProps({
name: 'upscaler',
description: 'upscaler override',
isRequired: false,
typeList: [ARGUMENT_TYPE.STRING],
acceptsMultiple: false,
forceEnum: true,
enumProvider: getSelectEnumProvider('sd_hr_upscaler', false),
}),
SlashCommandNamedArgument.fromProps({
name: 'hires',
description: 'enable high-res fix',
isRequired: false,
typeList: [ARGUMENT_TYPE.BOOLEAN],
acceptsMultiple: false,
enumProvider: commonEnumProviders.boolean('trueFalse'),
}),
SlashCommandNamedArgument.fromProps({
name: 'scale',
description: 'upscale amount',
isRequired: false,
typeList: [ARGUMENT_TYPE.NUMBER],
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'denoise',
description: 'denoising strength',
isRequired: false,
typeList: [ARGUMENT_TYPE.NUMBER],
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: '2ndpass',
description: 'second pass steps',
isRequired: false,
typeList: [ARGUMENT_TYPE.NUMBER],
acceptsMultiple: false,
}),
SlashCommandNamedArgument.fromProps({
name: 'faces',
description: 'restore faces',
isRequired: false,
typeList: [ARGUMENT_TYPE.BOOLEAN],
acceptsMultiple: false,
enumProvider: commonEnumProviders.boolean('trueFalse'),
}), }),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
@@ -3823,6 +4032,66 @@ jQuery(async () => {
`, `,
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'imagine-source',
aliases: ['sd-source', 'img-source'],
returns: 'a name of the current generation source',
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'source name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: false,
forceEnum: true,
enumProvider: getSelectEnumProvider('sd_source', true),
}),
],
helpString: 'If an argument is provided, change the source of the image generation, e.g. <code>/imagine-source comfy</code>. Returns the current source.',
callback: async (_args, name) => {
if (!name) {
return extension_settings.sd.source;
}
const isKnownSource = Object.keys(sources).includes(String(name));
if (!isKnownSource) {
throw new Error('The value provided is not a valid image generation source.');
}
const option = document.querySelector(`#sd_source [value="${name}"]`);
if (!(option instanceof HTMLOptionElement)) {
throw new Error('Could not find the source option in the dropdown.');
}
option.selected = true;
await onSourceChange();
return extension_settings.sd.source;
},
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'imagine-style',
aliases: ['sd-style', 'img-style'],
returns: 'a name of the current style',
unnamedArgumentList: [
SlashCommandArgument.fromProps({
description: 'style name',
typeList: [ARGUMENT_TYPE.STRING],
isRequired: false,
forceEnum: true,
enumProvider: getSelectEnumProvider('sd_style', false),
}),
],
helpString: 'If an argument is provided, change the style of the image generation, e.g. <code>/imagine-style MyStyle</code>. Returns the current style.',
callback: async (_args, name) => {
if (!name) {
return extension_settings.sd.style;
}
const option = document.querySelector(`#sd_style [value="${name}"]`);
if (!(option instanceof HTMLOptionElement)) {
throw new Error('Could not find the style option in the dropdown.');
}
option.selected = true;
onStyleSelect();
return extension_settings.sd.style;
},
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'imagine-comfy-workflow', name: 'imagine-comfy-workflow',
callback: changeComfyWorkflow, callback: changeComfyWorkflow,
@@ -3832,7 +4101,7 @@ jQuery(async () => {
description: 'workflow name', description: 'workflow name',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
isRequired: true, isRequired: true,
enumProvider: () => Array.from(document.querySelectorAll('#sd_comfy_workflow > [value]')).map(x => x.getAttribute('value')).map(workflow => new SlashCommandEnumValue(workflow)), enumProvider: getSelectEnumProvider('sd_comfy_workflow', false),
}), }),
], ],
helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g. <pre><code>/imagine-comfy-workflow MyWorkflow</code></pre>', 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 () => {
$('#sd_hr_scale').on('input', onHrScaleInput); $('#sd_hr_scale').on('input', onHrScaleInput);
$('#sd_denoising_strength').on('input', onDenoisingStrengthInput); $('#sd_denoising_strength').on('input', onDenoisingStrengthInput);
$('#sd_hr_second_pass_steps').on('input', onHrSecondPassStepsInput); $('#sd_hr_second_pass_steps').on('input', onHrSecondPassStepsInput);
$('#sd_novel_upscale_ratio').on('input', onNovelUpscaleRatioInput);
$('#sd_novel_anlas_guard').on('input', onNovelAnlasGuardInput); $('#sd_novel_anlas_guard').on('input', onNovelAnlasGuardInput);
$('#sd_novel_view_anlas').on('click', onViewAnlasClick); $('#sd_novel_view_anlas').on('click', onViewAnlasClick);
$('#sd_novel_sm').on('input', onNovelSmInput); $('#sd_novel_sm').on('input', onNovelSmInput);
@@ -3887,7 +4155,6 @@ jQuery(async () => {
$('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick); $('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick);
$('#sd_comfy_new_workflow').on('click', onComfyNewWorkflowClick); $('#sd_comfy_new_workflow').on('click', onComfyNewWorkflowClick);
$('#sd_comfy_delete_workflow').on('click', onComfyDeleteWorkflowClick); $('#sd_comfy_delete_workflow').on('click', onComfyDeleteWorkflowClick);
$('#sd_expand').on('input', onExpandInput);
$('#sd_style').on('change', onStyleSelect); $('#sd_style').on('change', onStyleSelect);
$('#sd_save_style').on('click', onSaveStyleClick); $('#sd_save_style').on('click', onSaveStyleClick);
$('#sd_delete_style').on('click', onDeleteStyleClick); $('#sd_delete_style').on('click', onDeleteStyleClick);

View File

@@ -27,11 +27,6 @@
<span data-i18n="sd_free_extend_txt">Extend free mode prompts</span> <span data-i18n="sd_free_extend_txt">Extend free mode prompts</span>
<small data-i18n="sd_free_extend_small">(interactive/commands)</small> <small data-i18n="sd_free_extend_small">(interactive/commands)</small>
</label> </label>
<label for="sd_expand" class="checkbox_label" data-i18n="[title]sd_expand" title="Automatically extend prompts using text generation model">
<input id="sd_expand" type="checkbox" />
<span data-i18n="sd_expand_txt">Auto-extend prompts</span>
<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>
</label>
<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)."> <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).">
<input id="sd_snap" type="checkbox" /> <input id="sd_snap" type="checkbox" />
<span data-i18n="sd_snap_txt">Snap auto-adjusted resolutions</span> <span data-i18n="sd_snap_txt">Snap auto-adjusted resolutions</span>
@@ -308,7 +303,7 @@
</div> </div>
<div class="flex-container"> <div class="flex-container">
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,drawthings"> <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,drawthings,novel">
<small> <small>
<span data-i18n="Upscale by">Upscale by</span> <span data-i18n="Upscale by">Upscale by</span>
</small> </small>
@@ -332,14 +327,6 @@
<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}}" > <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}}" >
</div> </div>
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="novel">
<small>
<span data-i18n="Upscale by">Upscale by</span>
</small>
<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}}" >
<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}}" >
</div>
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras"> <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras">
<small> <small>
<span data-i18n="CLIP Skip">CLIP Skip</span> <span data-i18n="CLIP Skip">CLIP Skip</span>

View File

@@ -384,6 +384,10 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
* @returns {string} Formatted instruct mode system prompt. * @returns {string} Formatted instruct mode system prompt.
*/ */
export function formatInstructModeSystemPrompt(systemPrompt) { export function formatInstructModeSystemPrompt(systemPrompt) {
if (!systemPrompt) {
return '';
}
const separator = power_user.instruct.wrap ? '\n' : ''; const separator = power_user.instruct.wrap ? '\n' : '';
if (power_user.instruct.system_sequence_prefix) { if (power_user.instruct.system_sequence_prefix) {

View File

@@ -70,6 +70,7 @@ import { POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js';
import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js'; import { SlashCommandExecutionError } from './slash-commands/SlashCommandExecutionError.js';
import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
export { export {
executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand, executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
}; };
@@ -242,6 +243,7 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'sendas', name: 'sendas',
callback: sendMessageAs, callback: sendMessageAs,
returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'name', name: 'name',
@@ -269,6 +271,14 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.NUMBER], typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'none',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@@ -301,6 +311,7 @@ export function initDefaultSlashCommands() {
name: 'sys', name: 'sys',
callback: sendNarratorMessage, callback: sendNarratorMessage,
aliases: ['nar'], aliases: ['nar'],
returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'compact', 'compact',
@@ -316,6 +327,14 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.NUMBER], typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'none',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@@ -355,6 +374,7 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'comment', name: 'comment',
callback: sendCommentMessage, callback: sendCommentMessage,
returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'compact', 'compact',
@@ -370,6 +390,14 @@ export function initDefaultSlashCommands() {
typeList: [ARGUMENT_TYPE.NUMBER], typeList: [ARGUMENT_TYPE.NUMBER],
enumProvider: commonEnumProviders.messages({ allowIdAfter: true }), enumProvider: commonEnumProviders.messages({ allowIdAfter: true }),
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'none',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@@ -509,7 +537,7 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'ask', name: 'ask',
callback: askCharacter, callback: askCharacter,
returns: 'the generated text', returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'name', name: 'name',
@@ -518,6 +546,14 @@ export function initDefaultSlashCommands() {
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.characters('character'), enumProvider: commonEnumProviders.characters('character'),
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'pipe',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@@ -556,6 +592,7 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'send', name: 'send',
callback: sendUserMessageCallback, callback: sendUserMessageCallback,
returns: 'Optionally the text of the sent message, if specified in the "return" argument',
namedArgumentList: [ namedArgumentList: [
new SlashCommandNamedArgument( new SlashCommandNamedArgument(
'compact', 'compact',
@@ -578,6 +615,14 @@ export function initDefaultSlashCommands() {
defaultValue: '{{user}}', defaultValue: '{{user}}',
enumProvider: commonEnumProviders.personas, enumProvider: commonEnumProviders.personas,
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'none',
enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
forceEnum: true,
}),
], ],
unnamedArgumentList: [ unnamedArgumentList: [
new SlashCommandArgument( new SlashCommandArgument(
@@ -1568,12 +1613,21 @@ export function initDefaultSlashCommands() {
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'listinjects', name: 'listinjects',
callback: listInjectsCallback, callback: listInjectsCallback,
helpString: 'Lists all script injections for the current chat. Displays injects in a popup by default. Use the <code>format</code> argument to change the output format.', helpString: 'Lists all script injections for the current chat. Displays injects in a popup by default. Use the <code>return</code> argument to change the return type.',
returns: 'JSON object of script injections', returns: 'Optionalls the JSON object of script injections',
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'popup-html',
enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
forceEnum: true,
}),
// TODO remove some day
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'format', name: 'format',
description: 'output format', description: '!!! DEPRECATED - use "return" instead !!! output format',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
isRequired: true, isRequired: true,
forceEnum: true, forceEnum: true,
@@ -1842,37 +1896,43 @@ function injectCallback(args, value) {
} }
async function listInjectsCallback(args) { async function listInjectsCallback(args) {
/** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
let returnType = args.return;
// Old legacy return type handling
if (args.format) {
toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
const type = String(args?.format).toLowerCase().trim(); const type = String(args?.format).toLowerCase().trim();
if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) { if (!chat_metadata.script_injects || !Object.keys(chat_metadata.script_injects).length) {
type !== 'none' && toastr.info('No script injections for the current chat'); type !== 'none' && toastr.info('No script injections for the current chat');
return JSON.stringify({}); }
switch (type) {
case 'none':
returnType = 'none';
break;
case 'chat':
returnType = 'chat-html';
break;
case 'popup':
default:
returnType = 'popup-html';
break;
}
} }
const injects = Object.entries(chat_metadata.script_injects) // Now the actual new return type handling
const buildTextValue = (injects) => {
const injectsStr = Object.entries(injects)
.map(([id, inject]) => { .map(([id, inject]) => {
const position = Object.entries(extension_prompt_types); const position = Object.entries(extension_prompt_types);
const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown'; const positionName = position.find(([_, value]) => value === inject.position)?.[0] ?? 'unknown';
return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`; return `* **${id}**: <code>${inject.value}</code> (${positionName}, depth: ${inject.depth}, scan: ${inject.scan ?? false}, role: ${inject.role ?? extension_prompt_roles.SYSTEM})`;
}) })
.join('\n'); .join('\n');
return `### Script injections:\n${injectsStr || 'No script injections for the current chat'}`;
};
const converter = new showdown.Converter(); return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', chat_metadata.script_injects ?? {}, { objectToStringFunc: buildTextValue });
const messageText = `### Script injections:\n${injects}`;
const htmlMessage = DOMPurify.sanitize(converter.makeHtml(messageText));
switch (type) {
case 'none':
break;
case 'chat':
sendSystemMessage(system_message_types.GENERIC, htmlMessage);
break;
case 'popup':
default:
await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
break;
}
return JSON.stringify(chat_metadata.script_injects);
} }
/** /**
@@ -2559,7 +2619,7 @@ async function askCharacter(args, text) {
// Not supported in group chats // Not supported in group chats
// TODO: Maybe support group chats? // TODO: Maybe support group chats?
if (selected_group) { if (selected_group) {
toastr.error('Cannot run /ask command in a group chat!'); toastr.warning('Cannot run /ask command in a group chat!');
return ''; return '';
} }
@@ -2633,7 +2693,9 @@ async function askCharacter(args, text) {
} }
} }
return askResult; const message = askResult ? chat[chat.length - 1] : null;
return await slashCommandReturnHelper.doReturn(args.return ?? 'pipe', message, { objectToStringFunc: x => x.mes });
} }
async function hideMessageCallback(_, arg) { async function hideMessageCallback(_, arg) {
@@ -2908,7 +2970,7 @@ function findPersonaByName(name) {
async function sendUserMessageCallback(args, text) { async function sendUserMessageCallback(args, text) {
if (!text) { if (!text) {
console.warn('WARN: No text provided for /send command'); toastr.warning('You must specify text to send');
return; return;
} }
@@ -2924,16 +2986,17 @@ async function sendUserMessageCallback(args, text) {
insertAt = chat.length + insertAt; insertAt = chat.length + insertAt;
} }
let message;
if ('name' in args) { if ('name' in args) {
const name = args.name || ''; const name = args.name || '';
const avatar = findPersonaByName(name) || user_avatar; const avatar = findPersonaByName(name) || user_avatar;
await sendMessageAsUser(text, bias, insertAt, compact, name, avatar); message = await sendMessageAsUser(text, bias, insertAt, compact, name, avatar);
} }
else { else {
await sendMessageAsUser(text, bias, insertAt, compact); message = await sendMessageAsUser(text, bias, insertAt, compact);
} }
return ''; return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
} }
async function deleteMessagesByNameCallback(_, name) { async function deleteMessagesByNameCallback(_, name) {
@@ -3027,7 +3090,7 @@ async function continueChatCallback(args, prompt) {
resolve(); resolve();
} catch (error) { } catch (error) {
console.error('Error running /continue command:', error); console.error('Error running /continue command:', error);
reject(); reject(error);
} }
}); });
@@ -3221,30 +3284,20 @@ export function getNameAndAvatarForMessage(character, name = null) {
export async function sendMessageAs(args, text) { export async function sendMessageAs(args, text) {
if (!text) { if (!text) {
toastr.warning('You must specify text to send as');
return ''; return '';
} }
let name; let name = args.name?.trim();
let mesText; let mesText;
if (args.name) { if (!name) {
name = args.name.trim();
if (!name && !text) {
toastr.warning('You must specify a name and text to send as');
return '';
}
} else {
const namelessWarningKey = 'sendAsNamelessWarningShown'; const namelessWarningKey = 'sendAsNamelessWarningShown';
if (localStorage.getItem(namelessWarningKey) !== 'true') { if (localStorage.getItem(namelessWarningKey) !== 'true') {
toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 }); toastr.warning('To avoid confusion, please use /sendas name="Character Name"', 'Name defaulted to {{char}}', { timeOut: 10000 });
localStorage.setItem(namelessWarningKey, 'true'); localStorage.setItem(namelessWarningKey, 'true');
} }
name = name2; name = name2;
if (!text) {
toastr.warning('You must specify text to send as');
return '';
}
} }
mesText = text.trim(); mesText = text.trim();
@@ -3321,11 +3374,12 @@ export async function sendMessageAs(args, text) {
await saveChatConditional(); await saveChatConditional();
} }
return ''; return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
} }
export async function sendNarratorMessage(args, text) { export async function sendNarratorMessage(args, text) {
if (!text) { if (!text) {
toastr.warning('You must specify text to send');
return ''; return '';
} }
@@ -3374,7 +3428,7 @@ export async function sendNarratorMessage(args, text) {
await saveChatConditional(); await saveChatConditional();
} }
return ''; return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
} }
export async function promptQuietForLoudResponse(who, text) { export async function promptQuietForLoudResponse(who, text) {
@@ -3420,6 +3474,7 @@ export async function promptQuietForLoudResponse(who, text) {
async function sendCommentMessage(args, text) { async function sendCommentMessage(args, text) {
if (!text) { if (!text) {
toastr.warning('You must specify text to send');
return ''; return '';
} }
@@ -3462,7 +3517,7 @@ async function sendCommentMessage(args, text) {
await saveChatConditional(); await saveChatConditional();
} }
return ''; return await slashCommandReturnHelper.doReturn(args.return ?? 'none', message, { objectToStringFunc: x => x.mes });
} }
/** /**

View File

@@ -508,6 +508,14 @@ export class SlashCommandClosure {
return v; return v;
}); });
} }
value ??= '';
// Make sure that if unnamed args are split, it should always return an array
if (executor.command.splitUnnamedArgument && !Array.isArray(value)) {
value = [value];
}
return value; return value;
} }

View File

@@ -36,6 +36,7 @@ export const enumIcons = {
message: '💬', message: '💬',
voice: '🎤', voice: '🎤',
server: '🖥️', server: '🖥️',
popup: '🗔',
true: '✔️', true: '✔️',
false: '❌', false: '❌',
@@ -152,6 +153,35 @@ export const commonEnumProviders = {
].filter((item, idx, list)=>idx == list.findIndex(it=>it.value == item.value)); ].filter((item, idx, list)=>idx == list.findIndex(it=>it.value == item.value));
}, },
/**
* Enum values for numbers and variable names
*
* Includes all variable names and the ability to specify any number
*
* @param {SlashCommandExecutor} executor - The executor of the slash command
* @param {SlashCommandScope} scope - The scope of the slash command
* @returns {SlashCommandEnumValue[]} The enum values
*/
numbersAndVariables: (executor, scope) => [
...commonEnumProviders.variables('all')(executor, scope),
new SlashCommandEnumValue(
'any variable name',
null,
enumTypes.variable,
enumIcons.variable,
(input) => /^\w*$/.test(input),
(input) => input,
),
new SlashCommandEnumValue(
'any number',
null,
enumTypes.number,
enumIcons.number,
(input) => input == '' || !Number.isNaN(Number(input)),
(input) => input,
),
],
/** /**
* All possible char entities, like characters and groups. Can be filtered down to just one type. * All possible char entities, like characters and groups. Can be filtered down to just one type.
* *

View File

@@ -0,0 +1,80 @@
import { sendSystemMessage, system_message_types } from '../../script.js';
import { callGenericPopup, POPUP_TYPE } from '../popup.js';
import { escapeHtml } from '../utils.js';
import { enumIcons } from './SlashCommandCommonEnumsProvider.js';
import { enumTypes, SlashCommandEnumValue } from './SlashCommandEnumValue.js';
/** @typedef {'pipe'|'object'|'chat-html'|'chat-text'|'popup-html'|'popup-text'|'toast-html'|'toast-text'|'console'|'none'} SlashCommandReturnType */
export const slashCommandReturnHelper = {
// Without this, VSCode formatter fucks up JS docs. Don't ask me why.
_: false,
/**
* Gets/creates the enum list of types of return relevant for a slash command
*
* @param {object} [options={}] Options
* @param {boolean} [options.allowPipe=true] Allow option to pipe the return value
* @param {boolean} [options.allowObject=false] Allow option to return the value as an object
* @param {boolean} [options.allowChat=false] Allow option to return the value as a chat message
* @param {boolean} [options.allowPopup=false] Allow option to return the value as a popup
* @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
* @returns {SlashCommandEnumValue[]} The enum list
*/
enumList: ({ allowPipe = true, allowObject = false, allowChat = false, allowPopup = false, allowTextVersion = true } = {}) => [
allowPipe && new SlashCommandEnumValue('pipe', 'Return to the pipe for the next command', enumTypes.name, '|'),
allowObject && new SlashCommandEnumValue('object', 'Return as an object (or array) to the pipe for the next command', enumTypes.variable, enumIcons.dictionary),
allowChat && new SlashCommandEnumValue('chat-html', 'Sending a chat message with the return value - Can display HTML', enumTypes.command, enumIcons.message),
allowChat && allowTextVersion && new SlashCommandEnumValue('chat-text', 'Sending a chat message with the return value - Will only display as text', enumTypes.qr, enumIcons.message),
allowPopup && new SlashCommandEnumValue('popup-html', 'Showing as a popup with the return value - Can display HTML', enumTypes.command, enumIcons.popup),
allowPopup && allowTextVersion && new SlashCommandEnumValue('popup-text', 'Showing as a popup with the return value - Will only display as text', enumTypes.qr, enumIcons.popup),
new SlashCommandEnumValue('toast-html', 'Show the return value as a toast notification - Can display HTML', enumTypes.command, ''),
allowTextVersion && new SlashCommandEnumValue('toast-text', 'Show the return value as a toast notification - Will only display as text', enumTypes.qr, ''),
new SlashCommandEnumValue('console', 'Log the return value (object, if it can be one) to the console', enumTypes.enum, '>'),
new SlashCommandEnumValue('none', 'No return value'),
].filter(x => !!x),
/**
* Handles the return value based on the specified type
*
* @param {SlashCommandReturnType} type The type of return
* @param {object|number|string} value The value to return
* @param {object} [options={}] Options
* @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
* @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
* @returns {Promise<*>} The processed return value
*/
async doReturn(type, value, { objectToStringFunc = o => o?.toString(), objectToHtmlFunc = null } = {}) {
const shouldHtml = type.endsWith('html');
const actualConverterFunc = shouldHtml && objectToHtmlFunc ? objectToHtmlFunc : objectToStringFunc;
const stringValue = typeof value !== 'string' ? actualConverterFunc(value) : value;
switch (type) {
case 'popup-html':
case 'popup-text':
case 'chat-text':
case 'chat-html':
case 'toast-text':
case 'toast-html': {
const htmlOrNotHtml = shouldHtml ? DOMPurify.sanitize((new showdown.Converter()).makeHtml(stringValue)) : escapeHtml(stringValue);
if (type.startsWith('popup')) await callGenericPopup(htmlOrNotHtml, POPUP_TYPE.TEXT);
if (type.startsWith('chat')) sendSystemMessage(system_message_types.GENERIC, htmlOrNotHtml);
if (type.startsWith('toast')) toastr.info(htmlOrNotHtml, null, { escapeHtml: !shouldHtml });
return '';
}
case 'pipe':
return stringValue ?? '';
case 'object':
return JSON.stringify(value);
case 'console':
console.info(value);
return '';
case 'none':
return '';
default:
throw new Error(`Unknown return type: ${type}`);
}
},
};

View File

@@ -11,6 +11,7 @@ import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureR
import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { slashCommandReturnHelper } from './slash-commands/SlashCommandReturnHelper.js';
import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js'; import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
@@ -305,7 +306,28 @@ export function replaceVariableMacros(input) {
} }
async function listVariablesCallback(args) { async function listVariablesCallback(args) {
const type = String(args?.format || '').toLowerCase().trim() || 'popup'; /** @type {import('./slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
let returnType = args.return;
// Old legacy return type handling
if (args.format) {
toastr.warning(`Legacy argument 'format' with value '${args.format}' is deprecated. Please use 'return' instead. Routing to the correct return type...`, 'Deprecation warning');
const type = String(args?.format).toLowerCase().trim();
switch (type) {
case 'none':
returnType = 'none';
break;
case 'chat':
returnType = 'chat-html';
break;
case 'popup':
default:
returnType = 'popup-html';
break;
}
}
// Now the actual new return type handling
const scope = String(args?.scope || '').toLowerCase().trim() || 'all'; const scope = String(args?.scope || '').toLowerCase().trim() || 'all';
if (!chat_metadata.variables) { if (!chat_metadata.variables) {
chat_metadata.variables = {}; chat_metadata.variables = {};
@@ -317,35 +339,24 @@ async function listVariablesCallback(args) {
const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : []; const localVariables = includeLocalVariables ? Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`) : [];
const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : []; const globalVariables = includeGlobalVariables ? Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`) : [];
const buildTextValue = (_) => {
const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables';
const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
const chatName = getCurrentChatId();
const message = [
includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
].filter(x => x).join('\n\n');
return message;
};
const jsonVariables = [ const jsonVariables = [
...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })), ...Object.entries(chat_metadata.variables).map(x => ({ key: x[0], value: x[1], scope: 'local' })),
...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })), ...Object.entries(extension_settings.variables.global).map(x => ({ key: x[0], value: x[1], scope: 'global' })),
]; ];
const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables'; return await slashCommandReturnHelper.doReturn(returnType ?? 'popup-html', jsonVariables, { objectToStringFunc: buildTextValue });
const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables';
const chatName = getCurrentChatId();
const converter = new showdown.Converter();
const message = [
includeLocalVariables ? `### Local variables (${chatName}):\n${localVariablesString}` : '',
includeGlobalVariables ? `### Global variables:\n${globalVariablesString}` : '',
].filter(x => x).join('\n\n');
const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message));
switch (type) {
case 'none':
break;
case 'chat':
sendSystemMessage(system_message_types.GENERIC, htmlMessage);
break;
case 'popup':
default:
await callGenericPopup(htmlMessage, POPUP_TYPE.TEXT);
break;
}
return JSON.stringify(jsonVariables);
} }
/** /**
@@ -669,8 +680,8 @@ function deleteGlobalVariable(name) {
} }
/** /**
* Parses a series of numeric values from a string. * Parses a series of numeric values from a string or a string array.
* @param {string} value A space-separated list of numeric values or variable names * @param {string|string[]} value A space-separated list of numeric values or variable names
* @param {SlashCommandScope} scope Scope * @param {SlashCommandScope} scope Scope
* @returns {number[]} An array of numeric values * @returns {number[]} An array of numeric values
*/ */
@@ -679,11 +690,17 @@ function parseNumericSeries(value, scope = null) {
return [value]; return [value];
} }
const array = value /** @type {(string|number)[]} */
.split(' ') let values = Array.isArray(value) ? value : value.split(' ');
.map(i => i.trim())
// If a JSON array was provided as the only value, convert it to an array
if (values.length === 1 && typeof values[0] === 'string' && values[0].startsWith('[')) {
values = convertValueType(values[0], 'array');
}
const array = values.map(i => typeof i === 'string' ? i.trim() : i)
.filter(i => i !== '') .filter(i => i !== '')
.map(i => isNaN(Number(i)) ? Number(resolveVariable(i, scope)) : Number(i)) .map(i => isNaN(Number(i)) ? Number(resolveVariable(String(i), scope)) : Number(i))
.filter(i => !isNaN(i)); .filter(i => !isNaN(i));
return array; return array;
@@ -703,7 +720,7 @@ function performOperation(value, operation, singleOperand = false, scope = null)
const result = singleOperand ? operation(array[0]) : operation(array); const result = singleOperand ? operation(array[0]) : operation(array);
if (isNaN(result) || !isFinite(result)) { if (isNaN(result)) {
return 0; return 0;
} }
@@ -731,7 +748,7 @@ function maxValuesCallback(args, value) {
} }
function subValuesCallback(args, value) { function subValuesCallback(args, value) {
return performOperation(value, (array) => array[0] - array[1], false, args._scope); return performOperation(value, (array) => array.reduce((a, b) => a - b, array.shift() ?? 0), false, args._scope);
} }
function divValuesCallback(args, value) { function divValuesCallback(args, value) {
@@ -910,7 +927,7 @@ export function registerVariableCommands() {
name: 'listvar', name: 'listvar',
callback: listVariablesCallback, callback: listVariablesCallback,
aliases: ['listchatvar'], aliases: ['listchatvar'],
helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>format</code> argument to change the output format.', helpString: 'List registered chat variables. Displays variables in a popup by default. Use the <code>return</code> argument to change the return type.',
returns: 'JSON list of local variables', returns: 'JSON list of local variables',
namedArgumentList: [ namedArgumentList: [
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
@@ -926,9 +943,18 @@ export function registerVariableCommands() {
new SlashCommandEnumValue('global', 'Global variables', enumTypes.enum, enumIcons.globalVariable), new SlashCommandEnumValue('global', 'Global variables', enumTypes.enum, enumIcons.globalVariable),
], ],
}), }),
SlashCommandNamedArgument.fromProps({
name: 'return',
description: 'The way how you want the return value to be provided',
typeList: [ARGUMENT_TYPE.STRING],
defaultValue: 'popup-html',
enumList: slashCommandReturnHelper.enumList({ allowPipe: false, allowObject: true, allowChat: true, allowPopup: true, allowTextVersion: false }),
forceEnum: true,
}),
// TODO remove some day
SlashCommandNamedArgument.fromProps({ SlashCommandNamedArgument.fromProps({
name: 'format', name: 'format',
description: 'output format', description: '!!! DEPRECATED - use "return" instead !!! output format',
typeList: [ARGUMENT_TYPE.STRING], typeList: [ARGUMENT_TYPE.STRING],
isRequired: true, isRequired: true,
forceEnum: true, forceEnum: true,
@@ -1595,36 +1621,15 @@ export function registerVariableCommands() {
})); }));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'add', name: 'add',
callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')), callback: (args, value) => addValuesCallback(args, value),
returns: 'sum of the provided values', returns: 'sum of the provided values',
unnamedArgumentList: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'values to sum', description: 'values to sum',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
isRequired: true, isRequired: true,
acceptsMultiple: true, acceptsMultiple: true,
enumProvider: (executor, scope) => { enumProvider: commonEnumProviders.numbersAndVariables,
const vars = commonEnumProviders.variables('all')(executor, scope);
vars.push(
new SlashCommandEnumValue(
'any variable name',
null,
enumTypes.variable,
enumIcons.variable,
(input) => /^\w*$/.test(input),
(input) => input,
),
new SlashCommandEnumValue(
'any number',
null,
enumTypes.number,
enumIcons.number,
(input) => input == '' || !Number.isNaN(Number(input)),
(input) => input,
),
);
return vars;
},
forceEnum: false, forceEnum: false,
}), }),
], ],
@@ -1632,7 +1637,9 @@ export function registerVariableCommands() {
helpString: ` helpString: `
<div> <div>
Performs an addition of the set of values and passes the result down the pipe. Performs an addition of the set of values and passes the result down the pipe.
Can use variable names. </div>
<div>
Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
</div> </div>
<div> <div>
<strong>Example:</strong> <strong>Example:</strong>
@@ -1640,6 +1647,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/add 10 i 30 j</code></pre> <pre><code class="language-stscript">/add 10 i 30 j</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/add ["count", 15, 2, "i"]</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@@ -1651,16 +1661,20 @@ export function registerVariableCommands() {
unnamedArgumentList: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'values to multiply', description: 'values to multiply',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
isRequired: true, isRequired: true,
acceptsMultiple: true, acceptsMultiple: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
splitUnnamedArgument: true,
helpString: ` helpString: `
<div> <div>
Performs a multiplication of the set of values and passes the result down the pipe. Can use variable names. Performs a multiplication of the set of values and passes the result down the pipe.
</div>
<div>
Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
</div> </div>
<div> <div>
<strong>Examples:</strong> <strong>Examples:</strong>
@@ -1668,6 +1682,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/mul 10 i 30 j</code></pre> <pre><code class="language-stscript">/mul 10 i 30 j</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/mul ["count", 15, 2, "i"]</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@@ -1679,16 +1696,20 @@ export function registerVariableCommands() {
unnamedArgumentList: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'values to find the max', description: 'values to find the max',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
isRequired: true, isRequired: true,
acceptsMultiple: true, acceptsMultiple: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
splitUnnamedArgument: true,
helpString: ` helpString: `
<div> <div>
Returns the maximum value of the set of values and passes the result down the pipe. Can use variable names. Returns the maximum value of the set of values and passes the result down the pipe.
</div>
<div>
Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
</div> </div>
<div> <div>
<strong>Examples:</strong> <strong>Examples:</strong>
@@ -1696,6 +1717,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/max 10 i 30 j</code></pre> <pre><code class="language-stscript">/max 10 i 30 j</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/max ["count", 15, 2, "i"]</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@@ -1707,17 +1731,20 @@ export function registerVariableCommands() {
unnamedArgumentList: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'values to find the min', description: 'values to find the min',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
isRequired: true, isRequired: true,
acceptsMultiple: true, acceptsMultiple: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
splitUnnamedArgument: true,
helpString: ` helpString: `
<div> <div>
Returns the minimum value of the set of values and passes the result down the pipe. Returns the minimum value of the set of values and passes the result down the pipe.
Can use variable names. </div>
<div>
Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
</div> </div>
<div> <div>
<strong>Example:</strong> <strong>Example:</strong>
@@ -1725,6 +1752,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/min 10 i 30 j</code></pre> <pre><code class="language-stscript">/min 10 i 30 j</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/min ["count", 15, 2, "i"]</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@@ -1735,18 +1765,21 @@ export function registerVariableCommands() {
returns: 'difference of the provided values', returns: 'difference of the provided values',
unnamedArgumentList: [ unnamedArgumentList: [
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'values to find the difference', description: 'values to subtract, starting form the first provided value',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.LIST],
isRequired: true, isRequired: true,
acceptsMultiple: true, acceptsMultiple: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
splitUnnamedArgument: true,
helpString: ` helpString: `
<div> <div>
Performs a subtraction of the set of values and passes the result down the pipe. Performs a subtraction of the set of values and passes the result down the pipe.
Can use variable names. </div>
<div>
Can use variable names, or a JSON array consisting of numbers and variables (with quotes).
</div> </div>
<div> <div>
<strong>Example:</strong> <strong>Example:</strong>
@@ -1754,6 +1787,9 @@ export function registerVariableCommands() {
<li> <li>
<pre><code class="language-stscript">/sub i 5</code></pre> <pre><code class="language-stscript">/sub i 5</code></pre>
</li> </li>
<li>
<pre><code class="language-stscript">/sub ["count", 4, "i"]</code></pre>
</li>
</ul> </ul>
</div> </div>
`, `,
@@ -1767,17 +1803,18 @@ export function registerVariableCommands() {
description: 'dividend', description: 'dividend',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'divisor', description: 'divisor',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
splitUnnamedArgument: true,
helpString: ` helpString: `
<div> <div>
Performs a division of two values and passes the result down the pipe. Performs a division of two values and passes the result down the pipe.
@@ -1802,17 +1839,18 @@ export function registerVariableCommands() {
description: 'dividend', description: 'dividend',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'divisor', description: 'divisor',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
splitUnnamedArgument: true,
helpString: ` helpString: `
<div> <div>
Performs a modulo operation of two values and passes the result down the pipe. Performs a modulo operation of two values and passes the result down the pipe.
@@ -1837,17 +1875,18 @@ export function registerVariableCommands() {
description: 'base', description: 'base',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
SlashCommandArgument.fromProps({ SlashCommandArgument.fromProps({
description: 'exponent', description: 'exponent',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
splitUnnamedArgument: true,
helpString: ` helpString: `
<div> <div>
Performs a power operation of two values and passes the result down the pipe. Performs a power operation of two values and passes the result down the pipe.
@@ -1872,7 +1911,7 @@ export function registerVariableCommands() {
description: 'value', description: 'value',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
@@ -1900,7 +1939,7 @@ export function registerVariableCommands() {
description: 'value', description: 'value',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
@@ -1929,7 +1968,7 @@ export function registerVariableCommands() {
description: 'value', description: 'value',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
@@ -1957,7 +1996,7 @@ export function registerVariableCommands() {
description: 'value', description: 'value',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
@@ -1985,7 +2024,7 @@ export function registerVariableCommands() {
description: 'value', description: 'value',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],
@@ -2013,7 +2052,7 @@ export function registerVariableCommands() {
description: 'value', description: 'value',
typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME],
isRequired: true, isRequired: true,
enumProvider: commonEnumProviders.variables('all'), enumProvider: commonEnumProviders.numbersAndVariables,
forceEnum: false, forceEnum: false,
}), }),
], ],

View File

@@ -323,7 +323,7 @@ async function sendMakerSuiteRequest(request, response) {
? (stream ? 'streamGenerateContent' : 'generateContent') ? (stream ? 'streamGenerateContent' : 'generateContent')
: (isText ? 'generateText' : 'generateMessage'); : (isText ? 'generateText' : 'generateMessage');
const generateResponse = await fetch(`${apiUrl.origin}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, { const generateResponse = await fetch(`${apiUrl}/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {
body: JSON.stringify(body), body: JSON.stringify(body),
method: 'POST', method: 'POST',
headers: { headers: {

View File

@@ -9,41 +9,6 @@ const { jsonParser } = require('../express-common');
const { readSecret, SECRET_KEYS } = require('./secrets.js'); const { readSecret, SECRET_KEYS } = require('./secrets.js');
const FormData = require('form-data'); const FormData = require('form-data');
/**
* Sanitizes a string.
* @param {string} x String to sanitize
* @returns {string} Sanitized string
*/
function safeStr(x) {
x = String(x);
x = x.replace(/ +/g, ' ');
x = x.trim();
x = x.replace(/^[\s,.]+|[\s,.]+$/g, '');
return x;
}
const splitStrings = [
', extremely',
', intricate,',
];
const dangerousPatterns = '[]【】()|:';
/**
* Removes patterns from a string.
* @param {string} x String to sanitize
* @param {string} pattern Pattern to remove
* @returns {string} Sanitized string
*/
function removePattern(x, pattern) {
for (let i = 0; i < pattern.length; i++) {
let p = pattern[i];
let regex = new RegExp('\\' + p, 'g');
x = x.replace(regex, '');
}
return x;
}
/** /**
* Gets the comfy workflows. * Gets the comfy workflows.
* @param {import('../users.js').UserDirectoryList} directories * @param {import('../users.js').UserDirectoryList} directories
@@ -391,40 +356,6 @@ router.post('/sd-next/upscalers', jsonParser, async (request, response) => {
} }
}); });
/**
* SD prompt expansion using GPT-2 text generation model.
* Adapted from: https://github.com/lllyasviel/Fooocus/blob/main/modules/expansion.py
*/
router.post('/expand', jsonParser, async (request, response) => {
const originalPrompt = request.body.prompt;
if (!originalPrompt) {
console.warn('No prompt provided for SD expansion.');
return response.send({ prompt: '' });
}
console.log('Refine prompt input:', originalPrompt);
const splitString = splitStrings[Math.floor(Math.random() * splitStrings.length)];
let prompt = safeStr(originalPrompt) + splitString;
try {
const task = 'text-generation';
const module = await import('../transformers.mjs');
const pipe = await module.default.getPipeline(task);
const result = await pipe(prompt, { num_beams: 1, max_new_tokens: 256, do_sample: true });
const newText = result[0].generated_text;
const newPrompt = safeStr(removePattern(newText, dangerousPatterns));
console.log('Refine prompt output:', newPrompt);
return response.send({ prompt: newPrompt });
} catch {
console.warn('Failed to load transformers.js pipeline.');
return response.send({ prompt: originalPrompt });
}
});
const comfy = express.Router(); const comfy = express.Router();
comfy.post('/ping', jsonParser, async (request, response) => { comfy.post('/ping', jsonParser, async (request, response) => {

View File

@@ -31,12 +31,6 @@ const tasks = {
configField: 'extras.embeddingModel', configField: 'extras.embeddingModel',
quantized: true, quantized: true,
}, },
'text-generation': {
defaultModel: 'Cohee/fooocus_expansion-onnx',
pipeline: null,
configField: 'extras.promptExpansionModel',
quantized: false,
},
'automatic-speech-recognition': { 'automatic-speech-recognition': {
defaultModel: 'Xenova/whisper-small', defaultModel: 'Xenova/whisper-small',
pipeline: null, pipeline: null,