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:`,
'
',
@@ -35,9 +39,152 @@ const helpString = [
`- ${m(j(triggerWords[generationMode.USER]))} – user character image
`,
`- ${m(j(triggerWords[generationMode.SCENARIO]))} – world scenario image
`,
'
',
- `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 = `
+
+
`;
+
+ $('#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