Files
ComfyAssets-ComfyUI_PromptM…/web/gallery.html
T
Vito Sansevero 09bbd22924 feat(legacy): say the old pages will be removed in a future release (#615)
Each old page shows a notice with a link to where its job lives in the
workspace. The pages keep working.
2026-11-02 07:02:36 -08:00

625 lines
39 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PromptManager - Gallery</title>
<script src="js/theme-bridge.js"></script>
<script src="js/html-escape.js"></script>
<script src="js/image-helpers.js"></script>
<script src="js/gallery-settings.js"></script>
<script src="js/api-client.js"></script>
<script src="js/png-metadata.js"></script>
<script src="js/list-state.js"></script>
<script src="js/sse-stream.js"></script>
<script src="js/data-actions.js"></script>
<link rel="stylesheet" href="lib/tailwind/comfyui-theme.css">
<link rel="stylesheet" href="lib/tailwind/styles.css">
<link rel="stylesheet" href="lib/viewerjs/viewer.min.css">
<script src="lib/viewerjs/viewer.min.js"></script>
<style>
/* Column count from Display Settings. An id selector inside the desktop
media query outranks the Tailwind breakpoint classes on #galleryGrid. */
@media (min-width: 1280px) {
#galleryGrid.gallery-columns-custom {
grid-template-columns: var(--gallery-columns, repeat(8, minmax(0, 1fr)));
}
}
</style>
</head>
<body class="bg-pm-primary text-pm min-h-screen">
<div id="legacy-removal-notice" role="note" class="bg-pm-surface border-b border-pm px-4 py-2 text-pm" style="font-size: 13px;">This page will be removed in a future release. Its features are moving to the new workspace. <a href="workspace#/images" style="text-decoration: underline;">Open the workspace</a></div>
<div class="min-h-screen flex flex-col">
<!-- Header -->
<header class="bg-pm-surface border-b border-pm px-4 py-2">
<div class="max-w-7xl mx-auto">
<div class="flex items-center justify-between">
<h1 class="text-sm font-semibold text-pm">PromptManager Gallery</h1>
<div class="hidden sm:flex items-center gap-2">
<div class="w-2 h-2 bg-pm-success rounded-full animate-pulse"></div>
<span class="text-pm-success text-xs font-medium">Online</span>
</div>
</div>
</div>
</header>
<!-- Controls -->
<div class="bg-pm-surface border-b border-pm-subtle px-4 py-2">
<div class="max-w-7xl mx-auto">
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2">
<label class="text-xs text-pm-secondary">Show:</label>
<select id="limitSelector" class="px-2 py-1 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs">
<option value="50">50</option>
<option value="100" selected>100</option>
<option value="200">200</option>
<option value="500">500</option>
</select>
<span class="text-xs text-pm-secondary">images</span>
<span class="text-pm-secondary mx-1">|</span>
<label class="text-xs text-pm-secondary">Size:</label>
<button id="thumbSmBtn" class="px-2 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs rounded-pm-sm transition-colors" title="Small thumbnails">S</button>
<button id="thumbMdBtn" class="px-2 py-1 bg-pm-accent text-pm-accent-fg text-xs rounded-pm-sm transition-colors" title="Medium thumbnails">M</button>
<button id="thumbLgBtn" class="px-2 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs rounded-pm-sm transition-colors" title="Large thumbnails">L</button>
</div>
<div class="flex flex-wrap items-center gap-2">
<button id="refreshBtn"
class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
Refresh
</button>
<button id="gridViewBtn"
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
Grid View
</button>
<button id="listViewBtn"
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
List View
</button>
<a id="settingsBtn" href="workspace#/system/appearance" target="_blank" rel="noopener"
title="Opens System › Appearance in a new tab"
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
Settings
</a>
<button id="pageOptionsBtn" type="button"
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
Page options
</button>
<button id="autoTagBtn"
class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
Auto Tag
</button>
<button id="addPromptBtn"
class="px-3 py-1 bg-pm-success hover:bg-pm-success text-pm text-xs font-medium rounded-pm-sm transition-colors">
Add Prompt
</button>
</div>
</div>
</div>
</div>
<!-- Stats Bar -->
<div class="bg-pm-surface px-4 py-2 border-b border-pm-subtle">
<div class="max-w-7xl mx-auto">
<div class="flex items-center justify-between text-xs">
<span class="text-pm-secondary">
Showing <span id="showingStart">0</span> to <span id="showingEnd">0</span> of <span id="totalImages">0</span> images
</span>
<span class="text-pm-secondary" id="loadingStatus">Loading...</span>
</div>
</div>
</div>
<!-- Gallery Content -->
<div class="flex-1 overflow-hidden">
<div class="max-w-full mx-auto p-3 flex gap-0" style="height: calc(100vh - 140px);">
<!-- Folder Tree Panel — sticky, scrolls independently -->
<div id="folderTreePanel" class="hidden flex-shrink-0 bg-pm-surface border border-pm rounded-pm-sm overflow-y-auto" style="width: 240px; min-width: 160px;">
<div class="px-3 py-2 border-b border-pm-subtle flex items-center justify-between sticky top-0 bg-pm-surface z-10">
<span class="text-sm font-semibold text-pm">Folders</span>
<button id="clearFolderFilterBtn" class="text-sm text-pm-secondary hover:text-pm transition-colors" title="Clear filter">&times;</button>
</div>
<div id="folderTree" class="py-1 text-sm">
<!-- Tree nodes inserted by JS -->
</div>
</div>
<!-- Resize Handle -->
<div id="folderResizeHandle" class="hidden w-1 cursor-col-resize bg-transparent hover:bg-pm-accent/30 flex-shrink-0"></div>
<!-- Main Gallery Area — scrolls independently -->
<div id="galleryScroll" class="flex-1 min-w-0 overflow-y-auto pl-3">
<!-- Loading State -->
<div id="loadingState" class="flex items-center justify-center py-12">
<div class="text-center">
<div class="w-8 h-8 border-4 border-pm-accent-tint border-t-pm-accent rounded-full animate-spin mx-auto mb-3"></div>
<p class="text-pm-secondary text-xs">Loading gallery...</p>
</div>
</div>
<!-- Error State -->
<div id="errorState" class="hidden flex items-center justify-center py-12">
<div class="text-center">
<h3 class="text-sm font-semibold text-pm-error mb-2">Error Loading Gallery</h3>
<p class="text-pm-secondary text-xs mb-3" id="errorMessage">Something went wrong while loading the images.</p>
<button id="retryBtn" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
Try Again
</button>
</div>
</div>
<!-- Gallery Grid -->
<div id="galleryGrid" class="hidden grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-8 gap-2">
<!-- Images will be inserted here -->
</div>
<!-- Gallery List -->
<div id="galleryList" class="hidden space-y-2">
<!-- Images will be inserted here -->
</div>
<!-- Pagination -->
<div id="paginationControls" class="hidden mt-4 flex items-center justify-center gap-2">
<button id="prevPageBtn" class="px-3 py-1 bg-pm-surface hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
Previous
</button>
<div class="flex items-center gap-2">
<span class="text-pm-secondary text-xs">Page</span>
<input id="pageInput" type="number" min="1" class="w-16 px-2 py-1 bg-pm-input text-pm text-xs text-center rounded-pm-sm border border-pm focus:border-pm-accent focus:outline-none" value="1">
<span id="pageInfo" class="text-pm-secondary text-xs">of 1</span>
</div>
<button id="nextPageBtn" class="px-3 py-1 bg-pm-surface hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
Next
</button>
</div>
</div> <!-- /Main Gallery Area -->
</div>
</div>
</div>
<!-- Settings Modal -->
<div id="settingsModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
<div class="bg-pm-surface rounded-pm-md max-w-2xl w-full max-h-[90vh] overflow-hidden mx-4 border border-pm">
<div class="p-3 border-b border-pm">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold text-pm">Page options</h3>
<button id="closeSettingsBtn" class="p-2 rounded-pm-sm hover:bg-pm-hover transition-colors">
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
<div class="p-3 overflow-y-auto max-h-[calc(90vh-200px)] space-y-4">
<!-- Performance Settings -->
<div class="space-y-3">
<h4 class="text-sm font-semibold text-pm">Performance Settings</h4>
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
<!-- Check thumbnails at startup -->
<div class="flex items-center justify-between">
<div>
<label class="text-xs text-pm-secondary">Check thumbnails at startup</label>
<p class="text-xs text-pm-muted">Prompt to generate thumbnails if missing when opening gallery</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="checkThumbnailsAtStartup" class="sr-only peer" checked>
<div class="w-11 h-6 bg-pm-input peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-pm-accent rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-pm after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-pm-accent"></div>
</label>
</div>
<div class="flex items-center justify-between">
<label class="text-xs text-pm-secondary">Lazy Loading</label>
<input type="checkbox" id="lazyLoadingToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
</div>
</div>
</div>
<!-- Display Settings -->
<div class="space-y-3">
<h4 class="text-sm font-semibold text-pm">Display Settings</h4>
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
<div class="flex items-center justify-between">
<label class="text-xs text-pm-secondary">Items per Page</label>
<select id="defaultLimitSelect" class="px-2 py-1 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs">
<option value="50">50</option>
<option value="100" selected>100</option>
<option value="200">200</option>
<option value="500">500</option>
</select>
</div>
<div class="flex items-center justify-between">
<label class="text-xs text-pm-secondary">Grid Columns (Desktop)</label>
<select id="gridColumnsSelect" class="px-2 py-1 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs">
<option value="4">4 Columns</option>
<option value="6">6 Columns</option>
<option value="8" selected>8 Columns</option>
<option value="10">10 Columns</option>
</select>
</div>
<div class="flex items-center justify-between">
<div>
<label class="text-xs text-pm-secondary">Infinite Scrolling</label>
<p class="text-[11px] text-pm-muted">Load the next page as you scroll instead of showing page controls</p>
</div>
<input type="checkbox" id="infiniteScrollToggle" class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
</div>
</div>
</div>
<!-- Advanced Settings -->
<div class="space-y-3">
<h4 class="text-sm font-semibold text-pm">Advanced</h4>
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
<div class="flex items-center justify-between">
<label class="text-xs text-pm-secondary">Debug Mode</label>
<input type="checkbox" id="debugModeToggle" class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
</div>
<div class="flex items-center justify-between">
<label class="text-xs text-pm-secondary">API Timeout (seconds)</label>
<input type="number" id="apiTimeoutInput" value="30" min="5" max="300" class="px-2 py-1 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs w-20">
</div>
</div>
</div>
<p class="text-xs text-pm-secondary">Thumbnails, cache, file listing and duplicates are in System.
<a id="pageOptionsMaintenanceLink" href="workspace#/system/maintenance" target="_blank" rel="noopener"
title="Opens System › Maintenance in a new tab" class="text-pm-accent hover:underline">Maintenance</a>
·
<a id="pageOptionsSourcesLink" href="workspace#/system/sources" target="_blank" rel="noopener"
title="Opens System › Sources &amp; scans in a new tab" class="text-pm-accent hover:underline">Sources &amp; scans</a>
</p>
</div>
<div class="p-3 border-t border-pm flex justify-end gap-2">
<button id="resetSettingsBtn" class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
Reset to Defaults
</button>
<button id="saveSettingsBtn" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
Save Settings
</button>
</div>
</div>
</div>
<!-- Auto Tag Modal -->
<div id="autoTagModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
<div class="bg-pm-surface rounded-pm-md p-3 max-w-lg w-full mx-4 border border-pm">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-semibold text-pm">Auto Tag Images</h3>
<button onclick="hideModal('autoTagModal')" class="p-2 rounded-pm-sm hover:bg-pm-hover transition-colors">
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<!-- Model Selection -->
<div class="mb-3">
<label class="block text-xs font-medium text-pm-secondary mb-2">Model Selection</label>
<div class="space-y-2">
<label class="flex items-center justify-between p-3 bg-pm-input rounded-pm-sm cursor-pointer hover:bg-pm-hover border border-pm">
<div class="flex items-center">
<input type="radio" name="autoTagModel" value="gguf" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm focus:ring-pm-accent">
<div class="ml-3">
<span class="text-pm text-xs font-medium">GGUF Model</span>
<span class="text-pm-secondary text-xs ml-2">(Recommended, ~4.5GB)</span>
</div>
</div>
<div id="ggufModelStatus" class="flex items-center gap-2">
<span class="text-pm-secondary text-xs">Checking...</span>
</div>
</label>
<label class="flex items-center justify-between p-3 bg-pm-input rounded-pm-sm cursor-pointer hover:bg-pm-hover border border-pm">
<div class="flex items-center">
<input type="radio" name="autoTagModel" value="hf" class="w-4 h-4 text-pm-accent bg-pm-input border-pm focus:ring-pm-accent">
<div class="ml-3">
<span class="text-pm text-xs font-medium">HuggingFace Model</span>
<span class="text-pm-secondary text-xs ml-2">(~16GB)</span>
</div>
</div>
<div id="hfModelStatus" class="flex items-center gap-2">
<span class="text-pm-secondary text-xs">Checking...</span>
</div>
</label>
<label class="flex items-center justify-between p-3 bg-pm-input rounded-pm-sm cursor-pointer hover:bg-pm-hover border border-pm">
<div class="flex items-center">
<input type="radio" name="autoTagModel" value="wd14-swinv2" class="w-4 h-4 text-pm-accent bg-pm-input border-pm focus:ring-pm-accent">
<div class="ml-3">
<span class="text-pm text-xs font-medium">WD14 SwinV2</span>
<span class="text-pm-secondary text-xs ml-2">(~400MB, fast classifier)</span>
</div>
</div>
<div id="wd14SwinV2ModelStatus" class="flex items-center gap-2">
<span class="text-pm-secondary text-xs">Checking...</span>
</div>
</label>
<label class="flex items-center justify-between p-3 bg-pm-input rounded-pm-sm cursor-pointer hover:bg-pm-hover border border-pm">
<div class="flex items-center">
<input type="radio" name="autoTagModel" value="wd14-vit" class="w-4 h-4 text-pm-accent bg-pm-input border-pm focus:ring-pm-accent">
<div class="ml-3">
<span class="text-pm text-xs font-medium">WD14 ViT</span>
<span class="text-pm-secondary text-xs ml-2">(~400MB, ViT architecture)</span>
</div>
</div>
<div id="wd14VitModelStatus" class="flex items-center gap-2">
<span class="text-pm-secondary text-xs">Checking...</span>
</div>
</label>
</div>
</div>
<!-- Keep Model in Memory Option -->
<div class="mb-3">
<label class="flex items-center justify-between p-3 bg-pm-input rounded-pm-sm cursor-pointer hover:bg-pm-hover border border-pm">
<div class="flex items-center">
<input type="checkbox" id="keepModelInMemory" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
<div class="ml-3">
<span class="text-pm text-xs font-medium">Keep model in memory</span>
<span class="text-pm-secondary text-xs ml-2">(Faster processing, uses ~4-16GB VRAM)</span>
</div>
</div>
<a id="unloadModelBtn" href="workspace#/system/tagging" target="_blank" rel="noopener"
title="Unload the model in System › Auto-tagging (opens a new tab)"
class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs rounded-pm-sm transition-colors hidden">
Manage in System
</a>
</label>
<p id="modelLoadedStatus" class="text-pm-muted text-xs mt-1 hidden">
<span class="text-pm-success">●</span> Model currently loaded: <span id="loadedModelType">none</span>
</p>
</div>
<!-- Prompt Template (LLM models only) -->
<div id="promptSection" class="mb-3">
<label class="block text-xs font-medium text-pm-secondary mb-2">Tag Generation Prompt</label>
<textarea id="autoTagPrompt" rows="6"
class="w-full px-3 py-2 bg-pm-input border border-pm rounded-pm-sm focus:ring-2 focus:ring-pm-accent focus:border-pm-accent text-pm text-xs font-mono resize-none"
placeholder="Enter prompt template...">Generate only comma-separated Danbooru tags (lowercase_underscores, no spaces).
Include: subject_count (1girl/1boy/etc), gender, ethnicity (asian, caucasian, etc), age_approx (teen, young_woman), body_attributes, face_features, hairstyle, appearance, clothing, accessories, pose, expression, action, camera_angle, camera_shot, scene_composition (wide_shot, long_shot, dutch_angle), environment, background_elements, lighting, mood.
Prioritize visual accuracy.
No sentences, no commentary, no captions. Only tags. Keep length short but descriptive.</textarea>
</div>
<!-- WD14 Threshold Controls -->
<div id="wd14ThresholdSection" class="mb-3 hidden">
<label class="block text-xs font-medium text-pm-secondary mb-2">Tag Confidence Thresholds</label>
<div class="space-y-3">
<div class="p-2.5 bg-pm-input rounded-pm-sm border border-pm">
<div class="flex items-center justify-between mb-1">
<span class="text-pm text-xs">General tags</span>
<span id="wd14GeneralThresholdValue" class="text-pm-accent text-xs font-mono">0.35</span>
</div>
<input type="range" id="wd14GeneralThreshold" min="0" max="1" step="0.05" value="0.35"
class="w-full h-1.5 bg-pm-primary rounded-lg appearance-none cursor-pointer accent-[var(--pm-accent)]">
</div>
<div class="p-2.5 bg-pm-input rounded-pm-sm border border-pm">
<div class="flex items-center justify-between mb-1">
<span class="text-pm text-xs">Character tags</span>
<span id="wd14CharacterThresholdValue" class="text-pm-accent text-xs font-mono">0.85</span>
</div>
<input type="range" id="wd14CharacterThreshold" min="0" max="1" step="0.05" value="0.85"
class="w-full h-1.5 bg-pm-primary rounded-lg appearance-none cursor-pointer accent-[var(--pm-accent)]">
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex justify-end gap-2">
<button onclick="hideModal('autoTagModal')"
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
Cancel
</button>
<button id="startReviewBtn"
class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
Review
</button>
<button id="startAutoTagBtn"
class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
Auto Tag
</button>
</div>
</div>
</div>
<!-- Auto Tag Review Modal -->
<div id="autoTagReviewModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
<div class="bg-pm-surface rounded-pm-md p-3 max-w-4xl w-full mx-4 border border-pm">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-semibold text-pm">
Review Tags
<span class="text-pm-secondary text-xs font-normal">(<span id="reviewCurrentIndex">1</span> of <span id="reviewTotalCount">0</span>)</span>
</h3>
<button onclick="window.gallery.cancelReview()" class="p-2 rounded-pm-sm hover:bg-pm-hover transition-colors">
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<!-- Image Preview -->
<div class="bg-pm-primary rounded-pm-sm p-3">
<img id="reviewImage" src="" alt="Review image" class="w-full h-64 object-contain rounded-pm-sm">
<p id="reviewImageName" class="text-pm-secondary text-xs mt-2 text-center truncate">image_name.png</p>
</div>
<!-- Generated Tags -->
<div>
<label class="block text-xs font-medium text-pm-secondary mb-2">Generated Tags (click to remove)</label>
<div id="reviewTagsContainer" class="flex flex-wrap gap-2 p-3 bg-pm-primary rounded-pm-sm min-h-[200px] max-h-[250px] overflow-y-auto">
<!-- Tag chips will be inserted here -->
</div>
<p class="text-pm-muted text-xs mt-1">Click the x on tags to remove them before applying</p>
</div>
</div>
<!-- Action Buttons -->
<div class="flex justify-between mt-3">
<button id="skipReviewBtn"
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
Skip
</button>
<div class="flex gap-2">
<button onclick="window.gallery.cancelReview()"
class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs font-medium rounded-pm-sm transition-colors">
Cancel All
</button>
<button id="applyReviewBtn"
class="px-3 py-1 bg-pm-success hover:bg-pm-success text-pm text-xs font-medium rounded-pm-sm transition-colors">
Apply & Next
</button>
</div>
</div>
</div>
</div>
<!-- Auto Tag Progress Modal -->
<div id="autoTagProgressModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
<div class="bg-pm-surface rounded-pm-md p-3 max-w-md w-full mx-4 border border-pm">
<h3 class="text-sm font-semibold text-pm mb-3">Auto Tagging Progress</h3>
<!-- Current File -->
<div class="mb-3">
<p class="text-pm-secondary text-xs">Processing:</p>
<p id="autoTagCurrentFile" class="text-pm text-xs font-medium truncate">Initializing...</p>
</div>
<!-- Progress Bar -->
<div class="mb-3">
<div class="flex justify-between text-xs text-pm-secondary mb-1">
<span>Progress</span>
<span id="autoTagProgressPercent">0%</span>
</div>
<div class="w-full bg-pm-input rounded-full h-2">
<div id="autoTagProgressBar" class="bg-pm-accent h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
<p id="autoTagCloseNote" class="text-pm-muted text-xs mt-1.5">Closing this window stops tagging. Tags already added are kept. Prompt Manager's Activity page shows the time left.</p>
</div>
<!-- Stats -->
<div class="grid grid-cols-3 gap-2 mb-3">
<div class="bg-pm-input rounded-pm-sm p-2 text-center">
<div class="text-sm font-semibold text-pm-accent" id="autoTagProcessed">0</div>
<div class="text-xs text-pm-secondary">Processed</div>
</div>
<div class="bg-pm-input rounded-pm-sm p-2 text-center">
<div class="text-sm font-semibold text-pm-success" id="autoTagApplied">0</div>
<div class="text-xs text-pm-secondary">Tags Applied</div>
</div>
<div class="bg-pm-input rounded-pm-sm p-2 text-center">
<div class="text-sm font-semibold text-pm-warning" id="autoTagSkipped">0</div>
<div class="text-xs text-pm-secondary">Skipped</div>
</div>
</div>
<!-- Cancel Button -->
<div class="flex justify-center">
<button id="cancelAutoTagBtn"
class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs font-medium rounded-pm-sm transition-colors">
Cancel
</button>
</div>
</div>
</div>
<!-- Add Prompt Modal -->
<div id="addPromptModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
<div class="bg-pm-surface rounded-pm-md p-3 max-w-2xl w-full mx-4 border border-pm max-h-[90vh] overflow-y-auto">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-semibold text-pm">Add New Prompt</h3>
<button onclick="window.gallery.hideAddPromptModal()" class="p-2 rounded-pm-sm hover:bg-pm-hover transition-colors">
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<!-- Prompt Text -->
<div class="mb-3">
<label class="block text-xs font-medium text-pm-secondary mb-1">Prompt Text <span class="text-pm-error">*</span></label>
<textarea id="addPromptText" rows="4"
class="w-full px-3 py-2 bg-pm-input border border-pm rounded-pm-sm focus:ring-2 focus:ring-pm-accent focus:border-pm-accent text-pm text-xs resize-none"
placeholder="Enter your prompt text here..."></textarea>
</div>
<!-- Category and Rating Row -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
<!-- Category -->
<div>
<label class="block text-xs font-medium text-pm-secondary mb-1">Category</label>
<div class="relative">
<input type="text" id="addPromptCategory" list="categoryList"
class="w-full px-3 py-2 bg-pm-input border border-pm rounded-pm-sm focus:ring-2 focus:ring-pm-accent focus:border-pm-accent text-pm text-xs"
placeholder="Select or type category...">
<datalist id="categoryList"></datalist>
</div>
</div>
<!-- Rating -->
<div>
<label class="block text-xs font-medium text-pm-secondary mb-1">Rating</label>
<div class="flex items-center gap-1" id="addPromptRating">
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="1">☆</button>
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="2">☆</button>
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="3">☆</button>
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="4">☆</button>
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="5">☆</button>
<button type="button" class="ml-2 text-xs text-pm-muted hover:text-pm-secondary" id="clearRatingBtn">Clear</button>
</div>
<input type="hidden" id="addPromptRatingValue" value="">
</div>
</div>
<!-- Tags -->
<div class="mb-3">
<label class="block text-xs font-medium text-pm-secondary mb-1">Tags</label>
<div class="relative">
<div id="addPromptTagsContainer" class="flex flex-wrap gap-2 p-2 bg-pm-input border border-pm rounded-pm-sm min-h-[44px]">
<input type="text" id="addPromptTagInput"
class="flex-1 min-w-[150px] bg-transparent border-none outline-none text-pm text-xs placeholder-pm"
placeholder="Type to add tags...">
</div>
<div id="tagSuggestions" class="absolute left-0 right-0 top-full mt-1 bg-pm-surface border border-pm rounded-pm-sm shadow-pm hidden z-10 max-h-48 overflow-y-auto"></div>
</div>
<p class="text-pm-muted text-xs mt-1">Press Enter or comma to add a tag. Click a tag to remove it.</p>
</div>
<!-- Notes -->
<div class="mb-3">
<label class="block text-xs font-medium text-pm-secondary mb-1">Notes</label>
<textarea id="addPromptNotes" rows="2"
class="w-full px-3 py-2 bg-pm-input border border-pm rounded-pm-sm focus:ring-2 focus:ring-pm-accent focus:border-pm-accent text-pm text-xs resize-none"
placeholder="Optional notes about this prompt..."></textarea>
</div>
<!-- Protection Option -->
<div class="mb-3">
<label class="flex items-start p-3 bg-pm-input rounded-pm-sm cursor-pointer hover:bg-pm-hover border border-pm">
<input type="checkbox" id="addPromptProtected" checked class="w-4 h-4 mt-0.5 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
<div class="ml-3">
<span class="text-pm text-xs font-medium">Protect from orphan cleanup</span>
<p class="text-pm-secondary text-xs mt-1">Keep this prompt even if it has no linked images. Uncheck if you want this prompt to be automatically removed during maintenance when it has no images.</p>
</div>
</label>
</div>
<!-- Action Buttons -->
<div class="flex justify-end gap-2">
<button onclick="window.gallery.hideAddPromptModal()"
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
Cancel
</button>
<button id="saveNewPromptBtn"
class="px-3 py-1 bg-pm-success hover:bg-pm-success text-pm text-xs font-medium rounded-pm-sm transition-colors">
Add Prompt
</button>
</div>
</div>
</div>
<script src="js/comfy-metadata.js"></script>
<script src="js/gallery.js"></script>
</body>
</html>