Files
ComfyAssets-ComfyUI_PromptM…/web/admin.html
T
Vito Sansevero a17ecd02c1 fix: pre-compile Tailwind CSS and track vendor libs (#88)
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)
2026-02-07 07:57:31 -08:00

1432 lines
82 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" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PromptManager - Admin Dashboard</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: rgba(0, 0, 0, 0.9) !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;
}
.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;
}
/* Film Strip Styles */
.prompt-film-strip {
--film-strip-thumbnail-size: 112px;
--film-strip-gap: 8px;
display: flex;
flex-wrap: nowrap;
gap: var(--film-strip-gap);
padding: 8px 0;
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: #4b5563 transparent;
}
.prompt-film-strip::-webkit-scrollbar {
height: 4px;
}
.prompt-film-strip::-webkit-scrollbar-track {
background: transparent;
}
.prompt-film-strip::-webkit-scrollbar-thumb {
background: #4b5563;
border-radius: 2px;
}
.film-strip-thumbnail {
flex-shrink: 0;
width: var(--film-strip-thumbnail-size);
height: var(--film-strip-thumbnail-size);
border-radius: 6px;
overflow: hidden;
cursor: pointer;
border: 2px solid transparent;
transition: all 0.15s ease;
background: #1f2937;
}
.film-strip-thumbnail:hover {
border-color: #3b82f6;
transform: scale(1.05);
}
.film-strip-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.film-strip-thumbnail--more {
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #374151 0%, #1f2937 100%);
color: #9ca3af;
font-size: 12px;
font-weight: 600;
}
.film-strip-thumbnail--more:hover {
background: linear-gradient(135deg, #4b5563 0%, #374151 100%);
color: #e5e7eb;
}
.film-strip-empty {
color: #6b7280;
font-size: 12px;
font-style: italic;
padding: 8px 0;
}
/* Film strip size variants */
.film-strip-size-small {
--film-strip-thumbnail-size: 72px;
--film-strip-gap: 6px;
}
.film-strip-size-medium {
--film-strip-thumbnail-size: 112px;
--film-strip-gap: 8px;
}
.film-strip-size-large {
--film-strip-thumbnail-size: 144px;
--film-strip-gap: 10px;
}
</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-blue-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-blue-400 to-purple-400 bg-clip-text text-transparent">
PromptManager Admin Dashboard
</h1>
<p class="text-gray-400 text-sm">Manage and organize your AI prompts with powerful search and editing tools</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>
<!-- Dashboard View -->
<div id="dashboardView">
<!-- Stats Grid -->
<div class="px-6 py-6">
<div class="max-w-7xl mx-auto">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-gradient-to-br from-gray-800 to-gray-850 rounded-xl p-6 border border-gray-700">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-400 text-sm font-medium">Total Prompts</p>
<p class="text-3xl font-bold text-blue-400" id="totalPrompts">-</p>
</div>
<div class="w-12 h-12 bg-blue-500/20 rounded-lg flex items-center justify-center">
<span class="text-2xl">📝</span>
</div>
</div>
</div>
<div class="bg-gradient-to-br from-gray-800 to-gray-850 rounded-xl p-6 border border-gray-700">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-400 text-sm font-medium">Categories</p>
<p class="text-3xl font-bold text-purple-400" id="totalCategories">-</p>
</div>
<div class="w-12 h-12 bg-purple-500/20 rounded-lg flex items-center justify-center">
<span class="text-2xl">📁</span>
</div>
</div>
</div>
<div class="bg-gradient-to-br from-gray-800 to-gray-850 rounded-xl p-6 border border-gray-700 cursor-pointer hover:border-green-500/50 transition-colors"
onclick="window.location.hash = '#/tags'" title="Click to manage tags">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-400 text-sm font-medium">Unique Tags</p>
<p class="text-3xl font-bold text-green-400" id="totalTags">-</p>
</div>
<div class="w-12 h-12 bg-green-500/20 rounded-lg flex items-center justify-center">
<span class="text-2xl">🏷️</span>
</div>
</div>
</div>
<div class="bg-gradient-to-br from-gray-800 to-gray-850 rounded-xl p-6 border border-gray-700">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-400 text-sm font-medium">Avg Rating</p>
<p class="text-3xl font-bold text-yellow-400" id="avgRating">-</p>
</div>
<div class="w-12 h-12 bg-yellow-500/20 rounded-lg flex items-center justify-center">
<span class="text-2xl">⭐</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Search Controls (Sticky) -->
<div class="sticky top-0 z-50 bg-gray-925 bg-opacity-95 backdrop-blur-sm border-b border-gray-700/50">
<div class="px-6 py-4">
<div class="max-w-7xl mx-auto">
<div class="bg-gradient-to-br from-gray-800 to-gray-850 rounded-xl p-6 border border-gray-700">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-4 mb-6">
<div class="lg:col-span-2">
<label class="block text-sm font-medium text-gray-400 mb-2">Search Text</label>
<input type="text" id="searchText" placeholder="Search prompt content..."
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100 placeholder-gray-400">
</div>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Category</label>
<select id="searchCategory"
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
<option value="">All Categories</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Tags</label>
<input type="text" id="searchTags" placeholder="tag1, tag2..."
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100 placeholder-gray-400">
</div>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Sort By</label>
<select id="sortBy"
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
<option value="created_desc">Newest First</option>
<option value="created_asc">Oldest First</option>
<option value="rating_desc">Highest Rated</option>
<option value="rating_asc">Lowest Rated</option>
<option value="text_asc">A-Z</option>
<option value="text_desc">Z-A</option>
</select>
</div>
<div class="flex items-end">
<button id="searchBtn"
class="w-full bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-medium py-3 px-6 rounded-lg transition-all duration-200 flex items-center justify-center space-x-2">
<span>🔍</span>
<span>Search</span>
</button>
</div>
</div>
<!-- Bulk Actions -->
<div class="flex flex-wrap items-center gap-3 pt-4 border-t border-gray-700">
<label class="flex items-center space-x-2 text-gray-300">
<input type="checkbox" id="selectAll" class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500">
<span class="text-sm font-medium">Select All</span>
</label>
<button id="bulkDeleteBtn" disabled
class="px-4 py-2 bg-red-600 hover:bg-red-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white text-sm font-medium rounded-lg transition-colors">
🗑️ Delete Selected
</button>
<button id="bulkTagBtn" disabled
class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white text-sm font-medium rounded-lg transition-colors">
🏷️ Add Tags
</button>
<button id="bulkCategoryBtn" disabled
class="px-4 py-2 bg-purple-600 hover:bg-purple-700 disabled:bg-gray-600 disabled:cursor-not-allowed text-white text-sm font-medium rounded-lg transition-colors">
📁 Set Category
</button>
<button id="addPromptBtn"
class="px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white text-sm font-medium rounded-lg transition-colors">
➕ Add Prompt
</button>
<button id="statsBtn"
class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-medium rounded-lg transition-colors">
📊 Stats
</button>
<button id="exportBtn"
class="px-4 py-2 bg-yellow-600 hover:bg-yellow-700 text-white text-sm font-medium rounded-lg transition-colors">
📄 Export
</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="diagnosticsBtn"
class="px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white text-sm font-medium rounded-lg transition-colors">
🔍 Diagnostics
</button>
<button id="maintenanceBtn"
class="px-4 py-2 bg-teal-600 hover:bg-teal-700 text-white text-sm font-medium rounded-lg transition-colors">
🔧 Maintenance
</button>
<button id="backupBtn"
class="px-4 py-2 bg-sky-600 hover:bg-sky-700 text-white text-sm font-medium rounded-lg transition-colors">
💾 Backup DB
</button>
<button id="restoreBtn"
class="px-4 py-2 bg-amber-600 hover:bg-amber-700 text-white text-sm font-medium rounded-lg transition-colors">
📁 Restore DB
</button>
<button id="scanBtn"
class="px-4 py-2 bg-pink-600 hover:bg-pink-700 text-white text-sm font-medium rounded-lg transition-colors">
📸 Scan Images
</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="logsBtn"
class="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-lg transition-colors">
📋 View Logs
</button>
<button id="metadataBtn"
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors">
🔍 Metadata
</button>
<button id="galleryBtn"
class="px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white text-sm font-medium rounded-lg transition-colors">
🖼️ Gallery
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Results Section -->
<div class="flex-1 px-6 pb-6">
<div class="max-w-7xl mx-auto h-full">
<div class="bg-gradient-to-br from-gray-800 to-gray-850 rounded-xl border border-gray-700 h-full flex flex-col">
<!-- Results Header -->
<div class="flex items-center justify-between p-6 border-b border-gray-700">
<div class="flex items-center space-x-4">
<h2 class="text-xl font-semibold text-gray-100" id="resultsTitle">Recent Prompts</h2>
<div class="flex items-center space-x-2">
<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="25">25</option>
<option value="50" selected>50</option>
<option value="100">100</option>
<option value="200">200</option>
<option value="500">500</option>
</select>
<span class="text-sm text-gray-400">per page</span>
</div>
</div>
<span class="text-sm font-medium text-gray-400 bg-gray-700 px-3 py-1 rounded-full" id="resultsCount">Loading...</span>
</div>
<!-- Loading State -->
<div id="loadingState" class="flex-1 flex items-center justify-center">
<div class="text-center">
<div class="w-12 h-12 border-4 border-blue-500/30 border-t-blue-500 rounded-full animate-spin mx-auto mb-4"></div>
<p class="text-gray-400">Loading prompts...</p>
</div>
</div>
<!-- Results List -->
<div id="resultsList" class="flex-1 overflow-y-auto custom-scrollbar p-6 space-y-4 hidden">
<!-- Prompts will be inserted here -->
</div>
<!-- Pagination Controls -->
<div id="paginationControls" class="border-t border-gray-700 p-4 hidden">
<div class="flex items-center justify-between">
<div class="text-sm text-gray-400">
Showing <span id="showingStart">1</span> to <span id="showingEnd">50</span> of <span id="totalResults">0</span> prompts
</div>
<div class="flex items-center space-x-2">
<button id="firstPageBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-gray-300 rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm">
First
</button>
<button id="prevPageBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-gray-300 rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm">
Previous
</button>
<span class="text-sm text-gray-400">
Page <span id="currentPage">1</span> of <span id="totalPages">1</span>
</span>
<button id="nextPageBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-gray-300 rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm">
Next
</button>
<button id="lastPageBtn" class="px-3 py-1 bg-gray-700 hover:bg-gray-600 text-gray-300 rounded disabled:opacity-50 disabled:cursor-not-allowed text-sm">
Last
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div><!-- /dashboardView -->
<!-- Tags Page View -->
<div id="tagsPageView" class="hidden flex-1 flex flex-col">
<!-- Tags Header -->
<div class="px-6 py-4 border-b border-gray-700/50 bg-gray-850/50">
<div class="max-w-7xl mx-auto flex items-center justify-between">
<div class="flex items-center space-x-4">
<button onclick="window.location.hash=''" class="text-gray-400 hover:text-white transition-colors text-sm flex items-center space-x-1">
<span>&larr;</span><span>Dashboard</span>
</button>
<h2 class="text-xl font-semibold text-gray-100">Tags</h2>
<span id="tagsTotalBadge" class="text-sm font-medium text-gray-400 bg-gray-700 px-3 py-1 rounded-full">0 tags</span>
</div>
<div id="filterModeToggle" class="hidden items-center space-x-2">
<span class="text-sm text-gray-400">Filter:</span>
<button id="filterModeAnd" class="px-3 py-1 text-xs font-medium rounded bg-green-600 text-white">AND</button>
<button id="filterModeOr" class="px-3 py-1 text-xs font-medium rounded bg-gray-700 text-gray-300 hover:bg-gray-600">OR</button>
</div>
</div>
</div>
<!-- Two-Panel Layout -->
<div class="flex-1 flex overflow-hidden">
<!-- Left Panel: Tag List -->
<div class="w-80 flex-shrink-0 border-r border-gray-700/50 flex flex-col bg-gray-850/30">
<!-- Tag Search & Sort -->
<div class="p-4 space-y-3 border-b border-gray-700/30">
<input type="text" id="tagSearchInput" placeholder="Search tags..."
class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 text-sm focus:ring-2 focus:ring-green-500 focus:border-green-500">
<select id="tagSortSelect" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 text-sm">
<option value="alpha_asc">A &rarr; Z</option>
<option value="alpha_desc">Z &rarr; A</option>
<option value="count_desc">Most Used</option>
<option value="count_asc">Least Used</option>
</select>
</div>
<!-- Tag List -->
<div id="tagsListContainer" class="flex-1 overflow-y-auto custom-scrollbar">
<div id="tagsList" class="p-2 space-y-1"></div>
<div id="tagsLoader" class="text-center">
<div class="tags-loader-spinner p-4 hidden">
<div class="w-6 h-6 border-2 border-green-500/30 border-t-green-500 rounded-full animate-spin mx-auto"></div>
</div>
</div>
</div>
</div>
<!-- Right Panel: Prompts Display -->
<div class="flex-1 flex flex-col overflow-hidden">
<!-- Active Tags Pills -->
<div id="activeTagsPills" class="px-6 py-3 border-b border-gray-700/30 hidden">
<div class="flex flex-wrap gap-2 items-center">
<span class="text-sm text-gray-400 mr-1">Filtering:</span>
<div id="tagPillsContainer" class="flex flex-wrap gap-2"></div>
<span id="tagPromptsCountBadge" class="text-xs text-gray-500 ml-auto"></span>
</div>
</div>
<!-- Prompts Grid -->
<div id="tagPromptsContainer" class="flex-1 overflow-y-auto custom-scrollbar">
<div id="tagPromptsGrid" class="p-6 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4"></div>
<div id="tagPromptsLoader" class="text-center">
<div class="prompts-loader-spinner p-4 hidden">
<div class="w-6 h-6 border-2 border-green-500/30 border-t-green-500 rounded-full animate-spin mx-auto"></div>
</div>
<button id="loadMorePromptsBtn" class="hidden px-4 py-2 my-3 text-sm text-gray-400 hover:text-green-400 border border-gray-700 hover:border-green-500/50 rounded-lg transition-colors">
Load more prompts
</button>
</div>
</div>
<!-- Empty State -->
<div id="tagPromptsEmpty" class="flex-1 flex items-center justify-center">
<div class="text-center text-gray-500">
<span class="text-4xl block mb-3">🏷️</span>
<p class="text-lg font-medium">Select a tag to view prompts</p>
<p class="text-sm mt-1">Click a tag from the list or use checkboxes for multi-tag filtering</p>
</div>
</div>
</div>
</div>
<!-- Context Menu for Tag Operations -->
<div id="tagContextMenu" class="hidden fixed z-50 bg-gray-800 border border-gray-600 rounded-lg shadow-xl py-1 min-w-[160px]">
</div>
</div><!-- /tagsPageView -->
</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 p-6 max-w-lg w-full mx-4 border border-gray-700 max-h-[90vh] overflow-y-auto">
<h3 class="text-xl font-semibold text-gray-100 mb-6">⚙️ Settings</h3>
<div class="space-y-6">
<!-- Gallery Settings Section -->
<div class="border-b border-gray-700 pb-4">
<h4 class="text-sm font-semibold text-gray-300 mb-4 uppercase tracking-wide">📁 Gallery Settings</h4>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-400 mb-2">Image Scan Directory</label>
<input type="text" id="galleryRootPath" placeholder="Leave empty for auto-detect"
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
<p class="text-xs text-gray-500 mt-2">Custom path to scan for images. Leave empty to use ComfyUI's default output directory.</p>
</div>
<div class="mb-4">
<div class="flex items-center justify-between">
<label class="block text-sm font-medium text-gray-400">Currently Monitoring</label>
<button id="refreshMonitoringStatus" class="text-xs text-blue-400 hover:text-blue-300">🔄 Refresh</button>
</div>
<div id="monitoringStatus" class="mt-2 px-3 py-2 bg-gray-900 rounded-lg text-xs text-gray-400 font-mono">
Loading...
</div>
</div>
</div>
<!-- UI Settings Section -->
<div class="border-b border-gray-700 pb-4">
<h4 class="text-sm font-semibold text-gray-300 mb-4 uppercase tracking-wide">🖥️ UI Settings</h4>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-400 mb-2">Search Results Auto-Hide (seconds)</label>
<input type="number" id="resultTimeout" min="0" max="300" value="30"
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
<p class="text-xs text-gray-500 mt-2">Set to 0 to disable auto-hide. Controls how long search results stay visible in the ComfyUI node.</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Web UI Display Mode</label>
<select id="webuiDisplayMode"
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
<option value="popup">Popup Window (Current)</option>
<option value="newtab">New Tab</option>
</select>
<p class="text-xs text-gray-500 mt-2">Choose how the Web UI opens when clicked from ComfyUI nodes.</p>
</div>
</div>
</div>
<div class="flex justify-end space-x-3 mt-8">
<button id="cancelSettings"
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
Cancel
</button>
<button id="saveSettings"
class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
Save Settings
</button>
</div>
</div>
</div>
<!-- Bulk Tag Modal -->
<div id="bulkTagModal" 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">🏷️ Add Tags to Selected Prompts</h3>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Tags (comma-separated)</label>
<input type="text" id="bulkTagInput" placeholder="tag1, tag2, tag3..."
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
</div>
<div class="flex justify-end space-x-3 mt-6">
<button id="cancelBulkTag"
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
Cancel
</button>
<button id="confirmBulkTag"
class="px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors">
Add Tags
</button>
</div>
</div>
</div>
<!-- Individual Tag Modal -->
<div id="individualTagModal" 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">🏷️ Add Tags to Prompt</h3>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Tags (comma-separated)</label>
<input type="text" id="individualTagInput" placeholder="tag1, tag2, tag3..."
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
</div>
<div class="flex justify-end space-x-3 mt-6">
<button id="cancelIndividualTag"
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
Cancel
</button>
<button id="confirmIndividualTag"
class="px-6 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors">
Add Tags
</button>
</div>
</div>
</div>
<!-- Bulk Category Modal -->
<div id="bulkCategoryModal" 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">📁 Set Category for Selected Prompts</h3>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Category</label>
<input type="text" id="bulkCategoryInput" placeholder="Enter category name..."
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
</div>
<div class="flex justify-end space-x-3 mt-6">
<button id="cancelBulkCategory"
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
Cancel
</button>
<button id="confirmBulkCategory"
class="px-6 py-2 bg-purple-600 hover:bg-purple-700 text-white font-medium rounded-lg transition-colors">
Set Category
</button>
</div>
</div>
</div>
<!-- Database Restore Modal -->
<div id="restoreModal" 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">📁 Restore Database</h3>
<div class="mb-4">
<div class="bg-yellow-900/20 border border-yellow-600 rounded-lg p-4 mb-4">
<div class="flex items-start space-x-2">
<span class="text-yellow-400 text-xl">⚠️</span>
<div>
<h4 class="text-yellow-400 font-medium">Warning</h4>
<p class="text-gray-300 text-sm">This will replace your current database. A backup will be created automatically.</p>
</div>
</div>
</div>
<label class="block text-sm font-medium text-gray-400 mb-2">Select Database File (.db)</label>
<input type="file" id="restoreFileInput" accept=".db"
class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
</div>
<div class="flex justify-end space-x-3 mt-6">
<button id="cancelRestore"
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
Cancel
</button>
<button id="confirmRestore"
class="px-6 py-2 bg-amber-600 hover:bg-amber-700 text-white font-medium rounded-lg transition-colors">
Restore Database
</button>
</div>
</div>
</div>
<!-- Diagnostics Modal -->
<div id="diagnosticsModal" 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">🔍 Gallery System Diagnostics</h3>
<button onclick="window.admin.closeDiagnostics()" 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-120px)] custom-scrollbar">
<div class="flex items-center justify-between mb-4">
<button id="runDiagnosticsBtn" class="px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white font-medium rounded-lg transition-colors">
🔍 Run Diagnostics
</button>
<button id="testImageLinkBtn" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
🧪 Test Image Link
</button>
</div>
<div id="diagnosticsContent" class="space-y-4">
<div class="text-center py-8 text-gray-400">
Click "Run Diagnostics" to check the gallery system status
</div>
</div>
</div>
</div>
</div>
<!-- Maintenance Modal -->
<div id="maintenanceModal" 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">🔧 Database Maintenance</h3>
<button onclick="window.admin.closeMaintenance()" 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-120px)] custom-scrollbar">
<!-- Maintenance Options -->
<div class="space-y-4 mb-6">
<h4 class="text-lg font-medium text-gray-100 mb-4">Select Maintenance Operations:</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label class="flex items-center space-x-3 p-4 bg-gray-700 rounded-lg hover:bg-gray-600 cursor-pointer">
<input type="checkbox" id="cleanup_duplicates" class="maintenance-option w-4 h-4 text-teal-600 bg-gray-600 border-gray-500 rounded focus:ring-teal-500" checked>
<div class="flex-1">
<div class="text-white font-medium">🗑️ Remove Duplicates</div>
<div class="text-sm text-gray-400">Find and remove duplicate prompts based on text content</div>
</div>
</label>
<label class="flex items-center space-x-3 p-4 bg-gray-700 rounded-lg hover:bg-gray-600 cursor-pointer">
<input type="checkbox" id="vacuum" class="maintenance-option w-4 h-4 text-teal-600 bg-gray-600 border-gray-500 rounded focus:ring-teal-500" checked>
<div class="flex-1">
<div class="text-white font-medium">⚡ Vacuum Database</div>
<div class="text-sm text-gray-400">Optimize database file size and performance</div>
</div>
</label>
<label class="flex items-center space-x-3 p-4 bg-gray-700 rounded-lg hover:bg-gray-600 cursor-pointer">
<input type="checkbox" id="cleanup_orphaned_images" class="maintenance-option w-4 h-4 text-teal-600 bg-gray-600 border-gray-500 rounded focus:ring-teal-500" checked>
<div class="flex-1">
<div class="text-white font-medium">🖼️ Clean Orphaned Images</div>
<div class="text-sm text-gray-400">Remove image records for files that no longer exist</div>
</div>
</label>
<label class="flex items-center space-x-3 p-4 bg-gray-700 rounded-lg hover:bg-gray-600 cursor-pointer">
<input type="checkbox" id="check_hash_duplicates" class="maintenance-option w-4 h-4 text-teal-600 bg-gray-600 border-gray-500 rounded focus:ring-teal-500">
<div class="flex-1">
<div class="text-white font-medium">🔍 Check Hash Duplicates</div>
<div class="text-sm text-gray-400">Identify prompts with duplicate hash values</div>
</div>
</label>
<label class="flex items-center space-x-3 p-4 bg-gray-700 rounded-lg hover:bg-gray-600 cursor-pointer">
<input type="checkbox" id="check_consistency" class="maintenance-option w-4 h-4 text-teal-600 bg-gray-600 border-gray-500 rounded focus:ring-teal-500">
<div class="flex-1">
<div class="text-white font-medium">🔧 Consistency Check</div>
<div class="text-sm text-gray-400">Verify database integrity and data consistency</div>
</div>
</label>
<label class="flex items-center space-x-3 p-4 bg-gray-700 rounded-lg hover:bg-gray-600 cursor-pointer">
<input type="checkbox" id="statistics" class="maintenance-option w-4 h-4 text-teal-600 bg-gray-600 border-gray-500 rounded focus:ring-teal-500">
<div class="flex-1">
<div class="text-white font-medium">📊 Database Statistics</div>
<div class="text-sm text-gray-400">Generate detailed database statistics report</div>
</div>
</label>
<label class="flex items-center space-x-3 p-4 bg-gray-700 rounded-lg hover:bg-gray-600 cursor-pointer">
<input type="checkbox" id="prune_orphaned_prompts" class="maintenance-option w-4 h-4 text-teal-600 bg-gray-600 border-gray-500 rounded focus:ring-teal-500">
<div class="flex-1">
<div class="text-white font-medium">🚮 Prune Orphaned Prompts</div>
<div class="text-sm text-gray-400">Remove prompts that have no linked images</div>
</div>
</label>
</div>
</div>
<!-- Action Buttons -->
<div class="flex items-center justify-between mb-6">
<div class="flex space-x-3">
<button id="runMaintenanceBtn" class="px-6 py-3 bg-teal-600 hover:bg-teal-700 text-white font-medium rounded-lg transition-colors">
🔧 Run Maintenance
</button>
<button id="selectAllMaintenanceBtn" class="px-4 py-3 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
✅ Select All
</button>
<button id="clearAllMaintenanceBtn" class="px-4 py-3 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
❌ Clear All
</button>
</div>
</div>
<!-- Results -->
<div id="maintenanceResults" class="space-y-4">
<div class="text-center py-8 text-gray-400">
Click "Run Maintenance" to perform selected operations
</div>
</div>
</div>
</div>
</div>
<!-- Gallery Modal -->
<div id="galleryModal" class="fixed inset-0 bg-black bg-opacity-75 hidden items-center justify-center z-50">
<div class="bg-gray-800 rounded-xl max-w-6xl 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" id="galleryTitle">Generated Images</h3>
<button onclick="window.admin.closeGallery()" 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-120px)] custom-scrollbar">
<div id="galleryContent" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Gallery images will be loaded here -->
</div>
<div id="galleryEmpty" class="text-center py-12 hidden">
<div class="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-4">
<span class="text-2xl">🖼️</span>
</div>
<h4 class="text-lg font-medium text-gray-300 mb-2">No images found</h4>
<p class="text-gray-500">No images have been generated with this prompt yet.</p>
</div>
<div id="galleryLoading" class="text-center py-12">
<div class="w-12 h-12 border-4 border-purple-500/30 border-t-purple-500 rounded-full animate-spin mx-auto mb-4"></div>
<p class="text-gray-400">Loading images...</p>
</div>
</div>
</div>
</div>
<!-- Scan Modal -->
<div id="scanModal" 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">
<h3 class="text-xl font-semibold text-gray-100 mb-6">📸 Scan ComfyUI Output Images</h3>
<div class="space-y-4">
<div class="bg-orange-900/30 border border-orange-600 rounded-lg p-4">
<div class="flex items-start space-x-3">
<span class="text-orange-400 text-xl">⚠️</span>
<div>
<h4 class="text-orange-300 font-medium mb-2">Important Information</h4>
<ul class="text-orange-200 text-sm space-y-1">
<li>• This will scan all PNG files in your ComfyUI output directory</li>
<li>• Depending on the size of your output folder, this could take several minutes</li>
<li>• This is an experimental feature - we recommend backing up your database first</li>
<li>• Only images with ComfyUI metadata will be processed</li>
<li>• Duplicate prompts will be automatically detected and skipped</li>
</ul>
</div>
</div>
</div>
<div class="bg-blue-900/30 border border-blue-600 rounded-lg p-4">
<div class="flex items-center justify-between">
<span class="text-blue-300 font-medium">Backup Database First (Recommended)</span>
<button id="quickBackupBtn"
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors">
💾 Backup Now
</button>
</div>
</div>
<div id="scanProgress" class="hidden space-y-3">
<div class="bg-gray-700 rounded-lg p-4">
<div class="flex items-center justify-between mb-2">
<span class="text-gray-300 text-sm font-medium">Scanning Progress</span>
<span id="scanStatusText" class="text-gray-400 text-sm">Preparing...</span>
</div>
<div class="w-full bg-gray-600 rounded-full h-2">
<div id="scanProgressBar" class="bg-pink-600 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
</div>
<div class="flex justify-between text-xs text-gray-400 mt-2">
<span id="scanCount">0 files processed</span>
<span id="scanFound">0 prompts found</span>
</div>
</div>
</div>
</div>
<div class="flex justify-end space-x-3 mt-6">
<button id="cancelScan"
class="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
Cancel
</button>
<button id="startScan"
class="px-6 py-2 bg-pink-600 hover:bg-pink-700 text-white font-medium rounded-lg transition-colors">
Start Scan
</button>
</div>
</div>
</div>
<!-- Logs Modal -->
<div id="logsModal" 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-6xl 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">📋 System Logs</h3>
<button onclick="window.admin.closeLogs()" 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>
<!-- Log Controls -->
<div class="p-6 border-b border-gray-700">
<div class="grid grid-cols-1 md:grid-cols-5 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Log Level</label>
<select id="logLevel" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
<option value="">All Levels</option>
<option value="DEBUG">Debug</option>
<option value="INFO">Info</option>
<option value="WARNING">Warning</option>
<option value="ERROR">Error</option>
<option value="CRITICAL">Critical</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Limit</label>
<select id="logLimit" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
<option value="50">Last 50</option>
<option value="100" selected>Last 100</option>
<option value="200">Last 200</option>
<option value="500">Last 500</option>
</select>
</div>
<div class="flex items-end">
<button id="refreshLogsBtn" class="w-full px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors">
🔄 Refresh
</button>
</div>
<div class="flex items-end">
<button id="downloadLogsBtn" class="w-full px-4 py-2 bg-green-600 hover:bg-green-700 text-white font-medium rounded-lg transition-colors">
📥 Download
</button>
</div>
<div class="flex items-end">
<button id="clearLogsBtn" class="w-full px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors">
🗑️ Clear Logs
</button>
</div>
</div>
<!-- Log Stats -->
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-4">
<div class="bg-gray-700 rounded-lg p-3">
<div class="text-xs text-gray-400">Total Logs</div>
<div class="text-lg font-semibold text-blue-400" id="logStatsTotal">-</div>
</div>
<div class="bg-gray-700 rounded-lg p-3">
<div class="text-xs text-gray-400">Errors</div>
<div class="text-lg font-semibold text-red-400" id="logStatsErrors">-</div>
</div>
<div class="bg-gray-700 rounded-lg p-3">
<div class="text-xs text-gray-400">Warnings</div>
<div class="text-lg font-semibold text-yellow-400" id="logStatsWarnings">-</div>
</div>
<div class="bg-gray-700 rounded-lg p-3">
<div class="text-xs text-gray-400">Current Level</div>
<div class="text-lg font-semibold text-green-400" id="logStatsLevel">-</div>
</div>
<div class="bg-gray-700 rounded-lg p-3">
<div class="text-xs text-gray-400">Log Size</div>
<div class="text-lg font-semibold text-purple-400" id="logStatsSize">-</div>
</div>
</div>
<!-- Log Settings -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-400 mb-2">Set Log Level</label>
<select id="setLogLevel" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-100">
<option value="DEBUG">Debug</option>
<option value="INFO" selected>Info</option>
<option value="WARNING">Warning</option>
<option value="ERROR">Error</option>
<option value="CRITICAL">Critical</option>
</select>
</div>
<div class="flex items-center">
<label class="flex items-center space-x-2 text-gray-300">
<input type="checkbox" id="consoleLogging" checked class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500">
<span class="text-sm">Console Logging</span>
</label>
</div>
<div class="flex items-end">
<button id="updateLogConfigBtn" class="w-full px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors">
💾 Update Config
</button>
</div>
</div>
</div>
<!-- Log Viewer -->
<div class="flex-1 overflow-hidden">
<div class="h-96 overflow-y-auto custom-scrollbar bg-gray-900 font-mono text-sm">
<div id="logsContainer" class="p-4 space-y-1">
<div class="text-center py-8 text-gray-500">
Click "Refresh" to load logs
</div>
</div>
</div>
</div>
<!-- Log Files -->
<div class="p-6 border-t border-gray-700">
<h4 class="text-lg font-medium text-gray-100 mb-4">Log Files</h4>
<div id="logFilesList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
<!-- Log files will be inserted here -->
</div>
</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.admin.hideModal('addPromptModal')" 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="addPromptCategoryList"
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="addPromptCategoryList"></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="clearAddPromptRatingBtn">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="addPromptTagSuggestions" 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.admin.hideModal('addPromptModal')"
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>
<!-- 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>
<!-- 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>
<!-- Tagged Images Handling (for Auto Tag mode) -->
<div class="mb-6">
<label class="block text-sm font-medium text-gray-400 mb-3">Already Tagged Images</label>
<div class="space-y-2">
<label class="flex items-center p-3 bg-gray-700 rounded-lg cursor-pointer hover:bg-gray-650 border border-gray-600">
<input type="radio" name="autoTagMode" value="skip" checked class="w-4 h-4 text-purple-600 bg-gray-700 border-gray-600 focus:ring-purple-500">
<span class="ml-3 text-gray-100">Skip images with existing tags</span>
</label>
<label class="flex items-center p-3 bg-gray-700 rounded-lg cursor-pointer hover:bg-gray-650 border border-gray-600">
<input type="radio" name="autoTagMode" value="retag" class="w-4 h-4 text-purple-600 bg-gray-700 border-gray-600 focus:ring-purple-500">
<span class="ml-3 text-gray-100">Re-tag all images</span>
</label>
</div>
<p class="text-gray-500 text-xs mt-2">Note: "auto-scanned" is not counted as a real tag</p>
</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.admin.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="p-4 bg-gray-900 rounded-lg">
<!-- Visible tags (first row) -->
<div id="reviewTagsVisible" class="flex flex-wrap gap-2">
<!-- First row of tags -->
</div>
<!-- Accordion for remaining tags -->
<div id="reviewTagsAccordion" class="hidden mt-3">
<div id="reviewTagsHidden" class="flex flex-wrap gap-2 max-h-[180px] overflow-y-auto custom-scrollbar">
<!-- Remaining tags -->
</div>
</div>
<!-- Toggle button -->
<button id="reviewTagsToggle" class="hidden mt-3 text-sm text-blue-400 hover:text-blue-300 transition-colors flex items-center gap-1">
<span id="reviewTagsToggleIcon">▼</span>
<span id="reviewTagsToggleText">Show all tags</span>
<span id="reviewTagsCount" class="text-gray-500"></span>
</button>
</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.admin.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 Loading Model Modal -->
<div id="autoTagLoadingModal" 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 text-center">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
<h3 class="text-xl font-semibold text-gray-100 mb-2">Loading Model</h3>
<p id="autoTagLoadingStatus" class="text-gray-400">Initializing...</p>
<p class="text-gray-500 text-sm mt-4">This may take 30-60 seconds on first load</p>
</div>
</div>
<!-- Re-tag Confirmation Modal -->
<div id="retagConfirmModal" 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">
<h3 class="text-xl font-semibold text-gray-100 mb-4">🏷️ Image Already Tagged</h3>
<!-- Image preview -->
<div class="mb-4">
<img id="retagPreviewImage" src="" alt="Preview" class="w-full h-48 object-contain bg-gray-900 rounded-lg">
<p id="retagImageName" class="text-gray-400 text-sm mt-2 text-center truncate"></p>
</div>
<!-- Existing tags -->
<div class="mb-4">
<p class="text-gray-300 text-sm mb-2">Current tags:</p>
<div id="retagExistingTags" class="flex flex-wrap gap-1 max-h-24 overflow-y-auto">
<!-- Tags will be inserted here -->
</div>
</div>
<p class="text-gray-400 mb-6">This image already has tags. Would you like to generate new tags?</p>
<!-- Buttons -->
<div class="flex gap-3 justify-end">
<button id="retagSkipBtn"
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
Skip
</button>
<button id="retagSkipAllBtn"
class="px-4 py-2 bg-yellow-600 hover:bg-yellow-700 text-white font-medium rounded-lg transition-colors">
Skip All Tagged
</button>
<button id="retagConfirmBtn"
class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white font-medium rounded-lg transition-colors">
Re-tag
</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>
<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/admin.js"></script>
<script src="/prompt_manager/js/tags-page.js"></script>
</body>
</html>