From 21fe2412f9913883eff44799afac4945e2517561 Mon Sep 17 00:00:00 2001 From: Towsty Date: Fri, 28 Aug 2026 21:22:37 -0500 Subject: [PATCH] Add Image v2 as a sibling of Image edit, with separate Klein graphs. Keep the v1 Image tab on its dual-branch template. The host agent follows Desktop's live port so Coolify stays on :8198. Co-authored-by: Cursor --- .env.example | 4 +- docs/image-v2.md | 78 ++++++ pages/index.vue | 377 +++++++++++++++++++++++++--- pages/queue.vue | 6 +- scripts/comfy-host-agent.mjs | 90 +++++-- scripts/start-comfy-host-agent.cmd | 3 + server/api/v2/generate.post.ts | 255 +++++++++++++++++++ server/assets/klein_v2_compose.json | 210 ++++++++++++++++ server/assets/klein_v2_edit.json | 171 +++++++++++++ server/routes/v2/generate.post.ts | 1 + server/utils/comfy.ts | 4 +- server/utils/generationPresets.ts | 41 ++- server/utils/imageChainV2.ts | 168 +++++++++++++ server/utils/imageComfy.ts | 2 +- server/utils/imageWorkflowV2.ts | 193 ++++++++++++++ server/utils/studioQueue.ts | 69 +++++ utils/generationPresets.ts | 9 +- utils/imageV2.ts | 64 +++++ 18 files changed, 1678 insertions(+), 67 deletions(-) create mode 100644 docs/image-v2.md create mode 100644 scripts/start-comfy-host-agent.cmd create mode 100644 server/api/v2/generate.post.ts create mode 100644 server/assets/klein_v2_compose.json create mode 100644 server/assets/klein_v2_edit.json create mode 100644 server/routes/v2/generate.post.ts create mode 100644 server/utils/imageChainV2.ts create mode 100644 server/utils/imageWorkflowV2.ts create mode 100644 utils/imageV2.ts diff --git a/.env.example b/.env.example index 2610b3b..78c6f72 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ PORT=3000 HOST=0.0.0.0 -# ComfyUI on the Windows desktop. 8188 is often taken (SSH / other Comfy). -# The host agent discovers the live instance (usually 8189) and proxies it on 8198. +# ComfyUI on the Windows desktop. Desktop hops ports (8188/8189/8190/...). +# The host agent (scripts/comfy-host-agent.mjs) finds the live instance and proxies it on 8198. COMFY_HOST=http://192.168.77.7:8198 COMFY_PORT=8198 # Optional: command used when this server runs on the Comfy host diff --git a/docs/image-v2.md b/docs/image-v2.md new file mode 100644 index 0000000..2ad41be --- /dev/null +++ b/docs/image-v2.md @@ -0,0 +1,78 @@ +# Image v1 vs Image v2 + +Image edit (v1) and Image v2 are siblings. v1 is unchanged. Do not mute-fix `workflow_flux2_klein_edit.json` (the dual-branch template that deletes `75:*` or `92:*` at runtime). + +| | Image edit (v1) | Image v2 | +|---|---|---| +| Tab | Image edit | Image v2 | +| Route | `POST /api/edit` | `POST /api/v2/generate` and `POST /v2/generate` | +| Graphs | One file, prune unused branch | `klein_v2_edit.json` or `klein_v2_compose.json` | +| Second still | Optional; switches branch | Compose only; required. Mismatch is a 400 | +| LoRAs | Optional user picker | SNOFS + Consistency, four strengths | +| Defaults | 20 steps, CFG 1 | 24 steps, CFG 4 (turbo: 8 / 1) | + +Poll v2 jobs the same way as v1: `GET /api/generate/{id}/stream` and `GET /api/generate/{id}`. + +## Modes + +- **edit** — one image + text. Task is `scene`. Sending `image_b` is rejected. +- **compose** — two images + text. `image_b` is required. No silent one-image fallback. + +Compose tasks: + +- **identity** — person from A; B reinforces the face; prompt changes pose/scene +- **outfit** — person, body, pose, background from A; clothing only from B +- **face_lock** — body/pose/scene from A; face from B +- **scene** — A is the edit image; B is style/background + +Dummy test: Compose + two unrelated photos + “keep everything the same” must change the output. If it matches old v1 gens, still B is not connected. + +## Nodes the mapper patches + +Both graphs: + +| Node | Role | +|------|------| +| `1` | Load Image A (`inputs.image`) | +| `2` / `23` | Scale to MP, lanczos (`megapixels`) | +| `7` | SNOFS LoRA (`lora_name`, `strength_model`, `strength_clip`) | +| `8` | Consistency LoRA (`lora_name`, `strength_model`, `strength_clip`) | +| `9` | Positive `CLIPTextEncode.text` (role header + user prompt) | +| `10` | Negative `CLIPTextEncode.text` | +| `15` | Seed | +| `17` | Steps (Flux2Scheduler; Klein-native, euler sampler) | +| `18` | CFG | +| `21` | SaveImage prefix | + +Compose only: + +| Node | Role | +|------|------| +| `22` | Load Image B | +| `23` | Scale B | +| `24` | VAE encode B | +| `25` / `26` | Second ReferenceLatent (B fused into pos/neg) | + +If `image_b` is sent and the executed graph has fewer than two `LoadImage` nodes, the job fails. + +## Default sliders + +| Slider | Default | +|--------|---------| +| SNOFS Model | 0.65 | +| SNOFS CLIP | 0.35 | +| Consistency Model | 0.70 | +| Consistency CLIP | 0.70 | +| Steps | 24 (turbo 8) | +| CFG | 4 (turbo 1) | +| Scale to MP | 1.0 lanczos | + +There is no Denoise slider. This is reference-latent Klein, not inpaint. + +### NSFW identity vs outfit swap + +- **identity / face_lock** — keep Consistency at 0.70 / 0.70 so the face from A (identity) or B (face_lock) holds. Do not drop SNOFS CLIP below ~0.30 or the skin/body read falls apart. +- **outfit** — keep SNOFS Model ~0.65 so cloth reads; do not raise it past ~0.85 or it starts rewriting the body from A. Consistency stays at 0.70 so the person in A does not become the person in B. +- **scene / edit** — start at the table defaults. Turbo is for drafts only. + +Presets on this tab store sliders + mode + task only. They do not load v1 LoRA stacks or a cached latent. diff --git a/pages/index.vue b/pages/index.vue index 556639f..01b8b85 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -54,9 +54,21 @@ > Image edit +

Input

-

{{ studioMode === 'edit' +

{{ studioMode === 'editv2' + ? (v2Mode === 'compose' + ? 'Compose: still A is the person/body. Still B is the face or outfit. Two stills required — no silent one-image fallback.' + : 'Edit: one still plus text. Compose is a separate graph if you need a second still.') + : studioMode === 'edit' ? 'Drop the still to edit. An optional second still applies image 2 onto image 1. Files leave the desktop after save.' : 'Drop a still, describe the motion, then send the job. Generate stays available — a long shot list takes one queue slot, and you can line up more while it runs.' }}

@@ -80,14 +92,45 @@
-

{{ studioMode === 'edit' ? 'Image 1' : (textToVideo ? 'Start still · optional' : 'Choose an image') }}

+

{{ studioMode === 'edit' || studioMode === 'editv2' ? (studioMode === 'editv2' ? 'Still A' : 'Image 1') : (textToVideo ? 'Start still · optional' : 'Choose an image') }}

{{ textToVideo ? 'Text-to-video does not need a still. Shot 2+ will still extend from the last frame.' : 'Browse the library or upload a new PNG, JPG, or WEBP' }}

-
+
+ + + +
+ +
-

Image 2 · optional

+

{{ studioMode === 'editv2' ? 'Still B · required' : 'Image 2 · optional' }}

- {{ editRefFile - ? 'Two-image branch on. Image 2 is applied onto image 1.' - : 'One-image branch on. Add a second still to transfer details from image 2.' }} + {{ studioMode === 'editv2' + ? (editRefFile ? stillBHint : 'Compose will reject this job until still B is loaded.') + : (editRefFile + ? 'Two-image branch on. Image 2 is applied onto image 1.' + : 'One-image branch on. Add a second still to transfer details from image 2.') }}

- +