diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index a049c9319..51d412242 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -25,6 +25,7 @@ const extension_settings = { expressions: {}, dice: {}, tts: {}, + sd: {}, }; let modules = []; diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index 6e299bd21..89b789139 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -381,4 +381,5 @@ function onClickExpressionImage() { addExpressionImage(); addSettings(); setInterval(moduleWorkerWrapper, UPDATE_INTERVAL); + moduleWorkerWrapper(); })(); \ No newline at end of file diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index ea9719fcf..b17977e17 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -1,5 +1,5 @@ -import { substituteParams } from "../../../script.js"; -import { getApiUrl, getContext } from "../../extensions.js"; +import { substituteParams, saveSettingsDebounced } from "../../../script.js"; +import { getApiUrl, getContext, extension_settings, defaultRequestArgs } from "../../extensions.js"; import { stringFormat } from "../../utils.js"; // Wraps a string into monospace font-face span @@ -7,6 +7,11 @@ const m = x => `${x}`; // Joins an array of strings with ' / ' const j = a => a.join(' / '); +const postHeaders = { +'Content-Type': 'application/json', +'Bypass-Tunnel-Reminder': 'bypass', +}; + const generationMode = { CHARACTER: 0, USER: 1, @@ -27,7 +32,6 @@ const quietPrompts = { [generationMode.FREE]: 'Please provide a detailed and vivid description of {0}', } - const helpString = [ `${m('what')} – requests an SD generation. Supported "what" arguments:`, '', - `Anything else would trigger a "free mode" with AI describing whatever you prompted.` + `Anything else would trigger a "free mode" with AI describing whatever you prompted.`, ].join('
'); +const defaultSettings = { + // CFG Scale + scale_min: 1, + scale_max: 30, + scale_step: 0.5, + scale: 7, + + // Sampler steps + steps_min: 1, + steps_max: 150, + steps_step: 1, + steps: 20, + + // Image dimensions (Width & Height) + dimension_min: 64, + dimension_max: 2048, + dimension_step: 64, + width: 512, + height: 512, + + prompt_prefix: 'best quality, absurdres, masterpiece, detailed, intricate, colorful,', + negative_prompt: 'lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry', + sampler: 'DDIM', + model: '', +} + +async function loadSettings() { + if (Object.keys(extension_settings.sd).length === 0) { + Object.assign(extension_settings.sd, defaultSettings); + } + + $('#sd_scale').val(extension_settings.sd.scale).trigger('input'); + $('#sd_steps').val(extension_settings.sd.steps).trigger('input'); + $('#sd_prompt_prefix').val(extension_settings.sd.prompt_prefix).trigger('input'); + $('#sd_negative_prompt').val(extension_settings.sd.negative_prompt).trigger('input'); + $('#sd_width').val(extension_settings.sd.width).trigger('input'); + $('#sd_height').val(extension_settings.sd.height).trigger('input'); + + await Promise.all([loadSamplers, loadModels]); +} + +function onScaleInput() { + extension_settings.sd.scale = Number($('#sd_scale').val()); + $('#sd_scale_value').text(extension_settings.sd.scale.toFixed(1)); + saveSettingsDebounced(); +} + +function onStepsInput() { + extension_settings.sd.steps = Number($('#sd_steps').val()); + $('#sd_steps_value').text(extension_settings.sd.steps); + saveSettingsDebounced(); +} + +function onPromptPrefixInput() { + extension_settings.sd.prompt_prefix = $('#sd_prompt_prefix').val(); + saveSettingsDebounced(); +} + +function onNegativePromptInput() { + extension_settings.sd.negative_prompt = $('#sd_negative_prompt').val(); + saveSettingsDebounced(); +} + +function onSamplerChange() { + extension_settings.sd.sampler = $('#sd_sampler').find(':selected').val(); + saveSettingsDebounced(); +} + +function onWidthInput() { + extension_settings.sd.width = Number($('#sd_width').val()); + $('#sd_width_value').text(extension_settings.sd.width); + saveSettingsDebounced(); +} + +function onHeightInput() { + extension_settings.sd.height = Number($('#sd_height').val()); + $('#sd_height_value').text(extension_settings.sd.height); + saveSettingsDebounced(); +} + +async function onModelChange() { + extension_settings.sd.model = $('#sd_model').find(':selected').val(); + saveSettingsDebounced(); + + const url = new URL(getApiUrl()); + url.pathname = '/api/image/model'; + const getCurrentModelResult = await fetch(url, { + method: 'POST', + headers: postHeaders, + body: JSON.stringify({ model: extension_settings.sd.model }), + }); + + if (getCurrentModelResult.ok) { + console.log('Model successfully updated on SD remote.'); + } +} + +async function loadSamplers() { + const url = new URL(getApiUrl()); + url.pathname = '/api/image/samplers'; + const result = await fetch(url, defaultRequestArgs); + + if (result.ok) { + const data = await result.json(); + const samplers = data.samplers; + + for (const sampler of samplers) { + const option = document.createElement('option'); + option.innerText = sampler; + option.value = sampler; + option.selected = sampler === extension_settings.sd.sampler; + $('#sd_sampler').append(option); + } + } +} + +async function loadModels() { + const url = new URL(getApiUrl()); + url.pathname = '/api/image/model'; + const getCurrentModelResult = await fetch(url, defaultRequestArgs); + + if (getCurrentModelResult.ok) { + const data = await getCurrentModelResult.json(); + extension_settings.sd.model = data.model; + } + + url.pathname = '/api/image/models'; + const getModelsResult = await fetch(url, defaultRequestArgs); + + if (getModelsResult.ok) { + const data = await getModelsResult.json(); + const models = data.models; + + for (const model of models) { + const option = document.createElement('option'); + option.innerText = model; + option.value = model; + option.selected = model === extension_settings.sd.model; + $('#sd_model').append(option); + } + } +} + function getGenerationType(prompt) { for (const [key, values] of Object.entries(triggerWords)) { for (const value of values) { @@ -92,11 +239,18 @@ async function generatePicture(_, trigger) { url.pathname = '/api/image'; const result = await fetch(url, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Bypass-Tunnel-Reminder': 'bypass', - }, - body: JSON.stringify({ prompt: prompt }) + headers: postHeaders, + body: JSON.stringify({ + prompt: prompt, + sampler: extension_settings.sd.sampler, + steps: extension_settings.sd.steps, + scale: extension_settings.sd.scale, + model: extension_settings.sd.model, + width: extension_settings.sd.width, + height: extension_settings.sd.height, + prompt_prefix: extension_settings.sd.prompt_prefix, + negative_prompt: extension_settings.sd.negative_prompt, + }), }); if (result.ok) { @@ -131,6 +285,45 @@ async function sendMessage(prompt, image) { context.saveChat(); } -jQuery(() => { +jQuery(async () => { getContext().registerSlashCommand('sd', generatePicture, ['picture', 'image'], helpString, true, true); + + const settingsHtml = ` +
+
+
+ Stable Diffusion +
+
+
+ Use slash commands to generate images. Type /help in chat for more details + + + + + + + + + + + + + + + + +
+
`; + + $('#extensions_settings').append(settingsHtml); + $('#sd_scale').on('input', onScaleInput); + $('#sd_steps').on('input', onStepsInput); + $('#sd_model').on('change', onModelChange); + $('#sd_sampler').on('change', onSamplerChange); + $('#sd_prompt_prefix').on('input', onPromptPrefixInput); + $('#sd_negative_prompt').on('input', onNegativePromptInput); + $('#sd_width').on('input', onWidthInput); + $('#sd_height').on('input', onHeightInput); + await loadSettings(); }); \ No newline at end of file diff --git a/public/scripts/extensions/stable-diffusion/style.css b/public/scripts/extensions/stable-diffusion/style.css index e69de29bb..0fe6446f8 100644 --- a/public/scripts/extensions/stable-diffusion/style.css +++ b/public/scripts/extensions/stable-diffusion/style.css @@ -0,0 +1,3 @@ +.sd_settings label { + display: block; +} \ No newline at end of file