mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge pull request #3088 from ceruleandeep/feature/comfyGgufModels
Feature/comfy gguf models
This commit is contained in:
137
default/content/Char_Avatar_Comfy_Workflow.json
Normal file
137
default/content/Char_Avatar_Comfy_Workflow.json
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
"3": {
|
||||||
|
"inputs": {
|
||||||
|
"seed": "%seed%",
|
||||||
|
"steps": "%steps%",
|
||||||
|
"cfg": "%scale%",
|
||||||
|
"sampler_name": "%sampler%",
|
||||||
|
"scheduler": "%scheduler%",
|
||||||
|
"denoise": "%denoise%",
|
||||||
|
"model": [
|
||||||
|
"4",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"positive": [
|
||||||
|
"6",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"negative": [
|
||||||
|
"7",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"latent_image": [
|
||||||
|
"12",
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"class_type": "KSampler",
|
||||||
|
"_meta": {
|
||||||
|
"title": "KSampler"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"inputs": {
|
||||||
|
"ckpt_name": "%model%"
|
||||||
|
},
|
||||||
|
"class_type": "CheckpointLoaderSimple",
|
||||||
|
"_meta": {
|
||||||
|
"title": "Load Checkpoint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"inputs": {
|
||||||
|
"text": "%prompt%",
|
||||||
|
"clip": [
|
||||||
|
"4",
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"class_type": "CLIPTextEncode",
|
||||||
|
"_meta": {
|
||||||
|
"title": "CLIP Text Encode (Prompt)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
"inputs": {
|
||||||
|
"text": "%negative_prompt%",
|
||||||
|
"clip": [
|
||||||
|
"4",
|
||||||
|
1
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"class_type": "CLIPTextEncode",
|
||||||
|
"_meta": {
|
||||||
|
"title": "CLIP Text Encode (Negative Prompt)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"inputs": {
|
||||||
|
"samples": [
|
||||||
|
"3",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"vae": [
|
||||||
|
"4",
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"class_type": "VAEDecode",
|
||||||
|
"_meta": {
|
||||||
|
"title": "VAE Decode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
"inputs": {
|
||||||
|
"filename_prefix": "SillyTavern",
|
||||||
|
"images": [
|
||||||
|
"8",
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"class_type": "SaveImage",
|
||||||
|
"_meta": {
|
||||||
|
"title": "Save Image"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
"inputs": {
|
||||||
|
"image": "%char_avatar%"
|
||||||
|
},
|
||||||
|
"class_type": "ETN_LoadImageBase64",
|
||||||
|
"_meta": {
|
||||||
|
"title": "Load Image (Base64) [https://github.com/Acly/comfyui-tooling-nodes]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"12": {
|
||||||
|
"inputs": {
|
||||||
|
"pixels": [
|
||||||
|
"13",
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"vae": [
|
||||||
|
"4",
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"class_type": "VAEEncode",
|
||||||
|
"_meta": {
|
||||||
|
"title": "VAE Encode"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"13": {
|
||||||
|
"inputs": {
|
||||||
|
"upscale_method": "bicubic",
|
||||||
|
"width": "%width%",
|
||||||
|
"height": "%height%",
|
||||||
|
"crop": "center",
|
||||||
|
"image": [
|
||||||
|
"10",
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"class_type": "ImageScale",
|
||||||
|
"_meta": {
|
||||||
|
"title": "Upscale Image"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -135,6 +135,10 @@
|
|||||||
"filename": "Default_Comfy_Workflow.json",
|
"filename": "Default_Comfy_Workflow.json",
|
||||||
"type": "workflow"
|
"type": "workflow"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"filename": "Char_Avatar_Comfy_Workflow.json",
|
||||||
|
"type": "workflow"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"filename": "presets/kobold/Ace of Spades.json",
|
"filename": "presets/kobold/Ace of Spades.json",
|
||||||
"type": "kobold_preset"
|
"type": "kobold_preset"
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
<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>
|
||||||
<li data-placeholder="scale" class="sd_comfy_workflow_editor_not_found">"%scale%"</li>
|
<li data-placeholder="scale" class="sd_comfy_workflow_editor_not_found">"%scale%"</li>
|
||||||
|
<li data-placeholder="denoise" class="sd_comfy_workflow_editor_not_found">"%denoise%"</li>
|
||||||
<li data-placeholder="clip_skip" class="sd_comfy_workflow_editor_not_found">"%clip_skip%"</li>
|
<li data-placeholder="clip_skip" class="sd_comfy_workflow_editor_not_found">"%clip_skip%"</li>
|
||||||
<li data-placeholder="width" class="sd_comfy_workflow_editor_not_found">"%width%"</li>
|
<li data-placeholder="width" class="sd_comfy_workflow_editor_not_found">"%width%"</li>
|
||||||
<li data-placeholder="height" class="sd_comfy_workflow_editor_not_found">"%height%"</li>
|
<li data-placeholder="height" class="sd_comfy_workflow_editor_not_found">"%height%"</li>
|
||||||
|
@@ -3269,6 +3269,10 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
|
|||||||
|
|
||||||
const seed = extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
const seed = extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||||
workflow = workflow.replaceAll('"%seed%"', JSON.stringify(seed));
|
workflow = workflow.replaceAll('"%seed%"', JSON.stringify(seed));
|
||||||
|
|
||||||
|
const denoising_strength = extension_settings.sd.denoising_strength === undefined ? 1.0 : extension_settings.sd.denoising_strength;
|
||||||
|
workflow = workflow.replaceAll('"%denoise%"', JSON.stringify(denoising_strength));
|
||||||
|
|
||||||
placeholders.forEach(ph => {
|
placeholders.forEach(ph => {
|
||||||
workflow = workflow.replaceAll(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
|
workflow = workflow.replaceAll(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
|
||||||
});
|
});
|
||||||
@@ -3279,7 +3283,8 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
|
|||||||
const response = await fetch(getUserAvatarUrl());
|
const response = await fetch(getUserAvatarUrl());
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const avatarBlob = await response.blob();
|
const avatarBlob = await response.blob();
|
||||||
const avatarBase64 = await getBase64Async(avatarBlob);
|
const avatarBase64DataUrl = await getBase64Async(avatarBlob);
|
||||||
|
const avatarBase64 = avatarBase64DataUrl.split(',')[1];
|
||||||
workflow = workflow.replaceAll('"%user_avatar%"', JSON.stringify(avatarBase64));
|
workflow = workflow.replaceAll('"%user_avatar%"', JSON.stringify(avatarBase64));
|
||||||
} else {
|
} else {
|
||||||
workflow = workflow.replaceAll('"%user_avatar%"', JSON.stringify(PNG_PIXEL));
|
workflow = workflow.replaceAll('"%user_avatar%"', JSON.stringify(PNG_PIXEL));
|
||||||
@@ -3289,7 +3294,8 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
|
|||||||
const response = await fetch(getCharacterAvatarUrl());
|
const response = await fetch(getCharacterAvatarUrl());
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const avatarBlob = await response.blob();
|
const avatarBlob = await response.blob();
|
||||||
const avatarBase64 = await getBase64Async(avatarBlob);
|
const avatarBase64DataUrl = await getBase64Async(avatarBlob);
|
||||||
|
const avatarBase64 = avatarBase64DataUrl.split(',')[1];
|
||||||
workflow = workflow.replaceAll('"%char_avatar%"', JSON.stringify(avatarBase64));
|
workflow = workflow.replaceAll('"%char_avatar%"', JSON.stringify(avatarBase64));
|
||||||
} else {
|
} else {
|
||||||
workflow = workflow.replaceAll('"%char_avatar%"', JSON.stringify(PNG_PIXEL));
|
workflow = workflow.replaceAll('"%char_avatar%"', JSON.stringify(PNG_PIXEL));
|
||||||
|
@@ -319,7 +319,7 @@
|
|||||||
<input class="neo-range-input" type="number" id="sd_hr_scale_value" data-for="sd_hr_scale" min="{{hr_scale_min}}" max="{{hr_scale_max}}" step="{{hr_scale_step}}" value="{{hr_scale}}" >
|
<input class="neo-range-input" type="number" id="sd_hr_scale_value" data-for="sd_hr_scale" min="{{hr_scale_min}}" max="{{hr_scale_max}}" step="{{hr_scale_step}}" value="{{hr_scale}}" >
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad">
|
<div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy">
|
||||||
<small>
|
<small>
|
||||||
<span data-i18n="Denoising strength">Denoising strength</span>
|
<span data-i18n="Denoising strength">Denoising strength</span>
|
||||||
</small>
|
</small>
|
||||||
|
@@ -7,7 +7,7 @@ import sanitize from 'sanitize-filename';
|
|||||||
import { sync as writeFileAtomicSync } from 'write-file-atomic';
|
import { sync as writeFileAtomicSync } from 'write-file-atomic';
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
|
|
||||||
import { getBasicAuthHeader, delay } from '../util.js';
|
import { delay, getBasicAuthHeader, tryParse } from '../util.js';
|
||||||
import { jsonParser } from '../express-common.js';
|
import { jsonParser } from '../express-common.js';
|
||||||
import { readSecret, SECRET_KEYS } from './secrets.js';
|
import { readSecret, SECRET_KEYS } from './secrets.js';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ import { readSecret, SECRET_KEYS } from './secrets.js';
|
|||||||
function getComfyWorkflows(directories) {
|
function getComfyWorkflows(directories) {
|
||||||
return fs
|
return fs
|
||||||
.readdirSync(directories.comfyWorkflows)
|
.readdirSync(directories.comfyWorkflows)
|
||||||
.filter(file => file[0] != '.' && file.toLowerCase().endsWith('.json'))
|
.filter(file => file[0] !== '.' && file.toLowerCase().endsWith('.json'))
|
||||||
.sort(Intl.Collator().compare);
|
.sort(Intl.Collator().compare);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +67,7 @@ router.post('/upscalers', jsonParser, async (request, response) => {
|
|||||||
|
|
||||||
/** @type {any} */
|
/** @type {any} */
|
||||||
const data = await result.json();
|
const data = await result.json();
|
||||||
const names = data.map(x => x.name);
|
return data.map(x => x.name);
|
||||||
return names;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLatentUpscalers() {
|
async function getLatentUpscalers() {
|
||||||
@@ -88,8 +87,7 @@ router.post('/upscalers', jsonParser, async (request, response) => {
|
|||||||
|
|
||||||
/** @type {any} */
|
/** @type {any} */
|
||||||
const data = await result.json();
|
const data = await result.json();
|
||||||
const names = data.map(x => x.name);
|
return data.map(x => x.name);
|
||||||
return names;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [upscalers, latentUpscalers] = await Promise.all([getUpscalerModels(), getLatentUpscalers()]);
|
const [upscalers, latentUpscalers] = await Promise.all([getUpscalerModels(), getLatentUpscalers()]);
|
||||||
@@ -241,8 +239,7 @@ router.post('/set-model', jsonParser, async (request, response) => {
|
|||||||
'Authorization': getBasicAuthHeader(request.body.auth),
|
'Authorization': getBasicAuthHeader(request.body.auth),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const data = await result.json();
|
return await result.json();
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(request.body.url);
|
const url = new URL(request.body.url);
|
||||||
@@ -274,7 +271,7 @@ router.post('/set-model', jsonParser, async (request, response) => {
|
|||||||
|
|
||||||
const progress = progressState['progress'];
|
const progress = progressState['progress'];
|
||||||
const jobCount = progressState['state']['job_count'];
|
const jobCount = progressState['state']['job_count'];
|
||||||
if (progress == 0.0 && jobCount === 0) {
|
if (progress === 0.0 && jobCount === 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,8 +409,19 @@ comfy.post('/models', jsonParser, async (request, response) => {
|
|||||||
}
|
}
|
||||||
/** @type {any} */
|
/** @type {any} */
|
||||||
const data = await result.json();
|
const data = await result.json();
|
||||||
return response.send(data.CheckpointLoaderSimple.input.required.ckpt_name[0].map(it => ({ value: it, text: it })));
|
|
||||||
} catch (error) {
|
const ckpts = data.CheckpointLoaderSimple.input.required.ckpt_name[0].map(it => ({ value: it, text: it })) || [];
|
||||||
|
const unets = data.UNETLoader.input.required.unet_name[0].map(it => ({ value: it, text: `UNet: ${it}` })) || [];
|
||||||
|
|
||||||
|
// load list of GGUF unets from diffusion_models if the loader node is available
|
||||||
|
const ggufs = data.UnetLoaderGGUF?.input.required.unet_name[0].map(it => ({ value: it, text: `GGUF: ${it}` })) || [];
|
||||||
|
const models = [...ckpts, ...unets, ...ggufs];
|
||||||
|
|
||||||
|
// make the display names of the models somewhat presentable
|
||||||
|
models.forEach(it => it.text = it.text.replace(/\.[^.]*$/, '').replace(/_/g, ' '));
|
||||||
|
|
||||||
|
return response.send(models);
|
||||||
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return response.sendStatus(500);
|
return response.sendStatus(500);
|
||||||
}
|
}
|
||||||
@@ -527,7 +535,8 @@ comfy.post('/generate', jsonParser, async (request, response) => {
|
|||||||
body: request.body.prompt,
|
body: request.body.prompt,
|
||||||
});
|
});
|
||||||
if (!promptResult.ok) {
|
if (!promptResult.ok) {
|
||||||
throw new Error('ComfyUI returned an error.');
|
const text = await promptResult.text();
|
||||||
|
throw new Error('ComfyUI returned an error.', { cause: tryParse(text) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {any} */
|
/** @type {any} */
|
||||||
@@ -550,7 +559,13 @@ comfy.post('/generate', jsonParser, async (request, response) => {
|
|||||||
await delay(100);
|
await delay(100);
|
||||||
}
|
}
|
||||||
if (item.status.status_str === 'error') {
|
if (item.status.status_str === 'error') {
|
||||||
throw new Error('ComfyUI generation did not succeed.');
|
// Report node tracebacks if available
|
||||||
|
const errorMessages = item.status?.messages
|
||||||
|
?.filter(it => it[0] === 'execution_error')
|
||||||
|
.map(it => it[1])
|
||||||
|
.map(it => `${it.node_type} [${it.node_id}] ${it.exception_type}: ${it.exception_message}`)
|
||||||
|
.join('\n') || '';
|
||||||
|
throw new Error(`ComfyUI generation did not succeed.\n\n${errorMessages}`.trim());
|
||||||
}
|
}
|
||||||
const imgInfo = Object.keys(item.outputs).map(it => item.outputs[it].images).flat()[0];
|
const imgInfo = Object.keys(item.outputs).map(it => item.outputs[it].images).flat()[0];
|
||||||
const imgUrl = new URL(request.body.url);
|
const imgUrl = new URL(request.body.url);
|
||||||
@@ -560,11 +575,12 @@ comfy.post('/generate', jsonParser, async (request, response) => {
|
|||||||
if (!imgResponse.ok) {
|
if (!imgResponse.ok) {
|
||||||
throw new Error('ComfyUI returned an error.');
|
throw new Error('ComfyUI returned an error.');
|
||||||
}
|
}
|
||||||
const imgBuffer = await imgResponse.buffer();
|
const imgBuffer = await imgResponse.arrayBuffer();
|
||||||
return response.send(imgBuffer.toString('base64'));
|
return response.send(Buffer.from(imgBuffer).toString('base64'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log('ComfyUI error:', error);
|
||||||
return response.sendStatus(500);
|
response.status(500).send(error.message);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user