mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 23:25:43 -03:00
Enhance LoRA management by introducing deletion status and UI updates. Implement warning indicators for deleted LoRAs in the import modal, update cache handling for added and removed recipes, and improve styling for deleted items. Adjust logic to exclude deleted LoRAs from download prompts and ensure proper display of their status in the UI.
This commit is contained in:
@@ -231,42 +231,49 @@ class RecipeRoutes:
|
|||||||
# Get additional info from Civitai
|
# Get additional info from Civitai
|
||||||
civitai_info = await self.civitai_client.get_model_version_info(model_version_id)
|
civitai_info = await self.civitai_client.get_model_version_info(model_version_id)
|
||||||
|
|
||||||
# Check if this LoRA exists locally by SHA256 hash
|
# Initialize lora entry with default values
|
||||||
exists_locally = False
|
|
||||||
local_path = None
|
|
||||||
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 for frontend display
|
|
||||||
lora_entry = {
|
lora_entry = {
|
||||||
'id': model_version_id,
|
'id': model_version_id,
|
||||||
'name': resource.get('modelName', ''),
|
'name': resource.get('modelName', ''),
|
||||||
'version': resource.get('modelVersionName', ''),
|
'version': resource.get('modelVersionName', ''),
|
||||||
'type': resource.get('type', 'lora'),
|
'type': resource.get('type', 'lora'),
|
||||||
'weight': resource.get('weight', 1.0),
|
'weight': resource.get('weight', 1.0),
|
||||||
'existsLocally': exists_locally,
|
'existsLocally': False,
|
||||||
'localPath': local_path,
|
'localPath': None,
|
||||||
'file_name': os.path.splitext(os.path.basename(local_path))[0] if local_path else '',
|
'file_name': '',
|
||||||
'hash': sha256,
|
'hash': '',
|
||||||
'thumbnailUrl': '',
|
'thumbnailUrl': '',
|
||||||
'baseModel': '',
|
'baseModel': '',
|
||||||
'size': 0,
|
'size': 0,
|
||||||
'downloadUrl': ''
|
'downloadUrl': '',
|
||||||
|
'isDeleted': False # New flag to indicate if the LoRA is deleted from Civitai
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add Civitai info if available
|
# Check if this LoRA exists locally by SHA256 hash
|
||||||
if civitai_info:
|
if civitai_info and civitai_info.get("error") != "Model not found":
|
||||||
|
# LoRA exists on Civitai, process its information
|
||||||
|
if '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)
|
||||||
|
lora_entry['existsLocally'] = True
|
||||||
|
lora_entry['localPath'] = local_path
|
||||||
|
lora_entry['file_name'] = os.path.splitext(os.path.basename(local_path))[0]
|
||||||
|
else:
|
||||||
|
# For missing LoRAs, get file_name from model_file.name
|
||||||
|
file_name = model_file.get('name', '')
|
||||||
|
lora_entry['file_name'] = os.path.splitext(file_name)[0] if file_name else ''
|
||||||
|
|
||||||
|
lora_entry['hash'] = sha256
|
||||||
|
lora_entry['size'] = model_file.get('sizeKB', 0) * 1024
|
||||||
|
|
||||||
# Get thumbnail URL from first image
|
# Get thumbnail URL from first image
|
||||||
if 'images' in civitai_info and civitai_info['images']:
|
if 'images' in civitai_info and civitai_info['images']:
|
||||||
lora_entry['thumbnailUrl'] = civitai_info['images'][0].get('url', '')
|
lora_entry['thumbnailUrl'] = civitai_info['images'][0].get('url', '')
|
||||||
@@ -274,12 +281,12 @@ class RecipeRoutes:
|
|||||||
# Get base model
|
# Get base model
|
||||||
lora_entry['baseModel'] = civitai_info.get('baseModel', '')
|
lora_entry['baseModel'] = civitai_info.get('baseModel', '')
|
||||||
|
|
||||||
# Get file size from model file
|
|
||||||
if model_file:
|
|
||||||
lora_entry['size'] = model_file.get('sizeKB', 0) * 1024
|
|
||||||
|
|
||||||
# Get download URL
|
# Get download URL
|
||||||
lora_entry['downloadUrl'] = civitai_info.get('downloadUrl', '')
|
lora_entry['downloadUrl'] = civitai_info.get('downloadUrl', '')
|
||||||
|
else:
|
||||||
|
# LoRA is deleted from Civitai or not found
|
||||||
|
lora_entry['isDeleted'] = True
|
||||||
|
lora_entry['thumbnailUrl'] = '/loras_static/images/no-preview.png'
|
||||||
|
|
||||||
loras.append(lora_entry)
|
loras.append(lora_entry)
|
||||||
|
|
||||||
@@ -385,6 +392,10 @@ class RecipeRoutes:
|
|||||||
# Format loras data according to the recipe.json format
|
# Format loras data according to the recipe.json format
|
||||||
loras_data = []
|
loras_data = []
|
||||||
for lora in metadata.get("loras", []):
|
for lora in metadata.get("loras", []):
|
||||||
|
# Skip deleted LoRAs if they're marked to be excluded
|
||||||
|
if lora.get("isDeleted", False) and lora.get("exclude", False):
|
||||||
|
continue
|
||||||
|
|
||||||
# Convert frontend lora format to recipe format
|
# Convert frontend lora format to recipe format
|
||||||
lora_entry = {
|
lora_entry = {
|
||||||
"file_name": lora.get("file_name", "") or os.path.splitext(os.path.basename(lora.get("localPath", "")))[0],
|
"file_name": lora.get("file_name", "") or os.path.splitext(os.path.basename(lora.get("localPath", "")))[0],
|
||||||
@@ -392,7 +403,8 @@ class RecipeRoutes:
|
|||||||
"strength": float(lora.get("weight", 1.0)),
|
"strength": float(lora.get("weight", 1.0)),
|
||||||
"modelVersionId": lora.get("id", ""),
|
"modelVersionId": lora.get("id", ""),
|
||||||
"modelName": lora.get("name", ""),
|
"modelName": lora.get("name", ""),
|
||||||
"modelVersionName": lora.get("version", "")
|
"modelVersionName": lora.get("version", ""),
|
||||||
|
"isDeleted": lora.get("isDeleted", False) # Preserve deletion status in saved recipe
|
||||||
}
|
}
|
||||||
loras_data.append(lora_entry)
|
loras_data.append(lora_entry)
|
||||||
|
|
||||||
@@ -434,15 +446,17 @@ class RecipeRoutes:
|
|||||||
json_path = os.path.join(recipes_dir, json_filename)
|
json_path = os.path.join(recipes_dir, json_filename)
|
||||||
with open(json_path, 'w', encoding='utf-8') as f:
|
with open(json_path, 'w', encoding='utf-8') as f:
|
||||||
json.dump(recipe_data, f, indent=4, ensure_ascii=False)
|
json.dump(recipe_data, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
# Add the new recipe directly to the cache instead of forcing a refresh
|
# Simplified cache update approach
|
||||||
try:
|
# Instead of trying to update the cache directly, just set it to None
|
||||||
# Use a timeout to prevent deadlocks
|
# to force a refresh on the next get_cached_data call
|
||||||
async with asyncio.timeout(5.0):
|
if self.recipe_scanner._cache is not None:
|
||||||
cache = await self.recipe_scanner.get_cached_data()
|
# Add the recipe to the raw data if the cache exists
|
||||||
await cache.add_recipe(recipe_data)
|
# This is a simple direct update without locks or timeouts
|
||||||
except asyncio.TimeoutError:
|
self.recipe_scanner._cache.raw_data.append(recipe_data)
|
||||||
logger.warning("Timeout adding recipe to cache - will be picked up on next refresh")
|
# Schedule a background task to resort the cache
|
||||||
|
asyncio.create_task(self.recipe_scanner._cache.resort())
|
||||||
|
logger.info(f"Added recipe {recipe_id} to cache")
|
||||||
|
|
||||||
return web.json_response({
|
return web.json_response({
|
||||||
'success': True,
|
'success': True,
|
||||||
@@ -486,14 +500,16 @@ class RecipeRoutes:
|
|||||||
os.remove(image_path)
|
os.remove(image_path)
|
||||||
logger.info(f"Deleted recipe image: {image_path}")
|
logger.info(f"Deleted recipe image: {image_path}")
|
||||||
|
|
||||||
# Remove from cache without forcing a full refresh
|
# Simplified cache update approach
|
||||||
try:
|
if self.recipe_scanner._cache is not None:
|
||||||
# Use a timeout to prevent deadlocks
|
# Remove the recipe from raw_data if it exists
|
||||||
async with asyncio.timeout(5.0):
|
self.recipe_scanner._cache.raw_data = [
|
||||||
cache = await self.recipe_scanner.get_cached_data(force_refresh=False)
|
r for r in self.recipe_scanner._cache.raw_data
|
||||||
await cache.remove_recipe(recipe_id)
|
if str(r.get('id', '')) != recipe_id
|
||||||
except asyncio.TimeoutError:
|
]
|
||||||
logger.warning("Timeout removing recipe from cache - will be picked up on next refresh")
|
# Schedule a background task to resort the cache
|
||||||
|
asyncio.create_task(self.recipe_scanner._cache.resort())
|
||||||
|
logger.info(f"Removed recipe {recipe_id} from cache")
|
||||||
|
|
||||||
return web.json_response({"success": True, "message": "Recipe deleted successfully"})
|
return web.json_response({"success": True, "message": "Recipe deleted successfully"})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ html, body {
|
|||||||
--lora-border: oklch(90% 0.02 256 / 0.15);
|
--lora-border: oklch(90% 0.02 256 / 0.15);
|
||||||
--lora-text: oklch(95% 0.02 256);
|
--lora-text: oklch(95% 0.02 256);
|
||||||
--lora-error: oklch(75% 0.32 29);
|
--lora-error: oklch(75% 0.32 29);
|
||||||
|
--lora-warning: oklch(75% 0.25 80); /* Add warning color for deleted LoRAs */
|
||||||
|
|
||||||
/* Spacing Scale */
|
/* Spacing Scale */
|
||||||
--space-1: calc(8px * 1);
|
--space-1: calc(8px * 1);
|
||||||
@@ -68,6 +69,7 @@ html, body {
|
|||||||
--lora-surface: oklch(25% 0.02 256 / 0.98);
|
--lora-surface: oklch(25% 0.02 256 / 0.98);
|
||||||
--lora-border: oklch(90% 0.02 256 / 0.15);
|
--lora-border: oklch(90% 0.02 256 / 0.15);
|
||||||
--lora-text: oklch(98% 0.02 256);
|
--lora-text: oklch(98% 0.02 256);
|
||||||
|
--lora-warning: oklch(75% 0.25 80); /* Add warning color for dark theme too */
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|||||||
@@ -161,6 +161,11 @@
|
|||||||
border-left: 4px solid var(--lora-error);
|
border-left: 4px solid var(--lora-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lora-item.is-deleted {
|
||||||
|
background: oklch(var(--lora-warning) / 0.05);
|
||||||
|
border-left: 4px solid var(--lora-warning);
|
||||||
|
}
|
||||||
|
|
||||||
.lora-thumbnail {
|
.lora-thumbnail {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
@@ -521,4 +526,89 @@
|
|||||||
.modal-content {
|
.modal-content {
|
||||||
padding-right: calc(var(--space-2) + var(--scrollbar-width)); /* Add extra padding for scrollbar */
|
padding-right: calc(var(--space-2) + var(--scrollbar-width)); /* Add extra padding for scrollbar */
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deleted LoRA styles - Fix layout issues */
|
||||||
|
.lora-item.is-deleted {
|
||||||
|
background: oklch(var(--lora-warning) / 0.05);
|
||||||
|
border-left: 4px solid var(--lora-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--lora-warning);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted-badge i {
|
||||||
|
margin-right: 4px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exclude-lora-checkbox {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Deleted LoRAs warning - redesigned to not interfere with modal buttons */
|
||||||
|
.deleted-loras-warning {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: oklch(var(--lora-warning) / 0.1);
|
||||||
|
border: 1px solid var(--lora-warning);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
color: var(--text-color);
|
||||||
|
margin-bottom: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-icon {
|
||||||
|
color: var(--lora-warning);
|
||||||
|
font-size: 1.2em;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-text {
|
||||||
|
font-size: 0.9em;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove the old warning-message styles that were causing layout issues */
|
||||||
|
.warning-message {
|
||||||
|
display: none; /* Hide the old style */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update deleted badge to be more prominent */
|
||||||
|
.deleted-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--lora-warning);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleted-badge i {
|
||||||
|
margin-right: 4px;
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
@@ -255,31 +255,39 @@ export class ImportManager {
|
|||||||
if (lorasList) {
|
if (lorasList) {
|
||||||
lorasList.innerHTML = this.recipeData.loras.map(lora => {
|
lorasList.innerHTML = this.recipeData.loras.map(lora => {
|
||||||
const existsLocally = lora.existsLocally;
|
const existsLocally = lora.existsLocally;
|
||||||
|
const isDeleted = lora.isDeleted;
|
||||||
const localPath = lora.localPath || '';
|
const localPath = lora.localPath || '';
|
||||||
|
|
||||||
// Create local status badge
|
// Create status badge based on LoRA status
|
||||||
const localStatus = existsLocally ?
|
let statusBadge;
|
||||||
`<div class="local-badge">
|
if (isDeleted) {
|
||||||
<i class="fas fa-check"></i> In Library
|
statusBadge = `<div class="deleted-badge">
|
||||||
<div class="local-path">${localPath}</div>
|
<i class="fas fa-exclamation-circle"></i> Deleted from Civitai
|
||||||
</div>` :
|
</div>`;
|
||||||
`<div class="missing-badge">
|
} else {
|
||||||
<i class="fas fa-exclamation-triangle"></i> Not in Library
|
statusBadge = existsLocally ?
|
||||||
</div>`;
|
`<div class="local-badge">
|
||||||
|
<i class="fas fa-check"></i> In Library
|
||||||
|
<div class="local-path">${localPath}</div>
|
||||||
|
</div>` :
|
||||||
|
`<div class="missing-badge">
|
||||||
|
<i class="fas fa-exclamation-triangle"></i> Not in Library
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
// Format size if available
|
// Format size if available
|
||||||
const sizeDisplay = lora.size ?
|
const sizeDisplay = lora.size ?
|
||||||
`<div class="size-badge">${this.formatFileSize(lora.size)}</div>` : '';
|
`<div class="size-badge">${this.formatFileSize(lora.size)}</div>` : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="lora-item ${existsLocally ? 'exists-locally' : 'missing-locally'}">
|
<div class="lora-item ${existsLocally ? 'exists-locally' : isDeleted ? 'is-deleted' : 'missing-locally'}">
|
||||||
<div class="lora-thumbnail">
|
<div class="lora-thumbnail">
|
||||||
<img src="${lora.thumbnailUrl || '/loras_static/images/no-preview.png'}" alt="LoRA preview">
|
<img src="${lora.thumbnailUrl || '/loras_static/images/no-preview.png'}" alt="LoRA preview">
|
||||||
</div>
|
</div>
|
||||||
<div class="lora-content">
|
<div class="lora-content">
|
||||||
<div class="lora-header">
|
<div class="lora-header">
|
||||||
<h3>${lora.name}</h3>
|
<h3>${lora.name}</h3>
|
||||||
${localStatus}
|
${statusBadge}
|
||||||
</div>
|
</div>
|
||||||
${lora.version ? `<div class="lora-version">${lora.version}</div>` : ''}
|
${lora.version ? `<div class="lora-version">${lora.version}</div>` : ''}
|
||||||
<div class="lora-info">
|
<div class="lora-info">
|
||||||
@@ -301,12 +309,55 @@ export class ImportManager {
|
|||||||
const nextButton = document.querySelector('#detailsStep .primary-btn');
|
const nextButton = document.querySelector('#detailsStep .primary-btn');
|
||||||
if (!nextButton) return;
|
if (!nextButton) return;
|
||||||
|
|
||||||
// If we have missing LoRAs, show "Download Missing LoRAs"
|
// Count deleted LoRAs
|
||||||
// Otherwise show "Save Recipe"
|
const deletedLoras = this.recipeData.loras.filter(lora => lora.isDeleted).length;
|
||||||
if (this.missingLoras.length > 0) {
|
|
||||||
nextButton.textContent = 'Download Missing LoRAs';
|
// If we have deleted LoRAs, show a warning and update button text
|
||||||
|
if (deletedLoras > 0) {
|
||||||
|
// Remove any existing warning
|
||||||
|
const existingWarning = document.getElementById('deletedLorasWarning');
|
||||||
|
if (existingWarning) {
|
||||||
|
existingWarning.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new warning container above the buttons
|
||||||
|
const buttonsContainer = document.querySelector('#detailsStep .modal-actions') || nextButton.parentNode;
|
||||||
|
const warningContainer = document.createElement('div');
|
||||||
|
warningContainer.id = 'deletedLorasWarning';
|
||||||
|
warningContainer.className = 'deleted-loras-warning';
|
||||||
|
|
||||||
|
// Create warning message
|
||||||
|
warningContainer.innerHTML = `
|
||||||
|
<div class="warning-icon"><i class="fas fa-exclamation-triangle"></i></div>
|
||||||
|
<div class="warning-content">
|
||||||
|
<div class="warning-title">${deletedLoras} LoRA(s) have been deleted from Civitai</div>
|
||||||
|
<div class="warning-text">These LoRAs cannot be downloaded. If you continue, they will be removed from the recipe.</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Insert before the buttons container
|
||||||
|
buttonsContainer.parentNode.insertBefore(warningContainer, buttonsContainer);
|
||||||
|
|
||||||
|
// Update next button text to be more clear
|
||||||
|
nextButton.textContent = 'Continue Without Deleted LoRAs';
|
||||||
} else {
|
} else {
|
||||||
nextButton.textContent = 'Save Recipe';
|
// Remove warning if no deleted LoRAs
|
||||||
|
const warningMsg = document.getElementById('deletedLorasWarning');
|
||||||
|
if (warningMsg) {
|
||||||
|
warningMsg.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have missing LoRAs (not deleted), show "Download Missing LoRAs"
|
||||||
|
// Otherwise show "Save Recipe"
|
||||||
|
const missingNotDeleted = this.recipeData.loras.filter(
|
||||||
|
lora => !lora.existsLocally && !lora.isDeleted
|
||||||
|
).length;
|
||||||
|
|
||||||
|
if (missingNotDeleted > 0) {
|
||||||
|
nextButton.textContent = 'Download Missing LoRAs';
|
||||||
|
} else {
|
||||||
|
nextButton.textContent = 'Save Recipe';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,8 +407,23 @@ export class ImportManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have missing LoRAs, go to location step
|
// Automatically mark all deleted LoRAs as excluded
|
||||||
|
if (this.recipeData && this.recipeData.loras) {
|
||||||
|
this.recipeData.loras.forEach(lora => {
|
||||||
|
if (lora.isDeleted) {
|
||||||
|
lora.exclude = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update missing LoRAs list to exclude deleted LoRAs
|
||||||
|
this.missingLoras = this.recipeData.loras.filter(lora =>
|
||||||
|
!lora.existsLocally && !lora.isDeleted);
|
||||||
|
|
||||||
|
// If we have downloadable missing LoRAs, go to location step
|
||||||
if (this.missingLoras.length > 0) {
|
if (this.missingLoras.length > 0) {
|
||||||
|
// Store only downloadable LoRAs for the download step
|
||||||
|
this.downloadableLoRAs = this.missingLoras;
|
||||||
this.proceedToLocation();
|
this.proceedToLocation();
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, save the recipe directly
|
// Otherwise, save the recipe directly
|
||||||
@@ -396,9 +462,9 @@ export class ImportManager {
|
|||||||
try {
|
try {
|
||||||
// Display missing LoRAs that will be downloaded
|
// Display missing LoRAs that will be downloaded
|
||||||
const missingLorasList = document.getElementById('missingLorasList');
|
const missingLorasList = document.getElementById('missingLorasList');
|
||||||
if (missingLorasList && this.missingLoras.length > 0) {
|
if (missingLorasList && this.downloadableLoRAs.length > 0) {
|
||||||
// Calculate total size
|
// Calculate total size
|
||||||
const totalSize = this.missingLoras.reduce((sum, lora) => {
|
const totalSize = this.downloadableLoRAs.reduce((sum, lora) => {
|
||||||
return sum + (lora.size ? parseInt(lora.size) : 0);
|
return sum + (lora.size ? parseInt(lora.size) : 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
@@ -411,11 +477,11 @@ export class ImportManager {
|
|||||||
// Update header to include count of missing LoRAs
|
// Update header to include count of missing LoRAs
|
||||||
const missingLorasHeader = document.querySelector('.summary-header h3');
|
const missingLorasHeader = document.querySelector('.summary-header h3');
|
||||||
if (missingLorasHeader) {
|
if (missingLorasHeader) {
|
||||||
missingLorasHeader.innerHTML = `Missing LoRAs <span class="lora-count-badge">(${this.missingLoras.length})</span> <span id="totalDownloadSize" class="total-size-badge">${this.formatFileSize(totalSize)}</span>`;
|
missingLorasHeader.innerHTML = `Missing LoRAs <span class="lora-count-badge">(${this.downloadableLoRAs.length})</span> <span id="totalDownloadSize" class="total-size-badge">${this.formatFileSize(totalSize)}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate missing LoRAs list
|
// Generate missing LoRAs list
|
||||||
missingLorasList.innerHTML = this.missingLoras.map(lora => {
|
missingLorasList.innerHTML = this.downloadableLoRAs.map(lora => {
|
||||||
const sizeDisplay = lora.size ? this.formatFileSize(lora.size) : 'Unknown size';
|
const sizeDisplay = lora.size ? this.formatFileSize(lora.size) : 'Unknown size';
|
||||||
const baseModel = lora.baseModel ? `<span class="lora-base-model">${lora.baseModel}</span>` : '';
|
const baseModel = lora.baseModel ? `<span class="lora-base-model">${lora.baseModel}</span>` : '';
|
||||||
|
|
||||||
@@ -538,7 +604,7 @@ export class ImportManager {
|
|||||||
|
|
||||||
|
|
||||||
// Check if we need to download LoRAs
|
// Check if we need to download LoRAs
|
||||||
if (this.missingLoras.length > 0) {
|
if (this.downloadableLoRAs && this.downloadableLoRAs.length > 0) {
|
||||||
// For download, we need to validate the target path
|
// For download, we need to validate the target path
|
||||||
const loraRoot = document.getElementById('importLoraRoot')?.value;
|
const loraRoot = document.getElementById('importLoraRoot')?.value;
|
||||||
if (!loraRoot) {
|
if (!loraRoot) {
|
||||||
@@ -561,7 +627,7 @@ export class ImportManager {
|
|||||||
const ws = new WebSocket(`${wsProtocol}${window.location.host}/ws/fetch-progress`);
|
const ws = new WebSocket(`${wsProtocol}${window.location.host}/ws/fetch-progress`);
|
||||||
|
|
||||||
// Show enhanced loading with progress details for multiple items
|
// Show enhanced loading with progress details for multiple items
|
||||||
const updateProgress = this.loadingManager.showDownloadProgress(this.missingLoras.length);
|
const updateProgress = this.loadingManager.showDownloadProgress(this.downloadableLoRAs.length);
|
||||||
|
|
||||||
let completedDownloads = 0;
|
let completedDownloads = 0;
|
||||||
let currentLoraProgress = 0;
|
let currentLoraProgress = 0;
|
||||||
@@ -574,7 +640,7 @@ export class ImportManager {
|
|||||||
currentLoraProgress = data.progress;
|
currentLoraProgress = data.progress;
|
||||||
|
|
||||||
// Get current LoRA name
|
// Get current LoRA name
|
||||||
const currentLora = this.missingLoras[completedDownloads];
|
const currentLora = this.downloadableLoRAs[completedDownloads];
|
||||||
const loraName = currentLora ? currentLora.name : '';
|
const loraName = currentLora ? currentLora.name : '';
|
||||||
|
|
||||||
// Update progress display
|
// Update progress display
|
||||||
@@ -583,32 +649,32 @@ export class ImportManager {
|
|||||||
// Add more detailed status messages based on progress
|
// Add more detailed status messages based on progress
|
||||||
if (currentLoraProgress < 3) {
|
if (currentLoraProgress < 3) {
|
||||||
this.loadingManager.setStatus(
|
this.loadingManager.setStatus(
|
||||||
`Preparing download for LoRA ${completedDownloads+1}/${this.missingLoras.length}`
|
`Preparing download for LoRA ${completedDownloads+1}/${this.downloadableLoRAs.length}`
|
||||||
);
|
);
|
||||||
} else if (currentLoraProgress === 3) {
|
} else if (currentLoraProgress === 3) {
|
||||||
this.loadingManager.setStatus(
|
this.loadingManager.setStatus(
|
||||||
`Downloaded preview for LoRA ${completedDownloads+1}/${this.missingLoras.length}`
|
`Downloaded preview for LoRA ${completedDownloads+1}/${this.downloadableLoRAs.length}`
|
||||||
);
|
);
|
||||||
} else if (currentLoraProgress > 3 && currentLoraProgress < 100) {
|
} else if (currentLoraProgress > 3 && currentLoraProgress < 100) {
|
||||||
this.loadingManager.setStatus(
|
this.loadingManager.setStatus(
|
||||||
`Downloading LoRA ${completedDownloads+1}/${this.missingLoras.length}`
|
`Downloading LoRA ${completedDownloads+1}/${this.downloadableLoRAs.length}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.loadingManager.setStatus(
|
this.loadingManager.setStatus(
|
||||||
`Finalizing LoRA ${completedDownloads+1}/${this.missingLoras.length}`
|
`Finalizing LoRA ${completedDownloads+1}/${this.downloadableLoRAs.length}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let i = 0; i < this.missingLoras.length; i++) {
|
for (let i = 0; i < this.downloadableLoRAs.length; i++) {
|
||||||
const lora = this.missingLoras[i];
|
const lora = this.downloadableLoRAs[i];
|
||||||
|
|
||||||
// Reset current LoRA progress for new download
|
// Reset current LoRA progress for new download
|
||||||
currentLoraProgress = 0;
|
currentLoraProgress = 0;
|
||||||
|
|
||||||
// Initial status update for new LoRA
|
// Initial status update for new LoRA
|
||||||
this.loadingManager.setStatus(`Starting download for LoRA ${i+1}/${this.missingLoras.length}`);
|
this.loadingManager.setStatus(`Starting download for LoRA ${i+1}/${this.downloadableLoRAs.length}`);
|
||||||
updateProgress(0, completedDownloads, lora.name);
|
updateProgress(0, completedDownloads, lora.name);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -633,9 +699,9 @@ export class ImportManager {
|
|||||||
// Update progress to show completion of current LoRA
|
// Update progress to show completion of current LoRA
|
||||||
updateProgress(100, completedDownloads, '');
|
updateProgress(100, completedDownloads, '');
|
||||||
|
|
||||||
if (completedDownloads < this.missingLoras.length) {
|
if (completedDownloads < this.downloadableLoRAs.length) {
|
||||||
this.loadingManager.setStatus(
|
this.loadingManager.setStatus(
|
||||||
`Completed ${completedDownloads}/${this.missingLoras.length} LoRAs. Starting next download...`
|
`Completed ${completedDownloads}/${this.downloadableLoRAs.length} LoRAs. Starting next download...`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -649,10 +715,10 @@ export class ImportManager {
|
|||||||
ws.close();
|
ws.close();
|
||||||
|
|
||||||
// Show final completion message
|
// Show final completion message
|
||||||
if (completedDownloads === this.missingLoras.length) {
|
if (completedDownloads === this.downloadableLoRAs.length) {
|
||||||
showToast(`All ${completedDownloads} LoRAs downloaded successfully`, 'success');
|
showToast(`All ${completedDownloads} LoRAs downloaded successfully`, 'success');
|
||||||
} else {
|
} else {
|
||||||
showToast(`Downloaded ${completedDownloads} of ${this.missingLoras.length} LoRAs`, 'warning');
|
showToast(`Downloaded ${completedDownloads} of ${this.downloadableLoRAs.length} LoRAs`, 'warning');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user