Files
Jason HokuandClaude Opus 4.7 6f9862f2b7 feat: Revise modal — rename CANCEL→EXIT and keep open after Generate New
Two QoL tweaks:
- CANCEL button text → EXIT (clearer when paired with the new "stays open"
  behavior — there's nothing to "cancel" after queuing).
- Generate New no longer closes the modal. The QUEUED! flash on the button
  is enough confirmation. Users can keep tweaking sampler/cfg/prompt and
  queueing additional revisions of the same image without re-opening.
  Close manually via EXIT or Esc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 12:38:04 -07:00

834 lines
51 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
/* INJECTED CSS */
__CSS_CONTENT__
</style>
</head>
<body>
<!-- COMPACT SINGLE-LINE HEADER -->
<div id="header">
<button class="filter-popup-btn" onclick="toggleFiltersPopup()" title="Open Filters & Info">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
</svg>
</button>
<div style="flex-grow: 1;"></div>
<div class="meta-box" style="overflow: visible;"> <span class="meta-label">Sort</span>
<div class="sort-container" id="sort-container">
<button class="sort-btn" id="sort-main-btn" onclick="toggleSortPopup()">
<span id="sort-primary-label">Oldest</span>
<span style="opacity:0.5; font-size:8px;">▼</span>
</button>
<div id="sort-popup" class="sort-popup-panel" style="display: none;">
<div class="sort-col">
<div class="sort-col-header" style="color:var(--accent);">PRIMARY</div>
<div id="sort-list-primary">
</div>
</div>
<div class="sort-col">
<div class="sort-col-header" style="color:var(--accent-sch);">SECONDARY</div>
<div id="sort-list-secondary">
</div>
</div>
</div>
</div>
</div>
<button class="fs-btn" onclick="loadSession()" title="Reload Session (R)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<polyline points="23 4 23 10 17 10"></polyline>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
</svg>
</button>
<button class="fs-btn" onclick="resetZoom();" title="Center Grid (0)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="22" y1="12" x2="18" y2="12"></line>
<line x1="6" y1="12" x2="2" y2="12"></line>
<line x1="12" y1="6" x2="12" y2="2"></line>
<line x1="12" y1="22" x2="12" y2="18"></line>
</svg>
</button>
<!-- COG WHEEL MENU -->
<div class="cog-menu-container">
<button class="action-btn" onclick="toggleCogMenu()" title="Settings">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
</button>
</div>
<button class="fs-btn" onclick="toggleFullscreen()" title="Toggle Fullscreen">⛶</button>
</div>
<!-- SETTINGS PANEL (Unified Cog + Session) -->
<div id="cog-menu-dropdown" class="cog-menu-panel" style="display: none;">
<div class="cog-panel-header">
<span style="font-size: 13px; font-weight: 700; color: #fff;">Settings</span>
<button class="close-popup-btn" onclick="toggleCogMenu()">&#10005;</button>
</div>
<div class="cog-panel-content">
<!-- Section 1: Session Management -->
<div class="cog-panel-section">
<div class="cog-panel-section-title">SESSION</div>
<input id="session-input" class="session-name-input" type="text" value="__TITLE__"
placeholder="Session name...">
<div class="session-actions" style="margin-top: 6px;">
<button class="session-action-btn save-btn" onclick="saveState()">SAVE</button>
</div>
<div style="margin-top: 8px;">
<span class="meta-label" style="font-size: 10px;">Load Session: <span style="color:#666;">(sorted by most recent)</span></span>
<input type="text" id="session-search" placeholder="Search sessions..."
style="width: 100%; margin-top: 4px; background: #1a1a1a; color: #ccc; border: 1px solid #444; border-radius: 4px; padding: 4px 6px; font-size: 11px; box-sizing: border-box;"
oninput="filterSessionPicker(this.value)">
<div id="session-picker-list" style="max-height: 180px; overflow-y: auto; margin-top: 4px; border: 1px solid #333; border-radius: 4px; background: #1a1a1a;"></div>
</div>
</div>
<!-- Section 2: Grid Display -->
<div class="cog-panel-section">
<div class="cog-panel-section-title">GRID DISPLAY</div>
<div class="cog-menu-item">
<span class="meta-label">Go To Card #: </span>
<input type="text" inputmode="numeric" id="goto-input" min="1" placeholder="#"
title="Jump to image number (press Enter)" style="width: 45px;">
</div>
<div class="cog-menu-item">
<span class="meta-label">Number Of Columns In Grid: </span>
<input type="text" inputmode="numeric" id="col-count" min="0" placeholder="Auto"
title="Set grid columns (0 = Auto)">
</div>
</div>
<!-- Section 3: Label Mode -->
<div class="cog-panel-section">
<div class="cog-panel-section-title">LABEL MODE</div>
<div style="font-size: 9px; color: #666; margin-bottom: 8px;">
Show metadata labels as overlays on card images. Unique-only filters out values shared by every card.
</div>
<div style="display: flex; align-items: center; gap: 6px; margin-bottom: 8px;">
<input type="checkbox" id="label-mode-toggle"
style="width: 14px; height: 14px; cursor: pointer;"
onchange="toggleLabelMode(this.checked)">
<label for="label-mode-toggle"
style="font-size: 11px; color: #ccc; cursor: pointer; user-select: none; font-weight: bold;">
Enable Label Overlays
</label>
</div>
<div id="label-fields-container" style="display: none;">
<div style="display: flex; align-items: center; gap: 6px; margin-bottom: 8px;">
<label for="label-size-slider" style="font-size: 10px; color: #999; white-space: nowrap;">Size:</label>
<input type="range" id="label-size-slider" min="6" max="16" value="9" step="1"
style="flex: 1; height: 14px; cursor: pointer;"
oninput="updateLabelSize(this.value)">
<span id="label-size-value" style="font-size: 10px; color: #999; min-width: 20px;">9px</span>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 4px;">
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-model" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-model" style="font-size: 10px; color: #b43cc8; cursor: pointer; user-select: none;">Model</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-lora" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-lora" style="font-size: 10px; color: #c88c28; cursor: pointer; user-select: none;">LoRA</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-prompt" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-prompt" style="font-size: 10px; color: #28a078; cursor: pointer; user-select: none;">Prompt</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-sampler" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-sampler" style="font-size: 10px; color: #3c82c8; cursor: pointer; user-select: none;">Sampler</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-scheduler" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-scheduler" style="font-size: 10px; color: #3c82c8; cursor: pointer; user-select: none;">Scheduler</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-cfg" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-cfg" style="font-size: 10px; color: #c85050; cursor: pointer; user-select: none;">CFG</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-steps" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-steps" style="font-size: 10px; color: #c85050; cursor: pointer; user-select: none;">Steps</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-seed" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-seed" style="font-size: 10px; color: #888; cursor: pointer; user-select: none;">Seed</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-denoise" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-denoise" style="font-size: 10px; color: #00b4a0; cursor: pointer; user-select: none;">Denoise</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-upscale" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-upscale" style="font-size: 10px; color: #e06090; cursor: pointer; user-select: none;">Upscale (all)</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-upscaleMode" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-upscaleMode" style="font-size: 10px; color: #d06080; cursor: pointer; user-select: none;">Up Mode</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-upscaleModel" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-upscaleModel" style="font-size: 10px; color: #d06080; cursor: pointer; user-select: none;">Up Model</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-upscaleRatio" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-upscaleRatio" style="font-size: 10px; color: #d06080; cursor: pointer; user-select: none;">Up Ratio</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-upscaleDenoise" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-upscaleDenoise" style="font-size: 10px; color: #d06080; cursor: pointer; user-select: none;">Up Denoise</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-upscaleResizeMethod" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-upscaleResizeMethod" style="font-size: 10px; color: #d06080; cursor: pointer; user-select: none;">Resize Method</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-upscaleHiresSteps" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-upscaleHiresSteps" style="font-size: 10px; color: #d06080; cursor: pointer; user-select: none;">HiRes Steps</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-upscaleTiling" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-upscaleTiling" style="font-size: 10px; color: #d06080; cursor: pointer; user-select: none;">Tiling Info</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-hiresPromptBehavior" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-hiresPromptBehavior" style="font-size: 10px; color: #d06080; cursor: pointer; user-select: none;">HiRes Prompt</label>
</div>
<div style="display: flex; align-items: center; gap: 4px;">
<input type="checkbox" id="label-field-hiresPromptText" onchange="updateLabelFields()" style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-field-hiresPromptText" style="font-size: 10px; color: #d06080; cursor: pointer; user-select: none;">HiRes Prompt Text</label>
</div>
</div>
<div style="margin-top: 8px; display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="label-unique-only" checked
onchange="updateLabelFields()"
style="width: 12px; height: 12px; cursor: pointer;">
<label for="label-unique-only"
style="font-size: 10px; color: #999; cursor: pointer; user-select: none;">
Hide values shared by every card (unique-only)
</label>
</div>
</div>
</div>
<!-- Section 4: Export Favorites -->
<div class="cog-panel-section">
<div class="cog-panel-section-title">EXPORT FAVORITES</div>
<button class="session-action-btn"
style="background: #6366f1; width: 100%; font-size: 11px; padding: 10px; margin-bottom: 8px;"
onclick="exportFavoritesAsConfigJSON()" title="Generate a Config Builder JSON from favorited items — paste into the Builder to re-run with different settings">
📋 EXPORT FAVORITES AS CONFIG JSON
</button>
<div style="font-size: 9px; color: #666; margin-bottom: 10px;">
Copies a Config Builder JSON to clipboard with all favorited items' settings (models, LoRAs, prompts, samplers, etc.). Paste into the Config Builder's lora_config widget to re-run with different sizes/settings.
</div>
<button class="session-action-btn"
style="background: #005e52; color: #fff; width: 100%; font-size: 11px; padding: 10px;"
onclick="exportFavorites()" title="Export favorited images to benchmark_favorites folder">
COPY FAVORITES TO BENCHMARK FAVORITES FOLDER
</button>
<div style="margin-top: 8px; display: flex; align-items: center; gap: 6px;">
<input type="checkbox" checked id="pack-metadata-checkbox"
style="width: 14px; height: 14px; cursor: pointer;">
<label for="pack-metadata-checkbox"
style="font-size: 10px; color: #999; cursor: pointer; user-select: none;">
Pack metadata into copied favorites (for CivitAI)
</label>
</div>
<div style="margin-top: 8px; display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="organize-by-prompt-checkbox"
style="width: 14px; height: 14px; cursor: pointer;">
<label for="organize-by-prompt-checkbox"
style="font-size: 10px; color: #999; cursor: pointer; user-select: none;">
Organize into separate folders by prompt (Prompt1, Prompt2, etc.)
</label>
</div>
<div style="margin-top: 8px; display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="organize-by-lora-checkbox"
style="width: 14px; height: 14px; cursor: pointer;">
<label for="organize-by-lora-checkbox"
style="font-size: 10px; color: #999; cursor: pointer; user-select: none;">
Organize into separate folders by lora sets (loras1, loras2, etc.)
</label>
</div>
<div style="margin-top: 8px; display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="export-prompt-txt-checkbox"
style="width: 14px; height: 14px; cursor: pointer;">
<label for="export-prompt-txt-checkbox"
style="font-size: 10px; color: #999; cursor: pointer; user-select: none;">
Export positive prompt as .txt file alongside each image
</label>
</div>
<div style="margin-top: 8px; display: flex; align-items: center; gap: 6px;">
<input type="checkbox" checked id="copy-manifest-checkbox"
style="width: 14px; height: 14px; cursor: pointer;">
<label for="copy-manifest-checkbox"
style="font-size: 10px; color: #999; cursor: pointer; user-select: none;">
Copy cleaned favorites-only manifest into favorites folder
</label>
</div>
<div style="margin-top: 8px; display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="pack-workflow-checkbox"
style="width: 14px; height: 14px; cursor: pointer;">
<label for="pack-workflow-checkbox"
style="font-size: 10px; color: #999; cursor: pointer; user-select: none;">
Pack full ComfyUI workflow into image metadata (drag into ComfyUI to load)
</label>
</div>
<div style="margin-top: 8px; display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="pack-nodes-workflow-checkbox"
style="width: 14px; height: 14px; cursor: pointer;">
<label for="pack-nodes-workflow-checkbox"
style="font-size: 10px; color: #999; cursor: pointer; user-select: none;">
Pack config data as pure nodes workflow into image metadata
</label>
</div>
<div id="export-status" style="margin-top: 8px; font-size: 10px; color: #666; min-height: 16px;"></div>
<button class="session-action-btn"
style="background: #cc2222; width: 100%; font-size: 11px; padding: 10px; margin-top: 12px; border: 2px solid #ff4444;"
onclick="deleteNonFavorites()" title="Permanently delete all non-favorited images and update manifest">
🗑️ DELETE ALL NON FAVORITED ITEMS
</button>
<button class="session-action-btn"
style="background: #7a2200; width: 100%; font-size: 11px; padding: 10px; margin-top: 6px; border: 2px solid #cc4400;"
onclick="deleteRejected()" title="Permanently delete all rejected images and update manifest">
🗑️ DELETE ALL REJECTED ITEMS
</button>
<div id="delete-status" style="margin-top: 4px; font-size: 10px; color: #666; min-height: 16px;"></div>
</div>
<!-- Section 5: Manifest Analytics -->
<div class="cog-panel-section">
<div class="cog-panel-section-title">MANIFEST ANALYTICS</div>
<div style="font-size: 9px; color: #666; margin-bottom: 8px;">
Analyze favorited items in the current session manifest.
</div>
<button class="session-action-btn"
style="background: #8b5cf6; width: 100%; font-size: 11px; padding: 10px; margin-bottom: 6px;"
onclick="runManifestAnalysis('lora')" title="Count LoRA usage across favorited items">
LORA USAGE STATS
</button>
<button class="session-action-btn"
style="background: #6366f1; width: 100%; font-size: 11px; padding: 10px; margin-bottom: 6px;"
onclick="runManifestAnalysis('model')" title="Count model/checkpoint usage across favorited items">
MODEL USAGE STATS
</button>
<button class="session-action-btn"
style="background: #005e52; color: #fff; width: 100%; font-size: 11px; padding: 10px; margin-bottom: 6px;"
onclick="runManifestAnalysis('prompt')" title="Count full prompts with favorited vs total">
FULL PROMPT STATS
</button>
<button class="session-action-btn"
style="background: #d0873e; width: 100%; font-size: 11px; padding: 10px; margin-bottom: 6px;"
onclick="runManifestAnalysis('tags')" title="Count individual prompt tags from favorited items">
PROMPT TAG STATS
</button>
<div id="analytics-status" style="margin-top: 4px; font-size: 10px; color: #666; min-height: 16px;"></div>
</div>
<!-- Section 6: Scan External Directory -->
<div class="cog-panel-section">
<div class="cog-panel-section-title">SCAN EXTERNAL DIRECTORY</div>
<div style="font-size: 9px; color: #666; margin-bottom: 8px;">
Load any folder of images into the dashboard. Reads PNG metadata for filtering/sorting.
</div>
<div style="display: flex; gap: 6px; align-items: center;">
<input type="text" id="scan-directory-input"
placeholder="Paste absolute directory path..."
style="flex: 1; background: #000; border: 1px solid #333; color: #fff;
padding: 8px; font-size: 11px; font-family: monospace; border-radius: 3px;"
onkeydown="if(event.key==='Enter')scanDirectory()">
<button class="session-action-btn" id="scan-directory-btn"
style="background: #6366f1; white-space: nowrap; padding: 8px 16px; font-size: 11px; font-weight: bold;"
onclick="scanDirectory()">
SCAN
</button>
</div>
<div id="scan-status" style="margin-top: 6px; font-size: 10px; color: #666; min-height: 16px;"></div>
</div>
<!-- Section 7: Keyboard Shortcuts Reference -->
<div class="cog-panel-section">
<div class="cog-panel-section-title">KEYBOARD SHORTCUTS</div>
<table style="width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 11px;">
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">Space</td>
<td style="padding: 4px 8px; color: #ccc;">Scroll down one row</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">Shift+Space</td>
<td style="padding: 4px 8px; color: #ccc;">Scroll up one row</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">Arrow Keys</td>
<td style="padding: 4px 8px; color: #ccc;">Pan in any direction</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">+ / -</td>
<td style="padding: 4px 8px; color: #ccc;">Zoom in / out</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">0</td>
<td style="padding: 4px 8px; color: #ccc;">Auto-fit first row to viewport</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">F</td>
<td style="padding: 4px 8px; color: #ccc;">Toggle fullscreen</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">R</td>
<td style="padding: 4px 8px; color: #ccc;">Reload session</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">1-9</td>
<td style="padding: 4px 8px; color: #ccc;">Quick favorite Nth visible card</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">Shift+0-9</td>
<td style="padding: 4px 8px; color: #ccc;">Set column count (0 = auto)</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">Ctrl+S</td>
<td style="padding: 4px 8px; color: #ccc;">Save (forwarded to ComfyUI)</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">Escape</td>
<td style="padding: 4px 8px; color: #ccc;">Close modal / lightbox</td>
</tr>
<tr style="border-bottom: 1px solid #333;">
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">Double-click</td>
<td style="padding: 4px 8px; color: #ccc;">Toggle favorite on card</td>
</tr>
<tr>
<td style="padding: 4px 8px; color: #0af; font-family: monospace;">Shift+Click</td>
<td style="padding: 4px 8px; color: #ccc;">Isolate a single filter</td>
</tr>
</table>
</div>
</div>
</div>
<!-- Overlay for settings panel -->
<div id="cog-menu-overlay" class="filters-overlay" onclick="toggleCogMenu()" style="display: none;"></div>
<!-- REAL-TIME ETA BAR -->
<div id="eta-bar" style="display: none; align-items: center; gap: 10px; padding: 4px 12px; background: #1a1a2e; border-bottom: 1px solid #333; font-size: 11px; color: #aaa; position: relative;">
<span style="color: #0af; font-weight: bold;">ETA</span>
<span id="eta-text" style="flex: 1;">--</span>
<div style="position: absolute; bottom: 0; left: 0; height: 2px; width: 100%; background: #222;">
<div id="eta-progress" style="height: 100%; width: 0%; background: linear-gradient(90deg, #0af, #0f6); transition: width 0.5s ease;"></div>
</div>
</div>
<div id="filters-popup" class="filters-popup" style="display: none;">
<div class="filters-popup-header">
<h3>Filters & Info</h3>
<button onclick="resetAllFilters()" title="Reset all filters to show everything"
style="background: #c45000; border: 1px solid #ff7733; color: #fff; font-size: 10px; font-weight: 700; padding: 4px 10px; border-radius: 4px; cursor: pointer; letter-spacing: 0.5px;">
RESET ALL
</button>
<button class="close-popup-btn" onclick="toggleFiltersPopup()">✕</button>
</div>
<div class="filters-popup-content">
<!-- TOP-LEVEL FILTERS SECTION -->
<div class="popup-section"
style="border-bottom: 2px solid #333; padding-bottom: 16px; margin-bottom: 16px;">
<div class="popup-section-title">VISIBILITY</div>
<div style="display: flex; gap: 10px; margin-top: 10px;">
<button id="top-filter-favorites" class="filter-btn top-filter-btn active"
onclick="toggleFavoritesFilter()" title="Toggle visibility of favorited images">
⭐ Favorites
</button>
<button id="top-filter-nonfavorited" class="filter-btn top-filter-btn active"
onclick="toggleNonFavoritedFilter()" title="Toggle visibility of non-favorited images">
◯ Non-Favorited
</button>
<button id="top-filter-rejected" class="filter-btn top-filter-btn" onclick="toggleRejectedFilter()"
title="Toggle visibility of rejected images">
✕ Rejected
</button>
</div>
</div>
<!-- SEARCH FILTER SECTION -->
<div class="popup-section">
<div class="popup-section-title">SEARCH FILTER</div>
<div class="search-filter-container">
<div class="search-filter-row">
<select id="search-filter-type" class="search-filter-select">
<option value="">Select filter type...</option>
<option value="model">Model</option>
<option value="positive">Positive Prompt</option>
<option value="negative">Negative Prompt</option>
<option value="sampler">Sampler</option>
<option value="scheduler">Scheduler</option>
<option value="lora">LoRA</option>
</select>
<input type="text" id="search-filter-input" class="search-filter-input"
placeholder="Enter search term..." onkeypress="if(event.key==='Enter') addSearchFilter()">
<button class="search-filter-add-btn" onclick="addSearchFilter()">ADD</button>
</div>
<div id="active-search-filters" class="active-search-filters"></div>
</div>
</div>
<!-- LOGIC FILTER SECTION -->
<div class="popup-section">
<div class="popup-section-title">LOGIC FILTER <span style="font-size: 9px; color: #555; font-weight: normal;">(numeric comparisons, AND logic)</span></div>
<div class="search-filter-container">
<div class="search-filter-row">
<select id="logic-filter-field" class="search-filter-select">
<option value="">Field...</option>
<option value="lora_strength">LoRA Strength</option>
<option value="cfg">CFG</option>
<option value="steps">Steps</option>
<option value="denoise">Denoise</option>
<option value="width">Width</option>
<option value="height">Height</option>
<option value="seed">Seed</option>
</select>
<select id="logic-filter-op" class="search-filter-select" style="min-width: 52px; flex: 0 0 52px;">
<option value=">">&gt;</option>
<option value="<">&lt;</option>
<option value=">=">&gt;=</option>
<option value="<=">&lt;=</option>
<option value="=">=</option>
<option value="!=">!=</option>
</select>
<input type="number" id="logic-filter-value" class="search-filter-input" step="0.01"
placeholder="Value..." onkeypress="if(event.key==='Enter') addLogicFilter()" style="min-width: 70px;">
<button class="search-filter-add-btn" onclick="addLogicFilter()">ADD</button>
</div>
<div id="active-logic-filters" class="active-search-filters"></div>
</div>
</div>
<!-- QUICK FILTER SECTION -->
<div class="popup-section" id="quick-filter-section">
<div class="popup-section-title">QUICK FILTER <span style="font-size: 9px; color: #555; font-weight: normal;">(faceted: pick type → pick value → drill down)</span></div>
<div class="search-filter-container">
<div class="search-filter-row">
<div id="quick-filter-anchor" style="position:relative; display:inline-block;">
<button class="search-filter-add-btn" id="quick-filter-add-btn"
onclick="_openQuickFilterTypeMenu(this)">
+ Add Quick Filter ▾
</button>
<div id="quick-filter-type-menu" style="display:none; position:absolute; z-index:1000; background:#1a1a1a; border:1px solid #333; border-radius:4px; min-width:150px; box-shadow:0 4px 12px rgba(0,0,0,0.5); top:100%; left:0; margin-top:2px;"></div>
<div id="quick-filter-value-menu" style="display:none; position:absolute; z-index:1001; background:#1a1a1a; border:1px solid #333; border-radius:4px; min-width:180px; max-height:220px; overflow-y:auto; box-shadow:0 4px 12px rgba(0,0,0,0.5); top:100%; left:0; margin-top:2px;"></div>
</div>
</div>
<div id="quick-filter-chips" style="margin-top:6px;"></div>
</div>
</div>
<!-- Meta Info Section -->
<!-- Filters Section -->
<div class="popup-section">
<div class="popup-section-title">FILTERS <span
style="font-size: 9px; color: #555; font-weight: normal;">(Shift+Click to isolate)</span></div>
<div style="margin: 6px 0 2px 0; padding: 4px 0;">
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 11px; color: #aaa;">
<input type="checkbox" id="toggle-triggers" checked
style="cursor: pointer;"
onchange="if(typeof toggleTriggerWords==='function') toggleTriggerWords(this.checked)">
Show Lora Trigger Words in Prompts
</label>
</div>
<br />
<div class="filter-row">
<span class="filter-label">Model</span>
<div id="filter-model" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Positive Prompt</span>
<div id="filter-positive" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Negative Prompt</span>
<div id="filter-negative" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Sampler</span>
<div id="filter-sampler" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Scheduler</span>
<div id="filter-scheduler" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Denoise</span>
<div id="filter-denoise" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">LoRA</span>
<div id="filter-lora" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Size</span>
<div id="filter-size" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Seed</span>
<div id="filter-seed" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Steps</span>
<div id="filter-steps" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">CFG</span>
<div id="filter-cfg" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Upscale</span>
<div id="filter-upscaleMethod" class="filter-buttons-container"></div>
</div>
<div class="filter-row">
<span class="filter-label">Type</span>
<div id="filter-mediaType" class="filter-buttons-container"></div>
</div>
</div>
</div>
</div>
<!-- Overlay for popup -->
<div id="filters-overlay" class="filters-overlay" onclick="toggleFiltersPopup()" style="display: none;"></div>
<!-- Session landing page (shown when no session is loaded) -->
<div id="session-landing" style="display: none; padding: 40px 20px; text-align: center; height: 100%; overflow-y: auto; box-sizing: border-box;">
<h2 style="color: #888; font-size: 18px; margin-bottom: 8px;">Ultimate Grid Dashboard</h2>
<p style="color: #555; font-size: 12px; margin-bottom: 24px;">Select a session to view</p>
<div id="session-cards" style="display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; max-width: 900px; margin: 0 auto; padding-bottom: 40px;"></div>
</div>
<div id="viewport">
<div id="canvas">
<div class="grid" id="grid"></div>
</div>
</div>
<!-- MOBILE NAVIGATION BUTTONS -->
<button id="mobile-nav-up" class="mobile-nav-btn mobile-nav-up" onclick="scrollUpOneRow()" title="Scroll Up">
▲
</button>
<button id="mobile-nav-down" class="mobile-nav-btn mobile-nav-down" onclick="scrollDownOneRow()"
title="Scroll Down">
▼
</button>
<div class="json-container">
<!-- Button row -->
<div style="display: flex; gap: 0;">
<button id="json-btn-good" class="json-view-btn" onclick="viewGoodJSON()"
title="Click to view accepted configs JSON">
View Configs (0)
</button>
<button id="json-btn-favorite" class="json-view-btn" onclick="viewFavoritedJSON()"
title="Click to view favorited configs JSON">
View Favorited (0)
</button>
<button id="json-btn-bad" class="json-view-btn" onclick="viewRejectedJSON()"
title="Click to view rejected configs JSON">
View Rejected (0)
</button>
</div>
<!-- Hidden JSON display areas (shown when button is clicked) -->
<div id="json-bar-good" class="json-bar" style="display: none;" title="ACCEPTED CONFIGS (Click to select)"
onclick="selectJSON('json-bar-good')">[]</div>
<div id="json-bar-favorite" class="json-bar" style="display: none;" title="FAVORITED CONFIGS (Click to select)"
onclick="selectJSON('json-bar-favorite')">[]</div>
<div id="json-bar-bad" class="json-bar" style="display: none;" title="REJECTED CONFIGS (Click to select)"
onclick="selectJSON('json-bar-bad')">[]</div>
</div>
<div id="modal">
<button class="modal-close-x" onclick="closeM()" title="Close (Esc)">&times;</button>
<!-- Lightbox navigation bar -->
<div style="display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: rgba(0,0,0,0.6); border-bottom: 1px solid #333; flex-shrink: 0;">
<button onclick="(function(){ var idx = processedData.findIndex(function(x){return x.id===window.currentModalId;}); if(idx>0) openM(processedData[idx-1].id); })()" title="Previous image (← or Shift+Space)"
style="background: #222; border: 1px solid #444; color: #ccc; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 14px;">◀</button>
<span id="modal-nav-counter" style="font-size: 11px; color: #777; min-width: 60px; text-align: center;"></span>
<button onclick="(function(){ var idx = processedData.findIndex(function(x){return x.id===window.currentModalId;}); if(idx < processedData.length-1) openM(processedData[idx+1].id); })()" title="Next image (→ or Space)"
style="background: #222; border: 1px solid #444; color: #ccc; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 14px;">▶</button>
<span style="font-size: 9px; color: #444; margin-left: 8px;">← → Space navigate &nbsp;|&nbsp; 1-9 favorite</span>
</div>
<div class="modal-main">
<div class="preview"><img id="m-img" src=""></div>
<div class="controls">
<h2 style="margin:0 0 10px 0; color:#fff;">Revise & Generate</h2>
<button class="modal-action-btn copy-comfy-btn" onclick="copyConfigsAsComfyNodes(window.currentModalId)"
style="background: #6366f1; margin-top: 10px;">
📋 Copy Configs To Clipboard As Comfy Nodes
</button>
<div class="field"><label>Model</label><input id="f-model"></div>
<div class="field"><label>Seed</label><input id="f-seed" type="number">
</div>
<div class="field"><label>Positive Prompt</label><textarea id="f-pos" rows="3"
style="background:#000; border:1px solid #333; color:#fff; padding:8px; font-size:12px; font-family:inherit; resize:vertical;"></textarea>
</div>
<div class="field"><label>Negative Prompt</label><textarea id="f-neg" rows="2"
style="background:#000; border:1px solid #333; color:#fff; padding:8px; font-size:12px; font-family:inherit; resize:vertical;"></textarea>
</div>
<div style="border-top:1px solid #333; margin:10px 0; padding-top:10px;">
<div style="font-size:11px; color:#666; margin-bottom:8px;">ADJUST PARAMETERS:</div>
</div>
<div class="field"><label>Sampler</label><input id="f-smp"></div>
<div class="field"><label>Scheduler</label><input id="f-sch"></div>
<div class="field"><label>Steps</label><input id="f-stp" type="number"></div>
<div class="field"><label>CFG</label><input id="f-cfg" type="number" step="0.5"></div>
<div class="field"><label>Denoise</label><input id="f-den" type="number" step="0.05"></div>
<div class="field"><label>LoRA</label><input id="f-lor"></div>
<div style="margin-top:auto; display:flex; gap:10px;">
<button class="action-btn"
style="flex:2; background:var(--accent); color:#000; border:none; height:36px; font-weight:800"
onclick="triggerGen(this)">GENERATE NEW</button>
<button class="action-btn" style="flex:1" onclick="closeM()">EXIT</button>
</div>
</div>
</div>
<div class="reel-wrap">
<div style="font-size:10px; font-weight:bold; color:#666;">RELATED VARIANTS</div>
<div class="reel" id="reel"></div>
</div>
</div>
<script>
// INJECTED DATA
const TARGET_NODE_ID = "__NODE_ID__";
let fullManifest = __JSON_DATA__;
// INJECTED LOGIC
__JS_CONTENT__
</script>
<style>
/* Default behavior: The button is visible */
.gh-hide-on-mobile {
display: block;
}
/* CONDITION 1: Hide if screen is narrower than 768px (Mobile/Tablets) */
@media (max-width: 768px) {
.gh-hide-on-mobile {
display: none !important;
}
}
/* CONDITION 2: Hide if screen is in Portrait Mode (Taller than it is wide) */
@media (orientation: portrait) {
.gh-hide-on-mobile {
display: none !important;
}
}
</style>
<div class="gh-hide-on-mobile"
style="position: absolute; top: 2px; left: 50%; transform: translateX(-50%); z-index: 999;">
<a href="https://github.com/JasonHoku/ComfyUI-Ultimate-Auto-Sampler-Config-Grid-Testing-Suite" target="_blank"
style="
display: inline-flex;
align-items: center;
gap: 6px;
background-color: #313131;
border: 1px solid rgba(27, 31, 35, 0.15);
border-radius: 6px;
color: #e6e6e6;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 600;
line-height: 1;
padding: 3px 10px;
line-height: 20px;
text-decoration: none;
white-space: nowrap;
" onmouseover="this.style.backgroundColor='#747474'" onmouseout="this.style.backgroundColor='#313131'">
<svg height="20" viewBox="0 0 16 16" width="20" style="fill: currentColor; opacity: 0.7;">
<path
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z">
</path>
</svg>
<span>Star</span>
<span id="gh-star-count" style="
background-color: rgba(27, 31, 35, 0.08);
padding: 2px 6px;
border-radius: 10px;
font-size: 12px;
display: none; /* Hidden until loaded */
"></span>
</a>
</div>
<script>
(async function () {
try {
const repo = 'JasonHoku/ComfyUI-Ultimate-Auto-Sampler-Config-Grid-Testing-Suite';
const response = await fetch(`https://api.github.com/repos/${repo}`);
if (response.ok) {
const data = await response.json();
const countSpan = document.getElementById('gh-star-count');
// Format number (e.g. 1500 -> 1.5k) if needed, or just show raw
countSpan.textContent = data.stargazers_count;
countSpan.style.display = 'inline-block'; // Show it now that we have data
}
} catch (e) {
console.log('Could not fetch GitHub stars');
}
})();
</script>
<!-- Upscale Modal -->
<div id="upscale-modal-overlay" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 1000; align-items: center; justify-content: center;" onclick="if(event.target===this)closeUpscaleModal()">
<div id="upscale-modal-content" style="background: #1e1e1e; border: 1px solid #444; border-radius: 8px; padding: 20px; width: 600px; max-width: 92vw; max-height: 90vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,0.5);"></div>
</div>
</body>
</html>