diff --git a/config_builder_node.py b/config_builder_node.py index 5934090..ef1ef16 100644 --- a/config_builder_node.py +++ b/config_builder_node.py @@ -716,6 +716,10 @@ class UltimateConfigBuilder: cooldown_data = state.get("cooldown", {}) if cooldown_data and cooldown_data.get("enabled", False): session_settings["cooldown"] = cooldown_data + # Start At Job # (skip to a specific job number) + start_at_job = state.get("start_at_job", 0) + if start_at_job and int(start_at_job) > 0: + session_settings["start_at_job"] = int(start_at_job) if session_settings: output_obj["_session_settings"] = session_settings diff --git a/generation_orchestrator.py b/generation_orchestrator.py index da18ae0..77f74a0 100644 --- a/generation_orchestrator.py +++ b/generation_orchestrator.py @@ -775,6 +775,9 @@ def run_generation_loop( total_generated = 0 gen_index_offset = len(existing_data.get("items", [])) # Sequential index for deterministic sort ordering skipped_count = 0 + start_at_job = int(session_settings.get("start_at_job", 0)) if session_settings else 0 + if start_at_job > 0: + print(f"[GridTester] ⏭️ Start At Job #{start_at_job} — skipping earlier jobs") job_durations = [] deferred_upscale_queue = [] eta_start_time = time.time() @@ -1041,6 +1044,11 @@ def run_generation_loop( ) actual_negative_prompt = conf["negative"] + # ==== START AT JOB # (skip earlier jobs) ==== + if start_at_job > 0 and current_job < start_at_job: + skipped_count += 1 + continue + # ==== CHECK EXISTING MATCH ==== match_index = check_if_job_completed( existing_data["items"], diff --git a/web/conf_builder/conf-builder-config-management.js b/web/conf_builder/conf-builder-config-management.js index bb24520..8985bd3 100644 --- a/web/conf_builder/conf-builder-config-management.js +++ b/web/conf_builder/conf-builder-config-management.js @@ -5218,6 +5218,59 @@ export function renderCooldownSection(node, container) { // --- MAIN RENDER UI FUNCTION --- +// ============================================================================ +// RUN SETTINGS (Start At Job #, etc.) +// ============================================================================ +export function renderRunSettingsSection(node, container) { + if (node.state.start_at_job === undefined) node.state.start_at_job = 0; + + const section = document.createElement("div"); + section.className = "cb-section full-width"; + section.id = "cb-sec-runsettings"; + + const header = document.createElement("div"); + header.className = "cb-section-header"; + header.textContent = "▸ Run Settings"; + header.style.cursor = "pointer"; + + const content = document.createElement("div"); + content.className = "cb-section-content"; + content.style.display = "none"; + + header.onclick = () => { + const isOpen = content.style.display !== "none"; + content.style.display = isOpen ? "none" : "block"; + header.textContent = (isOpen ? "▸" : "▾") + " Run Settings"; + }; + + // Start At Job # input + const jobDiv = document.createElement("div"); + jobDiv.style.cssText = "margin-bottom: 8px; display: flex; align-items: center; gap: 8px;"; + const jobLabel = document.createElement("label"); + jobLabel.textContent = "Start At Job #:"; + jobLabel.style.cssText = "font-size: 12px; color: #ccc; white-space: nowrap;"; + const jobInput = document.createElement("input"); + jobInput.type = "number"; + jobInput.min = "0"; + jobInput.value = node.state.start_at_job || 0; + jobInput.style.cssText = "width: 70px; background: #1a1a1a; color: #ccc; border: 1px solid #444; border-radius: 4px; padding: 4px 6px; font-size: 12px;"; + jobInput.onchange = () => { + node.state.start_at_job = parseInt(jobInput.value) || 0; + node.setDirtyCanvas(true); + }; + const jobDesc = document.createElement("div"); + jobDesc.style.cssText = "font-size: 9px; color: #666;"; + jobDesc.textContent = "Skip to this job number (0 = start from beginning). Useful for resuming at a specific point."; + jobDiv.appendChild(jobLabel); + jobDiv.appendChild(jobInput); + content.appendChild(jobDiv); + content.appendChild(jobDesc); + + section.appendChild(header); + section.appendChild(content); + container.appendChild(section); +} + export function renderPreviewSection(container) { const section = document.createElement("div"); section.className = "cb-section full-width"; @@ -5413,6 +5466,7 @@ export async function renderUI(node, availableLoras, modelLists, loraFolders, av // Session-level settings (applies to all configs) renderUpscalingSection(node, mainContent, modelLists); renderCooldownSection(node, mainContent); + renderRunSettingsSection(node, mainContent); // JSON Preview Section renderPreviewSection(mainContent); diff --git a/web/conf_builder/conf-builder-utilities.js b/web/conf_builder/conf-builder-utilities.js index a779560..d743863 100644 --- a/web/conf_builder/conf-builder-utilities.js +++ b/web/conf_builder/conf-builder-utilities.js @@ -717,6 +717,11 @@ export function convertStateToConfigs(state) { sessionSettings.cooldown = { ...state.cooldown }; } + // Start At Job # (skip to a specific job number) + if (state.start_at_job && parseInt(state.start_at_job) > 0) { + sessionSettings.start_at_job = parseInt(state.start_at_job); + } + // Attach session settings to the configs output if any are enabled if (Object.keys(sessionSettings).length > 0) { configs._session_settings = sessionSettings;