From f1c9ca816ac1806982fe97b33eb8d7c76154bec1 Mon Sep 17 00:00:00 2001 From: Robin Huang Date: Thu, 29 May 2025 10:27:40 -0700 Subject: [PATCH 1/4] Add BFL Kontext API Nodes. (#8333) * Added initial Flux.1 Kontext Pro Image node - recreated branch to save myself sanity from rebase crap after master got rebased * Add safety filter to Kontext. * Make safety = 2 and input image is optional. * Add BFL kontext API nodes. --------- Co-authored-by: Jedrzej Kosinski --- comfy_api_nodes/apis/bfl_api.py | 34 ++++ comfy_api_nodes/nodes_bfl.py | 288 +++++++++++++++++++++++++++++++- 2 files changed, 321 insertions(+), 1 deletion(-) diff --git a/comfy_api_nodes/apis/bfl_api.py b/comfy_api_nodes/apis/bfl_api.py index c189038fb8cb..504e507e1a6c 100644 --- a/comfy_api_nodes/apis/bfl_api.py +++ b/comfy_api_nodes/apis/bfl_api.py @@ -108,6 +108,40 @@ class BFLFluxProGenerateRequest(BaseModel): # ) +class BFLFluxKontextProGenerateRequest(BaseModel): + prompt: str = Field(..., description='The text prompt for what you wannt to edit.') + input_image: Optional[str] = Field(None, description='Image to edit in base64 format') + seed: Optional[int] = Field(None, description='The seed value for reproducibility.') + guidance: confloat(ge=0.1, le=99.0) = Field(..., description='Guidance strength for the image generation process') + steps: conint(ge=1, le=150) = Field(..., description='Number of steps for the image generation process') + safety_tolerance: Optional[conint(ge=0, le=2)] = Field( + 2, description='Tolerance level for input and output moderation. Between 0 and 2, 0 being most strict, 6 being least strict. Defaults to 2.' + ) + output_format: Optional[BFLOutputFormat] = Field( + BFLOutputFormat.png, description="Output format for the generated image. Can be 'jpeg' or 'png'.", examples=['png'] + ) + aspect_ratio: Optional[str] = Field(None, description='Aspect ratio of the image between 21:9 and 9:21.') + prompt_upsampling: Optional[bool] = Field( + None, description='Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation.' + ) + +class BFLFluxKontextMaxGenerateRequest(BaseModel): + prompt: str = Field(..., description='The text prompt for what you wannt to edit.') + input_image: Optional[str] = Field(None, description='Image to edit in base64 format') + seed: Optional[int] = Field(None, description='The seed value for reproducibility.') + guidance: confloat(ge=0.1, le=99.0) = Field(..., description='Guidance strength for the image generation process') + steps: conint(ge=1, le=150) = Field(..., description='Number of steps for the image generation process') + safety_tolerance: Optional[conint(ge=0, le=2)] = Field( + 2, description='Tolerance level for input and output moderation. Between 0 and 2, 0 being most strict, 6 being least strict. Defaults to 2.' + ) + output_format: Optional[BFLOutputFormat] = Field( + BFLOutputFormat.png, description="Output format for the generated image. Can be 'jpeg' or 'png'.", examples=['png'] + ) + aspect_ratio: Optional[str] = Field(None, description='Aspect ratio of the image between 21:9 and 9:21.') + prompt_upsampling: Optional[bool] = Field( + None, description='Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation.' + ) + class BFLFluxProUltraGenerateRequest(BaseModel): prompt: str = Field(..., description='The text prompt for image generation.') prompt_upsampling: Optional[bool] = Field( diff --git a/comfy_api_nodes/nodes_bfl.py b/comfy_api_nodes/nodes_bfl.py index 509170b343d9..a762472e6cb8 100644 --- a/comfy_api_nodes/nodes_bfl.py +++ b/comfy_api_nodes/nodes_bfl.py @@ -1,6 +1,6 @@ import io from inspect import cleandoc -from typing import Union +from typing import Union, Optional from comfy.comfy_types.node_typing import IO, ComfyNodeABC from comfy_api_nodes.apis.bfl_api import ( BFLStatus, @@ -9,6 +9,7 @@ BFLFluxCannyImageRequest, BFLFluxDepthImageRequest, BFLFluxProGenerateRequest, + BFLFluxKontextProGenerateRequest, BFLFluxProUltraGenerateRequest, BFLFluxProGenerateResponse, ) @@ -269,6 +270,287 @@ def api_call( return (output_image,) +class FluxKontextProImageNode(ComfyNodeABC): + """ + Edits images using Flux.1 Kontext Pro via api based on prompt and resolution. + """ + + MINIMUM_RATIO = 1 / 4 + MAXIMUM_RATIO = 4 / 1 + MINIMUM_RATIO_STR = "1:4" + MAXIMUM_RATIO_STR = "4:1" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "prompt": ( + IO.STRING, + { + "multiline": True, + "default": "", + "tooltip": "Prompt for the image generation - specify what and how to edit.", + }, + ), + "aspect_ratio": ( + IO.STRING, + { + "default": "16:9", + "tooltip": "Aspect ratio of image; must be between 1:4 and 4:1.", + }, + ), + "guidance": ( + IO.FLOAT, + { + "default": 3.0, + "min": 0.1, + "max": 99.0, + "step": 0.1, + "tooltip": "Guidance strength for the image generation process" + }, + ), + "steps": ( + IO.INT, + { + "default": 50, + "min": 1, + "max": 150, + "tooltip": "Number of steps for the image generation process" + }, + ), + "seed": ( + IO.INT, + { + "default": 0, + "min": 0, + "max": 0xFFFFFFFFFFFFFFFF, + "control_after_generate": True, + "tooltip": "The random seed used for creating the noise.", + }, + ), + "prompt_upsampling": ( + IO.BOOLEAN, + { + "default": False, + "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation, but results are nondeterministic (same seed will not produce exactly the same result).", + }, + ), + }, + "optional": { + "input_image": (IO.IMAGE,), + }, + "hidden": { + "auth_token": "AUTH_TOKEN_COMFY_ORG", + "comfy_api_key": "API_KEY_COMFY_ORG", + "unique_id": "UNIQUE_ID", + }, + } + + @classmethod + def VALIDATE_INPUTS(cls, aspect_ratio: str): + try: + validate_aspect_ratio( + aspect_ratio, + minimum_ratio=cls.MINIMUM_RATIO, + maximum_ratio=cls.MAXIMUM_RATIO, + minimum_ratio_str=cls.MINIMUM_RATIO_STR, + maximum_ratio_str=cls.MAXIMUM_RATIO_STR, + ) + except Exception as e: + return str(e) + return True + + RETURN_TYPES = (IO.IMAGE,) + DESCRIPTION = cleandoc(__doc__ or "") # Handle potential None value + FUNCTION = "api_call" + API_NODE = True + CATEGORY = "api node/image/BFL" + + def api_call( + self, + prompt: str, + aspect_ratio: str, + guidance: float, + steps: int, + input_image: Optional[torch.Tensor]=None, + seed=0, + prompt_upsampling=False, + unique_id: Union[str, None] = None, + **kwargs, + ): + if input_image is None: + validate_string(prompt, strip_whitespace=False) + operation = SynchronousOperation( + endpoint=ApiEndpoint( + path="/proxy/bfl/flux-kontext-pro/generate", + method=HttpMethod.POST, + request_model=BFLFluxKontextProGenerateRequest, + response_model=BFLFluxProGenerateResponse, + ), + request=BFLFluxKontextProGenerateRequest( + prompt=prompt, + prompt_upsampling=prompt_upsampling, + guidance=round(guidance, 1), + steps=steps, + seed=seed, + aspect_ratio=validate_aspect_ratio( + aspect_ratio, + minimum_ratio=self.MINIMUM_RATIO, + maximum_ratio=self.MAXIMUM_RATIO, + minimum_ratio_str=self.MINIMUM_RATIO_STR, + maximum_ratio_str=self.MAXIMUM_RATIO_STR, + ), + input_image=( + input_image + if input_image is None + else convert_image_to_base64(input_image) + ) + ), + auth_kwargs=kwargs, + ) + output_image = handle_bfl_synchronous_operation(operation, node_id=unique_id) + return (output_image,) + +class FluxKontextMaxImageNode(ComfyNodeABC): + """ + Edits images using Flux.1 Kontext Max via api based on prompt and resolution. + """ + + MINIMUM_RATIO = 1 / 4 + MAXIMUM_RATIO = 4 / 1 + MINIMUM_RATIO_STR = "1:4" + MAXIMUM_RATIO_STR = "4:1" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "prompt": ( + IO.STRING, + { + "multiline": True, + "default": "", + "tooltip": "Prompt for the image generation - specify what and how to edit.", + }, + ), + "aspect_ratio": ( + IO.STRING, + { + "default": "16:9", + "tooltip": "Aspect ratio of image; must be between 1:4 and 4:1.", + }, + ), + "guidance": ( + IO.FLOAT, + { + "default": 3.0, + "min": 0.1, + "max": 99.0, + "step": 0.1, + "tooltip": "Guidance strength for the image generation process" + }, + ), + "steps": ( + IO.INT, + { + "default": 50, + "min": 1, + "max": 150, + "tooltip": "Number of steps for the image generation process" + }, + ), + "seed": ( + IO.INT, + { + "default": 0, + "min": 0, + "max": 0xFFFFFFFFFFFFFFFF, + "control_after_generate": True, + "tooltip": "The random seed used for creating the noise.", + }, + ), + "prompt_upsampling": ( + IO.BOOLEAN, + { + "default": False, + "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation, but results are nondeterministic (same seed will not produce exactly the same result).", + }, + ), + }, + "optional": { + "input_image": (IO.IMAGE,), + }, + "hidden": { + "auth_token": "AUTH_TOKEN_COMFY_ORG", + "comfy_api_key": "API_KEY_COMFY_ORG", + "unique_id": "UNIQUE_ID", + }, + } + + @classmethod + def VALIDATE_INPUTS(cls, aspect_ratio: str): + try: + validate_aspect_ratio( + aspect_ratio, + minimum_ratio=cls.MINIMUM_RATIO, + maximum_ratio=cls.MAXIMUM_RATIO, + minimum_ratio_str=cls.MINIMUM_RATIO_STR, + maximum_ratio_str=cls.MAXIMUM_RATIO_STR, + ) + except Exception as e: + return str(e) + return True + + RETURN_TYPES = (IO.IMAGE,) + DESCRIPTION = cleandoc(__doc__ or "") # Handle potential None value + FUNCTION = "api_call" + API_NODE = True + CATEGORY = "api node/image/BFL" + + def api_call( + self, + prompt: str, + aspect_ratio: str, + guidance: float, + steps: int, + input_image: Optional[torch.Tensor]=None, + seed=0, + prompt_upsampling=False, + unique_id: Union[str, None] = None, + **kwargs, + ): + if input_image is None: + validate_string(prompt, strip_whitespace=False) + operation = SynchronousOperation( + endpoint=ApiEndpoint( + path="/proxy/bfl/flux-kontext-max/generate", + method=HttpMethod.POST, + request_model=BFLFluxKontextProGenerateRequest, + response_model=BFLFluxProGenerateResponse, + ), + request=BFLFluxKontextProGenerateRequest( + prompt=prompt, + prompt_upsampling=prompt_upsampling, + guidance=round(guidance, 1), + steps=steps, + seed=seed, + aspect_ratio=validate_aspect_ratio( + aspect_ratio, + minimum_ratio=self.MINIMUM_RATIO, + maximum_ratio=self.MAXIMUM_RATIO, + minimum_ratio_str=self.MINIMUM_RATIO_STR, + maximum_ratio_str=self.MAXIMUM_RATIO_STR, + ), + input_image=( + input_image + if input_image is None + else convert_image_to_base64(input_image) + ) + ), + auth_kwargs=kwargs, + ) + output_image = handle_bfl_synchronous_operation(operation, node_id=unique_id) + return (output_image,) class FluxProImageNode(ComfyNodeABC): """ @@ -914,6 +1196,8 @@ def api_call( NODE_CLASS_MAPPINGS = { "FluxProUltraImageNode": FluxProUltraImageNode, # "FluxProImageNode": FluxProImageNode, + "FluxKontextProImageNode": FluxKontextProImageNode, + "FluxKontextMaxImageNode": FluxKontextMaxImageNode, "FluxProExpandNode": FluxProExpandNode, "FluxProFillNode": FluxProFillNode, "FluxProCannyNode": FluxProCannyNode, @@ -924,6 +1208,8 @@ def api_call( NODE_DISPLAY_NAME_MAPPINGS = { "FluxProUltraImageNode": "Flux 1.1 [pro] Ultra Image", # "FluxProImageNode": "Flux 1.1 [pro] Image", + "FluxKontextProImageNode": "Flux.1 Kontext Pro Image", + "FluxKontextMaxImageNode": "Flux.1 Kontext Max Image", "FluxProExpandNode": "Flux.1 Expand Image", "FluxProFillNode": "Flux.1 Fill Image", "FluxProCannyNode": "Flux.1 Canny Control Image", From 31260f02753d0809204225cb5f61bace900120fa Mon Sep 17 00:00:00 2001 From: filtered <176114999+webfiltered@users.noreply.github.com> Date: Fri, 30 May 2025 03:52:27 +1000 Subject: [PATCH 2/4] Update templates 0.1.22 (#8334) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5e3c60659cf4..d2baedf0d6e7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ comfyui-frontend-package==1.20.7 -comfyui-workflow-templates==0.1.20 +comfyui-workflow-templates==0.1.22 torch torchsde torchvision From 094306b626e9cf505690c5d8b445032b3b8a36fa Mon Sep 17 00:00:00 2001 From: comfyanonymous Date: Thu, 29 May 2025 14:26:39 -0400 Subject: [PATCH 3/4] ComfyUI version 0.3.39 --- comfyui_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/comfyui_version.py b/comfyui_version.py index 64b326db85ed..f742410b1c36 100644 --- a/comfyui_version.py +++ b/comfyui_version.py @@ -1,3 +1,3 @@ # This file is automatically generated by the build process when version is # updated in pyproject.toml. -__version__ = "0.3.38" +__version__ = "0.3.39" diff --git a/pyproject.toml b/pyproject.toml index 0a841c8ce494..28a6158e028d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ComfyUI" -version = "0.3.38" +version = "0.3.39" readme = "README.md" license = { file = "LICENSE" } requires-python = ">=3.9" From aeba0b3a268eeb66c3a12cae6fd97f7c2d28f36f Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 29 May 2025 14:14:27 -0700 Subject: [PATCH 4/4] Reduce code duplication for [pro] and [max], rename Pro and Max to [pro] and [max] to be consistent with other BFL nodes, make default seed for Kontext nodes be 1234. since 0 is interpreted by API as 'choose random seed' (#8337) --- comfy_api_nodes/apis/bfl_api.py | 16 ---- comfy_api_nodes/nodes_bfl.py | 153 +++----------------------------- 2 files changed, 12 insertions(+), 157 deletions(-) diff --git a/comfy_api_nodes/apis/bfl_api.py b/comfy_api_nodes/apis/bfl_api.py index 504e507e1a6c..0e90aef7c681 100644 --- a/comfy_api_nodes/apis/bfl_api.py +++ b/comfy_api_nodes/apis/bfl_api.py @@ -125,22 +125,6 @@ class BFLFluxKontextProGenerateRequest(BaseModel): None, description='Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation.' ) -class BFLFluxKontextMaxGenerateRequest(BaseModel): - prompt: str = Field(..., description='The text prompt for what you wannt to edit.') - input_image: Optional[str] = Field(None, description='Image to edit in base64 format') - seed: Optional[int] = Field(None, description='The seed value for reproducibility.') - guidance: confloat(ge=0.1, le=99.0) = Field(..., description='Guidance strength for the image generation process') - steps: conint(ge=1, le=150) = Field(..., description='Number of steps for the image generation process') - safety_tolerance: Optional[conint(ge=0, le=2)] = Field( - 2, description='Tolerance level for input and output moderation. Between 0 and 2, 0 being most strict, 6 being least strict. Defaults to 2.' - ) - output_format: Optional[BFLOutputFormat] = Field( - BFLOutputFormat.png, description="Output format for the generated image. Can be 'jpeg' or 'png'.", examples=['png'] - ) - aspect_ratio: Optional[str] = Field(None, description='Aspect ratio of the image between 21:9 and 9:21.') - prompt_upsampling: Optional[bool] = Field( - None, description='Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation.' - ) class BFLFluxProUltraGenerateRequest(BaseModel): prompt: str = Field(..., description='The text prompt for image generation.') diff --git a/comfy_api_nodes/nodes_bfl.py b/comfy_api_nodes/nodes_bfl.py index a762472e6cb8..010564704480 100644 --- a/comfy_api_nodes/nodes_bfl.py +++ b/comfy_api_nodes/nodes_bfl.py @@ -272,7 +272,7 @@ def api_call( class FluxKontextProImageNode(ComfyNodeABC): """ - Edits images using Flux.1 Kontext Pro via api based on prompt and resolution. + Edits images using Flux.1 Kontext [pro] via api based on prompt and aspect ratio. """ MINIMUM_RATIO = 1 / 4 @@ -321,7 +321,7 @@ def INPUT_TYPES(s): "seed": ( IO.INT, { - "default": 0, + "default": 1234, "min": 0, "max": 0xFFFFFFFFFFFFFFFF, "control_after_generate": True, @@ -366,6 +366,8 @@ def VALIDATE_INPUTS(cls, aspect_ratio: str): API_NODE = True CATEGORY = "api node/image/BFL" + BFL_PATH = "/proxy/bfl/flux-kontext-pro/generate" + def api_call( self, prompt: str, @@ -382,7 +384,7 @@ def api_call( validate_string(prompt, strip_whitespace=False) operation = SynchronousOperation( endpoint=ApiEndpoint( - path="/proxy/bfl/flux-kontext-pro/generate", + path=self.BFL_PATH, method=HttpMethod.POST, request_model=BFLFluxKontextProGenerateRequest, response_model=BFLFluxProGenerateResponse, @@ -411,146 +413,15 @@ def api_call( output_image = handle_bfl_synchronous_operation(operation, node_id=unique_id) return (output_image,) -class FluxKontextMaxImageNode(ComfyNodeABC): + +class FluxKontextMaxImageNode(FluxKontextProImageNode): """ - Edits images using Flux.1 Kontext Max via api based on prompt and resolution. + Edits images using Flux.1 Kontext [max] via api based on prompt and aspect ratio. """ - MINIMUM_RATIO = 1 / 4 - MAXIMUM_RATIO = 4 / 1 - MINIMUM_RATIO_STR = "1:4" - MAXIMUM_RATIO_STR = "4:1" - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "prompt": ( - IO.STRING, - { - "multiline": True, - "default": "", - "tooltip": "Prompt for the image generation - specify what and how to edit.", - }, - ), - "aspect_ratio": ( - IO.STRING, - { - "default": "16:9", - "tooltip": "Aspect ratio of image; must be between 1:4 and 4:1.", - }, - ), - "guidance": ( - IO.FLOAT, - { - "default": 3.0, - "min": 0.1, - "max": 99.0, - "step": 0.1, - "tooltip": "Guidance strength for the image generation process" - }, - ), - "steps": ( - IO.INT, - { - "default": 50, - "min": 1, - "max": 150, - "tooltip": "Number of steps for the image generation process" - }, - ), - "seed": ( - IO.INT, - { - "default": 0, - "min": 0, - "max": 0xFFFFFFFFFFFFFFFF, - "control_after_generate": True, - "tooltip": "The random seed used for creating the noise.", - }, - ), - "prompt_upsampling": ( - IO.BOOLEAN, - { - "default": False, - "tooltip": "Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation, but results are nondeterministic (same seed will not produce exactly the same result).", - }, - ), - }, - "optional": { - "input_image": (IO.IMAGE,), - }, - "hidden": { - "auth_token": "AUTH_TOKEN_COMFY_ORG", - "comfy_api_key": "API_KEY_COMFY_ORG", - "unique_id": "UNIQUE_ID", - }, - } - - @classmethod - def VALIDATE_INPUTS(cls, aspect_ratio: str): - try: - validate_aspect_ratio( - aspect_ratio, - minimum_ratio=cls.MINIMUM_RATIO, - maximum_ratio=cls.MAXIMUM_RATIO, - minimum_ratio_str=cls.MINIMUM_RATIO_STR, - maximum_ratio_str=cls.MAXIMUM_RATIO_STR, - ) - except Exception as e: - return str(e) - return True - - RETURN_TYPES = (IO.IMAGE,) - DESCRIPTION = cleandoc(__doc__ or "") # Handle potential None value - FUNCTION = "api_call" - API_NODE = True - CATEGORY = "api node/image/BFL" + DESCRIPTION = cleandoc(__doc__ or "") + BFL_PATH = "/proxy/bfl/flux-kontext-max/generate" - def api_call( - self, - prompt: str, - aspect_ratio: str, - guidance: float, - steps: int, - input_image: Optional[torch.Tensor]=None, - seed=0, - prompt_upsampling=False, - unique_id: Union[str, None] = None, - **kwargs, - ): - if input_image is None: - validate_string(prompt, strip_whitespace=False) - operation = SynchronousOperation( - endpoint=ApiEndpoint( - path="/proxy/bfl/flux-kontext-max/generate", - method=HttpMethod.POST, - request_model=BFLFluxKontextProGenerateRequest, - response_model=BFLFluxProGenerateResponse, - ), - request=BFLFluxKontextProGenerateRequest( - prompt=prompt, - prompt_upsampling=prompt_upsampling, - guidance=round(guidance, 1), - steps=steps, - seed=seed, - aspect_ratio=validate_aspect_ratio( - aspect_ratio, - minimum_ratio=self.MINIMUM_RATIO, - maximum_ratio=self.MAXIMUM_RATIO, - minimum_ratio_str=self.MINIMUM_RATIO_STR, - maximum_ratio_str=self.MAXIMUM_RATIO_STR, - ), - input_image=( - input_image - if input_image is None - else convert_image_to_base64(input_image) - ) - ), - auth_kwargs=kwargs, - ) - output_image = handle_bfl_synchronous_operation(operation, node_id=unique_id) - return (output_image,) class FluxProImageNode(ComfyNodeABC): """ @@ -1208,8 +1079,8 @@ def api_call( NODE_DISPLAY_NAME_MAPPINGS = { "FluxProUltraImageNode": "Flux 1.1 [pro] Ultra Image", # "FluxProImageNode": "Flux 1.1 [pro] Image", - "FluxKontextProImageNode": "Flux.1 Kontext Pro Image", - "FluxKontextMaxImageNode": "Flux.1 Kontext Max Image", + "FluxKontextProImageNode": "Flux.1 Kontext [pro] Image", + "FluxKontextMaxImageNode": "Flux.1 Kontext [max] Image", "FluxProExpandNode": "Flux.1 Expand Image", "FluxProFillNode": "Flux.1 Fill Image", "FluxProCannyNode": "Flux.1 Canny Control Image",