mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-29 08:51:27 -03:00
fix(recipes): distinguish unobtainable LoRAs in recipe status and skip them in syntax
The recipe card pill counted LoRAs deleted from the source (isDeleted) as available, showing a green 'ready 2/2' for recipes that cannot be fully reproduced. LoRAs with an unresolvable hash (hashInvalid) were counted as missing/downloadable even though downloads always fail, and recipe syntax generation emitted broken tokens for them. - Four-state status on RecipeCard pill and RecipeTab badge: ready (all in library), missing (downloadable, red, keeps the action cue), partial (unobtainable entries skipped when used, amber, fa-circle-minus), unavailable (nothing usable, gray, fa-ban) - Pill numerator is now the real in-library count; tooltips spell out missing vs unavailable (deleted from source or unresolvable hash) - get_recipe_syntax_tokens skips hashInvalid entries like deleted ones instead of emitting tokens pointing at nonexistent files - Bulk missing-download manager and recipe context menu exclude hashInvalid LoRAs, matching the modal's per-item download block - New locale keys loraStatus.missingAndUnavailable/partial/noneUsable, translated for all 9 non-en locales
This commit is contained in:
@@ -41,15 +41,35 @@ class RecipeCard {
|
||||
const loras = this.recipe.loras || [];
|
||||
const lorasCount = loras.length;
|
||||
|
||||
// Check if all LoRAs are available in the library
|
||||
const missingLorasCount = loras.filter(lora => !lora.inLibrary && !lora.isDeleted).length;
|
||||
const allLorasAvailable = missingLorasCount === 0 && lorasCount > 0;
|
||||
// Count LoRAs by availability: in library, missing (still downloadable
|
||||
// from the source), or unobtainable (deleted from the source, or an
|
||||
// unresolvable hash) which is silently skipped when the recipe is used.
|
||||
const availableLorasCount = loras.filter(lora => lora.inLibrary).length;
|
||||
const missingLorasCount = loras.filter(lora => !lora.inLibrary && !lora.isDeleted && !lora.hashInvalid).length;
|
||||
const unavailableLorasCount = lorasCount - availableLorasCount - missingLorasCount;
|
||||
|
||||
// Compact status pill: state icon + available/total fraction.
|
||||
// Icon switches by state so status never relies on color alone.
|
||||
const availableLorasCount = lorasCount - missingLorasCount;
|
||||
const loraCountStateClass = missingLorasCount > 0 ? 'missing' : (allLorasAvailable ? 'ready' : '');
|
||||
const loraCountIcon = missingLorasCount > 0 ? 'fa-exclamation-triangle' : (allLorasAvailable ? 'fa-check' : 'fa-layer-group');
|
||||
// - missing (red): something can still be downloaded, most actionable
|
||||
// - partial (amber): usable but degraded, unobtainable LoRAs are skipped
|
||||
// - unavailable (gray, ban): no usable LoRA at all
|
||||
let loraCountStateClass = '';
|
||||
let loraCountIcon = 'fa-layer-group';
|
||||
if (lorasCount > 0) {
|
||||
if (availableLorasCount === lorasCount) {
|
||||
loraCountStateClass = 'ready';
|
||||
loraCountIcon = 'fa-check';
|
||||
} else if (missingLorasCount > 0) {
|
||||
loraCountStateClass = 'missing';
|
||||
loraCountIcon = 'fa-exclamation-triangle';
|
||||
} else if (availableLorasCount > 0) {
|
||||
loraCountStateClass = 'partial';
|
||||
loraCountIcon = 'fa-circle-minus';
|
||||
} else {
|
||||
loraCountStateClass = 'unavailable';
|
||||
loraCountIcon = 'fa-ban';
|
||||
}
|
||||
}
|
||||
const loraCountLabel = lorasCount > 0 ? `${availableLorasCount}/${lorasCount}` : `${lorasCount}`;
|
||||
|
||||
// Ensure file_url exists, fallback to API URL if needed
|
||||
@@ -135,7 +155,7 @@ class RecipeCard {
|
||||
<span class="model-name">${this.recipe.title}</span>
|
||||
</div>
|
||||
${!isDuplicatesMode ? `
|
||||
<div class="lora-count ${loraCountStateClass}" title="${this.getLoraStatusTitle(lorasCount, missingLorasCount)}">
|
||||
<div class="lora-count ${loraCountStateClass}" title="${this.getLoraStatusTitle(lorasCount, availableLorasCount, missingLorasCount, unavailableLorasCount)}">
|
||||
<i class="fas ${loraCountIcon}" aria-hidden="true"></i> ${loraCountLabel}
|
||||
</div>
|
||||
` : ''}
|
||||
@@ -154,17 +174,38 @@ class RecipeCard {
|
||||
return card;
|
||||
}
|
||||
|
||||
getLoraStatusTitle(totalCount, missingCount) {
|
||||
getLoraStatusTitle(totalCount, availableCount, missingCount, unavailableCount) {
|
||||
if (totalCount === 0) {
|
||||
return translate('recipes.loraStatus.none', {}, 'No LoRAs in this recipe');
|
||||
}
|
||||
if (missingCount === 0) {
|
||||
if (availableCount === totalCount) {
|
||||
return translate('recipes.loraStatus.allAvailable', {}, 'All LoRAs available - Ready to use');
|
||||
}
|
||||
if (missingCount > 0 && unavailableCount > 0) {
|
||||
return translate(
|
||||
'recipes.loraStatus.missingAndUnavailable',
|
||||
{ missing: missingCount, unavailable: unavailableCount, total: totalCount },
|
||||
`${missingCount} of ${totalCount} LoRAs missing, ${unavailableCount} unavailable (deleted from source or unresolvable hash)`
|
||||
);
|
||||
}
|
||||
if (missingCount > 0) {
|
||||
return translate(
|
||||
'recipes.loraStatus.missing',
|
||||
{ missing: missingCount, total: totalCount },
|
||||
`${missingCount} of ${totalCount} LoRAs missing`
|
||||
);
|
||||
}
|
||||
if (availableCount > 0) {
|
||||
return translate(
|
||||
'recipes.loraStatus.partial',
|
||||
{ unavailable: unavailableCount, total: totalCount },
|
||||
`${unavailableCount} of ${totalCount} LoRAs unavailable (deleted from source or unresolvable hash) - skipped when recipe is used`
|
||||
);
|
||||
}
|
||||
return translate(
|
||||
'recipes.loraStatus.missing',
|
||||
{ missing: missingCount, total: totalCount },
|
||||
`${missingCount} of ${totalCount} LoRAs missing`
|
||||
'recipes.loraStatus.noneUsable',
|
||||
{ unavailable: unavailableCount, total: totalCount },
|
||||
`No usable LoRAs - ${unavailableCount} of ${totalCount} deleted from source or unresolvable hash`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user