- 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
104 lines
4.4 KiB
JavaScript
104 lines
4.4 KiB
JavaScript
// 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);
|
||
};
|
||
}
|