implement vae selection in UI & vae iteration tools

This commit is contained in:
JasonHoku
2026-02-14 23:36:38 -08:00
parent d178bd064f
commit f7ec7cca23
7 changed files with 375 additions and 18 deletions
+11 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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"]
+18
View File
@@ -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);
});
+8
View File
@@ -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);
+56 -5
View File
@@ -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: {},