mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-29 08:51:27 -03:00
fix(recipes): resolve stale LoRA hash on import and add hashInvalid state
- import: prefer A1111 Lora hashes (12-char AutoV3) over conflicting Hashes JSON values; recover the quote-wrapped AutoV3 from CivitAI image API meta; merge EXIF-parsed LoRAs when the API-only parse yields none (meta=null) - rematch: treat entries whose hash failed CivitAI resolution (hashInvalid) as unresolved candidates; clear the flag on rematch/reconnect write-back - download: persist hashInvalid and show a distinct toast when hash lookup returns "Model not found", so unresolvable entries become recoverable - ui: add Unresolvable Hash badge styling and reconnect affordance - i18n: translate the new keys across all 10 locales
This commit is contained in:
@@ -850,7 +850,8 @@
|
||||
}
|
||||
|
||||
.local-badge,
|
||||
.missing-badge {
|
||||
.missing-badge,
|
||||
.invalid-hash-badge {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
@@ -861,7 +862,8 @@
|
||||
|
||||
/* Specific styles for recipe modal badges - update z-index */
|
||||
.recipe-lora-header .local-badge,
|
||||
.recipe-lora-header .missing-badge {
|
||||
.recipe-lora-header .missing-badge,
|
||||
.recipe-lora-header .invalid-hash-badge {
|
||||
z-index: 2; /* Ensure the badge is above other elements */
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
@@ -903,6 +905,26 @@
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Unresolvable-hash badge: the entry has identity fields, but its hash is
|
||||
not registered on CivitAI (stale or invalid). */
|
||||
.invalid-hash-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: var(--lora-warning);
|
||||
color: white;
|
||||
padding: 3px 6px;
|
||||
border-radius: var(--border-radius-xs);
|
||||
font-size: 0.75em;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.invalid-hash-badge i {
|
||||
margin-right: 4px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Deleted badge is a pure status indicator; the reconnect action lives on
|
||||
an explicit ghost button in the item's action row. */
|
||||
|
||||
@@ -1124,7 +1146,8 @@
|
||||
/* Badges are pure status indicators; actions live in .recipe-lora-actions */
|
||||
.badge-container .local-badge,
|
||||
.badge-container .missing-badge,
|
||||
.badge-container .deleted-badge {
|
||||
.badge-container .deleted-badge,
|
||||
.badge-container .invalid-hash-badge {
|
||||
position: static; /* Override absolute positioning */
|
||||
transform: none; /* Remove the transform */
|
||||
}
|
||||
@@ -1150,6 +1173,12 @@
|
||||
border: 1px solid rgba(127, 127, 127, 0.35);
|
||||
}
|
||||
|
||||
.badge-container .invalid-hash-badge {
|
||||
background: oklch(var(--lora-warning) / 0.14);
|
||||
color: var(--lora-warning);
|
||||
border: 1px solid oklch(var(--lora-warning) / 0.35);
|
||||
}
|
||||
|
||||
/* Missing LoRAs status is a real button: the affordance must be visible at
|
||||
rest (persistent border), not only on hover. */
|
||||
.recipe-status.missing.clickable {
|
||||
|
||||
@@ -897,6 +897,11 @@ class RecipeModal {
|
||||
<div class="deleted-badge" title="${escapeHtml(translate('recipes.resources.deletedTooltip', {}, 'This LoRA was deleted from the source and is no longer available for download'))}">
|
||||
<i class="fas fa-trash-alt" aria-hidden="true"></i> ${escapeHtml(translate('recipes.resources.deleted', {}, 'Deleted'))}
|
||||
</div>`;
|
||||
} else if (lora.hashInvalid) {
|
||||
statusBadge = `
|
||||
<div class="invalid-hash-badge" title="${escapeHtml(translate('recipes.resources.hashInvalidTooltip', {}, 'This LoRA hash cannot be resolved on CivitAI - the model may have been updated'))}">
|
||||
<i class="fas fa-question-circle" aria-hidden="true"></i> ${escapeHtml(translate('recipes.resources.hashInvalid', {}, 'Unresolvable Hash'))}
|
||||
</div>`;
|
||||
} else {
|
||||
statusBadge = `
|
||||
<div class="missing-badge" title="${escapeHtml(translate('recipes.resources.notInLibraryTooltip', {}, 'This model is not in your library'))}">
|
||||
@@ -1982,7 +1987,7 @@ class RecipeModal {
|
||||
}
|
||||
|
||||
const controls = [];
|
||||
if (isDeleted) {
|
||||
if (isDeleted || lora.hashInvalid) {
|
||||
const reconnectLabel = translate('recipes.resources.reconnect', {}, 'Reconnect');
|
||||
const reconnectTooltip = translate('recipes.resources.reconnectTooltip', {}, 'Reconnect with a local LoRA');
|
||||
controls.push(`
|
||||
@@ -2032,7 +2037,7 @@ class RecipeModal {
|
||||
const loraIndex = parseInt(button.dataset.loraIndex, 10);
|
||||
const lora = this.currentRecipe?.loras?.[loraIndex];
|
||||
if (lora) {
|
||||
this.downloadRecipeLora(lora, button);
|
||||
this.downloadRecipeLora(lora, button, loraIndex);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -2112,7 +2117,7 @@ class RecipeModal {
|
||||
}
|
||||
}
|
||||
|
||||
async downloadRecipeLora(lora, button) {
|
||||
async downloadRecipeLora(lora, button, loraIndex) {
|
||||
if (!this.canDownloadLora(lora)) {
|
||||
showToast('toast.recipes.missingLoraDownloadInfo', {}, 'error');
|
||||
return;
|
||||
@@ -2141,7 +2146,12 @@ class RecipeModal {
|
||||
state.loadingManager.hide();
|
||||
}
|
||||
if (!identifiers) {
|
||||
showToast('toast.recipes.missingLoraDownloadInfo', {}, 'error');
|
||||
if (!hasDirectIds && lora.hash) {
|
||||
await this.markLoraHashInvalid(loraIndex);
|
||||
showToast('toast.recipes.hashNotFoundOnCivitai', {}, 'error');
|
||||
} else {
|
||||
showToast('toast.recipes.missingLoraDownloadInfo', {}, 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2170,6 +2180,33 @@ class RecipeModal {
|
||||
}
|
||||
}
|
||||
|
||||
async markLoraHashInvalid(loraIndex) {
|
||||
const recipeId =
|
||||
this.recipeId ||
|
||||
extractRecipeId(this.listFilePath || this.currentRecipe?.file_path);
|
||||
if (!recipeId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await fetch('/api/lm/recipe/lora/mark-hash-invalid', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
recipe_id: recipeId,
|
||||
lora_index: loraIndex,
|
||||
}),
|
||||
});
|
||||
if (this.currentRecipe?.loras?.[loraIndex]) {
|
||||
this.currentRecipe.loras[loraIndex].hashInvalid = true;
|
||||
this.syncResourcesSection(this.currentRecipe);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to mark LoRA hash invalid:', error);
|
||||
}
|
||||
}
|
||||
|
||||
navigateToCheckpointPage(checkpoint) {
|
||||
const checkpointHash = this._getCheckpointHash(checkpoint);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user