* test(legacy): snapshot the routes and the settings response Pins every registered route and the key paths of GET /prompt_manager/settings while System S13 (#330) moves the legacy pages' tools to native System. Recorded on the unchanged main; cutover (#334) removes it. * test(legacy): prove each removed legacy control has native coverage TESTING.md gains the "Legacy System UI removed by S13" table (no rows yet). legacy-coverage.test.mjs fails when a removal cites a capability that is not covered, copies another native test, lists an id or method twice, or leaves the id or method on its page. legacy-system-links.spec.mjs holds the System link table and guards the kept admin flows. * refactor(admin): open System for Diagnostics and logs The admin page's Diagnostics and View Logs buttons become links that open System › Diagnostics & logs in a new tab. The legacy Diagnostics and System Logs dialogs and their methods go; TESTING.md lists them against their covered capabilities (rows B1–B7). * refactor(admin): open System for settings and keep page options Settings opens System › Overview in a new tab. The settings dialog becomes Page options (opened from the list header) with only the Infinite Scrolling default, and its Save posts {"infinite_scroll": …} alone, so a stale legacy form can no longer overwrite scan folders or other values edited in System (#330 S13-9). Scan folders, monitoring status, auto-hide, display mode, worker threads, LoraManager and the LoRA import leave the page (rows B8–B14). * refactor(admin): open System for maintenance Maintenance opens System › Maintenance in a new tab, where each operation runs as a reviewed Activity job. The one-click legacy Database Maintenance dialog and its methods go (rows B15–B18); formatFileSize stays for the image modal. * refactor(admin): open System for backup and restore Backup DB and Restore DB open System › Backup & export in a new tab, where a restore is staged, reviewed and run as an Activity job. The one-click Restore dialog and its methods go (rows B19–B20); the scan dialog's quick backup stays. The removed browser tests' data checks live on at the route: a new round trip test restores every prompt with its tags and image links. * test(legacy): find a removed modal id passed to showModal and settle the links spec The removal check also finds an id passed as a string to a ...Modal() call and no longer reads data-id= as id=. The no-request test waits for the page's own start-up reads to finish, the sort check compares as the server sorts, and the kept-flow test checks the added prompt shows in the list. * test(admin): open Page options after the saved default is read Drops the leftover LoraManager section comment above the Page options save. * test(legacy): a lazy thumbnail load is not a System link's request
1028 lines
64 KiB
HTML
1028 lines
64 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>PromptManager - Admin Dashboard</title>
|
||
<script src="js/theme-bridge.js"></script>
|
||
<script src="js/html-escape.js"></script>
|
||
<script src="js/image-helpers.js"></script>
|
||
<script src="js/api-client.js"></script>
|
||
<script src="js/png-metadata.js"></script>
|
||
<script src="js/list-state.js"></script>
|
||
<script src="js/tag-autocomplete.js"></script>
|
||
<script src="js/sse-stream.js"></script>
|
||
<script src="js/scan-progress.js"></script>
|
||
<script src="js/data-actions.js"></script>
|
||
<link rel="stylesheet" href="lib/tailwind/comfyui-theme.css">
|
||
<link rel="stylesheet" href="lib/tailwind/styles.css">
|
||
<link rel="stylesheet" href="lib/viewerjs/viewer.min.css">
|
||
<script src="lib/viewerjs/viewer.min.js"></script>
|
||
</head>
|
||
<body class="bg-pm-primary text-pm min-h-screen text-[13px]">
|
||
<div class="min-h-screen flex flex-col">
|
||
<!-- Header -->
|
||
<header class="bg-pm-surface border-b border-pm px-4 py-2" style="min-height: 2.5rem;">
|
||
<div class="max-w-7xl mx-auto">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex items-center gap-2">
|
||
<h1 class="text-sm font-semibold text-pm">
|
||
PromptManager Admin
|
||
</h1>
|
||
</div>
|
||
<div class="hidden sm:flex items-center gap-1.5">
|
||
<div class="w-2 h-2 bg-pm-success rounded-full animate-pulse"></div>
|
||
<span class="text-pm-success text-xs">Online</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Dashboard View -->
|
||
<div id="dashboardView">
|
||
|
||
<!-- Stats Grid -->
|
||
<div class="px-4 py-3">
|
||
<div class="max-w-7xl mx-auto">
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||
<div class="bg-pm-surface rounded-pm-lg p-3 border border-pm shadow-pm-sm">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-pm-secondary text-xs font-medium uppercase tracking-wide">Total Prompts</p>
|
||
<p class="text-lg font-bold text-pm-accent" id="totalPrompts">-</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="bg-pm-surface rounded-pm-lg p-3 border border-pm shadow-pm-sm">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-pm-secondary text-xs font-medium uppercase tracking-wide">Categories</p>
|
||
<p class="text-lg font-bold text-pm" id="totalCategories">-</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="bg-pm-surface rounded-pm-lg p-3 border border-pm shadow-pm-sm cursor-pointer hover:border-pm-accent transition-colors"
|
||
onclick="window.location.hash = '#/tags'" title="Click to manage tags">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-pm-secondary text-xs font-medium uppercase tracking-wide">Unique Tags</p>
|
||
<p class="text-lg font-bold text-pm-success" id="totalTags">-</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="bg-pm-surface rounded-pm-lg p-3 border border-pm shadow-pm-sm">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-pm-secondary text-xs font-medium uppercase tracking-wide">Avg Rating</p>
|
||
<p class="text-lg font-bold text-pm-warning" id="avgRating">-</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- #249 S6b: where the library (database, config.json, backups) lives and why. -->
|
||
<p class="text-pm-secondary text-xs mt-2" id="libraryLocation" hidden></p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Search Controls (Sticky) -->
|
||
<div class="sticky top-0 z-50 bg-pm-primary/95 backdrop-blur-sm border-b border-pm-subtle">
|
||
<div class="px-4 py-3">
|
||
<div class="max-w-7xl mx-auto">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 border border-pm">
|
||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-7 gap-3 mb-3">
|
||
<div class="lg:col-span-2">
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1 uppercase tracking-wide">Search Text</label>
|
||
<input type="text" id="searchText" placeholder="Search prompt content..."
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] placeholder:text-pm-muted focus:outline-none">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1 uppercase tracking-wide">Category</label>
|
||
<select id="searchCategory"
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] focus:outline-none">
|
||
<option value="">All Categories</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1 uppercase tracking-wide">Folder</label>
|
||
<select id="searchFolder"
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] focus:outline-none">
|
||
<option value="">All Folders</option>
|
||
</select>
|
||
</div>
|
||
<div class="relative">
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1 uppercase tracking-wide">Tags</label>
|
||
<input type="text" id="searchTags" placeholder="tag1, tag2..." autocomplete="off"
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] placeholder:text-pm-muted focus:outline-none">
|
||
<!-- Autocomplete: tags that co-occur with the ones already typed -->
|
||
<div id="searchTagSuggestions" role="listbox"
|
||
class="absolute left-0 right-0 top-full mt-1 bg-pm-surface border border-pm rounded-pm-md shadow-pm hidden z-20 max-h-56 overflow-y-auto"></div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1 uppercase tracking-wide">Sort By</label>
|
||
<select id="sortBy"
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] focus:outline-none">
|
||
<!-- Filled from PromptListSort.SORT_OPTIONS in admin.js -->
|
||
<option value="last_used_desc">Recently Used</option>
|
||
</select>
|
||
</div>
|
||
<div class="flex items-end">
|
||
<button id="searchBtn"
|
||
class="w-full bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg font-medium py-1.5 px-3 rounded-pm-sm text-[13px] transition-colors flex items-center justify-center gap-1.5">
|
||
<span>Search</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bulk Actions -->
|
||
<div class="flex flex-wrap items-center gap-2 pt-3 border-t border-pm">
|
||
<label class="flex items-center gap-1.5 text-pm">
|
||
<input type="checkbox" id="selectAll" class="w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||
<span class="text-xs font-medium">Select All</span>
|
||
</label>
|
||
<button id="bulkDeleteBtn" disabled
|
||
class="px-2.5 py-1 text-pm-error bg-transparent hover:bg-pm-error-tint disabled:opacity-25 disabled:cursor-not-allowed text-xs font-medium rounded-pm-sm transition-colors border border-transparent hover:border-pm">
|
||
Delete Selected
|
||
</button>
|
||
<button id="bulkTagBtn" disabled
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover disabled:opacity-25 disabled:cursor-not-allowed text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Add Tags
|
||
</button>
|
||
<button id="bulkCategoryBtn" disabled
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover disabled:opacity-25 disabled:cursor-not-allowed text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Set Category
|
||
</button>
|
||
<div class="w-px h-4 bg-pm-border mx-1"></div>
|
||
<button id="addPromptBtn"
|
||
class="px-2.5 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Add Prompt
|
||
</button>
|
||
<button id="statsBtn"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Stats
|
||
</button>
|
||
<button id="exportBtn"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Export
|
||
</button>
|
||
<a id="settingsBtn" href="workspace#/system/overview" target="_blank" rel="noopener"
|
||
title="Opens System › Overview in a new tab"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Settings
|
||
</a>
|
||
<a id="diagnosticsBtn" href="workspace#/system/diagnostics" target="_blank" rel="noopener"
|
||
title="Opens System › Diagnostics & logs in a new tab"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Diagnostics
|
||
</a>
|
||
<a id="maintenanceBtn" href="workspace#/system/maintenance" target="_blank" rel="noopener"
|
||
title="Opens System › Maintenance in a new tab"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Maintenance
|
||
</a>
|
||
<a id="backupBtn" href="workspace#/system/backups" target="_blank" rel="noopener"
|
||
title="Opens System › Backup & export in a new tab"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Backup DB
|
||
</a>
|
||
<a id="restoreBtn" href="workspace#/system/backups" target="_blank" rel="noopener"
|
||
title="Opens System › Backup & export in a new tab"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Restore DB
|
||
</a>
|
||
<button id="scanBtn"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Scan Images
|
||
</button>
|
||
<button id="autoTagBtn"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Auto Tag
|
||
</button>
|
||
<a id="logsBtn" href="workspace#/system/diagnostics" target="_blank" rel="noopener"
|
||
title="Opens System › Diagnostics & logs in a new tab"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
View Logs
|
||
</a>
|
||
<button id="metadataBtn"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Metadata
|
||
</button>
|
||
<button id="galleryBtn"
|
||
class="px-2.5 py-1 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Gallery
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Results Section -->
|
||
<div class="flex-1 px-4 pb-4">
|
||
<div class="max-w-7xl mx-auto h-full">
|
||
<div class="bg-pm-surface rounded-pm-lg border border-pm h-full flex flex-col">
|
||
<!-- Results Header -->
|
||
<div class="flex items-center justify-between p-3 border-b border-pm">
|
||
<div class="flex items-center gap-3">
|
||
<h2 class="text-[13px] font-semibold text-pm" id="resultsTitle">Recent Prompts</h2>
|
||
<div class="flex items-center gap-1.5">
|
||
<label class="text-xs text-pm-secondary">Show:</label>
|
||
<select id="limitSelector" class="px-2 py-0.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs">
|
||
<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-xs text-pm-secondary">per page</span>
|
||
</div>
|
||
<!-- Session-only toggle; the saved default lives in Page options -->
|
||
<label class="flex items-center gap-1.5 text-xs text-pm-secondary cursor-pointer" title="Load the next page as you scroll (this session)">
|
||
<input type="checkbox" id="infiniteScrollSession" class="w-3.5 h-3.5 rounded border-pm bg-pm-input text-pm-accent focus:ring-pm-accent">
|
||
Infinite scroll
|
||
</label>
|
||
<button id="pageOptionsBtn" type="button" class="text-xs text-pm-secondary hover:text-pm-accent underline">Page options</button>
|
||
</div>
|
||
<span class="text-xs font-medium text-pm-secondary bg-pm-input px-2 py-0.5 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-8 h-8 border-3 border-pm-accent/30 border-t-pm-accent rounded-full animate-spin mx-auto mb-3"></div>
|
||
<p class="text-pm-secondary text-xs">Loading prompts...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Results List -->
|
||
<div id="resultsList" class="flex-1 overflow-y-auto p-3 space-y-2 hidden">
|
||
</div>
|
||
|
||
<!-- Pagination Controls -->
|
||
<div id="paginationControls" class="border-t border-pm p-3 hidden">
|
||
<div class="flex items-center justify-between">
|
||
<div class="text-xs text-pm-secondary">
|
||
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 gap-1.5">
|
||
<button id="firstPageBtn" class="px-2 py-0.5 bg-pm-input hover:bg-pm-hover text-pm rounded-pm-sm disabled:opacity-25 disabled:cursor-not-allowed text-xs">
|
||
First
|
||
</button>
|
||
<button id="prevPageBtn" class="px-2 py-0.5 bg-pm-input hover:bg-pm-hover text-pm rounded-pm-sm disabled:opacity-25 disabled:cursor-not-allowed text-xs">
|
||
Previous
|
||
</button>
|
||
<span class="text-xs text-pm-secondary">
|
||
Page <span id="currentPage">1</span> of <span id="totalPages">1</span>
|
||
</span>
|
||
<button id="nextPageBtn" class="px-2 py-0.5 bg-pm-input hover:bg-pm-hover text-pm rounded-pm-sm disabled:opacity-25 disabled:cursor-not-allowed text-xs">
|
||
Next
|
||
</button>
|
||
<button id="lastPageBtn" class="px-2 py-0.5 bg-pm-input hover:bg-pm-hover text-pm rounded-pm-sm disabled:opacity-25 disabled:cursor-not-allowed text-xs">
|
||
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-4 py-2 border-b border-pm-subtle bg-pm-surface/50">
|
||
<div class="max-w-7xl mx-auto flex items-center justify-between">
|
||
<div class="flex items-center gap-3">
|
||
<button onclick="window.location.hash=''" class="text-pm-secondary hover:text-pm transition-colors text-xs flex items-center gap-1">
|
||
<span>←</span><span>Dashboard</span>
|
||
</button>
|
||
<h2 class="text-[13px] font-semibold text-pm">Tags</h2>
|
||
<span id="tagsTotalBadge" class="text-xs font-medium text-pm-secondary bg-pm-input px-2 py-0.5 rounded-full">0 tags</span>
|
||
</div>
|
||
<div id="filterModeToggle" class="hidden items-center gap-1.5">
|
||
<span class="text-xs text-pm-secondary">Filter:</span>
|
||
<button id="filterModeAnd" class="px-2 py-0.5 text-xs font-medium rounded-pm-sm bg-pm-accent text-pm-accent-fg">AND</button>
|
||
<button id="filterModeOr" class="px-2 py-0.5 text-xs font-medium rounded-pm-sm bg-pm-input text-pm hover:bg-pm-hover">OR</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Two-Panel Layout -->
|
||
<div class="flex-1 flex overflow-hidden">
|
||
<!-- Left Panel: Tag List -->
|
||
<div class="w-72 flex-shrink-0 border-r border-pm-subtle flex flex-col bg-pm-surface/30">
|
||
<!-- Tag Search & Sort -->
|
||
<div class="p-3 space-y-2 border-b border-pm-subtle">
|
||
<input type="text" id="tagSearchInput" placeholder="Search tags..."
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] focus:outline-none">
|
||
<select id="tagSortSelect" class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px]">
|
||
<option value="alpha_asc">A → Z</option>
|
||
<option value="alpha_desc">Z → 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">
|
||
<div id="tagsList" class="p-1.5 space-y-0.5"></div>
|
||
<div id="tagsLoader" class="text-center">
|
||
<div class="tags-loader-spinner p-3 hidden">
|
||
<div class="w-5 h-5 border-2 border-pm-accent/30 border-t-pm-accent 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-4 py-2 border-b border-pm-subtle hidden">
|
||
<div class="flex flex-wrap gap-1.5 items-center">
|
||
<span class="text-xs text-pm-secondary mr-0.5">Filtering:</span>
|
||
<div id="tagPillsContainer" class="flex flex-wrap gap-1.5"></div>
|
||
<span id="tagPromptsCountBadge" class="text-xs text-pm-muted ml-auto"></span>
|
||
</div>
|
||
</div>
|
||
<!-- Prompts Grid -->
|
||
<div id="tagPromptsContainer" class="flex-1 overflow-y-auto">
|
||
<div id="tagPromptsGrid" class="p-4 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3"></div>
|
||
<div id="tagPromptsLoader" class="text-center">
|
||
<div class="prompts-loader-spinner p-3 hidden">
|
||
<div class="w-5 h-5 border-2 border-pm-accent/30 border-t-pm-accent rounded-full animate-spin mx-auto"></div>
|
||
</div>
|
||
<button id="loadMorePromptsBtn" class="hidden px-3 py-1.5 my-2 text-xs text-pm-secondary hover:text-pm-accent border border-pm hover:border-pm-accent rounded-pm-sm 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-pm-muted">
|
||
<p class="text-sm font-medium">Select a tag to view prompts</p>
|
||
<p class="text-xs 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-pm-surface border border-pm rounded-pm-md shadow-pm py-1 min-w-[160px]">
|
||
</div>
|
||
</div><!-- /tagsPageView -->
|
||
|
||
</div>
|
||
|
||
<!-- Settings Modal -->
|
||
<div id="settingsModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-lg w-full mx-4 border border-pm max-h-[90vh] overflow-y-auto shadow-pm">
|
||
<h3 class="text-sm font-semibold text-pm mb-4">Page options</h3>
|
||
|
||
<div class="space-y-4">
|
||
<div class="border-b border-pm pb-3">
|
||
<h4 class="text-xs font-semibold text-pm-secondary mb-3 uppercase tracking-wide">Prompt list</h4>
|
||
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<label for="infiniteScrollDefault" class="block text-xs font-medium text-pm-secondary">Infinite Scrolling</label>
|
||
<p class="text-xs text-pm-muted">Load the next page of prompts as you scroll. The checkbox in the list header overrides this for the current session.</p>
|
||
</div>
|
||
<input type="checkbox" id="infiniteScrollDefault" class="w-4 h-4 rounded border-pm bg-pm-input text-pm-accent focus:ring-pm-accent ml-3">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex justify-end gap-2 mt-4">
|
||
<button id="cancelSettings"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Cancel
|
||
</button>
|
||
<button id="saveSettings"
|
||
class="px-3 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Save Settings
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bulk Tag Modal -->
|
||
<div id="bulkTagModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-md w-full mx-4 border border-pm shadow-pm">
|
||
<h3 class="text-sm font-semibold text-pm mb-4">Add Tags to Selected Prompts</h3>
|
||
|
||
<div>
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1">Tags (comma-separated)</label>
|
||
<input type="text" id="bulkTagInput" placeholder="tag1, tag2, tag3..."
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] placeholder:text-pm-muted focus:outline-none">
|
||
</div>
|
||
|
||
<div class="flex justify-end gap-2 mt-4">
|
||
<button id="cancelBulkTag"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Cancel
|
||
</button>
|
||
<button id="confirmBulkTag"
|
||
class="px-3 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Add Tags
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Individual Tag Modal -->
|
||
<div id="individualTagModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-md w-full mx-4 border border-pm shadow-pm">
|
||
<h3 class="text-sm font-semibold text-pm mb-4">Add Tags to Prompt</h3>
|
||
|
||
<div>
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1">Tags (comma-separated)</label>
|
||
<input type="text" id="individualTagInput" placeholder="tag1, tag2, tag3..."
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] placeholder:text-pm-muted focus:outline-none">
|
||
</div>
|
||
|
||
<div class="flex justify-end gap-2 mt-4">
|
||
<button id="cancelIndividualTag"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Cancel
|
||
</button>
|
||
<button id="confirmIndividualTag"
|
||
class="px-3 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Add Tags
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Bulk Category Modal -->
|
||
<div id="bulkCategoryModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-md w-full mx-4 border border-pm shadow-pm">
|
||
<h3 class="text-sm font-semibold text-pm mb-4">Set Category for Selected Prompts</h3>
|
||
|
||
<div>
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1">Category</label>
|
||
<input type="text" id="bulkCategoryInput" placeholder="Enter category name..."
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] placeholder:text-pm-muted focus:outline-none">
|
||
</div>
|
||
|
||
<div class="flex justify-end gap-2 mt-4">
|
||
<button id="cancelBulkCategory"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Cancel
|
||
</button>
|
||
<button id="confirmBulkCategory"
|
||
class="px-3 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Set Category
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Gallery Modal -->
|
||
<div id="galleryModal" class="fixed inset-0 bg-black/75 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg max-w-6xl w-full max-h-[90vh] overflow-hidden mx-4 border border-pm shadow-pm">
|
||
<div class="p-4 border-b border-pm">
|
||
<div class="flex items-center justify-between">
|
||
<h3 class="text-sm font-semibold text-pm" id="galleryTitle">Generated Images</h3>
|
||
<button onclick="window.admin.closeGallery()" class="p-1 rounded-pm-sm hover:bg-pm-hover transition-colors">
|
||
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="p-4 overflow-y-auto max-h-[calc(90vh-120px)]">
|
||
<div id="galleryContent" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||
</div>
|
||
<div id="galleryEmpty" class="text-center py-8 hidden">
|
||
<h4 class="text-sm font-medium text-pm mb-1">No images found</h4>
|
||
<p class="text-pm-muted text-xs">No images have been generated with this prompt yet.</p>
|
||
</div>
|
||
<div id="galleryLoading" class="text-center py-8">
|
||
<div class="w-8 h-8 border-3 border-pm-accent/30 border-t-pm-accent rounded-full animate-spin mx-auto mb-3"></div>
|
||
<p class="text-pm-secondary text-xs">Loading images...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Scan Modal -->
|
||
<div id="scanModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-lg w-full mx-4 border border-pm shadow-pm">
|
||
<h3 class="text-sm font-semibold text-pm mb-4">Scan ComfyUI Output Images</h3>
|
||
|
||
<div class="space-y-3">
|
||
<div class="bg-pm-warning-tint border border-pm-warning/40 rounded-pm-md p-3">
|
||
<h4 class="text-pm-warning font-medium text-xs mb-1.5">Important Information</h4>
|
||
<ul class="text-pm text-xs space-y-0.5">
|
||
<li>This will scan all PNG files in your ComfyUI output directory</li>
|
||
<li>Depending on size, this could take several minutes</li>
|
||
<li>This is experimental — we recommend backing up first</li>
|
||
<li>Only images with ComfyUI or A1111/Forge-style metadata will be processed</li>
|
||
<li>Duplicate prompts will be automatically detected and skipped</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="bg-pm-info-tint border border-pm-info/40 rounded-pm-md p-3">
|
||
<div class="flex items-center justify-between">
|
||
<span class="text-pm text-xs font-medium">Backup Database First (Recommended)</span>
|
||
<button id="quickBackupBtn"
|
||
class="px-2.5 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Backup Now
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="scanProgress" class="hidden space-y-2">
|
||
<div class="bg-pm-input rounded-pm-md p-3">
|
||
<div class="flex items-center justify-between mb-1.5">
|
||
<span class="text-pm text-xs font-medium">Scanning Progress</span>
|
||
<span id="scanStatusText" class="text-pm-secondary text-xs">Preparing...</span>
|
||
</div>
|
||
<div class="w-full bg-pm-primary rounded-full h-1.5">
|
||
<div id="scanProgressBar" class="bg-pm-accent h-1.5 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||
</div>
|
||
<div class="flex justify-between text-[11px] text-pm-muted mt-1">
|
||
<span id="scanCount">0 files processed</span>
|
||
<span id="scanFound">0 prompts found</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex justify-end gap-2 mt-4">
|
||
<button id="cancelScan"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Cancel
|
||
</button>
|
||
<button id="startScan"
|
||
class="px-3 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Start Scan
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Minimized scan progress: shown while a scan runs with its modal hidden -->
|
||
<button id="scanPill" type="button" title="Show scan progress"
|
||
class="hidden fixed bottom-4 right-4 z-40 px-3 py-1.5 bg-pm-surface border border-pm shadow-pm rounded-pm-md text-xs text-pm hover:bg-pm-hover transition-colors">
|
||
<span class="inline-block w-1.5 h-1.5 rounded-full bg-pm-accent mr-1.5 align-middle animate-pulse"></span><span id="scanPillText" class="align-middle">Scanning...</span>
|
||
</button>
|
||
|
||
<!-- Add Prompt Modal -->
|
||
<div id="addPromptModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-2xl w-full mx-4 border border-pm max-h-[90vh] overflow-y-auto shadow-pm">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-sm font-semibold text-pm">Add New Prompt</h3>
|
||
<button onclick="window.admin.hideModal('addPromptModal')" class="p-1 rounded-pm-sm hover:bg-pm-hover transition-colors">
|
||
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Prompt Text -->
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1">Prompt Text <span class="text-pm-error">*</span></label>
|
||
<textarea id="addPromptText" rows="4"
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] resize-none focus:outline-none"
|
||
placeholder="Enter your prompt text here..."></textarea>
|
||
</div>
|
||
|
||
<!-- Category and Rating Row -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 mb-4">
|
||
<div>
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1">Category</label>
|
||
<div class="relative">
|
||
<input type="text" id="addPromptCategory" list="addPromptCategoryList"
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] focus:outline-none"
|
||
placeholder="Select or type category...">
|
||
<datalist id="addPromptCategoryList"></datalist>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1">Rating</label>
|
||
<div class="flex items-center gap-0.5" id="addPromptRating">
|
||
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="1">☆</button>
|
||
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="2">☆</button>
|
||
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="3">☆</button>
|
||
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="4">☆</button>
|
||
<button type="button" class="rating-star text-lg text-pm-muted hover:text-pm-warning transition-colors" data-rating="5">☆</button>
|
||
<button type="button" class="ml-1.5 text-[11px] text-pm-muted hover:text-pm" id="clearAddPromptRatingBtn">Clear</button>
|
||
</div>
|
||
<input type="hidden" id="addPromptRatingValue" value="">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tags -->
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1">Tags</label>
|
||
<div class="relative">
|
||
<div id="addPromptTagsContainer" class="flex flex-wrap gap-1.5 p-2 bg-pm-input border border-pm rounded-pm-sm min-h-[36px]">
|
||
<input type="text" id="addPromptTagInput"
|
||
class="flex-1 min-w-[150px] bg-transparent border-none outline-none text-pm text-[13px] placeholder:text-pm-muted"
|
||
placeholder="Type to add tags...">
|
||
</div>
|
||
<div id="addPromptTagSuggestions" class="absolute left-0 right-0 top-full mt-1 bg-pm-surface border border-pm rounded-pm-md shadow-pm hidden z-10 max-h-48 overflow-y-auto"></div>
|
||
</div>
|
||
<p class="text-pm-muted text-[11px] mt-1">Press Enter or comma to add a tag. Click a tag to remove it.</p>
|
||
</div>
|
||
|
||
<!-- Notes -->
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1">Notes</label>
|
||
<textarea id="addPromptNotes" rows="2"
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] resize-none focus:outline-none"
|
||
placeholder="Optional notes about this prompt..."></textarea>
|
||
</div>
|
||
|
||
<!-- Protection Option -->
|
||
<div class="mb-4">
|
||
<label class="flex items-start p-3 bg-pm-input rounded-pm-md cursor-pointer hover:bg-pm-hover border border-pm">
|
||
<input type="checkbox" id="addPromptProtected" checked class="w-3.5 h-3.5 mt-0.5 accent-[var(--pm-accent)]">
|
||
<div class="ml-2.5">
|
||
<span class="text-pm text-xs font-medium">Protect from orphan cleanup</span>
|
||
<p class="text-pm-secondary text-[11px] mt-0.5">Keep this prompt even if it has no linked images.</p>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
|
||
<!-- Action Buttons -->
|
||
<div class="flex justify-end gap-2">
|
||
<button onclick="window.admin.hideModal('addPromptModal')"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Cancel
|
||
</button>
|
||
<button id="saveNewPromptBtn"
|
||
class="px-3 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Add Prompt
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Auto Tag Modal -->
|
||
<div id="autoTagModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-lg w-full mx-4 border border-pm shadow-pm">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-sm font-semibold text-pm">Auto Tag Images</h3>
|
||
<button onclick="window.admin.hideModal('autoTagModal')" class="p-1 rounded-pm-sm hover:bg-pm-hover transition-colors">
|
||
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Model Selection -->
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-pm-secondary mb-2">Model Selection</label>
|
||
<div class="space-y-2">
|
||
<label class="flex items-center justify-between p-3 bg-pm-input rounded-pm-md cursor-pointer hover:bg-pm-hover border border-pm">
|
||
<div class="flex items-center">
|
||
<input type="radio" name="autoTagModel" value="gguf" checked class="w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||
<div class="ml-2.5">
|
||
<span class="text-pm text-xs font-medium">GGUF Model</span>
|
||
<span class="text-pm-secondary text-[11px] ml-1.5">(Recommended, ~4.5GB)</span>
|
||
</div>
|
||
</div>
|
||
<div id="ggufModelStatus" class="flex items-center gap-1.5">
|
||
<span class="text-pm-secondary text-[11px]">Checking...</span>
|
||
</div>
|
||
</label>
|
||
<label class="flex items-center justify-between p-3 bg-pm-input rounded-pm-md cursor-pointer hover:bg-pm-hover border border-pm">
|
||
<div class="flex items-center">
|
||
<input type="radio" name="autoTagModel" value="hf" class="w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||
<div class="ml-2.5">
|
||
<span class="text-pm text-xs font-medium">HuggingFace Model</span>
|
||
<span class="text-pm-secondary text-[11px] ml-1.5">(~16GB)</span>
|
||
</div>
|
||
</div>
|
||
<div id="hfModelStatus" class="flex items-center gap-1.5">
|
||
<span class="text-pm-secondary text-[11px]">Checking...</span>
|
||
</div>
|
||
</label>
|
||
<label class="flex items-center justify-between p-3 bg-pm-input rounded-pm-md cursor-pointer hover:bg-pm-hover border border-pm">
|
||
<div class="flex items-center">
|
||
<input type="radio" name="autoTagModel" value="wd14-swinv2" class="w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||
<div class="ml-2.5">
|
||
<span class="text-pm text-xs font-medium">WD14 SwinV2</span>
|
||
<span class="text-pm-secondary text-[11px] ml-1.5">(~400MB, fast classifier)</span>
|
||
</div>
|
||
</div>
|
||
<div id="wd14SwinV2ModelStatus" class="flex items-center gap-1.5">
|
||
<span class="text-pm-secondary text-[11px]">Checking...</span>
|
||
</div>
|
||
</label>
|
||
<label class="flex items-center justify-between p-3 bg-pm-input rounded-pm-md cursor-pointer hover:bg-pm-hover border border-pm">
|
||
<div class="flex items-center">
|
||
<input type="radio" name="autoTagModel" value="wd14-vit" class="w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||
<div class="ml-2.5">
|
||
<span class="text-pm text-xs font-medium">WD14 ViT</span>
|
||
<span class="text-pm-secondary text-[11px] ml-1.5">(~400MB, ViT architecture)</span>
|
||
</div>
|
||
</div>
|
||
<div id="wd14VitModelStatus" class="flex items-center gap-1.5">
|
||
<span class="text-pm-secondary text-[11px]">Checking...</span>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Prompt Template (LLM models only) -->
|
||
<div id="promptSection" class="mb-4">
|
||
<label class="block text-xs font-medium text-pm-secondary mb-1">Tag Generation Prompt</label>
|
||
<textarea id="autoTagPrompt" rows="6"
|
||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] font-mono resize-none focus:outline-none"
|
||
placeholder="Enter prompt template...">Generate only comma-separated Danbooru tags (lowercase_underscores, no spaces).
|
||
Include: subject_count (1girl/1boy/etc), gender, ethnicity (asian, caucasian, etc), age_approx (teen, young_woman), body_attributes, face_features, hairstyle, appearance, clothing, accessories, pose, expression, action, camera_angle, camera_shot, scene_composition (wide_shot, long_shot, dutch_angle), environment, background_elements, lighting, mood.
|
||
Prioritize visual accuracy.
|
||
No sentences, no commentary, no captions. Only tags. Keep length short but descriptive.</textarea>
|
||
</div>
|
||
|
||
<!-- WD14 Threshold Controls -->
|
||
<div id="wd14ThresholdSection" class="mb-4 hidden">
|
||
<label class="block text-xs font-medium text-pm-secondary mb-2">Tag Confidence Thresholds</label>
|
||
<div class="space-y-3">
|
||
<div class="p-2.5 bg-pm-input rounded-pm-md border border-pm">
|
||
<div class="flex items-center justify-between mb-1">
|
||
<span class="text-pm text-xs">General tags</span>
|
||
<span id="wd14GeneralThresholdValue" class="text-pm-accent text-xs font-mono">0.35</span>
|
||
</div>
|
||
<input type="range" id="wd14GeneralThreshold" min="0" max="1" step="0.05" value="0.35"
|
||
class="w-full h-1.5 bg-pm-primary rounded-lg appearance-none cursor-pointer accent-[var(--pm-accent)]">
|
||
</div>
|
||
<div class="p-2.5 bg-pm-input rounded-pm-md border border-pm">
|
||
<div class="flex items-center justify-between mb-1">
|
||
<span class="text-pm text-xs">Character tags</span>
|
||
<span id="wd14CharacterThresholdValue" class="text-pm-accent text-xs font-mono">0.85</span>
|
||
</div>
|
||
<input type="range" id="wd14CharacterThreshold" min="0" max="1" step="0.05" value="0.85"
|
||
class="w-full h-1.5 bg-pm-primary rounded-lg appearance-none cursor-pointer accent-[var(--pm-accent)]">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tagged Images Handling -->
|
||
<div class="mb-4">
|
||
<label class="block text-xs font-medium text-pm-secondary mb-2">Already Tagged Images</label>
|
||
<div class="space-y-1.5">
|
||
<label class="flex items-center p-2.5 bg-pm-input rounded-pm-md cursor-pointer hover:bg-pm-hover border border-pm">
|
||
<input type="radio" name="autoTagMode" value="skip" checked class="w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||
<span class="ml-2.5 text-pm text-xs">Skip images already auto-tagged</span>
|
||
</label>
|
||
<label class="flex items-center p-2.5 bg-pm-input rounded-pm-md cursor-pointer hover:bg-pm-hover border border-pm">
|
||
<input type="radio" name="autoTagMode" value="retag" class="w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||
<span class="ml-2.5 text-pm text-xs">Re-tag all images</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Action Buttons -->
|
||
<div class="flex justify-end gap-2">
|
||
<button onclick="window.admin.hideModal('autoTagModal')"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Cancel
|
||
</button>
|
||
<button id="startReviewBtn"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Review
|
||
</button>
|
||
<button id="startAutoTagBtn"
|
||
class="px-3 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Auto Tag
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Auto Tag Review Modal -->
|
||
<div id="autoTagReviewModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-4xl w-full mx-4 border border-pm shadow-pm">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-sm font-semibold text-pm">
|
||
Review Tags
|
||
<span class="text-pm-secondary text-xs font-normal">(<span id="reviewCurrentIndex">1</span> of <span id="reviewTotalCount">0</span>)</span>
|
||
</h3>
|
||
<button onclick="window.admin.cancelReview()" class="p-1 rounded-pm-sm hover:bg-pm-hover transition-colors">
|
||
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<!-- Image Preview -->
|
||
<div class="bg-pm-primary rounded-pm-md p-3">
|
||
<img id="reviewImage" src="" alt="Review image" class="w-full h-56 object-contain rounded-pm-sm">
|
||
<p id="reviewImageName" class="text-pm-secondary text-xs mt-1.5 text-center truncate">image_name.png</p>
|
||
</div>
|
||
|
||
<!-- Generated Tags -->
|
||
<div>
|
||
<label class="block text-xs font-medium text-pm-secondary mb-2">Generated Tags (click to remove)</label>
|
||
<div id="reviewTagsContainer" class="p-3 bg-pm-primary rounded-pm-md">
|
||
<div id="reviewTagsVisible" class="flex flex-wrap gap-1.5"></div>
|
||
<div id="reviewTagsAccordion" class="hidden mt-2">
|
||
<div id="reviewTagsHidden" class="flex flex-wrap gap-1.5 max-h-[180px] overflow-y-auto"></div>
|
||
</div>
|
||
<button id="reviewTagsToggle" class="hidden mt-2 text-xs text-pm-accent hover:text-pm-accent-hover transition-colors flex items-center gap-0.5">
|
||
<span id="reviewTagsToggleIcon">▼</span>
|
||
<span id="reviewTagsToggleText">Show all tags</span>
|
||
<span id="reviewTagsCount" class="text-pm-muted"></span>
|
||
</button>
|
||
</div>
|
||
<p class="text-pm-muted text-[11px] mt-1">Click the × on tags to remove them before applying</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Action Buttons -->
|
||
<div class="flex justify-between mt-4">
|
||
<button id="skipReviewBtn"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Skip
|
||
</button>
|
||
<div class="flex gap-2">
|
||
<button onclick="window.admin.cancelReview()"
|
||
class="px-3 py-1.5 text-pm-error bg-transparent hover:bg-pm-error-tint border border-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Cancel All
|
||
</button>
|
||
<button id="applyReviewBtn"
|
||
class="px-3 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Apply & Next
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Auto Tag Progress Modal -->
|
||
<div id="autoTagProgressModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-md w-full mx-4 border border-pm shadow-pm">
|
||
<h3 class="text-sm font-semibold text-pm mb-4">Auto Tagging Progress</h3>
|
||
|
||
<div class="mb-3">
|
||
<p class="text-pm-secondary text-xs">Processing:</p>
|
||
<p id="autoTagCurrentFile" class="text-pm text-xs font-medium truncate">Initializing...</p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<div class="flex justify-between text-xs text-pm-secondary mb-1">
|
||
<span>Progress</span>
|
||
<span id="autoTagProgressPercent">0%</span>
|
||
</div>
|
||
<div class="w-full bg-pm-input rounded-full h-1.5">
|
||
<div id="autoTagProgressBar" class="bg-pm-accent h-1.5 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||
</div>
|
||
<p id="autoTagCloseNote" class="text-pm-muted text-[11px] mt-1.5">Closing this window stops tagging. Tags already added are kept. Prompt Manager's Activity page shows the time left.</p>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-3 gap-3 mb-4">
|
||
<div class="bg-pm-input rounded-pm-md p-2 text-center">
|
||
<div class="text-lg font-bold text-pm-accent" id="autoTagProcessed">0</div>
|
||
<div class="text-[11px] text-pm-secondary">Processed</div>
|
||
</div>
|
||
<div class="bg-pm-input rounded-pm-md p-2 text-center">
|
||
<div class="text-lg font-bold text-pm-success" id="autoTagApplied">0</div>
|
||
<div class="text-[11px] text-pm-secondary">Tags Applied</div>
|
||
</div>
|
||
<div class="bg-pm-input rounded-pm-md p-2 text-center">
|
||
<div class="text-lg font-bold text-pm-warning" id="autoTagSkipped">0</div>
|
||
<div class="text-[11px] text-pm-secondary">Skipped</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex justify-center">
|
||
<button id="cancelAutoTagBtn"
|
||
class="px-3 py-1.5 text-pm-error bg-transparent hover:bg-pm-error-tint border border-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Auto Tag Loading Model Modal -->
|
||
<div id="autoTagLoadingModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-md w-full mx-4 border border-pm shadow-pm text-center">
|
||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-pm-accent mx-auto mb-3"></div>
|
||
<h3 class="text-sm font-semibold text-pm mb-1">Loading Model</h3>
|
||
<p id="autoTagLoadingStatus" class="text-pm-secondary text-xs">Initializing...</p>
|
||
<p class="text-pm-muted text-[11px] mt-3">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/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-lg w-full mx-4 border border-pm shadow-pm">
|
||
<h3 class="text-sm font-semibold text-pm mb-3">Image Already Auto-tagged</h3>
|
||
|
||
<div class="mb-3">
|
||
<img id="retagPreviewImage" src="" alt="Preview" class="w-full h-40 object-contain bg-pm-primary rounded-pm-md">
|
||
<p id="retagImageName" class="text-pm-secondary text-xs mt-1.5 text-center truncate"></p>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<p class="text-pm text-xs mb-1.5">Current tags:</p>
|
||
<div id="retagExistingTags" class="flex flex-wrap gap-1 max-h-20 overflow-y-auto">
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-pm-secondary text-xs mb-4">A tagging model already tagged this image. Generate new tags?</p>
|
||
|
||
<div class="flex gap-2 justify-end">
|
||
<button id="retagSkipBtn"
|
||
class="px-2.5 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Skip
|
||
</button>
|
||
<button id="retagSkipAllBtn"
|
||
class="px-2.5 py-1.5 bg-pm-warning hover:opacity-80 text-black text-xs font-medium rounded-pm-sm transition-colors">
|
||
Skip All Tagged
|
||
</button>
|
||
<button id="retagConfirmBtn"
|
||
class="px-2.5 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Re-tag
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Update Notice Modal -->
|
||
<div id="updateNoticeModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-lg w-full mx-4 border border-pm shadow-pm">
|
||
<div class="flex items-start gap-3 mb-3">
|
||
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-pm-warning-tint flex items-center justify-center">
|
||
<svg class="w-4 h-4 text-pm-warning" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||
</svg>
|
||
</div>
|
||
<div>
|
||
<h3 class="text-sm font-semibold text-pm">Image Rescan Required</h3>
|
||
<p class="text-xs text-pm-secondary mt-0.5">Multi-directory support update</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bg-pm-warning-tint border border-pm-warning/40 rounded-pm-md p-3 mb-3">
|
||
<p class="text-pm text-xs leading-relaxed">
|
||
This update changes how images are stored and organized. To ensure your gallery works correctly
|
||
with the new multi-directory support, you need to <strong>rescan your images</strong>.
|
||
</p>
|
||
<ul class="text-pm text-xs mt-2 space-y-1 list-disc list-inside text-pm-secondary">
|
||
<li>Folder information will be added to existing image records</li>
|
||
<li>Your prompts and tags will not be affected</li>
|
||
<li>The scan may take a few minutes depending on your library size</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="flex items-center justify-between mt-4">
|
||
<label class="flex items-center gap-2 cursor-pointer">
|
||
<input type="checkbox" id="dismissUpdateNotice" class="rounded border-pm bg-pm-input text-pm-accent focus:ring-pm-accent w-3.5 h-3.5">
|
||
<span class="text-xs text-pm-muted">Don't show this again</span>
|
||
</label>
|
||
<div class="flex gap-2">
|
||
<button id="updateNoticeDismiss"
|
||
class="px-3 py-1.5 bg-pm-surface border border-pm hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Dismiss
|
||
</button>
|
||
<button id="updateNoticeScan"
|
||
class="px-3 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs font-medium rounded-pm-sm transition-colors">
|
||
Scan Now
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Auto Tag Download Modal -->
|
||
<div id="autoTagDownloadModal" class="fixed inset-0 bg-black/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||
<div class="bg-pm-surface rounded-pm-lg p-4 max-w-md w-full mx-4 border border-pm shadow-pm">
|
||
<h3 class="text-sm font-semibold text-pm mb-4">Downloading Model</h3>
|
||
|
||
<div class="mb-3">
|
||
<p class="text-pm-secondary text-xs">Downloading:</p>
|
||
<p id="downloadModelName" class="text-pm text-xs font-medium">GGUF Model</p>
|
||
</div>
|
||
|
||
<div class="mb-4">
|
||
<div class="flex justify-between text-xs text-pm-secondary mb-1">
|
||
<span id="downloadStatus">Preparing...</span>
|
||
<span id="downloadProgressPercent">0%</span>
|
||
</div>
|
||
<div class="w-full bg-pm-input rounded-full h-1.5">
|
||
<div id="downloadProgressBar" class="bg-pm-accent h-1.5 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||
</div>
|
||
<p class="text-pm-secondary text-xs mt-2">The download continues in Activity.</p>
|
||
</div>
|
||
|
||
<div class="flex justify-center">
|
||
<button id="cancelDownloadBtn"
|
||
class="px-3 py-1.5 text-pm-error bg-transparent hover:bg-pm-error-tint border border-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||
Close
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="js/comfy-metadata.js"></script>
|
||
<script src="js/prompt-list-sort.js"></script>
|
||
<script src="js/view-router.js"></script>
|
||
<script src="js/tags-page.js"></script>
|
||
<script src="js/admin.js"></script>
|
||
</body>
|
||
</html>
|