Files
JasonHoku-ComfyUI-Ultimate-…/config_builder_node.py
T

285 lines
10 KiB
Python

"""
Ultimate Config Builder - Complete HTML UI Version
ALL data stored in single widget (lora_config)
Python reads everything from that widget
"""
import os
import json
import folder_paths
from typing import List, Dict, Any
class UltimateConfigBuilder:
"""
Config builder with complete HTML UI.
All data is stored in the lora_config widget as a single JSON object.
"""
@classmethod
def INPUT_TYPES(cls):
sessions = cls.get_available_sessions()
return {
"required": {
# Session Management (hidden, controlled by HTML)
"session_name": ("STRING", {
"default": "my_test_session",
"multiline": False
}),
"load_session": (sessions, {
"default": sessions[0] if sessions else "None"
}),
# Sampler Settings (hidden, controlled by HTML)
"samplers": ("STRING", {
"default": "euler, dpmpp_2m",
"multiline": False
}),
"schedulers": ("STRING", {
"default": "normal, karras",
"multiline": False
}),
"steps": ("STRING", {
"default": "20, 30",
"multiline": False
}),
"cfg": ("STRING", {
"default": "7.0",
"multiline": False
}),
# LoRA Configuration (ACTUAL DATA STORAGE - contains EVERYTHING)
"lora_config": ("STRING", {
"default": cls.get_default_config(),
"multiline": True
}),
# Options (hidden, controlled by HTML)
"include_none": ("BOOLEAN", {
"default": True
}),
},
"optional": {
"model": ("STRING", {
"default": "",
"multiline": False
}),
}
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("configs_json", "session_name")
FUNCTION = "generate_config"
CATEGORY = "sampling/testing"
OUTPUT_NODE = True
@staticmethod
def get_default_config():
"""Return default complete configuration"""
config = {
"session_name": "my_test_session",
"include_none": True,
"config_arrays": [
{
"name": "Config 1",
"samplers": "euler, dpmpp_2m",
"schedulers": "normal, karras",
"steps": "20, 30",
"cfg": "7.0",
"model": "",
"loras": ["None"],
"combine": False
}
]
}
return json.dumps(config, indent=2, ensure_ascii=False)
@staticmethod
def get_available_sessions() -> List[str]:
"""Scan benchmarks folder for available sessions"""
sessions = ["None"]
try:
output_dir = folder_paths.get_output_directory()
benchmarks_dir = os.path.join(output_dir, "benchmarks")
if os.path.exists(benchmarks_dir):
for item in os.listdir(benchmarks_dir):
item_path = os.path.join(benchmarks_dir, item)
manifest_path = os.path.join(item_path, "manifest.json")
if os.path.isdir(item_path) and os.path.exists(manifest_path):
sessions.append(item)
except Exception as e:
print(f"[ConfigBuilder] Warning: Could not scan sessions: {e}")
return sessions
def parse_comma_list(self, value: str) -> List[str]:
"""Parse comma-separated string"""
if not value or value.strip() == "":
return []
return [item.strip() for item in value.split(",") if item.strip()]
def parse_number_list(self, value: str) -> List[float]:
"""Parse comma-separated numbers"""
items = self.parse_comma_list(value)
result = []
for item in items:
try:
result.append(float(item))
except ValueError:
print(f"[ConfigBuilder] Warning: Could not parse '{item}'")
return result
def process_lora_array(self, config_array: Dict, include_none: bool) -> List[str]:
"""
Process a SINGLE config array and return its lora strings.
Args:
config_array: Single config array dict from config_arrays
include_none: Whether to include "None" in results
Returns:
List of lora strings for this config array
"""
array_name = config_array.get("name", "Unnamed Config")
combine = config_array.get("combine", False)
loras = config_array.get("loras", [])
# Loras are already strings
lora_strings = [str(lora) for lora in loras if lora and lora != "None"]
# Add combined version if requested
if combine and len(lora_strings) > 1:
stackable = [s for s in lora_strings if not s.endswith("/")]
if len(stackable) > 1:
# When combine is true, ONLY return the combined version
combined = " + ".join(stackable)
lora_strings = [combined]
print(f"[ConfigBuilder] {array_name}: Using combined version only")
# Handle None option
if include_none:
lora_strings.insert(0, "None")
# Remove duplicates while preserving order
seen = set()
unique_strings = []
for item in lora_strings:
if item not in seen:
seen.add(item)
unique_strings.append(item)
print(f"[ConfigBuilder] {array_name}: Processed {len(unique_strings)} LoRA configs")
return unique_strings
def generate_config(
self,
session_name,
load_session,
samplers,
schedulers,
steps,
cfg,
lora_config,
include_none,
model=""
):
"""
Generate configuration.
NOTE: All widget parameters are IGNORED!
The actual data comes from the lora_config widget which contains everything.
"""
print(f"\n{'='*80}")
print(f"[ConfigBuilder] 🎯 Generating Configuration")
print(f"{'='*80}")
# Parse the COMPLETE state from lora_config widget
try:
state = json.loads(lora_config)
except json.JSONDecodeError as e:
print(f"[ConfigBuilder] ⚠️ Error parsing lora_config: {e}")
print(f"[ConfigBuilder] Using default config")
state = json.loads(self.get_default_config())
# Extract values from state
actual_session_name = state.get("session_name", session_name)
actual_include_none = state.get("include_none", include_none)
config_arrays = state.get("config_arrays", [])
if not config_arrays:
config_arrays = [{
"name": "Config 1",
"samplers": "euler",
"schedulers": "normal",
"steps": "20",
"cfg": "7.0",
"model": "",
"loras": ["None"],
"combine": False
}]
configs_output = []
total_lora_configs = 0
for config_array in config_arrays:
# Parse values from this config array
sampler_list = self.parse_comma_list(config_array.get("samplers", "euler"))
scheduler_list = self.parse_comma_list(config_array.get("schedulers", "normal"))
steps_list = self.parse_number_list(config_array.get("steps", "20"))
cfg_list = self.parse_number_list(config_array.get("cfg", "7.0"))
config_model = config_array.get("model", "")
# Process loras for this config
lora_strings = self.process_lora_array(config_array, actual_include_none)
total_lora_configs += len(lora_strings)
# Create ONE config for this array
config = {
"sampler": sampler_list if len(sampler_list) > 1 else sampler_list[0] if sampler_list else "euler",
"scheduler": scheduler_list if len(scheduler_list) > 1 else scheduler_list[0] if scheduler_list else "normal",
"steps": steps_list if len(steps_list) > 1 else steps_list[0] if steps_list else 20,
"cfg": cfg_list if len(cfg_list) > 1 else cfg_list[0] if cfg_list else 7.0,
"lora": lora_strings if len(lora_strings) > 1 else lora_strings[0] if lora_strings else "None"
}
if config_model and config_model.strip():
config["model"] = config_model.strip()
configs_output.append(config)
json_output = json.dumps(configs_output, indent=2, ensure_ascii=False)
# Calculate totals
total_combinations = 0
for config in configs_output:
lora_count = len(config["lora"]) if isinstance(config["lora"], list) else 1
sampler_count = len(config["sampler"]) if isinstance(config["sampler"], list) else 1
scheduler_count = len(config["scheduler"]) if isinstance(config["scheduler"], list) else 1
steps_count = len(config["steps"]) if isinstance(config["steps"], list) else 1
cfg_count = len(config["cfg"]) if isinstance(config["cfg"], list) else 1
total_combinations += (sampler_count * scheduler_count * steps_count * cfg_count * lora_count)
print(f"[ConfigBuilder] 📊 Configuration Summary:")
print(f" Session: {actual_session_name}")
print(f" Config Arrays: {len(config_arrays)}")
print(f" Total LoRA Configs: {total_lora_configs}")
print(f" Total Combinations: {total_combinations}")
print(f"{'='*80}\n")
# Return just the essentials
return (json_output, actual_session_name)
NODE_CLASS_MAPPINGS = {
"UltimateConfigBuilder": UltimateConfigBuilder
}
NODE_DISPLAY_NAME_MAPPINGS = {
"UltimateConfigBuilder": "Ultimate Config Builder"
}
WEB_DIRECTORY = "./web"