Add workflow schema with zod (#22)

* Safe parse with zod

* Fix zod issue

* Fix all validation errors

* nit

* Add tests

* Add color fields

* Passthrough
This commit is contained in:
Chenlei Hu
2024-06-17 17:28:52 -04:00
committed by GitHub
parent cc7ee23b91
commit b11a12d925
9 changed files with 1383 additions and 14 deletions

23
package-lock.json generated
View File

@@ -7,6 +7,10 @@
"": {
"name": "comfyui-frontend",
"version": "0.0.0",
"dependencies": {
"zod": "^3.23.8",
"zod-validation-error": "^3.3.0"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/preset-env": "^7.22.20",
@@ -8694,6 +8698,25 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zod-validation-error": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.3.0.tgz",
"integrity": "sha512-Syib9oumw1NTqEv4LT0e6U83Td9aVRk9iTXPUQr1otyV1PuXQKOvOwhMNqZIq5hluzHP2pMgnOmHEo7kPdI2mw==",
"engines": {
"node": ">=18.0.0"
},
"peerDependencies": {
"zod": "^3.18.0"
}
}
}
}

View File

@@ -7,7 +7,7 @@
"dev": "vite",
"build": "npm run typecheck && vite build",
"typecheck": "tsc --noEmit",
"test": "jest",
"test": "npm run build && jest",
"test:generate": "npx tsx tests-ui/setup",
"preview": "vite preview"
},
@@ -24,5 +24,9 @@
"typescript": "^5.4.5",
"vite": "^5.2.0",
"vite-plugin-static-copy": "^1.0.5"
},
"dependencies": {
"zod": "^3.23.8",
"zod-validation-error": "^3.3.0"
}
}

View File

@@ -10,6 +10,7 @@ import { DraggableList } from "./ui/draggableList";
import { applyTextReplacements, addStylesheet } from "./utils";
import type { ComfyExtension } from "/types/comfy";
import type { LGraph, LGraphCanvas, LGraphNode } from "/types/litegraph";
import { type ComfyWorkflow, parseComfyWorkflow } from "../types/comfyWorkflow";
export const ANIM_PREVIEW_WIDGET = "$$comfy_animation_preview"
@@ -973,16 +974,18 @@ export class ComfyApp {
// No image found. Look for node data
data = data.getData("text/plain");
let workflow;
let workflow: ComfyWorkflow;
try {
data = data.slice(data.indexOf("{"));
workflow = JSON.parse(data);
workflow = await parseComfyWorkflow(data);
} catch (err) {
try {
data = data.slice(data.indexOf("workflow\n"));
data = data.slice(data.indexOf("{"));
workflow = JSON.parse(data);
} catch (error) {}
workflow = await parseComfyWorkflow(data);
} catch (error) {
console.error(error);
}
}
if (workflow && workflow.version && workflow.nodes && workflow.extra) {
@@ -1652,8 +1655,7 @@ export class ComfyApp {
try {
const loadWorkflow = async (json) => {
if (json) {
const workflow = JSON.parse(json);
await this.loadGraphData(workflow);
await this.loadGraphData(await parseComfyWorkflow(json));
return true;
}
};
@@ -1895,7 +1897,7 @@ export class ComfyApp {
* @param {*} graphData A serialized graph object
* @param { boolean } clean If the graph state, e.g. images, should be cleared
*/
async loadGraphData(graphData?, clean: boolean = true, restore_view: boolean = true) {
async loadGraphData(graphData?: ComfyWorkflow, clean: boolean = true, restore_view: boolean = true) {
if (clean !== false) {
this.clean();
}
@@ -1932,6 +1934,9 @@ export class ComfyApp {
try {
this.graph.configure(graphData);
if (restore_view && this.enableWorkflowViewRestore.value && graphData.extra?.ds) {
// @ts-ignore
// Need to set strict: true for zod to match the type [number, number]
// https://github.com/colinhacks/zod/issues/3056
this.canvas.ds.offset = graphData.extra.ds.offset;
this.canvas.ds.scale = graphData.extra.ds.scale;
}
@@ -2273,7 +2278,7 @@ export class ComfyApp {
if (file.type === "image/png") {
const pngInfo = await getPngMetadata(file);
if (pngInfo?.workflow) {
await this.loadGraphData(JSON.parse(pngInfo.workflow));
await this.loadGraphData(await parseComfyWorkflow(pngInfo.workflow));
} else if (pngInfo?.prompt) {
this.loadApiJson(JSON.parse(pngInfo.prompt));
} else if (pngInfo?.parameters) {
@@ -2288,7 +2293,7 @@ export class ComfyApp {
const prompt = pngInfo?.prompt || pngInfo?.Prompt;
if (workflow) {
this.loadGraphData(JSON.parse(workflow));
this.loadGraphData(await parseComfyWorkflow(workflow));
} else if (prompt) {
this.loadApiJson(JSON.parse(prompt));
} else {
@@ -2297,13 +2302,14 @@ export class ComfyApp {
} else if (file.type === "application/json" || file.name?.endsWith(".json")) {
const reader = new FileReader();
reader.onload = async () => {
const jsonContent = JSON.parse(reader.result as string);
const readerResult = reader.result as string;
const jsonContent = JSON.parse(readerResult);
if (jsonContent?.templates) {
this.loadTemplateData(jsonContent);
} else if(this.isApiJson(jsonContent)) {
this.loadApiJson(jsonContent);
} else {
await this.loadGraphData(jsonContent);
await this.loadGraphData(await parseComfyWorkflow(readerResult));
}
};
reader.readAsText(file);
@@ -2313,7 +2319,7 @@ export class ComfyApp {
// @ts-ignore
if (info.workflow) {
// @ts-ignore
await this.loadGraphData(JSON.parse(info.workflow));
await this.loadGraphData(await parseComfyWorkflow(info.workflow));
// @ts-ignore
} else if (info.prompt) {
// @ts-ignore

View File

@@ -1,4 +1,6 @@
export const defaultGraph = {
import type { ComfyWorkflow } from "/types/comfyWorkflow";
export const defaultGraph: ComfyWorkflow = {
last_node_id: 9,
last_link_id: 9,
nodes: [

118
src/types/comfyWorkflow.ts Normal file
View File

@@ -0,0 +1,118 @@
import { z } from 'zod';
import { fromZodError } from 'zod-validation-error';
const zComfyLink = z.tuple([
z.number(), // Link id
z.number(), // Node id of source node
z.number(), // Output slot# of source node
z.number(), // Node id of destination node
z.number(), // Input slot# of destination node
z.string(), // Data type
]);
const zNodeOutput = z.object({
name: z.string(),
type: z.string(),
links: z.array(z.number()).nullable(),
slot_index: z.number().optional(),
}).passthrough();
const zNodeInput = z.object({
name: z.string(),
type: z.string(),
link: z.number().nullable(),
slot_index: z.number().optional(),
}).passthrough();
const zFlags = z.object({
collapsed: z.boolean().optional(),
pinned: z.boolean().optional(),
allow_interaction: z.boolean().optional(),
horizontal: z.boolean().optional(),
skip_repeated_outputs: z.boolean().optional(),
}).passthrough();
const zProperties = z.object({
["Node name for S&R"]: z.string().optional(),
}).passthrough();
const zVector2 = z.union([
z.object({ 0: z.number(), 1: z.number() }),
z.tuple([z.number(), z.number()]),
]);
const zComfyNode = z.object({
id: z.number(),
type: z.string(),
pos: z.tuple([z.number(), z.number()]),
size: zVector2,
flags: zFlags,
order: z.number(),
mode: z.number(),
inputs: z.array(zNodeInput).optional(),
outputs: z.array(zNodeOutput).optional(),
properties: zProperties,
widgets_values: z.array(z.any()).optional(), // This could contain mixed types
color: z.string().optional(),
bgcolor: z.string().optional(),
}).passthrough();
const zGroup = z.object({
title: z.string(),
bounding: z.tuple([z.number(), z.number(), z.number(), z.number()]),
color: z.string(),
font_size: z.number(),
locked: z.boolean(),
}).passthrough();
const zInfo = z.object({
name: z.string(),
author: z.string(),
description: z.string(),
version: z.string(),
created: z.string(),
modified: z.string(),
software: z.string(),
}).passthrough();
const zDS = z.object({
scale: z.number(),
offset: zVector2,
}).passthrough();
const zConfig = z.object({
links_ontop: z.boolean().optional(),
align_to_grid: z.boolean().optional(),
}).passthrough();
const zExtra = z.object({
ds: zDS.optional(),
info: zInfo.optional(),
}).passthrough();
const zComfyWorkflow = z.object({
last_node_id: z.number(),
last_link_id: z.number(),
nodes: z.array(zComfyNode),
links: z.array(zComfyLink),
groups: z.array(zGroup).optional(),
config: zConfig.optional().nullable(),
extra: zExtra.optional().nullable(),
version: z.number(),
}).passthrough();
export type NodeInput = z.infer<typeof zNodeInput>;
export type NodeOutput = z.infer<typeof zNodeOutput>;
export type ComfyLink = z.infer<typeof zComfyLink>;
export type ComfyNode = z.infer<typeof zComfyNode>;
export type ComfyWorkflow = z.infer<typeof zComfyWorkflow>;
export async function parseComfyWorkflow(data: string): Promise<ComfyWorkflow> {
// Validate
const result = await zComfyWorkflow.safeParseAsync(JSON.parse(data));
if (!result.success) {
throw fromZodError(result.error);
}
return result.data;
}

View File

@@ -0,0 +1,55 @@
import { parseComfyWorkflow } from "../../src/types/comfyWorkflow";
import { defaultGraph } from "../../src/scripts/defaultGraph";
import fs from "fs";
const WORKFLOW_DIR = "tests-ui/workflows";
describe("parseComfyWorkflow", () => {
it("parses valid workflow", async () => {
fs.readdirSync(WORKFLOW_DIR).forEach(async (file) => {
if (file.endsWith(".json")) {
const data = fs.readFileSync(`${WORKFLOW_DIR}/${file}`, "utf-8");
await expect(parseComfyWorkflow(data)).resolves.not.toThrow();
}
});
});
it("workflow.nodes", async () => {
const workflow = JSON.parse(JSON.stringify(defaultGraph));
workflow.nodes = undefined;
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow();
workflow.nodes = null;
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow();
workflow.nodes = [];
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow();
});
it("workflow.version", async () => {
const workflow = JSON.parse(JSON.stringify(defaultGraph));
workflow.version = undefined;
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow();
workflow.version = "1.0.1"; // Invalid format.
await expect(parseComfyWorkflow(JSON.stringify(workflow))).rejects.toThrow();
workflow.version = 1;
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow();
});
it("workflow.extra", async () => {
const workflow = JSON.parse(JSON.stringify(defaultGraph));
workflow.extra = undefined;
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow();
workflow.extra = null;
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow();
workflow.extra = {};
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow();
workflow.extra = { foo: "bar" }; // Should accept extra fields.
await expect(parseComfyWorkflow(JSON.stringify(workflow))).resolves.not.toThrow();
});
});

View File

@@ -0,0 +1,385 @@
{
"last_node_id": 9,
"last_link_id": 9,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
415,
186
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
473,
609
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
186
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
156680208700286,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1209,
188
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1451,
189
],
"size": {
"0": 210,
"1": 58
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
26,
474
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
1
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
3,
5
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
8
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"3Guofeng3_v32Light.safetensors"
]
}
],
"links": [
[
1,
4,
0,
3,
0,
"MODEL"
],
[
2,
5,
0,
3,
3,
"LATENT"
],
[
3,
4,
1,
6,
0,
"CLIP"
],
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
5,
4,
1,
7,
0,
"CLIP"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
8,
4,
2,
8,
1,
"VAE"
],
[
9,
8,
0,
9,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.8264462809917354,
"offset": [
565.6800000000005,
-43.919999999999995
]
},
"info": {
"name": "workflow",
"author": "",
"description": "",
"version": "1",
"created": "2024-06-02T20:17:02.243Z",
"modified": "2024-06-02T20:17:11.438Z",
"software": "ComfyUI"
}
},
"version": 0.4
}

View File

@@ -0,0 +1,378 @@
{
"last_node_id": 9,
"last_link_id": 9,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
406.3210576748693,
165.8590597338342
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
473,
609
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1451,
189
],
"size": {
"0": 210,
"1": 58
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
26,
474
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
1
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
3,
5
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
8
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"3Guofeng3_v32Light.safetensors"
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
186
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
156680208700286,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1209,
188
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
}
],
"links": [
[
1,
4,
0,
3,
0,
"MODEL"
],
[
2,
5,
0,
3,
3,
"LATENT"
],
[
3,
4,
1,
6,
0,
"CLIP"
],
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
5,
4,
1,
7,
0,
"CLIP"
],
[
8,
4,
2,
8,
1,
"VAE"
],
[
9,
8,
0,
9,
0,
"IMAGE"
]
],
"groups": [
{
"title": "Group",
"bounding": [
489,
95,
140,
80
],
"color": "#3f789e",
"font_size": 24,
"locked": false
}
],
"config": {},
"extra": {
"ds": {
"scale": 1.3513057093104008,
"offset": [
127.07026983402625,
138.77779138162384
]
},
"info": {
"name": "workflow",
"author": "",
"description": "",
"version": "1",
"created": "2024-06-11T23:37:08.326Z",
"modified": "2024-06-12T13:25:28.857Z",
"software": "ComfyUI"
}
},
"version": 0.4
}

View File

@@ -0,0 +1,398 @@
{
"last_node_id": 9,
"last_link_id": 9,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
413,
389
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
406.3210576748693,
165.8590597338342
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
473,
609
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
863,
186
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 4
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
156680208700286,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1209,
188
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 9,
"type": "SaveImage",
"pos": [
1451,
189
],
"size": {
"0": 210,
"1": 58
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {
"Node name for S&R": "SaveImage"
},
"widgets_values": [
"ComfyUI"
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
26,
474
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
1
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
3,
5
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
8
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"3Guofeng3_v32Light.safetensors"
]
}
],
"links": [
[
1,
4,
0,
3,
0,
"MODEL"
],
[
2,
5,
0,
3,
3,
"LATENT"
],
[
3,
4,
1,
6,
0,
"CLIP"
],
[
4,
6,
0,
3,
1,
"CONDITIONING"
],
[
5,
4,
1,
7,
0,
"CLIP"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
8,
4,
2,
8,
1,
"VAE"
],
[
9,
8,
0,
9,
0,
"IMAGE"
]
],
"groups": [
{
"title": "Group",
"bounding": [
489,
95,
140,
80
],
"color": "#3f789e",
"font_size": 24,
"locked": false
}
],
"config": {},
"extra": {
"ds": {
"scale": 1.11678157794248,
"offset": [
323.87991710157155,
235.75066665118254
]
},
"info": {
"name": "workflow",
"author": "",
"description": "",
"version": "1",
"created": "2024-06-11T23:37:08.326Z",
"modified": "2024-06-11T23:37:08.327Z",
"software": "ComfyUI"
}
},
"version": 0.4
}