diff --git a/config_builder_node.py b/config_builder_node.py index 31acb61..04cda6e 100644 --- a/config_builder_node.py +++ b/config_builder_node.py @@ -832,6 +832,42 @@ class UltimateConfigBuilder: image_format = state.get("image_format", "webp") if image_format and image_format != "webp": session_settings["image_format"] = image_format + # Generator-node override settings — emit only when non-default so the + # widget value on the SamplerGridTester node wins for users who haven't + # explicitly set them in the Builder UI. When the Generator widgets are + # eventually removed, these can be made unconditional. + ov_existing = state.get("overwrite_existing", False) + if ov_existing: + session_settings["overwrite_existing"] = True + + flush_be = state.get("flush_batch_every", 4) + try: + flush_be = int(flush_be) + except (TypeError, ValueError): + flush_be = 4 + if flush_be != 4: + session_settings["flush_batch_every"] = flush_be + + lora_tw = state.get("lora_triggerwords_mode", "None") + if lora_tw and lora_tw != "None": + session_settings["lora_triggerwords_mode"] = lora_tw + + save_cc = state.get("save_conditioning_cache_to_file", False) + if save_cc: + session_settings["save_conditioning_cache_to_file"] = True + + en_mc = state.get("enable_model_cache", False) + if en_mc: + session_settings["enable_model_cache"] = True + + vae_bs = state.get("vae_batch_size", 4) + try: + vae_bs = int(vae_bs) + except (TypeError, ValueError): + vae_bs = 4 + if vae_bs != 4: + session_settings["vae_batch_size"] = vae_bs + if session_settings: output_obj["_session_settings"] = session_settings diff --git a/sampler_node.py b/sampler_node.py index 076892c..a83a89d 100644 --- a/sampler_node.py +++ b/sampler_node.py @@ -292,6 +292,37 @@ class SamplerGridTester: except Exception as e: print(f"[GridTester] ⚠️ Error parsing configs_json for distribution: {e}") + # Builder UI Run Settings override widget values when present. + # When a user sets these in the Builder UI, _session_settings carries + # them through configs_json and they take precedence over what's + # wired to the SamplerGridTester widgets. Generator widgets are still + # the fallback for backward compat. + if session_settings: + if "overwrite_existing" in session_settings: + overwrite_existing = bool(session_settings["overwrite_existing"]) + print(f"[GridTester] ⚙️ overwrite_existing overridden by Builder UI: {overwrite_existing}") + if "flush_batch_every" in session_settings: + try: + flush_batch_every = int(session_settings["flush_batch_every"]) + print(f"[GridTester] ⚙️ flush_batch_every overridden by Builder UI: {flush_batch_every}") + except (TypeError, ValueError): + pass + if "lora_triggerwords_mode" in session_settings: + lora_triggerwords_mode = str(session_settings["lora_triggerwords_mode"]) + print(f"[GridTester] ⚙️ lora_triggerwords_mode overridden by Builder UI: {lora_triggerwords_mode}") + if "save_conditioning_cache_to_file" in session_settings: + save_conditioning_cache_to_file = bool(session_settings["save_conditioning_cache_to_file"]) + print(f"[GridTester] ⚙️ save_conditioning_cache_to_file overridden by Builder UI: {save_conditioning_cache_to_file}") + if "enable_model_cache" in session_settings: + enable_model_cache = bool(session_settings["enable_model_cache"]) + print(f"[GridTester] ⚙️ enable_model_cache overridden by Builder UI: {enable_model_cache}") + if "vae_batch_size" in session_settings: + try: + vae_batch_size = int(session_settings["vae_batch_size"]) + print(f"[GridTester] ⚙️ vae_batch_size overridden by Builder UI: {vae_batch_size}") + except (TypeError, ValueError): + pass + return run_generation_loop( self, ckpt_name, positive_text, negative_text, seed, denoise, vae_batch_size, diff --git a/web/conf_builder/conf-builder-config-management.js b/web/conf_builder/conf-builder-config-management.js index 6b13d1e..dca558d 100644 --- a/web/conf_builder/conf-builder-config-management.js +++ b/web/conf_builder/conf-builder-config-management.js @@ -6344,6 +6344,12 @@ export function renderCooldownSection(node, container) { export function renderRunSettingsSection(node, container) { if (node.state.start_at_job === undefined) node.state.start_at_job = 0; if (node.state.image_format === undefined) node.state.image_format = "webp"; + if (node.state.overwrite_existing === undefined) node.state.overwrite_existing = false; + if (node.state.flush_batch_every === undefined) node.state.flush_batch_every = 4; + if (node.state.lora_triggerwords_mode === undefined) node.state.lora_triggerwords_mode = "None"; + if (node.state.save_conditioning_cache_to_file === undefined) node.state.save_conditioning_cache_to_file = false; + if (node.state.enable_model_cache === undefined) node.state.enable_model_cache = false; + if (node.state.vae_batch_size === undefined) node.state.vae_batch_size = 4; const section = document.createElement("div"); section.className = "cb-section full-width"; @@ -6414,6 +6420,73 @@ export function renderRunSettingsSection(node, container) { content.appendChild(fmtDiv); content.appendChild(fmtDesc); + // Helper: renders a labeled row with a "?" tooltip icon, binding to node.state[stateKey]. + function _addRunSetting(content, opts) { + // opts: { stateKey, label, tooltip, kind: "bool"|"int"|"select", options?, min?, max?, defaultValue } + const row = document.createElement("div"); + row.style.cssText = "margin-top: 10px; display: flex; align-items: center; gap: 8px;"; + + const label = document.createElement("label"); + label.textContent = opts.label + ":"; + label.style.cssText = "font-size: 12px; color: #ccc; white-space: nowrap;"; + + const help = document.createElement("span"); + help.textContent = "?"; + help.title = opts.tooltip; + help.style.cssText = "display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; background: #2a2a2a; color: #8af; font-size: 10px; font-weight: bold; cursor: help; user-select: none;"; + + let input; + if (opts.kind === "bool") { + input = document.createElement("input"); + input.type = "checkbox"; + input.checked = !!node.state[opts.stateKey]; + input.style.cssText = "transform: scale(1.2); cursor: pointer;"; + input.onchange = () => { node.state[opts.stateKey] = input.checked; node.saveState(); }; + } else if (opts.kind === "int") { + input = document.createElement("input"); + input.type = "number"; + if (opts.min !== undefined) input.min = opts.min; + if (opts.max !== undefined) input.max = opts.max; + input.value = node.state[opts.stateKey] !== undefined ? node.state[opts.stateKey] : opts.defaultValue; + input.style.cssText = "width: 70px; background: #1a1a1a; color: #ccc; border: 1px solid #444; border-radius: 4px; padding: 4px 6px; font-size: 12px;"; + input.onchange = () => { + const v = parseInt(input.value); + node.state[opts.stateKey] = isNaN(v) ? opts.defaultValue : v; + node.saveState(); + }; + } else if (opts.kind === "select") { + input = document.createElement("select"); + input.style.cssText = "background: #1a1a1a; color: #ccc; border: 1px solid #444; border-radius: 4px; padding: 4px 6px; font-size: 12px;"; + (opts.options || []).forEach(o => { + const opt = document.createElement("option"); + opt.value = o; + opt.textContent = o; + if (o === (node.state[opts.stateKey] || opts.defaultValue)) opt.selected = true; + input.appendChild(opt); + }); + input.onchange = () => { node.state[opts.stateKey] = input.value; node.saveState(); }; + } + + row.appendChild(label); + row.appendChild(help); + row.appendChild(input); + content.appendChild(row); + } + + _addRunSetting(content, { stateKey: "overwrite_existing", label: "Overwrite Existing", kind: "bool", + tooltip: "True = Re-run everything. False = Skip already generated images (Resume)." }); + _addRunSetting(content, { stateKey: "flush_batch_every", label: "Flush Batch Every", kind: "int", min: 0, max: 64, defaultValue: 4, + tooltip: "Update dashboard every X images. 0 = Use VAE Batch Size." }); + _addRunSetting(content, { stateKey: "lora_triggerwords_mode", label: "LoRA Triggerwords Mode", kind: "select", + options: ["None", "Append To End", "Append To Start", "Read From Config"], defaultValue: "None", + tooltip: "None = Don't fetch/append trigger words. Append To End = Add triggers at end of prompt (default behavior). Append To Start = Add triggers at start of prompt. Read From Config = Use lora_triggerwords_append_settings in config JSON to specify per-lora placement." }); + _addRunSetting(content, { stateKey: "save_conditioning_cache_to_file", label: "Save Conditioning Cache To File", kind: "bool", + tooltip: "Save CLIP conditioning cache to disk. Useful when experimenting with the same prompts/models — skips text encoding on resume. WARNING: Can create very large files in output/benchmarks. Automatically disabled when optional inputs (model/clip/conditioning) are connected." }); + _addRunSetting(content, { stateKey: "enable_model_cache", label: "Enable Model Cache", kind: "bool", + tooltip: "Experimental: intelligent model/LoRA caching with async background preloading. Speeds up generation when switching LoRAs frequently. Loads cached LoRAs from RAM instead of disk. Disable to reduce RAM/VRAM usage." }); + _addRunSetting(content, { stateKey: "vae_batch_size", label: "VAE Batch Size", kind: "int", min: -1, max: 64, defaultValue: 4, + tooltip: "How many images to encode/decode per VAE pass. Lower = less VRAM. -1 = process all at once. Default: 4." }); + section.appendChild(header); section.appendChild(content); container.appendChild(section); @@ -6583,6 +6656,10 @@ export async function renderUI(node, availableLoras, modelLists, loraFolders, av // Global Prompts Section renderGlobalPromptsSection(node, mainContent); + // Run Settings Section — session-level generator overrides, shown at top + // so users see them before diving into config arrays. + renderRunSettingsSection(node, mainContent); + // Config Arrays Section const configSection = document.createElement("div"); configSection.className = "cb-section full-width"; @@ -6774,7 +6851,6 @@ export async function renderUI(node, availableLoras, modelLists, loraFolders, av // Session-level settings (applies to all configs) renderUpscalingSection(node, mainContent, modelLists); renderCooldownSection(node, mainContent); - renderRunSettingsSection(node, mainContent); // Output Preview — rendered as a SIBLING of layoutWrapper (not inside // mainContent), so it sits as a fixed/resizable panel at the bottom of diff --git a/web/conf_builder/conf-builder-main.js b/web/conf_builder/conf-builder-main.js index 8164a37..36b92d9 100644 --- a/web/conf_builder/conf-builder-main.js +++ b/web/conf_builder/conf-builder-main.js @@ -189,7 +189,13 @@ app.registerExtension({ every_n: 1, clear_vram: false }, - image_format: "webp" + image_format: "webp", + overwrite_existing: false, + flush_batch_every: 4, + lora_triggerwords_mode: "None", + save_conditioning_cache_to_file: false, + enable_model_cache: false, + vae_batch_size: 4 }; // Create HTML container immediately @@ -267,6 +273,14 @@ app.registerExtension({ }); } + // Migration: ensure top-level Run Settings fields exist + if (this.state.overwrite_existing === undefined) this.state.overwrite_existing = false; + if (this.state.flush_batch_every === undefined) this.state.flush_batch_every = 4; + if (this.state.lora_triggerwords_mode === undefined) this.state.lora_triggerwords_mode = "None"; + if (this.state.save_conditioning_cache_to_file === undefined) this.state.save_conditioning_cache_to_file = false; + if (this.state.enable_model_cache === undefined) this.state.enable_model_cache = false; + if (this.state.vae_batch_size === undefined) this.state.vae_batch_size = 4; + this.saveState(); this.renderUI(); }