mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-29 23:14:08 -03:00
feat(recipes): opt-in workflow embedding for widget recipe saves
Add a "Save Recipe with Workflow" action next to "Save Recipe" in the LoRA
widget context menu. It posts the current UI-format graph alongside the save
request so the stored preview embeds it and the recipe can send the graph back
to ComfyUI. Embedding stays opt-in rather than folded into "Save Recipe": the
workflow is by far the largest metadata field and its widget values may carry
sensitive data.
- web/comfyui: new menu entry; saveRecipeDirectly({ embedWorkflow }) posts the
UI graph and reports the outcome (embedded / skipped) via toasts.
- save_recipe_from_widget handler: reads an optional JSON workflow field so the
long-standing body-less POST keeps working, including from cached clients.
- RecipePersistenceService.save_recipe_from_widget: embeds the graph through
the existing optimize_image workflow path, derives has_workflow by detection,
and skips graphs above MAX_WORKFLOW_EMBED_BYTES with workflow_skipped.
This commit is contained in:
@@ -1821,6 +1821,10 @@ class RecipeManagementHandler:
|
||||
if recipe_scanner is None:
|
||||
raise RuntimeError("Recipe scanner unavailable")
|
||||
|
||||
# Opt-in workflow embedding. The widget historically POSTs with no
|
||||
# body at all, so a missing/empty body is not an error.
|
||||
workflow = await self._read_optional_json_field(request, "workflow")
|
||||
|
||||
analysis = await self._analysis_service.analyze_widget_metadata(
|
||||
recipe_scanner=recipe_scanner
|
||||
)
|
||||
@@ -1833,6 +1837,7 @@ class RecipeManagementHandler:
|
||||
recipe_scanner=recipe_scanner,
|
||||
metadata=metadata,
|
||||
image_bytes=image_bytes,
|
||||
workflow=workflow,
|
||||
)
|
||||
return web.json_response(result.payload, status=result.status)
|
||||
except RecipeValidationError as exc:
|
||||
@@ -1897,6 +1902,24 @@ class RecipeManagementHandler:
|
||||
return []
|
||||
return [tag.strip() for tag in tag_text.split(",") if tag.strip()]
|
||||
|
||||
async def _read_optional_json_field(
|
||||
self, request: web.Request, field: str
|
||||
) -> Any:
|
||||
"""Read one field from an optional JSON request body.
|
||||
|
||||
Some callers (notably the widget's long-standing "Save Recipe" action)
|
||||
POST with no body at all, and a stale cached extension may still do so
|
||||
after a body is introduced. A missing, empty or malformed body is
|
||||
therefore treated as "no value" rather than a request error.
|
||||
"""
|
||||
if not request.can_read_body:
|
||||
return None
|
||||
try:
|
||||
data = await request.json()
|
||||
except Exception:
|
||||
return None
|
||||
return data.get(field) if isinstance(data, dict) else None
|
||||
|
||||
async def _count_recipe_loras(
|
||||
self, recipe_scanner: Any, recipe_id: Optional[str]
|
||||
) -> Optional[int]:
|
||||
|
||||
Reference in New Issue
Block a user