claude prompt in ui updates 1
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git -C \".claude/worktrees/thirsty-greider\" status)",
|
||||
"Bash(git config --global --add safe.directory '%\\(prefix\\)///DESKTOP-2GIRPC5/comfy/comfy_v0.12.3/ComfyUI/custom_nodes/ComfyUI-Ultimate-Auto-Sampler-Config-Grid-Testing-Suite/.claude/worktrees/thirsty-greider')"
|
||||
]
|
||||
}
|
||||
}
|
||||
+36
-6
@@ -224,6 +224,8 @@ class UltimateConfigBuilder:
|
||||
config = {
|
||||
"session_name": "my_test_session",
|
||||
"include_none": False,
|
||||
"global_positive_groups": [],
|
||||
"global_negative": "",
|
||||
"config_arrays": [
|
||||
{
|
||||
"name": "Config 1",
|
||||
@@ -235,7 +237,10 @@ class UltimateConfigBuilder:
|
||||
"loras": ["None"],
|
||||
"lora_omit_triggers": [],
|
||||
"lora_triggerwords_append_settings": {},
|
||||
"combine": True
|
||||
"combine": True,
|
||||
"positive_prompt_groups": [],
|
||||
"negative_prompt": "",
|
||||
"use_custom_prompts": False
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -474,7 +479,11 @@ class UltimateConfigBuilder:
|
||||
actual_session_name = state.get("session_name", session_name)
|
||||
actual_include_none = state.get("include_none", include_none)
|
||||
config_arrays = state.get("config_arrays", [])
|
||||
|
||||
|
||||
# Global prompts (used when per-config prompts are not defined)
|
||||
global_positive_groups = state.get("global_positive_groups", [])
|
||||
global_negative = state.get("global_negative", "")
|
||||
|
||||
if not config_arrays:
|
||||
config_arrays = [{
|
||||
"name": "Config 1",
|
||||
@@ -486,7 +495,10 @@ class UltimateConfigBuilder:
|
||||
"loras": ["None"],
|
||||
"lora_omit_triggers": [],
|
||||
"lora_triggerwords_append_settings": {},
|
||||
"combine": True
|
||||
"combine": True,
|
||||
"positive_prompt_groups": [],
|
||||
"negative_prompt": "",
|
||||
"use_custom_prompts": False
|
||||
}]
|
||||
|
||||
configs_output = []
|
||||
@@ -522,12 +534,30 @@ class UltimateConfigBuilder:
|
||||
# Add omit triggers if present
|
||||
if omit_triggers:
|
||||
config["lora_omit_triggers"] = omit_triggers
|
||||
|
||||
|
||||
# Add trigger append settings if present
|
||||
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
|
||||
|
||||
|
||||
|
||||
# ==== PROMPT HANDLING ====
|
||||
# Priority: per-config > global > node inputs (omitted = use node inputs)
|
||||
use_custom = config_array.get("use_custom_prompts", False)
|
||||
per_config_positive_groups = config_array.get("positive_prompt_groups", [])
|
||||
per_config_negative = config_array.get("negative_prompt", "")
|
||||
|
||||
if use_custom and per_config_positive_groups:
|
||||
# Per-config prompts override everything
|
||||
# Store as nested array format for parse_prompt_input_nested() compatibility
|
||||
config["positive"] = per_config_positive_groups
|
||||
if per_config_negative:
|
||||
config["negative"] = per_config_negative
|
||||
elif global_positive_groups:
|
||||
# Global prompts override node inputs
|
||||
config["positive"] = global_positive_groups
|
||||
if global_negative:
|
||||
config["negative"] = global_negative
|
||||
# If neither, omit "positive"/"negative" keys - node inputs will be used as fallback
|
||||
|
||||
configs_output.append(config)
|
||||
|
||||
json_output = json.dumps(configs_output, indent=2, ensure_ascii=False)
|
||||
|
||||
+56
-19
@@ -260,41 +260,78 @@ def expand_lora_stack(lora_input):
|
||||
def expand_configs(raw_configs, pos_prompts, neg_prompts, denoise_values, seed, extra_seeds, ckpt_name=None):
|
||||
"""
|
||||
Expand raw config entries into full configuration combinations.
|
||||
|
||||
|
||||
Supports per-config prompts: if a config entry has "positive" and/or "negative" keys,
|
||||
those override the external pos_prompts/neg_prompts for that entry. This allows each
|
||||
config to define its own nested array prompts for Cartesian product expansion.
|
||||
|
||||
Args:
|
||||
raw_configs: List of config dictionaries with potentially wildcarded values
|
||||
pos_prompts: List of positive prompts
|
||||
neg_prompts: List of negative prompts
|
||||
pos_prompts: List of positive prompts (fallback when config doesn't define prompts)
|
||||
neg_prompts: List of negative prompts (fallback when config doesn't define prompts)
|
||||
denoise_values: List of denoise values
|
||||
seed: Base seed value
|
||||
extra_seeds: List of additional random seeds
|
||||
ckpt_name: Checkpoint name from node input (used as default when model is "Default")
|
||||
|
||||
|
||||
Returns:
|
||||
List of fully expanded config dictionaries
|
||||
"""
|
||||
ALL_SCHEDULERS = comfy.samplers.KSampler.SCHEDULERS
|
||||
ALL_SAMPLERS = comfy.samplers.KSampler.SAMPLERS
|
||||
expanded = []
|
||||
|
||||
# Determine prompt pairing strategy
|
||||
|
||||
# Default prompt pairing strategy (used when config doesn't define its own prompts)
|
||||
if len(pos_prompts) > 1 and len(neg_prompts) > 1 and len(pos_prompts) == len(neg_prompts):
|
||||
print("[GridTester] Detected matching prompt lists. Using 1-to-1 Pairing.")
|
||||
prompt_pairs = list(zip(pos_prompts, neg_prompts))
|
||||
default_prompt_pairs = list(zip(pos_prompts, neg_prompts))
|
||||
else:
|
||||
prompt_pairs = list(itertools.product(pos_prompts, neg_prompts))
|
||||
|
||||
default_prompt_pairs = list(itertools.product(pos_prompts, neg_prompts))
|
||||
|
||||
def to_list(x):
|
||||
return x if isinstance(x, list) else [x]
|
||||
|
||||
|
||||
for entry in raw_configs:
|
||||
# ==== PER-CONFIG PROMPT HANDLING ====
|
||||
# If this config defines its own prompts, parse them and use instead of defaults
|
||||
if "positive" in entry or "negative" in entry:
|
||||
raw_positive = entry.get("positive", "")
|
||||
raw_negative = entry.get("negative", "")
|
||||
|
||||
# Handle both string and already-parsed list/nested-list formats
|
||||
if isinstance(raw_positive, (list, tuple)):
|
||||
# Already a list/nested structure - pass as JSON string for parse_prompt_input_nested
|
||||
config_pos = parse_prompt_input_nested(json.dumps(raw_positive))
|
||||
elif isinstance(raw_positive, str) and raw_positive.strip():
|
||||
config_pos = parse_prompt_input_nested(raw_positive)
|
||||
else:
|
||||
config_pos = pos_prompts # Fallback to external
|
||||
|
||||
if isinstance(raw_negative, (list, tuple)):
|
||||
config_neg = parse_prompt_input_nested(json.dumps(raw_negative))
|
||||
elif isinstance(raw_negative, str) and raw_negative.strip():
|
||||
config_neg = parse_prompt_input_nested(raw_negative)
|
||||
else:
|
||||
config_neg = neg_prompts # Fallback to external
|
||||
|
||||
# Build prompt pairs for this config entry
|
||||
if len(config_pos) > 1 and len(config_neg) > 1 and len(config_pos) == len(config_neg):
|
||||
entry_prompt_pairs = list(zip(config_pos, config_neg))
|
||||
else:
|
||||
entry_prompt_pairs = list(itertools.product(config_pos, config_neg))
|
||||
|
||||
if entry_prompt_pairs:
|
||||
print(f"[GridTester] Config defines {len(entry_prompt_pairs)} prompt combinations (overriding node inputs)")
|
||||
else:
|
||||
entry_prompt_pairs = default_prompt_pairs
|
||||
|
||||
# Expand wildcards
|
||||
samplers = ALL_SAMPLERS if entry.get("sampler") == "*" else to_list(entry.get("sampler", "euler"))
|
||||
schedulers = ALL_SCHEDULERS if entry.get("scheduler") == "*" else to_list(entry.get("scheduler", "normal"))
|
||||
steps_l = to_list(entry.get("steps", 20))
|
||||
cfgs = to_list(entry.get("cfg", 7.0))
|
||||
clip_skips = to_list(entry.get("clip_skip", 0))
|
||||
|
||||
|
||||
# Expand model folders
|
||||
raw_models = to_list(entry.get("model", "Default"))
|
||||
expanded_models = []
|
||||
@@ -307,24 +344,24 @@ def expand_configs(raw_configs, pos_prompts, neg_prompts, denoise_values, seed,
|
||||
expanded_models.append("Default")
|
||||
else:
|
||||
expanded_models.extend(get_files_from_folder(m, "checkpoints"))
|
||||
|
||||
|
||||
# Expand LoRA stacks (strengths are now in the lora string itself)
|
||||
expanded_loras = expand_lora_stack(entry.get("lora", "None"))
|
||||
|
||||
|
||||
# Get LoRA trigger word omissions (if specified)
|
||||
lora_omit_triggers = entry.get("lora_omit_triggers", [])
|
||||
if not isinstance(lora_omit_triggers, list):
|
||||
lora_omit_triggers = [lora_omit_triggers]
|
||||
|
||||
|
||||
# Get LoRA trigger word append settings (if specified)
|
||||
lora_triggerwords_append_settings = entry.get("lora_triggerwords_append_settings", {})
|
||||
if not isinstance(lora_triggerwords_append_settings, dict):
|
||||
lora_triggerwords_append_settings = {}
|
||||
|
||||
|
||||
# Build all combinations
|
||||
base_combos = []
|
||||
for combo in itertools.product(samplers, schedulers, steps_l, cfgs, clip_skips, expanded_loras,
|
||||
denoise_values, prompt_pairs, expanded_models):
|
||||
for combo in itertools.product(samplers, schedulers, steps_l, cfgs, clip_skips, expanded_loras,
|
||||
denoise_values, entry_prompt_pairs, expanded_models):
|
||||
base_combos.append({
|
||||
"sampler": combo[0],
|
||||
"scheduler": combo[1],
|
||||
@@ -340,7 +377,7 @@ def expand_configs(raw_configs, pos_prompts, neg_prompts, denoise_values, seed,
|
||||
"lora_omit_triggers": lora_omit_triggers,
|
||||
"lora_triggerwords_append_settings": lora_triggerwords_append_settings
|
||||
})
|
||||
|
||||
|
||||
# Apply base seed and extra seeds
|
||||
for c in base_combos:
|
||||
expanded.append(c)
|
||||
@@ -348,7 +385,7 @@ def expand_configs(raw_configs, pos_prompts, neg_prompts, denoise_values, seed,
|
||||
new_c = c.copy()
|
||||
new_c["seed"] = extra_seed
|
||||
expanded.append(new_c)
|
||||
|
||||
|
||||
return expanded
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ import {
|
||||
parseLoraString,
|
||||
buildLoraString,
|
||||
getIterationCount,
|
||||
convertStateToConfigs
|
||||
convertStateToConfigs,
|
||||
countPromptCombinations,
|
||||
expandPromptPreview
|
||||
} from '/ultimate_config_sampler/js/conf_builder/conf-builder-utilities.js';
|
||||
|
||||
import {
|
||||
@@ -1254,6 +1256,494 @@ export async function showTriggerLookupModal(node, arrayIdx) {
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// --- PROMPT BUILDER COMPONENTS ---
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Creates a reusable prompt group editor.
|
||||
* Supports visual chip/tag mode and raw JSON mode.
|
||||
* Shows live preview of Cartesian product combinations.
|
||||
*
|
||||
* @param {Array} groups - Nested array of prompt groups: [["a", "b"], ["c", "d"]]
|
||||
* @param {Function} onChange - Callback when groups change: (newGroups) => void
|
||||
* @param {string} label - Label for this editor (e.g. "Positive Prompt" or "Negative Prompt")
|
||||
* @param {string} borderColor - CSS color for left border accent
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
function createPromptGroupEditor(groups, onChange, label, borderColor = "#0066cc") {
|
||||
const container = document.createElement("div");
|
||||
container.style.cssText = `display: flex; flex-direction: column; gap: 8px; width: 100%;`;
|
||||
|
||||
// Track mode state locally (visual vs raw)
|
||||
let isRawMode = false;
|
||||
|
||||
// --- HEADER ---
|
||||
const header = document.createElement("div");
|
||||
header.style.cssText = "display: flex; justify-content: space-between; align-items: center;";
|
||||
|
||||
const titleEl = document.createElement("div");
|
||||
titleEl.style.cssText = `font-size: 12px; font-weight: bold; color: ${borderColor};`;
|
||||
titleEl.textContent = label;
|
||||
header.appendChild(titleEl);
|
||||
|
||||
const modeToggleContainer = document.createElement("div");
|
||||
modeToggleContainer.style.cssText = "display: flex; gap: 4px;";
|
||||
|
||||
const visualBtn = document.createElement("button");
|
||||
visualBtn.className = "cb-prompt-mode-toggle active";
|
||||
visualBtn.textContent = "Visual";
|
||||
|
||||
const rawBtn = document.createElement("button");
|
||||
rawBtn.className = "cb-prompt-mode-toggle";
|
||||
rawBtn.textContent = "JSON";
|
||||
|
||||
modeToggleContainer.appendChild(visualBtn);
|
||||
modeToggleContainer.appendChild(rawBtn);
|
||||
header.appendChild(modeToggleContainer);
|
||||
container.appendChild(header);
|
||||
|
||||
// --- VISUAL MODE CONTAINER ---
|
||||
const visualContainer = document.createElement("div");
|
||||
visualContainer.style.cssText = "display: flex; flex-direction: column; gap: 6px;";
|
||||
|
||||
// --- RAW MODE CONTAINER ---
|
||||
const rawContainer = document.createElement("div");
|
||||
rawContainer.style.display = "none";
|
||||
|
||||
const rawTextarea = document.createElement("textarea");
|
||||
rawTextarea.className = "cb-prompt-raw-editor";
|
||||
rawTextarea.placeholder = '[["variation1", "variation2"], ["subject1", "subject2"]]';
|
||||
rawTextarea.value = groups.length > 0 ? JSON.stringify(groups, null, 2) : "";
|
||||
|
||||
rawTextarea.onchange = () => {
|
||||
try {
|
||||
const parsed = JSON.parse(rawTextarea.value);
|
||||
if (Array.isArray(parsed)) {
|
||||
// Normalize: ensure each item is an array
|
||||
const normalized = parsed.map(item =>
|
||||
Array.isArray(item) ? item.map(String) : [String(item)]
|
||||
);
|
||||
onChange(normalized);
|
||||
rawTextarea.style.borderColor = "#00aa44";
|
||||
setTimeout(() => { rawTextarea.style.borderColor = "#3a3a3a"; }, 1000);
|
||||
renderVisualGroups(normalized);
|
||||
renderPreview(normalized);
|
||||
}
|
||||
} catch (e) {
|
||||
rawTextarea.style.borderColor = "#cc3333";
|
||||
}
|
||||
};
|
||||
rawContainer.appendChild(rawTextarea);
|
||||
|
||||
// --- MODE TOGGLE LOGIC ---
|
||||
visualBtn.onclick = () => {
|
||||
isRawMode = false;
|
||||
visualContainer.style.display = "flex";
|
||||
rawContainer.style.display = "none";
|
||||
visualBtn.classList.add("active");
|
||||
rawBtn.classList.remove("active");
|
||||
};
|
||||
rawBtn.onclick = () => {
|
||||
isRawMode = true;
|
||||
visualContainer.style.display = "none";
|
||||
rawContainer.style.display = "block";
|
||||
rawBtn.classList.add("active");
|
||||
visualBtn.classList.remove("active");
|
||||
// Sync raw editor with current groups
|
||||
rawTextarea.value = groups.length > 0 ? JSON.stringify(groups, null, 2) : "";
|
||||
};
|
||||
|
||||
container.appendChild(visualContainer);
|
||||
container.appendChild(rawContainer);
|
||||
|
||||
// --- PREVIEW SECTION ---
|
||||
const previewContainer = document.createElement("div");
|
||||
container.appendChild(previewContainer);
|
||||
|
||||
function renderPreview(currentGroups) {
|
||||
previewContainer.innerHTML = "";
|
||||
const validGroups = (currentGroups || []).filter(g => Array.isArray(g) && g.length > 0);
|
||||
if (validGroups.length === 0) return;
|
||||
|
||||
const count = countPromptCombinations(validGroups);
|
||||
const previews = expandPromptPreview(validGroups, 20);
|
||||
|
||||
const previewDiv = document.createElement("div");
|
||||
previewDiv.className = "cb-prompt-preview";
|
||||
|
||||
const countLabel = document.createElement("div");
|
||||
countLabel.style.cssText = "font-weight: bold; margin-bottom: 4px; color: #00cc88;";
|
||||
countLabel.textContent = `${count} combination${count !== 1 ? 's' : ''}${count > 20 ? ' (showing first 20)' : ''}:`;
|
||||
previewDiv.appendChild(countLabel);
|
||||
|
||||
previews.forEach(preview => {
|
||||
const item = document.createElement("div");
|
||||
item.className = "cb-prompt-preview-item";
|
||||
item.textContent = preview;
|
||||
previewDiv.appendChild(item);
|
||||
});
|
||||
|
||||
previewContainer.appendChild(previewDiv);
|
||||
}
|
||||
|
||||
// --- VISUAL GROUP RENDERING ---
|
||||
function renderVisualGroups(currentGroups) {
|
||||
visualContainer.innerHTML = "";
|
||||
|
||||
(currentGroups || []).forEach((group, groupIdx) => {
|
||||
const groupDiv = document.createElement("div");
|
||||
groupDiv.className = "cb-prompt-group";
|
||||
|
||||
// Group header
|
||||
const groupHeader = document.createElement("div");
|
||||
groupHeader.className = "cb-prompt-group-header";
|
||||
|
||||
const groupLabel = document.createElement("span");
|
||||
groupLabel.textContent = `Group ${groupIdx + 1} (${group.length} variation${group.length !== 1 ? 's' : ''})`;
|
||||
groupHeader.appendChild(groupLabel);
|
||||
|
||||
const groupDeleteBtn = document.createElement("button");
|
||||
groupDeleteBtn.className = "cb-button danger";
|
||||
groupDeleteBtn.style.cssText = "padding: 1px 6px; font-size: 10px;";
|
||||
groupDeleteBtn.textContent = "✖";
|
||||
groupDeleteBtn.title = "Remove this group";
|
||||
groupDeleteBtn.onclick = () => {
|
||||
const newGroups = [...currentGroups];
|
||||
newGroups.splice(groupIdx, 1);
|
||||
onChange(newGroups);
|
||||
renderVisualGroups(newGroups);
|
||||
renderPreview(newGroups);
|
||||
rawTextarea.value = newGroups.length > 0 ? JSON.stringify(newGroups, null, 2) : "";
|
||||
};
|
||||
groupHeader.appendChild(groupDeleteBtn);
|
||||
groupDiv.appendChild(groupHeader);
|
||||
|
||||
// Chips container
|
||||
const chipsDiv = document.createElement("div");
|
||||
chipsDiv.className = "cb-prompt-chips";
|
||||
|
||||
group.forEach((variation, varIdx) => {
|
||||
const chip = document.createElement("span");
|
||||
chip.className = "cb-prompt-chip";
|
||||
|
||||
const chipText = document.createElement("span");
|
||||
chipText.textContent = variation;
|
||||
chipText.title = variation;
|
||||
chip.appendChild(chipText);
|
||||
|
||||
const chipClose = document.createElement("span");
|
||||
chipClose.className = "chip-close";
|
||||
chipClose.textContent = "×";
|
||||
chipClose.onclick = () => {
|
||||
const newGroups = currentGroups.map(g => [...g]);
|
||||
newGroups[groupIdx].splice(varIdx, 1);
|
||||
// Remove group if empty
|
||||
if (newGroups[groupIdx].length === 0) {
|
||||
newGroups.splice(groupIdx, 1);
|
||||
}
|
||||
onChange(newGroups);
|
||||
renderVisualGroups(newGroups);
|
||||
renderPreview(newGroups);
|
||||
rawTextarea.value = newGroups.length > 0 ? JSON.stringify(newGroups, null, 2) : "";
|
||||
};
|
||||
chip.appendChild(chipClose);
|
||||
chipsDiv.appendChild(chip);
|
||||
});
|
||||
|
||||
// Add variation input
|
||||
const addVarBtn = document.createElement("button");
|
||||
addVarBtn.className = "cb-prompt-add-variation";
|
||||
addVarBtn.textContent = "+ variation";
|
||||
addVarBtn.onclick = () => {
|
||||
// Replace button with inline input
|
||||
const inputContainer = document.createElement("div");
|
||||
inputContainer.style.cssText = "display: inline-flex; gap: 2px;";
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.className = "cb-input";
|
||||
input.style.cssText = "width: 180px; padding: 3px 6px; font-size: 12px;";
|
||||
input.placeholder = "Enter prompt text...";
|
||||
|
||||
const confirmBtn = document.createElement("button");
|
||||
confirmBtn.className = "cb-button primary";
|
||||
confirmBtn.style.cssText = "padding: 2px 8px; font-size: 11px;";
|
||||
confirmBtn.textContent = "Add";
|
||||
|
||||
const addVariation = () => {
|
||||
const val = input.value.trim();
|
||||
if (val) {
|
||||
const newGroups = currentGroups.map(g => [...g]);
|
||||
newGroups[groupIdx].push(val);
|
||||
onChange(newGroups);
|
||||
renderVisualGroups(newGroups);
|
||||
renderPreview(newGroups);
|
||||
rawTextarea.value = newGroups.length > 0 ? JSON.stringify(newGroups, null, 2) : "";
|
||||
}
|
||||
};
|
||||
|
||||
input.onkeydown = (e) => {
|
||||
if (e.key === "Enter") addVariation();
|
||||
if (e.key === "Escape") {
|
||||
inputContainer.replaceWith(addVarBtn);
|
||||
}
|
||||
};
|
||||
confirmBtn.onclick = addVariation;
|
||||
|
||||
inputContainer.appendChild(input);
|
||||
inputContainer.appendChild(confirmBtn);
|
||||
addVarBtn.replaceWith(inputContainer);
|
||||
input.focus();
|
||||
};
|
||||
chipsDiv.appendChild(addVarBtn);
|
||||
|
||||
groupDiv.appendChild(chipsDiv);
|
||||
visualContainer.appendChild(groupDiv);
|
||||
});
|
||||
|
||||
// Add Group button
|
||||
const addGroupBtn = document.createElement("button");
|
||||
addGroupBtn.className = "cb-prompt-add-group-btn";
|
||||
addGroupBtn.textContent = "➕ Add Prompt Group";
|
||||
addGroupBtn.onclick = () => {
|
||||
// Create input for the first variation of the new group
|
||||
const inputContainer = document.createElement("div");
|
||||
inputContainer.style.cssText = "display: flex; gap: 4px; margin-top: 4px;";
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.className = "cb-input";
|
||||
input.style.cssText = "flex: 1; padding: 6px 8px; font-size: 12px;";
|
||||
input.placeholder = "Enter first variation for new group...";
|
||||
|
||||
const confirmBtn = document.createElement("button");
|
||||
confirmBtn.className = "cb-button primary";
|
||||
confirmBtn.style.padding = "4px 12px";
|
||||
confirmBtn.textContent = "Add Group";
|
||||
|
||||
const addGroup = () => {
|
||||
const val = input.value.trim();
|
||||
if (val) {
|
||||
const newGroups = [...(currentGroups || []), [val]];
|
||||
onChange(newGroups);
|
||||
renderVisualGroups(newGroups);
|
||||
renderPreview(newGroups);
|
||||
rawTextarea.value = newGroups.length > 0 ? JSON.stringify(newGroups, null, 2) : "";
|
||||
}
|
||||
};
|
||||
|
||||
input.onkeydown = (e) => {
|
||||
if (e.key === "Enter") addGroup();
|
||||
if (e.key === "Escape") {
|
||||
inputContainer.replaceWith(addGroupBtn);
|
||||
}
|
||||
};
|
||||
confirmBtn.onclick = addGroup;
|
||||
|
||||
inputContainer.appendChild(input);
|
||||
inputContainer.appendChild(confirmBtn);
|
||||
addGroupBtn.replaceWith(inputContainer);
|
||||
input.focus();
|
||||
};
|
||||
visualContainer.appendChild(addGroupBtn);
|
||||
}
|
||||
|
||||
// Initial render
|
||||
renderVisualGroups(groups);
|
||||
renderPreview(groups);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
// --- GLOBAL PROMPTS SECTION ---
|
||||
|
||||
export function renderGlobalPromptsSection(node, container) {
|
||||
const isCollapsed = node.uiState.globalPromptsSectionCollapsed || false;
|
||||
|
||||
const section = document.createElement("div");
|
||||
section.className = "cb-section full-width";
|
||||
|
||||
// Header (collapsible)
|
||||
const header = document.createElement("div");
|
||||
header.className = "cb-section-toggle";
|
||||
header.style.cssText = "display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; margin-bottom: 10px;";
|
||||
|
||||
const titleSpan = document.createElement("div");
|
||||
titleSpan.className = "cb-section-title";
|
||||
titleSpan.style.cssText = "margin-bottom: 0; border-bottom: none; padding-bottom: 0; color: #00aa44;";
|
||||
titleSpan.textContent = "📝 Global Prompts (Override Node Inputs)";
|
||||
header.appendChild(titleSpan);
|
||||
|
||||
const arrowSpan = document.createElement("span");
|
||||
arrowSpan.textContent = isCollapsed ? "▶" : "▼";
|
||||
arrowSpan.style.cssText = "color: #00aa44; font-size: 12px;";
|
||||
header.appendChild(arrowSpan);
|
||||
|
||||
section.appendChild(header);
|
||||
|
||||
// Content container
|
||||
const contentDiv = document.createElement("div");
|
||||
contentDiv.style.display = isCollapsed ? "none" : "flex";
|
||||
contentDiv.style.flexDirection = "column";
|
||||
contentDiv.style.gap = "12px";
|
||||
|
||||
header.onclick = () => {
|
||||
const isNowCollapsed = contentDiv.style.display !== "none";
|
||||
contentDiv.style.display = isNowCollapsed ? "none" : "flex";
|
||||
arrowSpan.textContent = isNowCollapsed ? "▶" : "▼";
|
||||
node.uiState.globalPromptsSectionCollapsed = isNowCollapsed;
|
||||
};
|
||||
|
||||
// Info text
|
||||
const info = document.createElement("div");
|
||||
info.style.cssText = "font-size: 11px; color: #888; font-style: italic;";
|
||||
info.textContent = "These prompts override the sampler node's positive/negative text inputs for ALL config arrays. Per-config prompts (below) can further override these.";
|
||||
contentDiv.appendChild(info);
|
||||
|
||||
// Positive Prompt Editor
|
||||
const positiveSection = document.createElement("div");
|
||||
positiveSection.className = "cb-prompt-section global";
|
||||
|
||||
const positiveEditor = createPromptGroupEditor(
|
||||
node.state.global_positive_groups || [],
|
||||
(newGroups) => {
|
||||
node.state.global_positive_groups = newGroups;
|
||||
node.saveState();
|
||||
},
|
||||
"✅ Positive Prompt Groups",
|
||||
"#00aa44"
|
||||
);
|
||||
positiveSection.appendChild(positiveEditor);
|
||||
contentDiv.appendChild(positiveSection);
|
||||
|
||||
// Negative Prompt (simple text area, not nested groups)
|
||||
const negativeSection = document.createElement("div");
|
||||
negativeSection.className = "cb-prompt-section global";
|
||||
|
||||
const negLabel = document.createElement("div");
|
||||
negLabel.style.cssText = "font-size: 12px; font-weight: bold; color: #cc4444; margin-bottom: 6px;";
|
||||
negLabel.textContent = "❌ Negative Prompt";
|
||||
negativeSection.appendChild(negLabel);
|
||||
|
||||
const negInput = document.createElement("textarea");
|
||||
negInput.className = "cb-prompt-raw-editor";
|
||||
negInput.style.minHeight = "50px";
|
||||
negInput.placeholder = "bad quality, worst quality, lowres";
|
||||
negInput.value = node.state.global_negative || "";
|
||||
negInput.onchange = () => {
|
||||
node.state.global_negative = negInput.value;
|
||||
node.saveState();
|
||||
};
|
||||
negativeSection.appendChild(negInput);
|
||||
contentDiv.appendChild(negativeSection);
|
||||
|
||||
section.appendChild(contentDiv);
|
||||
container.appendChild(section);
|
||||
}
|
||||
|
||||
// --- PER-CONFIG PROMPTS SECTION ---
|
||||
|
||||
export function renderConfigPromptsSection(node, div, configArray, arrayIdx) {
|
||||
const uid = `prompts_${arrayIdx}`;
|
||||
const isCollapsed = node.uiState.promptsSectionCollapsed[uid] !== false; // Default collapsed
|
||||
|
||||
const section = document.createElement("div");
|
||||
section.style.cssText = "width: 100%; margin-top: 10px; padding-top: 10px; border-top: 1px dashed #444;";
|
||||
|
||||
// Header
|
||||
const header = document.createElement("div");
|
||||
header.className = "cb-section-toggle";
|
||||
header.style.cssText = "padding: 8px; background: #3a3a3a; border-radius: 4px; margin-bottom: 8px; font-weight: bold; color: #9966cc;";
|
||||
|
||||
const promptCount = configArray.use_custom_prompts && configArray.positive_prompt_groups
|
||||
? countPromptCombinations(configArray.positive_prompt_groups)
|
||||
: 0;
|
||||
|
||||
const titleSpan = document.createElement("span");
|
||||
titleSpan.textContent = `Prompts${promptCount > 0 ? ` (${promptCount} combinations)` : ''}`;
|
||||
header.appendChild(titleSpan);
|
||||
|
||||
const arrowSpan = document.createElement("span");
|
||||
arrowSpan.textContent = isCollapsed ? "▶" : "▼";
|
||||
header.appendChild(arrowSpan);
|
||||
|
||||
section.appendChild(header);
|
||||
|
||||
// Content
|
||||
const contentDiv = document.createElement("div");
|
||||
contentDiv.style.display = isCollapsed ? "none" : "block";
|
||||
|
||||
header.onclick = () => {
|
||||
const isNowCollapsed = contentDiv.style.display !== "none";
|
||||
contentDiv.style.display = isNowCollapsed ? "none" : "block";
|
||||
arrowSpan.textContent = isNowCollapsed ? "▶" : "▼";
|
||||
node.uiState.promptsSectionCollapsed[uid] = isNowCollapsed;
|
||||
};
|
||||
|
||||
// Toggle: Use Custom Prompts
|
||||
const toggleLabel = document.createElement("label");
|
||||
toggleLabel.style.cssText = "display: flex; align-items: center; gap: 8px; cursor: pointer; margin-bottom: 10px; font-size: 13px;";
|
||||
|
||||
const toggleCheck = document.createElement("input");
|
||||
toggleCheck.type = "checkbox";
|
||||
toggleCheck.checked = configArray.use_custom_prompts || false;
|
||||
toggleCheck.onchange = () => {
|
||||
node.state.config_arrays[arrayIdx].use_custom_prompts = toggleCheck.checked;
|
||||
node.saveState();
|
||||
node.renderUI();
|
||||
};
|
||||
toggleLabel.appendChild(toggleCheck);
|
||||
toggleLabel.appendChild(document.createTextNode("Use Custom Prompts (Override Global/Node)"));
|
||||
contentDiv.appendChild(toggleLabel);
|
||||
|
||||
if (configArray.use_custom_prompts) {
|
||||
// Positive Prompt Editor
|
||||
const positiveSection = document.createElement("div");
|
||||
positiveSection.className = "cb-prompt-section per-config";
|
||||
|
||||
const positiveEditor = createPromptGroupEditor(
|
||||
configArray.positive_prompt_groups || [],
|
||||
(newGroups) => {
|
||||
node.state.config_arrays[arrayIdx].positive_prompt_groups = newGroups;
|
||||
node.saveState();
|
||||
},
|
||||
"✅ Positive Prompt Groups",
|
||||
"#9966cc"
|
||||
);
|
||||
positiveSection.appendChild(positiveEditor);
|
||||
contentDiv.appendChild(positiveSection);
|
||||
|
||||
// Negative Prompt (simple text)
|
||||
const negativeSection = document.createElement("div");
|
||||
negativeSection.className = "cb-prompt-section per-config";
|
||||
negativeSection.style.marginTop = "8px";
|
||||
|
||||
const negLabel = document.createElement("div");
|
||||
negLabel.style.cssText = "font-size: 12px; font-weight: bold; color: #cc4444; margin-bottom: 6px;";
|
||||
negLabel.textContent = "❌ Negative Prompt";
|
||||
negativeSection.appendChild(negLabel);
|
||||
|
||||
const negInput = document.createElement("textarea");
|
||||
negInput.className = "cb-prompt-raw-editor";
|
||||
negInput.style.minHeight = "50px";
|
||||
negInput.placeholder = "bad quality, worst quality, lowres";
|
||||
negInput.value = configArray.negative_prompt || "";
|
||||
negInput.onchange = () => {
|
||||
node.state.config_arrays[arrayIdx].negative_prompt = negInput.value;
|
||||
node.saveState();
|
||||
};
|
||||
negativeSection.appendChild(negInput);
|
||||
contentDiv.appendChild(negativeSection);
|
||||
} else {
|
||||
const infoText = document.createElement("div");
|
||||
infoText.style.cssText = "font-size: 11px; color: #666; font-style: italic; padding: 4px;";
|
||||
infoText.textContent = "Using global prompts (or node inputs if no global prompts defined).";
|
||||
contentDiv.appendChild(infoText);
|
||||
}
|
||||
|
||||
section.appendChild(contentDiv);
|
||||
div.appendChild(section);
|
||||
}
|
||||
|
||||
// --- MAIN RENDER UI FUNCTION ---
|
||||
|
||||
export function renderPreviewSection(container) {
|
||||
@@ -1288,6 +1778,9 @@ export async function renderUI(node, availableLoras, availableModels, loraFolder
|
||||
renderConfigSection(node, topRow, availableConfigs);
|
||||
root.appendChild(topRow);
|
||||
|
||||
// Global Prompts Section (between session/config management and config arrays)
|
||||
renderGlobalPromptsSection(node, root);
|
||||
|
||||
const configSection = document.createElement("div");
|
||||
configSection.className = "cb-section full-width";
|
||||
configSection.innerHTML = '<div class="cb-section-title">⚙️ Config Arrays</div>';
|
||||
@@ -1308,7 +1801,10 @@ export async function renderUI(node, availableLoras, availableModels, loraFolder
|
||||
loras: ["None"],
|
||||
lora_omit_triggers: [],
|
||||
lora_triggerwords_append_settings: {},
|
||||
combine: false
|
||||
combine: false,
|
||||
positive_prompt_groups: [],
|
||||
negative_prompt: "",
|
||||
use_custom_prompts: false
|
||||
});
|
||||
node.saveState();
|
||||
node.renderUI();
|
||||
@@ -1321,6 +1817,7 @@ export async function renderUI(node, availableLoras, availableModels, loraFolder
|
||||
|
||||
node.state.config_arrays.forEach((configArray, arrayIdx) => {
|
||||
const arrayElement = createConfigArrayElement(node, configArray, arrayIdx);
|
||||
renderConfigPromptsSection(node, arrayElement, configArray, arrayIdx);
|
||||
renderModelsSection(node, arrayElement, configArray, arrayIdx, availableModels, modelFolders);
|
||||
renderLorasSection(node, arrayElement, configArray, arrayIdx, availableLoras, loraFolders);
|
||||
arraysContainer.appendChild(arrayElement);
|
||||
|
||||
@@ -241,7 +241,155 @@ export function getStyles() {
|
||||
width: 98%; /* Use more screen space on mobile */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* --- Prompt Builder Styles --- */
|
||||
.cb-prompt-section {
|
||||
background: #252525;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.cb-prompt-section.global {
|
||||
border-left: 3px solid #00aa44;
|
||||
}
|
||||
.cb-prompt-section.per-config {
|
||||
border-left: 3px solid #9966cc;
|
||||
}
|
||||
.cb-prompt-group {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #3a3a3a;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.cb-prompt-group-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
}
|
||||
.cb-prompt-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
min-height: 28px;
|
||||
align-items: center;
|
||||
}
|
||||
.cb-prompt-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: #0066cc33;
|
||||
border: 1px solid #0066cc66;
|
||||
color: #88ccff;
|
||||
border-radius: 12px;
|
||||
padding: 3px 8px;
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
gap: 4px;
|
||||
max-width: 300px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.cb-prompt-chip .chip-close {
|
||||
cursor: pointer;
|
||||
color: #ff8888;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.cb-prompt-chip .chip-close:hover {
|
||||
color: #ff4444;
|
||||
}
|
||||
.cb-prompt-preview {
|
||||
background: #0a0a0a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
margin-top: 8px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
font-family: monospace;
|
||||
font-size: 11px;
|
||||
color: #00cc88;
|
||||
}
|
||||
.cb-prompt-preview-item {
|
||||
padding: 2px 0;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
}
|
||||
.cb-prompt-preview-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.cb-prompt-raw-editor {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
background: #0a0a0a;
|
||||
border: 1px solid #3a3a3a;
|
||||
color: #0cc;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
resize: vertical;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.cb-prompt-raw-editor:focus {
|
||||
outline: none;
|
||||
border-color: #0066cc;
|
||||
}
|
||||
.cb-prompt-mode-toggle {
|
||||
background: transparent;
|
||||
border: 1px solid #555;
|
||||
color: #aaa;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 10px;
|
||||
}
|
||||
.cb-prompt-mode-toggle:hover {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
}
|
||||
.cb-prompt-mode-toggle.active {
|
||||
background: #0066cc33;
|
||||
border-color: #0066cc;
|
||||
color: #88ccff;
|
||||
}
|
||||
.cb-prompt-add-group-btn {
|
||||
width: 100%;
|
||||
border: 1px dashed #555;
|
||||
background: rgba(0,102,204,0.1);
|
||||
color: #88ccff;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.cb-prompt-add-group-btn:hover {
|
||||
background: rgba(0,102,204,0.2);
|
||||
border-color: #0066cc;
|
||||
}
|
||||
.cb-prompt-add-variation {
|
||||
background: transparent;
|
||||
border: 1px dashed #444;
|
||||
color: #888;
|
||||
padding: 3px 8px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 11px;
|
||||
}
|
||||
.cb-prompt-add-variation:hover {
|
||||
background: #333;
|
||||
color: #ccc;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
@@ -175,6 +175,48 @@ export function buildLoraString(name, modelStr, clipStr) {
|
||||
return `${norm}:${modelStr.toFixed(2)}:${clipStr.toFixed(2)}`;
|
||||
}
|
||||
|
||||
// --- PROMPT HELPER FUNCTIONS ---
|
||||
|
||||
/**
|
||||
* Count the number of Cartesian product combinations from nested prompt groups.
|
||||
* Each group is an array of variations. The total is the product of all group sizes.
|
||||
* Example: [["a", "b"], ["c"]] = 2 * 1 = 2 combinations
|
||||
*/
|
||||
export function countPromptCombinations(groups) {
|
||||
if (!groups || !Array.isArray(groups) || groups.length === 0) return 1;
|
||||
// Filter out empty groups
|
||||
const validGroups = groups.filter(g => Array.isArray(g) && g.length > 0);
|
||||
if (validGroups.length === 0) return 1;
|
||||
return validGroups.reduce((total, group) => total * group.length, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate preview of expanded prompt combinations from nested groups.
|
||||
* Returns array of strings, capped at `limit` entries.
|
||||
* Example: [["a", "b"], ["1", "2"]] -> ["a, 1", "a, 2", "b, 1", "b, 2"]
|
||||
*/
|
||||
export function expandPromptPreview(groups, limit = 20) {
|
||||
if (!groups || !Array.isArray(groups) || groups.length === 0) return [];
|
||||
const validGroups = groups.filter(g => Array.isArray(g) && g.length > 0);
|
||||
if (validGroups.length === 0) return [];
|
||||
|
||||
// Iterative Cartesian product with early cutoff
|
||||
let combinations = [[]];
|
||||
for (const group of validGroups) {
|
||||
const newCombinations = [];
|
||||
for (const existing of combinations) {
|
||||
for (const item of group) {
|
||||
newCombinations.push([...existing, item]);
|
||||
if (newCombinations.length > limit) {
|
||||
return newCombinations.slice(0, limit).map(combo => combo.join(", "));
|
||||
}
|
||||
}
|
||||
}
|
||||
combinations = newCombinations;
|
||||
}
|
||||
return combinations.map(combo => combo.join(", "));
|
||||
}
|
||||
|
||||
// --- ITERATION COUNT CALCULATION ---
|
||||
|
||||
export function getIterationCount(configArray) {
|
||||
@@ -238,7 +280,13 @@ export function getIterationCount(configArray) {
|
||||
}
|
||||
if (l_count === 0) l_count = 1;
|
||||
|
||||
return m_count * l_count * s_count * sch_count * st_count * c_count;
|
||||
// 4. 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;
|
||||
}
|
||||
|
||||
// --- CONFIG CONVERSION ---
|
||||
@@ -247,10 +295,14 @@ export function convertStateToConfigs(state) {
|
||||
const configs = [];
|
||||
const split = (str) => str.split(",").map(s => s.trim()).filter(s => s);
|
||||
|
||||
// Global prompts from state (used when per-config prompts not set)
|
||||
const globalPositiveGroups = state.global_positive_groups || [];
|
||||
const globalNegative = state.global_negative || "";
|
||||
|
||||
state.config_arrays.forEach(configArray => {
|
||||
// Process LoRAs - FIXED VERSION
|
||||
let loras = configArray.loras.filter(l => l && l !== "None");
|
||||
|
||||
|
||||
// Convert loras array to proper format
|
||||
let loraValue;
|
||||
if (loras.length === 0) {
|
||||
@@ -302,6 +354,20 @@ export function convertStateToConfigs(state) {
|
||||
config.lora_strength_lock = configArray.lora_strength_lock;
|
||||
}
|
||||
|
||||
// ==== PROMPT HANDLING ====
|
||||
// Priority: per-config > global > omit (node inputs used as fallback)
|
||||
if (configArray.use_custom_prompts && configArray.positive_prompt_groups && configArray.positive_prompt_groups.length > 0) {
|
||||
config.positive = configArray.positive_prompt_groups;
|
||||
if (configArray.negative_prompt) {
|
||||
config.negative = configArray.negative_prompt;
|
||||
}
|
||||
} else if (globalPositiveGroups.length > 0) {
|
||||
config.positive = globalPositiveGroups;
|
||||
if (globalNegative) {
|
||||
config.negative = globalNegative;
|
||||
}
|
||||
}
|
||||
|
||||
configs.push(config);
|
||||
});
|
||||
return configs;
|
||||
@@ -321,7 +387,10 @@ export function convertConfigsToConfigArrays(configs) {
|
||||
lora_triggerwords_append_settings: {},
|
||||
lora_bypass_states: {},
|
||||
lora_strength_lock: {},
|
||||
combine: false
|
||||
combine: false,
|
||||
positive_prompt_groups: [],
|
||||
negative_prompt: "",
|
||||
use_custom_prompts: false
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -383,6 +452,37 @@ export function convertConfigsToConfigArrays(configs) {
|
||||
strengthLock = { ...config.lora_strength_lock };
|
||||
}
|
||||
|
||||
// Parse prompt fields from loaded config
|
||||
let positivePromptGroups = [];
|
||||
let negativePrompt = "";
|
||||
let useCustomPrompts = false;
|
||||
|
||||
if (config.positive) {
|
||||
// Config has per-config prompts
|
||||
useCustomPrompts = true;
|
||||
if (Array.isArray(config.positive)) {
|
||||
// Could be nested array [["a", "b"], ["c"]] or simple array ["a", "b"]
|
||||
if (config.positive.length > 0 && Array.isArray(config.positive[0])) {
|
||||
// Nested array format - use as-is
|
||||
positivePromptGroups = config.positive;
|
||||
} else {
|
||||
// Simple array - wrap as single group
|
||||
positivePromptGroups = [config.positive];
|
||||
}
|
||||
} else if (typeof config.positive === 'string' && config.positive.trim()) {
|
||||
// Plain string - wrap as single variation in single group
|
||||
positivePromptGroups = [[config.positive]];
|
||||
}
|
||||
}
|
||||
|
||||
if (config.negative) {
|
||||
if (typeof config.negative === 'string') {
|
||||
negativePrompt = config.negative;
|
||||
} else if (Array.isArray(config.negative)) {
|
||||
negativePrompt = JSON.stringify(config.negative);
|
||||
}
|
||||
}
|
||||
|
||||
configArrays.push({
|
||||
name: `Loaded Config ${idx + 1}`,
|
||||
samplers: toString(config.sampler || "euler"),
|
||||
@@ -395,7 +495,10 @@ export function convertConfigsToConfigArrays(configs) {
|
||||
lora_triggerwords_append_settings: triggerPlacements,
|
||||
lora_bypass_states: bypassStates,
|
||||
lora_strength_lock: strengthLock,
|
||||
combine: hasCombined
|
||||
combine: hasCombined,
|
||||
positive_prompt_groups: positivePromptGroups,
|
||||
negative_prompt: negativePrompt,
|
||||
use_custom_prompts: useCustomPrompts
|
||||
});
|
||||
});
|
||||
|
||||
@@ -411,6 +514,9 @@ export function convertConfigsToConfigArrays(configs) {
|
||||
lora_triggerwords_append_settings: {},
|
||||
lora_bypass_states: {},
|
||||
lora_strength_lock: {},
|
||||
combine: false
|
||||
combine: false,
|
||||
positive_prompt_groups: [],
|
||||
negative_prompt: "",
|
||||
use_custom_prompts: false
|
||||
}];
|
||||
}
|
||||
@@ -66,7 +66,9 @@ app.registerExtension({
|
||||
modelsSectionCollapsed: {},
|
||||
lorasSectionCollapsed: {},
|
||||
modelsCollapsed: {},
|
||||
lorasCollapsed: {}
|
||||
lorasCollapsed: {},
|
||||
promptsSectionCollapsed: {},
|
||||
globalPromptsSectionCollapsed: false
|
||||
};
|
||||
|
||||
// Initialize default state structure
|
||||
@@ -75,6 +77,8 @@ app.registerExtension({
|
||||
config_name: "default_config",
|
||||
auto_save: false,
|
||||
include_none: false,
|
||||
global_positive_groups: [],
|
||||
global_negative: "",
|
||||
config_arrays: [{
|
||||
name: "Config 1",
|
||||
samplers: "euler, dpmpp_2m",
|
||||
@@ -87,7 +91,10 @@ app.registerExtension({
|
||||
lora_triggerwords_append_settings: {},
|
||||
lora_bypass_states: {},
|
||||
lora_strength_lock: {},
|
||||
combine: false
|
||||
combine: false,
|
||||
positive_prompt_groups: [],
|
||||
negative_prompt: "",
|
||||
use_custom_prompts: false
|
||||
}]
|
||||
};
|
||||
|
||||
@@ -248,6 +255,8 @@ app.registerExtension({
|
||||
config_name: "default_config",
|
||||
auto_save: false,
|
||||
include_none: oldState.include_none !== undefined ? oldState.include_none : false,
|
||||
global_positive_groups: [],
|
||||
global_negative: "",
|
||||
config_arrays: arrays.map(arr => ({
|
||||
name: arr.name,
|
||||
samplers: oldState.samplers || "euler",
|
||||
@@ -260,7 +269,10 @@ app.registerExtension({
|
||||
lora_triggerwords_append_settings: {},
|
||||
lora_bypass_states: {},
|
||||
lora_strength_lock: {},
|
||||
combine: arr.combine || false
|
||||
combine: arr.combine || false,
|
||||
positive_prompt_groups: [],
|
||||
negative_prompt: "",
|
||||
use_custom_prompts: false
|
||||
}))
|
||||
};
|
||||
};
|
||||
@@ -281,6 +293,10 @@ app.registerExtension({
|
||||
if (!this.state.config_name) this.state.config_name = "default_config";
|
||||
if (this.state.auto_save === undefined) this.state.auto_save = false;
|
||||
|
||||
// Migration: ensure global prompt fields exist
|
||||
if (!this.state.global_positive_groups) this.state.global_positive_groups = [];
|
||||
if (this.state.global_negative === undefined) this.state.global_negative = "";
|
||||
|
||||
// Migration logic requiring utilities
|
||||
this.state.config_arrays.forEach(arr => {
|
||||
if (arr.model && !arr.models) {
|
||||
@@ -293,12 +309,17 @@ app.registerExtension({
|
||||
const p = utilities.parseLoraString(l);
|
||||
return utilities.buildLoraString(p.name, p.model_str, p.clip_str);
|
||||
}) : ["None"];
|
||||
|
||||
|
||||
// Ensure keys exist
|
||||
if (!arr.lora_omit_triggers) arr.lora_omit_triggers = [];
|
||||
if (!arr.lora_triggerwords_append_settings) arr.lora_triggerwords_append_settings = {};
|
||||
if (!arr.lora_bypass_states) arr.lora_bypass_states = {};
|
||||
if (!arr.lora_strength_lock) arr.lora_strength_lock = {};
|
||||
|
||||
// Migration: ensure prompt fields exist
|
||||
if (!arr.positive_prompt_groups) arr.positive_prompt_groups = [];
|
||||
if (arr.negative_prompt === undefined) arr.negative_prompt = "";
|
||||
if (arr.use_custom_prompts === undefined) arr.use_custom_prompts = false;
|
||||
});
|
||||
} else if (existing.lora_config) {
|
||||
this.state = this.migrateOldFormat(existing);
|
||||
|
||||
Reference in New Issue
Block a user