Merge branch 'SillyTavern:staging' into staging

5b9504e7bbf80b0f66a58ae1ce47817ba5838604

Yokayo <52032299+Yokayo@users.noreply.github.com>

Signed
5 files changed, +327 -149Ignore whitespace
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>
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,