Files
ComfyAssets-ComfyUI_PromptM…/web/gallery.html
T
Vito 95586e6395 refactor(gallery): open System for gallery settings, maintenance and tagging models (#330, PR 3) (#408)
* 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.

* refactor(gallery): open System for settings and maintenance

The gallery's Settings opens System › Appearance in a new tab. Its settings
dialog becomes Page options (a new header button) with only the page's own
settings: thumbnail startup check, lazy loading, items per page, grid columns,
infinite scrolling, debug mode and API timeout. Save and Reset keep every other
stored key, so the Appearance values System saves survive (S13-13). Generate
Thumbnails, Clear cache, Rescan and Find duplicates leave the page; Page options
links System › Maintenance and Sources & scans (rows B21–B28).

* refactor(autotag): manage tagging models in System

In both auto-tag dialogs a model that is not downloaded shows a Manage in
System link to System › Auto-tagging (a new tab) instead of a Download button,
and the gallery's Unload Model becomes the same link. The legacy download
dialog and the download, cancel and unload methods go (rows B29–B31); the batch
and review stay. A test hook marks a fake model loaded.

* docs(system): name System as the home for settings and maintenance

README and TESTING.md send settings, maintenance, backups, diagnostics, the
gallery's tools and auto-tag model downloads to their System section and name
each legacy page's Page options.

* fix(gallery): keep the default view in Page options and save viewer choices alone

System › Appearance's Default layout is the workspace's own key, so the legacy
gallery's Default View Mode stays a page option instead of losing its only
control. The viewer's mute and loop buttons remember just that choice instead of
saving the Page options dialog's unfilled inputs over the stored page options.
The pending-thumbnails toast names System › Sources & scans.

* test(legacy): check each System link sends no request in its own test

The no-request check runs inside each link's test, so a slow page no longer
times out the whole table, and it ignores lazy thumbnail loads the layout
starts on its own.

* test(legacy): a lazy thumbnail load is not a System link's request

* fix(gallery): let the header buttons wrap on a narrow screen

With Page options beside Settings the header no longer fits a phone's width;
it wrapped off the page and covered the Page options dialog in the touch runs.

* feat(gallery): remove Default View Mode from Page options (#330)

System > Appearance's Default layout is the one default-layout control, as
the S13 design's G5 says. The legacy gallery's Page options no longer show
or save a default view and Reset no longer resets it. A stored legacy
defaultViewMode still picks the view the gallery opens in (grid otherwise),
and Save and Reset keep that key for the workspace-preferences migration.
2026-10-28 13:31:23 +00:00

624 lines
39 KiB
HTML
Raw Blame History

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