mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
checkpoint
This commit is contained in:
@@ -220,12 +220,19 @@ class RecipeRoutes:
|
||||
|
||||
# Check if this LoRA exists locally by SHA256 hash
|
||||
exists_locally = False
|
||||
local_path = None
|
||||
|
||||
if civitai_info and 'files' in civitai_info and civitai_info['files']:
|
||||
sha256 = civitai_info['files'][0].get('hashes', {}).get('SHA256', '')
|
||||
if sha256:
|
||||
sha256 = sha256.lower() # Convert to lowercase for consistency
|
||||
exists_locally = self.recipe_scanner._lora_scanner.has_lora_hash(sha256)
|
||||
if civitai_info and 'files' in civitai_info:
|
||||
# Find the model file (type="Model") in the files list
|
||||
model_file = next((file for file in civitai_info.get('files', [])
|
||||
if file.get('type') == 'Model'), None)
|
||||
|
||||
if model_file:
|
||||
sha256 = model_file.get('hashes', {}).get('SHA256', '')
|
||||
if sha256:
|
||||
exists_locally = self.recipe_scanner._lora_scanner.has_lora_hash(sha256)
|
||||
if exists_locally:
|
||||
local_path = self.recipe_scanner._lora_scanner.get_lora_path_by_hash(sha256)
|
||||
|
||||
# Create LoRA entry
|
||||
lora_entry = {
|
||||
@@ -235,6 +242,7 @@ class RecipeRoutes:
|
||||
'type': resource.get('type', 'lora'),
|
||||
'weight': resource.get('weight', 1.0),
|
||||
'existsLocally': exists_locally,
|
||||
'localPath': local_path,
|
||||
'thumbnailUrl': '',
|
||||
'baseModel': '',
|
||||
'size': 0,
|
||||
@@ -250,9 +258,9 @@ class RecipeRoutes:
|
||||
# Get base model
|
||||
lora_entry['baseModel'] = civitai_info.get('baseModel', '')
|
||||
|
||||
# Get file size
|
||||
if 'files' in civitai_info and civitai_info['files']:
|
||||
lora_entry['size'] = civitai_info['files'][0].get('sizeKB', 0) * 1024
|
||||
# Get file size from model file
|
||||
if model_file:
|
||||
lora_entry['size'] = model_file.get('sizeKB', 0) * 1024
|
||||
|
||||
# Get download URL
|
||||
lora_entry['downloadUrl'] = civitai_info.get('downloadUrl', '')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* Import Modal Styles */
|
||||
.import-step {
|
||||
margin: var(--space-2) 0;
|
||||
transition: none !important; /* Disable any transitions that might affect display */
|
||||
}
|
||||
|
||||
/* File Input Styles */
|
||||
@@ -364,14 +365,6 @@
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Modal Actions */
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-2);
|
||||
margin-top: var(--space-3);
|
||||
}
|
||||
|
||||
/* Dark theme adjustments */
|
||||
[data-theme="dark"] .lora-item {
|
||||
background: var(--lora-surface);
|
||||
@@ -391,3 +384,58 @@
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Size badge for LoRA items */
|
||||
.size-badge {
|
||||
background: var(--lora-surface);
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--border-radius-xs);
|
||||
font-size: 0.85em;
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Missing LoRAs summary section */
|
||||
.missing-loras-summary {
|
||||
margin-bottom: var(--space-3);
|
||||
padding: var(--space-2);
|
||||
background: var(--bg-color);
|
||||
border-radius: var(--border-radius-sm);
|
||||
border: 1px solid var(--lora-error);
|
||||
}
|
||||
|
||||
.missing-loras-summary h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--space-2);
|
||||
font-size: 1.1em;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.total-size-info {
|
||||
margin-bottom: var(--space-2);
|
||||
font-size: 0.9em;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.missing-lora-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.missing-lora-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.missing-lora-name {
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.missing-lora-size {
|
||||
font-size: 0.9em;
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
@@ -36,14 +36,22 @@ export class ImportManager {
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
// Always reset the state when opening the modal
|
||||
this.resetSteps();
|
||||
|
||||
// Show the modal
|
||||
modalManager.showModal('importModal', null, () => {
|
||||
// Cleanup handler when modal closes
|
||||
this.cleanupFolderBrowser();
|
||||
|
||||
// 移除任何强制样式
|
||||
// Remove any injected styles
|
||||
this.removeInjectedStyles();
|
||||
});
|
||||
this.resetSteps();
|
||||
|
||||
// Verify the modal is properly shown
|
||||
setTimeout(() => {
|
||||
this.ensureModalVisible();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
// 添加移除注入样式的方法
|
||||
@@ -52,12 +60,18 @@ export class ImportManager {
|
||||
this.injectedStyles.parentNode.removeChild(this.injectedStyles);
|
||||
this.injectedStyles = null;
|
||||
}
|
||||
|
||||
// Also reset any inline styles that might have been set with !important
|
||||
document.querySelectorAll('.import-step').forEach(step => {
|
||||
step.style.cssText = '';
|
||||
});
|
||||
}
|
||||
|
||||
resetSteps() {
|
||||
// 移除可能存在的强制样式
|
||||
// Remove any existing injected styles
|
||||
this.removeInjectedStyles();
|
||||
|
||||
// Show the first step
|
||||
this.showStep('uploadStep');
|
||||
|
||||
// Reset file input
|
||||
@@ -78,6 +92,18 @@ export class ImportManager {
|
||||
previewElement.innerHTML = '<div class="placeholder">Image preview will appear here</div>';
|
||||
}
|
||||
|
||||
// Reset recipe name input
|
||||
const recipeName = document.getElementById('recipeName');
|
||||
if (recipeName) {
|
||||
recipeName.value = '';
|
||||
}
|
||||
|
||||
// Reset tags container
|
||||
const tagsContainer = document.getElementById('tagsContainer');
|
||||
if (tagsContainer) {
|
||||
tagsContainer.innerHTML = '<div class="empty-tags">No tags added</div>';
|
||||
}
|
||||
|
||||
// Reset state variables
|
||||
this.recipeImage = null;
|
||||
this.recipeData = null;
|
||||
@@ -92,6 +118,18 @@ export class ImportManager {
|
||||
folderBrowser.querySelectorAll('.folder-item').forEach(f =>
|
||||
f.classList.remove('selected'));
|
||||
}
|
||||
|
||||
// Clear missing LoRAs list if it exists
|
||||
const missingLorasList = document.getElementById('missingLorasList');
|
||||
if (missingLorasList) {
|
||||
missingLorasList.innerHTML = '';
|
||||
}
|
||||
|
||||
// Reset total download size
|
||||
const totalSizeDisplay = document.getElementById('totalDownloadSize');
|
||||
if (totalSizeDisplay) {
|
||||
totalSizeDisplay.textContent = 'Calculating...';
|
||||
}
|
||||
}
|
||||
|
||||
handleImageUpload(event) {
|
||||
@@ -207,6 +245,10 @@ export class ImportManager {
|
||||
<i class="fas fa-exclamation-triangle"></i> Not in Library
|
||||
</div>`;
|
||||
|
||||
// Format size if available
|
||||
const sizeDisplay = lora.size ?
|
||||
`<div class="size-badge">${this.formatFileSize(lora.size)}</div>` : '';
|
||||
|
||||
return `
|
||||
<div class="lora-item ${existsLocally ? 'exists-locally' : 'missing-locally'}">
|
||||
<div class="lora-thumbnail">
|
||||
@@ -220,6 +262,7 @@ export class ImportManager {
|
||||
${lora.version ? `<div class="lora-version">${lora.version}</div>` : ''}
|
||||
<div class="lora-info">
|
||||
${lora.baseModel ? `<div class="base-model">${lora.baseModel}</div>` : ''}
|
||||
${sizeDisplay}
|
||||
<div class="weight-badge">Weight: ${lora.weight || 1.0}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -291,8 +334,6 @@ export class ImportManager {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Proceeding from details, missing LoRAs:', this.missingLoras.length);
|
||||
|
||||
// If we have missing LoRAs, go to location step
|
||||
if (this.missingLoras.length > 0) {
|
||||
this.proceedToLocation();
|
||||
@@ -303,30 +344,61 @@ export class ImportManager {
|
||||
}
|
||||
|
||||
async proceedToLocation() {
|
||||
// 先移除可能已有的样式
|
||||
this.removeInjectedStyles();
|
||||
|
||||
// 添加强制CSS覆盖
|
||||
this.injectedStyles = document.createElement('style');
|
||||
this.injectedStyles.innerHTML = `
|
||||
#locationStep {
|
||||
display: block !important;
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
position: static !important;
|
||||
z-index: 10000 !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
overflow: visible !important;
|
||||
transform: none !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(this.injectedStyles);
|
||||
console.log('Added override CSS to force visibility');
|
||||
|
||||
// Show the location step with special handling
|
||||
this.showStep('locationStep');
|
||||
|
||||
// Double-check after a short delay to ensure the step is visible
|
||||
setTimeout(() => {
|
||||
const locationStep = document.getElementById('locationStep');
|
||||
if (locationStep.style.display !== 'block' ||
|
||||
window.getComputedStyle(locationStep).display !== 'block') {
|
||||
// Force display again
|
||||
locationStep.style.display = 'block';
|
||||
|
||||
// If still not visible, try with injected style
|
||||
if (window.getComputedStyle(locationStep).display !== 'block') {
|
||||
this.injectedStyles = document.createElement('style');
|
||||
this.injectedStyles.innerHTML = `
|
||||
#locationStep {
|
||||
display: block !important;
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(this.injectedStyles);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
|
||||
try {
|
||||
// Display missing LoRAs that will be downloaded
|
||||
const missingLorasList = document.getElementById('missingLorasList');
|
||||
if (missingLorasList && this.missingLoras.length > 0) {
|
||||
// Calculate total size
|
||||
const totalSize = this.missingLoras.reduce((sum, lora) => {
|
||||
return sum + (lora.size ? parseInt(lora.size) : 0);
|
||||
}, 0);
|
||||
|
||||
// Update total size display
|
||||
const totalSizeDisplay = document.getElementById('totalDownloadSize');
|
||||
if (totalSizeDisplay) {
|
||||
totalSizeDisplay.textContent = this.formatFileSize(totalSize);
|
||||
}
|
||||
|
||||
// Generate missing LoRAs list
|
||||
missingLorasList.innerHTML = this.missingLoras.map(lora => {
|
||||
const sizeDisplay = lora.size ? this.formatFileSize(lora.size) : 'Unknown size';
|
||||
|
||||
return `
|
||||
<div class="missing-lora-item">
|
||||
<div class="missing-lora-name">${lora.name}</div>
|
||||
<div class="missing-lora-size">${sizeDisplay}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Fetch LoRA roots
|
||||
const rootsResponse = await fetch('/api/lora-roots');
|
||||
if (!rootsResponse.ok) {
|
||||
@@ -365,6 +437,18 @@ export class ImportManager {
|
||||
|
||||
backToUpload() {
|
||||
this.showStep('uploadStep');
|
||||
|
||||
// Reset file input to ensure it can trigger change events again
|
||||
const fileInput = document.getElementById('recipeImageUpload');
|
||||
if (fileInput) {
|
||||
fileInput.value = '';
|
||||
}
|
||||
|
||||
// Clear any previous error messages
|
||||
const errorElement = document.getElementById('uploadError');
|
||||
if (errorElement) {
|
||||
errorElement.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
backToDetails() {
|
||||
@@ -397,7 +481,6 @@ export class ImportManager {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// Handle successful save
|
||||
console.log(`Recipe saved with ID: ${result.recipe_id}`);
|
||||
// Show success message for recipe save
|
||||
showToast(`Recipe "${this.recipeName}" saved successfully`, 'success');
|
||||
|
||||
@@ -544,7 +627,6 @@ export class ImportManager {
|
||||
if (loraRoot) loraRoot.addEventListener('change', this.updateTargetPath);
|
||||
if (newFolder) newFolder.addEventListener('input', this.updateTargetPath);
|
||||
|
||||
console.log('Initializing folder browser...');
|
||||
// Update initial path
|
||||
this.updateTargetPath();
|
||||
}
|
||||
@@ -588,36 +670,80 @@ export class ImportManager {
|
||||
}
|
||||
|
||||
showStep(stepId) {
|
||||
// 隐藏所有步骤
|
||||
|
||||
// First, remove any injected styles to prevent conflicts
|
||||
this.removeInjectedStyles();
|
||||
|
||||
// Hide all steps first
|
||||
document.querySelectorAll('.import-step').forEach(step => {
|
||||
step.style.display = 'none';
|
||||
});
|
||||
|
||||
// 显示目标步骤
|
||||
// Show target step with a monitoring mechanism
|
||||
const targetStep = document.getElementById(stepId);
|
||||
if (targetStep) {
|
||||
// 强制显示目标步骤 - 使用 !important 覆盖任何其他CSS规则
|
||||
targetStep.setAttribute('style', 'display: block !important');
|
||||
// Use direct style setting
|
||||
targetStep.style.display = 'block';
|
||||
|
||||
// 调试信息
|
||||
console.log(`Showing step: ${stepId}`);
|
||||
const rect = targetStep.getBoundingClientRect();
|
||||
console.log('Step dimensions:', {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
top: rect.top,
|
||||
left: rect.left,
|
||||
visible: rect.width > 0 && rect.height > 0
|
||||
});
|
||||
// For the locationStep specifically, we need additional measures
|
||||
if (stepId === 'locationStep') {
|
||||
// Create a more persistent style to override any potential conflicts
|
||||
this.injectedStyles = document.createElement('style');
|
||||
this.injectedStyles.innerHTML = `
|
||||
#locationStep {
|
||||
display: block !important;
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(this.injectedStyles);
|
||||
|
||||
// Force layout recalculation
|
||||
targetStep.offsetHeight;
|
||||
|
||||
// Set up a monitor to ensure the step remains visible
|
||||
setTimeout(() => {
|
||||
if (targetStep.style.display !== 'block') {
|
||||
targetStep.style.display = 'block';
|
||||
}
|
||||
|
||||
// Check dimensions again after a short delay
|
||||
const newRect = targetStep.getBoundingClientRect();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
// 强制重新计算布局
|
||||
targetStep.offsetHeight;
|
||||
|
||||
// 滚动模态内容到顶部
|
||||
// Scroll modal content to top
|
||||
const modalContent = document.querySelector('#importModal .modal-content');
|
||||
if (modalContent) {
|
||||
modalContent.scrollTop = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a helper method to format file sizes
|
||||
formatFileSize(bytes) {
|
||||
if (!bytes || isNaN(bytes)) return '';
|
||||
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// Add this method to ensure the modal is fully visible and initialized
|
||||
ensureModalVisible() {
|
||||
const importModal = document.getElementById('importModal');
|
||||
if (!importModal) {
|
||||
console.error('Import modal element not found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if modal is actually visible
|
||||
const modalDisplay = window.getComputedStyle(importModal).display;
|
||||
if (modalDisplay !== 'block') {
|
||||
console.error('Import modal is not visible, display: ' + modalDisplay);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,17 @@
|
||||
<!-- Step 3: Download Location (if needed) -->
|
||||
<div class="import-step" id="locationStep" style="display: none;">
|
||||
<div class="location-selection">
|
||||
<!-- Add missing LoRAs summary section -->
|
||||
<div class="missing-loras-summary">
|
||||
<h3>Missing LoRAs to Download</h3>
|
||||
<div class="total-size-info">
|
||||
Total download size: <span id="totalDownloadSize">Calculating...</span>
|
||||
</div>
|
||||
<div id="missingLorasList" class="missing-loras-list">
|
||||
<!-- Missing LoRAs will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Move path preview to top -->
|
||||
<div class="path-preview">
|
||||
<label>Download Location Preview:</label>
|
||||
|
||||
Reference in New Issue
Block a user