feat: add Start At Job # option for custom job resume/skipping

New 'Run Settings' section in Builder UI with a job number input.
When set > 0, the generation loop skips all jobs before that number.
Useful for resuming at a specific point without relying on
auto-detection. Flows through session settings to the orchestrator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Hoku
2026-03-16 16:00:43 -07:00
co-authored by Claude Opus 4.6
parent e1bba3e8f5
commit 9b8f01ea48
4 changed files with 71 additions and 0 deletions
+4
View File
@@ -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
+8
View File
@@ -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"],
@@ -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);
@@ -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;