mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
Support re-import for recipes without a source URL
Recipes imported by drag & drop / file-picker record no source_path and were rejected by re-import. Fall back to the recipe's own saved image, which still carries the original embedded generation metadata. Re-import now re-parses that original metadata instead of the appended recipe JSON block, so parser upgrades produce fresh results. The already-optimized preview image is kept verbatim: only its WebP EXIF chunk is rewritten in place to replace the recipe metadata block, and the recipe JSON is rewritten with the new analysis plus carried-over user edits.
This commit is contained in:
@@ -197,6 +197,7 @@ class StubAnalysisService:
|
||||
self.upload_calls: List[bytes] = []
|
||||
self.remote_calls: List[Optional[str]] = []
|
||||
self.local_calls: List[Optional[str]] = []
|
||||
self.local_ignore_recipe_metadata_calls: List[bool] = []
|
||||
self.result = SimpleNamespace(payload={"loras": []}, status=200)
|
||||
self._recipe_parser_factory: Any = None
|
||||
StubAnalysisService.instances.append(self)
|
||||
@@ -218,11 +219,16 @@ class StubAnalysisService:
|
||||
return self.result
|
||||
|
||||
async def analyze_local_image(
|
||||
self, *, file_path: Optional[str], recipe_scanner
|
||||
self,
|
||||
*,
|
||||
file_path: Optional[str],
|
||||
recipe_scanner,
|
||||
ignore_recipe_metadata: bool = False,
|
||||
) -> SimpleNamespace: # noqa: D401
|
||||
if self.raise_for_local:
|
||||
raise self.raise_for_local
|
||||
self.local_calls.append(file_path)
|
||||
self.local_ignore_recipe_metadata_calls.append(ignore_recipe_metadata)
|
||||
return self.result
|
||||
|
||||
async def analyze_widget_metadata(self, *, recipe_scanner) -> SimpleNamespace:
|
||||
@@ -257,6 +263,7 @@ class StubPersistenceService:
|
||||
extension=None,
|
||||
recipe_id=None,
|
||||
target_dir=None,
|
||||
skip_optimize=False,
|
||||
) -> SimpleNamespace: # noqa: D401
|
||||
self.save_calls.append(
|
||||
{
|
||||
@@ -269,6 +276,7 @@ class StubPersistenceService:
|
||||
"extension": extension,
|
||||
"recipe_id": recipe_id,
|
||||
"target_dir": target_dir,
|
||||
"skip_optimize": skip_optimize,
|
||||
}
|
||||
)
|
||||
return self.save_result
|
||||
@@ -2168,3 +2176,73 @@ async def test_checkpoint_mark_hash_invalid_route_requires_recipe_id(
|
||||
json={},
|
||||
)
|
||||
assert response.status == 400
|
||||
|
||||
|
||||
async def test_reimport_without_source_path_falls_back_to_recipe_file(
|
||||
monkeypatch, tmp_path: Path
|
||||
) -> None:
|
||||
"""Drag & drop imports record no source_path; re-import must fall back to
|
||||
the recipe's own saved image and re-parse ignoring the recipe metadata."""
|
||||
async with recipe_harness(monkeypatch, tmp_path) as harness:
|
||||
recipe_file = harness.tmp_dir / "recipes" / "rec1.webp"
|
||||
recipe_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
recipe_file.write_bytes(b"fake-image")
|
||||
|
||||
harness.scanner.recipes["rec1"] = {
|
||||
"id": "rec1",
|
||||
"title": "Old title",
|
||||
"file_path": str(recipe_file),
|
||||
"tags": ["tag1"],
|
||||
# no source_path on purpose
|
||||
}
|
||||
harness.analysis.result = SimpleNamespace(
|
||||
payload={
|
||||
"success": True,
|
||||
"recipe_id": "new-rec",
|
||||
"loras": [],
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
harness.persistence.save_result = SimpleNamespace(
|
||||
payload={"success": True, "recipe_id": "new-rec"}, status=200
|
||||
)
|
||||
|
||||
response = await harness.client.post("/api/lm/recipe/rec1/reimport")
|
||||
payload = await response.json()
|
||||
|
||||
assert response.status == 200
|
||||
assert payload["success"] is True
|
||||
assert payload["old_recipe_id"] == "rec1"
|
||||
assert payload["recipe_id"] == "new-rec"
|
||||
# Local analysis is used on the saved image, ignoring recipe metadata.
|
||||
assert harness.analysis.local_calls == [str(recipe_file)]
|
||||
assert harness.analysis.local_ignore_recipe_metadata_calls == [True]
|
||||
# The old recipe is deleted after the fresh save.
|
||||
assert harness.persistence.delete_calls == ["rec1"]
|
||||
# The already-optimized preview image must be stored verbatim.
|
||||
assert harness.persistence.save_calls[-1]["skip_optimize"] is True
|
||||
assert harness.persistence.save_calls[-1]["image_bytes"] == b"fake-image"
|
||||
# User edits (title, tags) are carried over to the new recipe.
|
||||
assert harness.persistence.update_calls[-1]["recipe_id"] == "new-rec"
|
||||
assert harness.persistence.update_calls[-1]["updates"]["title"] == "Old title"
|
||||
assert harness.persistence.update_calls[-1]["updates"]["tags"] == ["tag1"]
|
||||
|
||||
|
||||
async def test_reimport_without_any_source_returns_400(
|
||||
monkeypatch, tmp_path: Path
|
||||
) -> None:
|
||||
"""Recipes with neither source_path nor an accessible image cannot re-import."""
|
||||
async with recipe_harness(monkeypatch, tmp_path) as harness:
|
||||
harness.scanner.recipes["rec2"] = {
|
||||
"id": "rec2",
|
||||
"title": "No source",
|
||||
"file_path": str(harness.tmp_dir / "recipes" / "missing.webp"),
|
||||
}
|
||||
|
||||
response = await harness.client.post("/api/lm/recipe/rec2/reimport")
|
||||
payload = await response.json()
|
||||
|
||||
assert response.status == 400
|
||||
assert payload["success"] is False
|
||||
assert harness.analysis.local_calls == []
|
||||
assert harness.persistence.delete_calls == []
|
||||
|
||||
Reference in New Issue
Block a user