@@ -12,6 +12,7 @@
"%prompt%"
"%negative_prompt%"
"%model%"
+ "%vae%"
"%sampler%"
"%scheduler%"
"%steps%"
diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js
index c9e2fc29e..4e2b1ccc9 100644
--- a/public/scripts/extensions/stable-diffusion/index.js
+++ b/public/scripts/extensions/stable-diffusion/index.js
@@ -19,7 +19,7 @@ import {
} from "../../../script.js";
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplate } from "../../extensions.js";
import { selected_group } from "../../group-chats.js";
-import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async } from "../../utils.js";
+import { stringFormat, initScrollHeight, resetScrollHeight, getCharaFilename, saveBase64AsFile, getBase64Async, delay } from "../../utils.js";
import { getMessageTimeStamp, humanizedDateTime } from "../../RossAscends-mods.js";
import { SECRET_KEYS, secret_state } from "../../secrets.js";
import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from "../../nai-settings.js";
@@ -186,6 +186,7 @@ const defaultSettings = {
negative_prompt: defaultNegative,
sampler: 'DDIM',
model: '',
+ vae: '',
// Automatic1111/Horde exclusives
restore_faces: false,
@@ -242,94 +243,7 @@ const defaultSettings = {
// ComyUI settings
comfy_url: 'http://127.0.0.1:8188',
- comfy_workflow: `
- {
- "3": {
- "class_type": "KSampler",
- "inputs": {
- "cfg": "%scale%",
- "denoise": 1,
- "latent_image": [
- "5",
- 0
- ],
- "model": [
- "4",
- 0
- ],
- "negative": [
- "7",
- 0
- ],
- "positive": [
- "6",
- 0
- ],
- "sampler_name": "%sampler%",
- "scheduler": "%scheduler%",
- "seed": 8566257,
- "steps": "%steps%"
- }
- },
- "4": {
- "class_type": "CheckpointLoaderSimple",
- "inputs": {
- "ckpt_name": "%model%"
- }
- },
- "5": {
- "class_type": "EmptyLatentImage",
- "inputs": {
- "batch_size": 1,
- "height": "%height%",
- "width": "%width%"
- }
- },
- "6": {
- "class_type": "CLIPTextEncode",
- "inputs": {
- "clip": [
- "4",
- 1
- ],
- "text": "%prompt%"
- }
- },
- "7": {
- "class_type": "CLIPTextEncode",
- "inputs": {
- "clip": [
- "4",
- 1
- ],
- "text": "%negative_prompt%"
- }
- },
- "8": {
- "class_type": "VAEDecode",
- "inputs": {
- "samples": [
- "3",
- 0
- ],
- "vae": [
- "4",
- 2
- ]
- }
- },
- "9": {
- "class_type": "SaveImage",
- "inputs": {
- "filename_prefix": "SillyTavern",
- "images": [
- "8",
- 0
- ]
- }
- }
- }
- `,
+ comfy_workflow: 'Default_Comfy_Workflow.json',
}
function processTriggers(chat, _, abort) {
@@ -480,7 +394,17 @@ async function loadSettings() {
toggleSourceControls();
addPromptTemplates();
- await Promise.all([loadSamplers(), loadModels(), loadSchedulers()]);
+ await loadSettingOptions();
+}
+
+async function loadSettingOptions() {
+ return Promise.all([
+ loadSamplers(),
+ loadModels(),
+ loadSchedulers(),
+ loadVaes(),
+ loadComfyWorkflows()
+ ]);
}
function addPromptTemplates() {
@@ -846,9 +770,8 @@ function onComfyUrlInput() {
saveSettingsDebounced();
}
-function onComfyPromptInput() {
- extension_settings.sd.comfy_prompt = $('#sd_comfy_prompt').val();
- resetScrollHeight($(this));
+function onComfyWorkflowChange() {
+ extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow').find(':selected').val();
saveSettingsDebounced();
}
@@ -920,6 +843,8 @@ async function validateComfyUrl() {
await loadSamplers();
await loadSchedulers();
await loadModels();
+ await loadVaes();
+ await loadComfyWorkflows();
toastr.success('ComfyUI API connected.');
} catch (error) {
toastr.error(`Could not validate ComfyUI API: ${error.message}`);
@@ -966,6 +891,10 @@ async function getAutoRemoteModel() {
}
}
+async function onVaeChange() {
+ extension_settings.sd.vae = $('#sd_vae').find(':selected').val();
+}
+
async function getAutoRemoteUpscalers() {
try {
const result = await fetch('/api/sd/upscalers', {
@@ -1479,6 +1408,95 @@ async function loadComfySchedulers() {
}
}
+async function loadVaes() {
+ $('#sd_vae').empty();
+ let vaes = [];
+
+ switch (extension_settings.sd.source) {
+ case sources.extras:
+ vaes = ['N/A'];
+ break;
+ case sources.horde:
+ vaes = ['N/A'];
+ break;
+ case sources.auto:
+ vaes = ['N/A'];
+ break;
+ case sources.novel:
+ vaes = ['N/A'];
+ break;
+ case sources.vlad:
+ vaes = ['N/A'];
+ break;
+ case sources.openai:
+ vaes = ['N/A'];
+ break;
+ case sources.comfy:
+ vaes = await loadComfyVaes();
+ break;
+ }
+
+ for (const vae of vaes) {
+ const option = document.createElement('option');
+ option.innerText = vae;
+ option.value = vae;
+ option.selected = vae === extension_settings.sd.vae;
+ $('#sd_vae').append(option);
+ }
+}
+
+async function loadComfyVaes() {
+ if (!extension_settings.sd.comfy_url) {
+ return [];
+ }
+
+ try {
+ const result = await fetch(`/api/sd/comfy/vaes`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({
+ url: extension_settings.sd.comfy_url,
+ })
+ });
+ if (!result.ok) {
+ throw new Error('ComfyUI returned an error.');
+ }
+ return await result.json();
+ } catch (error) {
+ return [];
+ }
+}
+
+async function loadComfyWorkflows() {
+ if (!extension_settings.sd.comfy_url) {
+ return;
+ }
+
+ try {
+ $('#sd_comfy_workflow').empty();
+ const result = await fetch(`/api/sd/comfy/workflows`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({
+ url: extension_settings.sd.comfy_url,
+ })
+ });
+ if (!result.ok) {
+ throw new Error('ComfyUI returned an error.');
+ }
+ const workflows = await result.json();
+ for (const workflow of workflows) {
+ const option = document.createElement('option');
+ option.innerText = workflow;
+ option.value = workflow;
+ option.selected = workflow === extension_settings.sd.comfy_workflow;
+ $('#sd_comfy_workflow').append(option);
+ }
+ } catch (error) {
+ return;
+ }
+}
+
function getGenerationType(prompt) {
let mode = generationMode.FREE;
@@ -2043,6 +2061,7 @@ async function generateComfyImage(prompt) {
const placeholders = [
'negative_prompt',
'model',
+ 'vae',
'sampler',
'scheduler',
'steps',
@@ -2051,7 +2070,18 @@ async function generateComfyImage(prompt) {
'height',
];
- let workflow = extension_settings.sd.comfy_workflow.replace('"%prompt%"', JSON.stringify(prompt));
+ const workflowResponse = await fetch('/api/sd/comfy/workflow', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({
+ file_name: extension_settings.sd.comfy_workflow,
+ }),
+ });
+ if (!workflowResponse.ok) {
+ const text = await workflowResponse.text();
+ toastr.error(`Failed to load workflow.\n\n${text}`);
+ }
+ let workflow = (await workflowResponse.json()).replace('"%prompt%"', JSON.stringify(prompt));
workflow = workflow.replace('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)));
placeholders.forEach(ph => {
workflow = workflow.replace(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
@@ -2073,25 +2103,90 @@ async function generateComfyImage(prompt) {
}
async function onComfyOpenWorkflowEditorClick() {
+ let workflow = await (await fetch(`/api/sd/comfy/workflow`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({
+ file_name: extension_settings.sd.comfy_workflow,
+ }),
+ })).json();
const editorHtml = $(await $.get('scripts/extensions/stable-diffusion/comfyWorkflowEditor.html'));
const popupResult = callPopup(editorHtml, "confirm", undefined, { okButton: "Save", wide: true, large: true, rows: 1 });
const checkPlaceholders = () => {
- const workflow = $('#sd_comfy_workflow_editor_workflow').val().toString();
+ workflow = $('#sd_comfy_workflow_editor_workflow').val().toString();
$('.sd_comfy_workflow_editor_placeholder_list > li[data-placeholder]').each(function (idx) {
const key = this.getAttribute('data-placeholder');
const found = workflow.search(`"%${key}%"`) != -1;
this.classList[found ? 'remove' : 'add']('sd_comfy_workflow_editor_not_found');
});
};
- $('#sd_comfy_workflow_editor_workflow').val(extension_settings.sd.comfy_workflow);
+ $('#sd_comfy_workflow_editor_name').text(extension_settings.sd.comfy_workflow);
+ $('#sd_comfy_workflow_editor_workflow').val(workflow);
checkPlaceholders();
$('#sd_comfy_workflow_editor_workflow').on('input', checkPlaceholders);
if (await popupResult) {
- extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow_editor_workflow').val().toString();
- saveSettingsDebounced();
+ const response = await fetch(`/api/sd/comfy/save-workflow`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({
+ file_name: extension_settings.sd.comfy_workflow,
+ workflow: $('#sd_comfy_workflow_editor_workflow').val().toString(),
+ }),
+ });
+ if (!response.ok) {
+ const text = await response.text();
+ toastr.error(`Failed to save workflow.\n\n${text}`);
+ }
}
}
+async function onComfyNewWorkflowClick() {
+ let name = await callPopup('Workflow name:
', 'input');
+ if (!name) {
+ return;
+ }
+ if (!name.toLowerCase().endsWith('.json')) {
+ name += '.json';
+ }
+ extension_settings.sd.comfy_workflow = name;
+ const response = await fetch(`/api/sd/comfy/save-workflow`, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({
+ file_name: extension_settings.sd.comfy_workflow,
+ workflow: '',
+ }),
+ });
+ if (!response.ok) {
+ const text = await response.text();
+ toastr.error(`Failed to save workflow.\n\n${text}`);
+ }
+ saveSettingsDebounced();
+ await loadComfyWorkflows();
+ await delay(200);
+ await onComfyOpenWorkflowEditorClick();
+}
+
+async function onComfyDeleteWorkflowClick() {
+ const confirm = await callPopup('Delete the workflow? This action is irreversible.', 'confirm');
+ if (!confirm) {
+ return;
+ }
+ const response = await fetch('/api/sd/comfy/delete-workflow', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({
+ file_name: extension_settings.sd.comfy_workflow,
+ }),
+ });
+ if (!response.ok) {
+ const text = await response.text();
+ toastr.error(`Failed to save workflow.\n\n${text}`);
+ }
+ await loadComfyWorkflows();
+ onComfyWorkflowChange();
+}
+
async function sendMessage(prompt, image, generationType) {
const context = getContext();
const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`;
@@ -2300,6 +2395,7 @@ jQuery(async () => {
$('#sd_scale').on('input', onScaleInput);
$('#sd_steps').on('input', onStepsInput);
$('#sd_model').on('change', onModelChange);
+ $('#sd_vae').on('change', onVaeChange);
$('#sd_sampler').on('change', onSamplerChange);
$('#sd_scheduler').on('change', onSchedulerChange);
$('#sd_prompt_prefix').on('input', onPromptPrefixInput);
@@ -2328,7 +2424,10 @@ jQuery(async () => {
$('#sd_novel_view_anlas').on('click', onViewAnlasClick);
$('#sd_comfy_validate').on('click', validateComfyUrl);
$('#sd_comfy_url').on('input', onComfyUrlInput);
+ $('#sd_comfy_workflow').on('change', onComfyWorkflowChange);
$('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick);
+ $('#sd_comfy_new_workflow').on('click', onComfyNewWorkflowClick);
+ $('#sd_comfy_delete_workflow').on('click', onComfyDeleteWorkflowClick);
$('#sd_expand').on('input', onExpandInput);
$('#sd_style').on('change', onStyleSelect);
$('#sd_save_style').on('click', onSaveStyleClick);
diff --git a/public/scripts/extensions/stable-diffusion/settings.html b/public/scripts/extensions/stable-diffusion/settings.html
index cb0a20cd4..d3d7e6e20 100644
--- a/public/scripts/extensions/stable-diffusion/settings.html
+++ b/public/scripts/extensions/stable-diffusion/settings.html
@@ -128,11 +128,20 @@