Replace 398KB Tailwind Play Mode runtime (not for production) with 37KB pre-compiled CSS. Fix .gitignore so web/lib/ vendor files (Tailwind CSS, ViewerJS) are tracked — fresh clones were completely broken without them. - Add gitignore negation rules for web/lib/ - Compile Tailwind v3.4.17 CSS at dev time via standalone CLI - Replace <script> tags with <link> in admin, gallery, metadata HTML - Delete tailwind.js runtime (no longer needed) - Add Makefile with css/css-watch/css-setup targets for devs - Track ViewerJS vendor files (viewer.min.js, viewer.min.css)
901 lines
50 KiB
HTML
901 lines
50 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" class="dark">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>PromptManager - Gallery</title>
|
||
<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>
|
||
.custom-scrollbar::-webkit-scrollbar {
|
||
width: 8px;
|
||
}
|
||
.custom-scrollbar::-webkit-scrollbar-track {
|
||
background: #374151;
|
||
border-radius: 4px;
|
||
}
|
||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||
background: #6b7280;
|
||
border-radius: 4px;
|
||
}
|
||
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||
background: #9ca3af;
|
||
}
|
||
|
||
.viewer-dark-theme .viewer-backdrop {
|
||
background-color: #000000 !important;
|
||
background: #000000 !important;
|
||
}
|
||
|
||
/* Additional selectors to ensure black background */
|
||
.viewer-backdrop {
|
||
background-color: #000000 !important;
|
||
background: #000000 !important;
|
||
}
|
||
|
||
.viewer-container {
|
||
background-color: #000000 !important;
|
||
background: #000000 !important;
|
||
}
|
||
|
||
.viewer-canvas {
|
||
background-color: #000000 !important;
|
||
background: #000000 !important;
|
||
}
|
||
|
||
.viewer-dark-theme .viewer-toolbar {
|
||
background-color: rgba(31, 41, 55, 0.95) !important;
|
||
}
|
||
|
||
.viewer-dark-theme .viewer-navbar {
|
||
background-color: rgba(31, 41, 55, 0.95) !important;
|
||
}
|
||
|
||
.viewer-dark-theme .viewer-title {
|
||
color: #f3f4f6 !important;
|
||
}
|
||
|
||
.image-item {
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
|
||
.image-item:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.metadata-sidebar {
|
||
position: fixed !important;
|
||
top: 0 !important;
|
||
right: 0 !important;
|
||
width: 384px !important;
|
||
height: 100vh !important;
|
||
background: rgb(17, 24, 39) !important;
|
||
border-left: 1px solid rgb(55, 65, 81) !important;
|
||
display: flex !important;
|
||
flex-direction: column !important;
|
||
z-index: 10000 !important;
|
||
transition: transform 0.3s ease-in-out !important;
|
||
}
|
||
|
||
.metadata-sidebar.collapsed {
|
||
transform: translateX(340px) !important;
|
||
}
|
||
|
||
.metadata-collapse-btn {
|
||
position: absolute;
|
||
left: -40px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 32px;
|
||
height: 80px;
|
||
background: rgb(17, 24, 39);
|
||
border: 1px solid rgb(55, 65, 81);
|
||
border-right: none;
|
||
border-radius: 6px 0 0 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
z-index: 10001;
|
||
}
|
||
|
||
.metadata-collapse-btn:hover {
|
||
background: rgb(31, 41, 55);
|
||
}
|
||
|
||
.metadata-collapse-btn svg {
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.metadata-sidebar.collapsed .metadata-collapse-btn svg {
|
||
transform: rotate(180deg);
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="bg-gray-925 text-gray-100 min-h-screen">
|
||
<div class="min-h-screen flex flex-col">
|
||
<!-- Header -->
|
||
<header class="bg-gradient-to-r from-gray-850 to-gray-800 border-b border-gray-700 px-6 py-4">
|
||
<div class="max-w-7xl mx-auto">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center space-x-3">
|
||
<div class="w-10 h-10 bg-gradient-to-br from-violet-500 to-purple-600 rounded-lg flex items-center justify-center">
|
||
<span class="text-xl">🖼️</span>
|
||
</div>
|
||
<div>
|
||
<h1 class="text-2xl font-bold bg-gradient-to-r from-violet-400 to-purple-400 bg-clip-text text-transparent">
|
||
PromptManager Gallery
|
||
</h1>
|
||
<p class="text-gray-400 text-sm">Browse all images from your ComfyUI output folder</p>
|
||
</div>
|
||
</div>
|
||
<div class="hidden sm:flex items-center space-x-2">
|
||
<div class="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
|
||
<span class="text-green-400 text-sm font-medium">Online</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Controls -->
|
||
<div class="bg-gray-850 border-b border-gray-700/50 px-6 py-4">
|
||
<div class="max-w-7xl mx-auto">
|
||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||
<div class="flex items-center space-x-4">
|
||
<label class="text-sm text-gray-400">Show:</label>
|
||
<select id="limitSelector" class="px-3 py-1 bg-gray-700 border border-gray-600 rounded text-gray-100 text-sm">
|
||
<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-sm text-gray-400">images</span>
|
||
</div>
|
||
<div class="flex items-center space-x-2">
|
||
<button id="refreshBtn"
|
||
class="px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white text-sm font-medium rounded-lg transition-colors">
|
||
🔄 Refresh
|
||
</button>
|
||
<button id="gridViewBtn"
|
||
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded-lg transition-colors">
|
||
⊞ Grid View
|
||
</button>
|
||
<button id="listViewBtn"
|
||
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded-lg transition-colors">
|
||
☰ List View
|
||
</button>
|
||
<button id="settingsBtn"
|
||
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded-lg transition-colors">
|
||
⚙️ Settings
|
||
</button>
|
||
<button id="autoTagBtn"
|
||
class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white text-sm font-medium rounded-lg transition-colors">
|
||
🏷️ Auto Tag
|
||
</button>
|
||
<button id="addPromptBtn"
|
||
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-medium rounded-lg transition-colors">
|
||
➕ Add Prompt
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stats Bar -->
|
||
<div class="bg-gray-800 px-6 py-3 border-b border-gray-700/50">
|
||
<div class="max-w-7xl mx-auto">
|
||
<div class="flex items-center justify-between text-sm">
|
||
<span class="text-gray-400">
|
||
Showing <span id="showingStart">0</span> to <span id="showingEnd">0</span> of <span id="totalImages">0</span> images
|
||
</span>
|
||
<span class="text-gray-400" 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-6">
|
||
<!-- Loading State -->
|
||
<div id="loadingState" class="flex items-center justify-center py-20">
|
||
<div class="text-center">
|
||
<div class="w-12 h-12 border-4 border-violet-500/30 border-t-violet-500 rounded-full animate-spin mx-auto mb-4"></div>
|
||
<p class="text-gray-400">Loading gallery...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Error State -->
|
||
<div id="errorState" class="hidden flex items-center justify-center py-20">
|
||
<div class="text-center">
|
||
<div class="w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||
<span class="text-3xl">❌</span>
|
||
</div>
|
||
<h3 class="text-xl font-semibold text-gray-100 mb-2">Error Loading Gallery</h3>
|
||
<p class="text-gray-400 mb-4" id="errorMessage">Something went wrong while loading the images.</p>
|
||
<button id="retryBtn" class="px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white font-medium rounded-lg 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-4">
|
||
<!-- Images will be inserted here -->
|
||
</div>
|
||
|
||
<!-- Gallery List -->
|
||
<div id="galleryList" class="hidden space-y-3">
|
||
<!-- Images will be inserted here -->
|
||
</div>
|
||
|
||
<!-- Pagination -->
|
||
<div id="paginationControls" class="hidden mt-8 flex items-center justify-center space-x-2">
|
||
<button id="prevPageBtn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
|
||
← Previous
|
||
</button>
|
||
<div class="flex items-center space-x-2">
|
||
<span class="text-gray-400">Page</span>
|
||
<input id="pageInput" type="number" min="1" class="w-16 px-2 py-1 bg-gray-700 text-white text-center rounded border border-gray-600 focus:border-blue-500 focus:outline-none" value="1">
|
||
<span id="pageInfo" class="text-gray-400">of 1</span>
|
||
</div>
|
||
<button id="nextPageBtn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white font-medium rounded-lg 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-gray-800 rounded-xl max-w-2xl w-full max-h-[90vh] overflow-hidden mx-4 border border-gray-700">
|
||
<div class="p-6 border-b border-gray-700">
|
||
<div class="flex items-center justify-between">
|
||
<h3 class="text-xl font-semibold text-gray-100">⚙️ Gallery Settings</h3>
|
||
<button id="closeSettingsBtn" class="p-2 rounded-lg hover:bg-gray-700 transition-colors">
|
||
<svg class="w-5 h-5 text-gray-400" 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-6 overflow-y-auto max-h-[calc(90vh-200px)] custom-scrollbar space-y-6">
|
||
|
||
<!-- Performance Settings -->
|
||
<div class="space-y-4">
|
||
<h4 class="text-lg font-medium text-gray-100 flex items-center">
|
||
<span class="mr-2">🚀</span>
|
||
Performance Settings
|
||
</h4>
|
||
|
||
<!-- Thumbnail Generation -->
|
||
<div class="bg-gray-700 rounded-lg p-4">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<div>
|
||
<h5 class="text-white font-medium">Generate Thumbnails</h5>
|
||
<p class="text-sm text-gray-400">Create optimized thumbnails for faster loading</p>
|
||
</div>
|
||
<button id="generateThumbnailsBtn" class="px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white text-sm rounded transition-colors">
|
||
Generate
|
||
</button>
|
||
</div>
|
||
<div class="text-xs text-gray-500 mb-2">
|
||
Status: <span id="thumbnailStatus">Not generated</span>
|
||
<div class="text-xs text-gray-600 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-gray-600">
|
||
<div>
|
||
<label class="text-sm text-gray-300">Check thumbnails at startup</label>
|
||
<p class="text-xs text-gray-500">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-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-violet-300 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-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-violet-600"></div>
|
||
</label>
|
||
</div>
|
||
<!-- Progress bar (hidden by default) -->
|
||
<div id="thumbnailProgress" class="hidden">
|
||
<div class="flex items-center justify-between text-xs text-gray-400 mb-1">
|
||
<span id="progressText">Processing...</span>
|
||
<div class="flex items-center gap-2">
|
||
<span id="progressPercent">0%</span>
|
||
<button id="cancelThumbnailsBtn" class="text-red-400 hover:text-red-300 text-xs px-2 py-1 bg-red-900/20 rounded hidden">
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="w-full bg-gray-600 rounded-full h-2">
|
||
<div id="progressBar" class="bg-violet-600 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||
</div>
|
||
<div class="flex items-center justify-between text-xs text-gray-500 mt-1">
|
||
<span id="progressDetails">Scanning images...</span>
|
||
<span id="progressETA" class="text-gray-600"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Image Loading -->
|
||
<div class="bg-gray-700 rounded-lg p-4">
|
||
<h5 class="text-white font-medium mb-3">Image Loading</h5>
|
||
<div class="space-y-3">
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Lazy Loading</label>
|
||
<input type="checkbox" id="lazyLoadingToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
|
||
</div>
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Image Quality</label>
|
||
<select id="imageQualitySelect" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm">
|
||
<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-4">
|
||
<h4 class="text-lg font-medium text-gray-100 flex items-center">
|
||
<span class="mr-2">🖼️</span>
|
||
Display Settings
|
||
</h4>
|
||
|
||
<div class="bg-gray-700 rounded-lg p-4 space-y-3">
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Default View Mode</label>
|
||
<select id="defaultViewModeSelect" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm">
|
||
<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-sm text-gray-300">Items per Page</label>
|
||
<select id="defaultLimitSelect" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm">
|
||
<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-sm text-gray-300">Grid Columns (Desktop)</label>
|
||
<select id="gridColumnsSelect" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm">
|
||
<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-sm text-gray-300">Show Media Info</label>
|
||
<input type="checkbox" id="showImageInfoToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Video Settings -->
|
||
<div class="bg-gray-700 rounded-lg p-4 space-y-3">
|
||
<h5 class="text-white font-medium mb-3">Video Settings</h5>
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Autoplay Videos</label>
|
||
<input type="checkbox" id="videoAutoplayToggle" class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
|
||
</div>
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Mute Videos by Default</label>
|
||
<input type="checkbox" id="videoMuteToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
|
||
</div>
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Loop Videos</label>
|
||
<input type="checkbox" id="videoLoopToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Metadata Settings -->
|
||
<div class="space-y-4">
|
||
<h4 class="text-lg font-medium text-gray-100 flex items-center">
|
||
<span class="mr-2">📋</span>
|
||
Metadata Settings
|
||
</h4>
|
||
|
||
<div class="bg-gray-700 rounded-lg p-4 space-y-3">
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Auto-load Metadata</label>
|
||
<input type="checkbox" id="autoLoadMetadataToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
|
||
</div>
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Cache Metadata</label>
|
||
<input type="checkbox" id="cacheMetadataToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
|
||
</div>
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Show File Paths</label>
|
||
<input type="checkbox" id="showFilePathsToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Maintenance -->
|
||
<div class="space-y-4">
|
||
<h4 class="text-lg font-medium text-gray-100 flex items-center">
|
||
<span class="mr-2">🔧</span>
|
||
Maintenance
|
||
</h4>
|
||
|
||
<div class="bg-gray-700 rounded-lg p-4 space-y-3">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<h5 class="text-white text-sm font-medium">Clear Cache</h5>
|
||
<p class="text-xs text-gray-400">Clear cached metadata and thumbnails</p>
|
||
</div>
|
||
<button id="clearCacheBtn" class="px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-sm rounded transition-colors">
|
||
Clear
|
||
</button>
|
||
</div>
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<h5 class="text-white text-sm font-medium">Rescan Output Folder</h5>
|
||
<p class="text-xs text-gray-400">Refresh the image list from disk</p>
|
||
</div>
|
||
<button id="rescanFolderBtn" class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors">
|
||
Rescan
|
||
</button>
|
||
</div>
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<h5 class="text-white text-sm font-medium">Find Duplicate Images</h5>
|
||
<p class="text-xs text-gray-400">Scan for duplicate images based on file content</p>
|
||
</div>
|
||
<button id="scanDuplicatesBtn" class="px-3 py-1 bg-orange-600 hover:bg-orange-700 text-white text-sm rounded transition-colors">
|
||
Scan
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Advanced Settings -->
|
||
<div class="space-y-4">
|
||
<h4 class="text-lg font-medium text-gray-100 flex items-center">
|
||
<span class="mr-2">🔬</span>
|
||
Advanced
|
||
</h4>
|
||
|
||
<div class="bg-gray-700 rounded-lg p-4 space-y-3">
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">Debug Mode</label>
|
||
<input type="checkbox" id="debugModeToggle" class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
|
||
</div>
|
||
<div class="flex items-center justify-between">
|
||
<label class="text-sm text-gray-300">API Timeout (seconds)</label>
|
||
<input type="number" id="apiTimeoutInput" value="30" min="5" max="300" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm w-20">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="p-6 border-t border-gray-700 flex justify-end space-x-3">
|
||
<button id="resetSettingsBtn" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
|
||
Reset to Defaults
|
||
</button>
|
||
<button id="saveSettingsBtn" class="px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white font-medium rounded-lg 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-gray-800 rounded-xl max-w-4xl w-full max-h-[90vh] overflow-hidden mx-4 border border-gray-700">
|
||
<div class="p-6 border-b border-gray-700">
|
||
<div class="flex items-center justify-between">
|
||
<h3 class="text-xl font-semibold text-gray-100">🔍 Duplicate Images Found</h3>
|
||
<button id="closeDuplicatesBtn" class="p-2 rounded-lg hover:bg-gray-700 transition-colors">
|
||
<svg class="w-5 h-5 text-gray-400" 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-6 overflow-y-auto max-h-[calc(90vh-200px)] custom-scrollbar">
|
||
<div id="duplicatesScanStatus" class="text-center py-8">
|
||
<div class="inline-flex items-center px-4 py-2 bg-orange-600 rounded-lg">
|
||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" 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-6 p-4 bg-red-900/20 border border-red-500/50 rounded-lg">
|
||
<div class="flex items-start">
|
||
<svg class="w-5 h-5 text-red-400 mt-0.5 mr-3 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-red-300 font-medium">⚠️ Warning: Removing duplicate images cannot be undone!</h4>
|
||
<p class="text-red-200 text-sm 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-4">
|
||
<!-- Duplicates will be populated here -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="duplicatesFooter" class="hidden p-6 border-t border-gray-700 flex justify-between items-center">
|
||
<div class="text-sm text-gray-400">
|
||
<span id="duplicatesCount">0</span> duplicate image groups found
|
||
</div>
|
||
<div class="flex space-x-3">
|
||
<button id="selectAllBtn" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
|
||
Select All to Delete
|
||
</button>
|
||
<button id="removeDuplicatesBtn" class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg 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-gray-800 rounded-xl p-6 max-w-lg w-full mx-4 border border-gray-700">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h3 class="text-xl font-semibold text-gray-100">🏷️ Auto Tag Images</h3>
|
||
<button onclick="hideModal('autoTagModal')" class="p-2 rounded-lg hover:bg-gray-700 transition-colors">
|
||
<svg class="w-5 h-5 text-gray-400" 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-6">
|
||
<label class="block text-sm font-medium text-gray-400 mb-3">Model Selection</label>
|
||
<div class="space-y-3">
|
||
<label class="flex items-center justify-between p-4 bg-gray-700 rounded-lg cursor-pointer hover:bg-gray-650 border border-gray-600">
|
||
<div class="flex items-center">
|
||
<input type="radio" name="autoTagModel" value="gguf" checked class="w-4 h-4 text-purple-600 bg-gray-700 border-gray-600 focus:ring-purple-500">
|
||
<div class="ml-3">
|
||
<span class="text-gray-100 font-medium">GGUF Model</span>
|
||
<span class="text-gray-400 text-sm ml-2">(Recommended, ~4.5GB)</span>
|
||
</div>
|
||
</div>
|
||
<div id="ggufModelStatus" class="flex items-center space-x-2">
|
||
<span class="text-gray-400 text-sm">Checking...</span>
|
||
</div>
|
||
</label>
|
||
<label class="flex items-center justify-between p-4 bg-gray-700 rounded-lg cursor-pointer hover:bg-gray-650 border border-gray-600">
|
||
<div class="flex items-center">
|
||
<input type="radio" name="autoTagModel" value="hf" class="w-4 h-4 text-purple-600 bg-gray-700 border-gray-600 focus:ring-purple-500">
|
||
<div class="ml-3">
|
||
<span class="text-gray-100 font-medium">HuggingFace Model</span>
|
||
<span class="text-gray-400 text-sm ml-2">(~16GB)</span>
|
||
</div>
|
||
</div>
|
||
<div id="hfModelStatus" class="flex items-center space-x-2">
|
||
<span class="text-gray-400 text-sm">Checking...</span>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Keep Model in Memory Option -->
|
||
<div class="mb-6">
|
||
<label class="flex items-center justify-between p-4 bg-gray-700 rounded-lg cursor-pointer hover:bg-gray-650 border border-gray-600">
|
||
<div class="flex items-center">
|
||
<input type="checkbox" id="keepModelInMemory" checked class="w-4 h-4 text-purple-600 bg-gray-700 border-gray-600 rounded focus:ring-purple-500">
|
||
<div class="ml-3">
|
||
<span class="text-gray-100 font-medium">Keep model in memory</span>
|
||
<span class="text-gray-400 text-sm ml-2">(Faster processing, uses ~4-16GB VRAM)</span>
|
||
</div>
|
||
</div>
|
||
<button id="unloadModelBtn" class="px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-xs rounded transition-colors hidden">
|
||
Unload Model
|
||
</button>
|
||
</label>
|
||
<p id="modelLoadedStatus" class="text-gray-500 text-xs mt-2 hidden">
|
||
<span class="text-green-400">●</span> Model currently loaded: <span id="loadedModelType">none</span>
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Prompt Template -->
|
||
<div class="mb-6">
|
||
<label class="block text-sm font-medium text-gray-400 mb-2">Tag Generation Prompt</label>
|
||
<textarea id="autoTagPrompt" rows="6"
|
||
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-purple-500 text-gray-100 text-sm font-mono resize-none custom-scrollbar"
|
||
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 space-x-3">
|
||
<button onclick="hideModal('autoTagModal')"
|
||
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
|
||
Cancel
|
||
</button>
|
||
<button id="startReviewBtn"
|
||
class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
|
||
👁️ Review
|
||
</button>
|
||
<button id="startAutoTagBtn"
|
||
class="px-6 py-2 bg-purple-600 hover:bg-purple-700 text-white font-medium rounded-lg 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-gray-800 rounded-xl p-6 max-w-4xl w-full mx-4 border border-gray-700">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h3 class="text-xl font-semibold text-gray-100">
|
||
👁️ Review Tags
|
||
<span class="text-gray-400 text-base font-normal">(<span id="reviewCurrentIndex">1</span> of <span id="reviewTotalCount">0</span>)</span>
|
||
</h3>
|
||
<button onclick="window.gallery.cancelReview()" class="p-2 rounded-lg hover:bg-gray-700 transition-colors">
|
||
<svg class="w-5 h-5 text-gray-400" 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-6">
|
||
<!-- Image Preview -->
|
||
<div class="bg-gray-900 rounded-lg p-4">
|
||
<img id="reviewImage" src="" alt="Review image" class="w-full h-64 object-contain rounded">
|
||
<p id="reviewImageName" class="text-gray-400 text-sm mt-2 text-center truncate">image_name.png</p>
|
||
</div>
|
||
|
||
<!-- Generated Tags -->
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-400 mb-3">Generated Tags (click to remove)</label>
|
||
<div id="reviewTagsContainer" class="flex flex-wrap gap-2 p-4 bg-gray-900 rounded-lg min-h-[200px] max-h-[250px] overflow-y-auto custom-scrollbar">
|
||
<!-- Tag chips will be inserted here -->
|
||
</div>
|
||
<p class="text-gray-500 text-xs mt-2">Click the × on tags to remove them before applying</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Action Buttons -->
|
||
<div class="flex justify-between mt-6">
|
||
<button id="skipReviewBtn"
|
||
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
|
||
⏭️ Skip
|
||
</button>
|
||
<div class="flex space-x-3">
|
||
<button onclick="window.gallery.cancelReview()"
|
||
class="px-6 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors">
|
||
Cancel All
|
||
</button>
|
||
<button id="applyReviewBtn"
|
||
class="px-6 py-2 bg-green-600 hover:bg-green-700 text-white font-medium rounded-lg 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-gray-800 rounded-xl p-6 max-w-md w-full mx-4 border border-gray-700">
|
||
<h3 class="text-xl font-semibold text-gray-100 mb-6">🏷️ Auto Tagging Progress</h3>
|
||
|
||
<!-- Current File -->
|
||
<div class="mb-4">
|
||
<p class="text-gray-400 text-sm">Processing:</p>
|
||
<p id="autoTagCurrentFile" class="text-gray-100 font-medium truncate">Initializing...</p>
|
||
</div>
|
||
|
||
<!-- Progress Bar -->
|
||
<div class="mb-6">
|
||
<div class="flex justify-between text-sm text-gray-400 mb-2">
|
||
<span>Progress</span>
|
||
<span id="autoTagProgressPercent">0%</span>
|
||
</div>
|
||
<div class="w-full bg-gray-700 rounded-full h-3">
|
||
<div id="autoTagProgressBar" class="bg-purple-600 h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stats -->
|
||
<div class="grid grid-cols-3 gap-4 mb-6">
|
||
<div class="bg-gray-700 rounded-lg p-3 text-center">
|
||
<div class="text-2xl font-bold text-purple-400" id="autoTagProcessed">0</div>
|
||
<div class="text-xs text-gray-400">Processed</div>
|
||
</div>
|
||
<div class="bg-gray-700 rounded-lg p-3 text-center">
|
||
<div class="text-2xl font-bold text-green-400" id="autoTagApplied">0</div>
|
||
<div class="text-xs text-gray-400">Tags Applied</div>
|
||
</div>
|
||
<div class="bg-gray-700 rounded-lg p-3 text-center">
|
||
<div class="text-2xl font-bold text-yellow-400" id="autoTagSkipped">0</div>
|
||
<div class="text-xs text-gray-400">Skipped</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cancel Button -->
|
||
<div class="flex justify-center">
|
||
<button id="cancelAutoTagBtn"
|
||
class="px-6 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg 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-gray-800 rounded-xl p-6 max-w-md w-full mx-4 border border-gray-700">
|
||
<h3 class="text-xl font-semibold text-gray-100 mb-6">📥 Downloading Model</h3>
|
||
|
||
<!-- Model Name -->
|
||
<div class="mb-4">
|
||
<p class="text-gray-400 text-sm">Downloading:</p>
|
||
<p id="downloadModelName" class="text-gray-100 font-medium">GGUF Model</p>
|
||
</div>
|
||
|
||
<!-- Progress Bar -->
|
||
<div class="mb-6">
|
||
<div class="flex justify-between text-sm text-gray-400 mb-2">
|
||
<span id="downloadStatus">Preparing...</span>
|
||
<span id="downloadProgressPercent">0%</span>
|
||
</div>
|
||
<div class="w-full bg-gray-700 rounded-full h-3">
|
||
<div id="downloadProgressBar" class="bg-blue-600 h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cancel Button -->
|
||
<div class="flex justify-center">
|
||
<button id="cancelDownloadBtn"
|
||
class="px-6 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg 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-gray-800 rounded-xl p-6 max-w-2xl w-full mx-4 border border-gray-700 max-h-[90vh] overflow-y-auto custom-scrollbar">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h3 class="text-xl font-semibold text-gray-100">➕ Add New Prompt</h3>
|
||
<button onclick="window.gallery.hideAddPromptModal()" class="p-2 rounded-lg hover:bg-gray-700 transition-colors">
|
||
<svg class="w-5 h-5 text-gray-400" 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-5">
|
||
<label class="block text-sm font-medium text-gray-400 mb-2">Prompt Text <span class="text-red-400">*</span></label>
|
||
<textarea id="addPromptText" rows="4"
|
||
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-gray-100 text-sm resize-none custom-scrollbar"
|
||
placeholder="Enter your prompt text here..."></textarea>
|
||
</div>
|
||
|
||
<!-- Category and Rating Row -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-5">
|
||
<!-- Category -->
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-400 mb-2">Category</label>
|
||
<div class="relative">
|
||
<input type="text" id="addPromptCategory" list="categoryList"
|
||
class="w-full px-4 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-gray-100 text-sm"
|
||
placeholder="Select or type category...">
|
||
<datalist id="categoryList"></datalist>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Rating -->
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-400 mb-2">Rating</label>
|
||
<div class="flex items-center space-x-1" id="addPromptRating">
|
||
<button type="button" class="rating-star text-2xl text-gray-500 hover:text-yellow-400 transition-colors" data-rating="1">☆</button>
|
||
<button type="button" class="rating-star text-2xl text-gray-500 hover:text-yellow-400 transition-colors" data-rating="2">☆</button>
|
||
<button type="button" class="rating-star text-2xl text-gray-500 hover:text-yellow-400 transition-colors" data-rating="3">☆</button>
|
||
<button type="button" class="rating-star text-2xl text-gray-500 hover:text-yellow-400 transition-colors" data-rating="4">☆</button>
|
||
<button type="button" class="rating-star text-2xl text-gray-500 hover:text-yellow-400 transition-colors" data-rating="5">☆</button>
|
||
<button type="button" class="ml-2 text-xs text-gray-500 hover:text-gray-300" id="clearRatingBtn">Clear</button>
|
||
</div>
|
||
<input type="hidden" id="addPromptRatingValue" value="">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tags -->
|
||
<div class="mb-5">
|
||
<label class="block text-sm font-medium text-gray-400 mb-2">Tags</label>
|
||
<div class="relative">
|
||
<div id="addPromptTagsContainer" class="flex flex-wrap gap-2 p-3 bg-gray-700 border border-gray-600 rounded-lg min-h-[44px]">
|
||
<input type="text" id="addPromptTagInput"
|
||
class="flex-1 min-w-[150px] bg-transparent border-none outline-none text-gray-100 text-sm placeholder-gray-500"
|
||
placeholder="Type to add tags...">
|
||
</div>
|
||
<div id="tagSuggestions" class="absolute left-0 right-0 top-full mt-1 bg-gray-700 border border-gray-600 rounded-lg shadow-lg hidden z-10 max-h-48 overflow-y-auto custom-scrollbar"></div>
|
||
</div>
|
||
<p class="text-gray-500 text-xs mt-1">Press Enter or comma to add a tag. Click a tag to remove it.</p>
|
||
</div>
|
||
|
||
<!-- Notes -->
|
||
<div class="mb-5">
|
||
<label class="block text-sm font-medium text-gray-400 mb-2">Notes</label>
|
||
<textarea id="addPromptNotes" rows="2"
|
||
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-green-500 text-gray-100 text-sm resize-none custom-scrollbar"
|
||
placeholder="Optional notes about this prompt..."></textarea>
|
||
</div>
|
||
|
||
<!-- Protection Option -->
|
||
<div class="mb-6">
|
||
<label class="flex items-start p-4 bg-gray-700 rounded-lg cursor-pointer hover:bg-gray-650 border border-gray-600">
|
||
<input type="checkbox" id="addPromptProtected" checked class="w-4 h-4 mt-0.5 text-green-600 bg-gray-700 border-gray-600 rounded focus:ring-green-500">
|
||
<div class="ml-3">
|
||
<span class="text-gray-100 font-medium">Protect from orphan cleanup</span>
|
||
<p class="text-gray-400 text-sm 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 space-x-3">
|
||
<button onclick="window.gallery.hideAddPromptModal()"
|
||
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
|
||
Cancel
|
||
</button>
|
||
<button id="saveNewPromptBtn"
|
||
class="px-6 py-2 bg-green-600 hover:bg-green-700 text-white font-medium rounded-lg transition-colors">
|
||
➕ Add Prompt
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
/* Tag chip styles for Auto Tag feature */
|
||
.tag-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 0.25rem 0.5rem;
|
||
background-color: #374151;
|
||
border: 1px solid #4B5563;
|
||
border-radius: 0.375rem;
|
||
font-size: 0.875rem;
|
||
color: #D1D5DB;
|
||
transition: all 0.2s;
|
||
}
|
||
.tag-chip:hover {
|
||
background-color: #4B5563;
|
||
}
|
||
.tag-chip .tag-remove {
|
||
margin-left: 0.375rem;
|
||
padding: 0.125rem;
|
||
border-radius: 0.25rem;
|
||
cursor: pointer;
|
||
color: #9CA3AF;
|
||
transition: all 0.2s;
|
||
}
|
||
.tag-chip .tag-remove:hover {
|
||
color: #EF4444;
|
||
background-color: rgba(239, 68, 68, 0.2);
|
||
}
|
||
.hover\:bg-gray-650:hover {
|
||
background-color: #525252;
|
||
}
|
||
</style>
|
||
|
||
<script src="/prompt_manager/js/gallery.js"></script>
|
||
</body>
|
||
</html> |