feat: complete Phase 2 component redesign across all frontend files

Migrate all remaining hardcoded Tailwind color classes to --pm-* design
tokens across gallery.html, metadata.html, admin.js, gallery.js, and
tags-page.js. Zero hardcoded color classes remain in HTML/JS files.

- gallery.html: remove inline styles (now in comfyui-theme.css), strip
  gradients/decorative elements, tighten spacing for ComfyUI density
- metadata.html: add theme imports, replace all color classes
- admin.js: replace 164 color occurrences in template literals, convert
  raw hex values to CSS variable references
- gallery.js: replace 161 color occurrences in template literals
- tags-page.js: replace 30 color occurrences in template literals
This commit is contained in:
Vito Sansevero
2026-02-08 06:35:43 -08:00
parent d0aa7d7080
commit 8652bb70d6
6 changed files with 768 additions and 931 deletions
+288 -453
View File
File diff suppressed because it is too large Load Diff
+200 -200
View File
@@ -421,11 +421,11 @@
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">
<div class="w-16 h-16 bg-pm-surface rounded-full flex items-center justify-center mx-auto mb-4">
<span class="text-2xl">📭</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-lg font-medium 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="bg-pm-surface rounded-pm-md border border-pm hover:border-pm transition-all duration-200" data-id="${prompt.id}">
<div class="p-6">
<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)}">&times;</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)}">&times;</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 custom-scrollbar 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)}">&times;</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)}">&times;</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-6 py-4 rounded-pm-sm shadow-pm ${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>'">
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 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">
<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>
@@ -1247,8 +1247,8 @@
resultsContainer.innerHTML = `
<div class="text-center py-8">
<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>`;
}
@@ -1381,7 +1381,7 @@
content.innerHTML = `
<div class="text-center py-8">
<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>`;
@@ -1691,12 +1691,12 @@
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-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-6 max-w-2xl w-full mx-4 border border-pm">
<h3 class="text-xl font-semibold text-pm mb-4">📋 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-6 py-2 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-8 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,7 +2441,7 @@
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>
@@ -2977,22 +2977,22 @@ 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-lg font-medium 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">
<!-- 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>
@@ -3016,7 +3016,7 @@ 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>
@@ -3033,7 +3033,7 @@ 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">
<div class="text-center text-pm-error py-8">
<p class="text-lg 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">&times;</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');
+204 -204
View File
@@ -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,7 +219,7 @@
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-xs">Failed to load</div>
@@ -228,22 +228,22 @@
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-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">
<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>
`}
@@ -377,20 +377,20 @@
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-lg font-medium 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>
@@ -398,8 +398,8 @@
<!-- Scrollable Content -->
<div id="metadata-content" class="flex-1 overflow-y-auto p-4 space-y-6">
<!-- 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 bg-opacity-50 hover:bg-opacity-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 bg-opacity-50 hover:bg-opacity-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 bg-opacity-50 hover:bg-opacity-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-lg font-medium 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">
<!-- 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 bg-opacity-50 hover:bg-opacity-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 bg-opacity-50 hover:bg-opacity-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 bg-opacity-50 hover:bg-opacity-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 bg-opacity-50 hover:bg-opacity-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 bg-opacity-50 hover:bg-opacity-70 rounded text-pm text-sm">
🔁
</button>
</div>
@@ -617,20 +617,20 @@
<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-lg font-medium 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>
@@ -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-6 py-4 rounded-pm-md shadow-pm 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-8 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 bg-opacity-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>
@@ -2395,23 +2395,23 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-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="bg-pm-surface rounded-pm-md max-w-2xl w-full mx-4 border border-pm">
<div class="p-6">
<h3 class="text-xl font-semibold text-gray-100 mb-4 flex items-center">
<h3 class="text-xl font-semibold text-pm mb-4 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-6">
💡 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">
<h3 class="text-xl font-semibold text-pm mb-4 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="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 custom-scrollbar">
<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-6 py-3 rounded-pm-md shadow-pm z-50 animate-fade-in`;
notification.textContent = message;
document.body.appendChild(notification);
+32 -32
View File
@@ -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-8 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);
@@ -596,7 +596,7 @@ class TagsPageManager {
const spinner = document.createElement('div');
spinner.className = 'col-span-full text-center py-8';
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-8 text-pm-error';
errDiv.textContent = 'Failed to load prompts';
grid.appendChild(errDiv);
}
@@ -752,7 +752,7 @@ 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-12 text-pm-muted';
const icon = document.createElement('span');
icon.className = 'text-3xl block mb-2';
icon.textContent = '\uD83D\uDCED';
@@ -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) => {
File diff suppressed because one or more lines are too long
+43 -41
View File
@@ -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="css/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,12 +592,12 @@ 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>
@@ -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,24 +631,24 @@ 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-lg font-medium 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>