Merge pull request #3088 from ceruleandeep/feature/comfyGgufModels Feature/comfy gguf models

dde9f93d77e58f06d8907c8dd5006e8495a5303b

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
6 files changed, +183 -19Showing whitespace changes
default/content/Char_Avatar_Comfy_Workflow.json+137 -0
@@ -0,0 +1,137 @@
1+{
2+ "3": {
3+ "inputs": {
4+ "seed": "%seed%",
5+ "steps": "%steps%",
6+ "cfg": "%scale%",
7+ "sampler_name": "%sampler%",
8+ "scheduler": "%scheduler%",
9+ "denoise": "%denoise%",
10+ "model": [
11+ "4",
12+ 0
13+ ],
14+ "positive": [
15+ "6",
16+ 0
17+ ],
18+ "negative": [
19+ "7",
20+ 0
21+ ],
22+ "latent_image": [
23+ "12",
24+ 0
25+ ]
26+ },
27+ "class_type": "KSampler",
28+ "_meta": {
29+ "title": "KSampler"
30+ }
31+ },
32+ "4": {
33+ "inputs": {
34+ "ckpt_name": "%model%"
35+ },
36+ "class_type": "CheckpointLoaderSimple",
37+ "_meta": {
38+ "title": "Load Checkpoint"
39+ }
40+ },
41+ "6": {
42+ "inputs": {
43+ "text": "%prompt%",
44+ "clip": [
45+ "4",
46+ 1
47+ ]
48+ },
49+ "class_type": "CLIPTextEncode",
50+ "_meta": {
51+ "title": "CLIP Text Encode (Prompt)"
52+ }
53+ },
54+ "7": {
55+ "inputs": {
56+ "text": "%negative_prompt%",
57+ "clip": [
58+ "4",
59+ 1
60+ ]
61+ },
62+ "class_type": "CLIPTextEncode",
63+ "_meta": {
64+ "title": "CLIP Text Encode (Negative Prompt)"
65+ }
66+ },
67+ "8": {
68+ "inputs": {
69+ "samples": [
70+ "3",
71+ 0
72+ ],
73+ "vae": [
74+ "4",
75+ 2
76+ ]
77+ },
78+ "class_type": "VAEDecode",
79+ "_meta": {
80+ "title": "VAE Decode"
81+ }
82+ },
83+ "9": {
84+ "inputs": {
85+ "filename_prefix": "SillyTavern",
86+ "images": [
87+ "8",
88+ 0
89+ ]
90+ },
91+ "class_type": "SaveImage",
92+ "_meta": {
93+ "title": "Save Image"
94+ }
95+ },
96+ "10": {
97+ "inputs": {
98+ "image": "%char_avatar%"
99+ },
100+ "class_type": "ETN_LoadImageBase64",
101+ "_meta": {
102+ "title": "Load Image (Base64) [https://github.com/Acly/comfyui-tooling-nodes]"
103+ }
104+ },
105+ "12": {
106+ "inputs": {
107+ "pixels": [
108+ "13",
109+ 0
110+ ],
111+ "vae": [
112+ "4",
113+ 2
114+ ]
115+ },
116+ "class_type": "VAEEncode",
117+ "_meta": {
118+ "title": "VAE Encode"
119+ }
120+ },
121+ "13": {
122+ "inputs": {
123+ "upscale_method": "bicubic",
124+ "width": "%width%",
125+ "height": "%height%",
126+ "crop": "center",
127+ "image": [
128+ "10",
129+ 0
130+ ]
131+ },
132+ "class_type": "ImageScale",
133+ "_meta": {
134+ "title": "Upscale Image"
135+ }
136+ }
137+}
default/content/index.json+4 -0
@@ -136,6 +136,10 @@
136136 "type": "workflow"
137137 },
138138 {
139+ "filename": "Char_Avatar_Comfy_Workflow.json",
140+ "type": "workflow"
141+ },
142+ {
139143 "filename": "presets/kobold/Ace of Spades.json",
140144 "type": "kobold_preset"
141145 },
public/scripts/extensions/stable-diffusion/comfyWorkflowEditor.html+1 -0
@@ -17,6 +17,7 @@
1717 <li data-placeholder="scheduler" class="sd_comfy_workflow_editor_not_found">"%scheduler%"</li>
1818 <li data-placeholder="steps" class="sd_comfy_workflow_editor_not_found">"%steps%"</li>
1919 <li data-placeholder="scale" class="sd_comfy_workflow_editor_not_found">"%scale%"</li>
20+ <li data-placeholder="denoise" class="sd_comfy_workflow_editor_not_found">"%denoise%"</li>
2021 <li data-placeholder="clip_skip" class="sd_comfy_workflow_editor_not_found">"%clip_skip%"</li>
2122 <li data-placeholder="width" class="sd_comfy_workflow_editor_not_found">"%width%"</li>
2223 <li data-placeholder="height" class="sd_comfy_workflow_editor_not_found">"%height%"</li>
public/scripts/extensions/stable-diffusion/index.js+8 -2
@@ -3269,6 +3269,10 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
32693269
32703270 const seed = extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
32713271 workflow = workflow.replaceAll('"%seed%"', JSON.stringify(seed));
3272+
3273+ const denoising_strength = extension_settings.sd.denoising_strength === undefined ? 1.0 : extension_settings.sd.denoising_strength;
3274+ workflow = workflow.replaceAll('"%denoise%"', JSON.stringify(denoising_strength));
3275+
32723276 placeholders.forEach(ph => {
32733277 workflow = workflow.replaceAll(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
32743278 });
@@ -3279,7 +3283,8 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
32793283 const response = await fetch(getUserAvatarUrl());
32803284 if (response.ok) {
32813285 const avatarBlob = await response.blob();
32823286 const avatarBase64avatarBase64DataUrl = await getBase64Async(avatarBlob);
3287+ const avatarBase64 = avatarBase64DataUrl.split(',')[1];
32833288 workflow = workflow.replaceAll('"%user_avatar%"', JSON.stringify(avatarBase64));
32843289 } else {
32853290 workflow = workflow.replaceAll('"%user_avatar%"', JSON.stringify(PNG_PIXEL));
@@ -3289,7 +3294,8 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
32893294 const response = await fetch(getCharacterAvatarUrl());
32903295 if (response.ok) {
32913296 const avatarBlob = await response.blob();
32923297 const avatarBase64avatarBase64DataUrl = await getBase64Async(avatarBlob);
3298+ const avatarBase64 = avatarBase64DataUrl.split(',')[1];
32933299 workflow = workflow.replaceAll('"%char_avatar%"', JSON.stringify(avatarBase64));
32943300 } else {
32953301 workflow = workflow.replaceAll('"%char_avatar%"', JSON.stringify(PNG_PIXEL));
public/scripts/extensions/stable-diffusion/settings.html+1 -1
@@ -319,7 +319,7 @@
319319 <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}}" >
320320 </div>
321321
322322 <div class="alignitemscenter flex-container flexFlowColumn flexGrow flexShrink gap0 flexBasis48p" data-sd-source="auto,vlad,comfy">
323323 <small>
324324 <span data-i18n="Denoising strength">Denoising strength</span>
325325 </small>
src/endpoints/stable-diffusion.js+32 -16
@@ -7,7 +7,7 @@ import sanitize from 'sanitize-filename';
77import { sync as writeFileAtomicSync } from 'write-file-atomic';
88import FormData from 'form-data';
99
1010import { delay, getBasicAuthHeader, delaytryParse } from '../util.js';
1111import { jsonParser } from '../express-common.js';
1212import { readSecret, SECRET_KEYS } from './secrets.js';
1313
@@ -19,7 +19,7 @@ import { readSecret, SECRET_KEYS } from './secrets.js';
1919function getComfyWorkflows(directories) {
2020 return fs
2121 .readdirSync(directories.comfyWorkflows)
2222 .filter(file => file[0] !== '.' && file.toLowerCase().endsWith('.json'))
2323 .sort(Intl.Collator().compare);
2424}
2525
@@ -67,8 +67,7 @@ router.post('/upscalers', jsonParser, async (request, response) => {
6767
6868 /** @type {any} */
6969 const data = await result.json();
7070 const names =return data.map(x => x.name);
71- return names;
7271 }
7372
7473 async function getLatentUpscalers() {
@@ -88,8 +87,7 @@ router.post('/upscalers', jsonParser, async (request, response) => {
8887
8988 /** @type {any} */
9089 const data = await result.json();
9190 const names =return data.map(x => x.name);
92- return names;
9391 }
9492
9593 const [upscalers, latentUpscalers] = await Promise.all([getUpscalerModels(), getLatentUpscalers()]);
@@ -241,8 +239,7 @@ router.post('/set-model', jsonParser, async (request, response) => {
241239 'Authorization': getBasicAuthHeader(request.body.auth),
242240 },
243241 });
244242 const data =return await result.json();
245- return data;
246243 }
247244
248245 const url = new URL(request.body.url);
@@ -274,7 +271,7 @@ router.post('/set-model', jsonParser, async (request, response) => {
274271
275272 const progress = progressState['progress'];
276273 const jobCount = progressState['state']['job_count'];
277274 if (progress === 0.0 && jobCount === 0) {
278275 break;
279276 }
280277
@@ -412,7 +409,18 @@ comfy.post('/models', jsonParser, async (request, response) => {
412409 }
413410 /** @type {any} */
414411 const data = await result.json();
415- return response.send(data.CheckpointLoaderSimple.input.required.ckpt_name[0].map(it => ({ value: it, text: it })));
412+
413+ const ckpts = data.CheckpointLoaderSimple.input.required.ckpt_name[0].map(it => ({ value: it, text: it })) || [];
414+ const unets = data.UNETLoader.input.required.unet_name[0].map(it => ({ value: it, text: `UNet: ${it}` })) || [];
415+
416+ // load list of GGUF unets from diffusion_models if the loader node is available
417+ const ggufs = data.UnetLoaderGGUF?.input.required.unet_name[0].map(it => ({ value: it, text: `GGUF: ${it}` })) || [];
418+ const models = [...ckpts, ...unets, ...ggufs];
419+
420+ // make the display names of the models somewhat presentable
421+ models.forEach(it => it.text = it.text.replace(/\.[^.]*$/, '').replace(/_/g, ' '));
422+
423+ return response.send(models);
416424 } catch (error) {
417425 console.log(error);
418426 return response.sendStatus(500);
@@ -527,7 +535,8 @@ comfy.post('/generate', jsonParser, async (request, response) => {
527535 body: request.body.prompt,
528536 });
529537 if (!promptResult.ok) {
530- throw new Error('ComfyUI returned an error.');
538+ const text = await promptResult.text();
539+ throw new Error('ComfyUI returned an error.', { cause: tryParse(text) });
531540 }
532541
533542 /** @type {any} */
@@ -550,7 +559,13 @@ comfy.post('/generate', jsonParser, async (request, response) => {
550559 await delay(100);
551560 }
552561 if (item.status.status_str === 'error') {
553- throw new Error('ComfyUI generation did not succeed.');
562+ // Report node tracebacks if available
563+ const errorMessages = item.status?.messages
564+ ?.filter(it => it[0] === 'execution_error')
565+ .map(it => it[1])
566+ .map(it => `${it.node_type} [${it.node_id}] ${it.exception_type}: ${it.exception_message}`)
567+ .join('\n') || '';
568+ throw new Error(`ComfyUI generation did not succeed.\n\n${errorMessages}`.trim());
554569 }
555570 const imgInfo = Object.keys(item.outputs).map(it => item.outputs[it].images).flat()[0];
556571 const imgUrl = new URL(request.body.url);
@@ -560,11 +575,12 @@ comfy.post('/generate', jsonParser, async (request, response) => {
560575 if (!imgResponse.ok) {
561576 throw new Error('ComfyUI returned an error.');
562577 }
563578 const imgBuffer = await imgResponse.bufferarrayBuffer();
564579 return response.send(Buffer.from(imgBuffer).toString('base64'));
565580 } catch (error) {
566581 console.log('ComfyUI error:', error);
567582 return response.sendStatusstatus(500).send(error.message);
583+ return response;
568584 }
569585});
570586