- Widen the overlay and switch the combo view to a 33/67 left/right split. - Increase the preview/composite image size to 384px. - Make combo-axis card grids wrap instead of horizontal-scroll. - Add ADetailer/Upscale settings as a new 'Generation settings' section. - Store ADetailer A/B outputs per combo key in queue.js; add an A/B toggle below the image dimensions to switch between regular and ADetailer outputs in the overlay (#328).
This commit is contained in:
@@ -221,6 +221,8 @@ function setupBatchTracking(combinations, comboKeys) {
|
||||
if (idx < 0) return;
|
||||
const comboKey = (comboKeys && comboKeys[idx]) ?? "";
|
||||
const images = output.flake_images;
|
||||
const adImages = output.flake_images_ad;
|
||||
const isAB = Array.isArray(output.adetailer_ab) && output.adetailer_ab.length > 0;
|
||||
if (!Array.isArray(images) || images.length === 0) return;
|
||||
const candidates = nodeId
|
||||
? [app.graph.getNodeById(nodeId)].filter(n => n && n.type === "FlakeGenerate")
|
||||
@@ -229,6 +231,11 @@ function setupBatchTracking(combinations, comboKeys) {
|
||||
fg.properties = fg.properties || {};
|
||||
fg.properties._images_by_combo = fg.properties._images_by_combo || {};
|
||||
fg.properties._images_by_combo[comboKey] = images[0];
|
||||
if (isAB && Array.isArray(adImages) && adImages.length > 0) {
|
||||
fg.properties._images_by_combo_ad = fg.properties._images_by_combo_ad || {};
|
||||
fg.properties._images_by_combo_ad[comboKey] = adImages[0];
|
||||
fg.properties._adetailer_ab = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,18 @@ export function setupFlakeGenerateWidget(node) {
|
||||
if (link) startNode = graph.getNodeById(link.origin_id);
|
||||
}
|
||||
const model = startNode ? buildModel(startNode) : { axes: [], fixed: { presetName: null, stackFlakes: [] } };
|
||||
openGenerationDataOverlay(model, node.properties._images_by_combo || {});
|
||||
const adetailer = findWidget("adetailer")?.value || "Off";
|
||||
openGenerationDataOverlay(model, node.properties._images_by_combo || {}, {
|
||||
lastImagesByComboAd: node.properties._images_by_combo_ad || {},
|
||||
adetailerAB: !!node.properties._adetailer_ab,
|
||||
adetailer,
|
||||
adetailerDenoise: findWidget("adetailer_denoise")?.value,
|
||||
adetailerSteps: findWidget("adetailer_steps")?.value,
|
||||
adetailerBbox: findWidget("adetailer_bbox")?.value,
|
||||
upscale: !!findWidget("upscale")?.value,
|
||||
upscaleModel: findWidget("upscale_model")?.value,
|
||||
upscaleFactor: findWidget("upscale_factor")?.value,
|
||||
});
|
||||
});
|
||||
topRow.appendChild(genDataBtn);
|
||||
container.appendChild(topRow);
|
||||
@@ -198,6 +209,15 @@ export function setupFlakeGenerateWidget(node) {
|
||||
const img = output.flake_images[0];
|
||||
showImage(img);
|
||||
node.properties._last_image = img;
|
||||
// Store ADetailer A/B outputs when present (#328).
|
||||
const isAB = Array.isArray(output.adetailer_ab) && output.adetailer_ab.length > 0;
|
||||
if (isAB && Array.isArray(output.flake_images_ad) && output.flake_images_ad.length > 0) {
|
||||
node.properties._last_image_ad = output.flake_images_ad[0];
|
||||
node.properties._adetailer_ab = true;
|
||||
} else {
|
||||
node.properties._last_image_ad = null;
|
||||
node.properties._adetailer_ab = false;
|
||||
}
|
||||
// Per-combo storage is populated from queue.js via the `executed`
|
||||
// websocket event (which carries the correct prompt_id), so we no
|
||||
// longer write _images_by_combo here — doing so raced when the
|
||||
|
||||
+117
-19
@@ -355,11 +355,21 @@ export function combinationKeyFor(model, selIdx) {
|
||||
}
|
||||
|
||||
// ── Overlay UI ─────────────────────────────────────────────────────────────
|
||||
export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
export function openGenerationDataOverlay(model, lastImagesByCombo, opts = {}) {
|
||||
const {
|
||||
lastImagesByComboAd = {},
|
||||
adetailerAB = false,
|
||||
adetailer = "Off",
|
||||
adetailerDenoise,
|
||||
adetailerSteps,
|
||||
adetailerBbox,
|
||||
upscale = false,
|
||||
upscaleModel,
|
||||
upscaleFactor,
|
||||
} = opts;
|
||||
let { content, footer, close } = openOverlay();
|
||||
// Cap width at 70% of previous (#229 — reduce overlay width by 30%): the
|
||||
// previous cap was min(1400px, 95vw); new cap is min(980px, 66vw).
|
||||
css(content.parentElement, content.parentElement.style.cssText + "width:auto;max-width:min(980px,66vw);min-width:0;");
|
||||
// Wider overlay (#324): give the right-hand image + data panel more room.
|
||||
css(content.parentElement, content.parentElement.style.cssText + "width:auto;max-width:min(1200px,85vw);min-width:0;");
|
||||
|
||||
const header = document.createElement("div");
|
||||
css(header, "display:flex;align-items:center;gap:8px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid #333;");
|
||||
@@ -393,12 +403,12 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
css(split, "display:flex;gap:8px;align-items:flex-start;");
|
||||
content.appendChild(split);
|
||||
|
||||
// Left half: combo axis grids + combo-specific fields below
|
||||
// Left third: combo axis grids + combo-specific fields below
|
||||
const left = document.createElement("div");
|
||||
css(left, "flex:1;display:flex;flex-direction:column;gap:12px;min-width:0;max-height:60vh;overflow:auto;");
|
||||
// Right half: composite image + common data sections
|
||||
css(left, "flex:0 0 33%;display:flex;flex-direction:column;gap:12px;min-width:0;max-height:65vh;overflow:auto;");
|
||||
// Right two-thirds: composite image + common data sections
|
||||
const right = document.createElement("div");
|
||||
css(right, "flex:1;display:flex;flex-direction:column;gap:10px;min-width:0;max-height:60vh;overflow:auto;");
|
||||
css(right, "flex:1;display:flex;flex-direction:column;gap:10px;min-width:0;max-height:65vh;overflow:auto;");
|
||||
split.appendChild(left);
|
||||
split.appendChild(right);
|
||||
|
||||
@@ -420,7 +430,7 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
section.appendChild(lbl);
|
||||
|
||||
const scroll = document.createElement("div");
|
||||
css(scroll, "display:flex;gap:6px;overflow-x:auto;padding-bottom:4px;");
|
||||
css(scroll, "display:flex;flex-wrap:wrap;gap:6px;padding-bottom:4px;");
|
||||
const axisCards = [];
|
||||
axis.items.forEach((item, ii) => {
|
||||
const card = document.createElement("div");
|
||||
@@ -570,18 +580,21 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
const compositeImg = document.createElement("img");
|
||||
// Aspect ratio is set per-combination in refreshRight (#231); start at
|
||||
// a 1:1 placeholder while data loads.
|
||||
css(compositeImg, "width:256px;height:256px;object-fit:cover;border-radius:6px;border:1px solid #333;background:#1a1a1a;");
|
||||
css(compositeImg, "width:384px;height:384px;object-fit:cover;border-radius:6px;border:1px solid #333;background:#1a1a1a;");
|
||||
const compositeLabel = document.createElement("div");
|
||||
css(compositeLabel, "font-size:11px;color:#888;text-align:center;");
|
||||
const compositeDimensions = document.createElement("div");
|
||||
css(compositeDimensions, "font-size:10px;color:#666;text-align:center;");
|
||||
const adToggleWrap = document.createElement("div");
|
||||
css(adToggleWrap, "display:flex;align-items:center;gap:8px;font-size:12px;color:#ddd;");
|
||||
compositeWrap.appendChild(compositeImg);
|
||||
compositeWrap.appendChild(compositeLabel);
|
||||
compositeWrap.appendChild(compositeDimensions);
|
||||
compositeWrap.appendChild(adToggleWrap);
|
||||
right.appendChild(compositeWrap);
|
||||
function applyAspectRatio(w, h) {
|
||||
if (!w || !h) return;
|
||||
const MAX = 256;
|
||||
const MAX = 384;
|
||||
const ratio = w / h;
|
||||
let cw, ch;
|
||||
if (ratio >= 1) { cw = MAX; ch = Math.round(MAX / ratio); }
|
||||
@@ -632,7 +645,9 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
applyAspectRatio(data.width, data.height);
|
||||
|
||||
// Composite (or generated output if we have one for this combination)
|
||||
const generated = lastImagesByCombo && lastImagesByCombo[key];
|
||||
const generated = showAd
|
||||
? (lastImagesByComboAd && lastImagesByComboAd[key])
|
||||
: (lastImagesByCombo && lastImagesByCombo[key]);
|
||||
if (generated) {
|
||||
compositeImg.src = `/view?filename=${encodeURIComponent(generated.filename)}&type=${generated.type || "output"}&subfolder=${encodeURIComponent(generated.subfolder || "")}`;
|
||||
const sub = (generated.subfolder || "").replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
||||
@@ -650,8 +665,9 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
const s1 = section("Model", data.modelRows);
|
||||
const s2 = section("LoRAs", data.commonLoraRows);
|
||||
const s3 = section("Prompts", data.commonPromptRows);
|
||||
for (const s of [s1, s2, s3]) if (s) dataWrap.appendChild(s);
|
||||
if (!s1 && !s2 && !s3) {
|
||||
const s4 = section("Generation settings", extraInfoRows());
|
||||
for (const s of [s1, s2, s3, s4]) if (s) dataWrap.appendChild(s);
|
||||
if (!s1 && !s2 && !s3 && !s4) {
|
||||
const empty = document.createElement("div");
|
||||
css(empty, "font-size:12px;color:#555;text-align:center;padding:20px;");
|
||||
empty.textContent = "No data for this combination";
|
||||
@@ -667,6 +683,45 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
if (cs2) leftDataWrap.appendChild(cs2);
|
||||
}
|
||||
|
||||
// ADetailer A/B toggle (#328): only visible when this run produced both
|
||||
// regular and ADetailer outputs. It switches the previewed image.
|
||||
let showAd = false;
|
||||
let adToggle = null;
|
||||
if (adetailerAB) {
|
||||
adToggle = document.createElement("label");
|
||||
css(adToggle, "display:flex;align-items:center;gap:6px;cursor:pointer;");
|
||||
const chk = document.createElement("input");
|
||||
chk.type = "checkbox";
|
||||
chk.checked = false;
|
||||
css(chk, "cursor:pointer;margin:0;");
|
||||
const span = document.createElement("span");
|
||||
span.textContent = "Show ADetailer output";
|
||||
adToggle.appendChild(chk);
|
||||
adToggle.appendChild(span);
|
||||
chk.addEventListener("change", () => {
|
||||
showAd = chk.checked;
|
||||
refreshRight();
|
||||
});
|
||||
adToggleWrap.appendChild(adToggle);
|
||||
}
|
||||
|
||||
// Build extra info rows for ADetailer/Upscale settings (#324).
|
||||
function extraInfoRows() {
|
||||
const rows = [];
|
||||
if (adetailer !== "Off") {
|
||||
rows.push(["ADetailer", adetailer]);
|
||||
if (adetailerDenoise != null) rows.push(["ADetailer denoise", String(adetailerDenoise)]);
|
||||
if (adetailerSteps != null) rows.push(["ADetailer steps", String(adetailerSteps)]);
|
||||
if (adetailerBbox) rows.push(["ADetailer bbox", adetailerBbox]);
|
||||
}
|
||||
if (upscale) {
|
||||
rows.push(["Upscale", "On"]);
|
||||
if (upscaleModel) rows.push(["Upscale model", upscaleModel]);
|
||||
if (upscaleFactor != null) rows.push(["Upscale factor", String(upscaleFactor)]);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
refreshRight();
|
||||
} else {
|
||||
// No combo nodes: single centered panel
|
||||
@@ -677,18 +732,21 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
const compositeWrap = document.createElement("div");
|
||||
css(compositeWrap, "display:flex;flex-direction:column;align-items:center;gap:4px;");
|
||||
const compositeImg = document.createElement("img");
|
||||
css(compositeImg, "width:256px;height:256px;object-fit:cover;border-radius:6px;border:1px solid #333;background:#1a1a1a;");
|
||||
css(compositeImg, "width:384px;height:384px;object-fit:cover;border-radius:6px;border:1px solid #333;background:#1a1a1a;");
|
||||
const compositeLabel = document.createElement("div");
|
||||
css(compositeLabel, "font-size:11px;color:#888;text-align:center;");
|
||||
const compositeDimensions = document.createElement("div");
|
||||
css(compositeDimensions, "font-size:10px;color:#666;text-align:center;");
|
||||
const adToggleWrap = document.createElement("div");
|
||||
css(adToggleWrap, "display:flex;align-items:center;gap:8px;font-size:12px;color:#ddd;");
|
||||
compositeWrap.appendChild(compositeImg);
|
||||
compositeWrap.appendChild(compositeLabel);
|
||||
compositeWrap.appendChild(compositeDimensions);
|
||||
compositeWrap.appendChild(adToggleWrap);
|
||||
singleWrap.appendChild(compositeWrap);
|
||||
function applyAspectRatioSingle(w, h) {
|
||||
if (!w || !h) return;
|
||||
const MAX = 256;
|
||||
const MAX = 384;
|
||||
const ratio = w / h;
|
||||
let cw, ch;
|
||||
if (ratio >= 1) { cw = MAX; ch = Math.round(MAX / ratio); }
|
||||
@@ -727,6 +785,43 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
return wrap;
|
||||
}
|
||||
|
||||
// ADetailer A/B toggle (#328) for the single-panel path.
|
||||
let showAd = false;
|
||||
if (adetailerAB) {
|
||||
const adToggle = document.createElement("label");
|
||||
css(adToggle, "display:flex;align-items:center;gap:6px;cursor:pointer;");
|
||||
const chk = document.createElement("input");
|
||||
chk.type = "checkbox";
|
||||
chk.checked = false;
|
||||
css(chk, "cursor:pointer;margin:0;");
|
||||
const span = document.createElement("span");
|
||||
span.textContent = "Show ADetailer output";
|
||||
adToggle.appendChild(chk);
|
||||
adToggle.appendChild(span);
|
||||
chk.addEventListener("change", () => {
|
||||
showAd = chk.checked;
|
||||
refreshRight();
|
||||
});
|
||||
adToggleWrap.appendChild(adToggle);
|
||||
}
|
||||
|
||||
// Extra info rows for ADetailer/Upscale settings (#324).
|
||||
function extraInfoRows() {
|
||||
const rows = [];
|
||||
if (adetailer !== "Off") {
|
||||
rows.push(["ADetailer", adetailer]);
|
||||
if (adetailerDenoise != null) rows.push(["ADetailer denoise", String(adetailerDenoise)]);
|
||||
if (adetailerSteps != null) rows.push(["ADetailer steps", String(adetailerSteps)]);
|
||||
if (adetailerBbox) rows.push(["ADetailer bbox", adetailerBbox]);
|
||||
}
|
||||
if (upscale) {
|
||||
rows.push(["Upscale", "On"]);
|
||||
if (upscaleModel) rows.push(["Upscale model", upscaleModel]);
|
||||
if (upscaleFactor != null) rows.push(["Upscale factor", String(upscaleFactor)]);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
let refreshToken = 0;
|
||||
async function refreshRight() {
|
||||
const token = ++refreshToken;
|
||||
@@ -744,7 +839,9 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
? ""
|
||||
: combinationKeyFor(model, model.axes.map(() => 0));
|
||||
applyAspectRatioSingle(data.width, data.height);
|
||||
const generated = lastImagesByCombo && (lastImagesByCombo[key] ?? lastImagesByCombo[""]);
|
||||
const generated = showAd
|
||||
? (lastImagesByComboAd && (lastImagesByComboAd[key] ?? lastImagesByComboAd[""]))
|
||||
: (lastImagesByCombo && (lastImagesByCombo[key] ?? lastImagesByCombo[""]));
|
||||
if (generated) {
|
||||
compositeImg.src = `/view?filename=${encodeURIComponent(generated.filename)}&type=${generated.type || "output"}&subfolder=${encodeURIComponent(generated.subfolder || "")}`;
|
||||
const sub = (generated.subfolder || "").replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
||||
@@ -761,8 +858,9 @@ export function openGenerationDataOverlay(model, lastImagesByCombo) {
|
||||
const s1 = section("Model", data.modelRows);
|
||||
const s2 = section("LoRAs", [...data.commonLoraRows, ...data.comboLoraRows]);
|
||||
const s3 = section("Prompts", [...data.commonPromptRows, ...data.comboPromptRows]);
|
||||
for (const s of [s1, s2, s3]) if (s) dataWrap.appendChild(s);
|
||||
if (!s1 && !s2 && !s3) {
|
||||
const s4 = section("Generation settings", extraInfoRows());
|
||||
for (const s of [s1, s2, s3, s4]) if (s) dataWrap.appendChild(s);
|
||||
if (!s1 && !s2 && !s3 && !s4) {
|
||||
const empty = document.createElement("div");
|
||||
css(empty, "font-size:12px;color:#555;text-align:center;padding:20px;");
|
||||
empty.textContent = "No data for this combination";
|
||||
|
||||
Reference in New Issue
Block a user