mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-10 12:59:24 -03:00
feat(recipe): add reimport endpoint to re-import recipe from source URL
Adds POST /api/lm/recipe/{recipe_id}/reimport that atomically:
1. Reads the existing recipe to extract source_url and user edits
2. Deletes the old recipe files and cache entries
3. Re-downloads the image from CivitAI, re-parses EXIF metadata
4. Carries over user edits (title, tags, favorite) and timestamps
This commit is contained in:
@@ -102,6 +102,7 @@ class RecipeHandlerSet:
|
|||||||
"check_image_exists": self.management.check_image_exists,
|
"check_image_exists": self.management.check_image_exists,
|
||||||
"import_from_url": self.management.import_from_url,
|
"import_from_url": self.management.import_from_url,
|
||||||
"create_from_example": self.management.create_from_example,
|
"create_from_example": self.management.create_from_example,
|
||||||
|
"reimport_recipe": self.management.reimport_recipe,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -799,6 +800,116 @@ class RecipeManagementHandler:
|
|||||||
self._logger.error("Error repairing single recipe: %s", exc, exc_info=True)
|
self._logger.error("Error repairing single recipe: %s", exc, exc_info=True)
|
||||||
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
||||||
|
|
||||||
|
async def reimport_recipe(self, request: web.Request) -> web.Response:
|
||||||
|
"""Delete a recipe and re-import it from its source URL.
|
||||||
|
|
||||||
|
This gives the recipe a fresh start — re-downloads the image from
|
||||||
|
CivitAI, re-parses EXIF metadata with the current parser, and
|
||||||
|
re-resolves LoRAs / checkpoint. User edits (title, tags, favorite)
|
||||||
|
are carried over from the old recipe.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await self._ensure_dependencies_ready()
|
||||||
|
recipe_scanner = self._recipe_scanner_getter()
|
||||||
|
if recipe_scanner is None:
|
||||||
|
raise RuntimeError("Recipe scanner unavailable")
|
||||||
|
|
||||||
|
recipe_id = request.match_info["recipe_id"]
|
||||||
|
old_recipe = await recipe_scanner.get_recipe_by_id(recipe_id)
|
||||||
|
if not old_recipe:
|
||||||
|
raise RecipeNotFoundError(f"Recipe {recipe_id} not found")
|
||||||
|
|
||||||
|
source_path = old_recipe.get("source_path")
|
||||||
|
if not source_path:
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"error": (
|
||||||
|
"Recipe has no source URL — cannot re-import. "
|
||||||
|
"Use repair or manual import instead."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
status=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
user_edits: dict[str, Any] = {}
|
||||||
|
for key in ("title", "tags", "favorite"):
|
||||||
|
if key in old_recipe and old_recipe[key]:
|
||||||
|
user_edits[key] = old_recipe[key]
|
||||||
|
if "tags" in user_edits and not isinstance(user_edits["tags"], list):
|
||||||
|
del user_edits["tags"]
|
||||||
|
|
||||||
|
old_created = old_recipe.get("created_date")
|
||||||
|
old_modified = old_recipe.get("modified")
|
||||||
|
|
||||||
|
await self._persistence_service.delete_recipe(
|
||||||
|
recipe_scanner=recipe_scanner, recipe_id=recipe_id
|
||||||
|
)
|
||||||
|
|
||||||
|
async with self._import_semaphore:
|
||||||
|
import_response = await self._do_import_from_url(
|
||||||
|
source_path, recipe_scanner
|
||||||
|
)
|
||||||
|
|
||||||
|
body_bytes = import_response.body
|
||||||
|
if not body_bytes:
|
||||||
|
raise RuntimeError("Re-import returned an empty response")
|
||||||
|
import_body = json.loads(body_bytes.decode())
|
||||||
|
new_recipe_id = import_body.get("recipe_id")
|
||||||
|
|
||||||
|
if new_recipe_id and user_edits:
|
||||||
|
try:
|
||||||
|
await self._persistence_service.update_recipe(
|
||||||
|
recipe_scanner=recipe_scanner,
|
||||||
|
recipe_id=new_recipe_id,
|
||||||
|
updates=user_edits,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
self._logger.warning(
|
||||||
|
"Re-import succeeded but failed to carry over "
|
||||||
|
"user edits for new recipe %s: %s",
|
||||||
|
new_recipe_id,
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
|
||||||
|
timestamp_updates: dict[str, Any] = {}
|
||||||
|
if old_created is not None:
|
||||||
|
timestamp_updates["created_date"] = old_created
|
||||||
|
if old_modified is not None:
|
||||||
|
timestamp_updates["modified"] = old_modified
|
||||||
|
if new_recipe_id and timestamp_updates:
|
||||||
|
try:
|
||||||
|
await recipe_scanner.update_recipe_metadata(
|
||||||
|
new_recipe_id, timestamp_updates
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
self._logger.warning(
|
||||||
|
"Re-import succeeded but failed to preserve "
|
||||||
|
"timestamps for new recipe %s: %s",
|
||||||
|
new_recipe_id,
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"success": True,
|
||||||
|
"old_recipe_id": recipe_id,
|
||||||
|
"recipe_id": new_recipe_id,
|
||||||
|
"source_path": source_path,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except RecipeNotFoundError as exc:
|
||||||
|
return web.json_response({"success": False, "error": str(exc)}, status=404)
|
||||||
|
except RecipeValidationError as exc:
|
||||||
|
return web.json_response({"success": False, "error": str(exc)}, status=400)
|
||||||
|
except RecipeDownloadError as exc:
|
||||||
|
return web.json_response({"success": False, "error": str(exc)}, status=400)
|
||||||
|
except Exception as exc:
|
||||||
|
self._logger.error(
|
||||||
|
"Error reimporting recipe: %s", exc, exc_info=True
|
||||||
|
)
|
||||||
|
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
||||||
|
|
||||||
async def get_repair_progress(self, request: web.Request) -> web.Response:
|
async def get_repair_progress(self, request: web.Request) -> web.Response:
|
||||||
try:
|
try:
|
||||||
progress = self._ws_manager.get_recipe_repair_progress()
|
progress = self._ws_manager.get_recipe_repair_progress()
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ ROUTE_DEFINITIONS: tuple[RouteDefinition, ...] = (
|
|||||||
RouteDefinition(
|
RouteDefinition(
|
||||||
"POST", "/api/lm/recipes/create-from-example", "create_from_example"
|
"POST", "/api/lm/recipes/create-from-example", "create_from_example"
|
||||||
),
|
),
|
||||||
|
RouteDefinition(
|
||||||
|
"POST", "/api/lm/recipe/{recipe_id}/reimport", "reimport_recipe"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user