mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 14:42:11 -03:00
feat(recipes): expose recipe scanner mutation apis
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
"""Services encapsulating recipe persistence workflows."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
@@ -123,7 +122,7 @@ class RecipePersistenceService:
|
||||
self._exif_utils.append_recipe_metadata(image_path, recipe_data)
|
||||
|
||||
matching_recipes = await self._find_matching_recipes(recipe_scanner, fingerprint, exclude_id=recipe_id)
|
||||
await self._update_cache(recipe_scanner, recipe_data)
|
||||
await recipe_scanner.add_recipe(recipe_data)
|
||||
|
||||
return PersistenceResult(
|
||||
{
|
||||
@@ -154,7 +153,7 @@ class RecipePersistenceService:
|
||||
if image_path and os.path.exists(image_path):
|
||||
os.remove(image_path)
|
||||
|
||||
await self._remove_from_cache(recipe_scanner, recipe_id)
|
||||
await recipe_scanner.remove_recipe(recipe_id)
|
||||
return PersistenceResult({"success": True, "message": "Recipe deleted successfully"})
|
||||
|
||||
async def update_recipe(self, *, recipe_scanner, recipe_id: str, updates: dict[str, Any]) -> PersistenceResult:
|
||||
@@ -185,40 +184,16 @@ class RecipePersistenceService:
|
||||
if not os.path.exists(recipe_path):
|
||||
raise RecipeNotFoundError("Recipe not found")
|
||||
|
||||
lora_scanner = getattr(recipe_scanner, "_lora_scanner", None)
|
||||
target_lora = None if lora_scanner is None else await lora_scanner.get_model_info_by_name(target_name)
|
||||
target_lora = await recipe_scanner.get_local_lora(target_name)
|
||||
if not target_lora:
|
||||
raise RecipeNotFoundError(f"Local LoRA not found with name: {target_name}")
|
||||
|
||||
with open(recipe_path, "r", encoding="utf-8") as file_obj:
|
||||
recipe_data = json.load(file_obj)
|
||||
|
||||
loras = recipe_data.get("loras", [])
|
||||
if lora_index >= len(loras):
|
||||
raise RecipeNotFoundError("LoRA index out of range in recipe")
|
||||
|
||||
lora = loras[lora_index]
|
||||
lora["isDeleted"] = False
|
||||
lora["exclude"] = False
|
||||
lora["file_name"] = target_name
|
||||
if "sha256" in target_lora:
|
||||
lora["hash"] = target_lora["sha256"].lower()
|
||||
if target_lora.get("civitai"):
|
||||
lora["modelName"] = target_lora["civitai"]["model"]["name"]
|
||||
lora["modelVersionName"] = target_lora["civitai"]["name"]
|
||||
lora["modelVersionId"] = target_lora["civitai"]["id"]
|
||||
|
||||
recipe_data["fingerprint"] = calculate_recipe_fingerprint(recipe_data.get("loras", []))
|
||||
|
||||
with open(recipe_path, "w", encoding="utf-8") as file_obj:
|
||||
json.dump(recipe_data, file_obj, indent=4, ensure_ascii=False)
|
||||
|
||||
updated_lora = dict(lora)
|
||||
updated_lora["inLibrary"] = True
|
||||
updated_lora["preview_url"] = config.get_preview_static_url(target_lora["preview_url"])
|
||||
updated_lora["localPath"] = target_lora["file_path"]
|
||||
|
||||
await self._refresh_cache_after_update(recipe_scanner, recipe_id, recipe_data)
|
||||
recipe_data, updated_lora = await recipe_scanner.update_lora_entry(
|
||||
recipe_id,
|
||||
lora_index,
|
||||
target_name=target_name,
|
||||
target_lora=target_lora,
|
||||
)
|
||||
|
||||
image_path = recipe_data.get("file_path")
|
||||
if image_path and os.path.exists(image_path):
|
||||
@@ -276,7 +251,7 @@ class RecipePersistenceService:
|
||||
failed_recipes.append({"id": recipe_id, "reason": str(exc)})
|
||||
|
||||
if deleted_recipes:
|
||||
await self._bulk_remove_from_cache(recipe_scanner, deleted_recipes)
|
||||
await recipe_scanner.bulk_remove(deleted_recipes)
|
||||
|
||||
return PersistenceResult(
|
||||
{
|
||||
@@ -314,14 +289,11 @@ class RecipePersistenceService:
|
||||
if not lora_matches:
|
||||
raise RecipeValidationError("No LoRAs found in the generation metadata")
|
||||
|
||||
lora_scanner = getattr(recipe_scanner, "_lora_scanner", None)
|
||||
loras_data = []
|
||||
base_model_counts: Dict[str, int] = {}
|
||||
|
||||
for name, strength in lora_matches:
|
||||
lora_info = None
|
||||
if lora_scanner is not None:
|
||||
lora_info = await lora_scanner.get_model_info_by_name(name)
|
||||
lora_info = await recipe_scanner.get_local_lora(name)
|
||||
lora_data = {
|
||||
"file_name": name,
|
||||
"strength": float(strength),
|
||||
@@ -366,7 +338,7 @@ class RecipePersistenceService:
|
||||
json.dump(recipe_data, file_obj, indent=4, ensure_ascii=False)
|
||||
|
||||
self._exif_utils.append_recipe_metadata(image_path, recipe_data)
|
||||
await self._update_cache(recipe_scanner, recipe_data)
|
||||
await recipe_scanner.add_recipe(recipe_data)
|
||||
|
||||
return PersistenceResult(
|
||||
{
|
||||
@@ -422,45 +394,6 @@ class RecipePersistenceService:
|
||||
matches.remove(exclude_id)
|
||||
return matches
|
||||
|
||||
async def _update_cache(self, recipe_scanner, recipe_data: dict[str, Any]) -> None:
|
||||
cache = getattr(recipe_scanner, "_cache", None)
|
||||
if cache is not None:
|
||||
cache.raw_data.append(recipe_data)
|
||||
asyncio.create_task(cache.resort())
|
||||
self._logger.info("Added recipe %s to cache", recipe_data.get("id"))
|
||||
|
||||
async def _remove_from_cache(self, recipe_scanner, recipe_id: str) -> None:
|
||||
cache = getattr(recipe_scanner, "_cache", None)
|
||||
if cache is not None:
|
||||
cache.raw_data = [item for item in cache.raw_data if str(item.get("id", "")) != recipe_id]
|
||||
asyncio.create_task(cache.resort())
|
||||
self._logger.info("Removed recipe %s from cache", recipe_id)
|
||||
|
||||
async def _bulk_remove_from_cache(self, recipe_scanner, recipe_ids: Iterable[str]) -> None:
|
||||
cache = getattr(recipe_scanner, "_cache", None)
|
||||
if cache is not None:
|
||||
recipe_ids_set = set(recipe_ids)
|
||||
cache.raw_data = [item for item in cache.raw_data if item.get("id") not in recipe_ids_set]
|
||||
asyncio.create_task(cache.resort())
|
||||
self._logger.info("Removed %s recipes from cache", len(recipe_ids_set))
|
||||
|
||||
async def _refresh_cache_after_update(
|
||||
self,
|
||||
recipe_scanner,
|
||||
recipe_id: str,
|
||||
recipe_data: dict[str, Any],
|
||||
) -> None:
|
||||
cache = getattr(recipe_scanner, "_cache", None)
|
||||
if cache is not None:
|
||||
for cache_item in cache.raw_data:
|
||||
if cache_item.get("id") == recipe_id:
|
||||
cache_item.update({
|
||||
"loras": recipe_data.get("loras", []),
|
||||
"fingerprint": recipe_data.get("fingerprint"),
|
||||
})
|
||||
asyncio.create_task(cache.resort())
|
||||
break
|
||||
|
||||
def _derive_recipe_name(self, lora_matches: list[tuple[str, str]]) -> str:
|
||||
recipe_name_parts = [f"{name.strip()}-{float(strength):.2f}" for name, strength in lora_matches[:3]]
|
||||
recipe_name = "_".join(recipe_name_parts)
|
||||
|
||||
@@ -38,11 +38,7 @@ class RecipeSharingService:
|
||||
async def share_recipe(self, *, recipe_scanner, recipe_id: str) -> SharingResult:
|
||||
"""Prepare a temporary downloadable copy of a recipe image."""
|
||||
|
||||
cache = await recipe_scanner.get_cached_data()
|
||||
recipe = next(
|
||||
(r for r in getattr(cache, "raw_data", []) if str(r.get("id", "")) == recipe_id),
|
||||
None,
|
||||
)
|
||||
recipe = await recipe_scanner.get_recipe_by_id(recipe_id)
|
||||
if not recipe:
|
||||
raise RecipeNotFoundError("Recipe not found")
|
||||
|
||||
@@ -81,11 +77,7 @@ class RecipeSharingService:
|
||||
self._cleanup_entry(recipe_id)
|
||||
raise RecipeNotFoundError("Shared recipe file not found")
|
||||
|
||||
cache = await recipe_scanner.get_cached_data()
|
||||
recipe = next(
|
||||
(r for r in getattr(cache, "raw_data", []) if str(r.get("id", "")) == recipe_id),
|
||||
None,
|
||||
)
|
||||
recipe = await recipe_scanner.get_recipe_by_id(recipe_id)
|
||||
filename_base = (
|
||||
f"recipe_{recipe.get('title', '').replace(' ', '_').lower()}" if recipe else recipe_id
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user