From 92de68aabd5c4ec756720fa7ce73ab279cadfbda Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Fri, 21 Mar 2025 17:04:31 -0400 Subject: [PATCH] Add REQUIRED_FRONTEND_VERSION prop on node def --- app/frontend_management.py | 42 ++++++++++++++++++++--------- comfy/comfy_types/node_typing.py | 2 +- server.py | 46 +++++++++++++++++++++++++++----- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/app/frontend_management.py b/app/frontend_management.py index b4ba994d..d4b44d6d 100644 --- a/app/frontend_management.py +++ b/app/frontend_management.py @@ -31,12 +31,12 @@ def frontend_install_warning_message(): return f"Please install the updated requirements.txt file by running:\n{sys.executable} {extra}-m pip install -r {req_path}\n\nThis error is happening because the ComfyUI frontend is no longer shipped as part of the main repo but as a pip package instead.\n\nIf you are on the portable package you can run: update\\update_comfyui.bat to solve this problem" +def parse_version(version: str) -> tuple[int, int, int]: + return tuple(map(int, version.split("."))) + + def check_frontend_version(): """Check if the frontend version is up to date.""" - - def parse_version(version: str) -> tuple[int, int, int]: - return tuple(map(int, version.split("."))) - try: frontend_version_str = version("comfyui-frontend-package") frontend_version = parse_version(frontend_version_str) @@ -73,6 +73,11 @@ class FrontEndProvider: owner: str repo: str + @property + def is_official(self) -> bool: + """Check if the provider is the default official one.""" + return self.owner == "Comfy-Org" and self.repo == "ComfyUI_frontend" + @property def folder_name(self) -> str: return f"{self.owner}_{self.repo}" @@ -143,14 +148,24 @@ def download_release_asset_zip(release: Release, destination_path: str) -> None: zip_ref.extractall(destination_path) +class FrontendInit(TypedDict): + web_root: str + """ The path to the initialized frontend. """ + version: tuple[int, int, int] | None + """ The version of the initialized frontend. None for unrecognized version.""" + class FrontendManager: CUSTOM_FRONTENDS_ROOT = str(Path(__file__).parents[1] / "web_custom_versions") @classmethod - def default_frontend_path(cls) -> str: + def init_default_frontend(cls) -> FrontendInit: + check_frontend_version() try: import comfyui_frontend_package - return str(importlib.resources.files(comfyui_frontend_package) / "static") + return FrontendInit( + web_root=str(importlib.resources.files(comfyui_frontend_package) / "static"), + version=parse_version(version("comfyui-frontend-package")), + ) except ImportError: logging.error(f"\n\n********** ERROR ***********\n\ncomfyui-frontend-package is not installed. {frontend_install_warning_message()}\n********** ERROR **********\n") sys.exit(-1) @@ -175,7 +190,7 @@ class FrontendManager: return match_result.group(1), match_result.group(2), match_result.group(3) @classmethod - def init_frontend_unsafe(cls, version_string: str, provider: Optional[FrontEndProvider] = None) -> str: + def init_frontend_unsafe(cls, version_string: str, provider: Optional[FrontEndProvider] = None) -> FrontendInit: """ Initializes the frontend for the specified version. @@ -191,8 +206,7 @@ class FrontendManager: main error source might be request timeout or invalid URL. """ if version_string == DEFAULT_VERSION_STRING: - check_frontend_version() - return cls.default_frontend_path() + return cls.init_default_frontend() repo_owner, repo_name, version = cls.parse_version_string(version_string) @@ -227,10 +241,13 @@ class FrontendManager: if not os.listdir(web_root): os.rmdir(web_root) - return web_root + return FrontendInit( + web_root=web_root, + version=parse_version(semantic_version) if provider.is_official else None, + ) @classmethod - def init_frontend(cls, version_string: str) -> str: + def init_frontend(cls, version_string: str) -> FrontendInit: """ Initializes the frontend with the specified version string. @@ -245,5 +262,4 @@ class FrontendManager: except Exception as e: logging.error("Failed to initialize frontend: %s", e) logging.info("Falling back to the default frontend.") - check_frontend_version() - return cls.default_frontend_path() + return cls.init_default_frontend() diff --git a/comfy/comfy_types/node_typing.py b/comfy/comfy_types/node_typing.py index 22f4f1bb..372fca15 100644 --- a/comfy/comfy_types/node_typing.py +++ b/comfy/comfy_types/node_typing.py @@ -220,7 +220,7 @@ class ComfyNodeABC(ABC): """Flags a node as experimental, informing users that it may change or not work as expected.""" DEPRECATED: bool """Flags a node as deprecated, indicating to users that they should find alternatives to this node.""" - REQUIRED_FRONTEND_VERSION: str + REQUIRED_FRONTEND_VERSION: str | None """The minimum version of the ComfyUI frontend required to load this node. Usage:: diff --git a/server.py b/server.py index 76a99167..dd581fbe 100644 --- a/server.py +++ b/server.py @@ -24,11 +24,12 @@ import logging import mimetypes from comfy.cli_args import args +from comfy.comfy_types.node_typing import ComfyNodeABC import comfy.utils import comfy.model_management import node_helpers from comfyui_version import __version__ -from app.frontend_management import FrontendManager +from app.frontend_management import FrontendInit, FrontendManager, parse_version from app.user_manager import UserManager from app.model_manager import ModelFileManager from app.custom_node_manager import CustomNodeManager @@ -146,6 +147,11 @@ def create_origin_only_middleware(): return origin_only_middleware class PromptServer(): + web_root: str + """The path to the initialized frontend assets.""" + frontend_version: tuple[int, int, int] | None = None + """The version of the initialized frontend. None for unrecognized version.""" + def __init__(self, loop): PromptServer.instance = self @@ -176,12 +182,19 @@ class PromptServer(): max_upload_size = round(args.max_upload_size * 1024 * 1024) self.app = web.Application(client_max_size=max_upload_size, middlewares=middlewares) self.sockets = dict() - self.web_root = ( - FrontendManager.init_frontend(args.front_end_version) - if args.front_end_root is None - else args.front_end_root - ) + + if args.front_end_root: + frontend_init = FrontendInit( + web_root=args.front_end_root, + version=None, + ) + else: + frontend_init = FrontendManager.init_frontend(args.front_end_version) + + self.frontend_version = frontend_init["version"] + self.web_root = frontend_init["web_root"] logging.info(f"[Prompt Server] web root: {self.web_root}") + routes = web.RouteTableDef() self.routes = routes self.last_node_id = None @@ -587,6 +600,9 @@ class PromptServer(): with folder_paths.cache_helper: out = {} for x in nodes.NODE_CLASS_MAPPINGS: + if not self.node_is_supported(x): + continue + try: out[x] = node_info(x) except Exception: @@ -598,7 +614,11 @@ class PromptServer(): async def get_object_info_node(request): node_class = request.match_info.get("node_class", None) out = {} - if (node_class is not None) and (node_class in nodes.NODE_CLASS_MAPPINGS): + if ( + node_class is not None + and node_class in nodes.NODE_CLASS_MAPPINGS + and self.node_is_supported(node_class) + ): out[node_class] = node_info(node_class) return web.json_response(out) @@ -863,3 +883,15 @@ class PromptServer(): logging.warning(traceback.format_exc()) return json_data + + def node_is_supported(self, node_class: ComfyNodeABC) -> bool: + """Check if the node is supported by the frontend.""" + # For unrecognized frontend version, we assume the node is supported. + if self.frontend_version is None: + return True + + # Check if the node is supported by the frontend. + if node_class.REQUIRED_FRONTEND_VERSION is None: + return True + + return parse_version(node_class.REQUIRED_FRONTEND_VERSION) <= self.frontend_version