mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 03:01:27 -03:00
a17399d667
Recipes imported from CivitAI image URLs can contain 0 LoRAs: the backend
only sees the REST image API + EXIF, while the complete generation data
lives in the image page's internal trpc payload (see
docs/recipe-civitai-image-no-metadata.md). When the companion
lm-civitai-extension is installed with a valid license, re-import (single
and bulk) of CivitAI-image-sourced recipes is now delegated to the
extension via DOM CustomEvents; the extension scrapes the image page with
the user's session and calls back into the reimport endpoint with the
full metadata payload. Without the extension (or with an invalid license)
the native path runs unchanged.
- POST /api/lm/recipe/{id}/reimport accepts optional payload params
(image_url/name/resources/gen_params/base_model/tags); the payload path
reuses the import-remote engine with reimport semantics (user-edit
carryover, delete-after-save), and malformed/failed payloads fall back
to the legacy URL import. Response gains loras_count.
- The endpoint also accepts GET: the extension is GET-only by convention
(documented in AGENTS.md).
- New static/js/utils/extensionReimportBridge.js (probeExtension /
delegateReimport / getCivitaiImageInfo) wired into RecipeContextMenu
and BulkManager with silent native fallback.
- i18n: toast.recipes.reimportingViaExtension added and translated in
all 9 locales.
148 lines
6.0 KiB
Python
148 lines
6.0 KiB
Python
"""Route registrar for recipe endpoints."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, Callable, Mapping
|
|
|
|
from aiohttp import web
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RouteDefinition:
|
|
"""Declarative definition for a recipe HTTP route."""
|
|
|
|
method: str
|
|
path: str
|
|
handler_name: str
|
|
|
|
|
|
ROUTE_DEFINITIONS: tuple[RouteDefinition, ...] = (
|
|
RouteDefinition("GET", "/loras/recipes", "render_page"),
|
|
RouteDefinition("GET", "/api/lm/recipes", "list_recipes"),
|
|
RouteDefinition("GET", "/api/lm/recipe/{recipe_id}", "get_recipe"),
|
|
RouteDefinition("GET", "/api/lm/recipes/import-remote", "import_remote_recipe"),
|
|
RouteDefinition("POST", "/api/lm/recipes/analyze-image", "analyze_uploaded_image"),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipes/analyze-local-image", "analyze_local_image"
|
|
),
|
|
RouteDefinition("POST", "/api/lm/recipes/save", "save_recipe"),
|
|
RouteDefinition("DELETE", "/api/lm/recipe/{recipe_id}", "delete_recipe"),
|
|
RouteDefinition("GET", "/api/lm/recipes/top-tags", "get_top_tags"),
|
|
RouteDefinition("GET", "/api/lm/recipes/search-tags", "search_tags"),
|
|
RouteDefinition("GET", "/api/lm/recipes/base-models", "get_base_models"),
|
|
RouteDefinition("GET", "/api/lm/recipes/roots", "get_roots"),
|
|
RouteDefinition("GET", "/api/lm/recipes/folders", "get_folders"),
|
|
RouteDefinition("GET", "/api/lm/recipes/folder-tree", "get_folder_tree"),
|
|
RouteDefinition(
|
|
"GET", "/api/lm/recipes/unified-folder-tree", "get_unified_folder_tree"
|
|
),
|
|
RouteDefinition("GET", "/api/lm/recipe/{recipe_id}/share", "share_recipe"),
|
|
RouteDefinition(
|
|
"GET", "/api/lm/recipe/{recipe_id}/share/download", "download_shared_recipe"
|
|
),
|
|
RouteDefinition("GET", "/api/lm/recipe/{recipe_id}/syntax", "get_recipe_syntax"),
|
|
RouteDefinition("PUT", "/api/lm/recipe/{recipe_id}/update", "update_recipe"),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipe/{recipe_id}/opened", "record_recipe_open"
|
|
),
|
|
RouteDefinition("POST", "/api/lm/recipe/move", "move_recipe"),
|
|
RouteDefinition("POST", "/api/lm/recipes/move-bulk", "move_recipes_bulk"),
|
|
RouteDefinition("POST", "/api/lm/recipe/lora/reconnect", "reconnect_lora"),
|
|
RouteDefinition("POST", "/api/lm/recipe/lora/restore", "restore_lora"),
|
|
RouteDefinition(
|
|
"GET",
|
|
"/api/lm/recipe/{recipe_id}/lora/{lora_index}/reconnect-suggestions",
|
|
"get_reconnect_suggestions",
|
|
),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipe/lora/mark-hash-invalid", "mark_lora_hash_invalid"
|
|
),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipe/checkpoint/reconnect", "reconnect_checkpoint"
|
|
),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipe/checkpoint/restore", "restore_checkpoint"
|
|
),
|
|
RouteDefinition(
|
|
"GET",
|
|
"/api/lm/recipe/{recipe_id}/checkpoint/reconnect-suggestions",
|
|
"get_checkpoint_reconnect_suggestions",
|
|
),
|
|
RouteDefinition(
|
|
"POST",
|
|
"/api/lm/recipe/checkpoint/mark-hash-invalid",
|
|
"mark_checkpoint_hash_invalid",
|
|
),
|
|
RouteDefinition("GET", "/api/lm/recipes/find-duplicates", "find_duplicates"),
|
|
RouteDefinition("POST", "/api/lm/recipes/bulk-delete", "bulk_delete"),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipes/save-from-widget", "save_recipe_from_widget"
|
|
),
|
|
RouteDefinition("GET", "/api/lm/recipes/for-lora", "get_recipes_for_lora"),
|
|
RouteDefinition(
|
|
"GET", "/api/lm/recipes/for-checkpoint", "get_recipes_for_checkpoint"
|
|
),
|
|
RouteDefinition("GET", "/api/lm/recipes/scan", "scan_recipes"),
|
|
RouteDefinition("POST", "/api/lm/recipes/rematch", "rematch_recipes"),
|
|
RouteDefinition("POST", "/api/lm/recipes/rematch-bulk", "rematch_recipes_bulk"),
|
|
RouteDefinition("POST", "/api/lm/recipe/{recipe_id}/rematch", "rematch_recipe"),
|
|
RouteDefinition("POST", "/api/lm/recipes/cancel-rematch", "cancel_rematch"),
|
|
RouteDefinition("GET", "/api/lm/recipes/rematch-progress", "get_rematch_progress"),
|
|
RouteDefinition("POST", "/api/lm/recipes/batch-import/start", "start_batch_import"),
|
|
RouteDefinition(
|
|
"GET", "/api/lm/recipes/batch-import/progress", "get_batch_import_progress"
|
|
),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipes/batch-import/cancel", "cancel_batch_import"
|
|
),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipes/batch-import/directory", "start_directory_import"
|
|
),
|
|
RouteDefinition("POST", "/api/lm/recipes/browse-directory", "browse_directory"),
|
|
RouteDefinition(
|
|
"GET", "/api/lm/recipes/check-image-exists", "check_image_exists"
|
|
),
|
|
RouteDefinition("GET", "/api/lm/recipes/import-from-url", "import_from_url"),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipes/create-from-example", "create_from_example"
|
|
),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipe/{recipe_id}/reimport", "reimport_recipe"
|
|
),
|
|
# The companion browser extension only ever issues GET requests, so the
|
|
# payload-based re-import variant must also be reachable via GET.
|
|
RouteDefinition(
|
|
"GET", "/api/lm/recipe/{recipe_id}/reimport", "reimport_recipe"
|
|
),
|
|
RouteDefinition(
|
|
"POST", "/api/lm/recipe/{recipe_id}/send-workflow", "send_recipe_workflow"
|
|
),
|
|
)
|
|
|
|
|
|
class RecipeRouteRegistrar:
|
|
"""Bind declarative recipe definitions to an aiohttp router."""
|
|
|
|
_METHOD_MAP = {
|
|
"GET": "add_get",
|
|
"POST": "add_post",
|
|
"PUT": "add_put",
|
|
"DELETE": "add_delete",
|
|
}
|
|
|
|
def __init__(self, app: web.Application) -> None:
|
|
self._app = app
|
|
|
|
def register_routes(
|
|
self, handler_lookup: Mapping[str, Callable[[web.Request], object]]
|
|
) -> None:
|
|
for definition in ROUTE_DEFINITIONS:
|
|
handler = handler_lookup[definition.handler_name]
|
|
self._bind_route(definition.method, definition.path, handler)
|
|
|
|
def _bind_route(self, method: str, path: str, handler: Callable[..., Any]) -> None:
|
|
add_method_name = self._METHOD_MAP[method.upper()]
|
|
add_method = getattr(self._app.router, add_method_name)
|
|
add_method(path, handler)
|