Files
chrmc40 097697cc76 Krea 2 (RAW + Turbo) integration + VAE-Utils vendored pack + modal polish
- Krea 2 RAW + Turbo: model configs, prompts, and templates (t2i/i2i, highdetail,
  turbo-t2i-3d-regional) — 3D-poser depth control bridges into Krea 2 via SDXL i2i
- Bundled ComfyUI-VAE-Utils pack: tiled detail-VAE decode + 2x latent-upscale weights
- Upscale/inpaint engines: native qwen_image_vae for krea2 sectional passes, euler the
  krea2 default sampler, real per-engine Steps/CFG defaults, CN-less tile denoise caps,
  krea2 resolution-shift sync
- Model-settings + install-section wiring for the new models and bundled pack
2026-06-26 20:01:54 -07:00

104 lines
4.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Krea 2 RAW uses a resolution-dynamic flow-shift: the ModelSamplingFlux node's
// width/height decide mu, and they MUST match the actual render resolution or the
// shift is mis-applied — a 1024² shift on an 832×1024 render over-shifts mu ~10%
// and compresses the detail tail (the same failure mode the node exists to fix,
// milder). The model Settings resolution row mirrors this live for t2i, but that
// misses i2i (size comes from the loaded image — there is no resolution row) and
// any graph whose resolution was set on-canvas or loaded from disk. So re-derive
// every krea2_raw ModelSamplingFlux's width/height from its true render size just
// before each queue — the one chokepoint that catches all cases. Event-driven on
// the queue (not a timer). No-op for any graph without a krea2_raw shift node.
import { app } from "../../../scripts/app.js";
import { getLink } from "./slot-utils.js";
const SHIFT_TYPE = "ModelSamplingFlux";
const LATENT_DIM_TYPES = new Set(["EmptySD3LatentImage", "EmptyLatentImage", "EmptyFlux2LatentImage"]);
const IMAGE_TYPES = new Set(["LoadImage"]);
// Nearest ancestor of `startNode` (walking input links) whose class is in `types`.
function findUpstreamByType(startNode, types) {
const graph = startNode?.graph || app.graph;
if (!graph || !startNode) return null;
const visited = new Set();
const queue = [startNode];
while (queue.length) {
const n = queue.shift();
if (!n || visited.has(n.id)) continue;
visited.add(n.id);
for (const input of n.inputs || []) {
if (input.link == null) continue;
const link = getLink(graph, input.link);
if (!link) continue;
const src = graph.getNodeById(link.origin_id);
if (!src) continue;
if (types.has(src.comfyClass)) return src;
queue.push(src);
}
}
return null;
}
function widgetValue(node, name) {
return node?.widgets?.find((w) => w.name === name)?.value;
}
function setWidget(node, name, value) {
const w = node?.widgets?.find((x) => x.name === name);
if (!w || w.value === value) return;
w.value = value;
w.callback?.(value);
}
// The render resolution feeding a sampler: the latent node's width/height widgets
// (t2i), or — when the latent is encoded from an input image and has no such
// widgets (i2i) — the loaded image's natural pixel dimensions.
function samplerResolution(samplerNode) {
const latent = findUpstreamByType(samplerNode, LATENT_DIM_TYPES);
if (latent) {
const w = widgetValue(latent, "width");
const h = widgetValue(latent, "height");
if (typeof w === "number" && typeof h === "number") return { width: w, height: h };
}
const img = findUpstreamByType(samplerNode, IMAGE_TYPES)?.imgs?.[0];
if (img?.naturalWidth) return { width: img.naturalWidth, height: img.naturalHeight };
return null;
}
// Scope the sync to Krea 2 RAW: only its UNET wants a resolution-tracking shift.
// Turbo bakes a fixed mu (and carries no shift node); Flux's shift is its own
// concern. Gating on the upstream UNET filename keeps every other arch untouched.
function isKrea2RawShift(shiftNode) {
const name = widgetValue(findUpstreamByType(shiftNode, new Set(["UNETLoader"])), "unet_name");
return typeof name === "string" && /krea2_raw/i.test(name);
}
// Re-derive every krea2_raw ModelSamplingFlux's width/height from its render size.
export function syncShiftNodes() {
const graph = app?.graph;
if (!graph) return;
for (const sampler of graph._nodes || []) {
const cls = sampler.comfyClass || sampler.type || "";
if (!cls.includes("KSampler") || cls === "KSamplerSelect") continue;
const shift = findUpstreamByType(sampler, new Set([SHIFT_TYPE]));
if (!shift || !isKrea2RawShift(shift)) continue;
const res = samplerResolution(sampler);
if (!res) continue;
const before = `${widgetValue(shift, "width")}×${widgetValue(shift, "height")}`;
setWidget(shift, "width", res.width);
setWidget(shift, "height", res.height);
const after = `${res.width}×${res.height}`;
if (before !== after) console.log(`[PromptChain] krea2 RAW shift synced ${before} → ${after}`);
}
}
// Wrap queuePrompt so the shift is corrected for the live graph before it serializes.
export function setupShiftSync() {
const orig = app.queuePrompt?.bind(app);
if (!orig) return;
app.queuePrompt = async function (...args) {
try { syncShiftNodes(); } catch (e) { console.warn("[PromptChain] krea2 shift-sync skipped:", e); }
return orig(...args);
};
}