From af713470c12b05395f52b4b47c02f03ec6af7c67 Mon Sep 17 00:00:00 2001 From: pixelpaws Date: Thu, 25 Sep 2025 15:59:44 +0800 Subject: [PATCH] fix(recipes): parse loras from civitai hashes --- py/recipes/parsers/civitai_image.py | 54 +++++++++++++++++- tests/services/test_civitai_image_parser.py | 61 +++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 tests/services/test_civitai_image_parser.py diff --git a/py/recipes/parsers/civitai_image.py b/py/recipes/parsers/civitai_image.py index 31234ab9..409c5fa3 100644 --- a/py/recipes/parsers/civitai_image.py +++ b/py/recipes/parsers/civitai_image.py @@ -284,7 +284,59 @@ class CivitaiApiMetadataParser(RecipeMetadataParser): logger.error(f"Error fetching Civitai info for model ID {version_id}: {e}") result["loras"].append(lora_entry) - + + # If we found LoRA hashes in the metadata but haven't already + # populated entries for them, fall back to creating LoRAs from + # the hashes section. Some Civitai image responses only include + # LoRA information here without explicit resources entries. + for lora_name, lora_hash in lora_hashes.items(): + if not lora_hash: + continue + + # Skip LoRAs we've already added via resources or other fields + if lora_hash in added_loras: + continue + + lora_entry = { + 'name': lora_name, + 'type': "lora", + 'weight': 1.0, + 'hash': lora_hash, + 'existsLocally': False, + 'localPath': None, + 'file_name': lora_name, + 'thumbnailUrl': '/loras_static/images/no-preview.png', + 'baseModel': '', + 'size': 0, + 'downloadUrl': '', + 'isDeleted': False + } + + if metadata_provider: + try: + civitai_info = await metadata_provider.get_model_by_hash(lora_hash) + + populated_entry = await self.populate_lora_from_civitai( + lora_entry, + civitai_info, + recipe_scanner, + base_model_counts, + lora_hash + ) + + if populated_entry is None: + continue + + lora_entry = populated_entry + + if 'id' in lora_entry and lora_entry['id']: + added_loras[str(lora_entry['id'])] = len(result["loras"]) + except Exception as e: + logger.error(f"Error fetching Civitai info for LoRA hash {lora_hash}: {e}") + + added_loras[lora_hash] = len(result["loras"]) + result["loras"].append(lora_entry) + # Check for LoRA info in the format "Lora_0 Model hash", "Lora_0 Model name", etc. lora_index = 0 while f"Lora_{lora_index} Model hash" in metadata and f"Lora_{lora_index} Model name" in metadata: diff --git a/tests/services/test_civitai_image_parser.py b/tests/services/test_civitai_image_parser.py new file mode 100644 index 00000000..54353336 --- /dev/null +++ b/tests/services/test_civitai_image_parser.py @@ -0,0 +1,61 @@ +import pytest + +from py.recipes.parsers.civitai_image import CivitaiApiMetadataParser + + +@pytest.mark.asyncio +async def test_parse_metadata_creates_loras_from_hashes(monkeypatch): + async def fake_metadata_provider(): + return None + + monkeypatch.setattr( + "py.recipes.parsers.civitai_image.get_default_metadata_provider", + fake_metadata_provider, + ) + + parser = CivitaiApiMetadataParser() + + metadata = { + "Size": "1536x2688", + "seed": 3766932689, + "Model": "indexed_v1", + "steps": 30, + "hashes": { + "model": "692186a14a", + "LORA:Jedst1": "fb4063c470", + "LORA:HassaKu_style": "3ce00b926b", + "LORA:DetailedEyes_V3": "2c1c3f889f", + "LORA:jiaocha_illustriousXL": "35d3e6f8b0", + "LORA:绪儿 厚涂构图光影质感增强V3": "d9b5900a59", + }, + "prompt": "test", + "Version": "ComfyUI", + "sampler": "er_sde_ays_30", + "cfgScale": 5, + "clipSkip": 2, + "resources": [ + { + "hash": "692186a14a", + "name": "indexed_v1", + "type": "model", + } + ], + "Model hash": "692186a14a", + "negativePrompt": "bad", + "username": "LumaRift", + "baseModel": "Illustrious", + } + + result = await parser.parse_metadata(metadata) + + assert result["base_model"] == "Illustrious" + assert len(result["loras"]) == 5 + assert all(lora["weight"] == 1.0 for lora in result["loras"]) + assert {lora["name"] for lora in result["loras"]} == { + "Jedst1", + "HassaKu_style", + "DetailedEyes_V3", + "jiaocha_illustriousXL", + "绪儿 厚涂构图光影质感增强V3", + } +