Merge pull request #1378 from LenAnderson/more-comfy-merge
Add more ComfyUI options
This commit is contained in:
commit
91429ce516
|
@ -0,0 +1,86 @@
|
||||||
|
{
|
||||||
|
"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": "%seed%",
|
||||||
|
"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
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,5 +22,9 @@
|
||||||
{
|
{
|
||||||
"filename": "user-default.png",
|
"filename": "user-default.png",
|
||||||
"type": "avatar"
|
"type": "avatar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "Default_Comfy_Workflow.json",
|
||||||
|
"type": "workflow"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div id="sd_comfy_workflow_editor_template">
|
<div id="sd_comfy_workflow_editor_template">
|
||||||
<div class="sd_comfy_workflow_editor">
|
<div class="sd_comfy_workflow_editor">
|
||||||
<h3><strong>ComfyUI Workflow Editor</strong></h3>
|
<h3><strong>ComfyUI Workflow Editor: <span id="sd_comfy_workflow_editor_name"></span></strong></h3>
|
||||||
<div class="sd_comfy_workflow_editor_content">
|
<div class="sd_comfy_workflow_editor_content">
|
||||||
<div class="flex-container flexFlowColumn sd_comfy_workflow_editor_workflow_container">
|
<div class="flex-container flexFlowColumn sd_comfy_workflow_editor_workflow_container">
|
||||||
<label for="sd_comfy_workflow_editor_workflow">Workflow (JSON)</label>
|
<label for="sd_comfy_workflow_editor_workflow">Workflow (JSON)</label>
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
<li data-placeholder="prompt" class="sd_comfy_workflow_editor_not_found">"%prompt%"</li>
|
<li data-placeholder="prompt" class="sd_comfy_workflow_editor_not_found">"%prompt%"</li>
|
||||||
<li data-placeholder="negative_prompt" class="sd_comfy_workflow_editor_not_found">"%negative_prompt%"</li>
|
<li data-placeholder="negative_prompt" class="sd_comfy_workflow_editor_not_found">"%negative_prompt%"</li>
|
||||||
<li data-placeholder="model" class="sd_comfy_workflow_editor_not_found">"%model%"</li>
|
<li data-placeholder="model" class="sd_comfy_workflow_editor_not_found">"%model%"</li>
|
||||||
|
<li data-placeholder="vae" class="sd_comfy_workflow_editor_not_found">"%vae%"</li>
|
||||||
<li data-placeholder="sampler" class="sd_comfy_workflow_editor_not_found">"%sampler%"</li>
|
<li data-placeholder="sampler" class="sd_comfy_workflow_editor_not_found">"%sampler%"</li>
|
||||||
<li data-placeholder="scheduler" class="sd_comfy_workflow_editor_not_found">"%scheduler%"</li>
|
<li data-placeholder="scheduler" class="sd_comfy_workflow_editor_not_found">"%scheduler%"</li>
|
||||||
<li data-placeholder="steps" class="sd_comfy_workflow_editor_not_found">"%steps%"</li>
|
<li data-placeholder="steps" class="sd_comfy_workflow_editor_not_found">"%steps%"</li>
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
} from "../../../script.js";
|
} from "../../../script.js";
|
||||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplate } from "../../extensions.js";
|
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules, renderExtensionTemplate } from "../../extensions.js";
|
||||||
import { selected_group } from "../../group-chats.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 { getMessageTimeStamp, humanizedDateTime } from "../../RossAscends-mods.js";
|
||||||
import { SECRET_KEYS, secret_state } from "../../secrets.js";
|
import { SECRET_KEYS, secret_state } from "../../secrets.js";
|
||||||
import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from "../../nai-settings.js";
|
import { getNovelUnlimitedImageGeneration, getNovelAnlas, loadNovelSubscriptionData } from "../../nai-settings.js";
|
||||||
|
@ -186,6 +186,7 @@ const defaultSettings = {
|
||||||
negative_prompt: defaultNegative,
|
negative_prompt: defaultNegative,
|
||||||
sampler: 'DDIM',
|
sampler: 'DDIM',
|
||||||
model: '',
|
model: '',
|
||||||
|
vae: '',
|
||||||
|
|
||||||
// Automatic1111/Horde exclusives
|
// Automatic1111/Horde exclusives
|
||||||
restore_faces: false,
|
restore_faces: false,
|
||||||
|
@ -242,94 +243,7 @@ const defaultSettings = {
|
||||||
|
|
||||||
// ComyUI settings
|
// ComyUI settings
|
||||||
comfy_url: 'http://127.0.0.1:8188',
|
comfy_url: 'http://127.0.0.1:8188',
|
||||||
comfy_workflow: `
|
comfy_workflow: 'Default_Comfy_Workflow.json',
|
||||||
{
|
|
||||||
"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
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function processTriggers(chat, _, abort) {
|
function processTriggers(chat, _, abort) {
|
||||||
|
@ -480,7 +394,17 @@ async function loadSettings() {
|
||||||
toggleSourceControls();
|
toggleSourceControls();
|
||||||
addPromptTemplates();
|
addPromptTemplates();
|
||||||
|
|
||||||
await Promise.all([loadSamplers(), loadModels(), loadSchedulers()]);
|
await loadSettingOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSettingOptions() {
|
||||||
|
return Promise.all([
|
||||||
|
loadSamplers(),
|
||||||
|
loadModels(),
|
||||||
|
loadSchedulers(),
|
||||||
|
loadVaes(),
|
||||||
|
loadComfyWorkflows()
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPromptTemplates() {
|
function addPromptTemplates() {
|
||||||
|
@ -846,9 +770,8 @@ function onComfyUrlInput() {
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onComfyPromptInput() {
|
function onComfyWorkflowChange() {
|
||||||
extension_settings.sd.comfy_prompt = $('#sd_comfy_prompt').val();
|
extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow').find(':selected').val();
|
||||||
resetScrollHeight($(this));
|
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -920,6 +843,8 @@ async function validateComfyUrl() {
|
||||||
await loadSamplers();
|
await loadSamplers();
|
||||||
await loadSchedulers();
|
await loadSchedulers();
|
||||||
await loadModels();
|
await loadModels();
|
||||||
|
await loadVaes();
|
||||||
|
await loadComfyWorkflows();
|
||||||
toastr.success('ComfyUI API connected.');
|
toastr.success('ComfyUI API connected.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toastr.error(`Could not validate ComfyUI API: ${error.message}`);
|
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() {
|
async function getAutoRemoteUpscalers() {
|
||||||
try {
|
try {
|
||||||
const result = await fetch('/api/sd/upscalers', {
|
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) {
|
function getGenerationType(prompt) {
|
||||||
let mode = generationMode.FREE;
|
let mode = generationMode.FREE;
|
||||||
|
|
||||||
|
@ -2043,6 +2061,7 @@ async function generateComfyImage(prompt) {
|
||||||
const placeholders = [
|
const placeholders = [
|
||||||
'negative_prompt',
|
'negative_prompt',
|
||||||
'model',
|
'model',
|
||||||
|
'vae',
|
||||||
'sampler',
|
'sampler',
|
||||||
'scheduler',
|
'scheduler',
|
||||||
'steps',
|
'steps',
|
||||||
|
@ -2051,7 +2070,18 @@ async function generateComfyImage(prompt) {
|
||||||
'height',
|
'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)));
|
workflow = workflow.replace('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)));
|
||||||
placeholders.forEach(ph => {
|
placeholders.forEach(ph => {
|
||||||
workflow = workflow.replace(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
|
workflow = workflow.replace(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
|
||||||
|
@ -2073,25 +2103,90 @@ async function generateComfyImage(prompt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onComfyOpenWorkflowEditorClick() {
|
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 editorHtml = $(await $.get('scripts/extensions/stable-diffusion/comfyWorkflowEditor.html'));
|
||||||
const popupResult = callPopup(editorHtml, "confirm", undefined, { okButton: "Save", wide: true, large: true, rows: 1 });
|
const popupResult = callPopup(editorHtml, "confirm", undefined, { okButton: "Save", wide: true, large: true, rows: 1 });
|
||||||
const checkPlaceholders = () => {
|
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) {
|
$('.sd_comfy_workflow_editor_placeholder_list > li[data-placeholder]').each(function (idx) {
|
||||||
const key = this.getAttribute('data-placeholder');
|
const key = this.getAttribute('data-placeholder');
|
||||||
const found = workflow.search(`"%${key}%"`) != -1;
|
const found = workflow.search(`"%${key}%"`) != -1;
|
||||||
this.classList[found ? 'remove' : 'add']('sd_comfy_workflow_editor_not_found');
|
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();
|
checkPlaceholders();
|
||||||
$('#sd_comfy_workflow_editor_workflow').on('input', checkPlaceholders);
|
$('#sd_comfy_workflow_editor_workflow').on('input', checkPlaceholders);
|
||||||
if (await popupResult) {
|
if (await popupResult) {
|
||||||
extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow_editor_workflow').val().toString();
|
const response = await fetch(`/api/sd/comfy/save-workflow`, {
|
||||||
saveSettingsDebounced();
|
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('<h3>Workflow name:</h3>', '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) {
|
async function sendMessage(prompt, image, generationType) {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`;
|
const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`;
|
||||||
|
@ -2300,6 +2395,7 @@ jQuery(async () => {
|
||||||
$('#sd_scale').on('input', onScaleInput);
|
$('#sd_scale').on('input', onScaleInput);
|
||||||
$('#sd_steps').on('input', onStepsInput);
|
$('#sd_steps').on('input', onStepsInput);
|
||||||
$('#sd_model').on('change', onModelChange);
|
$('#sd_model').on('change', onModelChange);
|
||||||
|
$('#sd_vae').on('change', onVaeChange);
|
||||||
$('#sd_sampler').on('change', onSamplerChange);
|
$('#sd_sampler').on('change', onSamplerChange);
|
||||||
$('#sd_scheduler').on('change', onSchedulerChange);
|
$('#sd_scheduler').on('change', onSchedulerChange);
|
||||||
$('#sd_prompt_prefix').on('input', onPromptPrefixInput);
|
$('#sd_prompt_prefix').on('input', onPromptPrefixInput);
|
||||||
|
@ -2328,7 +2424,10 @@ jQuery(async () => {
|
||||||
$('#sd_novel_view_anlas').on('click', onViewAnlasClick);
|
$('#sd_novel_view_anlas').on('click', onViewAnlasClick);
|
||||||
$('#sd_comfy_validate').on('click', validateComfyUrl);
|
$('#sd_comfy_validate').on('click', validateComfyUrl);
|
||||||
$('#sd_comfy_url').on('input', onComfyUrlInput);
|
$('#sd_comfy_url').on('input', onComfyUrlInput);
|
||||||
|
$('#sd_comfy_workflow').on('change', onComfyWorkflowChange);
|
||||||
$('#sd_comfy_open_workflow_editor').on('click', onComfyOpenWorkflowEditorClick);
|
$('#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_expand').on('input', onExpandInput);
|
||||||
$('#sd_style').on('change', onStyleSelect);
|
$('#sd_style').on('change', onStyleSelect);
|
||||||
$('#sd_save_style').on('click', onSaveStyleClick);
|
$('#sd_save_style').on('click', onSaveStyleClick);
|
||||||
|
|
|
@ -128,11 +128,20 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="sd_comfy_open_workflow_editor" class="menu_button">
|
|
||||||
<i class="fa-solid fa-pen-to-square"></i>
|
|
||||||
<span>Open Workflow Editor</span>
|
|
||||||
</div>
|
|
||||||
<p><i><b>Important:</b> The server must be accessible from the SillyTavern host machine.</i></p>
|
<p><i><b>Important:</b> The server must be accessible from the SillyTavern host machine.</i></p>
|
||||||
|
<label for="sd_comfy_workflow">ComfyUI Workflow</label>
|
||||||
|
<div class="flex-container flexnowrap">
|
||||||
|
<select id="sd_comfy_workflow" class="flex1 text_pole"></select>
|
||||||
|
<div id="sd_comfy_open_workflow_editor" class="menu_button menu_button_icon" title="Open workflow editor">
|
||||||
|
<i class="fa-solid fa-pen-to-square"></i>
|
||||||
|
</div>
|
||||||
|
<div id="sd_comfy_new_workflow" class="menu_button menu_button_icon" title="Create new workflow">
|
||||||
|
<i class="fa-solid fa-plus"></i>
|
||||||
|
</div>
|
||||||
|
<div id="sd_comfy_delete_workflow" class="menu_button menu_button_icon" title="Delete workflow">
|
||||||
|
<i class="fa-solid fa-trash-can"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
|
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
|
||||||
<input id="sd_scale" type="range" min="{{scale_min}}" max="{{scale_max}}" step="{{scale_step}}" value="{{scale}}" />
|
<input id="sd_scale" type="range" min="{{scale_min}}" max="{{scale_max}}" step="{{scale_step}}" value="{{scale}}" />
|
||||||
|
@ -150,6 +159,10 @@
|
||||||
<label for="sd_scheduler">Scheduler</label>
|
<label for="sd_scheduler">Scheduler</label>
|
||||||
<select id="sd_scheduler"></select>
|
<select id="sd_scheduler"></select>
|
||||||
</div>
|
</div>
|
||||||
|
<div data-sd-source="comfy">
|
||||||
|
<label for="sd_vae">VAE</label>
|
||||||
|
<select id="sd_vae"></select>
|
||||||
|
</div>
|
||||||
<div class="flex-container marginTop10 margin-bot-10px">
|
<div class="flex-container marginTop10 margin-bot-10px">
|
||||||
<label class="flex1 checkbox_label">
|
<label class="flex1 checkbox_label">
|
||||||
<input id="sd_restore_faces" type="checkbox" />
|
<input id="sd_restore_faces" type="checkbox" />
|
||||||
|
|
|
@ -23,6 +23,7 @@ const DIRECTORIES = {
|
||||||
backups: 'backups/',
|
backups: 'backups/',
|
||||||
quickreplies: 'public/QuickReplies',
|
quickreplies: 'public/QuickReplies',
|
||||||
assets: 'public/assets',
|
assets: 'public/assets',
|
||||||
|
comfyWorkflows: 'public/user/workflows',
|
||||||
};
|
};
|
||||||
|
|
||||||
const UNSAFE_EXTENSIONS = [
|
const UNSAFE_EXTENSIONS = [
|
||||||
|
|
|
@ -71,6 +71,8 @@ function getTargetByType(type) {
|
||||||
return 'public/User Avatars';
|
return 'public/User Avatars';
|
||||||
case 'theme':
|
case 'theme':
|
||||||
return 'public/themes';
|
return 'public/themes';
|
||||||
|
case 'workflow':
|
||||||
|
return 'public/user/workflows';
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
const fetch = require('node-fetch').default;
|
const fetch = require('node-fetch').default;
|
||||||
|
const sanitize = require('sanitize-filename');
|
||||||
const { getBasicAuthHeader, delay } = require('./util');
|
const { getBasicAuthHeader, delay } = require('./util');
|
||||||
|
const fs = require('fs');
|
||||||
|
const { DIRECTORIES } = require('./constants.js');
|
||||||
|
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitizes a string.
|
* Sanitizes a string.
|
||||||
|
@ -38,6 +42,13 @@ function removePattern(x, pattern) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getComfyWorkflows() {
|
||||||
|
return fs
|
||||||
|
.readdirSync(DIRECTORIES.comfyWorkflows)
|
||||||
|
.filter(file => file[0]!='.' && file.toLowerCase().endsWith('.json'))
|
||||||
|
.sort(Intl.Collator().compare);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the endpoints for the Stable Diffusion API extension.
|
* Registers the endpoints for the Stable Diffusion API extension.
|
||||||
* @param {import("express").Express} app Express app
|
* @param {import("express").Express} app Express app
|
||||||
|
@ -418,6 +429,79 @@ function registerEndpoints(app, jsonParser) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/sd/comfy/vaes', jsonParser, async (request, response) => {
|
||||||
|
try {
|
||||||
|
const url = new URL(request.body.url);
|
||||||
|
url.pathname = '/object_info'
|
||||||
|
|
||||||
|
const result = await fetch(url);
|
||||||
|
if (!result.ok) {
|
||||||
|
throw new Error('ComfyUI returned an error.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await result.json();
|
||||||
|
return response.send(data.VAELoader.input.required.vae_name[0]);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/sd/comfy/workflows', jsonParser, async (request, response) => {
|
||||||
|
try {
|
||||||
|
const data = getComfyWorkflows();
|
||||||
|
return response.send(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/sd/comfy/workflow', jsonParser, async (request, response) => {
|
||||||
|
try {
|
||||||
|
let path = `${DIRECTORIES.comfyWorkflows}/${sanitize(String(request.body.file_name))}`;
|
||||||
|
if (!fs.existsSync(path)) {
|
||||||
|
path = `${DIRECTORIES.comfyWorkflows}/Default_Comfy_Workflow.json`;
|
||||||
|
}
|
||||||
|
const data = fs.readFileSync(
|
||||||
|
path,
|
||||||
|
{encoding:'utf-8'}
|
||||||
|
);
|
||||||
|
return response.send(JSON.stringify(data));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/sd/comfy/save-workflow', jsonParser, async (request, response) => {
|
||||||
|
try {
|
||||||
|
writeFileAtomicSync(
|
||||||
|
`${DIRECTORIES.comfyWorkflows}/${sanitize(String(request.body.file_name))}`,
|
||||||
|
request.body.workflow,
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
const data = getComfyWorkflows();
|
||||||
|
return response.send(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/sd/comfy/delete-workflow', jsonParser, async (request, response) => {
|
||||||
|
try {
|
||||||
|
let path = `${DIRECTORIES.comfyWorkflows}/${sanitize(String(request.body.file_name))}`;
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
fs.unlinkSync(path);
|
||||||
|
}
|
||||||
|
return response.sendStatus(200);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.post('/api/sd/comfy/generate', jsonParser, async (request, response) => {
|
app.post('/api/sd/comfy/generate', jsonParser, async (request, response) => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(request.body.url);
|
const url = new URL(request.body.url);
|
||||||
|
|
Loading…
Reference in New Issue