Migrate all remaining hardcoded Tailwind color classes to --pm-* design tokens across gallery.html, metadata.html, admin.js, gallery.js, and tags-page.js. Zero hardcoded color classes remain in HTML/JS files. - gallery.html: remove inline styles (now in comfyui-theme.css), strip gradients/decorative elements, tighten spacing for ComfyUI density - metadata.html: add theme imports, replace all color classes - admin.js: replace 164 color occurrences in template literals, convert raw hex values to CSS variable references - gallery.js: replace 161 color occurrences in template literals - tags-page.js: replace 30 color occurrences in template literals
736 lines
45 KiB
HTML
736 lines
45 KiB
HTML
<!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="/prompt_manager/js/theme-bridge.js"></script>
|
|
<link rel="stylesheet" href="css/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>
|
|
</head>
|
|
<body class="bg-pm-primary text-pm min-h-screen">
|
|
<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>
|
|
</div>
|
|
<div class="flex 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>
|
|
<button id="settingsBtn"
|
|
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
|
Settings
|
|
</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-y-auto custom-scrollbar">
|
|
<div class="max-w-7xl mx-auto p-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>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Modal -->
|
|
<div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-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">Gallery Settings</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>
|
|
|
|
<!-- Thumbnail Generation -->
|
|
<div class="bg-pm-input rounded-pm-sm p-3">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<div>
|
|
<h5 class="text-pm text-xs font-medium">Generate Thumbnails</h5>
|
|
<p class="text-xs text-pm-secondary">Create optimized thumbnails for faster loading</p>
|
|
</div>
|
|
<button id="generateThumbnailsBtn" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs rounded-pm-sm transition-colors">
|
|
Generate
|
|
</button>
|
|
</div>
|
|
<div class="text-xs text-pm-muted mb-2">
|
|
Status: <span id="thumbnailStatus">Not generated</span>
|
|
<div class="text-xs text-pm-muted mt-1">
|
|
Thumbnails speed up gallery loading. Original images always used for viewing and metadata.
|
|
</div>
|
|
</div>
|
|
<!-- Check thumbnails at startup -->
|
|
<div class="flex items-center justify-between py-2 border-t border-pm">
|
|
<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>
|
|
<!-- Progress bar (hidden by default) -->
|
|
<div id="thumbnailProgress" class="hidden">
|
|
<div class="flex items-center justify-between text-xs text-pm-secondary mb-1">
|
|
<span id="progressText">Processing...</span>
|
|
<div class="flex items-center gap-2">
|
|
<span id="progressPercent">0%</span>
|
|
<button id="cancelThumbnailsBtn" class="text-pm-error hover:text-pm-error text-xs px-2 py-1 bg-pm-error-tint rounded-pm-sm hidden">
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="w-full bg-pm-input rounded-full h-2">
|
|
<div id="progressBar" class="bg-pm-accent h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
|
|
</div>
|
|
<div class="flex items-center justify-between text-xs text-pm-muted mt-1">
|
|
<span id="progressDetails">Scanning images...</span>
|
|
<span id="progressETA" class="text-pm-muted"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Image Loading -->
|
|
<div class="bg-pm-input rounded-pm-sm p-3">
|
|
<h5 class="text-pm text-xs font-medium mb-2">Image Loading</h5>
|
|
<div class="space-y-2">
|
|
<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 class="flex items-center justify-between">
|
|
<label class="text-xs text-pm-secondary">Image Quality</label>
|
|
<select id="imageQualitySelect" class="px-2 py-1 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs">
|
|
<option value="high">High (Original)</option>
|
|
<option value="medium" selected>Medium (Optimized)</option>
|
|
<option value="low">Low (Fast)</option>
|
|
</select>
|
|
</div>
|
|
</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">Default View Mode</label>
|
|
<select id="defaultViewModeSelect" class="px-2 py-1 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs">
|
|
<option value="grid" selected>Grid View</option>
|
|
<option value="list">List View</option>
|
|
</select>
|
|
</div>
|
|
<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">
|
|
<label class="text-xs text-pm-secondary">Show Media Info</label>
|
|
<input type="checkbox" id="showImageInfoToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Video Settings -->
|
|
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
|
|
<h5 class="text-pm text-xs font-medium mb-2">Video Settings</h5>
|
|
<div class="flex items-center justify-between">
|
|
<label class="text-xs text-pm-secondary">Autoplay Videos</label>
|
|
<input type="checkbox" id="videoAutoplayToggle" 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">Mute Videos by Default</label>
|
|
<input type="checkbox" id="videoMuteToggle" checked 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">Loop Videos</label>
|
|
<input type="checkbox" id="videoLoopToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Metadata Settings -->
|
|
<div class="space-y-3">
|
|
<h4 class="text-sm font-semibold text-pm">Metadata 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">Auto-load Metadata</label>
|
|
<input type="checkbox" id="autoLoadMetadataToggle" checked 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">Cache Metadata</label>
|
|
<input type="checkbox" id="cacheMetadataToggle" checked 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">Show File Paths</label>
|
|
<input type="checkbox" id="showFilePathsToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Maintenance -->
|
|
<div class="space-y-3">
|
|
<h4 class="text-sm font-semibold text-pm">Maintenance</h4>
|
|
|
|
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h5 class="text-pm text-xs font-medium">Clear Cache</h5>
|
|
<p class="text-xs text-pm-secondary">Clear cached metadata and thumbnails</p>
|
|
</div>
|
|
<button id="clearCacheBtn" class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs rounded-pm-sm transition-colors">
|
|
Clear
|
|
</button>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h5 class="text-pm text-xs font-medium">Rescan Output Folder</h5>
|
|
<p class="text-xs text-pm-secondary">Refresh the image list from disk</p>
|
|
</div>
|
|
<button id="rescanFolderBtn" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs rounded-pm-sm transition-colors">
|
|
Rescan
|
|
</button>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h5 class="text-pm text-xs font-medium">Find Duplicate Images</h5>
|
|
<p class="text-xs text-pm-secondary">Scan for duplicate images based on file content</p>
|
|
</div>
|
|
<button id="scanDuplicatesBtn" class="px-3 py-1 bg-pm-warning hover:bg-pm-warning text-pm text-xs rounded-pm-sm transition-colors">
|
|
Scan
|
|
</button>
|
|
</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>
|
|
|
|
</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>
|
|
|
|
<!-- Duplicates Modal -->
|
|
<div id="duplicatesModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
|
|
<div class="bg-pm-surface rounded-pm-md max-w-4xl 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">Duplicate Images Found</h3>
|
|
<button id="closeDuplicatesBtn" 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)]">
|
|
<div id="duplicatesScanStatus" class="text-center py-6">
|
|
<div class="inline-flex items-center px-3 py-2 bg-pm-warning rounded-pm-sm text-xs text-pm">
|
|
<svg class="animate-spin -ml-1 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Scanning for duplicate images...
|
|
</div>
|
|
</div>
|
|
<div id="duplicatesContent" class="hidden">
|
|
<div class="mb-3 p-3 bg-pm-error-tint border border-pm-accent rounded-pm-sm">
|
|
<div class="flex items-start">
|
|
<svg class="w-4 h-4 text-pm-error mt-0.5 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
|
</svg>
|
|
<div>
|
|
<h4 class="text-pm-error text-xs font-medium">Warning: Removing duplicate images cannot be undone!</h4>
|
|
<p class="text-pm-secondary text-xs mt-1">When you remove a duplicate image, the selected file will be permanently deleted from disk. Choose carefully which images to keep.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="duplicatesList" class="space-y-3">
|
|
<!-- Duplicates will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="duplicatesFooter" class="hidden p-3 border-t border-pm flex justify-between items-center">
|
|
<div class="text-xs text-pm-secondary">
|
|
<span id="duplicatesCount">0</span> duplicate image groups found
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button id="selectAllBtn" class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
|
Select All to Delete
|
|
</button>
|
|
<button id="removeDuplicatesBtn" class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
|
Delete Selected
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Auto Tag Modal -->
|
|
<div id="autoTagModal" class="fixed inset-0 bg-black bg-opacity-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>
|
|
</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>
|
|
<button id="unloadModelBtn" class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs rounded-pm-sm transition-colors hidden">
|
|
Unload Model
|
|
</button>
|
|
</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 -->
|
|
<div 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>
|
|
|
|
<!-- 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 bg-opacity-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 bg-opacity-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>
|
|
</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>
|
|
|
|
<!-- Auto Tag Download Modal -->
|
|
<div id="autoTagDownloadModal" class="fixed inset-0 bg-black bg-opacity-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">Downloading Model</h3>
|
|
|
|
<!-- Model Name -->
|
|
<div class="mb-3">
|
|
<p class="text-pm-secondary text-xs">Downloading:</p>
|
|
<p id="downloadModelName" class="text-pm text-xs font-medium">GGUF Model</p>
|
|
</div>
|
|
|
|
<!-- Progress Bar -->
|
|
<div class="mb-3">
|
|
<div class="flex justify-between text-xs text-pm-secondary mb-1">
|
|
<span id="downloadStatus">Preparing...</span>
|
|
<span id="downloadProgressPercent">0%</span>
|
|
</div>
|
|
<div class="w-full bg-pm-input rounded-full h-2">
|
|
<div id="downloadProgressBar" class="bg-pm-accent h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cancel Button -->
|
|
<div class="flex justify-center">
|
|
<button id="cancelDownloadBtn"
|
|
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 bg-opacity-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="/prompt_manager/js/gallery.js"></script>
|
|
</body>
|
|
</html> |