implement vae selection in UI & vae iteration tools
This commit is contained in:
+11
-1
@@ -545,6 +545,12 @@ class UltimateConfigBuilder:
|
||||
if seed_behavior == "randomize":
|
||||
config["seed_behavior"] = "randomize"
|
||||
|
||||
# Process VAEs
|
||||
vaes_raw = config_array.get("vaes", ["None"])
|
||||
vae_strings = [str(v) for v in vaes_raw if v and v != "None"]
|
||||
if vae_strings:
|
||||
config["vae"] = vae_strings if len(vae_strings) > 1 else vae_strings[0]
|
||||
|
||||
# Add model_type and related fields for non-checkpoint models
|
||||
if model_type != "checkpoint":
|
||||
config["model_type"] = model_type
|
||||
@@ -759,6 +765,9 @@ async def get_model_lists_endpoint(request):
|
||||
"hunyuan_image", "hunyuan_video_15"
|
||||
]
|
||||
|
||||
# VAE list
|
||||
vae_list = folder_paths.get_filename_list("vae")
|
||||
|
||||
return web.json_response({
|
||||
"checkpoints": checkpoints,
|
||||
"diffusion_models": diffusion_models,
|
||||
@@ -766,7 +775,8 @@ async def get_model_lists_endpoint(request):
|
||||
"text_encoders": text_encoders,
|
||||
"clip_gguf": clip_gguf,
|
||||
"clip_types": clip_types,
|
||||
"dual_clip_types": dual_clip_types
|
||||
"dual_clip_types": dual_clip_types,
|
||||
"vae": vae_list
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[ConfigBuilder] Error in model_lists endpoint: {e}")
|
||||
|
||||
+5
-1
@@ -358,6 +358,9 @@ def expand_configs(raw_configs, pos_prompts, neg_prompts, denoise_values, seed,
|
||||
else:
|
||||
expanded_models.extend(get_files_from_folder(m, model_folder))
|
||||
|
||||
# Expand VAE list
|
||||
raw_vaes = to_list(entry.get("vae", "Default"))
|
||||
|
||||
# Expand LoRA stacks (strengths are now in the lora string itself)
|
||||
expanded_loras = expand_lora_stack(entry.get("lora", "None"))
|
||||
|
||||
@@ -374,7 +377,7 @@ def expand_configs(raw_configs, pos_prompts, neg_prompts, denoise_values, seed,
|
||||
# Build all combinations
|
||||
base_combos = []
|
||||
for combo in itertools.product(samplers, schedulers, steps_l, cfgs, clip_skips, expanded_loras,
|
||||
denoise_values, entry_prompt_pairs, expanded_models):
|
||||
denoise_values, entry_prompt_pairs, expanded_models, raw_vaes):
|
||||
base_combos.append({
|
||||
"sampler": combo[0],
|
||||
"scheduler": combo[1],
|
||||
@@ -386,6 +389,7 @@ def expand_configs(raw_configs, pos_prompts, neg_prompts, denoise_values, seed,
|
||||
"positive": combo[7][0],
|
||||
"negative": combo[7][1],
|
||||
"model": combo[8],
|
||||
"vae": combo[9],
|
||||
"seed": seed,
|
||||
"seed_behavior": entry.get("seed_behavior", "fixed"),
|
||||
"model_type": model_type,
|
||||
|
||||
+54
-10
@@ -19,7 +19,8 @@ from .manifest_utils import load_existing_manifest, save_manifest
|
||||
from .model_loader import (
|
||||
load_checkpoint, load_loras, cleanup_model_references,
|
||||
get_latent_channels, load_loras_for_preencoding,
|
||||
print_incompatible_loras_summary, load_diffusion_model_and_clip
|
||||
print_incompatible_loras_summary, load_diffusion_model_and_clip,
|
||||
load_vae_by_name
|
||||
)
|
||||
from .lora_utils import expand_lora_folder
|
||||
from .image_generation import (
|
||||
@@ -219,7 +220,7 @@ def run_generation_loop(
|
||||
extra_seeds = [random.randint(0, 2**32 - 1) for _ in range(add_random_seeds_to_gens)]
|
||||
|
||||
expanded = expand_configs(raw_configs, pos_prompts, neg_prompts, denoise_values, seed, extra_seeds, ckpt_name)
|
||||
expanded.sort(key=lambda x: (x.get('model_type', 'checkpoint'), x['model'], tuple(x.get('text_encoders', [])), x['lora'], x['positive'], x['negative']))
|
||||
expanded.sort(key=lambda x: (x.get('model_type', 'checkpoint'), x['model'], tuple(x.get('text_encoders', [])), x.get('vae', 'Default'), x['lora'], x['positive'], x['negative']))
|
||||
|
||||
# ==== EXPAND LORA FOLDERS ====
|
||||
print(f"[GridTester] 🎲 Expanding LoRA folders and random selections...")
|
||||
@@ -245,13 +246,20 @@ def run_generation_loop(
|
||||
print(f"[GridTester] ✅ LoRA expansion complete")
|
||||
|
||||
# ==== VAE VALIDATION FOR NON-CHECKPOINT MODELS ====
|
||||
has_non_checkpoint = any(c.get("model_type", "checkpoint") != "checkpoint" for c in expanded)
|
||||
if has_non_checkpoint and not use_remote_vae and optional_vae is None:
|
||||
# Only error if non-checkpoint model has no VAE source (no optional_vae AND no per-config VAE)
|
||||
use_remote_vae = remote_vae_endpoint and remote_vae_endpoint != "None"
|
||||
needs_vae = any(
|
||||
c.get("model_type", "checkpoint") != "checkpoint"
|
||||
and c.get("vae", "Default") == "Default"
|
||||
for c in expanded
|
||||
)
|
||||
if needs_vae and not use_remote_vae and optional_vae is None:
|
||||
raise ValueError(
|
||||
"GGUF and diffusion models do not include a bundled VAE.\n"
|
||||
"You must either:\n"
|
||||
" 1. Connect a VAE to the optional_vae input on the sampler node, or\n"
|
||||
" 2. Enable remote VAE decoding via remote_vae_endpoint\n"
|
||||
" 2. Enable remote VAE decoding via remote_vae_endpoint, or\n"
|
||||
" 3. Set a specific VAE in the config builder for those configs\n"
|
||||
)
|
||||
|
||||
# ==== REGISTER SMART CACHE SCHEDULE ====
|
||||
@@ -273,9 +281,8 @@ def run_generation_loop(
|
||||
else:
|
||||
pos_hash, neg_hash = None, None
|
||||
|
||||
# ==== REMOTE VAE SETUP ====
|
||||
use_remote_vae = remote_vae_endpoint and remote_vae_endpoint != "None"
|
||||
|
||||
# ==== REMOTE VAE SETUP (use_remote_vae already defined above for VAE validation) ====
|
||||
|
||||
try:
|
||||
if PromptServer is not None:
|
||||
pbar = PromptServer.instance.progress_bar_pool.get_progress_bar(unique_id)
|
||||
@@ -290,6 +297,8 @@ def run_generation_loop(
|
||||
cached_model_key = None
|
||||
cached_lora_key = None
|
||||
cached_lora_cache_key = None
|
||||
cached_vae_key = None # Track which VAE is currently loaded
|
||||
default_model_vae = None # Track the model's bundled/default VAE for reverting
|
||||
conditioning_cache = {"positive": {}, "negative": {}}
|
||||
incompatible_loras = {}
|
||||
pending_batch = []
|
||||
@@ -325,9 +334,21 @@ def run_generation_loop(
|
||||
optional_model, optional_clip, optional_vae,
|
||||
optional_positive, optional_negative, None, None, model_cache=model_cache
|
||||
)
|
||||
|
||||
# VAE fallback for non-checkpoint models
|
||||
if loaded_vae is None and optional_vae is not None:
|
||||
loaded_vae = optional_vae
|
||||
# Remember this model's default VAE for reverting later
|
||||
default_model_vae = loaded_vae
|
||||
|
||||
# Per-config VAE: if first config specifies a VAE, load it
|
||||
target_vae = first_conf.get("vae", "Default")
|
||||
if target_vae != "Default":
|
||||
print(f"[GridTester] 🎨 Loading per-config VAE: {target_vae}")
|
||||
loaded_vae = load_vae_by_name(target_vae)
|
||||
cached_vae_key = target_vae
|
||||
else:
|
||||
cached_vae_key = "Default"
|
||||
|
||||
if first_conf["lora_expanded"] != "None":
|
||||
patched_model, patched_clip = load_loras_for_preencoding(
|
||||
@@ -363,7 +384,7 @@ def run_generation_loop(
|
||||
cached_lora_cache_key = None
|
||||
latent_channels = get_latent_channels(loaded_model, optional_latent)
|
||||
elif len(unique_model_keys) > 1:
|
||||
print(f"[GridTester] ⚠️ Multiple models detected ({len(unique_models)} different models) - pre-encoding DISABLED")
|
||||
print(f"[GridTester] ⚠️ Multiple models detected ({len(unique_model_keys)} different models) - pre-encoding DISABLED")
|
||||
cond_cache = None
|
||||
latent_channels = 4
|
||||
else:
|
||||
@@ -492,6 +513,9 @@ def run_generation_loop(
|
||||
# VAE fallback for non-checkpoint models
|
||||
if loaded_vae is None and optional_vae is not None:
|
||||
loaded_vae = optional_vae
|
||||
# Remember this model's default VAE for reverting later
|
||||
default_model_vae = loaded_vae
|
||||
cached_vae_key = "Default"
|
||||
|
||||
cached_model_key = target_model_key
|
||||
cached_lora_key = None
|
||||
@@ -500,7 +524,27 @@ def run_generation_loop(
|
||||
model_switched = True
|
||||
else:
|
||||
model_switched = False
|
||||
|
||||
|
||||
# ==== PER-CONFIG VAE SWITCHING ====
|
||||
target_vae = conf.get("vae", "Default")
|
||||
if target_vae != cached_vae_key:
|
||||
# Flush pending batch before switching VAE (they need current VAE for decoding)
|
||||
if pending_batch:
|
||||
if use_remote_vae:
|
||||
flush_batch_with_remote_vae(pending_batch, remote_vae_worker, existing_data, session_name)
|
||||
else:
|
||||
flush_batch_with_vae(pending_batch, loaded_vae, paths["images"], existing_data, session_name, paths["manifest"], unique_id)
|
||||
pending_batch = []
|
||||
|
||||
if target_vae == "Default":
|
||||
# Revert to model's bundled/default VAE
|
||||
loaded_vae = default_model_vae
|
||||
print(f"[GridTester] 🎨 Reverting to Default VAE")
|
||||
else:
|
||||
print(f"[GridTester] 🎨 Loading per-config VAE: {target_vae}")
|
||||
loaded_vae = load_vae_by_name(target_vae)
|
||||
cached_vae_key = target_vae
|
||||
|
||||
# ==== LORA SWITCHING ====
|
||||
current_lora_string = conf["lora_expanded"]
|
||||
|
||||
|
||||
@@ -14,6 +14,24 @@ from .lora_utils import LoRAFileNotFoundError
|
||||
from .config_utils import parse_lora_definition
|
||||
|
||||
|
||||
def load_vae_by_name(vae_name):
|
||||
"""
|
||||
Load a VAE by filename from the vae folder.
|
||||
Follows the pattern of ComfyUI's VAELoader.load_vae().
|
||||
|
||||
Args:
|
||||
vae_name: Filename of the VAE model
|
||||
|
||||
Returns:
|
||||
comfy.sd.VAE: Loaded VAE instance
|
||||
"""
|
||||
vae_path = folder_paths.get_full_path_or_raise("vae", vae_name)
|
||||
sd, metadata = comfy.utils.load_torch_file(vae_path, return_metadata=True)
|
||||
vae = comfy.sd.VAE(sd=sd, metadata=metadata)
|
||||
vae.throw_exception_if_invalid()
|
||||
return vae
|
||||
|
||||
|
||||
def load_checkpoint(
|
||||
target_model_name,
|
||||
ckpt_name,
|
||||
|
||||
@@ -11,7 +11,9 @@ import {
|
||||
getIterationCount,
|
||||
convertStateToConfigs,
|
||||
countPromptCombinations,
|
||||
expandPromptPreview
|
||||
expandPromptPreview,
|
||||
getAvailableVAEs,
|
||||
getVAEFolders
|
||||
} from './conf-builder-utilities.js';
|
||||
|
||||
import {
|
||||
@@ -199,6 +201,18 @@ export function createConfigArrayElement(node, configArray, arrayIdx) {
|
||||
};
|
||||
controlsBar.appendChild(addModelBtn);
|
||||
|
||||
const addVaeBtn = document.createElement("button");
|
||||
addVaeBtn.className = "cb-button";
|
||||
addVaeBtn.style.borderLeft = "4px solid #9900cc";
|
||||
addVaeBtn.textContent = `➕ Add VAE`;
|
||||
addVaeBtn.onclick = () => {
|
||||
if (!node.state.config_arrays[arrayIdx].vaes) node.state.config_arrays[arrayIdx].vaes = [];
|
||||
node.state.config_arrays[arrayIdx].vaes.push("None");
|
||||
node.saveState();
|
||||
node.renderUI();
|
||||
};
|
||||
controlsBar.appendChild(addVaeBtn);
|
||||
|
||||
const addLoraBtn = document.createElement("button");
|
||||
addLoraBtn.className = "cb-button";
|
||||
addLoraBtn.style.borderLeft = "4px solid #0066cc";
|
||||
@@ -1224,6 +1238,212 @@ function renderGGUFOptionsSection(node, container, configArray, arrayIdx) {
|
||||
container.appendChild(section);
|
||||
}
|
||||
|
||||
// --- VAE SECTION RENDERER ---
|
||||
|
||||
export function renderVAEsSection(node, div, configArray, arrayIdx) {
|
||||
if (!configArray.vaes || configArray.vaes.length === 0) return; // Don't show section if no VAEs added
|
||||
|
||||
// Filter out "only None" case — if vaes is ["None"], don't show section unless explicitly added
|
||||
const hasRealVAEs = configArray.vaes.some(v => v && v !== "None");
|
||||
if (!hasRealVAEs && configArray.vaes.length <= 1) return;
|
||||
|
||||
const vaeList = getAvailableVAEs();
|
||||
const vFolders = getVAEFolders();
|
||||
|
||||
const isSectionCollapsed = node.uiState.vaesSectionCollapsed?.[arrayIdx] || false;
|
||||
|
||||
const vaeGrid = document.createElement("div");
|
||||
vaeGrid.className = "cb-list-grid";
|
||||
|
||||
const vaeHeader = document.createElement("div");
|
||||
vaeHeader.className = "cb-section-toggle";
|
||||
vaeHeader.style.cssText = "padding: 8px; background: #3a3a3a; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #9900cc;";
|
||||
|
||||
const activeVaes = configArray.vaes.filter(v => v && v !== "None").length;
|
||||
const titleSpan = document.createElement("span");
|
||||
titleSpan.textContent = `VAEs (${configArray.vaes.length} Entries, ${activeVaes} Active)`;
|
||||
vaeHeader.appendChild(titleSpan);
|
||||
|
||||
const arrowSpan = document.createElement("span");
|
||||
arrowSpan.textContent = isSectionCollapsed ? "▶" : "▼";
|
||||
vaeHeader.appendChild(arrowSpan);
|
||||
|
||||
vaeGrid.appendChild(vaeHeader);
|
||||
|
||||
const contentContainer = document.createElement("div");
|
||||
contentContainer.style.display = isSectionCollapsed ? "none" : "contents";
|
||||
|
||||
vaeHeader.onclick = () => {
|
||||
const isNowCollapsed = contentContainer.style.display === "none";
|
||||
if (isNowCollapsed) {
|
||||
contentContainer.style.display = "contents";
|
||||
arrowSpan.textContent = "▼";
|
||||
if (!node.uiState.vaesSectionCollapsed) node.uiState.vaesSectionCollapsed = {};
|
||||
node.uiState.vaesSectionCollapsed[arrayIdx] = false;
|
||||
} else {
|
||||
contentContainer.style.display = "none";
|
||||
arrowSpan.textContent = "▶";
|
||||
if (!node.uiState.vaesSectionCollapsed) node.uiState.vaesSectionCollapsed = {};
|
||||
node.uiState.vaesSectionCollapsed[arrayIdx] = true;
|
||||
}
|
||||
};
|
||||
|
||||
configArray.vaes.forEach((vae, vaeIdx) => {
|
||||
contentContainer.appendChild(createVAEElement(node, vae, arrayIdx, vaeIdx, vaeList, vFolders));
|
||||
});
|
||||
|
||||
const addRow = document.createElement("div");
|
||||
addRow.style.width = "100%";
|
||||
addRow.style.padding = "4px 0";
|
||||
const addBtn = document.createElement("button");
|
||||
addBtn.className = "cb-button";
|
||||
addBtn.style.cssText = "width: 100%; border: 1px dashed #555; background: rgba(0,0,0,0.2); color: #aaa;";
|
||||
addBtn.textContent = "➕ Add New VAE";
|
||||
addBtn.onmouseover = () => addBtn.style.background = "rgba(255,255,255,0.1)";
|
||||
addBtn.onmouseout = () => addBtn.style.background = "rgba(0,0,0,0.2)";
|
||||
addBtn.onclick = () => {
|
||||
node.state.config_arrays[arrayIdx].vaes.push("None");
|
||||
node.saveState();
|
||||
node.renderUI();
|
||||
};
|
||||
addRow.appendChild(addBtn);
|
||||
contentContainer.appendChild(addRow);
|
||||
|
||||
vaeGrid.appendChild(contentContainer);
|
||||
div.appendChild(vaeGrid);
|
||||
}
|
||||
|
||||
// --- VAE ELEMENT CREATOR ---
|
||||
|
||||
function createVAEElement(node, vaeName, arrayIdx, vaeIdx, vaeList, vFolders) {
|
||||
const isFolder = vaeName && vaeName.endsWith("/");
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.className = "cb-item-card";
|
||||
div.style.borderLeft = "3px solid #9900cc";
|
||||
const uid = `vae_${arrayIdx}_${vaeIdx}`;
|
||||
|
||||
const isCollapsed = node.uiState.vaesCollapsed?.[uid] || false;
|
||||
|
||||
// Header
|
||||
const header = document.createElement("div");
|
||||
header.className = "cb-header-bar";
|
||||
|
||||
const leftGroup = document.createElement("div");
|
||||
leftGroup.className = "cb-header-left";
|
||||
|
||||
const toggleArrow = document.createElement("span");
|
||||
toggleArrow.textContent = isCollapsed ? "▶" : "▼";
|
||||
toggleArrow.style.color = "#aaa";
|
||||
toggleArrow.style.fontSize = "10px";
|
||||
toggleArrow.style.width = "12px";
|
||||
leftGroup.appendChild(toggleArrow);
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.textContent = `VAE #${vaeIdx + 1}`;
|
||||
label.style.color = "#9900cc";
|
||||
label.style.fontSize = "10px";
|
||||
label.style.marginRight = "6px";
|
||||
leftGroup.appendChild(label);
|
||||
|
||||
const nameSpan = document.createElement("span");
|
||||
nameSpan.className = "cb-header-name";
|
||||
nameSpan.textContent = getShortName(vaeName || "None");
|
||||
leftGroup.appendChild(nameSpan);
|
||||
|
||||
header.appendChild(leftGroup);
|
||||
|
||||
const deleteBtn = document.createElement("button");
|
||||
deleteBtn.className = "cb-button danger";
|
||||
deleteBtn.style.padding = "2px 6px";
|
||||
deleteBtn.style.fontSize = "10px";
|
||||
deleteBtn.textContent = "✖";
|
||||
deleteBtn.onclick = () => {
|
||||
node.state.config_arrays[arrayIdx].vaes.splice(vaeIdx, 1);
|
||||
if (node.state.config_arrays[arrayIdx].vaes.length === 0) {
|
||||
node.state.config_arrays[arrayIdx].vaes = ["None"];
|
||||
}
|
||||
node.saveState();
|
||||
node.renderUI();
|
||||
};
|
||||
header.appendChild(deleteBtn);
|
||||
div.appendChild(header);
|
||||
|
||||
// Content container
|
||||
const contentDiv = document.createElement("div");
|
||||
contentDiv.style.display = isCollapsed ? "none" : "flex";
|
||||
contentDiv.style.flexDirection = "column";
|
||||
contentDiv.style.gap = "6px";
|
||||
contentDiv.style.width = "100%";
|
||||
|
||||
header.onclick = (e) => {
|
||||
if (e.target.tagName === 'BUTTON') return;
|
||||
const isNowCollapsed = contentDiv.style.display !== "none";
|
||||
contentDiv.style.display = isNowCollapsed ? "none" : "flex";
|
||||
toggleArrow.textContent = isNowCollapsed ? "▶" : "▼";
|
||||
if (!node.uiState.vaesCollapsed) node.uiState.vaesCollapsed = {};
|
||||
node.uiState.vaesCollapsed[uid] = isNowCollapsed;
|
||||
};
|
||||
|
||||
// File/Folder Type Select
|
||||
const typeSelect = document.createElement("select");
|
||||
typeSelect.className = "cb-select";
|
||||
typeSelect.innerHTML = `
|
||||
<option value="file" ${!isFolder ? 'selected' : ''}>VAE File</option>
|
||||
<option value="folder" ${isFolder ? 'selected' : ''}>Folder</option>
|
||||
`;
|
||||
typeSelect.onchange = () => {
|
||||
const newVal = typeSelect.value === "folder" ? "/" : "None";
|
||||
node.state.config_arrays[arrayIdx].vaes[vaeIdx] = newVal;
|
||||
node.saveState();
|
||||
node.renderUI();
|
||||
};
|
||||
contentDiv.appendChild(typeSelect);
|
||||
|
||||
// Searchable Select for VAE
|
||||
const options = isFolder ? vFolders : vaeList;
|
||||
const currentVal = vaeName || "None";
|
||||
const optionsList = (options && options.includes(currentVal)) || currentVal === "None" || currentVal === "/"
|
||||
? options || ["None"]
|
||||
: [currentVal, ...(options || ["None"])];
|
||||
|
||||
const nameSearchable = createSearchableSelect(
|
||||
optionsList,
|
||||
currentVal,
|
||||
(value) => {
|
||||
node.state.config_arrays[arrayIdx].vaes[vaeIdx] = normalizePath(value);
|
||||
node.saveState();
|
||||
node.renderUI();
|
||||
},
|
||||
isFolder ? "Search folders..." : "Search VAEs..."
|
||||
);
|
||||
contentDiv.appendChild(nameSearchable);
|
||||
|
||||
// Folder expand button
|
||||
if (isFolder && vaeName !== "None" && vaeName !== "/") {
|
||||
const expandBtn = document.createElement("button");
|
||||
expandBtn.className = "cb-button";
|
||||
expandBtn.style.cssText = "width: 100%; border-left: 3px solid #9900cc; font-size: 11px; margin-top: 4px;";
|
||||
expandBtn.textContent = "📂 Add all individually";
|
||||
expandBtn.onclick = () => {
|
||||
const normalize = (str) => str.replace(/\\/g, "/");
|
||||
const folderPrefix = normalize(vaeName);
|
||||
const matchingVAEs = vaeList ? vaeList.filter(v => normalize(v).startsWith(folderPrefix)) : [];
|
||||
if (matchingVAEs.length > 0) {
|
||||
node.state.config_arrays[arrayIdx].vaes.splice(vaeIdx, 1, ...matchingVAEs);
|
||||
node.saveState();
|
||||
node.renderUI();
|
||||
} else {
|
||||
alert(`No VAEs found in folder: ${folderPrefix}`);
|
||||
}
|
||||
};
|
||||
contentDiv.appendChild(expandBtn);
|
||||
}
|
||||
|
||||
div.appendChild(contentDiv);
|
||||
return div;
|
||||
}
|
||||
|
||||
export function renderLorasSection(node, div, configArray, arrayIdx, availableLoras, loraFolders) {
|
||||
if (!configArray.loras || configArray.loras.length === 0) configArray.loras = ["None"];
|
||||
|
||||
@@ -2075,6 +2295,7 @@ export async function renderUI(node, availableLoras, modelLists, loraFolders, av
|
||||
cfg: "7.0",
|
||||
seed_behavior: "fixed",
|
||||
models: ["None"],
|
||||
vaes: ["None"],
|
||||
text_encoders: [],
|
||||
clip_type: "stable_diffusion",
|
||||
gguf_options: {},
|
||||
@@ -2115,6 +2336,7 @@ export async function renderUI(node, availableLoras, modelLists, loraFolders, av
|
||||
const arrayElement = createConfigArrayElement(node, configArray, arrayIdx);
|
||||
renderConfigPromptsSection(node, arrayElement, configArray, arrayIdx);
|
||||
renderModelsSection(node, arrayElement, configArray, arrayIdx, modelLists);
|
||||
renderVAEsSection(node, arrayElement, configArray, arrayIdx);
|
||||
renderLorasSection(node, arrayElement, configArray, arrayIdx, availableLoras, loraFolders);
|
||||
arraysContainer.appendChild(arrayElement);
|
||||
});
|
||||
|
||||
@@ -65,8 +65,10 @@ app.registerExtension({
|
||||
this.uiState = {
|
||||
modelsSectionCollapsed: {},
|
||||
lorasSectionCollapsed: {},
|
||||
vaesSectionCollapsed: {},
|
||||
modelsCollapsed: {},
|
||||
lorasCollapsed: {},
|
||||
vaesCollapsed: {},
|
||||
promptsSectionCollapsed: {},
|
||||
globalPromptsSectionCollapsed: false
|
||||
};
|
||||
@@ -87,6 +89,7 @@ app.registerExtension({
|
||||
steps: "20, 30",
|
||||
cfg: "7.0",
|
||||
models: ["None"],
|
||||
vaes: ["None"],
|
||||
text_encoders: [],
|
||||
clip_type: "stable_diffusion",
|
||||
gguf_options: {},
|
||||
@@ -262,6 +265,7 @@ app.registerExtension({
|
||||
if (!arr.text_encoders) arr.text_encoders = [];
|
||||
if (!arr.clip_type) arr.clip_type = "stable_diffusion";
|
||||
if (!arr.gguf_options) arr.gguf_options = {};
|
||||
if (!arr.vaes) arr.vaes = ["None"];
|
||||
});
|
||||
|
||||
this.state.config_arrays = loadedArrays;
|
||||
@@ -296,6 +300,7 @@ app.registerExtension({
|
||||
steps: oldState.steps || "20",
|
||||
cfg: oldState.cfg || "7.0",
|
||||
models: oldState.model ? [utilities.normalizePath(oldState.model)] : ["None"],
|
||||
vaes: ["None"],
|
||||
text_encoders: [],
|
||||
clip_type: "stable_diffusion",
|
||||
gguf_options: {},
|
||||
@@ -367,6 +372,9 @@ app.registerExtension({
|
||||
if (!arr.text_encoders) arr.text_encoders = [];
|
||||
if (!arr.clip_type) arr.clip_type = "stable_diffusion";
|
||||
if (!arr.gguf_options) arr.gguf_options = {};
|
||||
|
||||
// Migration: ensure VAE field exists
|
||||
if (!arr.vaes) arr.vaes = ["None"];
|
||||
});
|
||||
} else if (existing.lora_config) {
|
||||
this.state = this.migrateOldFormat(existing);
|
||||
|
||||
@@ -14,6 +14,8 @@ let availableGGUFModels = null;
|
||||
let ggufModelFolders = null;
|
||||
let availableTextEncoders = null;
|
||||
let textEncoderFolders = null;
|
||||
let availableVAEs = null;
|
||||
let vaeFolders = null;
|
||||
let clipTypes = [];
|
||||
let dualClipTypes = [];
|
||||
let availableSessions = ["None"];
|
||||
@@ -36,6 +38,8 @@ export function clearAllCaches() {
|
||||
ggufModelFolders = null;
|
||||
availableTextEncoders = null;
|
||||
textEncoderFolders = null;
|
||||
availableVAEs = null;
|
||||
vaeFolders = null;
|
||||
clipTypes = [];
|
||||
dualClipTypes = [];
|
||||
}
|
||||
@@ -175,9 +179,19 @@ export async function getModelLists() {
|
||||
clipTypes = data.clip_types || [];
|
||||
dualClipTypes = data.dual_clip_types || [];
|
||||
|
||||
console.log(`[ConfigBuilder] Model lists: ${availableModels?.length || 0} checkpoints, ` +
|
||||
`${availableDiffusionModels.length} diffusion, ${availableGGUFModels.length} GGUF, ` +
|
||||
`${availableTextEncoders.length} text encoders`);
|
||||
// VAE list
|
||||
availableVAEs = (data.vae || []).map(normalizePath);
|
||||
vaeFolders = extractFolders(availableVAEs);
|
||||
|
||||
console.log(`[ConfigBuilder] Model lists loaded: ${availableModels?.length || 0} checkpoints, ` +
|
||||
`${availableDiffusionModels.length} diffusion models, ${availableGGUFModels.length} GGUFs, ` +
|
||||
`${availableTextEncoders.length} text encoders, ${availableVAEs.length} VAEs`);
|
||||
if (availableDiffusionModels.length === 0) {
|
||||
console.log(`[ConfigBuilder] ℹ️ No diffusion models found. Place .safetensors files in ComfyUI/models/unet/ or ComfyUI/models/diffusion_models/`);
|
||||
}
|
||||
if (availableGGUFModels.length === 0) {
|
||||
console.log(`[ConfigBuilder] ℹ️ No GGUF models found. Install ComfyUI-GGUF and place .gguf files in the unet_gguf folder.`);
|
||||
}
|
||||
return data;
|
||||
} catch (e) {
|
||||
console.error("[ConfigBuilder] Error fetching model lists:", e);
|
||||
@@ -191,6 +205,8 @@ export function getAvailableGGUFModels() { return availableGGUFModels || []; }
|
||||
export function getGGUFModelFolders() { return ggufModelFolders || ["/"]; }
|
||||
export function getAvailableTextEncoders() { return availableTextEncoders || []; }
|
||||
export function getTextEncoderFolders() { return textEncoderFolders || ["/"]; }
|
||||
export function getAvailableVAEs() { return availableVAEs || []; }
|
||||
export function getVAEFolders() { return vaeFolders || ["/"]; }
|
||||
export function getClipTypes() { return clipTypes; }
|
||||
export function getDualClipTypes() { return dualClipTypes; }
|
||||
|
||||
@@ -352,13 +368,28 @@ export function getIterationCount(configArray) {
|
||||
}
|
||||
if (l_count === 0) l_count = 1;
|
||||
|
||||
// 4. Prompts (per-config custom prompts multiply the iteration count)
|
||||
// 4. VAEs
|
||||
let v_count = 0;
|
||||
if (!configArray.vaes || configArray.vaes.length === 0) {
|
||||
v_count = 1; // Default VAE
|
||||
} else {
|
||||
configArray.vaes.forEach(v => {
|
||||
if (!v || v === "None") {
|
||||
v_count += 1; // "None" = use Default
|
||||
} else {
|
||||
v_count += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (v_count === 0) v_count = 1;
|
||||
|
||||
// 5. Prompts (per-config custom prompts multiply the iteration count)
|
||||
let p_count = 1;
|
||||
if (configArray.use_custom_prompts && configArray.positive_prompt_groups && configArray.positive_prompt_groups.length > 0) {
|
||||
p_count = countPromptCombinations(configArray.positive_prompt_groups);
|
||||
}
|
||||
|
||||
return m_count * l_count * s_count * sch_count * st_count * c_count * p_count;
|
||||
return m_count * l_count * v_count * s_count * sch_count * st_count * c_count * p_count;
|
||||
}
|
||||
|
||||
// --- CONFIG CONVERSION ---
|
||||
@@ -400,6 +431,9 @@ export function convertStateToConfigs(state) {
|
||||
}
|
||||
});
|
||||
|
||||
// Process VAEs
|
||||
const vaes = (configArray.vaes || []).filter(v => v && v !== "None");
|
||||
|
||||
const config = {
|
||||
sampler: split(configArray.samplers),
|
||||
scheduler: split(configArray.schedulers),
|
||||
@@ -409,6 +443,11 @@ export function convertStateToConfigs(state) {
|
||||
model: finalModels.length > 1 ? finalModels : finalModels[0] || "None"
|
||||
};
|
||||
|
||||
// Add VAE if any are selected (not "None")
|
||||
if (vaes.length > 0) {
|
||||
config.vae = vaes.length > 1 ? vaes : vaes[0];
|
||||
}
|
||||
|
||||
// Add model_type and related fields for non-checkpoint models
|
||||
if (modelType !== "checkpoint") {
|
||||
config.model_type = modelType;
|
||||
@@ -563,6 +602,16 @@ export function convertConfigsToConfigArrays(configs) {
|
||||
strengthLock = { ...config.lora_strength_lock };
|
||||
}
|
||||
|
||||
// Parse VAE
|
||||
let vaes = ["None"];
|
||||
if (config.vae) {
|
||||
if (Array.isArray(config.vae)) {
|
||||
vaes = config.vae.map(v => normalizePath(String(v)));
|
||||
} else {
|
||||
vaes = [normalizePath(String(config.vae))];
|
||||
}
|
||||
}
|
||||
|
||||
// Parse prompt fields from loaded config
|
||||
let positivePromptGroups = [];
|
||||
let negativePrompt = "";
|
||||
@@ -602,6 +651,7 @@ export function convertConfigsToConfigArrays(configs) {
|
||||
cfg: toString(config.cfg || "7.0"),
|
||||
seed_behavior: config.seed_behavior || "fixed",
|
||||
models: models,
|
||||
vaes: vaes,
|
||||
text_encoders: config.text_encoders || [],
|
||||
clip_type: config.clip_type || "stable_diffusion",
|
||||
gguf_options: config.gguf_options || {},
|
||||
@@ -625,6 +675,7 @@ export function convertConfigsToConfigArrays(configs) {
|
||||
cfg: "7.0",
|
||||
seed_behavior: "fixed",
|
||||
models: ["None"],
|
||||
vaes: ["None"],
|
||||
text_encoders: [],
|
||||
clip_type: "stable_diffusion",
|
||||
gguf_options: {},
|
||||
|
||||
Reference in New Issue
Block a user