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

89642f217d06b1644f223d61d83dbace6e04315d

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

9 files changed, +354 -156Ignore whitespace
UpdateAndStart.bat+12 -3
@@ -2,17 +2,26 @@
2pushd %~dp02pushd %~dp0
3git --version > nul 2>&13git --version > nul 2>&1
4if %errorlevel% neq 0 (4if %errorlevel% neq 0 (
5 echo Git is not installed on this system. Skipping update.5 echo Git is not installed on this system.
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
7) else (8) 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 )
8 call git pull --rebase --autostash14 call git pull --rebase --autostash
9 if %errorlevel% neq 0 (15 if %errorlevel% neq 0 (
10 REM incase there is still something wrong16 REM incase there is still something wrong
11 echo There were errors while updating. Please download the latest version manually.17 echo There were errors while updating.
18 echo See the update FAQ at https://docs.sillytavern.app/usage/update/#common-update-problems
19 goto end
12 )20 )
13)21)
14set NODE_ENV=production22set NODE_ENV=production
15call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev23call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
16node server.js %*24node server.js %*
25:end
17pause26pause
18popd27popd
UpdateForkAndStart.bat+10 -3
@@ -5,8 +5,14 @@ pushd %~dp0
5echo Checking Git installation5echo Checking Git installation
6git --version > nul 2>&16git --version > nul 2>&1
7if %errorlevel% neq 0 (7if %errorlevel% neq 0 (
8 echo Git is not installed on this system. Skipping update.8 echo Git is not installed on this system.
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
13if 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/
10 goto end16 goto end
11)17)
1218
@@ -89,7 +95,8 @@ git pull --rebase --autostash origin %TARGET_BRANCH%
8995
90:install96:install
91if %errorlevel% neq 0 (97if %errorlevel% neq 0 (
92 echo There were errors while updating. Please check manually.98 echo There were errors while updating.
99 echo See the update FAQ at https://docs.sillytavern.app/usage/update/#common-update-problems
93 goto end100 goto end
94)101)
95102
default/config.yaml+0 -1
@@ -118,7 +118,6 @@ extras:
118 classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx118 classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx
119 captioningModel: Xenova/vit-gpt2-image-captioning119 captioningModel: Xenova/vit-gpt2-image-captioning
120 embeddingModel: Cohee/jina-embeddings-v2-base-en120 embeddingModel: Cohee/jina-embeddings-v2-base-en
121 promptExpansionModel: Cohee/fooocus_expansion-onnx
122 speechToTextModel: Xenova/whisper-small121 speechToTextModel: Xenova/whisper-small
123 textToSpeechModel: Xenova/speecht5_tts122 textToSpeechModel: Xenova/speecht5_tts
124# -- OPENAI CONFIGURATION --123# -- OPENAI CONFIGURATION --
public/scripts/extensions/stable-diffusion/index.js+326 -59
@@ -20,7 +20,7 @@ import {
20} from '../../../script.js';20} from '../../../script.js';
21import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';21import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
22import { selected_group } from '../../group-chats.js';22import { selected_group } from '../../group-chats.js';
23import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce } from '../../utils.js';23import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay, isTrueBoolean, debounce, isFalseBoolean } from '../../utils.js';
24import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';24import { getMessageTimeStamp, humanizedDateTime } from '../../RossAscends-mods.js';
25import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';25import { SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js';
26import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';26import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from '../../nai-settings.js';
@@ -31,6 +31,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
31import { debounce_timeout } from '../../constants.js';31import { debounce_timeout } from '../../constants.js';
32import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';32import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js';
33import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';33import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js';
34import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
34export { MODULE_NAME };35export { MODULE_NAME };
3536
36const MODULE_NAME = 'sd';37const MODULE_NAME = 'sd';
@@ -221,7 +222,6 @@ const defaultSettings = {
221222
222 // Refine mode223 // Refine mode
223 refine_mode: false,224 refine_mode: false,
224 expand: false,
225 interactive_mode: false,225 interactive_mode: false,
226 multimodal_captioning: false,226 multimodal_captioning: false,
227 snap: false,227 snap: false,
@@ -240,7 +240,7 @@ const defaultSettings = {
240 drawthings_auth: '',240 drawthings_auth: '',
241241
242 hr_upscaler: 'Latent',242 hr_upscaler: 'Latent',
243 hr_scale: 2.0,243 hr_scale: 1.0,
244 hr_scale_min: 1.0,244 hr_scale_min: 1.0,
245 hr_scale_max: 4.0,245 hr_scale_max: 4.0,
246 hr_scale_step: 0.1,246 hr_scale_step: 0.1,
@@ -260,10 +260,6 @@ const defaultSettings = {
260 clip_skip: 1,260 clip_skip: 1,
261261
262 // NovelAI settings262 // 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,
267 novel_anlas_guard: false,263 novel_anlas_guard: false,
268 novel_sm: false,264 novel_sm: false,
269 novel_sm_dyn: false,265 novel_sm_dyn: false,
@@ -416,7 +412,6 @@ async function loadSettings() {
416 $('#sd_hr_scale').val(extension_settings.sd.hr_scale).trigger('input');412 $('#sd_hr_scale').val(extension_settings.sd.hr_scale).trigger('input');
417 $('#sd_denoising_strength').val(extension_settings.sd.denoising_strength).trigger('input');413 $('#sd_denoising_strength').val(extension_settings.sd.denoising_strength).trigger('input');
418 $('#sd_hr_second_pass_steps').val(extension_settings.sd.hr_second_pass_steps).trigger('input');414 $('#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');
420 $('#sd_novel_anlas_guard').prop('checked', extension_settings.sd.novel_anlas_guard);415 $('#sd_novel_anlas_guard').prop('checked', extension_settings.sd.novel_anlas_guard);
421 $('#sd_novel_sm').prop('checked', extension_settings.sd.novel_sm);416 $('#sd_novel_sm').prop('checked', extension_settings.sd.novel_sm);
422 $('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn);417 $('#sd_novel_sm_dyn').prop('checked', extension_settings.sd.novel_sm_dyn);
@@ -430,7 +425,6 @@ async function loadSettings() {
430 $('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);425 $('#sd_restore_faces').prop('checked', extension_settings.sd.restore_faces);
431 $('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);426 $('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
432 $('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);427 $('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
433 $('#sd_expand').prop('checked', extension_settings.sd.expand);
434 $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);428 $('#sd_multimodal_captioning').prop('checked', extension_settings.sd.multimodal_captioning);
435 $('#sd_auto_url').val(extension_settings.sd.auto_url);429 $('#sd_auto_url').val(extension_settings.sd.auto_url);
436 $('#sd_auto_auth').val(extension_settings.sd.auto_auth);430 $('#sd_auto_auth').val(extension_settings.sd.auto_auth);
@@ -644,37 +638,13 @@ async function onSaveStyleClick() {
644 saveSettingsDebounced();638 saveSettingsDebounced();
645}639}
646640
647async 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
666/**641/**
667 * Modifies prompt based on auto-expansion and user inputs.642 * Modifies prompt based on user inputs.
668 * @param {string} prompt Prompt to refine643 * @param {string} prompt Prompt to refine
669 * @param {boolean} allowExpand Whether to allow auto-expansion
670 * @param {boolean} isNegative Whether the prompt is a negative one644 * @param {boolean} isNegative Whether the prompt is a negative one
671 * @returns {Promise<string>} Refined prompt645 * @returns {Promise<string>} Refined prompt
672 */646 */
673async function refinePrompt(prompt, allowExpand, isNegative = false) {647async function refinePrompt(prompt, isNegative) {
674 if (allowExpand && extension_settings.sd.expand) {
675 prompt = await expandPrompt(prompt);
676 }
677
678 if (extension_settings.sd.refine_mode) {648 if (extension_settings.sd.refine_mode) {
679 const text = isNegative ? '<h3>Review and edit the <i>negative</i> prompt:</h3>' : '<h3>Review and edit the prompt:</h3>';649 const text = isNegative ? '<h3>Review and edit the <i>negative</i> prompt:</h3>' : '<h3>Review and edit the prompt:</h3>';
680 const refinedPrompt = await callGenericPopup(text + 'Press "Cancel" to abort the image generation.', POPUP_TYPE.INPUT, prompt.trim(), { rows: 5, okButton: 'Continue' });650 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 = '') {
800 return process(result);770 return process(result);
801}771}
802772
803function onExpandInput() {
804 extension_settings.sd.expand = !!$(this).prop('checked');
805 saveSettingsDebounced();
806}
807
808function onRefineModeInput() {773function onRefineModeInput() {
809 extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked');774 extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked');
810 saveSettingsDebounced();775 saveSettingsDebounced();
@@ -969,12 +934,6 @@ async function onViewAnlasClick() {
969 toastr.info(`Free image generation: ${unlimitedGeneration ? 'Yes' : 'No'}`, `Anlas: ${anlas}`);934 toastr.info(`Free image generation: ${unlimitedGeneration ? 'Yes' : 'No'}`, `Anlas: ${anlas}`);
970}935}
971936
972function 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
978function onNovelAnlasGuardInput() {937function onNovelAnlasGuardInput() {
979 extension_settings.sd.novel_anlas_guard = !!$('#sd_novel_anlas_guard').prop('checked');938 extension_settings.sd.novel_anlas_guard = !!$('#sd_novel_anlas_guard').prop('checked');
980 saveSettingsDebounced();939 saveSettingsDebounced();
@@ -2273,6 +2232,25 @@ function getRawLastMessage() {
2273}2232}
22742233
2275/**2234/**
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 */
2240function 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/**
2276 * Generates an image based on the given trigger word.2254 * Generates an image based on the given trigger word.
2277 * @param {string} initiator The initiator of the image generation2255 * @param {string} initiator The initiator of the image generation
2278 * @param {Record<string, object>} args Command arguments2256 * @param {Record<string, object>} args Command arguments
@@ -2292,8 +2270,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
2292 return;2270 return;
2293 }2271 }
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
2298 trigger = trigger.trim();2276 trigger = trigger.trim();
2299 const generationType = getGenerationType(trigger);2277 const generationType = getGenerationType(trigger);
@@ -2441,7 +2419,7 @@ async function getPrompt(generationType, message, trigger, quietPrompt, combineN
2441 }2419 }
24422420
2443 if (generationType !== generationMode.FREE) {2421 if (generationType !== generationMode.FREE) {
2444 prompt = await refinePrompt(prompt, true);2422 prompt = await refinePrompt(prompt, false);
2445 }2423 }
24462424
2447 return prompt;2425 return prompt;
@@ -2469,7 +2447,7 @@ function generateFreeModePrompt(trigger, combineNegatives) {
2469 return message.original_avatar.replace(/\.[^/.]+$/, '');2447 return message.original_avatar.replace(/\.[^/.]+$/, '');
2470 }2448 }
2471 }2449 }
2472 throw new Error('No usable messages found.');2450 return '';
2473 };2451 };
24742452
2475 const key = getLastCharacterKey();2453 const key = getLastCharacterKey();
@@ -3031,7 +3009,7 @@ async function generateNovelImage(prompt, negativePrompt, signal) {
3031 width: width,3009 width: width,
3032 height: height,3010 height: height,
3033 negative_prompt: negativePrompt,3011 negative_prompt: negativePrompt,
3034 upscale_ratio: extension_settings.sd.novel_upscale_ratio,3012 upscale_ratio: extension_settings.sd.hr_scale,
3035 decrisper: extension_settings.sd.novel_decrisper,3013 decrisper: extension_settings.sd.novel_decrisper,
3036 sm: sm,3014 sm: sm,
3037 sm_dyn: sm_dyn,3015 sm_dyn: sm_dyn,
@@ -3613,8 +3591,8 @@ async function sdMessageButton(e) {
3613 try {3591 try {
3614 setBusyIcon(true);3592 setBusyIcon(true);
3615 if (hasSavedImage) {3593 if (hasSavedImage) {
3616 const prompt = await refinePrompt(message.extra.title, false, false);3594 const prompt = await refinePrompt(message.extra.title, false);
3617 const negative = hasSavedNegative ? await refinePrompt(message.extra.negative, false, true) : '';3595 const negative = hasSavedNegative ? await refinePrompt(message.extra.negative, true) : '';
3618 message.extra.title = prompt;3596 message.extra.title = prompt;
36193597
3620 const generationType = message?.extra?.generationType ?? generationMode.FREE;3598 const generationType = message?.extra?.generationType ?? generationMode.FREE;
@@ -3756,8 +3734,8 @@ async function onImageSwiped({ message, element, direction }) {
3756 eventSource.once(CUSTOM_STOP_EVENT, stopListener);3734 eventSource.once(CUSTOM_STOP_EVENT, stopListener);
3757 const callback = () => { };3735 const callback = () => { };
3758 const hasNegative = message.extra.negative;3736 const hasNegative = message.extra.negative;
3759 const prompt = await refinePrompt(message.extra.title, false, false);3737 const prompt = await refinePrompt(message.extra.title, false);
3760 const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, false, true) : '';3738 const negativePromptPrefix = hasNegative ? await refinePrompt(message.extra.negative, true) : '';
3761 const characterName = context.groupId3739 const characterName = context.groupId
3762 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()3740 ? context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]]?.id?.toString()
3763 : context.characters[context.characterId]?.name;3741 : context.characters[context.characterId]?.name;
@@ -3788,12 +3766,85 @@ async function onImageSwiped({ message, element, direction }) {
3788 await context.saveChat();3766 await context.saveChat();
3789}3767}
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 */
3776function 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
3791jQuery(async () => {3825jQuery(async () => {
3792 await addSDGenButtons();3826 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
3794 SlashCommandParser.addCommandObject(SlashCommand.fromProps({3830 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
3795 name: 'imagine',3831 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 },
3797 aliases: ['sd', 'img', 'image'],3848 aliases: ['sd', 'img', 'image'],
3798 namedArgumentList: [3849 namedArgumentList: [
3799 new SlashCommandNamedArgument(3850 new SlashCommandNamedArgument(
@@ -3803,6 +3854,164 @@ jQuery(async () => {
3803 name: 'negative',3854 name: 'negative',
3804 description: 'negative prompt prefix',3855 description: 'negative prompt prefix',
3805 typeList: [ARGUMENT_TYPE.STRING],3856 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'),
3806 }),4015 }),
3807 ],4016 ],
3808 unnamedArgumentList: [4017 unnamedArgumentList: [
@@ -3824,6 +4033,66 @@ jQuery(async () => {
3824 }));4033 }));
38254034
3826 SlashCommandParser.addCommandObject(SlashCommand.fromProps({4035 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({
3827 name: 'imagine-comfy-workflow',4096 name: 'imagine-comfy-workflow',
3828 callback: changeComfyWorkflow,4097 callback: changeComfyWorkflow,
3829 aliases: ['icw'],4098 aliases: ['icw'],
@@ -3832,7 +4101,7 @@ jQuery(async () => {
3832 description: 'workflow name',4101 description: 'workflow name',
3833 typeList: [ARGUMENT_TYPE.STRING],4102 typeList: [ARGUMENT_TYPE.STRING],
3834 isRequired: true,4103 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),
3836 }),4105 }),
3837 ],4106 ],
3838 helpString: '(workflowName) - change the workflow to be used for image generation with ComfyUI, e.g. <pre><code>/imagine-comfy-workflow MyWorkflow</code></pre>',4107 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 () => {
3874 $('#sd_hr_scale').on('input', onHrScaleInput);4143 $('#sd_hr_scale').on('input', onHrScaleInput);
3875 $('#sd_denoising_strength').on('input', onDenoisingStrengthInput);4144 $('#sd_denoising_strength').on('input', onDenoisingStrengthInput);
3876 $('#sd_hr_second_pass_steps').on('input', onHrSecondPassStepsInput);4145 $('#sd_hr_second_pass_steps').on('input', onHrSecondPassStepsInput);
3877 $('#sd_novel_upscale_ratio').on('input', onNovelUpscaleRatioInput);
3878 $('#sd_novel_anlas_guard').on('input', onNovelAnlasGuardInput);4146 $('#sd_novel_anlas_guard').on('input', onNovelAnlasGuardInput);
3879 $('#sd_novel_view_anlas').on('click', onViewAnlasClick);4147 $('#sd_novel_view_anlas').on('click', onViewAnlasClick);
3880 $('#sd_novel_sm').on('input', onNovelSmInput);4148 $('#sd_novel_sm').on('input', onNovelSmInput);
@@ -3887,7 +4155,6 @@ jQuery(async () => {
3887 $('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick);4155 $('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick);
3888 $('#sd_comfy_new_workflow').on('click', onComfyNewWorkflowClick);4156 $('#sd_comfy_new_workflow').on('click', onComfyNewWorkflowClick);
3889 $('#sd_comfy_delete_workflow').on('click', onComfyDeleteWorkflowClick);4157 $('#sd_comfy_delete_workflow').on('click', onComfyDeleteWorkflowClick);
3890 $('#sd_expand').on('input', onExpandInput);
3891 $('#sd_style').on('change', onStyleSelect);4158 $('#sd_style').on('change', onStyleSelect);
3892 $('#sd_save_style').on('click', onSaveStyleClick);4159 $('#sd_save_style').on('click', onSaveStyleClick);
3893 $('#sd_delete_style').on('click', onDeleteStyleClick);4160 $('#sd_delete_style').on('click', onDeleteStyleClick);
public/scripts/extensions/stable-diffusion/settings.html+1 -14
@@ -27,11 +27,6 @@
27 <span data-i18n="sd_free_extend_txt">Extend free mode prompts</span>27 <span data-i18n="sd_free_extend_txt">Extend free mode prompts</span>
28 <small data-i18n="sd_free_extend_small">(interactive/commands)</small>28 <small data-i18n="sd_free_extend_small">(interactive/commands)</small>
29 </label>29 </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>
35 <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).">30 <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).">
36 <input id="sd_snap" type="checkbox" />31 <input id="sd_snap" type="checkbox" />
37 <span data-i18n="sd_snap_txt">Snap auto-adjusted resolutions</span>32 <span data-i18n="sd_snap_txt">Snap auto-adjusted resolutions</span>
@@ -308,7 +303,7 @@
308 </div>303 </div>
309304
310 <div class="flex-container">305 <div class="flex-container">
311 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,drawthings">306 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,drawthings,novel">
312 <small>307 <small>
313 <span data-i18n="Upscale by">Upscale by</span>308 <span data-i18n="Upscale by">Upscale by</span>
314 </small>309 </small>
@@ -332,14 +327,6 @@
332 <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}}" >327 <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}}" >
333 </div>328 </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
343 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras">330 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy,horde,drawthings,extras">
344 <small>331 <small>
345 <span data-i18n="CLIP Skip">CLIP Skip</span>332 <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
384 * @returns {string} Formatted instruct mode system prompt.384 * @returns {string} Formatted instruct mode system prompt.
385 */385 */
386export function formatInstructModeSystemPrompt(systemPrompt) {386export function formatInstructModeSystemPrompt(systemPrompt) {
387 if (!systemPrompt) {
388 return '';
389 }
390
387 const separator = power_user.instruct.wrap ? '\n' : '';391 const separator = power_user.instruct.wrap ? '\n' : '';
388392
389 if (power_user.instruct.system_sequence_prefix) {393 if (power_user.instruct.system_sequence_prefix) {
public/scripts/slash-commands.js+1 -1
@@ -3090,7 +3090,7 @@ async function continueChatCallback(args, prompt) {
3090 resolve();3090 resolve();
3091 } catch (error) {3091 } catch (error) {
3092 console.error('Error running /continue command:', error);3092 console.error('Error running /continue command:', error);
3093 reject();3093 reject(error);
3094 }3094 }
3095 });3095 });
30963096
src/endpoints/stable-diffusion.js+0 -69
@@ -10,41 +10,6 @@ const { readSecret, SECRET_KEYS } = require('./secrets.js');
10const FormData = require('form-data');10const FormData = require('form-data');
1111
12/**12/**
13 * Sanitizes a string.
14 * @param {string} x String to sanitize
15 * @returns {string} Sanitized string
16 */
17function 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
25const splitStrings = [
26 ', extremely',
27 ', intricate,',
28];
29
30const 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 */
38function 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/**
48 * Gets the comfy workflows.13 * Gets the comfy workflows.
49 * @param {import('../users.js').UserDirectoryList} directories14 * @param {import('../users.js').UserDirectoryList} directories
50 * @returns {string[]} List of comfy workflows15 * @returns {string[]} List of comfy workflows
@@ -391,40 +356,6 @@ router.post('/sd-next/upscalers', jsonParser, async (request, response) => {
391 }356 }
392});357});
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 */
398router.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
428const comfy = express.Router();359const comfy = express.Router();
429360
430comfy.post('/ping', jsonParser, async (request, response) => {361comfy.post('/ping', jsonParser, async (request, response) => {
src/transformers.mjs+0 -6
@@ -31,12 +31,6 @@ const tasks = {
31 configField: 'extras.embeddingModel',31 configField: 'extras.embeddingModel',
32 quantized: true,32 quantized: true,
33 },33 },
34 'text-generation': {
35 defaultModel: 'Cohee/fooocus_expansion-onnx',
36 pipeline: null,
37 configField: 'extras.promptExpansionModel',
38 quantized: false,
39 },
40 'automatic-speech-recognition': {34 'automatic-speech-recognition': {
41 defaultModel: 'Xenova/whisper-small',35 defaultModel: 'Xenova/whisper-small',
42 pipeline: null,36 pipeline: null,