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:
@@ -331,6 +331,49 @@ async def test_update_lora_entry_updates_cache_and_file(tmp_path: Path, recipe_s
|
||||
assert cached_recipe["fingerprint"] == expected_fingerprint
|
||||
|
||||
|
||||
async def test_set_lora_entry_hash_invalid_persists_flag(tmp_path: Path, recipe_scanner):
|
||||
scanner, _ = recipe_scanner
|
||||
recipes_dir = Path(config.loras_roots[0]) / "recipes"
|
||||
recipes_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
recipe_id = "hash-invalid-1"
|
||||
recipe_path = recipes_dir / f"{recipe_id}.recipe.json"
|
||||
recipe_data = {
|
||||
"id": recipe_id,
|
||||
"file_path": str(tmp_path / "image.png"),
|
||||
"title": "Hash invalid",
|
||||
"modified": 0.0,
|
||||
"created_date": 0.0,
|
||||
"loras": [
|
||||
{
|
||||
"file_name": "Daphne Blake Cosplay_v1",
|
||||
"strength": 1.0,
|
||||
"hash": "a2a12bfa01",
|
||||
},
|
||||
],
|
||||
}
|
||||
recipe_path.write_text(json.dumps(recipe_data))
|
||||
await scanner.add_recipe(dict(recipe_data))
|
||||
|
||||
updated_recipe, updated_lora = await scanner.set_lora_entry_hash_invalid(
|
||||
recipe_id, 0, True
|
||||
)
|
||||
|
||||
assert updated_lora["hashInvalid"] is True
|
||||
assert updated_recipe["loras"][0]["hashInvalid"] is True
|
||||
with recipe_path.open("r", encoding="utf-8") as file_obj:
|
||||
persisted = json.load(file_obj)
|
||||
assert persisted["loras"][0]["hashInvalid"] is True
|
||||
assert persisted["loras"][0]["hash"] == "a2a12bfa01"
|
||||
|
||||
cache = await scanner.get_cached_data()
|
||||
cached_recipe = next(item for item in cache.raw_data if item["id"] == recipe_id)
|
||||
assert cached_recipe["loras"][0]["hashInvalid"] is True
|
||||
|
||||
_, cleared_lora = await scanner.set_lora_entry_hash_invalid(recipe_id, 0, False)
|
||||
assert cleared_lora["hashInvalid"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_recipe_rewrites_missing_image_path(tmp_path: Path, recipe_scanner):
|
||||
scanner, _ = recipe_scanner
|
||||
@@ -2212,6 +2255,24 @@ async def test_is_rematch_candidate_rejects_non_dict(tmp_path: Path):
|
||||
assert not scanner._is_rematch_candidate(malformed)
|
||||
|
||||
|
||||
async def test_is_rematch_candidate_hash_invalid_passes(tmp_path: Path):
|
||||
scanner, _, _ = _make_rematch_scanner([], [], tmp_path)
|
||||
assert scanner._is_rematch_candidate(
|
||||
{"hash": "abc", "file_name": "m.safetensors", "hashInvalid": True}
|
||||
)
|
||||
|
||||
|
||||
async def test_is_rematch_candidate_healthy_not_in_library_rejected(tmp_path: Path):
|
||||
# A healthy entry whose hash is simply absent from the local library
|
||||
# (recipe imported without downloading the model) must not become a
|
||||
# candidate: its CivitAI-valid hash would be at risk of being
|
||||
# overwritten by the imprecise filename fallback.
|
||||
scanner, _, _ = _make_rematch_scanner([], [], tmp_path)
|
||||
assert not scanner._is_rematch_candidate(
|
||||
{"hash": "abc", "file_name": "m.safetensors", "hashInvalid": False}
|
||||
)
|
||||
|
||||
|
||||
# _match_rematch_entry — L1 hash-cache lookup
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user