Merge branch 'feat/331-collections-pr2' into feat/331-collections-pr3
# Conflicts: # tests/js/workspace-file-inspector.test.js
This commit is contained in:
+29
-481
@@ -164,26 +164,31 @@
|
||||
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>
|
||||
<button id="settingsBtn"
|
||||
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">
|
||||
<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
|
||||
</button>
|
||||
<button id="diagnosticsBtn"
|
||||
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">
|
||||
</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
|
||||
</button>
|
||||
<button id="maintenanceBtn"
|
||||
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">
|
||||
</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
|
||||
</button>
|
||||
<button id="backupBtn"
|
||||
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">
|
||||
</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
|
||||
</button>
|
||||
<button id="restoreBtn"
|
||||
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">
|
||||
</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
|
||||
</button>
|
||||
</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
|
||||
@@ -192,10 +197,11 @@
|
||||
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>
|
||||
<button id="logsBtn"
|
||||
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">
|
||||
<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
|
||||
</button>
|
||||
</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
|
||||
@@ -229,11 +235,12 @@
|
||||
</select>
|
||||
<span class="text-xs text-pm-secondary">per page</span>
|
||||
</div>
|
||||
<!-- Session-only toggle; the saved default lives in Settings -->
|
||||
<!-- 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>
|
||||
@@ -367,61 +374,13 @@
|
||||
<!-- 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">Settings</h3>
|
||||
<h3 class="text-sm font-semibold text-pm mb-4">Page options</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Gallery Settings Section -->
|
||||
<div class="border-b border-pm pb-3">
|
||||
<h4 class="text-xs font-semibold text-pm-secondary mb-3 uppercase tracking-wide">Gallery Settings</h4>
|
||||
<h4 class="text-xs font-semibold text-pm-secondary mb-3 uppercase tracking-wide">Prompt list</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-xs font-medium text-pm-secondary mb-1">Image Scan Directories</label>
|
||||
<div id="scanPathsList" class="space-y-2">
|
||||
<!-- Populated dynamically by JS -->
|
||||
</div>
|
||||
<button id="addScanPathBtn" type="button"
|
||||
class="mt-2 flex items-center gap-1 text-xs text-pm-accent hover:text-pm-accent-hover">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
Add Directory
|
||||
</button>
|
||||
<p class="text-xs text-pm-muted mt-1">Directories to scan for images. Leave empty to use ComfyUI default output directory.</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="block text-xs font-medium text-pm-secondary">Currently Monitoring</label>
|
||||
<button id="refreshMonitoringStatus" class="text-xs text-pm-accent hover:text-pm-accent-hover">Refresh</button>
|
||||
</div>
|
||||
<div id="monitoringStatus" class="mt-1.5 px-2.5 py-1.5 bg-pm-input rounded-pm-sm text-xs text-pm-secondary font-mono">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- UI Settings Section -->
|
||||
<div class="border-b border-pm pb-3">
|
||||
<h4 class="text-xs font-semibold text-pm-secondary mb-3 uppercase tracking-wide">UI Settings</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-xs font-medium text-pm-secondary mb-1">Search Results Auto-Hide (seconds)</label>
|
||||
<input type="number" id="resultTimeout" min="0" max="300" value="30"
|
||||
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">
|
||||
<p class="text-xs text-pm-muted mt-1">Set to 0 to disable auto-hide.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-pm-secondary mb-1">Web UI Display Mode</label>
|
||||
<select id="webuiDisplayMode"
|
||||
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="popup">Popup Window (Current)</option>
|
||||
<option value="newtab">New Tab</option>
|
||||
</select>
|
||||
<p class="text-xs text-pm-muted mt-1">Choose how the Web UI opens from ComfyUI nodes.</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center justify-between">
|
||||
<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>
|
||||
@@ -429,76 +388,6 @@
|
||||
<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>
|
||||
|
||||
<!-- Performance Section -->
|
||||
<div class="border-b border-pm pb-3">
|
||||
<h4 class="text-xs font-semibold text-pm-secondary mb-3 uppercase tracking-wide">Performance</h4>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-1.5">
|
||||
<label for="workerThreads" class="text-xs font-medium text-pm-secondary">Worker threads</label>
|
||||
<span id="workerThreadsValue" class="text-pm-accent text-xs font-mono">1 / 1 cores</span>
|
||||
</div>
|
||||
<input type="range" id="workerThreads" min="1" max="1" step="1" value="1"
|
||||
class="w-full h-1.5 bg-pm-primary rounded-lg appearance-none cursor-pointer accent-[var(--pm-accent)]">
|
||||
<p class="text-xs text-pm-muted mt-1">Threads used by the image rescan and thumbnail generation. More threads finish sooner; fewer leave CPU for ComfyUI while it generates.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Integrations Section -->
|
||||
<div class="border-b border-pm pb-3">
|
||||
<h4 class="text-xs font-semibold text-pm-secondary mb-3 uppercase tracking-wide">Integrations</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="block text-xs font-medium text-pm-secondary">LoraManager</label>
|
||||
<span id="loraDetectionBadge" class="text-[10px] px-1.5 py-0.5 rounded-full bg-pm-input text-pm-muted">checking...</span>
|
||||
</div>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" id="loraEnabled" class="sr-only peer" disabled>
|
||||
<div class="w-8 h-4 bg-pm-input rounded-full peer peer-checked:bg-pm-accent transition-colors pointer-events-none after:content-[''] after:absolute after:top-0.5 after:left-0.5 after:bg-white after:rounded-full after:h-3 after:w-3 after:transition-all peer-checked:after:translate-x-4"></div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="loraSettings" class="hidden space-y-2 mt-2 pl-2 border-l-2 border-pm">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-pm-secondary mb-1">Path</label>
|
||||
<input type="text" id="loraManagerPath" readonly
|
||||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] text-pm-muted">
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="block text-xs font-medium text-pm-secondary">Auto-inject trigger words</label>
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" id="loraTriggerWords" class="sr-only peer">
|
||||
<div class="w-8 h-4 bg-pm-input rounded-full peer peer-checked:bg-pm-accent transition-colors pointer-events-none after:content-[''] after:absolute after:top-0.5 after:left-0.5 after:bg-white after:rounded-full after:h-3 after:w-3 after:transition-all peer-checked:after:translate-x-4"></div>
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-pm-muted">Automatically append trigger words when <code class="text-pm-accent"><lora:name:weight></code> is detected in prompts.</p>
|
||||
|
||||
<div class="mt-2">
|
||||
<label class="block text-xs font-medium text-pm-secondary mb-1">CivitAI API Key</label>
|
||||
<input type="password" id="civitaiApiKey" placeholder="Optional — required for NSFW example images"
|
||||
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">
|
||||
<p class="text-xs text-pm-muted mt-1">Get your key from <a href="https://civitai.com/user/account" target="_blank" class="text-pm-accent hover:underline">civitai.com/user/account</a></p>
|
||||
</div>
|
||||
|
||||
<label class="mt-1 flex items-center gap-1.5 text-xs text-pm-secondary cursor-pointer">
|
||||
<input type="checkbox" id="clearCivitaiKey" class="w-3.5 h-3.5 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
||||
<span>Clear the stored key on save</span>
|
||||
</label>
|
||||
|
||||
<button id="loraImportBtn" type="button"
|
||||
class="mt-1 flex items-center gap-1 text-xs text-pm-accent hover:text-pm-accent-hover">
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
|
||||
</svg>
|
||||
Import LoRA Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
@@ -514,32 +403,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LoRA Import Progress Modal -->
|
||||
<div id="loraImportModal" 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">Importing LoRA Data</h3>
|
||||
|
||||
<div class="mb-3">
|
||||
<p class="text-pm-secondary text-xs">Status:</p>
|
||||
<p id="loraImportStatus" class="text-pm text-xs font-medium truncate">Initializing...</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="flex justify-between text-xs text-pm-secondary mb-1">
|
||||
<span>Progress</span>
|
||||
<span id="loraImportPercent">0%</span>
|
||||
</div>
|
||||
<div class="w-full bg-pm-input rounded-full h-1.5">
|
||||
<div id="loraImportBar" 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="loraImportProcessed">0 processed</span>
|
||||
<span id="loraImportImported">0 imported</span>
|
||||
</div>
|
||||
</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">
|
||||
@@ -612,167 +475,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Database Restore Modal -->
|
||||
<div id="restoreModal" 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">Restore Database</h3>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="bg-pm-warning-tint border border-pm-warning/40 rounded-pm-md p-3 mb-3">
|
||||
<h4 class="text-pm-warning font-medium text-xs">Warning</h4>
|
||||
<p class="text-pm text-xs mt-1">This will replace your current database. A backup will be created automatically.</p>
|
||||
</div>
|
||||
|
||||
<label class="block text-xs font-medium text-pm-secondary mb-1">Select Database File (.db)</label>
|
||||
<input type="file" id="restoreFileInput" accept=".db"
|
||||
class="w-full px-2.5 py-1.5 bg-pm-input border border-pm rounded-pm-sm text-pm text-[13px] file:mr-3 file:py-1 file:px-3 file:rounded-pm-sm file:border-0 file:text-xs file:font-medium file:bg-pm-accent file:text-pm-accent-fg">
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2 mt-4">
|
||||
<button id="cancelRestore"
|
||||
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="confirmRestore"
|
||||
class="px-3 py-1.5 bg-pm-warning hover:opacity-80 text-black text-xs font-medium rounded-pm-sm transition-colors">
|
||||
Restore Database
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Diagnostics Modal -->
|
||||
<div id="diagnosticsModal" 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 max-w-4xl 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">Gallery System Diagnostics</h3>
|
||||
<button onclick="window.admin.closeDiagnostics()" 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 class="flex items-center justify-between mb-3">
|
||||
<button id="runDiagnosticsBtn" 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">
|
||||
Run Diagnostics
|
||||
</button>
|
||||
<button id="testImageLinkBtn" 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">
|
||||
Test Image Link
|
||||
</button>
|
||||
</div>
|
||||
<div id="diagnosticsContent" class="space-y-3">
|
||||
<div class="text-center py-6 text-pm-secondary text-xs">
|
||||
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/60 backdrop-blur-[2px] hidden items-center justify-center z-50">
|
||||
<div class="bg-pm-surface rounded-pm-lg max-w-4xl 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">Database Maintenance</h3>
|
||||
<button onclick="window.admin.closeMaintenance()" 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)]">
|
||||
<!-- Maintenance Options -->
|
||||
<div class="space-y-3 mb-4">
|
||||
<h4 class="text-xs font-semibold text-pm-secondary uppercase tracking-wide mb-3">Select Maintenance Operations:</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<label class="flex items-center gap-2.5 p-3 bg-pm-input rounded-pm-md hover:bg-pm-hover cursor-pointer">
|
||||
<input type="checkbox" id="cleanup_duplicates" class="maintenance-option w-3.5 h-3.5 accent-[var(--pm-accent)]" checked>
|
||||
<div class="flex-1">
|
||||
<div class="text-pm text-xs font-medium">Remove Duplicates</div>
|
||||
<div class="text-[11px] text-pm-secondary">Find and remove duplicate prompts based on text content</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2.5 p-3 bg-pm-input rounded-pm-md hover:bg-pm-hover cursor-pointer">
|
||||
<input type="checkbox" id="vacuum" class="maintenance-option w-3.5 h-3.5 accent-[var(--pm-accent)]" checked>
|
||||
<div class="flex-1">
|
||||
<div class="text-pm text-xs font-medium">Vacuum Database</div>
|
||||
<div class="text-[11px] text-pm-secondary">Optimize database file size and performance</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2.5 p-3 bg-pm-input rounded-pm-md hover:bg-pm-hover cursor-pointer">
|
||||
<input type="checkbox" id="cleanup_orphaned_images" class="maintenance-option w-3.5 h-3.5 accent-[var(--pm-accent)]" checked>
|
||||
<div class="flex-1">
|
||||
<div class="text-pm text-xs font-medium">Clean Orphaned Images</div>
|
||||
<div class="text-[11px] text-pm-secondary">Remove image records for files that no longer exist</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2.5 p-3 bg-pm-input rounded-pm-md hover:bg-pm-hover cursor-pointer">
|
||||
<input type="checkbox" id="check_hash_duplicates" class="maintenance-option w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||||
<div class="flex-1">
|
||||
<div class="text-pm text-xs font-medium">Check Hash Duplicates</div>
|
||||
<div class="text-[11px] text-pm-secondary">Identify prompts with duplicate hash values</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2.5 p-3 bg-pm-input rounded-pm-md hover:bg-pm-hover cursor-pointer">
|
||||
<input type="checkbox" id="check_consistency" class="maintenance-option w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||||
<div class="flex-1">
|
||||
<div class="text-pm text-xs font-medium">Consistency Check</div>
|
||||
<div class="text-[11px] text-pm-secondary">Verify database integrity and data consistency</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2.5 p-3 bg-pm-input rounded-pm-md hover:bg-pm-hover cursor-pointer">
|
||||
<input type="checkbox" id="statistics" class="maintenance-option w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||||
<div class="flex-1">
|
||||
<div class="text-pm text-xs font-medium">Database Statistics</div>
|
||||
<div class="text-[11px] text-pm-secondary">Generate detailed database statistics report</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-2.5 p-3 bg-pm-input rounded-pm-md hover:bg-pm-hover cursor-pointer">
|
||||
<input type="checkbox" id="prune_orphaned_prompts" class="maintenance-option w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||||
<div class="flex-1">
|
||||
<div class="text-pm text-xs font-medium">Prune Orphaned Prompts</div>
|
||||
<div class="text-[11px] text-pm-secondary">Remove prompts that have no linked images</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex gap-2">
|
||||
<button id="runMaintenanceBtn" 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">
|
||||
Run Maintenance
|
||||
</button>
|
||||
<button id="selectAllMaintenanceBtn" 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">
|
||||
Select All
|
||||
</button>
|
||||
<button id="clearAllMaintenanceBtn" 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">
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results -->
|
||||
<div id="maintenanceResults" class="space-y-3">
|
||||
<div class="text-center py-6 text-pm-secondary text-xs">
|
||||
Click "Run Maintenance" to perform selected operations
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
@@ -864,130 +566,6 @@
|
||||
<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>
|
||||
|
||||
<!-- Logs Modal -->
|
||||
<div id="logsModal" 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 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">System Logs</h3>
|
||||
<button onclick="window.admin.closeLogs()" 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>
|
||||
|
||||
<!-- Log Controls -->
|
||||
<div class="p-4 border-b border-pm">
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-3 mb-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-pm-secondary mb-1">Log Level</label>
|
||||
<select id="logLevel" 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 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-xs font-medium text-pm-secondary mb-1">Limit</label>
|
||||
<select id="logLimit" 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="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-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">
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="downloadLogsBtn" class="w-full 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">
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="clearLogsBtn" class="w-full px-2.5 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">
|
||||
Clear Logs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Stats -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-3 mb-3">
|
||||
<div class="bg-pm-input rounded-pm-md p-2">
|
||||
<div class="text-[11px] text-pm-secondary">Total Logs</div>
|
||||
<div class="text-sm font-semibold text-pm-accent" id="logStatsTotal">-</div>
|
||||
</div>
|
||||
<div class="bg-pm-input rounded-pm-md p-2">
|
||||
<div class="text-[11px] text-pm-secondary">Errors</div>
|
||||
<div class="text-sm font-semibold text-pm-error" id="logStatsErrors">-</div>
|
||||
</div>
|
||||
<div class="bg-pm-input rounded-pm-md p-2">
|
||||
<div class="text-[11px] text-pm-secondary">Warnings</div>
|
||||
<div class="text-sm font-semibold text-pm-warning" id="logStatsWarnings">-</div>
|
||||
</div>
|
||||
<div class="bg-pm-input rounded-pm-md p-2">
|
||||
<div class="text-[11px] text-pm-secondary">Current Level</div>
|
||||
<div class="text-sm font-semibold text-pm-success" id="logStatsLevel">-</div>
|
||||
</div>
|
||||
<div class="bg-pm-input rounded-pm-md p-2">
|
||||
<div class="text-[11px] text-pm-secondary">Log Size</div>
|
||||
<div class="text-sm font-semibold text-pm" id="logStatsSize">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Settings -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-pm-secondary mb-1">Set Log Level</label>
|
||||
<select id="setLogLevel" 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="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 gap-1.5 text-pm">
|
||||
<input type="checkbox" id="consoleLogging" checked class="w-3.5 h-3.5 accent-[var(--pm-accent)]">
|
||||
<span class="text-xs">Console Logging</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<button id="updateLogConfigBtn" class="w-full 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">
|
||||
Update Config
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Viewer -->
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<div class="h-96 overflow-y-auto bg-pm-primary font-mono text-xs">
|
||||
<div id="logsContainer" class="p-3 space-y-0.5">
|
||||
<div class="text-center py-6 text-pm-muted text-xs">
|
||||
Click "Refresh" to load logs
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Files -->
|
||||
<div class="p-4 border-t border-pm">
|
||||
<h4 class="text-xs font-semibold text-pm-secondary uppercase tracking-wide mb-3">Log Files</h4>
|
||||
<div id="logFilesList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
@@ -1410,36 +988,6 @@ No sentences, no commentary, no captions. Only tags. Keep length short but descr
|
||||
</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>
|
||||
|
||||
+27
-249
@@ -8,8 +8,6 @@
|
||||
<script src="js/html-escape.js"></script>
|
||||
<script src="js/image-helpers.js"></script>
|
||||
<script src="js/gallery-settings.js"></script>
|
||||
<script src="js/cache-ownership.js"></script>
|
||||
<script src="js/scan-progress.js"></script>
|
||||
<script src="js/api-client.js"></script>
|
||||
<script src="js/png-metadata.js"></script>
|
||||
<script src="js/list-state.js"></script>
|
||||
@@ -63,7 +61,7 @@
|
||||
<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 items-center gap-2">
|
||||
<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
|
||||
@@ -76,9 +74,14 @@
|
||||
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||||
List View
|
||||
</button>
|
||||
<button id="settingsBtn"
|
||||
class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||||
<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">
|
||||
@@ -176,7 +179,7 @@
|
||||
<div class="bg-pm-surface rounded-pm-md max-w-2xl w-full max-h-[90vh] overflow-hidden mx-4 border border-pm">
|
||||
<div class="p-3 border-b border-pm">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-pm">Gallery Settings</h3>
|
||||
<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>
|
||||
@@ -190,25 +193,9 @@
|
||||
<div class="space-y-3">
|
||||
<h4 class="text-sm font-semibold text-pm">Performance Settings</h4>
|
||||
|
||||
<!-- Thumbnail Generation -->
|
||||
<div class="bg-pm-input rounded-pm-sm p-3">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div>
|
||||
<h5 class="text-pm text-xs font-medium">Generate Thumbnails</h5>
|
||||
<p class="text-xs text-pm-secondary">Create optimized thumbnails for faster loading</p>
|
||||
</div>
|
||||
<button id="generateThumbnailsBtn" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs rounded-pm-sm transition-colors">
|
||||
Generate
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-xs text-pm-muted mb-2">
|
||||
Status: <span id="thumbnailStatus">Not generated</span>
|
||||
<div class="text-xs text-pm-muted mt-1">
|
||||
Thumbnails speed up gallery loading. Original images always used for viewing and metadata.
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
|
||||
<!-- Check thumbnails at startup -->
|
||||
<div class="flex items-center justify-between py-2 border-t border-pm">
|
||||
<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>
|
||||
@@ -218,43 +205,9 @@
|
||||
<div class="w-11 h-6 bg-pm-input peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-pm-accent rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-pm after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-pm-accent"></div>
|
||||
</label>
|
||||
</div>
|
||||
<!-- Progress bar (hidden by default) -->
|
||||
<div id="thumbnailProgress" class="hidden">
|
||||
<div class="flex items-center justify-between text-xs text-pm-secondary mb-1">
|
||||
<span id="progressText">Processing...</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span id="progressPercent">0%</span>
|
||||
<button id="cancelThumbnailsBtn" class="text-pm-error hover:text-pm-error text-xs px-2 py-1 bg-pm-error-tint rounded-pm-sm hidden">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full bg-pm-input rounded-full h-2">
|
||||
<div id="progressBar" class="bg-pm-accent h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-pm-muted mt-1">
|
||||
<span id="progressDetails">Scanning images...</span>
|
||||
<span id="progressETA" class="text-pm-muted"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image Loading -->
|
||||
<div class="bg-pm-input rounded-pm-sm p-3">
|
||||
<h5 class="text-pm text-xs font-medium mb-2">Image Loading</h5>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Lazy Loading</label>
|
||||
<input type="checkbox" id="lazyLoadingToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Image Quality</label>
|
||||
<select id="imageQualitySelect" class="px-2 py-1 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs">
|
||||
<option value="high">High (Original)</option>
|
||||
<option value="medium" selected>Medium (Optimized)</option>
|
||||
<option value="low">Low (Fast)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div 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>
|
||||
@@ -264,13 +217,6 @@
|
||||
<h4 class="text-sm font-semibold text-pm">Display Settings</h4>
|
||||
|
||||
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Default View Mode</label>
|
||||
<select id="defaultViewModeSelect" class="px-2 py-1 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs">
|
||||
<option value="grid" selected>Grid View</option>
|
||||
<option value="list">List View</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Items per Page</label>
|
||||
<select id="defaultLimitSelect" class="px-2 py-1 bg-pm-input border border-pm rounded-pm-sm text-pm text-xs">
|
||||
@@ -289,14 +235,6 @@
|
||||
<option value="10">10 Columns</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Show Media Info</label>
|
||||
<input type="checkbox" id="showImageInfoToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Collapse Sidebar by Default</label>
|
||||
<input type="checkbox" id="sidebarCollapsedByDefaultToggle" 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">
|
||||
<div>
|
||||
<label class="text-xs text-pm-secondary">Infinite Scrolling</label>
|
||||
@@ -305,78 +243,6 @@
|
||||
<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>
|
||||
|
||||
<!-- Video Settings -->
|
||||
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
|
||||
<h5 class="text-pm text-xs font-medium mb-2">Video Settings</h5>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Autoplay Videos</label>
|
||||
<input type="checkbox" id="videoAutoplayToggle" class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Mute Videos by Default</label>
|
||||
<input type="checkbox" id="videoMuteToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Loop Videos</label>
|
||||
<input type="checkbox" id="videoLoopToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metadata Settings -->
|
||||
<div class="space-y-3">
|
||||
<h4 class="text-sm font-semibold text-pm">Metadata Settings</h4>
|
||||
|
||||
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Auto-load Metadata</label>
|
||||
<input type="checkbox" id="autoLoadMetadataToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Cache Metadata</label>
|
||||
<input type="checkbox" id="cacheMetadataToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs text-pm-secondary">Show File Paths</label>
|
||||
<input type="checkbox" id="showFilePathsToggle" checked class="w-4 h-4 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Maintenance -->
|
||||
<div class="space-y-3">
|
||||
<h4 class="text-sm font-semibold text-pm">Maintenance</h4>
|
||||
|
||||
<div class="bg-pm-input rounded-pm-sm p-3 space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h5 class="text-pm text-xs font-medium">Clear Cache</h5>
|
||||
<p class="text-xs text-pm-secondary">Clear cached metadata and thumbnails</p>
|
||||
</div>
|
||||
<button id="clearCacheBtn" class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs rounded-pm-sm transition-colors">
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h5 class="text-pm text-xs font-medium">Rescan Output Folder</h5>
|
||||
<p class="text-xs text-pm-secondary">Refresh the image list from disk</p>
|
||||
</div>
|
||||
<button id="rescanFolderBtn" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm-accent-fg text-xs rounded-pm-sm transition-colors">
|
||||
Rescan
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h5 class="text-pm text-xs font-medium">Find Duplicate Images</h5>
|
||||
<p class="text-xs text-pm-secondary">Scan for duplicate images based on file content</p>
|
||||
</div>
|
||||
<button id="scanDuplicatesBtn" class="px-3 py-1 bg-pm-warning hover:bg-pm-warning text-pm text-xs rounded-pm-sm transition-colors">
|
||||
Scan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Settings -->
|
||||
@@ -395,6 +261,14 @@
|
||||
</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 & scans in a new tab" class="text-pm-accent hover:underline">Sources & 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">
|
||||
@@ -407,71 +281,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Minimized duplicate scan: shown while it runs with its modal hidden, or when results are waiting -->
|
||||
<button id="dupScanPill" type="button" title="Show duplicate scan"
|
||||
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="dupScanPillText" class="align-middle">Scanning...</span>
|
||||
</button>
|
||||
|
||||
<!-- Duplicates Modal -->
|
||||
<div id="duplicatesModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
|
||||
<div class="bg-pm-surface rounded-pm-md max-w-4xl w-full max-h-[90vh] overflow-hidden mx-4 border border-pm">
|
||||
<div class="p-3 border-b border-pm">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-pm">Duplicate Images Found</h3>
|
||||
<button id="closeDuplicatesBtn" class="p-2 rounded-pm-sm hover:bg-pm-hover transition-colors">
|
||||
<svg class="w-4 h-4 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 overflow-y-auto max-h-[calc(90vh-200px)]">
|
||||
<div id="duplicatesScanStatus" class="py-6">
|
||||
<div class="max-w-md mx-auto 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 for duplicate images</span>
|
||||
<span id="dupProgressText" class="text-pm-secondary text-xs">Starting...</span>
|
||||
</div>
|
||||
<div class="w-full bg-pm-primary rounded-full h-1.5">
|
||||
<div id="dupProgressBar" class="bg-pm-accent h-1.5 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||||
</div>
|
||||
<p class="text-[11px] text-pm-muted mt-2">Every file is hashed, so this takes a while on a large library. You can close this window; the scan keeps running and the results will be waiting.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="duplicatesContent" class="hidden">
|
||||
<div class="mb-3 p-3 bg-pm-error-tint border border-pm-accent rounded-pm-sm">
|
||||
<div class="flex items-start">
|
||||
<svg class="w-4 h-4 text-pm-error mt-0.5 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<div>
|
||||
<h4 class="text-pm-error text-xs font-medium">Warning: Removing duplicate images cannot be undone!</h4>
|
||||
<p class="text-pm-secondary text-xs mt-1">When you remove a duplicate image, the selected file will be permanently deleted from disk. Choose carefully which images to keep.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="duplicatesList" class="space-y-3">
|
||||
<!-- Duplicates will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="duplicatesFooter" class="hidden p-3 border-t border-pm flex justify-between items-center">
|
||||
<div class="text-xs text-pm-secondary">
|
||||
<span id="duplicatesCount">0</span> duplicate image groups found
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button id="selectAllBtn" class="px-3 py-1 bg-pm-input hover:bg-pm-hover text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||||
Select All to Delete
|
||||
</button>
|
||||
<button id="removeDuplicatesBtn" class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||||
Delete Selected
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto Tag Modal -->
|
||||
<div id="autoTagModal" class="fixed inset-0 bg-black/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">
|
||||
@@ -549,9 +358,11 @@
|
||||
<span class="text-pm-secondary text-xs ml-2">(Faster processing, uses ~4-16GB VRAM)</span>
|
||||
</div>
|
||||
</div>
|
||||
<button id="unloadModelBtn" class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs rounded-pm-sm transition-colors hidden">
|
||||
Unload Model
|
||||
</button>
|
||||
<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>
|
||||
@@ -711,39 +522,6 @@ No sentences, no commentary, no captions. Only tags. Keep length short but descr
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto Tag Download Modal -->
|
||||
<div id="autoTagDownloadModal" 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">Downloading Model</h3>
|
||||
|
||||
<!-- Model Name -->
|
||||
<div class="mb-3">
|
||||
<p class="text-pm-secondary text-xs">Downloading:</p>
|
||||
<p id="downloadModelName" class="text-pm text-xs font-medium">GGUF Model</p>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="mb-3">
|
||||
<div class="flex justify-between text-xs text-pm-secondary mb-1">
|
||||
<span id="downloadStatus">Preparing...</span>
|
||||
<span id="downloadProgressPercent">0%</span>
|
||||
</div>
|
||||
<div class="w-full bg-pm-input rounded-full h-2">
|
||||
<div id="downloadProgressBar" class="bg-pm-accent h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||||
</div>
|
||||
<p class="text-pm-secondary text-xs mt-2">The download continues in Activity.</p>
|
||||
</div>
|
||||
|
||||
<!-- Cancel Button -->
|
||||
<div class="flex justify-center">
|
||||
<button id="cancelDownloadBtn"
|
||||
class="px-3 py-1 bg-pm-error hover:bg-pm-error text-pm text-xs font-medium rounded-pm-sm transition-colors">
|
||||
Close
|
||||
</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">
|
||||
|
||||
+16
-1114
File diff suppressed because it is too large
Load Diff
+54
-803
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,8 @@ const REPRESENTATIONS = {
|
||||
limited: 'Record exceeds inspection limits; text is unavailable',
|
||||
};
|
||||
const TEXTLESS = new Set(['invalid', 'limited']);
|
||||
// #332: the hidden prefix that names where a category chip's value came from.
|
||||
const CATEGORY_PREFIX = {image: 'Image category: ', positive: 'Positive prompt category: ', pair: 'Recipe category: '};
|
||||
const REVEAL_URL = '/prompt_manager/images/reveal';
|
||||
// D38 Show in folder: the route's answers, in the maintainer's words.
|
||||
const REVEAL_OPENED = 'Opened the folder on this computer.';
|
||||
@@ -159,9 +161,11 @@ function generationFacts(record, listClass, noteClass) {
|
||||
}
|
||||
// D21: the panel badge names the strongest resolved prompt source; any other source shows no badge.
|
||||
// A prompt that left out a run-time PromptManager part (D125) or a later stage's added conditioning (D126) is still
|
||||
// inferred from the workflow.
|
||||
// inferred from the workflow. "Recorded run" (#386): recorded run history named the save node that wrote the file; it
|
||||
// ranks below any workflow-inferred side, so a branch with an omitted or added part still reads "Workflow inferred".
|
||||
const EVIDENCE_BADGES = [['execution', 'Execution recorded'], ['graph-inferred', 'Workflow inferred'], ['runtime-parts-omitted', 'Workflow inferred'],
|
||||
['added-conditioning', 'Workflow inferred'], ['runtime-parts-omitted-and-added-conditioning', 'Workflow inferred'], ['embedded-parameters', 'Embedded parameters']];
|
||||
['added-conditioning', 'Workflow inferred'], ['runtime-parts-omitted-and-added-conditioning', 'Workflow inferred'], ['recorded-run', 'Recorded run'],
|
||||
['embedded-parameters', 'Embedded parameters']];
|
||||
function evidenceBadge(prompts) {
|
||||
const sources = new Set(Object.values(prompts).filter(role => role?.status === 'resolved').map(role => role.source));
|
||||
return EVIDENCE_BADGES.find(([source]) => sources.has(source))?.[1] ?? null;
|
||||
@@ -269,7 +273,10 @@ export function mountFileInspector(container, {api, sender, parentScope, variant
|
||||
inner.append(heading, el('p', filename, 'small'));
|
||||
const badges = el('div', undefined, 'tags'), badge = evidenceBadge(record.prompts);
|
||||
if (badge) badges.append(el('span', badge, 'chip blue'));
|
||||
if (linked && library.category) badges.append(prefixedChip(library.category_scope === 'pair' ? 'Recipe category: ' : 'Positive prompt category: ', library.category));
|
||||
// #332: an image's own category shows for any link status; an inherited one only when linked.
|
||||
if (library?.category && (library.category_scope === 'image' || linked)) {
|
||||
badges.append(prefixedChip(CATEGORY_PREFIX[library.category_scope] ?? CATEGORY_PREFIX.positive, library.category));
|
||||
}
|
||||
if (badges.childNodes.length) inner.append(badges);
|
||||
const prompts = el('div'); inner.append(prompts);
|
||||
const blocks = mountPromptBlocks(prompts, {sender, parentScope:contentScope, includePairAction:false, contextLabel:promptContext, title});
|
||||
|
||||
@@ -283,8 +283,13 @@ export function createImageStream({ api, onChange = () => {}, batchSize = 40, re
|
||||
};
|
||||
}
|
||||
|
||||
/** The concept's count line (app.js a446): "N of M images · K positive prompts", without the count while unknown. */
|
||||
export function countLine({ loaded, total, positiveCount }) {
|
||||
/**
|
||||
* The concept's count line (app.js a446): "N of M images · K positive prompts", without the count while unknown, and
|
||||
* the page's first "Loading images…" while a first page loads.
|
||||
*/
|
||||
export function countLine({ loaded, total, positiveCount, loading = false, error = null }) {
|
||||
// A first page in flight has no count yet: "0 of 0 images" would claim the filter matched nothing (#391).
|
||||
if (loading && !loaded && !error) return 'Loading images…';
|
||||
const images = `${loaded.toLocaleString('en-US')} of ${total.toLocaleString('en-US')} ${noun(total, 'image')}`;
|
||||
return isCount(positiveCount) ? `${images} · ${positiveCount.toLocaleString('en-US')} ${noun(positiveCount, 'positive prompt')}` : images;
|
||||
}
|
||||
|
||||
@@ -201,7 +201,9 @@ function resolutionFor(value) {
|
||||
function libraryFor(value) {
|
||||
if (!object(value) || !['linked', 'none', 'ambiguous', 'unavailable', 'limited'].includes(value.status) || value.scope !== 'saved-library' ||
|
||||
!Array.isArray(value.tags) || value.tags.some(tag => typeof tag !== 'string') ||
|
||||
![value.category_scope, value.tags_scope].every(scope => [null, 'pair', 'positive'].includes(scope))) throw new TypeError('Invalid inspection library');
|
||||
![null, 'pair', 'positive', 'image'].includes(value.category_scope) ||
|
||||
![null, 'pair', 'positive'].includes(value.tags_scope)) throw new TypeError('Invalid inspection library');
|
||||
// #332: an image's own category (scope "image") may sit on any link status; tags never have that scope.
|
||||
// D39: each field names its own source, so a recipe's category can sit beside its positive's tags.
|
||||
const library = { status: value.status, scope: value.scope, tags: [...value.tags], category_scope: value.category_scope, tags_scope: value.tags_scope };
|
||||
for (const key of ['positive_id', 'pair_id', 'title', 'category']) {
|
||||
|
||||
@@ -225,6 +225,23 @@ export function clearSentence(files) {
|
||||
return `Empties ${ACTIVE_LOG}, deletes ${number(rotated.length)} rotated ${noun(rotated.length, 'copy', 'copies')} (${sizeText(size)}) ${tail}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* #389: the `/logs/truncate` body for the files a confirmation showed. Their active and rotated rows go back with the
|
||||
* server's `identity`, so the server deletes only those and refuses (409) when any changed meanwhile. A list that could
|
||||
* not be read (`null`, the general sentence) sends no list: the server then clears what is there, as the sentence says.
|
||||
*/
|
||||
export function clearBody(files) {
|
||||
if (!Array.isArray(files)) return {};
|
||||
const expected = files.filter(row => (row?.kind === 'active' || row?.kind === 'rotated') && typeof row.filename === 'string'
|
||||
&& typeof row.identity === 'string').map(row => ({ name: row.filename, identity: row.identity }));
|
||||
return { expected };
|
||||
}
|
||||
|
||||
/** A refused clear (409) says why in the server's words; any other failure the general line. */
|
||||
export function clearFailureText(error) {
|
||||
return error?.status === 409 && typeof error.message === 'string' && error.message ? error.message : COPY.clearFailed;
|
||||
}
|
||||
|
||||
/** CUJ 7: the toast for `/logs/truncate`'s `results` ({truncated: [names], errors: [messages]}). */
|
||||
export function clearToast(results) {
|
||||
const truncated = Array.isArray(results?.truncated) ? results.truncated : [];
|
||||
@@ -469,13 +486,14 @@ function logsSurface({ api, activity, scope, drafts, navigate, notify, latestOnl
|
||||
const list = el('div', undefined, 'operation-list');
|
||||
let listed = null;
|
||||
// The confirmation names what Clear deletes now: the files are read again before it opens (a read that fails
|
||||
// gives the general sentence, never an old count).
|
||||
// gives the general sentence, never an old count). Clear sends exactly the files the dialog was built from (#389).
|
||||
const clear = operationRow({ scope, title: 'Clear all logs', description: COPY.clearDescription, label: 'Clear all logs…',
|
||||
danger: true, onClick: button => pressing(button, async () => {
|
||||
await readFiles();
|
||||
if (scope.signal.aborted) return;
|
||||
confirmDialog({ scope, trigger: button, title: 'Clear all logs', sentence: clearSentence(listed), label: 'Clear',
|
||||
danger: true, onConfirm: clearLogs });
|
||||
const shown = listed;
|
||||
confirmDialog({ scope, trigger: button, title: 'Clear all logs', sentence: clearSentence(shown), label: 'Clear',
|
||||
danger: true, onConfirm: () => clearLogs(shown) });
|
||||
}) });
|
||||
files.append(el('summary', 'Log files & retention'), line, review, retention, list, clear);
|
||||
const runtime = runtimeRows({ scope, drafts });
|
||||
@@ -562,12 +580,12 @@ function logsSurface({ api, activity, scope, drafts, navigate, notify, latestOnl
|
||||
readConfig();
|
||||
}
|
||||
|
||||
async function clearLogs() {
|
||||
async function clearLogs(shown) {
|
||||
let answer;
|
||||
try {
|
||||
answer = await api.post(TRUNCATE_URL, {});
|
||||
} catch {
|
||||
if (!scope.signal.aborted) notify(COPY.clearFailed);
|
||||
answer = await api.post(TRUNCATE_URL, clearBody(shown));
|
||||
} catch (error) {
|
||||
if (!scope.signal.aborted) notify(clearFailureText(error));
|
||||
}
|
||||
if (scope.signal.aborted) return;
|
||||
if (answer) notify(clearToast(answer.results));
|
||||
|
||||
Reference in New Issue
Block a user