diff --git a/config_builder_node.py b/config_builder_node.py index 0612e13..05b3593 100644 --- a/config_builder_node.py +++ b/config_builder_node.py @@ -521,7 +521,14 @@ class UltimateConfigBuilder: configs_output = [] total_lora_configs = 0 - + + # ============================================================================ + # SYNC WARNING: This config-building loop MUST stay in sync with the JS-side + # convertStateToConfigs() in web/conf_builder/conf-builder-utilities.js. + # That function generates the preview JSON in the Builder UI. + # If you add a new config field here, add it there too (and vice versa). + # Fields consumed by config_utils.expand_configs() must be output by BOTH. + # ============================================================================ for config_array in config_arrays: # Parse values from this config array sampler_list = self.parse_comma_list(config_array.get("samplers", "euler")) @@ -603,6 +610,37 @@ class UltimateConfigBuilder: if lora_triggerwords_append_settings and any(v != "none" for v in lora_triggerwords_append_settings.values()): config["lora_triggerwords_append_settings"] = lora_triggerwords_append_settings + # Per-config resolutions (override sampler's resolutions_json) + raw_resolutions = config_array.get("resolutions", []) + if raw_resolutions and len(raw_resolutions) > 0: + # Convert "WxH" strings to [W, H] arrays for config_utils.expand_configs() + parsed_res = [] + for r in raw_resolutions: + if isinstance(r, str) and "x" in r: + parts = r.split("x") + parsed_res.append([int(parts[0]), int(parts[1])]) + elif isinstance(r, (list, tuple)) and len(r) == 2: + parsed_res.append([int(r[0]), int(r[1])]) + if parsed_res: + config["resolutions"] = parsed_res + + # Attention mode(s) for testing different attention implementations + attention_modes = config_array.get("attention_modes", ["default"]) + if isinstance(attention_modes, list): + filtered = [a for a in attention_modes if a and a != "default"] + if filtered: + config["attention_mode"] = filtered if len(filtered) > 1 else filtered[0] + elif isinstance(attention_modes, str) and attention_modes != "default": + config["attention_mode"] = attention_modes + + # Model prompt prefix/suffix (quality tags prepended/appended to prompts) + model_prompt_prefix = config_array.get("model_prompt_prefix", "") + if model_prompt_prefix and model_prompt_prefix.strip(): + config["model_prompt_prefix"] = model_prompt_prefix.strip() + model_prompt_suffix = config_array.get("model_prompt_suffix", "") + if model_prompt_suffix and model_prompt_suffix.strip(): + config["model_prompt_suffix"] = model_prompt_suffix.strip() + # Add extra model & sampling options if enabled if model_sampling_override and model_sampling_override != "none": config["model_sampling_override"] = model_sampling_override diff --git a/pyproject.toml b/pyproject.toml index d54f707..65fedae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "ultimate-auto-sampler-config-grid-testing-suite" description = "Test Everything All In One Run!!! Testing & benchmarking tool for samplers, schedulers, CFG scales, multiple prompts, img2img denoise values, Models, Weights, LoRA and more! Features infinite-canvas dashboard image grids with virtual scrolling that can handle thousands of images! Smart caching, powerful sorting, filters, favoriting, reviewing, automatic resume on interrupt, and real-time visualization. Test entire checkpoint folders, stack multiple LoRAs, generate random seed variations, and export optimized configs, save sessions. Includes powerful UI for building AIO comparison runs, fullscreen mode, keyboard navigation, smart filtering by parameters, civitai model data info, and one-click regeneration workflow, and SO MUCH MORE. Perfect for sampler output optimization and parameter tuning." -version = "1.16.0" +version = "1.16.1" license = {file = "LICENSE"} classifiers = [ # # For OS-independent nodes (works on all operating systems) diff --git a/web/conf_builder/conf-builder-config-management.js b/web/conf_builder/conf-builder-config-management.js index 15817a8..b66bf44 100644 --- a/web/conf_builder/conf-builder-config-management.js +++ b/web/conf_builder/conf-builder-config-management.js @@ -4427,6 +4427,9 @@ export function updatePreview(node) { if (!preview) return; const configs = convertStateToConfigs(node.state); + // NOTE: This preview shows what convertStateToConfigs() produces (JS-side). + // The ACTUAL configs_json output comes from generate_config() in config_builder_node.py. + // Both must output the same fields — see SYNC WARNING in each file. // Build the full output object matching what configs_json will contain const output = { configs }; if (node.state.distribution_enabled) { diff --git a/web/conf_builder/conf-builder-utilities.js b/web/conf_builder/conf-builder-utilities.js index 74245ac..52cefe3 100644 --- a/web/conf_builder/conf-builder-utilities.js +++ b/web/conf_builder/conf-builder-utilities.js @@ -477,6 +477,13 @@ export function getIterationCount(configArray) { } // --- CONFIG CONVERSION --- +// ============================================================================ +// SYNC WARNING: This function MUST stay in sync with the Python-side +// generate_config() in config_builder_node.py. +// That function produces the actual configs_json consumed by the sampler node. +// If you add a new config field here, add it there too (and vice versa). +// Fields consumed by config_utils.expand_configs() must be output by BOTH. +// ============================================================================ export function convertStateToConfigs(state) { const configs = [];