mAi: #10 - multi-model backend expansion (workflow templates + compare harness)

Path 1 architecture: one comfyui adapter, workflows as data.

- workflow_template.go: embed.FS + token substitution with type-preserving
  whole-value placeholders. ${prompt} → string, ${seed} → int64,
  ${cfg} → float64 — no JSON round-tripping. Partial matches ignored.
- comfyui.go: refactored to load workflow from embedded FS or filesystem
  path. Back-compat preserved: workflow: defaults to flux1-schnell.
- workflows/{flux1-schnell,flux2-klein,sd35-medium}.json — bundled
  templates. flux1-schnell migrated from hardcoded with identical node IDs.
- compare.go: new `imagen compare` subcommand. Sequential N-backend run
  (one GPU on mRock — parallel would OOM), per-backend PNG, sidecar JSON
  with per-model metadata + errors, composite contact sheet via Go image
  package (no ImageMagick dep).
- Sample config gains flux2-klein-local + sd35-medium-local instances.
- docs/backends.md: architecture rationale + per-model HF download paths
  + how to add a new bundled workflow + compare-harness reference.

Live smoke verified: compare mock + flux-schnell-local at 768×768 →
both PNGs written, sidecar JSON has workflow="flux1-schnell" + full
metadata, contact sheet renders. Worker contract (Request → Generate)
unchanged, so flexsiebels /imagine UI API surface preserved.

Tests: 11 existing comfyui + 6 new workflow_template + 5 new compare
tests, all green.

Adding a new model is now yaml + JSON, never Go.
This commit is contained in:
mAi
2026-05-11 17:29:57 +02:00
parent 623dd290c5
commit 8435817ce1
15 changed files with 1638 additions and 122 deletions

View File

@@ -0,0 +1,84 @@
{
"6": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "${prompt}",
"clip": ["11", 0]
}
},
"8": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["31", 0],
"vae": ["10", 0]
}
},
"9": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "imagen",
"images": ["8", 0]
}
},
"10": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "${vae}"
}
},
"11": {
"class_type": "DualCLIPLoader",
"inputs": {
"clip_name1": "${clip_t5}",
"clip_name2": "${clip_l}",
"type": "flux"
}
},
"12": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "${model}",
"weight_dtype": "${dtype}"
}
},
"13": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "${negative}",
"clip": ["11", 0]
}
},
"27": {
"class_type": "EmptySD3LatentImage",
"inputs": {
"width": "${width}",
"height": "${height}",
"batch_size": 1
}
},
"30": {
"class_type": "ModelSamplingFlux",
"inputs": {
"model": ["12", 0],
"max_shift": 1.15,
"base_shift": 0.5,
"width": "${width}",
"height": "${height}"
}
},
"31": {
"class_type": "KSampler",
"inputs": {
"model": ["30", 0],
"seed": "${seed}",
"steps": "${steps}",
"cfg": "${cfg}",
"sampler_name": "${sampler}",
"scheduler": "${scheduler}",
"denoise": 1.0,
"positive": ["6", 0],
"negative": ["13", 0],
"latent_image": ["27", 0]
}
}
}

View File

@@ -0,0 +1,79 @@
{
"6": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "${prompt}",
"clip": ["11", 0]
}
},
"8": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["31", 0],
"vae": ["10", 0]
}
},
"9": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "imagen",
"images": ["8", 0]
}
},
"10": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "${vae}"
}
},
"11": {
"class_type": "CLIPLoader",
"inputs": {
"clip_name": "${clip}",
"type": "flux2"
}
},
"12": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "${model}",
"weight_dtype": "${dtype}"
}
},
"14": {
"class_type": "FluxGuidance",
"inputs": {
"conditioning": ["6", 0],
"guidance": "${guidance}"
}
},
"15": {
"class_type": "ConditioningZeroOut",
"inputs": {
"conditioning": ["6", 0]
}
},
"27": {
"class_type": "EmptyFlux2LatentImage",
"inputs": {
"width": "${width}",
"height": "${height}",
"batch_size": 1
}
},
"31": {
"class_type": "KSampler",
"inputs": {
"model": ["12", 0],
"seed": "${seed}",
"steps": "${steps}",
"cfg": "${cfg}",
"sampler_name": "${sampler}",
"scheduler": "${scheduler}",
"denoise": 1.0,
"positive": ["14", 0],
"negative": ["15", 0],
"latent_image": ["27", 0]
}
}
}

View File

@@ -0,0 +1,66 @@
{
"4": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "${model}"
}
},
"6": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "${prompt}",
"clip": ["4", 1]
}
},
"7": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "${negative}",
"clip": ["4", 1]
}
},
"8": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["31", 0],
"vae": ["4", 2]
}
},
"9": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "imagen",
"images": ["8", 0]
}
},
"13": {
"class_type": "ModelSamplingSD3",
"inputs": {
"model": ["4", 0],
"shift": "${shift}"
}
},
"27": {
"class_type": "EmptySD3LatentImage",
"inputs": {
"width": "${width}",
"height": "${height}",
"batch_size": 1
}
},
"31": {
"class_type": "KSampler",
"inputs": {
"model": ["13", 0],
"seed": "${seed}",
"steps": "${steps}",
"cfg": "${cfg}",
"sampler_name": "${sampler}",
"scheduler": "${scheduler}",
"denoise": 1.0,
"positive": ["6", 0],
"negative": ["7", 0],
"latent_image": ["27", 0]
}
}
}