Merge pull request #112 from ComfyAssets/feature/tailwind-v4-comfyui-theme-101
feat: migrate frontend to Tailwind v4 with ComfyUI theme tokens
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
TAILWIND_VERSION := 3.4.17
|
||||
TAILWIND_VERSION := 4.1.18
|
||||
TAILWIND_CLI := ./tailwindcss
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_M := $(shell uname -m)
|
||||
|
||||
@@ -1065,6 +1065,7 @@ class AdminRoutesMixin:
|
||||
prompt_text.strip(),
|
||||
"scanned",
|
||||
["auto-scanned"],
|
||||
None,
|
||||
f"Auto-scanned from {os.path.basename(png_file)}",
|
||||
prompt_hash,
|
||||
)
|
||||
|
||||
+19
-14
@@ -5,6 +5,25 @@ import json
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
try:
|
||||
from ...utils.validators import (
|
||||
validate_prompt_text,
|
||||
validate_rating,
|
||||
validate_tags,
|
||||
validate_category,
|
||||
sanitize_input,
|
||||
)
|
||||
from ...utils.hashing import generate_prompt_hash
|
||||
except ImportError:
|
||||
from utils.validators import (
|
||||
validate_prompt_text,
|
||||
validate_rating,
|
||||
validate_tags,
|
||||
validate_category,
|
||||
sanitize_input,
|
||||
)
|
||||
from utils.hashing import generate_prompt_hash
|
||||
|
||||
|
||||
class PromptRoutesMixin:
|
||||
"""Mixin providing prompt-related API endpoints."""
|
||||
@@ -491,14 +510,6 @@ class PromptRoutesMixin:
|
||||
async def save_prompt(self, request):
|
||||
"""Save a new prompt with metadata and duplicate detection."""
|
||||
try:
|
||||
from utils.validators import (
|
||||
validate_prompt_text,
|
||||
validate_rating,
|
||||
validate_tags,
|
||||
validate_category,
|
||||
sanitize_input,
|
||||
)
|
||||
|
||||
data = await request.json()
|
||||
|
||||
text = data.get("text", "").strip()
|
||||
@@ -524,8 +535,6 @@ class PromptRoutesMixin:
|
||||
|
||||
text = sanitize_input(text)
|
||||
|
||||
from utils.hashing import generate_prompt_hash
|
||||
|
||||
prompt_hash = generate_prompt_hash(text)
|
||||
|
||||
existing = await self._run_in_executor(
|
||||
@@ -608,8 +617,6 @@ class PromptRoutesMixin:
|
||||
async def update_prompt(self, request):
|
||||
"""Update prompt text."""
|
||||
try:
|
||||
from utils.validators import validate_prompt_text, sanitize_input
|
||||
|
||||
prompt_id = int(request.match_info["prompt_id"])
|
||||
data = await request.json()
|
||||
new_text = data.get("text", "").strip()
|
||||
@@ -654,8 +661,6 @@ class PromptRoutesMixin:
|
||||
async def update_prompt_rating(self, request):
|
||||
"""Update prompt rating."""
|
||||
try:
|
||||
from utils.validators import validate_rating
|
||||
|
||||
prompt_id = int(request.match_info["prompt_id"])
|
||||
data = await request.json()
|
||||
rating = data.get("rating")
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "promptmanager"
|
||||
description = "A powerful ComfyUI custom node that extends the standard text encoder with persistent prompt storage, advanced search capabilities, and an automatic image gallery system using SQLite."
|
||||
version = "3.0.34"
|
||||
version = "3.0.35"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["# Core dependencies for PromptManager", "# Note: Most dependencies are already included with ComfyUI", "# Already included with Python standard library:", "# - sqlite3", "# - hashlib", "# - json", "# - datetime", "# - os", "# - typing", "# - threading", "# - uuid", "# Required for gallery functionality:", "watchdog>=2.1.0 # For file system monitoring", "Pillow>=8.0.0 # For image metadata extraction (usually included with ComfyUI)", "# Optional dependencies for enhanced search functionality:", "# fuzzywuzzy[speedup]>=0.18.0 # For fuzzy string matching (optional)", "# sqlalchemy>=1.4.0 # For advanced ORM features (optional)", "# Development dependencies (optional):", "# pytest>=6.0.0 # For running tests", "# black>=22.0.0 # For code formatting", "# flake8>=4.0.0 # For linting", "# mypy>=0.910 # For type checking"]
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
content: ["./web/**/*.html", "./web/js/**/*.js"],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
gray: { 850: '#1f2937', 875: '#1a202c', 925: '#0f172a' }
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
+558
-778
File diff suppressed because it is too large
Load Diff
+296
-461
File diff suppressed because it is too large
Load Diff
+213
-213
@@ -420,12 +420,12 @@
|
||||
|
||||
if (this.prompts.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center py-12">
|
||||
<div class="w-16 h-16 bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span class="text-2xl">📭</span>
|
||||
<div class="text-center py-6">
|
||||
<div class="w-16 h-16 bg-pm-surface rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<span class="text-lg">📭</span>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-gray-300 mb-2">No prompts found</h3>
|
||||
<p class="text-gray-500">Try adjusting your search criteria or create some prompts in ComfyUI</p>
|
||||
<h3 class="text-sm font-semibold text-pm-secondary mb-2">No prompts found</h3>
|
||||
<p class="text-pm-muted">Try adjusting your search criteria or create some prompts in ComfyUI</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -490,20 +490,20 @@
|
||||
const created = new Date(prompt.created_at).toLocaleDateString();
|
||||
|
||||
return `
|
||||
<div class="bg-gray-700/50 rounded-xl border border-gray-600 hover:border-gray-500 transition-all duration-200" data-id="${prompt.id}">
|
||||
<div class="p-6">
|
||||
<div class="bg-pm-surface rounded-pm-md border border-pm hover:border-pm transition-all duration-200" data-id="${prompt.id}">
|
||||
<div class="p-4">
|
||||
<div class="flex items-start space-x-4">
|
||||
<input type="checkbox" class="prompt-checkbox w-5 h-5 text-blue-600 bg-gray-600 border-gray-500 rounded focus:ring-blue-500 mt-1" data-id="${prompt.id}">
|
||||
|
||||
<input type="checkbox" class="prompt-checkbox w-5 h-5 text-pm-accent bg-pm-input border-pm rounded focus:ring-pm-accent mt-1" data-id="${prompt.id}">
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="bg-gray-800 rounded-lg p-4 mb-4 relative group">
|
||||
<div class="prompt-text text-gray-100 leading-relaxed whitespace-pre-wrap" data-id="${prompt.id}">${this.escapeHtml(prompt.text)}</div>
|
||||
<button class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 bg-gray-700 hover:bg-gray-600 text-gray-300 hover:text-white p-2 rounded-lg text-sm" onclick="window.admin.copyPromptToClipboard(${prompt.id})">
|
||||
<div class="bg-pm-surface rounded-pm-sm p-4 mb-4 relative group">
|
||||
<div class="prompt-text text-pm leading-relaxed whitespace-pre-wrap" data-id="${prompt.id}">${this.escapeHtml(prompt.text)}</div>
|
||||
<button class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 bg-pm-surface hover:bg-pm-hover text-pm-secondary hover:text-pm p-2 rounded-pm-sm text-sm" onclick="window.admin.copyPromptToClipboard(${prompt.id})">
|
||||
📋 Copy
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-400 mb-3">
|
||||
|
||||
<div class="flex flex-wrap items-center gap-4 text-sm text-pm-secondary mb-3">
|
||||
<div class="flex items-center space-x-1">
|
||||
<span>📁</span>
|
||||
<span class="category-text" data-id="${prompt.id}">${this.escapeHtml(category)}</span>
|
||||
@@ -522,28 +522,28 @@
|
||||
<div class="tags-accordion" data-prompt-id="${prompt.id}">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
${tags.slice(0, 10).map(tag => `
|
||||
<span class="inline-flex items-center space-x-1 bg-blue-600/20 text-blue-300 px-3 py-1 rounded-full text-sm border border-blue-600/30">
|
||||
<span class="inline-flex items-center space-x-1 bg-pm-accent-tint text-pm-accent px-3 py-1 rounded-full text-sm border border-pm-accent">
|
||||
<span>${this.escapeHtml(tag)}</span>
|
||||
<button class="remove-tag-btn text-blue-300 hover:text-blue-100 ml-1" data-prompt-id="${prompt.id}" data-tag="${this.escapeHtml(tag)}">×</button>
|
||||
<button class="remove-tag-btn text-pm-accent hover:text-pm-accent ml-1" data-prompt-id="${prompt.id}" data-tag="${this.escapeHtml(tag)}">×</button>
|
||||
</span>
|
||||
`).join("")}
|
||||
<button class="inline-flex items-center space-x-1 bg-gray-600 hover:bg-gray-500 text-gray-300 px-3 py-1 rounded-full text-sm transition-colors" onclick="window.admin.addTag(${prompt.id})">
|
||||
<button class="inline-flex items-center space-x-1 bg-pm-input hover:bg-pm-hover text-pm-secondary px-3 py-1 rounded-full text-sm transition-colors" onclick="window.admin.addTag(${prompt.id})">
|
||||
<span>+</span>
|
||||
<span>Add Tags</span>
|
||||
</button>
|
||||
</div>
|
||||
${tags.length > 10 ? `
|
||||
<div class="tags-hidden hidden mt-2">
|
||||
<div class="flex flex-wrap items-center gap-2 max-h-[180px] overflow-y-auto custom-scrollbar p-2 bg-gray-800/50 rounded-lg">
|
||||
<div class="flex flex-wrap items-center gap-2 max-h-[180px] overflow-y-auto p-2 bg-pm-surface rounded-pm-sm">
|
||||
${tags.slice(10).map(tag => `
|
||||
<span class="inline-flex items-center space-x-1 bg-blue-600/20 text-blue-300 px-3 py-1 rounded-full text-sm border border-blue-600/30">
|
||||
<span class="inline-flex items-center space-x-1 bg-pm-accent-tint text-pm-accent px-3 py-1 rounded-full text-sm border border-pm-accent">
|
||||
<span>${this.escapeHtml(tag)}</span>
|
||||
<button class="remove-tag-btn text-blue-300 hover:text-blue-100 ml-1" data-prompt-id="${prompt.id}" data-tag="${this.escapeHtml(tag)}">×</button>
|
||||
<button class="remove-tag-btn text-pm-accent hover:text-pm-accent ml-1" data-prompt-id="${prompt.id}" data-tag="${this.escapeHtml(tag)}">×</button>
|
||||
</span>
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
<button class="tags-toggle-btn mt-2 text-sm text-blue-400 hover:text-blue-300 transition-colors flex items-center gap-1" onclick="window.admin.toggleMainTags(${prompt.id})">
|
||||
<button class="tags-toggle-btn mt-2 text-sm text-pm-accent hover:text-pm-accent transition-colors flex items-center gap-1" onclick="window.admin.toggleMainTags(${prompt.id})">
|
||||
<span class="toggle-icon">▼</span>
|
||||
<span class="toggle-text">Show ${tags.length - 10} more tags</span>
|
||||
</button>
|
||||
@@ -551,19 +551,19 @@
|
||||
</div>
|
||||
|
||||
<!-- Film Strip -->
|
||||
<div class="film-strip-container mt-3 pt-3 border-t border-gray-600/50" id="filmStrip-${prompt.id}">
|
||||
<div class="film-strip-container mt-3 pt-3 border-t border-pm-subtle" id="filmStrip-${prompt.id}">
|
||||
<div class="film-strip-empty">Loading images...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-2">
|
||||
<button class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white text-sm font-medium rounded-lg transition-colors" onclick="window.admin.viewGallery(${prompt.id})">
|
||||
<button class="px-4 py-2 bg-pm-accent hover:bg-pm-accent-hover text-pm text-sm font-medium rounded-pm-sm transition-colors" onclick="window.admin.viewGallery(${prompt.id})">
|
||||
🖼️ Gallery
|
||||
</button>
|
||||
<button class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors" onclick="window.admin.editPrompt(${prompt.id})">
|
||||
<button class="px-4 py-2 bg-pm-accent hover:bg-pm-accent-hover text-pm text-sm font-medium rounded-pm-sm transition-colors" onclick="window.admin.editPrompt(${prompt.id})">
|
||||
✏️ Edit
|
||||
</button>
|
||||
<button class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-lg transition-colors" onclick="window.admin.deletePrompt(${prompt.id})">
|
||||
<button class="px-4 py-2 bg-pm-error hover:bg-pm-error text-pm text-sm font-medium rounded-pm-sm transition-colors" onclick="window.admin.deletePrompt(${prompt.id})">
|
||||
🗑️ Delete
|
||||
</button>
|
||||
</div>
|
||||
@@ -578,7 +578,7 @@
|
||||
const starNum = i + 1;
|
||||
const isActive = starNum <= rating;
|
||||
const starIcon = isActive ? '⭐' : '☆'; // filled star vs outline star
|
||||
const color = isActive ? '#fbbf24' : '#9ca3af'; // yellow-400 : gray-400
|
||||
const color = isActive ? 'var(--pm-warning)' : 'var(--pm-text-secondary)'; // yellow-400 : gray-400
|
||||
return `<button class="star-btn" data-star="${starNum}" data-prompt-id="${promptId}" style="font-size: 1.125rem; color: ${color}; background: none; border: none; cursor: pointer; transition: all 0.2s; width: 1.125rem; height: 1.125rem; display: inline-flex; align-items: center; justify-content: center; padding: 0; line-height: 1;" onclick="window.admin.setRating(${promptId}, ${starNum})">${starIcon}</button>`;
|
||||
}).join("");
|
||||
}
|
||||
@@ -596,23 +596,23 @@
|
||||
starButtons.forEach((btn, i) => {
|
||||
if (i < starNum) {
|
||||
btn.textContent = '⭐';
|
||||
btn.style.color = '#fcd34d'; // bright yellow on hover
|
||||
btn.style.color = 'var(--pm-warning)'; // bright yellow on hover
|
||||
} else {
|
||||
btn.textContent = '☆';
|
||||
btn.style.color = '#9ca3af'; // gray
|
||||
btn.style.color = 'var(--pm-text-secondary)'; // gray
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
starBtn.addEventListener('mouseleave', () => {
|
||||
// Reset to current rating
|
||||
starButtons.forEach((btn, i) => {
|
||||
if (i < currentRating) {
|
||||
btn.textContent = '⭐';
|
||||
btn.style.color = '#fbbf24'; // yellow
|
||||
btn.style.color = 'var(--pm-warning)'; // yellow
|
||||
} else {
|
||||
btn.textContent = '☆';
|
||||
btn.style.color = '#9ca3af'; // gray
|
||||
btn.style.color = 'var(--pm-text-secondary)'; // gray
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -633,13 +633,13 @@
|
||||
const isViewerOpen = viewerContainer && viewerContainer.style.display !== 'none';
|
||||
const zIndex = isViewerOpen ? 'z-[50000]' : 'z-50';
|
||||
|
||||
notification.className = `fixed top-4 right-4 px-6 py-4 rounded-lg shadow-lg ${zIndex} transition-all duration-300 transform translate-x-full`;
|
||||
|
||||
notification.className = `fixed top-4 right-4 px-4 py-2 rounded-pm-sm shadow-pm text-sm ${zIndex} transition-all duration-300 transform translate-x-full`;
|
||||
|
||||
const colors = {
|
||||
success: "bg-green-600 text-white",
|
||||
error: "bg-red-600 text-white",
|
||||
warning: "bg-yellow-600 text-white",
|
||||
info: "bg-blue-600 text-white"
|
||||
success: "bg-pm-success text-pm",
|
||||
error: "bg-pm-error text-pm",
|
||||
warning: "bg-pm-warning text-pm",
|
||||
info: "bg-pm-accent text-pm"
|
||||
};
|
||||
|
||||
notification.className += ` ${colors[type] || colors.info}`;
|
||||
@@ -868,7 +868,7 @@
|
||||
const originalText = promptElement.textContent;
|
||||
promptElement.contentEditable = true;
|
||||
promptElement.focus();
|
||||
promptElement.classList.add("bg-gray-700", "border", "border-blue-500", "rounded-lg", "p-3");
|
||||
promptElement.classList.add("bg-pm-surface", "border", "border-pm-accent", "rounded-pm-sm", "p-3");
|
||||
|
||||
const saveEdit = async () => {
|
||||
const newText = promptElement.textContent.trim();
|
||||
@@ -896,7 +896,7 @@
|
||||
}
|
||||
}
|
||||
promptElement.contentEditable = false;
|
||||
promptElement.classList.remove("bg-gray-700", "border", "border-blue-500", "rounded-lg", "p-3");
|
||||
promptElement.classList.remove("bg-pm-surface", "border", "border-pm-accent", "rounded-pm-sm", "p-3");
|
||||
};
|
||||
|
||||
promptElement.addEventListener("blur", saveEdit, { once: true });
|
||||
@@ -908,7 +908,7 @@
|
||||
if (e.key === "Escape") {
|
||||
promptElement.textContent = originalText;
|
||||
promptElement.contentEditable = false;
|
||||
promptElement.classList.remove("bg-gray-700", "border", "border-blue-500", "rounded-lg", "p-3");
|
||||
promptElement.classList.remove("bg-pm-surface", "border", "border-pm-accent", "rounded-pm-sm", "p-3");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1095,25 +1095,25 @@
|
||||
empty.classList.add("hidden");
|
||||
|
||||
content.innerHTML = images.map((image, index) => `
|
||||
<div class="group cursor-pointer bg-gray-700 rounded-xl overflow-hidden border border-gray-600 hover:border-gray-500 transition-all duration-200">
|
||||
<div class="aspect-square bg-gray-800 overflow-hidden relative">
|
||||
<div class="group cursor-pointer bg-pm-surface rounded-pm-md overflow-hidden border border-pm hover:border-pm transition-all duration-200">
|
||||
<div class="aspect-square bg-pm-surface overflow-hidden relative">
|
||||
<img src="/prompt_manager/images/${image.id}/file"
|
||||
alt="Generated image ${index + 1}"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
data-original="/prompt_manager/images/${image.id}/file"
|
||||
data-caption="Generated: ${new Date(image.generation_time).toLocaleDateString()} ${new Date(image.generation_time).toLocaleTimeString()} | ${image.width && image.height ? `${image.width}×${image.height}` : 'Unknown size'}${image.file_size ? ` | ${this.formatFileSize(image.file_size)}` : ''}"
|
||||
onerror="this.parentElement.innerHTML='<div class=\\'flex items-center justify-center h-full text-gray-400\\'>⚠️ Image not found</div>'">
|
||||
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-20 transition-opacity duration-300 flex items-center justify-center pointer-events-none">
|
||||
<svg class="w-8 h-8 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
onerror="this.parentElement.innerHTML='<div class=\\'flex items-center justify-center h-full text-pm-secondary\\'>⚠️ Image not found</div>'">
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-opacity duration-300 flex items-center justify-center pointer-events-none">
|
||||
<svg class="w-8 h-8 text-pm opacity-0 group-hover:opacity-100 transition-opacity duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3">
|
||||
<div class="text-xs text-gray-400 mb-1">
|
||||
<div class="text-xs text-pm-secondary mb-1">
|
||||
${new Date(image.generation_time).toLocaleDateString()} ${new Date(image.generation_time).toLocaleTimeString()}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
<div class="text-xs text-pm-muted">
|
||||
${image.width && image.height ? `${image.width}×${image.height}` : 'Unknown size'}
|
||||
${image.file_size ? ` • ${this.formatFileSize(image.file_size)}` : ''}
|
||||
</div>
|
||||
@@ -1162,7 +1162,7 @@
|
||||
zoomOnWheel: true,
|
||||
slideOnTouch: true,
|
||||
toggleOnDblclick: false,
|
||||
className: 'viewer-dark-theme',
|
||||
className: '',
|
||||
shown: () => {
|
||||
this.addMetadataSidebar();
|
||||
},
|
||||
@@ -1245,10 +1245,10 @@
|
||||
runButton.disabled = true;
|
||||
runButton.innerHTML = '🔄 Running...';
|
||||
resultsContainer.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<div class="text-center py-4">
|
||||
<div class="w-12 h-12 border-4 border-teal-500/30 border-t-teal-500 rounded-full animate-spin mx-auto mb-4"></div>
|
||||
<p class="text-gray-400">Running maintenance operations...</p>
|
||||
<p class="text-sm text-gray-500 mt-2">Operations: ${selectedOps.map(op => op.replace(/_/g, ' ')).join(', ')}</p>
|
||||
<p class="text-pm-secondary">Running maintenance operations...</p>
|
||||
<p class="text-sm text-pm-muted mt-2">Operations: ${selectedOps.map(op => op.replace(/_/g, ' ')).join(', ')}</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1282,9 +1282,9 @@
|
||||
} catch (error) {
|
||||
console.error('Maintenance error:', error);
|
||||
resultsContainer.innerHTML = `
|
||||
<div class="bg-red-900/20 border border-red-600 rounded-lg p-4">
|
||||
<h4 class="text-red-400 font-medium mb-2">❌ Maintenance Failed</h4>
|
||||
<p class="text-gray-300">${error.message}</p>
|
||||
<div class="bg-pm-error-tint border border-pm-error rounded-pm-sm p-4">
|
||||
<h4 class="text-pm-error font-medium mb-2">❌ Maintenance Failed</h4>
|
||||
<p class="text-pm-secondary">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
this.showNotification('❌ Maintenance failed', 'error');
|
||||
@@ -1300,19 +1300,19 @@
|
||||
let html = '<div class="space-y-4">';
|
||||
|
||||
// Summary
|
||||
html += '<div class="bg-gray-700 rounded-lg p-4">';
|
||||
html += '<h4 class="text-white font-medium mb-3">📋 Maintenance Summary</h4>';
|
||||
html += '<div class="bg-pm-surface rounded-pm-sm p-4">';
|
||||
html += '<h4 class="text-pm font-medium mb-3">📋 Maintenance Summary</h4>';
|
||||
html += '<div class="grid grid-cols-1 md:grid-cols-2 gap-3">';
|
||||
html += `
|
||||
<div class="flex justify-between items-center p-2 bg-gray-600 rounded">
|
||||
<span class="text-gray-300">Operations Completed</span>
|
||||
<span class="text-green-400 font-mono text-sm">${data.operations_completed}</span>
|
||||
<div class="flex justify-between items-center p-2 bg-pm-input rounded">
|
||||
<span class="text-pm-secondary">Operations Completed</span>
|
||||
<span class="text-pm-success font-mono text-sm">${data.operations_completed}</span>
|
||||
</div>
|
||||
`;
|
||||
html += `
|
||||
<div class="flex justify-between items-center p-2 bg-gray-600 rounded">
|
||||
<span class="text-gray-300">Overall Success</span>
|
||||
<span class="${data.all_successful ? 'text-green-400' : 'text-yellow-400'} font-mono text-sm">
|
||||
<div class="flex justify-between items-center p-2 bg-pm-input rounded">
|
||||
<span class="text-pm-secondary">Overall Success</span>
|
||||
<span class="${data.all_successful ? 'text-pm-success' : 'text-pm-warning'} font-mono text-sm">
|
||||
${data.all_successful ? '✅ ALL PASSED' : '⚠️ SOME ISSUES'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1321,50 +1321,50 @@
|
||||
|
||||
// Detailed results
|
||||
for (const [operation, result] of Object.entries(data.results)) {
|
||||
const bgColor = result.success ? 'bg-green-900/20 border-green-600' : 'bg-red-900/20 border-red-600';
|
||||
const bgColor = result.success ? 'bg-pm-success-tint border-pm-success' : 'bg-pm-error-tint border-pm-error';
|
||||
const statusIcon = result.success ? '✅' : '❌';
|
||||
const statusText = result.success ? 'SUCCESS' : 'FAILED';
|
||||
|
||||
html += `<div class="${bgColor} border rounded-lg p-4">`;
|
||||
html += `<h4 class="text-white font-medium mb-2 flex items-center space-x-2">`;
|
||||
html += `<div class="${bgColor} border rounded-pm-sm p-4">`;
|
||||
html += `<h4 class="text-pm font-medium mb-2 flex items-center space-x-2">`;
|
||||
html += `<span>${statusIcon}</span>`;
|
||||
html += `<span class="capitalize">${operation.replace(/_/g, ' ')}</span>`;
|
||||
html += `<span class="text-sm font-mono ${result.success ? 'text-green-400' : 'text-red-400'}">${statusText}</span>`;
|
||||
html += `<span class="text-sm font-mono ${result.success ? 'text-pm-success' : 'text-pm-error'}">${statusText}</span>`;
|
||||
html += `</h4>`;
|
||||
|
||||
|
||||
if (result.message) {
|
||||
html += `<p class="text-gray-300 mb-2">${result.message}</p>`;
|
||||
html += `<p class="text-pm-secondary mb-2">${result.message}</p>`;
|
||||
}
|
||||
|
||||
|
||||
// Show specific details
|
||||
if (result.removed_count !== undefined) {
|
||||
html += `<div class="text-sm text-gray-400">Items removed: ${result.removed_count}</div>`;
|
||||
html += `<div class="text-sm text-pm-secondary">Items removed: ${result.removed_count}</div>`;
|
||||
}
|
||||
|
||||
|
||||
if (result.duplicate_hashes !== undefined) {
|
||||
html += `<div class="text-sm text-gray-400">Duplicate hash groups found: ${result.duplicate_hashes}</div>`;
|
||||
html += `<div class="text-sm text-pm-secondary">Duplicate hash groups found: ${result.duplicate_hashes}</div>`;
|
||||
}
|
||||
|
||||
|
||||
if (result.issues_found !== undefined) {
|
||||
html += `<div class="text-sm text-gray-400">Issues found: ${result.issues_found}</div>`;
|
||||
html += `<div class="text-sm text-pm-secondary">Issues found: ${result.issues_found}</div>`;
|
||||
if (result.issues && result.issues.length > 0) {
|
||||
html += `<ul class="ml-4 list-disc text-xs text-gray-500 mt-1">`;
|
||||
html += `<ul class="ml-4 list-disc text-xs text-pm-muted mt-1">`;
|
||||
result.issues.forEach(issue => {
|
||||
html += `<li>${issue}</li>`;
|
||||
});
|
||||
html += `</ul>`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (result.info) {
|
||||
html += `<div class="text-sm text-gray-400 mt-2">`;
|
||||
html += `<div class="text-sm text-pm-secondary mt-2">`;
|
||||
html += `<p>Total prompts: ${result.info.total_prompts || 'N/A'}</p>`;
|
||||
html += `<p>Database size: ${result.info.database_size_bytes ? this.formatFileSize(result.info.database_size_bytes) : 'N/A'}</p>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
|
||||
if (result.error) {
|
||||
html += `<div class="text-sm text-red-300 mt-2 font-mono bg-red-900/20 p-2 rounded">`;
|
||||
html += `<div class="text-sm text-pm-error mt-2 font-mono bg-pm-error-tint p-2 rounded">`;
|
||||
html += `Error: ${result.error}`;
|
||||
html += `</div>`;
|
||||
}
|
||||
@@ -1379,9 +1379,9 @@
|
||||
async runDiagnostics() {
|
||||
const content = document.getElementById("diagnosticsContent");
|
||||
content.innerHTML = `
|
||||
<div class="text-center py-8">
|
||||
<div class="text-center py-4">
|
||||
<div class="w-12 h-12 border-4 border-orange-500/30 border-t-orange-500 rounded-full animate-spin mx-auto mb-4"></div>
|
||||
<p class="text-gray-400">Running diagnostics...</p>
|
||||
<p class="text-pm-secondary">Running diagnostics...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1400,9 +1400,9 @@
|
||||
} catch (error) {
|
||||
console.error('Diagnostics error:', error);
|
||||
content.innerHTML = `
|
||||
<div class="bg-red-900/20 border border-red-600 rounded-lg p-4">
|
||||
<h4 class="text-red-400 font-medium mb-2">❌ Diagnostics Failed</h4>
|
||||
<p class="text-gray-300">${error.message}</p>
|
||||
<div class="bg-pm-error-tint border border-pm-error rounded-pm-sm p-4">
|
||||
<h4 class="text-pm-error font-medium mb-2">❌ Diagnostics Failed</h4>
|
||||
<p class="text-pm-secondary">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -1414,17 +1414,17 @@
|
||||
let html = '<div class="space-y-4">';
|
||||
|
||||
// Summary
|
||||
html += '<div class="bg-gray-700 rounded-lg p-4">';
|
||||
html += '<h4 class="text-white font-medium mb-3">📋 Diagnostic Summary</h4>';
|
||||
html += '<div class="bg-pm-surface rounded-pm-sm p-4">';
|
||||
html += '<h4 class="text-pm font-medium mb-3">📋 Diagnostic Summary</h4>';
|
||||
html += '<div class="grid grid-cols-1 md:grid-cols-2 gap-3">';
|
||||
|
||||
|
||||
for (const [category, result] of Object.entries(diagnostics)) {
|
||||
const status = result.status === 'ok' ? '✅ PASS' : (result.status === 'warning' ? '⚠️ WARNING' : '❌ FAIL');
|
||||
const statusColor = result.status === 'ok' ? 'text-green-400' : (result.status === 'warning' ? 'text-yellow-400' : 'text-red-400');
|
||||
|
||||
const statusColor = result.status === 'ok' ? 'text-pm-success' : (result.status === 'warning' ? 'text-pm-warning' : 'text-pm-error');
|
||||
|
||||
html += `
|
||||
<div class="flex justify-between items-center p-2 bg-gray-600 rounded">
|
||||
<span class="text-gray-300 capitalize">${this.escapeHtml(category)}</span>
|
||||
<div class="flex justify-between items-center p-2 bg-pm-input rounded">
|
||||
<span class="text-pm-secondary capitalize">${this.escapeHtml(category)}</span>
|
||||
<span class="${statusColor} font-mono text-sm">${status}</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -1434,26 +1434,26 @@
|
||||
|
||||
// Detailed results
|
||||
for (const [category, result] of Object.entries(diagnostics)) {
|
||||
const bgColor = result.status === 'ok' ? 'bg-green-900/20 border-green-600' :
|
||||
(result.status === 'warning' ? 'bg-yellow-900/20 border-yellow-600' : 'bg-red-900/20 border-red-600');
|
||||
|
||||
html += `<div class="${bgColor} border rounded-lg p-4">`;
|
||||
html += `<h4 class="text-white font-medium mb-2 capitalize">${this.escapeHtml(category)}</h4>`;
|
||||
|
||||
const bgColor = result.status === 'ok' ? 'bg-pm-success-tint border-pm-success' :
|
||||
(result.status === 'warning' ? 'bg-pm-warning/20 border-pm-warning' : 'bg-pm-error-tint border-pm-error');
|
||||
|
||||
html += `<div class="${bgColor} border rounded-pm-sm p-4">`;
|
||||
html += `<h4 class="text-pm font-medium mb-2 capitalize">${this.escapeHtml(category)}</h4>`;
|
||||
|
||||
if (result.message) {
|
||||
html += `<p class="text-gray-300 mb-2">${result.message}</p>`;
|
||||
html += `<p class="text-pm-secondary mb-2">${result.message}</p>`;
|
||||
}
|
||||
|
||||
|
||||
// Show specific details based on category
|
||||
if (category === 'database' && result.status === 'ok') {
|
||||
html += `<div class="text-sm text-gray-400">`;
|
||||
html += `<div class="text-sm text-pm-secondary">`;
|
||||
html += `<p>Prompts: ${result.prompt_count || 0}</p>`;
|
||||
html += `<p>Images table: ${result.has_images_table ? 'Yes' : 'No'}</p>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
|
||||
if (category === 'images_table' && result.status === 'ok') {
|
||||
html += `<div class="text-sm text-gray-400">`;
|
||||
html += `<div class="text-sm text-pm-secondary">`;
|
||||
html += `<p>Images: ${result.image_count || 0}</p>`;
|
||||
if (result.recent_images && result.recent_images.length > 0) {
|
||||
html += `<p>Recent images:</p>`;
|
||||
@@ -1465,9 +1465,9 @@
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
|
||||
if (category === 'comfyui_output' && result.output_dirs) {
|
||||
html += `<div class="text-sm text-gray-400">`;
|
||||
html += `<div class="text-sm text-pm-secondary">`;
|
||||
html += `<p>Output directories found:</p>`;
|
||||
html += `<ul class="ml-4 list-disc">`;
|
||||
result.output_dirs.forEach(dir => {
|
||||
@@ -1476,9 +1476,9 @@
|
||||
html += `</ul>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
|
||||
if (category === 'dependencies' && result.dependencies) {
|
||||
html += `<div class="text-sm text-gray-400">`;
|
||||
html += `<div class="text-sm text-pm-secondary">`;
|
||||
for (const [dep, available] of Object.entries(result.dependencies)) {
|
||||
const status = available ? '✅' : '❌';
|
||||
html += `<p>${status} ${dep}</p>`;
|
||||
@@ -1689,14 +1689,14 @@
|
||||
showCopyFallbackModal(text) {
|
||||
// Create a temporary modal for manual copy
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
|
||||
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
|
||||
modal.innerHTML = `
|
||||
<div class="bg-gray-800 rounded-xl p-6 max-w-2xl w-full mx-4 border border-gray-700">
|
||||
<h3 class="text-xl font-semibold text-gray-100 mb-4">📋 Copy Prompt Text</h3>
|
||||
<p class="text-gray-400 mb-4">Please manually copy the text below:</p>
|
||||
<textarea readonly class="w-full h-32 px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg text-gray-100 resize-none" style="font-family: monospace;">${text}</textarea>
|
||||
<div class="bg-pm-surface rounded-pm-md p-4 max-w-2xl w-full mx-4 border border-pm">
|
||||
<h3 class="text-sm font-semibold text-pm mb-3">Copy Prompt Text</h3>
|
||||
<p class="text-pm-secondary mb-4">Please manually copy the text below:</p>
|
||||
<textarea readonly class="w-full h-32 px-4 py-3 bg-pm-surface border border-pm rounded-pm-sm text-pm resize-none" style="font-family: monospace;">${text}</textarea>
|
||||
<div class="flex justify-end mt-4">
|
||||
<button class="px-6 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors" onclick="this.closest('[class*=fixed]').remove()">
|
||||
<button class="px-4 py-1.5 bg-pm-accent hover:bg-pm-accent-hover text-pm font-medium rounded-pm-sm transition-colors" onclick="this.closest('[class*=fixed]').remove()">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
@@ -1903,21 +1903,21 @@
|
||||
const container = document.getElementById("logFilesList");
|
||||
|
||||
if (data.files.length === 0) {
|
||||
container.innerHTML = '<div class="col-span-full text-center text-gray-500">No log files found</div>';
|
||||
container.innerHTML = '<div class="col-span-full text-center text-pm-muted">No log files found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = data.files.map(file => `
|
||||
<div class="bg-gray-700 rounded-lg p-3">
|
||||
<div class="bg-pm-surface rounded-pm-sm p-3">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-gray-200">${file.filename}</span>
|
||||
${file.is_main ? '<span class="bg-blue-500 text-xs px-2 py-1 rounded">Active</span>' : ''}
|
||||
<span class="text-sm font-medium text-pm">${file.filename}</span>
|
||||
${file.is_main ? '<span class="bg-pm-accent text-xs px-2 py-1 rounded">Active</span>' : ''}
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 mb-2">
|
||||
<div class="text-xs text-pm-secondary mb-2">
|
||||
Size: ${this.formatBytes(file.size)} | Modified: ${new Date(file.modified).toLocaleString()}
|
||||
</div>
|
||||
<button onclick="window.admin.downloadLogFile('${file.filename}')"
|
||||
class="w-full px-3 py-1 bg-green-600 hover:bg-green-700 text-white text-xs rounded transition-colors">
|
||||
<button onclick="window.admin.downloadLogFile('${file.filename}')"
|
||||
class="w-full px-3 py-1 bg-pm-success hover:bg-pm-success text-pm text-xs rounded transition-colors">
|
||||
📥 Download
|
||||
</button>
|
||||
</div>
|
||||
@@ -1956,31 +1956,31 @@
|
||||
const container = document.getElementById("logsContainer");
|
||||
|
||||
if (logs.length === 0) {
|
||||
container.innerHTML = '<div class="text-center py-8 text-gray-500">No logs found</div>';
|
||||
container.innerHTML = '<div class="text-center py-4 text-pm-muted">No logs found</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
container.innerHTML = logs.map(log => {
|
||||
const levelColors = {
|
||||
DEBUG: 'text-gray-400',
|
||||
INFO: 'text-blue-400',
|
||||
WARNING: 'text-yellow-400',
|
||||
ERROR: 'text-red-400',
|
||||
CRITICAL: 'text-red-600'
|
||||
DEBUG: 'text-pm-secondary',
|
||||
INFO: 'text-pm-accent',
|
||||
WARNING: 'text-pm-warning',
|
||||
ERROR: 'text-pm-error',
|
||||
CRITICAL: 'text-pm-error'
|
||||
};
|
||||
|
||||
const levelColor = levelColors[log.level] || 'text-gray-400';
|
||||
|
||||
const levelColor = levelColors[log.level] || 'text-pm-secondary';
|
||||
const timestamp = new Date(log.timestamp).toLocaleString();
|
||||
|
||||
|
||||
return `
|
||||
<div class="border-l-2 border-gray-600 pl-3 py-1 hover:bg-gray-800 transition-colors">
|
||||
<div class="border-l-2 border-pm pl-3 py-1 hover:bg-pm-surface transition-colors">
|
||||
<div class="flex items-start space-x-2 text-sm">
|
||||
<span class="text-gray-500 text-xs font-mono w-24 flex-shrink-0">${timestamp.split(' ')[1]}</span>
|
||||
<span class="text-pm-muted text-xs font-mono w-24 flex-shrink-0">${timestamp.split(' ')[1]}</span>
|
||||
<span class="${levelColor} font-semibold w-16 flex-shrink-0">${log.level}</span>
|
||||
<span class="text-purple-400 text-xs w-32 flex-shrink-0">${log.logger}</span>
|
||||
<span class="text-gray-300 flex-1">${log.message}</span>
|
||||
<span class="text-pm-accent text-xs w-32 flex-shrink-0">${log.logger}</span>
|
||||
<span class="text-pm-secondary flex-1">${log.message}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 ml-44">
|
||||
<div class="text-xs text-pm-muted ml-44">
|
||||
${log.filename}:${log.lineno}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2357,76 +2357,76 @@
|
||||
metadataContent.innerHTML = `
|
||||
<!-- File Path -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-2">File Path</h2>
|
||||
<div class="text-sm text-blue-400 hover:text-blue-300 cursor-pointer bg-gray-800 p-2 rounded break-all" onclick="window.admin.copyToClipboard('${filePath}')">
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">File Path</h2>
|
||||
<div class="text-sm text-pm-accent hover:text-pm-accent cursor-pointer bg-pm-surface p-2 rounded break-all" onclick="window.admin.copyToClipboard('${filePath}')">
|
||||
${filePath}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resources used -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-2">Resources used</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">Resources used</h2>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-blue-400 hover:text-blue-300 cursor-pointer">${checkpoint}</div>
|
||||
<div class="text-xs text-gray-500">ComfyUI Generated</div>
|
||||
<div class="text-pm-accent hover:text-pm-accent cursor-pointer">${checkpoint}</div>
|
||||
<div class="text-xs text-pm-muted">ComfyUI Generated</div>
|
||||
</div>
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">CHECKPOINT</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">CHECKPOINT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prompt -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h2 class="text-sm font-medium text-gray-300">Prompt</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Prompt</h2>
|
||||
<span class="px-2 py-1 text-xs bg-orange-600 text-orange-100 rounded">COMFYUI</span>
|
||||
<button class="ml-auto text-gray-400 hover:text-gray-300" onclick="window.admin.copyPrompt('positive')">
|
||||
<button class="ml-auto text-pm-secondary hover:text-pm-secondary" onclick="window.admin.copyPrompt('positive')">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-gray-300 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto">
|
||||
<div class="text-sm text-pm-secondary bg-pm-surface p-3 rounded max-h-32 overflow-y-auto">
|
||||
${positivePrompt.substring(0, 200)}${positivePrompt.length > 200 ? '...' : ''}
|
||||
</div>
|
||||
${positivePrompt.length > 200 ? '<button class="text-blue-400 hover:text-blue-300 text-sm mt-1" onclick="window.admin.showFullPrompt(\'positive\')">Show more</button>' : ''}
|
||||
${positivePrompt.length > 200 ? '<button class="text-pm-accent hover:text-pm-accent text-sm mt-1" onclick="window.admin.showFullPrompt(\'positive\')">Show more</button>' : ''}
|
||||
</div>
|
||||
|
||||
<!-- Negative prompt -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h2 class="text-sm font-medium text-gray-300">Negative prompt</h2>
|
||||
<button class="text-gray-400 hover:text-gray-300" onclick="window.admin.copyPrompt('negative')">
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Negative prompt</h2>
|
||||
<button class="text-pm-secondary hover:text-pm-secondary" onclick="window.admin.copyPrompt('negative')">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-gray-300 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto">
|
||||
<div class="text-sm text-pm-secondary bg-pm-surface p-3 rounded max-h-32 overflow-y-auto">
|
||||
${negativePrompt.substring(0, 200)}${negativePrompt.length > 200 ? '...' : ''}
|
||||
</div>
|
||||
${negativePrompt.length > 200 ? '<button class="text-blue-400 hover:text-blue-300 text-sm mt-1" onclick="window.admin.showFullPrompt(\'negative\')">Show more</button>' : ''}
|
||||
${negativePrompt.length > 200 ? '<button class="text-pm-accent hover:text-pm-accent text-sm mt-1" onclick="window.admin.showFullPrompt(\'negative\')">Show more</button>' : ''}
|
||||
</div>
|
||||
|
||||
<!-- Other metadata -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-3">Other metadata</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-3">Other metadata</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">CFG SCALE: ${cfgScale}</span>
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">STEPS: ${steps}</span>
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">SAMPLER: ${sampler}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">CFG SCALE: ${cfgScale}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">STEPS: ${steps}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">SAMPLER: ${sampler}</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">SEED: ${seed}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">SEED: ${seed}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Raw Workflow Data -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-2">ComfyUI Workflow</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">ComfyUI Workflow</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="text-blue-400 hover:text-blue-300 text-sm" onclick="window.admin.showWorkflowData()">View Raw Workflow JSON</button>
|
||||
<button class="text-blue-400 hover:text-blue-300" onclick="window.admin.downloadWorkflowJSON()" title="Download JSON">
|
||||
<button class="text-pm-accent hover:text-pm-accent text-sm" onclick="window.admin.showWorkflowData()">View Raw Workflow JSON</button>
|
||||
<button class="text-pm-accent hover:text-pm-accent" onclick="window.admin.downloadWorkflowJSON()" title="Download JSON">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
@@ -2441,11 +2441,11 @@
|
||||
if (!metadataContent) return;
|
||||
|
||||
metadataContent.innerHTML = `
|
||||
<div class="text-center text-red-400 py-8">
|
||||
<div class="text-center text-pm-error py-8">
|
||||
<svg class="w-16 h-16 mx-auto mb-4 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<p class="text-lg mb-2">Error Loading Metadata</p>
|
||||
<p class="text-sm mb-2">Error Loading Metadata</p>
|
||||
<p class="text-sm">Could not extract ComfyUI metadata from this image</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -2977,26 +2977,26 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
sidebar.className = 'metadata-sidebar';
|
||||
sidebar.innerHTML = `
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between p-4 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between p-4 border-b border-pm">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<h1 class="text-lg font-medium text-white">Generation data</h1>
|
||||
<h1 class="text-sm font-semibold text-pm">Generation data</h1>
|
||||
</div>
|
||||
<button class="text-xs text-blue-400 hover:text-blue-300 transition-colors metadata-copy-all">
|
||||
<button class="text-xs text-pm-accent hover:text-pm-accent transition-colors metadata-copy-all">
|
||||
📋 COPY ALL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Scrollable Content -->
|
||||
<div id="metadata-content" class="flex-1 overflow-y-auto p-4 space-y-6">
|
||||
<div id="metadata-content" class="flex-1 overflow-y-auto p-4 space-y-3">
|
||||
<!-- Initial placeholder content -->
|
||||
<div class="text-center text-gray-500 py-8">
|
||||
<div class="text-center text-pm-muted py-8">
|
||||
<svg class="w-16 h-16 mx-auto mb-4 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<p class="text-lg mb-2">Loading Metadata...</p>
|
||||
<p class="text-sm mb-2">Loading Metadata...</p>
|
||||
<p class="text-sm">Extracting ComfyUI workflow data</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3016,11 +3016,11 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
// Show loading state
|
||||
metadataContent.innerHTML = `
|
||||
<div class="metadata-loading">
|
||||
<div class="text-center text-gray-500 py-8">
|
||||
<div class="text-center text-pm-muted py-8">
|
||||
<svg class="w-16 h-16 mx-auto mb-4 opacity-30 animate-spin" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
<p class="text-lg mb-2">Loading Metadata...</p>
|
||||
<p class="text-sm mb-2">Loading Metadata...</p>
|
||||
<p class="text-sm">Extracting ComfyUI workflow data</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3033,8 +3033,8 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
} catch (error) {
|
||||
console.error('Failed to load metadata:', error);
|
||||
metadataContent.innerHTML = `
|
||||
<div class="text-center text-red-500 py-8">
|
||||
<p class="text-lg mb-2">Failed to Load Metadata</p>
|
||||
<div class="text-center text-pm-error py-8">
|
||||
<p class="text-sm mb-2">Failed to Load Metadata</p>
|
||||
<p class="text-sm">Could not extract ComfyUI workflow data</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -3048,76 +3048,76 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
metadataContent.innerHTML = `
|
||||
<!-- File Path -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-2">File Path</h2>
|
||||
<div class="text-sm text-blue-400 hover:text-blue-300 cursor-pointer bg-gray-800 p-2 rounded break-all" data-copy-path="">
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">File Path</h2>
|
||||
<div class="text-sm text-pm-accent hover:text-pm-accent cursor-pointer bg-pm-surface p-2 rounded break-all" data-copy-path="">
|
||||
${metadata.imagePath}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resources used -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-2">Resources used</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">Resources used</h2>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-blue-400 hover:text-blue-300 cursor-pointer">${metadata.checkpoint}</div>
|
||||
<div class="text-xs text-gray-500">ComfyUI Generated</div>
|
||||
<div class="text-pm-accent hover:text-pm-accent cursor-pointer">${metadata.checkpoint}</div>
|
||||
<div class="text-xs text-pm-muted">ComfyUI Generated</div>
|
||||
</div>
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">CHECKPOINT</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">CHECKPOINT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prompt -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h2 class="text-sm font-medium text-gray-300">Prompt</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Prompt</h2>
|
||||
<span class="px-2 py-1 text-xs bg-orange-600 text-orange-100 rounded">COMFYUI</span>
|
||||
<button class="ml-auto text-gray-400 hover:text-gray-300" data-copy-type="positive">
|
||||
<button class="ml-auto text-pm-secondary hover:text-pm-secondary" data-copy-type="positive">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-gray-300 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto">
|
||||
<div class="text-sm text-pm-secondary bg-pm-surface p-3 rounded max-h-32 overflow-y-auto">
|
||||
${metadata.positivePrompt.substring(0, 200)}${metadata.positivePrompt.length > 200 ? '...' : ''}
|
||||
</div>
|
||||
${metadata.positivePrompt.length > 200 ? '<button class="text-blue-400 hover:text-blue-300 text-sm mt-1" data-show-type="positive">Show more</button>' : ''}
|
||||
${metadata.positivePrompt.length > 200 ? '<button class="text-pm-accent hover:text-pm-accent text-sm mt-1" data-show-type="positive">Show more</button>' : ''}
|
||||
</div>
|
||||
|
||||
<!-- Negative prompt -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h2 class="text-sm font-medium text-gray-300">Negative prompt</h2>
|
||||
<button class="text-gray-400 hover:text-gray-300" data-copy-type="negative">
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Negative prompt</h2>
|
||||
<button class="text-pm-secondary hover:text-pm-secondary" data-copy-type="negative">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-gray-300 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto">
|
||||
<div class="text-sm text-pm-secondary bg-pm-surface p-3 rounded max-h-32 overflow-y-auto">
|
||||
${metadata.negativePrompt.substring(0, 200)}${metadata.negativePrompt.length > 200 ? '...' : ''}
|
||||
</div>
|
||||
${metadata.negativePrompt.length > 200 ? '<button class="text-blue-400 hover:text-blue-300 text-sm mt-1" data-show-type="negative">Show more</button>' : ''}
|
||||
${metadata.negativePrompt.length > 200 ? '<button class="text-pm-accent hover:text-pm-accent text-sm mt-1" data-show-type="negative">Show more</button>' : ''}
|
||||
</div>
|
||||
|
||||
<!-- Other metadata -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-3">Other metadata</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-3">Other metadata</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">CFG SCALE: ${metadata.cfgScale}</span>
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">STEPS: ${metadata.steps}</span>
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">SAMPLER: ${metadata.sampler}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">CFG SCALE: ${metadata.cfgScale}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">STEPS: ${metadata.steps}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">SAMPLER: ${metadata.sampler}</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">SEED: ${metadata.seed}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded">SEED: ${metadata.seed}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Raw Workflow Data -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-2">ComfyUI Workflow</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">ComfyUI Workflow</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="text-blue-400 hover:text-blue-300 text-sm" data-action="show-workflow">View Raw Workflow JSON</button>
|
||||
<button class="text-blue-400 hover:text-blue-300" data-action="download-workflow" title="Download JSON">
|
||||
<button class="text-pm-accent hover:text-pm-accent text-sm" data-action="show-workflow">View Raw Workflow JSON</button>
|
||||
<button class="text-pm-accent hover:text-pm-accent" data-action="download-workflow" title="Download JSON">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
@@ -3341,12 +3341,12 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
stars.forEach((star, index) => {
|
||||
if (index < rating) {
|
||||
star.textContent = '★';
|
||||
star.classList.add('text-yellow-400');
|
||||
star.classList.remove('text-gray-500');
|
||||
star.classList.add('text-pm-warning');
|
||||
star.classList.remove('text-pm-muted');
|
||||
} else {
|
||||
star.textContent = '☆';
|
||||
star.classList.remove('text-yellow-400');
|
||||
star.classList.add('text-gray-500');
|
||||
star.classList.remove('text-pm-warning');
|
||||
star.classList.add('text-pm-muted');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -3361,7 +3361,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
// Add tag chips
|
||||
this.addPromptTags.forEach(tag => {
|
||||
const chip = document.createElement('span');
|
||||
chip.className = 'tag-chip inline-flex items-center px-2 py-1 bg-blue-600 text-white text-xs rounded cursor-pointer hover:bg-blue-700';
|
||||
chip.className = 'tag-chip inline-flex items-center px-2 py-1 bg-pm-accent text-pm text-xs rounded cursor-pointer hover:bg-pm-accent-hover';
|
||||
chip.innerHTML = `${tag} <span class="ml-1">×</span>`;
|
||||
chip.addEventListener('click', () => {
|
||||
this.addPromptTags = this.addPromptTags.filter(t => t !== tag);
|
||||
@@ -3389,7 +3389,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
}
|
||||
|
||||
suggestionsContainer.innerHTML = matchingTags.map(tag => `
|
||||
<div class="px-3 py-2 hover:bg-gray-600 cursor-pointer text-sm text-gray-200" data-tag="${tag}">
|
||||
<div class="px-3 py-2 hover:bg-pm-hover cursor-pointer text-sm text-pm" data-tag="${tag}">
|
||||
${tag}
|
||||
</div>
|
||||
`).join('');
|
||||
@@ -3660,8 +3660,8 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
const ggufStatus = document.getElementById("ggufModelStatus");
|
||||
const hfStatus = document.getElementById("hfModelStatus");
|
||||
|
||||
ggufStatus.innerHTML = '<span class="text-gray-400 text-sm">Checking...</span>';
|
||||
hfStatus.innerHTML = '<span class="text-gray-400 text-sm">Checking...</span>';
|
||||
ggufStatus.innerHTML = '<span class="text-pm-secondary text-sm">Checking...</span>';
|
||||
hfStatus.innerHTML = '<span class="text-pm-secondary text-sm">Checking...</span>';
|
||||
|
||||
try {
|
||||
const response = await fetch('/prompt_manager/autotag/models');
|
||||
@@ -3670,25 +3670,25 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
if (data.success) {
|
||||
// Update GGUF status
|
||||
if (data.models.gguf.downloaded) {
|
||||
ggufStatus.innerHTML = '<span class="text-green-400 text-sm">✓ Downloaded</span>';
|
||||
ggufStatus.innerHTML = '<span class="text-pm-success text-sm">✓ Downloaded</span>';
|
||||
} else {
|
||||
ggufStatus.innerHTML = `<button onclick="window.admin.downloadModel('gguf')" class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-xs rounded transition-colors">Download</button>`;
|
||||
ggufStatus.innerHTML = `<button onclick="window.admin.downloadModel('gguf')" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm text-xs rounded transition-colors">Download</button>`;
|
||||
}
|
||||
|
||||
// Update HF status
|
||||
if (data.models.hf.downloaded) {
|
||||
hfStatus.innerHTML = '<span class="text-green-400 text-sm">✓ Downloaded</span>';
|
||||
hfStatus.innerHTML = '<span class="text-pm-success text-sm">✓ Downloaded</span>';
|
||||
} else {
|
||||
hfStatus.innerHTML = `<button onclick="window.admin.downloadModel('hf')" class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-xs rounded transition-colors">Download</button>`;
|
||||
hfStatus.innerHTML = `<button onclick="window.admin.downloadModel('hf')" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm text-xs rounded transition-colors">Download</button>`;
|
||||
}
|
||||
} else {
|
||||
ggufStatus.innerHTML = '<span class="text-red-400 text-sm">Error</span>';
|
||||
hfStatus.innerHTML = '<span class="text-red-400 text-sm">Error</span>';
|
||||
ggufStatus.innerHTML = '<span class="text-pm-error text-sm">Error</span>';
|
||||
hfStatus.innerHTML = '<span class="text-pm-error text-sm">Error</span>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking models:', error);
|
||||
ggufStatus.innerHTML = '<span class="text-red-400 text-sm">Error</span>';
|
||||
hfStatus.innerHTML = '<span class="text-red-400 text-sm">Error</span>';
|
||||
ggufStatus.innerHTML = '<span class="text-pm-error text-sm">Error</span>';
|
||||
hfStatus.innerHTML = '<span class="text-pm-error text-sm">Error</span>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3910,7 +3910,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
// Display existing tags
|
||||
const tagsContainer = document.getElementById('retagExistingTags');
|
||||
tagsContainer.innerHTML = realTags.map(tag =>
|
||||
`<span class="px-2 py-1 bg-purple-600 text-white text-xs rounded">${this.escapeHtml(tag)}</span>`
|
||||
`<span class="px-2 py-1 bg-pm-accent text-pm text-xs rounded">${this.escapeHtml(tag)}</span>`
|
||||
).join('');
|
||||
|
||||
// Show modal
|
||||
@@ -3973,7 +3973,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
|
||||
document.getElementById('reviewImage').src = imageUrl;
|
||||
document.getElementById('reviewImageName').textContent = filename || 'Unknown';
|
||||
document.getElementById('reviewTagsVisible').innerHTML = '<div class="text-gray-400">Generating tags...</div>';
|
||||
document.getElementById('reviewTagsVisible').innerHTML = '<div class="text-pm-secondary">Generating tags...</div>';
|
||||
document.getElementById('reviewTagsHidden').innerHTML = '';
|
||||
document.getElementById('reviewTagsAccordion').classList.add('hidden');
|
||||
document.getElementById('reviewTagsToggle').classList.add('hidden');
|
||||
@@ -4002,12 +4002,12 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
this.renderReviewTags();
|
||||
} else {
|
||||
document.getElementById('reviewTagsVisible').innerHTML =
|
||||
`<div class="text-red-400">Error: ${data.error}</div>`;
|
||||
`<div class="text-pm-error">Error: ${data.error}</div>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating tags:', error);
|
||||
document.getElementById('reviewTagsVisible').innerHTML =
|
||||
'<div class="text-red-400">Failed to generate tags</div>';
|
||||
'<div class="text-pm-error">Failed to generate tags</div>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4021,7 +4021,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
const countSpan = document.getElementById('reviewTagsCount');
|
||||
|
||||
if (this.autoTagState.currentTags.length === 0) {
|
||||
visibleContainer.innerHTML = '<div class="text-gray-400">No tags generated</div>';
|
||||
visibleContainer.innerHTML = '<div class="text-pm-secondary">No tags generated</div>';
|
||||
hiddenContainer.innerHTML = '';
|
||||
accordion.classList.add('hidden');
|
||||
toggleBtn.classList.add('hidden');
|
||||
|
||||
+215
-215
@@ -155,15 +155,15 @@
|
||||
const listBtn = document.getElementById('listViewBtn');
|
||||
|
||||
if (mode === 'grid') {
|
||||
gridBtn.classList.remove('bg-gray-600');
|
||||
gridBtn.classList.add('bg-violet-600');
|
||||
listBtn.classList.remove('bg-violet-600');
|
||||
listBtn.classList.add('bg-gray-600');
|
||||
gridBtn.classList.remove('bg-pm-input');
|
||||
gridBtn.classList.add('bg-pm-accent');
|
||||
listBtn.classList.remove('bg-pm-accent');
|
||||
listBtn.classList.add('bg-pm-input');
|
||||
} else {
|
||||
listBtn.classList.remove('bg-gray-600');
|
||||
listBtn.classList.add('bg-violet-600');
|
||||
gridBtn.classList.remove('bg-violet-600');
|
||||
gridBtn.classList.add('bg-gray-600');
|
||||
listBtn.classList.remove('bg-pm-input');
|
||||
listBtn.classList.add('bg-pm-accent');
|
||||
gridBtn.classList.remove('bg-pm-accent');
|
||||
gridBtn.classList.add('bg-pm-input');
|
||||
}
|
||||
|
||||
this.renderGallery();
|
||||
@@ -206,8 +206,8 @@
|
||||
const mediaType = image.media_type || 'image';
|
||||
|
||||
return `
|
||||
<div class="image-item bg-gray-800 rounded-lg overflow-hidden border border-gray-700 hover:border-gray-600 cursor-pointer group">
|
||||
<div class="aspect-square bg-gray-900 overflow-hidden relative">
|
||||
<div class="image-item bg-pm-surface rounded-pm-md overflow-hidden border border-pm hover:border-pm cursor-pointer group">
|
||||
<div class="aspect-square bg-pm-primary overflow-hidden relative">
|
||||
<img src="${displayUrl}"
|
||||
alt="${this.escapeHtml(image.filename)}"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
@@ -219,31 +219,31 @@
|
||||
data-is-video="${isVideo}"
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex'">
|
||||
<!-- Fallback for failed thumbnails -->
|
||||
<div class="hidden absolute inset-0 bg-gray-700 text-gray-400 flex items-center justify-center">
|
||||
<div class="hidden absolute inset-0 bg-pm-surface text-pm-secondary flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div class="text-2xl mb-1">${isVideo ? '🎬' : '🖼️'}</div>
|
||||
<div class="text-lg mb-1">${isVideo ? '🎬' : '🖼️'}</div>
|
||||
<div class="text-xs">Failed to load</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-20 transition-opacity duration-300 flex items-center justify-center">
|
||||
<div class="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-opacity duration-300 flex items-center justify-center">
|
||||
${isVideo ? `
|
||||
<div class="w-12 h-12 bg-black bg-opacity-60 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<svg class="w-6 h-6 text-white ml-1" fill="currentColor" viewBox="0 0 24 24">
|
||||
<div class="w-12 h-12 bg-black/60 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||
<svg class="w-6 h-6 text-pm ml-1" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
</svg>
|
||||
</div>
|
||||
` : `
|
||||
<svg class="w-8 h-8 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-8 h-8 text-pm opacity-0 group-hover:opacity-100 transition-opacity duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
`}
|
||||
</div>
|
||||
${hasThumb ? '<div class="absolute top-2 right-2 w-3 h-3 bg-green-500 rounded-full" title="Thumbnail available"></div>' : ''}
|
||||
${isVideo ? '<div class="absolute top-2 left-2 px-2 py-1 bg-red-600 text-white text-xs rounded" title="Video file">VIDEO</div>' : ''}
|
||||
${hasThumb ? '<div class="absolute top-2 right-2 w-3 h-3 bg-pm-success rounded-full" title="Thumbnail available"></div>' : ''}
|
||||
${isVideo ? '<div class="absolute top-2 left-2 px-2 py-1 bg-pm-error text-pm text-xs rounded" title="Video file">VIDEO</div>' : ''}
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<div class="text-xs text-gray-400 truncate" title="${this.escapeHtml(image.filename)}">${this.escapeHtml(image.filename)}</div>
|
||||
<div class="text-xs text-gray-500">${this.formatFileSize(image.size)}${hasThumb ? ' • Fast' : ''}${isVideo ? ' • Video' : ''}</div>
|
||||
<div class="text-xs text-pm-secondary truncate" title="${this.escapeHtml(image.filename)}">${this.escapeHtml(image.filename)}</div>
|
||||
<div class="text-xs text-pm-muted">${this.formatFileSize(image.size)}${hasThumb ? ' • Fast' : ''}${isVideo ? ' • Video' : ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -265,8 +265,8 @@
|
||||
const mediaType = image.media_type || 'image';
|
||||
|
||||
return `
|
||||
<div class="image-item bg-gray-800 rounded-lg border border-gray-700 hover:border-gray-600 cursor-pointer group flex items-center p-4">
|
||||
<div class="w-16 h-16 bg-gray-900 rounded-lg overflow-hidden flex-shrink-0 mr-4 relative">
|
||||
<div class="image-item bg-pm-surface rounded-pm-md border border-pm hover:border-pm cursor-pointer group flex items-center p-4">
|
||||
<div class="w-16 h-16 bg-pm-primary rounded-pm-md overflow-hidden flex-shrink-0 mr-4 relative">
|
||||
<img src="${displayUrl}"
|
||||
alt="${this.escapeHtml(image.filename)}"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
@@ -277,21 +277,21 @@
|
||||
data-media-type="${mediaType}"
|
||||
data-is-video="${isVideo}"
|
||||
onerror="this.style.display='none'">
|
||||
${hasThumb ? '<div class="absolute top-1 right-1 w-2 h-2 bg-green-500 rounded-full" title="Thumbnail available"></div>' : ''}
|
||||
${isVideo ? '<div class="absolute top-1 left-1 w-4 h-3 bg-red-600 text-white text-xs flex items-center justify-center rounded" title="Video">▶</div>' : ''}
|
||||
${hasThumb ? '<div class="absolute top-1 right-1 w-2 h-2 bg-pm-success rounded-full" title="Thumbnail available"></div>' : ''}
|
||||
${isVideo ? '<div class="absolute top-1 left-1 w-4 h-3 bg-pm-error text-pm text-xs flex items-center justify-center rounded" title="Video">▶</div>' : ''}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-gray-100 truncate">${this.escapeHtml(image.filename)}${isVideo ? ' 🎬' : ''}</div>
|
||||
<div class="text-xs text-gray-400">${this.formatFileSize(image.size)} • ${new Date(image.modified_time * 1000).toLocaleDateString()}${hasThumb ? ' • Fast' : ''}${isVideo ? ' • Video' : ''}</div>
|
||||
<div class="text-xs text-gray-500 truncate">${image.relative_path}</div>
|
||||
<div class="text-sm font-medium text-pm truncate">${this.escapeHtml(image.filename)}${isVideo ? ' 🎬' : ''}</div>
|
||||
<div class="text-xs text-pm-secondary">${this.formatFileSize(image.size)} • ${new Date(image.modified_time * 1000).toLocaleDateString()}${hasThumb ? ' • Fast' : ''}${isVideo ? ' • Video' : ''}</div>
|
||||
<div class="text-xs text-pm-muted truncate">${image.relative_path}</div>
|
||||
</div>
|
||||
<div class="flex-shrink-0 ml-4">
|
||||
${isVideo ? `
|
||||
<svg class="w-5 h-5 text-red-400 group-hover:text-red-300 transition-colors" fill="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-pm-error group-hover:text-pm-error transition-colors" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
</svg>
|
||||
` : `
|
||||
<svg class="w-5 h-5 text-gray-400 group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-pm-secondary group-hover:text-pm transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
`}
|
||||
@@ -340,7 +340,7 @@
|
||||
flipHorizontal: 1,
|
||||
flipVertical: 1,
|
||||
},
|
||||
className: 'viewer-dark-theme',
|
||||
className: '',
|
||||
title: [1, (image, imageData) => `${imageData.alt} (${this.images.length} images) - Original Image`],
|
||||
viewed: (event) => {
|
||||
console.log('ViewerJS opened original image for metadata');
|
||||
@@ -377,29 +377,29 @@
|
||||
sidebar.innerHTML = `
|
||||
<!-- Collapse Button -->
|
||||
<div class="metadata-collapse-btn" onclick="this.closest('.metadata-sidebar').classList.toggle('collapsed')">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between p-4 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between p-4 border-b border-pm">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<h1 class="text-lg font-medium text-white">Generation data</h1>
|
||||
<h1 class="text-sm font-semibold text-pm">Generation data</h1>
|
||||
</div>
|
||||
<button class="text-xs text-blue-400 hover:text-blue-300 transition-colors metadata-copy-all">
|
||||
<button class="text-xs text-pm-accent hover:text-pm-accent transition-colors metadata-copy-all">
|
||||
📋 COPY ALL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable Content -->
|
||||
<div id="metadata-content" class="flex-1 overflow-y-auto p-4 space-y-6">
|
||||
<div id="metadata-content" class="flex-1 overflow-y-auto p-4 space-y-3">
|
||||
<!-- Loading state -->
|
||||
<div class="text-center text-gray-500 py-8">
|
||||
<div class="w-8 h-8 border-2 border-blue-500/30 border-t-blue-500 rounded-full animate-spin mx-auto mb-2"></div>
|
||||
<div class="text-center text-pm-muted py-8">
|
||||
<div class="w-8 h-8 border-2 border-pm-accent/30 border-t-pm-accent rounded-full animate-spin mx-auto mb-2"></div>
|
||||
<p class="text-sm">Loading metadata...</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -495,15 +495,15 @@
|
||||
<!-- Image area with navigation -->
|
||||
<div class="flex-1 flex items-center justify-center relative">
|
||||
<!-- Navigation arrows -->
|
||||
<button class="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" id="prevImageBtn">
|
||||
<button class="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black/50 hover:bg-black/70 rounded-full flex items-center justify-center text-pm text-xl z-10" id="prevImageBtn">
|
||||
‹
|
||||
</button>
|
||||
<button class="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" id="nextImageBtn">
|
||||
<button class="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black/50 hover:bg-black/70 rounded-full flex items-center justify-center text-pm text-xl z-10" id="nextImageBtn">
|
||||
›
|
||||
</button>
|
||||
|
||||
<!-- Close button -->
|
||||
<button class="absolute top-4 right-4 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" onclick="this.parentElement.parentElement.remove()">
|
||||
<button class="absolute top-4 right-4 w-10 h-10 bg-black/50 hover:bg-black/70 rounded-full flex items-center justify-center text-pm text-xl z-10" onclick="this.parentElement.parentElement.remove()">
|
||||
×
|
||||
</button>
|
||||
|
||||
@@ -515,29 +515,29 @@
|
||||
<div class="metadata-sidebar">
|
||||
<!-- Collapse Button -->
|
||||
<div class="metadata-collapse-btn" onclick="this.closest('.metadata-sidebar').classList.toggle('collapsed')">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between p-4 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between p-4 border-b border-pm">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<h1 class="text-lg font-medium text-white">Generation data</h1>
|
||||
<h1 class="text-sm font-semibold text-pm">Generation data</h1>
|
||||
</div>
|
||||
<button class="text-xs text-blue-400 hover:text-blue-300 transition-colors metadata-copy-all">
|
||||
<button class="text-xs text-pm-accent hover:text-pm-accent transition-colors metadata-copy-all">
|
||||
📋 COPY ALL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Scrollable Content -->
|
||||
<div id="metadata-content" class="flex-1 overflow-y-auto p-4 space-y-6">
|
||||
<div id="metadata-content" class="flex-1 overflow-y-auto p-4 space-y-3">
|
||||
<!-- Loading state -->
|
||||
<div class="text-center text-gray-500 py-8">
|
||||
<div class="w-8 h-8 border-2 border-blue-500/30 border-t-blue-500 rounded-full animate-spin mx-auto mb-2"></div>
|
||||
<div class="text-center text-pm-muted py-8">
|
||||
<div class="w-8 h-8 border-2 border-pm-accent/30 border-t-pm-accent rounded-full animate-spin mx-auto mb-2"></div>
|
||||
<p class="text-sm">Loading metadata...</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -579,24 +579,24 @@
|
||||
<!-- Video area with navigation -->
|
||||
<div class="flex-1 flex items-center justify-center relative">
|
||||
<!-- Navigation arrows -->
|
||||
<button class="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" id="prevVideoBtn">
|
||||
<button class="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black/50 hover:bg-black/70 rounded-full flex items-center justify-center text-pm text-xl z-10" id="prevVideoBtn">
|
||||
‹
|
||||
</button>
|
||||
<button class="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" id="nextVideoBtn">
|
||||
<button class="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black/50 hover:bg-black/70 rounded-full flex items-center justify-center text-pm text-xl z-10" id="nextVideoBtn">
|
||||
›
|
||||
</button>
|
||||
|
||||
<!-- Close button -->
|
||||
<button class="absolute top-4 right-4 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" onclick="this.parentElement.parentElement.remove()">
|
||||
<button class="absolute top-4 right-4 w-10 h-10 bg-black/50 hover:bg-black/70 rounded-full flex items-center justify-center text-pm text-xl z-10" onclick="this.parentElement.parentElement.remove()">
|
||||
×
|
||||
</button>
|
||||
|
||||
<!-- Video controls -->
|
||||
<div class="absolute bottom-4 right-4 flex space-x-2 z-10">
|
||||
<button id="videoMuteBtn" class="px-3 py-2 bg-black bg-opacity-50 hover:bg-opacity-70 rounded text-white text-sm">
|
||||
<button id="videoMuteBtn" class="px-3 py-2 bg-black/50 hover:bg-black/70 rounded text-pm text-sm">
|
||||
🔊
|
||||
</button>
|
||||
<button id="videoLoopBtn" class="px-3 py-2 bg-black bg-opacity-50 hover:bg-opacity-70 rounded text-white text-sm">
|
||||
<button id="videoLoopBtn" class="px-3 py-2 bg-black/50 hover:bg-black/70 rounded text-pm text-sm">
|
||||
🔁
|
||||
</button>
|
||||
</div>
|
||||
@@ -617,26 +617,26 @@
|
||||
<div class="metadata-sidebar">
|
||||
<!-- Collapse Button -->
|
||||
<div class="metadata-collapse-btn" onclick="this.closest('.metadata-sidebar').classList.toggle('collapsed')">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between p-4 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between p-4 border-b border-pm">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-red-400" fill="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-pm-error" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
</svg>
|
||||
<h1 class="text-lg font-medium text-white">Video Info</h1>
|
||||
<h1 class="text-sm font-semibold text-pm">Video Info</h1>
|
||||
</div>
|
||||
<button class="text-xs text-blue-400 hover:text-blue-300 transition-colors" onclick="navigator.clipboard.writeText('${videoUrl}').then(() => this.textContent = '✓ Copied!'); setTimeout(() => this.textContent = '📋 COPY URL', 1000)">
|
||||
<button class="text-xs text-pm-accent hover:text-pm-accent transition-colors" onclick="navigator.clipboard.writeText('${videoUrl}').then(() => this.textContent = '✓ Copied!'); setTimeout(() => this.textContent = '📋 COPY URL', 1000)">
|
||||
📋 COPY URL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Video info content -->
|
||||
<div id="video-info-content" class="flex-1 overflow-y-auto p-4 space-y-6">
|
||||
<div id="video-info-content" class="flex-1 overflow-y-auto p-4 space-y-3">
|
||||
<!-- Video details will be loaded here -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -775,48 +775,48 @@
|
||||
container.innerHTML = `
|
||||
<!-- File Info -->
|
||||
<div class="space-y-2">
|
||||
<h2 class="text-sm font-medium text-gray-300">File Info</h2>
|
||||
<div class="text-xs text-gray-400 font-mono bg-gray-800 p-2 rounded break-all cursor-pointer hover:bg-gray-700"
|
||||
<h2 class="text-sm font-medium text-pm-secondary">File Info</h2>
|
||||
<div class="text-xs text-pm-secondary font-mono bg-pm-surface p-2 rounded break-all cursor-pointer hover:bg-pm-hover"
|
||||
onclick="navigator.clipboard.writeText('${videoUrl}').then(() => this.textContent = 'Copied!'); setTimeout(() => this.textContent = '${fileName}', 1000)"
|
||||
title="Click to copy path">
|
||||
${fileName}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
<div class="text-xs text-pm-muted">
|
||||
Type: ${fileExt} Video
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Video Properties -->
|
||||
<div class="space-y-2" id="video-properties">
|
||||
<h2 class="text-sm font-medium text-gray-300">Video Properties</h2>
|
||||
<div class="text-xs text-gray-400">
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Video Properties</h2>
|
||||
<div class="text-xs text-pm-secondary">
|
||||
Loading video information...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Video Settings -->
|
||||
<div class="space-y-3">
|
||||
<h2 class="text-sm font-medium text-gray-300">Playback Settings</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Playback Settings</h2>
|
||||
<div class="space-y-2 text-xs">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-400">Autoplay:</span>
|
||||
<span class="text-gray-200">${this.getSettings().videoAutoplay ? 'Enabled' : 'Disabled'}</span>
|
||||
<span class="text-pm-secondary">Autoplay:</span>
|
||||
<span class="text-pm">${this.getSettings().videoAutoplay ? 'Enabled' : 'Disabled'}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-400">Muted:</span>
|
||||
<span class="text-gray-200">${this.getSettings().videoMute ? 'Yes' : 'No'}</span>
|
||||
<span class="text-pm-secondary">Muted:</span>
|
||||
<span class="text-pm">${this.getSettings().videoMute ? 'Yes' : 'No'}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-400">Loop:</span>
|
||||
<span class="text-gray-200">${this.getSettings().videoLoop ? 'Enabled' : 'Disabled'}</span>
|
||||
<span class="text-pm-secondary">Loop:</span>
|
||||
<span class="text-pm">${this.getSettings().videoLoop ? 'Enabled' : 'Disabled'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Note about metadata -->
|
||||
<div class="bg-blue-900/20 border border-blue-600 rounded-lg p-3">
|
||||
<h4 class="text-blue-400 font-medium mb-1 text-sm">ℹ️ Video Note</h4>
|
||||
<p class="text-gray-300 text-xs">
|
||||
<div class="bg-pm-accent-tint border border-pm-accent rounded-pm-md p-3">
|
||||
<h4 class="text-pm-accent font-medium mb-1 text-sm">ℹ️ Video Note</h4>
|
||||
<p class="text-pm-secondary text-xs">
|
||||
Videos don't contain the same metadata as PNG images. ComfyUI workflow data is only embedded in PNG outputs.
|
||||
</p>
|
||||
</div>
|
||||
@@ -833,19 +833,19 @@
|
||||
const height = video.videoHeight;
|
||||
|
||||
propertiesDiv.innerHTML = `
|
||||
<h2 class="text-sm font-medium text-gray-300">Video Properties</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Video Properties</h2>
|
||||
<div class="space-y-1 text-xs">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">Duration:</span>
|
||||
<span class="text-gray-200">${this.formatDuration(duration)}</span>
|
||||
<span class="text-pm-secondary">Duration:</span>
|
||||
<span class="text-pm">${this.formatDuration(duration)}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">Resolution:</span>
|
||||
<span class="text-gray-200">${width} × ${height}</span>
|
||||
<span class="text-pm-secondary">Resolution:</span>
|
||||
<span class="text-pm">${width} × ${height}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">Aspect Ratio:</span>
|
||||
<span class="text-gray-200">${(width/height).toFixed(2)}:1</span>
|
||||
<span class="text-pm-secondary">Aspect Ratio:</span>
|
||||
<span class="text-pm">${(width/height).toFixed(2)}:1</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -856,9 +856,9 @@
|
||||
} catch (error) {
|
||||
console.error('Error loading video info:', error);
|
||||
container.innerHTML = `
|
||||
<div class="bg-red-900/20 border border-red-600 rounded-lg p-4">
|
||||
<h4 class="text-red-400 font-medium mb-2">❌ Video Info Error</h4>
|
||||
<p class="text-gray-300 text-sm">${error.message}</p>
|
||||
<div class="bg-pm-error-tint border border-pm-error rounded-pm-md p-4">
|
||||
<h4 class="text-pm-error font-medium mb-2">❌ Video Info Error</h4>
|
||||
<p class="text-pm-secondary text-sm">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -950,9 +950,9 @@
|
||||
} catch (error) {
|
||||
console.error('Error loading metadata:', error);
|
||||
container.innerHTML = `
|
||||
<div class="bg-red-900/20 border border-red-600 rounded-lg p-4">
|
||||
<h4 class="text-red-400 font-medium mb-2">❌ Metadata Error</h4>
|
||||
<p class="text-gray-300 text-sm">${error.message}</p>
|
||||
<div class="bg-pm-error-tint border border-pm-error rounded-pm-md p-4">
|
||||
<h4 class="text-pm-error font-medium mb-2">❌ Metadata Error</h4>
|
||||
<p class="text-pm-secondary text-sm">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -961,7 +961,7 @@
|
||||
displayMetadata(metadata, container) {
|
||||
if (!metadata) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center text-gray-500 py-8">
|
||||
<div class="text-center text-pm-muted py-8">
|
||||
<svg class="w-16 h-16 mx-auto mb-4 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
@@ -974,19 +974,19 @@
|
||||
const html = `
|
||||
<!-- File Path -->
|
||||
<div class="space-y-2">
|
||||
<h2 class="text-sm font-medium text-gray-300">File Path</h2>
|
||||
<div class="text-xs text-gray-400 font-mono bg-gray-800 p-2 rounded break-all cursor-pointer hover:bg-gray-700" data-copy-path title="Click to copy">
|
||||
<h2 class="text-sm font-medium text-pm-secondary">File Path</h2>
|
||||
<div class="text-xs text-pm-secondary font-mono bg-pm-surface p-2 rounded break-all cursor-pointer hover:bg-pm-hover" data-copy-path title="Click to copy">
|
||||
${metadata.imagePath || 'Unknown'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resources used -->
|
||||
<div class="space-y-3">
|
||||
<h2 class="text-sm font-medium text-gray-300">Resources used</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Resources used</h2>
|
||||
<div class="space-y-2 text-xs">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">Model:</span>
|
||||
<span class="text-gray-200">${metadata.checkpoint || 'Unknown'}</span>
|
||||
<span class="text-pm-secondary">Model:</span>
|
||||
<span class="text-pm">${metadata.checkpoint || 'Unknown'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -994,15 +994,15 @@
|
||||
<!-- Prompt Section -->
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-sm font-medium text-gray-300">Prompt</h2>
|
||||
<button class="text-xs px-2 py-1 bg-orange-600 hover:bg-orange-700 text-white rounded transition-colors" data-copy-type="positive">
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Prompt</h2>
|
||||
<button class="text-xs px-2 py-1 bg-pm-warning hover:bg-pm-warning text-pm rounded transition-colors" data-copy-type="positive">
|
||||
COPY PROMPT
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-xs text-gray-200 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto break-words leading-relaxed">
|
||||
<div class="text-xs text-pm bg-pm-surface p-3 rounded max-h-32 overflow-y-auto break-words leading-relaxed">
|
||||
${this.formatPromptText(metadata.positivePrompt)}
|
||||
<div class="mt-2 pt-2 border-t border-gray-700">
|
||||
<button class="text-blue-400 hover:text-blue-300 underline text-xs" data-show-type="positive">
|
||||
<div class="mt-2 pt-2 border-t border-pm">
|
||||
<button class="text-pm-accent hover:text-pm-accent underline text-xs" data-show-type="positive">
|
||||
Show more
|
||||
</button>
|
||||
</div>
|
||||
@@ -1011,11 +1011,11 @@
|
||||
|
||||
<!-- Negative Prompt -->
|
||||
<div class="space-y-3">
|
||||
<h2 class="text-sm font-medium text-gray-300">Negative prompt</h2>
|
||||
<div class="text-xs text-gray-200 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto break-words leading-relaxed">
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Negative prompt</h2>
|
||||
<div class="text-xs text-pm bg-pm-surface p-3 rounded max-h-32 overflow-y-auto break-words leading-relaxed">
|
||||
${this.formatPromptText(metadata.negativePrompt)}
|
||||
<div class="mt-2 pt-2 border-t border-gray-700">
|
||||
<button class="text-blue-400 hover:text-blue-300 underline text-xs" data-show-type="negative">
|
||||
<div class="mt-2 pt-2 border-t border-pm">
|
||||
<button class="text-pm-accent hover:text-pm-accent underline text-xs" data-show-type="negative">
|
||||
Show more
|
||||
</button>
|
||||
</div>
|
||||
@@ -1024,23 +1024,23 @@
|
||||
|
||||
<!-- Other metadata -->
|
||||
<div class="space-y-3">
|
||||
<h2 class="text-sm font-medium text-gray-300">Other metadata</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Other metadata</h2>
|
||||
<div class="space-y-2 text-xs">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">CFG SCALE:</span>
|
||||
<span class="text-gray-200 px-2 py-1 bg-gray-700 rounded text-xs">${metadata.cfgScale || 'Unknown'}</span>
|
||||
<span class="text-pm-secondary">CFG SCALE:</span>
|
||||
<span class="text-pm px-2 py-1 bg-pm-surface rounded text-xs">${metadata.cfgScale || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">STEPS:</span>
|
||||
<span class="text-gray-200 px-2 py-1 bg-gray-700 rounded text-xs">${metadata.steps || 'Unknown'}</span>
|
||||
<span class="text-pm-secondary">STEPS:</span>
|
||||
<span class="text-pm px-2 py-1 bg-pm-surface rounded text-xs">${metadata.steps || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">SAMPLER:</span>
|
||||
<span class="text-gray-200 px-2 py-1 bg-gray-700 rounded text-xs">${metadata.sampler || 'Unknown'}</span>
|
||||
<span class="text-pm-secondary">SAMPLER:</span>
|
||||
<span class="text-pm px-2 py-1 bg-pm-surface rounded text-xs">${metadata.sampler || 'Unknown'}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-400">SEED:</span>
|
||||
<span class="text-gray-200 font-mono px-2 py-1 bg-gray-700 rounded text-xs">${metadata.seed || 'Unknown'}</span>
|
||||
<span class="text-pm-secondary">SEED:</span>
|
||||
<span class="text-pm font-mono px-2 py-1 bg-pm-surface rounded text-xs">${metadata.seed || 'Unknown'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1048,15 +1048,15 @@
|
||||
<!-- ComfyUI Workflow -->
|
||||
${metadata.workflow ? `
|
||||
<div class="space-y-3">
|
||||
<h2 class="text-sm font-medium text-gray-300">ComfyUI Workflow</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary">ComfyUI Workflow</h2>
|
||||
<div class="space-y-2">
|
||||
<button class="text-xs text-blue-400 hover:text-blue-300 underline flex items-center gap-1" data-action="show-workflow">
|
||||
<button class="text-xs text-pm-accent hover:text-pm-accent underline flex items-center gap-1" data-action="show-workflow">
|
||||
<span>View Raw Workflow JSON</span>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-xs text-blue-400 hover:text-blue-300 underline flex items-center gap-1" data-action="download-workflow">
|
||||
<button class="text-xs text-pm-accent hover:text-pm-accent underline flex items-center gap-1" data-action="download-workflow">
|
||||
<span>Download Workflow</span>
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
@@ -1095,8 +1095,8 @@
|
||||
// Update metadata using original image
|
||||
const metadataContainer = modal.querySelector('#metadata-content');
|
||||
metadataContainer.innerHTML = `
|
||||
<div class="text-center text-gray-500 py-8">
|
||||
<div class="w-8 h-8 border-2 border-blue-500/30 border-t-blue-500 rounded-full animate-spin mx-auto mb-2"></div>
|
||||
<div class="text-center text-pm-muted py-8">
|
||||
<div class="w-8 h-8 border-2 border-pm-accent/30 border-t-pm-accent rounded-full animate-spin mx-auto mb-2"></div>
|
||||
<p class="text-sm">Loading metadata from original image...</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -1276,7 +1276,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
|
||||
formatPromptText(text) {
|
||||
if (!text || text === 'No prompt found' || text === 'No negative prompt found') {
|
||||
return `<span class="text-gray-500 italic">${text || 'No prompt found'}</span>`;
|
||||
return `<span class="text-pm-muted italic">${text || 'No prompt found'}</span>`;
|
||||
}
|
||||
return this.escapeHtml(text);
|
||||
}
|
||||
@@ -1295,20 +1295,20 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
|
||||
showNotification(message, type = 'info') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `fixed top-4 right-4 px-6 py-4 rounded-lg shadow-lg z-[20000] transition-all duration-300 transform translate-x-full`;
|
||||
|
||||
notification.className = `fixed top-4 right-4 px-4 py-2 rounded-pm-md shadow-pm text-sm z-[20000] transition-all duration-300 transform translate-x-full`;
|
||||
|
||||
const colors = {
|
||||
success: "bg-green-600 text-white",
|
||||
error: "bg-red-600 text-white",
|
||||
warning: "bg-yellow-600 text-white",
|
||||
info: "bg-blue-600 text-white"
|
||||
success: "bg-pm-success text-pm",
|
||||
error: "bg-pm-error text-pm",
|
||||
warning: "bg-pm-warning text-pm",
|
||||
info: "bg-pm-accent text-pm"
|
||||
};
|
||||
|
||||
|
||||
notification.className += ` ${colors[type] || colors.info}`;
|
||||
notification.textContent = message;
|
||||
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
|
||||
setTimeout(() => notification.classList.remove("translate-x-full"), 100);
|
||||
setTimeout(() => {
|
||||
notification.classList.add("translate-x-full");
|
||||
@@ -1814,15 +1814,15 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
progressText.textContent = 'Failed';
|
||||
progressDetails.textContent = error.message;
|
||||
progressBar.style.width = '0%';
|
||||
progressBar.classList.add('bg-red-600');
|
||||
progressBar.classList.add('bg-pm-error');
|
||||
progressPercent.textContent = 'Error';
|
||||
|
||||
|
||||
this.showNotification('Failed to generate thumbnails: ' + error.message, 'error');
|
||||
|
||||
|
||||
// Hide progress after a delay
|
||||
setTimeout(() => {
|
||||
progressContainer.classList.add('hidden');
|
||||
progressBar.classList.remove('bg-red-600');
|
||||
progressBar.classList.remove('bg-pm-error');
|
||||
}, 5000);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
@@ -2138,7 +2138,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
scanStatus.classList.add('hidden');
|
||||
|
||||
if (duplicates.length === 0) {
|
||||
list.innerHTML = '<div class="text-center py-8 text-gray-400">No duplicate images found! 🎉</div>';
|
||||
list.innerHTML = '<div class="text-center py-4 text-pm-secondary">No duplicate images found! 🎉</div>';
|
||||
content.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
@@ -2148,29 +2148,29 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
|
||||
duplicates.forEach((group, groupIndex) => {
|
||||
const groupEl = document.createElement('div');
|
||||
groupEl.className = 'bg-gray-700 rounded-lg p-4 border border-gray-600';
|
||||
groupEl.className = 'bg-pm-surface rounded-pm-md p-4 border border-pm';
|
||||
|
||||
groupEl.innerHTML = `
|
||||
<div class="mb-3 text-sm font-medium text-gray-300">
|
||||
<div class="mb-3 text-sm font-medium text-pm-secondary">
|
||||
Duplicate Group ${groupIndex + 1} (${group.images.length} identical ${group.images[0].media_type}s)
|
||||
</div>
|
||||
<div class="mb-3 p-3 bg-gray-800 rounded text-sm text-gray-300">
|
||||
<div class="mb-3 p-3 bg-pm-primary rounded text-sm text-pm-secondary">
|
||||
<strong>Content Hash:</strong> ${group.hash.substring(0, 16)}...
|
||||
</div>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
||||
${group.images.map((image, imageIndex) => `
|
||||
<div class="flex items-start p-3 bg-gray-800 rounded border border-gray-600 ${imageIndex === 0 ? 'border-green-500' : ''}">
|
||||
<div class="flex items-start p-3 bg-pm-primary rounded border border-pm ${imageIndex === 0 ? 'border-pm-success' : ''}">
|
||||
<div class="flex-shrink-0 mr-3">
|
||||
${image.media_type === 'video' ? `
|
||||
<div class="w-20 h-20 bg-gray-600 rounded flex items-center justify-center relative">
|
||||
<div class="w-20 h-20 bg-pm-input rounded flex items-center justify-center relative">
|
||||
${image.thumbnail_url ? `
|
||||
<img src="${image.thumbnail_url}" alt="Video thumbnail" class="w-full h-full object-cover rounded">
|
||||
` : `
|
||||
<svg class="w-8 h-8 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg class="w-8 h-8 text-pm-secondary" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2 6a2 2 0 012-2h6l2 2h6a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6zM14.553 7.106A1 1 0 0014 8v4a1 1 0 00.553.894l2 1A1 1 0 0018 13V7a1 1 0 00-1.447-.894l-2 1z"/>
|
||||
</svg>
|
||||
`}
|
||||
<div class="absolute bottom-0 right-0 bg-black bg-opacity-75 text-white text-xs px-1 rounded">VIDEO</div>
|
||||
<div class="absolute bottom-0 right-0 bg-black/75 text-pm text-xs px-1 rounded">VIDEO</div>
|
||||
</div>
|
||||
` : `
|
||||
<img src="${image.thumbnail_url || image.url}" alt="${this.escapeHtml(image.filename)}"
|
||||
@@ -2179,31 +2179,31 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
`}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm text-gray-300 mb-2">
|
||||
<div class="text-sm text-pm-secondary mb-2">
|
||||
<div class="font-medium truncate" title="${this.escapeHtml(image.filename)}">${this.escapeHtml(image.filename)}</div>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
<span class="font-medium">Size:</span> ${this.formatFileSize(image.size)} •
|
||||
<div class="text-xs text-pm-secondary mt-1">
|
||||
<span class="font-medium">Size:</span> ${this.formatFileSize(image.size)} •
|
||||
<span class="font-medium">Modified:</span> ${new Date(image.modified_time * 1000).toLocaleDateString()}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1 truncate" title="${image.relative_path}">
|
||||
<div class="text-xs text-pm-muted mt-1 truncate" title="${image.relative_path}">
|
||||
${image.relative_path}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button class="image-preview text-xs bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded"
|
||||
<button class="image-preview text-xs bg-pm-accent hover:bg-pm-accent-hover text-pm px-2 py-1 rounded"
|
||||
onclick="window.open('${image.url}', '_blank')">
|
||||
View Full
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
${imageIndex === 0 ? `
|
||||
<span class="px-3 py-1 bg-green-600 text-white text-xs rounded">KEEP (Oldest)</span>
|
||||
<span class="px-3 py-1 bg-pm-success text-pm text-xs rounded">KEEP (Oldest)</span>
|
||||
` : `
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" class="duplicate-checkbox" data-group="${groupIndex}" data-image-path="${image.path}"
|
||||
class="w-4 h-4 text-red-600 bg-gray-600 border-gray-500 rounded focus:ring-red-500">
|
||||
<span class="ml-2 text-sm text-red-300">Delete</span>
|
||||
<input type="checkbox" class="duplicate-checkbox" data-group="${groupIndex}" data-image-path="${image.path}"
|
||||
class="w-4 h-4 text-pm-error bg-pm-input border-pm rounded focus:ring-pm-error">
|
||||
<span class="ml-2 text-sm text-pm-error">Delete</span>
|
||||
</label>
|
||||
`}
|
||||
</div>
|
||||
@@ -2393,25 +2393,25 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
|
||||
showThumbnailGenerationPrompt(imageCount, missingCount = imageCount) {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
|
||||
modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
|
||||
modal.innerHTML = `
|
||||
<div class="bg-gray-800 rounded-xl max-w-2xl w-full mx-4 border border-gray-700">
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold text-gray-100 mb-4 flex items-center">
|
||||
<div class="bg-pm-surface rounded-pm-md max-w-2xl w-full mx-4 border border-pm">
|
||||
<div class="p-4">
|
||||
<h3 class="text-sm font-semibold text-pm mb-3 flex items-center">
|
||||
<span class="mr-2">🚀</span>
|
||||
Generate Thumbnails?
|
||||
</h3>
|
||||
<p class="text-gray-300 mb-4">
|
||||
<p class="text-pm-secondary mb-4">
|
||||
Found ${missingCount} images without thumbnails (${imageCount} total images). Would you like to generate thumbnails for faster loading?
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 mb-6">
|
||||
<p class="text-sm text-pm-muted mb-3">
|
||||
💡 Thumbnails speed up gallery loading significantly. You can change this setting in Gallery Settings.
|
||||
</p>
|
||||
<div class="flex space-x-3">
|
||||
<button id="thumbnailPromptNo" class="flex-1 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors">
|
||||
<button id="thumbnailPromptNo" class="flex-1 px-4 py-2 bg-pm-input hover:bg-pm-hover text-pm rounded-pm-md transition-colors">
|
||||
Not Now
|
||||
</button>
|
||||
<button id="thumbnailPromptYes" class="flex-1 px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white rounded-lg transition-colors">
|
||||
<button id="thumbnailPromptYes" class="flex-1 px-4 py-2 bg-pm-accent hover:bg-pm-accent-hover text-pm rounded-pm-md transition-colors">
|
||||
Generate
|
||||
</button>
|
||||
</div>
|
||||
@@ -2441,43 +2441,43 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
|
||||
async startThumbnailGenerationInModal(modal, imageCount, missingCount) {
|
||||
// Transform the modal to show progress
|
||||
const modalContent = modal.querySelector('.bg-gray-800');
|
||||
const modalContent = modal.querySelector('.bg-pm-surface');
|
||||
modalContent.innerHTML = `
|
||||
<div class="p-6">
|
||||
<h3 class="text-xl font-semibold text-gray-100 mb-4 flex items-center">
|
||||
<div class="p-4">
|
||||
<h3 class="text-sm font-semibold text-pm mb-3 flex items-center">
|
||||
<span class="mr-2">🚀</span>
|
||||
Generating Thumbnails
|
||||
</h3>
|
||||
<p class="text-gray-300 mb-4">
|
||||
<p class="text-pm-secondary mb-4">
|
||||
Processing ${missingCount} images out of ${imageCount} total...
|
||||
</p>
|
||||
|
||||
|
||||
<!-- Progress Section -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between text-sm text-gray-400 mb-2">
|
||||
<div class="mb-3">
|
||||
<div class="flex items-center justify-between text-sm text-pm-secondary mb-2">
|
||||
<span id="modalProgressText">Initializing...</span>
|
||||
<span id="modalProgressPercent">0%</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-600 rounded-full h-3">
|
||||
<div id="modalProgressBar" class="bg-violet-600 h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||||
<div class="w-full bg-pm-input rounded-full h-3">
|
||||
<div id="modalProgressBar" class="bg-pm-accent h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-gray-500 mt-2">
|
||||
<div class="flex items-center justify-between text-xs text-pm-muted mt-2">
|
||||
<span id="modalProgressDetails">Starting thumbnail generation...</span>
|
||||
<span id="modalProgressETA" class="text-gray-600"></span>
|
||||
<span id="modalProgressETA" class="text-pm-muted"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Status Messages -->
|
||||
<div id="modalStatusMessages" class="text-sm text-gray-400 mb-4 max-h-40 overflow-y-auto custom-scrollbar">
|
||||
<div class="text-violet-400">• Starting thumbnail generation...</div>
|
||||
<div id="modalStatusMessages" class="text-sm text-pm-secondary mb-4 max-h-40 overflow-y-auto">
|
||||
<div class="text-pm-accent">• Starting thumbnail generation...</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex space-x-3">
|
||||
<button id="modalCancelBtn" class="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors">
|
||||
<button id="modalCancelBtn" class="flex-1 px-4 py-2 bg-pm-error hover:bg-pm-error text-pm rounded-pm-md transition-colors">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="modalCloseBtn" class="flex-1 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors hidden">
|
||||
<button id="modalCloseBtn" class="flex-1 px-4 py-2 bg-pm-input hover:bg-pm-hover text-pm rounded-pm-md transition-colors hidden">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
@@ -2498,10 +2498,10 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
// Add status message helper
|
||||
const addStatusMessage = (message, type = 'info') => {
|
||||
const colors = {
|
||||
'info': 'text-gray-400',
|
||||
'success': 'text-green-400',
|
||||
'error': 'text-red-400',
|
||||
'warning': 'text-yellow-400'
|
||||
'info': 'text-pm-secondary',
|
||||
'success': 'text-pm-success',
|
||||
'error': 'text-pm-error',
|
||||
'warning': 'text-pm-warning'
|
||||
};
|
||||
const msgEl = document.createElement('div');
|
||||
msgEl.className = colors[type] || colors.info;
|
||||
@@ -2561,8 +2561,8 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
} catch (error) {
|
||||
console.error('Thumbnail generation failed:', error);
|
||||
addStatusMessage(`Generation failed: ${error.message}`, 'error');
|
||||
progressBar.classList.add('bg-red-600');
|
||||
progressBar.classList.remove('bg-violet-600');
|
||||
progressBar.classList.add('bg-pm-error');
|
||||
progressBar.classList.remove('bg-pm-accent');
|
||||
} finally {
|
||||
// Show close button and hide cancel
|
||||
cancelBtn.classList.add('hidden');
|
||||
@@ -2730,9 +2730,9 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
}
|
||||
|
||||
addStatusMessage(errorMsg, 'error');
|
||||
progressBar.classList.add('bg-red-600');
|
||||
progressBar.classList.remove('bg-violet-600');
|
||||
|
||||
progressBar.classList.add('bg-pm-error');
|
||||
progressBar.classList.remove('bg-pm-accent');
|
||||
|
||||
// Clean up
|
||||
eventSource.close();
|
||||
reject(new Error(errorMsg));
|
||||
@@ -2754,8 +2754,8 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
}
|
||||
|
||||
addStatusMessage('Connection to server lost', 'error');
|
||||
progressBar.classList.add('bg-red-600');
|
||||
progressBar.classList.remove('bg-violet-600');
|
||||
progressBar.classList.add('bg-pm-error');
|
||||
progressBar.classList.remove('bg-pm-accent');
|
||||
|
||||
// Clean up
|
||||
eventSource.close();
|
||||
@@ -2888,7 +2888,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
const container = document.getElementById('tagSuggestions');
|
||||
container.innerHTML = suggestions
|
||||
.map((tag, index) => `
|
||||
<div class="px-3 py-2 hover:bg-gray-600 cursor-pointer text-gray-200 text-sm suggestion-item"
|
||||
<div class="px-3 py-2 hover:bg-pm-hover cursor-pointer text-pm text-sm suggestion-item"
|
||||
data-index="${index}"
|
||||
onclick="window.gallery.selectSuggestion('${this.escapeHtml(tag)}')">
|
||||
${this.escapeHtml(tag)}
|
||||
@@ -2907,11 +2907,11 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
const items = container.querySelectorAll('.suggestion-item');
|
||||
if (items.length === 0) return;
|
||||
|
||||
const current = container.querySelector('.bg-gray-600');
|
||||
const current = container.querySelector('.bg-pm-hover');
|
||||
let nextIndex = 0;
|
||||
|
||||
if (current) {
|
||||
current.classList.remove('bg-gray-600');
|
||||
current.classList.remove('bg-pm-hover');
|
||||
nextIndex = parseInt(current.dataset.index) + direction;
|
||||
if (nextIndex < 0) nextIndex = items.length - 1;
|
||||
if (nextIndex >= items.length) nextIndex = 0;
|
||||
@@ -2919,7 +2919,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
nextIndex = items.length - 1;
|
||||
}
|
||||
|
||||
items[nextIndex].classList.add('bg-gray-600');
|
||||
items[nextIndex].classList.add('bg-pm-hover');
|
||||
}
|
||||
|
||||
selectSuggestion(tag) {
|
||||
@@ -2977,12 +2977,12 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
stars.forEach((star, index) => {
|
||||
if (index < rating) {
|
||||
star.textContent = '★';
|
||||
star.classList.remove('text-gray-500');
|
||||
star.classList.add('text-yellow-400');
|
||||
star.classList.remove('text-pm-muted');
|
||||
star.classList.add('text-pm-warning');
|
||||
} else {
|
||||
star.textContent = '☆';
|
||||
star.classList.remove('text-yellow-400');
|
||||
star.classList.add('text-gray-500');
|
||||
star.classList.remove('text-pm-warning');
|
||||
star.classList.add('text-pm-muted');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -3085,8 +3085,8 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
const loadedModelType = document.getElementById("loadedModelType");
|
||||
const unloadModelBtn = document.getElementById("unloadModelBtn");
|
||||
|
||||
ggufStatus.innerHTML = '<span class="text-gray-400 text-sm">Checking...</span>';
|
||||
hfStatus.innerHTML = '<span class="text-gray-400 text-sm">Checking...</span>';
|
||||
ggufStatus.innerHTML = '<span class="text-pm-secondary text-sm">Checking...</span>';
|
||||
hfStatus.innerHTML = '<span class="text-pm-secondary text-sm">Checking...</span>';
|
||||
|
||||
try {
|
||||
const response = await fetch('/prompt_manager/autotag/models');
|
||||
@@ -3095,16 +3095,16 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
if (data.success) {
|
||||
// Update GGUF status
|
||||
if (data.models.gguf.downloaded) {
|
||||
ggufStatus.innerHTML = '<span class="text-green-400 text-sm">✓ Downloaded</span>';
|
||||
ggufStatus.innerHTML = '<span class="text-pm-success text-sm">✓ Downloaded</span>';
|
||||
} else {
|
||||
ggufStatus.innerHTML = `<button onclick="window.gallery.downloadModel('gguf')" class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-xs rounded transition-colors">Download</button>`;
|
||||
ggufStatus.innerHTML = `<button onclick="window.gallery.downloadModel('gguf')" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm text-xs rounded transition-colors">Download</button>`;
|
||||
}
|
||||
|
||||
// Update HF status
|
||||
if (data.models.hf.downloaded) {
|
||||
hfStatus.innerHTML = '<span class="text-green-400 text-sm">✓ Downloaded</span>';
|
||||
hfStatus.innerHTML = '<span class="text-pm-success text-sm">✓ Downloaded</span>';
|
||||
} else {
|
||||
hfStatus.innerHTML = `<button onclick="window.gallery.downloadModel('hf')" class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-xs rounded transition-colors">Download</button>`;
|
||||
hfStatus.innerHTML = `<button onclick="window.gallery.downloadModel('hf')" class="px-3 py-1 bg-pm-accent hover:bg-pm-accent-hover text-pm text-xs rounded transition-colors">Download</button>`;
|
||||
}
|
||||
|
||||
// Update model loaded status
|
||||
@@ -3117,13 +3117,13 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
unloadModelBtn.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
ggufStatus.innerHTML = '<span class="text-red-400 text-sm">Error</span>';
|
||||
hfStatus.innerHTML = '<span class="text-red-400 text-sm">Error</span>';
|
||||
ggufStatus.innerHTML = '<span class="text-pm-error text-sm">Error</span>';
|
||||
hfStatus.innerHTML = '<span class="text-pm-error text-sm">Error</span>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking models:', error);
|
||||
ggufStatus.innerHTML = '<span class="text-red-400 text-sm">Error</span>';
|
||||
hfStatus.innerHTML = '<span class="text-red-400 text-sm">Error</span>';
|
||||
ggufStatus.innerHTML = '<span class="text-pm-error text-sm">Error</span>';
|
||||
hfStatus.innerHTML = '<span class="text-pm-error text-sm">Error</span>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3340,7 +3340,7 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
document.getElementById('reviewCurrentIndex').textContent = this.autoTagState.reviewIndex + 1;
|
||||
document.getElementById('reviewImage').src = image.thumbnail_url || image.url;
|
||||
document.getElementById('reviewImageName').textContent = image.filename || 'Unknown';
|
||||
document.getElementById('reviewTagsContainer').innerHTML = '<div class="text-gray-400">Generating tags...</div>';
|
||||
document.getElementById('reviewTagsContainer').innerHTML = '<div class="text-pm-secondary">Generating tags...</div>';
|
||||
|
||||
try {
|
||||
const response = await fetch('/prompt_manager/autotag/single', {
|
||||
@@ -3360,19 +3360,19 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
this.renderReviewTags();
|
||||
} else {
|
||||
document.getElementById('reviewTagsContainer').innerHTML =
|
||||
`<div class="text-red-400">Error: ${data.error}</div>`;
|
||||
`<div class="text-pm-error">Error: ${data.error}</div>`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating tags:', error);
|
||||
document.getElementById('reviewTagsContainer').innerHTML =
|
||||
'<div class="text-red-400">Failed to generate tags</div>';
|
||||
'<div class="text-pm-error">Failed to generate tags</div>';
|
||||
}
|
||||
}
|
||||
|
||||
renderReviewTags() {
|
||||
const container = document.getElementById('reviewTagsContainer');
|
||||
if (this.autoTagState.currentTags.length === 0) {
|
||||
container.innerHTML = '<div class="text-gray-400">No tags generated</div>';
|
||||
container.innerHTML = '<div class="text-pm-secondary">No tags generated</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3444,14 +3444,14 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
|
||||
showNotification(message, type = 'info') {
|
||||
// Simple notification - could be enhanced with a toast library
|
||||
const colors = {
|
||||
success: 'bg-green-600',
|
||||
error: 'bg-red-600',
|
||||
warning: 'bg-yellow-600',
|
||||
info: 'bg-blue-600'
|
||||
success: 'bg-pm-success',
|
||||
error: 'bg-pm-error',
|
||||
warning: 'bg-pm-warning',
|
||||
info: 'bg-pm-accent'
|
||||
};
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `fixed bottom-4 right-4 ${colors[type]} text-white px-6 py-3 rounded-lg shadow-lg z-50 animate-fade-in`;
|
||||
notification.className = `fixed bottom-4 right-4 ${colors[type]} text-pm px-4 py-2 rounded-pm-md shadow-pm text-sm z-50 animate-fade-in`;
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
|
||||
+34
-34
@@ -134,7 +134,7 @@ class TagsPageManager {
|
||||
if (container) {
|
||||
container.replaceChildren();
|
||||
const errDiv = document.createElement('div');
|
||||
errDiv.className = 'text-center py-8 text-red-400 text-sm';
|
||||
errDiv.className = 'text-center py-4 text-pm-error text-sm';
|
||||
errDiv.textContent = 'Failed to load tags. Try refreshing.';
|
||||
container.appendChild(errDiv);
|
||||
}
|
||||
@@ -210,8 +210,8 @@ class TagsPageManager {
|
||||
createUntaggedElement() {
|
||||
const isSelected = this.selectedTags.includes('__untagged__');
|
||||
const div = document.createElement('div');
|
||||
div.className = `flex items-center px-3 py-2 rounded-lg cursor-pointer border transition-all duration-200 relative overflow-hidden ${
|
||||
isSelected ? 'bg-yellow-600/20 border-yellow-500/50' : 'border-transparent hover:bg-gray-700/50'
|
||||
div.className = `flex items-center px-3 py-2 rounded-pm-md cursor-pointer border transition-all duration-200 relative overflow-hidden ${
|
||||
isSelected ? 'bg-pm-warning/20 border-pm-warning/50' : 'border-transparent hover:bg-pm-hover/50'
|
||||
}`;
|
||||
|
||||
div.addEventListener('click', () => this.selectTag('__untagged__'));
|
||||
@@ -221,11 +221,11 @@ class TagsPageManager {
|
||||
icon.textContent = '\u2205';
|
||||
|
||||
const nameSpan = document.createElement('span');
|
||||
nameSpan.className = 'flex-1 text-sm text-yellow-300 italic truncate';
|
||||
nameSpan.className = 'flex-1 text-sm text-pm-warning italic truncate';
|
||||
nameSpan.textContent = '[untagged]';
|
||||
|
||||
const countSpan = document.createElement('span');
|
||||
countSpan.className = 'text-xs text-gray-500 ml-2 font-mono';
|
||||
countSpan.className = 'text-xs text-pm-muted ml-2 font-mono';
|
||||
countSpan.textContent = this.untaggedCount;
|
||||
|
||||
div.appendChild(icon);
|
||||
@@ -238,15 +238,15 @@ class TagsPageManager {
|
||||
createTagElement(tag) {
|
||||
const isSelected = this.selectedTags.includes(tag.name);
|
||||
const div = document.createElement('div');
|
||||
div.className = `flex items-center px-3 py-2 rounded-lg cursor-pointer border transition-all duration-200 relative overflow-hidden group ${
|
||||
isSelected ? 'bg-green-600/20 border-green-500/50' : 'border-transparent hover:bg-gray-700/50'
|
||||
div.className = `flex items-center px-3 py-2 rounded-pm-md cursor-pointer border transition-all duration-200 relative overflow-hidden group ${
|
||||
isSelected ? 'bg-pm-success/20 border-pm-success/50' : 'border-transparent hover:bg-pm-hover/50'
|
||||
}`;
|
||||
div.dataset.tagName = tag.name;
|
||||
|
||||
// Usage bar (background)
|
||||
const barWidth = (tag.count / this.maxTagCount) * 100;
|
||||
const bar = document.createElement('div');
|
||||
bar.className = 'absolute inset-y-0 left-0 bg-green-500/8 pointer-events-none transition-all duration-300';
|
||||
bar.className = 'absolute inset-y-0 left-0 bg-pm-success/8 pointer-events-none transition-all duration-300';
|
||||
bar.style.width = barWidth + '%';
|
||||
div.appendChild(bar);
|
||||
|
||||
@@ -259,7 +259,7 @@ class TagsPageManager {
|
||||
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.className = 'tag-filter-checkbox mr-3 rounded border-gray-500 bg-gray-700 text-green-500 focus:ring-green-500 cursor-pointer relative z-10';
|
||||
checkbox.className = 'tag-filter-checkbox mr-3 rounded border-pm bg-pm-surface text-pm-success focus:ring-pm-accent cursor-pointer relative z-10';
|
||||
checkbox.checked = isSelected;
|
||||
checkbox.dataset.tag = tag.name;
|
||||
checkbox.addEventListener('click', (e) => {
|
||||
@@ -268,11 +268,11 @@ class TagsPageManager {
|
||||
});
|
||||
|
||||
const nameSpan = document.createElement('span');
|
||||
nameSpan.className = 'flex-1 text-sm text-gray-200 truncate relative z-10';
|
||||
nameSpan.className = 'flex-1 text-sm text-pm truncate relative z-10';
|
||||
nameSpan.textContent = tag.name;
|
||||
|
||||
const countSpan = document.createElement('span');
|
||||
countSpan.className = 'text-xs text-gray-500 ml-2 font-mono relative z-10';
|
||||
countSpan.className = 'text-xs text-pm-muted ml-2 font-mono relative z-10';
|
||||
countSpan.textContent = tag.count;
|
||||
|
||||
div.appendChild(checkbox);
|
||||
@@ -297,7 +297,7 @@ class TagsPageManager {
|
||||
const fragment = document.createDocumentFragment();
|
||||
items.forEach(item => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'w-full text-left px-4 py-2 text-sm text-gray-200 hover:bg-gray-700 flex items-center space-x-2';
|
||||
btn.className = 'w-full text-left px-4 py-2 text-sm text-pm hover:bg-pm-hover flex items-center space-x-2';
|
||||
const iconSpan = document.createElement('span');
|
||||
iconSpan.textContent = item.icon;
|
||||
const labelSpan = document.createElement('span');
|
||||
@@ -493,11 +493,11 @@ class TagsPageManager {
|
||||
const orBtn = document.getElementById('filterModeOr');
|
||||
|
||||
if (mode === 'and') {
|
||||
andBtn.className = 'px-3 py-1 text-xs font-medium rounded bg-green-600 text-white';
|
||||
orBtn.className = 'px-3 py-1 text-xs font-medium rounded bg-gray-700 text-gray-300 hover:bg-gray-600';
|
||||
andBtn.className = 'px-3 py-1 text-xs font-medium rounded bg-pm-success text-pm';
|
||||
orBtn.className = 'px-3 py-1 text-xs font-medium rounded bg-pm-surface text-pm-secondary hover:bg-pm-hover';
|
||||
} else {
|
||||
orBtn.className = 'px-3 py-1 text-xs font-medium rounded bg-green-600 text-white';
|
||||
andBtn.className = 'px-3 py-1 text-xs font-medium rounded bg-gray-700 text-gray-300 hover:bg-gray-600';
|
||||
orBtn.className = 'px-3 py-1 text-xs font-medium rounded bg-pm-success text-pm';
|
||||
andBtn.className = 'px-3 py-1 text-xs font-medium rounded bg-pm-surface text-pm-secondary hover:bg-pm-hover';
|
||||
}
|
||||
|
||||
this.updateUrlHash();
|
||||
@@ -548,15 +548,15 @@ class TagsPageManager {
|
||||
const isUntaggedPill = tag === '__untagged__';
|
||||
pill.className = `inline-flex items-center px-3 py-1 rounded-full text-sm ${
|
||||
isUntaggedPill
|
||||
? 'bg-yellow-600/20 text-yellow-300 border border-yellow-500/30'
|
||||
: 'bg-green-600/20 text-green-300 border border-green-500/30'
|
||||
? 'bg-pm-warning/20 text-pm-warning border border-pm-warning/30'
|
||||
: 'bg-pm-success/20 text-pm-success border border-pm-success/30'
|
||||
}`;
|
||||
|
||||
const text = document.createTextNode(isUntaggedPill ? '[untagged]' : tag);
|
||||
pill.appendChild(text);
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.className = isUntaggedPill ? 'ml-2 text-yellow-400 hover:text-white' : 'ml-2 text-green-400 hover:text-white';
|
||||
btn.className = isUntaggedPill ? 'ml-2 text-pm-warning hover:text-pm' : 'ml-2 text-pm-success hover:text-pm';
|
||||
btn.textContent = '\u00d7';
|
||||
btn.addEventListener('click', () => this.removeTagFromFilter(tag));
|
||||
pill.appendChild(btn);
|
||||
@@ -594,9 +594,9 @@ class TagsPageManager {
|
||||
if (grid) {
|
||||
grid.replaceChildren();
|
||||
const spinner = document.createElement('div');
|
||||
spinner.className = 'col-span-full text-center py-8';
|
||||
spinner.className = 'col-span-full text-center py-4';
|
||||
const spinEl = document.createElement('div');
|
||||
spinEl.className = 'w-8 h-8 border-2 border-green-500/30 border-t-green-500 rounded-full animate-spin mx-auto';
|
||||
spinEl.className = 'w-8 h-8 border-2 border-pm-success/30 border-t-pm-success rounded-full animate-spin mx-auto';
|
||||
spinner.appendChild(spinEl);
|
||||
grid.appendChild(spinner);
|
||||
}
|
||||
@@ -638,7 +638,7 @@ class TagsPageManager {
|
||||
if (grid) {
|
||||
grid.replaceChildren();
|
||||
const errDiv = document.createElement('div');
|
||||
errDiv.className = 'col-span-full text-center py-8 text-red-400';
|
||||
errDiv.className = 'col-span-full text-center py-4 text-pm-error';
|
||||
errDiv.textContent = 'Failed to load prompts';
|
||||
grid.appendChild(errDiv);
|
||||
}
|
||||
@@ -752,9 +752,9 @@ class TagsPageManager {
|
||||
if (this.prompts.length === 0) {
|
||||
grid.replaceChildren();
|
||||
const emptyDiv = document.createElement('div');
|
||||
emptyDiv.className = 'col-span-full text-center py-12 text-gray-500';
|
||||
emptyDiv.className = 'col-span-full text-center py-6 text-pm-muted';
|
||||
const icon = document.createElement('span');
|
||||
icon.className = 'text-3xl block mb-2';
|
||||
icon.className = 'text-lg block mb-2';
|
||||
icon.textContent = '\uD83D\uDCED';
|
||||
emptyDiv.appendChild(icon);
|
||||
const msg = document.createElement('span');
|
||||
@@ -787,7 +787,7 @@ class TagsPageManager {
|
||||
|
||||
createPromptCard(prompt) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'tag-prompt-card bg-gray-800/50 rounded-xl p-4 border border-gray-700/50 hover:border-gray-600 transition-colors cursor-pointer';
|
||||
card.className = 'tag-prompt-card bg-pm-surface/50 rounded-pm-md p-4 border border-pm/50 hover:border-pm-hover transition-colors cursor-pointer';
|
||||
|
||||
// Click card → navigate to dashboard with search
|
||||
card.addEventListener('click', () => {
|
||||
@@ -814,7 +814,7 @@ class TagsPageManager {
|
||||
imgEl.src = this.getThumbnailUrl(img);
|
||||
imgEl.alt = '';
|
||||
imgEl.loading = 'lazy';
|
||||
imgEl.className = 'w-20 h-20 object-cover rounded-lg bg-gray-700 cursor-zoom-in hover:ring-2 hover:ring-green-500/50 transition-all';
|
||||
imgEl.className = 'w-20 h-20 object-cover rounded-pm-md bg-pm-surface cursor-zoom-in hover:ring-2 hover:ring-pm-success/50 transition-all';
|
||||
imgEl.onerror = function() {
|
||||
this.onerror = null;
|
||||
this.src = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80"><rect fill="#374151" width="80" height="80"/><text x="40" y="44" text-anchor="middle" fill="#6B7280" font-size="20">?</text></svg>');
|
||||
@@ -830,7 +830,7 @@ class TagsPageManager {
|
||||
const moreCount = (prompt.image_count || 0) - prompt.images.length;
|
||||
if (moreCount > 0) {
|
||||
const moreSpan = document.createElement('span');
|
||||
moreSpan.className = 'w-20 h-20 rounded-lg bg-gray-700 flex items-center justify-center text-gray-400 text-sm font-medium cursor-zoom-in hover:bg-gray-600 transition-colors';
|
||||
moreSpan.className = 'w-20 h-20 rounded-pm-md bg-pm-surface flex items-center justify-center text-pm-secondary text-sm font-medium cursor-zoom-in hover:bg-pm-hover transition-colors';
|
||||
moreSpan.textContent = `+${moreCount}`;
|
||||
moreSpan.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -844,20 +844,20 @@ class TagsPageManager {
|
||||
|
||||
// Prompt text (truncated)
|
||||
const textP = document.createElement('p');
|
||||
textP.className = 'text-sm text-gray-200 leading-relaxed line-clamp-3 mb-3';
|
||||
textP.className = 'text-sm text-pm leading-relaxed line-clamp-3 mb-3';
|
||||
textP.textContent = prompt.text || '';
|
||||
card.appendChild(textP);
|
||||
|
||||
// Meta row
|
||||
const metaRow = document.createElement('div');
|
||||
metaRow.className = 'flex items-center justify-between text-xs text-gray-500 mb-2';
|
||||
metaRow.className = 'flex items-center justify-between text-xs text-pm-muted mb-2';
|
||||
|
||||
const metaLeft = document.createElement('div');
|
||||
metaLeft.className = 'flex items-center space-x-3';
|
||||
|
||||
if (prompt.category) {
|
||||
const catSpan = document.createElement('span');
|
||||
catSpan.className = 'px-2 py-0.5 bg-purple-600/20 text-purple-300 rounded';
|
||||
catSpan.className = 'px-2 py-0.5 bg-pm-accent/20 text-pm-accent rounded';
|
||||
catSpan.textContent = prompt.category;
|
||||
metaLeft.appendChild(catSpan);
|
||||
}
|
||||
@@ -874,7 +874,7 @@ class TagsPageManager {
|
||||
starsSpan.className = 'flex';
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const star = document.createElement('span');
|
||||
star.className = i <= rating ? 'text-yellow-400' : 'text-gray-600';
|
||||
star.className = i <= rating ? 'text-pm-warning' : 'text-pm-muted';
|
||||
star.textContent = '\u2605';
|
||||
starsSpan.appendChild(star);
|
||||
}
|
||||
@@ -884,7 +884,7 @@ class TagsPageManager {
|
||||
|
||||
if (prompt.image_count) {
|
||||
const imgCount = document.createElement('span');
|
||||
imgCount.className = 'text-gray-500';
|
||||
imgCount.className = 'text-pm-muted';
|
||||
imgCount.textContent = `${prompt.image_count} img${prompt.image_count !== 1 ? 's' : ''}`;
|
||||
metaRow.appendChild(imgCount);
|
||||
}
|
||||
@@ -900,8 +900,8 @@ class TagsPageManager {
|
||||
const isActive = this.selectedTags.includes(tagName);
|
||||
tagSpan.className = `px-2 py-0.5 rounded text-xs cursor-pointer transition-colors ${
|
||||
isActive
|
||||
? 'bg-green-600 text-white hover:bg-green-500'
|
||||
: 'bg-gray-700 text-gray-300 hover:bg-green-600/40 hover:text-green-200'
|
||||
? 'bg-pm-success text-pm hover:bg-pm-success/80'
|
||||
: 'bg-pm-surface text-pm-secondary hover:bg-pm-success/40 hover:text-pm-success'
|
||||
}`;
|
||||
tagSpan.textContent = tagName;
|
||||
tagSpan.addEventListener('click', (e) => {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Theme Bridge — Detects ComfyUI palette and applies body-level styling.
|
||||
*
|
||||
* When running inside ComfyUI, the parent page already injects palette CSS
|
||||
* variables so our --pm-* tokens auto-resolve. This script adds a data
|
||||
* attribute for CSS selectors that need to distinguish light vs dark.
|
||||
*
|
||||
* When running standalone (direct URL), we fall back to dark defaults.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const LIGHT_PALETTES = ['light', 'solarized', 'github', 'arc'];
|
||||
|
||||
async function detectPalette() {
|
||||
try {
|
||||
const res = await fetch('/api/settings/Comfy.ColorPalette');
|
||||
if (!res.ok) return 'dark';
|
||||
const data = await res.json();
|
||||
// data is the palette name string, or an object with value
|
||||
const name = (typeof data === 'string' ? data : data?.value || 'dark').toLowerCase();
|
||||
return name;
|
||||
} catch {
|
||||
// Standalone mode — no ComfyUI API available
|
||||
return 'dark';
|
||||
}
|
||||
}
|
||||
|
||||
function applyPalette(name) {
|
||||
const isLight = LIGHT_PALETTES.some(p => name.includes(p));
|
||||
document.documentElement.setAttribute('data-pm-theme', isLight ? 'light' : 'dark');
|
||||
}
|
||||
|
||||
// Run immediately — script is loaded in <head> so body isn't parsed yet.
|
||||
// We set the attribute as soon as possible to prevent flash.
|
||||
document.documentElement.setAttribute('data-pm-theme', 'dark');
|
||||
detectPalette().then(applyPalette);
|
||||
})();
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* ComfyUI Theme Bridge — PromptManager Design Tokens
|
||||
*
|
||||
* Maps --pm-* tokens to ComfyUI CSS variables with standalone dark fallbacks.
|
||||
* ComfyUI exposes palette via --bg-color, --fg-color, --comfy-menu-bg, etc.
|
||||
* When running outside ComfyUI these fall back to a neutral dark theme.
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ── Surfaces ─────────────────────────────────── */
|
||||
--pm-bg-primary: var(--bg-color, #202020);
|
||||
--pm-bg-surface: var(--comfy-menu-bg, #353535);
|
||||
--pm-bg-input: var(--comfy-input-bg, #222);
|
||||
--pm-bg-hover: color-mix(in srgb, var(--fg-color, #fff) 10%, var(--comfy-menu-bg, #353535));
|
||||
--pm-bg-elevated: color-mix(in srgb, var(--comfy-menu-bg, #353535) 90%, var(--fg-color, #fff) 10%);
|
||||
|
||||
/* ── Text ──────────────────────────────────────── */
|
||||
--pm-text: var(--fg-color, #ddd);
|
||||
--pm-text-secondary: var(--descrip-text, #999);
|
||||
--pm-text-input: var(--input-text, #ddd);
|
||||
--pm-text-muted: color-mix(in srgb, var(--descrip-text, #999) 70%, transparent);
|
||||
|
||||
/* ── Borders ───────────────────────────────────── */
|
||||
--pm-border: var(--border-color, #4e4e4e);
|
||||
--pm-border-subtle: color-mix(in srgb, var(--border-color, #4e4e4e) 50%, transparent);
|
||||
|
||||
/* ── Accent (ComfyUI blue) ─────────────────────── */
|
||||
--pm-accent: var(--primary-bg, #007aff);
|
||||
--pm-accent-hover: var(--primary-hover-bg, #3485bb);
|
||||
--pm-accent-fg: var(--primary-fg, #fff);
|
||||
--pm-accent-tint: color-mix(in srgb, var(--primary-bg, #007aff) 15%, transparent);
|
||||
|
||||
/* ── Semantic Colors ───────────────────────────── */
|
||||
--pm-error: var(--error-text, #f44);
|
||||
--pm-error-tint: color-mix(in srgb, var(--error-text, #f44) 15%, transparent);
|
||||
--pm-success: #22c55e;
|
||||
--pm-success-tint: color-mix(in srgb, #22c55e 15%, transparent);
|
||||
--pm-warning: #eab308;
|
||||
--pm-warning-tint: color-mix(in srgb, #eab308 15%, transparent);
|
||||
--pm-info: #3b82f6;
|
||||
--pm-info-tint: color-mix(in srgb, #3b82f6 15%, transparent);
|
||||
|
||||
/* ── Elevation / Shadows ───────────────────────── */
|
||||
--pm-shadow: 0 8px 32px rgb(0 0 0 / 0.15);
|
||||
--pm-shadow-sm: 0 4px 4px rgb(0 0 0 / 0.25);
|
||||
|
||||
/* ── Border Radius (ComfyUI scale) ─────────────── */
|
||||
--pm-radius-sm: 5px;
|
||||
--pm-radius-md: 8px;
|
||||
--pm-radius-lg: 12px;
|
||||
|
||||
/* ── Spacing base ──────────────────────────────── */
|
||||
--pm-space: 4px;
|
||||
}
|
||||
|
||||
/* ── Global Scrollbar (ComfyUI-native) ───────────── */
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: var(--pm-border); border-radius: 3px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--pm-text-secondary); }
|
||||
|
||||
/* Firefox */
|
||||
* { scrollbar-width: thin; scrollbar-color: var(--pm-border) transparent; }
|
||||
|
||||
/* ── Viewer.js theme overrides (adapts to palette) ─ */
|
||||
.viewer-backdrop { background-color: rgb(0 0 0 / 0.9) !important; }
|
||||
.viewer-container { background-color: rgb(0 0 0 / 0.9) !important; }
|
||||
.viewer-canvas { background-color: rgb(0 0 0 / 0.9) !important; }
|
||||
.viewer-toolbar { background-color: color-mix(in srgb, var(--pm-bg-surface) 95%, transparent) !important; }
|
||||
.viewer-navbar { background-color: color-mix(in srgb, var(--pm-bg-surface) 95%, transparent) !important; }
|
||||
.viewer-title { color: var(--pm-text) !important; }
|
||||
|
||||
/* ── Film Strip (admin.html) ─────────────────────── */
|
||||
.prompt-film-strip {
|
||||
--film-strip-thumbnail-size: 112px;
|
||||
--film-strip-gap: 8px;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--film-strip-gap);
|
||||
padding: 8px 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.film-strip-thumbnail {
|
||||
flex-shrink: 0;
|
||||
width: var(--film-strip-thumbnail-size);
|
||||
height: var(--film-strip-thumbnail-size);
|
||||
border-radius: var(--pm-radius-sm);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.15s ease;
|
||||
background: var(--pm-bg-input);
|
||||
}
|
||||
|
||||
.film-strip-thumbnail:hover {
|
||||
border-color: var(--pm-accent);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.film-strip-thumbnail img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.film-strip-thumbnail--more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--pm-bg-surface);
|
||||
color: var(--pm-text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.film-strip-thumbnail--more:hover {
|
||||
background: var(--pm-bg-hover);
|
||||
color: var(--pm-text);
|
||||
}
|
||||
|
||||
.film-strip-empty {
|
||||
color: var(--pm-text-muted);
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.film-strip-size-small { --film-strip-thumbnail-size: 72px; --film-strip-gap: 6px; }
|
||||
.film-strip-size-medium { --film-strip-thumbnail-size: 112px; --film-strip-gap: 8px; }
|
||||
.film-strip-size-large { --film-strip-thumbnail-size: 144px; --film-strip-gap: 10px; }
|
||||
|
||||
/* ── Metadata Sidebar ────────────────────────────── */
|
||||
.metadata-sidebar {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
right: 0 !important;
|
||||
width: 384px !important;
|
||||
height: 100vh !important;
|
||||
background: var(--pm-bg-surface) !important;
|
||||
border-left: 1px solid var(--pm-border) !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
z-index: 10000 !important;
|
||||
transition: transform 0.3s ease-in-out !important;
|
||||
}
|
||||
|
||||
.metadata-sidebar.collapsed {
|
||||
transform: translateX(340px) !important;
|
||||
}
|
||||
|
||||
.metadata-collapse-btn {
|
||||
position: absolute;
|
||||
left: -40px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 32px;
|
||||
height: 80px;
|
||||
background: var(--pm-bg-surface);
|
||||
border: 1px solid var(--pm-border);
|
||||
border-right: none;
|
||||
border-radius: var(--pm-radius-sm) 0 0 var(--pm-radius-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
.metadata-collapse-btn:hover { background: var(--pm-bg-hover); }
|
||||
.metadata-collapse-btn svg { transition: transform 0.3s ease; }
|
||||
.metadata-sidebar.collapsed .metadata-collapse-btn svg { transform: rotate(180deg); }
|
||||
|
||||
/* ── Tag Chips ───────────────────────────────────── */
|
||||
.tag-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
background: var(--pm-bg-input);
|
||||
border: 1px solid var(--pm-border);
|
||||
border-radius: var(--pm-radius-sm);
|
||||
font-size: 12px;
|
||||
color: var(--pm-text-secondary);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tag-chip:hover { background: var(--pm-bg-hover); }
|
||||
|
||||
.tag-chip .tag-remove {
|
||||
margin-left: 6px;
|
||||
padding: 2px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
color: var(--pm-text-muted);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tag-chip .tag-remove:hover {
|
||||
color: var(--pm-error);
|
||||
background: var(--pm-error-tint);
|
||||
}
|
||||
|
||||
/* ── Gallery image hover ─────────────────────────── */
|
||||
.image-item {
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.image-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--pm-shadow);
|
||||
}
|
||||
@@ -1,3 +1,61 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
|
||||
/*
|
||||
* Content sources — tell Tailwind v4 where to scan for class usage.
|
||||
* Replaces the v3 `content` array in tailwind.config.js.
|
||||
*/
|
||||
@source "../../**/*.html";
|
||||
@source "../../js/**/*.js";
|
||||
|
||||
/* ── Custom Utilities — map to --pm-* design tokens ───────── */
|
||||
|
||||
/* Backgrounds */
|
||||
@utility bg-pm-primary { background-color: var(--pm-bg-primary); }
|
||||
@utility bg-pm-surface { background-color: var(--pm-bg-surface); }
|
||||
@utility bg-pm-input { background-color: var(--pm-bg-input); }
|
||||
@utility bg-pm-hover { background-color: var(--pm-bg-hover); }
|
||||
@utility bg-pm-elevated { background-color: var(--pm-bg-elevated); }
|
||||
@utility bg-pm-accent { background-color: var(--pm-accent); }
|
||||
@utility bg-pm-accent-hover { background-color: var(--pm-accent-hover); }
|
||||
@utility bg-pm-accent-tint { background-color: var(--pm-accent-tint); }
|
||||
@utility bg-pm-error { background-color: var(--pm-error); }
|
||||
@utility bg-pm-error-tint { background-color: var(--pm-error-tint); }
|
||||
@utility bg-pm-success { background-color: var(--pm-success); }
|
||||
@utility bg-pm-success-tint { background-color: var(--pm-success-tint); }
|
||||
@utility bg-pm-warning { background-color: var(--pm-warning); }
|
||||
@utility bg-pm-warning-tint { background-color: var(--pm-warning-tint); }
|
||||
@utility bg-pm-info { background-color: var(--pm-info); }
|
||||
@utility bg-pm-info-tint { background-color: var(--pm-info-tint); }
|
||||
|
||||
/* Text */
|
||||
@utility text-pm { color: var(--pm-text); }
|
||||
@utility text-pm-secondary { color: var(--pm-text-secondary); }
|
||||
@utility text-pm-input { color: var(--pm-text-input); }
|
||||
@utility text-pm-muted { color: var(--pm-text-muted); }
|
||||
@utility text-pm-accent { color: var(--pm-accent); }
|
||||
@utility text-pm-accent-fg { color: var(--pm-accent-fg); }
|
||||
@utility text-pm-error { color: var(--pm-error); }
|
||||
@utility text-pm-success { color: var(--pm-success); }
|
||||
@utility text-pm-warning { color: var(--pm-warning); }
|
||||
|
||||
/* Borders */
|
||||
@utility border-pm { border-color: var(--pm-border); }
|
||||
@utility border-pm-subtle { border-color: var(--pm-border-subtle); }
|
||||
@utility border-pm-accent { border-color: var(--pm-accent); }
|
||||
|
||||
/* Shadows */
|
||||
@utility shadow-pm { box-shadow: var(--pm-shadow); }
|
||||
@utility shadow-pm-sm { box-shadow: var(--pm-shadow-sm); }
|
||||
|
||||
/* Border radius */
|
||||
@utility rounded-pm-sm { border-radius: var(--pm-radius-sm); }
|
||||
@utility rounded-pm-md { border-radius: var(--pm-radius-md); }
|
||||
@utility rounded-pm-lg { border-radius: var(--pm-radius-lg); }
|
||||
|
||||
/* Ring / outline accent */
|
||||
@utility ring-pm-accent { --tw-ring-color: var(--pm-accent); }
|
||||
|
||||
/* Placeholder */
|
||||
@utility placeholder-pm {
|
||||
&::placeholder { color: var(--pm-text-muted); }
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
+45
-43
@@ -4,6 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Image Gallery - Generation Data</title>
|
||||
<script src="/prompt_manager/js/theme-bridge.js"></script>
|
||||
<link rel="stylesheet" href="lib/tailwind/comfyui-theme.css">
|
||||
<link rel="stylesheet" href="lib/tailwind/styles.css">
|
||||
<style>
|
||||
/* Ensure the layout takes full height and prevents overflow */
|
||||
@@ -58,10 +60,10 @@
|
||||
// Show temporary feedback
|
||||
const originalContent = button.innerHTML;
|
||||
button.innerHTML = '✓ Copied!';
|
||||
button.classList.add('text-green-400');
|
||||
button.classList.add('text-pm-success');
|
||||
setTimeout(() => {
|
||||
button.innerHTML = originalContent;
|
||||
button.classList.remove('text-green-400');
|
||||
button.classList.remove('text-pm-success');
|
||||
}, 1000);
|
||||
} catch (err) {
|
||||
alert('Copy failed: ' + err);
|
||||
@@ -385,76 +387,76 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
dataPanel.innerHTML = `
|
||||
<!-- File Path -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-2">File Path</h2>
|
||||
<div class="text-sm text-blue-400 hover:text-blue-300 cursor-pointer bg-gray-800 p-2 rounded break-all" onclick="openInFileExplorer('${filename}')">
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">File Path</h2>
|
||||
<div class="text-sm text-pm-accent hover:bg-pm-hover cursor-pointer bg-pm-surface p-2 rounded-pm-sm break-all" onclick="openInFileExplorer('${filename}')">
|
||||
${filename}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resources used -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-2">Resources used</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">Resources used</h2>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-blue-400 hover:text-blue-300 cursor-pointer">${checkpoint}</div>
|
||||
<div class="text-xs text-gray-500">ComfyUI Generated</div>
|
||||
<div class="text-pm-accent cursor-pointer">${checkpoint}</div>
|
||||
<div class="text-xs text-pm-muted">ComfyUI Generated</div>
|
||||
</div>
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">CHECKPOINT</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">CHECKPOINT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prompt -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h2 class="text-sm font-medium text-gray-300">Prompt</h2>
|
||||
<span class="px-2 py-1 text-xs bg-orange-600 text-orange-100 rounded">COMFYUI</span>
|
||||
<button class="ml-auto text-gray-400 hover:text-gray-300" onclick="copyPositivePrompt(event)">
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Prompt</h2>
|
||||
<span class="px-2 py-1 text-xs bg-pm-warning text-pm rounded-pm-sm">COMFYUI</span>
|
||||
<button class="ml-auto text-pm-secondary hover:text-pm" onclick="copyPositivePrompt(event)">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-gray-300 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto">
|
||||
<div class="text-sm text-pm-secondary bg-pm-surface p-3 rounded-pm-sm max-h-32 overflow-y-auto">
|
||||
${positivePrompt.substring(0, 200)}${positivePrompt.length > 200 ? '...' : ''}
|
||||
</div>
|
||||
${positivePrompt.length > 200 ? '<button class="text-blue-400 hover:text-blue-300 text-sm mt-1" onclick="showFullPrompt(\'positive\')">Show more</button>' : ''}
|
||||
${positivePrompt.length > 200 ? '<button class="text-pm-accent hover:bg-pm-hover text-sm mt-1" onclick="showFullPrompt(\'positive\')">Show more</button>' : ''}
|
||||
</div>
|
||||
|
||||
<!-- Negative prompt -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h2 class="text-sm font-medium text-gray-300">Negative prompt</h2>
|
||||
<button class="text-gray-400 hover:text-gray-300" onclick="copyNegativePrompt(event)">
|
||||
<h2 class="text-sm font-medium text-pm-secondary">Negative prompt</h2>
|
||||
<button class="text-pm-secondary hover:text-pm" onclick="copyNegativePrompt(event)">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm text-gray-300 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto">
|
||||
<div class="text-sm text-pm-secondary bg-pm-surface p-3 rounded-pm-sm max-h-32 overflow-y-auto">
|
||||
${negativePrompt.substring(0, 200)}${negativePrompt.length > 200 ? '...' : ''}
|
||||
</div>
|
||||
${negativePrompt.length > 200 ? '<button class="text-blue-400 hover:text-blue-300 text-sm mt-1" onclick="showFullPrompt(\'negative\')">Show more</button>' : ''}
|
||||
${negativePrompt.length > 200 ? '<button class="text-pm-accent hover:bg-pm-hover text-sm mt-1" onclick="showFullPrompt(\'negative\')">Show more</button>' : ''}
|
||||
</div>
|
||||
|
||||
<!-- Other metadata -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-3">Other metadata</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-3">Other metadata</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">CFG SCALE: ${cfgScale}</span>
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">STEPS: ${steps}</span>
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">SAMPLER: ${sampler}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">CFG SCALE: ${cfgScale}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">STEPS: ${steps}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">SAMPLER: ${sampler}</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<span class="px-2 py-1 text-xs bg-gray-700 text-gray-300 rounded">SEED: ${seed}</span>
|
||||
<span class="px-2 py-1 text-xs bg-pm-surface text-pm-secondary rounded-pm-sm">SEED: ${seed}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Raw Workflow Data -->
|
||||
<div>
|
||||
<h2 class="text-sm font-medium text-gray-300 mb-2">ComfyUI Workflow</h2>
|
||||
<h2 class="text-sm font-medium text-pm-secondary mb-2">ComfyUI Workflow</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="text-blue-400 hover:text-blue-300 text-sm" onclick="showWorkflowData()">View Raw Workflow JSON</button>
|
||||
<button class="text-blue-400 hover:text-blue-300" onclick="downloadWorkflowJSON()" title="Download JSON">
|
||||
<button class="text-pm-accent hover:bg-pm-hover text-sm" onclick="showWorkflowData()">View Raw Workflow JSON</button>
|
||||
<button class="text-pm-accent hover:bg-pm-hover" onclick="downloadWorkflowJSON()" title="Download JSON">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
@@ -555,17 +557,17 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
|
||||
imageArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
imageArea.classList.add('bg-gray-900');
|
||||
imageArea.classList.add('bg-pm-surface');
|
||||
});
|
||||
|
||||
imageArea.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
imageArea.classList.remove('bg-gray-900');
|
||||
imageArea.classList.remove('bg-pm-surface');
|
||||
});
|
||||
|
||||
imageArea.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
imageArea.classList.remove('bg-gray-900');
|
||||
imageArea.classList.remove('bg-pm-surface');
|
||||
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
@@ -590,16 +592,16 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-black text-gray-100 font-sans overflow-hidden">
|
||||
<body class="bg-pm-primary text-pm font-sans overflow-hidden">
|
||||
<div class="h-screen flex">
|
||||
<!-- Main Image Area -->
|
||||
<div id="image-area" class="flex-1 flex items-center justify-center p-4 bg-black relative border-2 border-dashed border-transparent transition-colors min-w-0">
|
||||
<div id="image-area" class="flex-1 flex items-center justify-center p-3 bg-pm-primary relative border-2 border-dashed border-transparent transition-colors min-w-0">
|
||||
<!-- Upload prompt overlay -->
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center text-gray-500 pointer-events-none">
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center text-pm-muted pointer-events-none">
|
||||
<svg class="w-16 h-16 mb-4 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
||||
</svg>
|
||||
<p class="text-lg mb-2">Drag & Drop PNG with ComfyUI Workflow</p>
|
||||
<p class="text-sm mb-2">Drag & Drop PNG with ComfyUI Workflow</p>
|
||||
<p class="text-sm">Or click to browse files</p>
|
||||
</div>
|
||||
|
||||
@@ -607,7 +609,7 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
<input type="file" id="file-input" accept=".png" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" onchange="handleImageUpload(event)">
|
||||
|
||||
<!-- Navigation arrows -->
|
||||
<button class="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-gray-800 bg-opacity-50 hover:bg-opacity-75 rounded-full flex items-center justify-center text-white transition-all z-10">
|
||||
<button class="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-pm-surface/50 hover:bg-pm-hover rounded-full flex items-center justify-center text-pm transition-all z-10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
||||
</svg>
|
||||
@@ -618,10 +620,10 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
<img id="main-image"
|
||||
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='800' height='1200' viewBox='0 0 800 1200'%3E%3Crect width='800' height='1200' fill='%23374151'/%3E%3Ctext x='400' y='600' font-family='Arial' font-size='24' fill='%23D1D5DB' text-anchor='middle'%3EDrop PNG Here%3C/text%3E%3Ctext x='400' y='640' font-family='Arial' font-size='16' fill='%239CA3AF' text-anchor='middle'%3EComfyUI Workflow%3C/text%3E%3C/svg%3E"
|
||||
alt="Generated Image"
|
||||
class="max-w-full max-h-full object-contain rounded shadow-2xl">
|
||||
class="max-w-full max-h-full object-contain rounded-pm-md shadow-pm">
|
||||
</div>
|
||||
|
||||
<button class="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-gray-800 bg-opacity-50 hover:bg-opacity-75 rounded-full flex items-center justify-center text-white transition-all z-10">
|
||||
<button class="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-pm-surface/50 hover:bg-pm-hover rounded-full flex items-center justify-center text-pm transition-all z-10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
@@ -629,28 +631,28 @@ Seed: ${currentWorkflowData.seed || 'Unknown'}`;
|
||||
</div>
|
||||
|
||||
<!-- Generation Data Panel -->
|
||||
<div class="w-96 bg-gray-900 border-l border-gray-700 flex flex-col">
|
||||
<div class="w-96 bg-pm-primary border-l border-pm flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between p-4 border-b border-gray-700">
|
||||
<div class="flex items-center justify-between p-3 border-b border-pm">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-5 h-5 text-pm-secondary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<h1 class="text-lg font-medium text-white">Generation data</h1>
|
||||
<h1 class="text-sm font-semibold text-pm">Generation data</h1>
|
||||
</div>
|
||||
<button class="text-xs text-blue-400 hover:text-blue-300 transition-colors" onclick="copyAllData()">
|
||||
<button class="text-xs text-pm-accent hover:bg-pm-hover transition-colors" onclick="copyAllData()">
|
||||
📋 COPY ALL
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable Content -->
|
||||
<div id="data-panel-content" class="flex-1 overflow-y-auto p-4 space-y-6">
|
||||
<div id="data-panel-content" class="flex-1 overflow-y-auto p-3 space-y-3">
|
||||
<!-- Initial placeholder content -->
|
||||
<div class="text-center text-gray-500 py-8">
|
||||
<div class="text-center text-pm-muted py-8">
|
||||
<svg class="w-16 h-16 mx-auto mb-4 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
<p class="text-lg mb-2">No Image Loaded</p>
|
||||
<p class="text-sm mb-2">No Image Loaded</p>
|
||||
<p class="text-sm">Upload a PNG with embedded ComfyUI workflow to view generation data</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user