From b1f5a3254b3c3602210c53b9c8a2d4a2e6413c16 Mon Sep 17 00:00:00 2001 From: onlyforthesis Date: Mon, 27 Apr 2026 10:00:29 +0800 Subject: [PATCH 1/2] Add WaveSpeed text-to-image node using Flux models Adds a new `WavespeedTextToImageNode` that generates images via WaveSpeed's fast Flux inference API (flux-dev, flux-dev-fp8, flux-schnell, flux-schnell-fp8). Also adds the corresponding `WavespeedTextToImageRequest` Pydantic model to the wavespeed API module. Co-Authored-By: Claude Sonnet 4.6 --- comfy_api_nodes/apis/wavespeed.py | 16 +++ comfy_api_nodes/nodes_wavespeed.py | 153 +++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) diff --git a/comfy_api_nodes/apis/wavespeed.py b/comfy_api_nodes/apis/wavespeed.py index 07a7bfa5d..c356d8522 100644 --- a/comfy_api_nodes/apis/wavespeed.py +++ b/comfy_api_nodes/apis/wavespeed.py @@ -1,6 +1,22 @@ +from typing import Optional + from pydantic import BaseModel, Field +class WavespeedTextToImageRequest(BaseModel): + prompt: str = Field(...) + width: int = Field(1024) + height: int = Field(1024) + num_inference_steps: int = Field(28) + guidance_scale: float = Field(3.5) + seed: int = Field(-1) + enable_safety_checker: bool = Field(True) + enable_sync_mode: bool = Field(False) + num_images: int = Field(1) + output_format: str = Field("jpeg") + negative_prompt: Optional[str] = Field(None) + + class SeedVR2ImageRequest(BaseModel): image: str = Field(...) target_resolution: str = Field(...) diff --git a/comfy_api_nodes/nodes_wavespeed.py b/comfy_api_nodes/nodes_wavespeed.py index c59fafd3b..7d878321e 100644 --- a/comfy_api_nodes/nodes_wavespeed.py +++ b/comfy_api_nodes/nodes_wavespeed.py @@ -6,6 +6,7 @@ from comfy_api_nodes.apis.wavespeed import ( TaskCreatedResponse, TaskResultResponse, SeedVR2ImageRequest, + WavespeedTextToImageRequest, ) from comfy_api_nodes.util import ( ApiEndpoint, @@ -165,10 +166,162 @@ class WavespeedImageUpscaleNode(IO.ComfyNode): return IO.NodeOutput(await download_url_to_image_tensor(final_response.data.outputs[0])) +_TEXT_TO_IMAGE_MODELS = [ + "wavespeed-ai/flux-dev", + "wavespeed-ai/flux-dev-fp8", + "wavespeed-ai/flux-schnell", + "wavespeed-ai/flux-schnell-fp8", +] + +_MODEL_ENDPOINT = { + "wavespeed-ai/flux-dev": "flux-dev", + "wavespeed-ai/flux-dev-fp8": "flux-dev-fp8", + "wavespeed-ai/flux-schnell": "flux-schnell", + "wavespeed-ai/flux-schnell-fp8": "flux-schnell-fp8", +} + +_SCHNELL_MODELS = {"wavespeed-ai/flux-schnell", "wavespeed-ai/flux-schnell-fp8"} + + +class WavespeedTextToImageNode(IO.ComfyNode): + @classmethod + def define_schema(cls): + return IO.Schema( + node_id="WavespeedTextToImageNode", + display_name="WaveSpeed Text to Image", + category="api node/image/WaveSpeed", + description="Generate images from text prompts using WaveSpeed's fast Flux inference.", + inputs=[ + IO.Combo.Input("model", options=_TEXT_TO_IMAGE_MODELS), + IO.String.Input( + "prompt", + multiline=True, + default="", + tooltip="Text prompt describing the image to generate.", + ), + IO.Int.Input( + "width", + default=1024, + min=256, + max=2048, + step=64, + tooltip="Image width in pixels.", + ), + IO.Int.Input( + "height", + default=1024, + min=256, + max=2048, + step=64, + tooltip="Image height in pixels.", + ), + IO.Int.Input( + "steps", + default=28, + min=1, + max=50, + tooltip="Number of inference steps. Schnell models work well with 4 steps.", + ), + IO.Float.Input( + "guidance_scale", + default=3.5, + min=0.0, + max=20.0, + step=0.1, + tooltip="Guidance scale (CFG). Not used for Schnell models.", + advanced=True, + ), + IO.Int.Input( + "seed", + default=0, + min=0, + max=0xFFFFFFFFFFFFFFFF, + control_after_generate=True, + tooltip="Seed for reproducibility. Use -1 for random.", + ), + IO.Boolean.Input( + "safety_checker", + default=True, + tooltip="Enable the safety checker.", + advanced=True, + ), + ], + outputs=[IO.Image.Output()], + hidden=[ + IO.Hidden.auth_token_comfy_org, + IO.Hidden.api_key_comfy_org, + IO.Hidden.unique_id, + ], + is_api_node=True, + price_badge=IO.PriceBadge( + depends_on=IO.PriceBadgeDepends(widgets=["model"]), + expr=""" + ( + $prices := { + "wavespeed-ai/flux-dev": 0.003, + "wavespeed-ai/flux-dev-fp8": 0.003, + "wavespeed-ai/flux-schnell": 0.001, + "wavespeed-ai/flux-schnell-fp8": 0.001 + }; + {"type":"usd", "usd": $lookup($prices, widgets.model)} + ) + """, + ), + ) + + @classmethod + async def execute( + cls, + model: str, + prompt: str, + width: int, + height: int, + steps: int, + guidance_scale: float, + seed: int, + safety_checker: bool, + ) -> IO.NodeOutput: + endpoint_name = _MODEL_ENDPOINT[model] + is_schnell = model in _SCHNELL_MODELS + initial_res = await sync_op( + cls, + ApiEndpoint( + path=f"/proxy/wavespeed/api/v3/wavespeed-ai/{endpoint_name}", + method="POST", + ), + response_model=TaskCreatedResponse, + data=WavespeedTextToImageRequest( + prompt=prompt, + width=width, + height=height, + num_inference_steps=steps, + guidance_scale=1.0 if is_schnell else guidance_scale, + seed=seed, + enable_safety_checker=safety_checker, + ), + ) + if initial_res.code != 200: + raise ValueError(f"Task creation failed with code={initial_res.code} and message={initial_res.message}") + final_response = await poll_op( + cls, + ApiEndpoint(path=f"/proxy/wavespeed/api/v3/predictions/{initial_res.data.id}/result"), + response_model=TaskResultResponse, + status_extractor=lambda x: "failed" if x.data is None else x.data.status, + poll_interval=3.0, + max_poll_attempts=200, + ) + if final_response.code != 200: + raise ValueError( + f"Task processing failed with code={final_response.code} and message={final_response.message}" + ) + return IO.NodeOutput(await download_url_to_image_tensor(final_response.data.outputs[0])) + + class WavespeedExtension(ComfyExtension): @override async def get_node_list(self) -> list[type[IO.ComfyNode]]: return [ + WavespeedTextToImageNode, WavespeedFlashVSRNode, WavespeedImageUpscaleNode, ] From 6817d1303d0c5ffd1b725ac7b704d7ed9869ecda Mon Sep 17 00:00:00 2001 From: onlyforthesis Date: Mon, 27 Apr 2026 10:11:28 +0800 Subject: [PATCH 2/2] Add blueprint for WaveSpeed Flux text-to-image Adds a ComfyUI blueprint that wraps the new WavespeedTextToImageNode, exposing prompt, model, width, and height as user-facing inputs. Co-Authored-By: Claude Sonnet 4.6 --- .../Text to Image (WaveSpeed Flux).json | 382 ++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 blueprints/Text to Image (WaveSpeed Flux).json diff --git a/blueprints/Text to Image (WaveSpeed Flux).json b/blueprints/Text to Image (WaveSpeed Flux).json new file mode 100644 index 000000000..80dbc64cd --- /dev/null +++ b/blueprints/Text to Image (WaveSpeed Flux).json @@ -0,0 +1,382 @@ +{ + "revision": 0, + "last_node_id": 1, + "last_link_id": 5, + "nodes": [ + { + "id": 1, + "type": "d7434b88-8db4-4db4-833f-1f5f729bc8c3", + "pos": [ + 0, + 0 + ], + "size": [ + 400, + 310 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "prompt", + "type": "STRING", + "widget": { + "name": "prompt" + }, + "link": null + }, + { + "name": "model", + "type": "COMBO", + "widget": { + "name": "model" + }, + "link": null + }, + { + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": null + }, + { + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [] + } + ], + "properties": { + "proxyWidgets": [ + [ + "-1", + "prompt" + ], + [ + "-1", + "model" + ], + [ + "-1", + "width" + ], + [ + "-1", + "height" + ], + [ + "1", + "seed" + ] + ], + "cnr_id": "comfy-core", + "ver": "0.18.1", + "ue_properties": { + "widget_ue_connectable": { + "prompt": true, + "model": true + }, + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "", + "wavespeed-ai/flux-dev", + 1024, + 1024 + ], + "title": "Text to Image (WaveSpeed Flux)" + } + ], + "links": [], + "version": 0.4, + "definitions": { + "subgraphs": [ + { + "id": "d7434b88-8db4-4db4-833f-1f5f729bc8c3", + "version": 1, + "state": { + "lastGroupId": 0, + "lastNodeId": 1, + "lastLinkId": 5, + "lastRerouteId": 0 + }, + "revision": 0, + "config": {}, + "name": "Text to Image (WaveSpeed Flux)", + "inputNode": { + "id": -10, + "bounding": [ + -350, + 100, + 120, + 160 + ] + }, + "outputNode": { + "id": -20, + "bounding": [ + 550, + 200, + 120, + 60 + ] + }, + "inputs": [ + { + "id": "5a5065bf-290c-4592-a3e1-5582603295d3", + "name": "prompt", + "type": "STRING", + "linkIds": [ + 1 + ], + "pos": [ + -230, + 120 + ] + }, + { + "id": "14adcda4-e0b1-4cfb-95e2-b468774548a2", + "name": "model", + "type": "COMBO", + "linkIds": [ + 2 + ], + "pos": [ + -230, + 140 + ] + }, + { + "id": "aed38934-ba2f-4a5e-8829-60ce4ce7b80b", + "name": "width", + "type": "INT", + "linkIds": [ + 3 + ], + "pos": [ + -230, + 160 + ] + }, + { + "id": "a5c7d360-174d-4ab8-b646-311fd5544e5f", + "name": "height", + "type": "INT", + "linkIds": [ + 4 + ], + "pos": [ + -230, + 180 + ] + } + ], + "outputs": [ + { + "id": "8654951d-aeb3-4ca1-82b1-0eebffc506ae", + "name": "IMAGE", + "type": "IMAGE", + "linkIds": [ + 5 + ], + "localized_name": "IMAGE", + "pos": [ + 530, + 220 + ] + } + ], + "widgets": [], + "nodes": [ + { + "id": 1, + "type": "WavespeedTextToImageNode", + "pos": [ + 100, + 100 + ], + "size": [ + 380, + 390 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "localized_name": "model", + "name": "model", + "type": "COMBO", + "widget": { + "name": "model" + }, + "link": 2 + }, + { + "localized_name": "prompt", + "name": "prompt", + "type": "STRING", + "widget": { + "name": "prompt" + }, + "link": 1 + }, + { + "localized_name": "width", + "name": "width", + "type": "INT", + "widget": { + "name": "width" + }, + "link": 3 + }, + { + "localized_name": "height", + "name": "height", + "type": "INT", + "widget": { + "name": "height" + }, + "link": 4 + }, + { + "localized_name": "steps", + "name": "steps", + "type": "INT", + "widget": { + "name": "steps" + }, + "link": null + }, + { + "localized_name": "guidance_scale", + "name": "guidance_scale", + "type": "FLOAT", + "widget": { + "name": "guidance_scale" + }, + "link": null + }, + { + "localized_name": "seed", + "name": "seed", + "type": "INT", + "widget": { + "name": "seed" + }, + "link": null + }, + { + "localized_name": "safety_checker", + "name": "safety_checker", + "type": "BOOLEAN", + "widget": { + "name": "safety_checker" + }, + "link": null + } + ], + "outputs": [ + { + "localized_name": "IMAGE", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 5 + ] + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.18.1", + "Node name for S&R": "WavespeedTextToImageNode", + "ue_properties": { + "widget_ue_connectable": {}, + "input_ue_unconnectable": {} + } + }, + "widgets_values": [ + "wavespeed-ai/flux-dev", + "", + 1024, + 1024, + 28, + 3.5, + 0, + "randomize", + true + ] + } + ], + "groups": [], + "links": [ + { + "id": 1, + "origin_id": -10, + "origin_slot": 0, + "target_id": 1, + "target_slot": 1, + "type": "STRING" + }, + { + "id": 2, + "origin_id": -10, + "origin_slot": 1, + "target_id": 1, + "target_slot": 0, + "type": "COMBO" + }, + { + "id": 3, + "origin_id": -10, + "origin_slot": 2, + "target_id": 1, + "target_slot": 2, + "type": "INT" + }, + { + "id": 4, + "origin_id": -10, + "origin_slot": 3, + "target_id": 1, + "target_slot": 3, + "type": "INT" + }, + { + "id": 5, + "origin_id": 1, + "origin_slot": 0, + "target_id": -20, + "target_slot": 0, + "type": "IMAGE" + } + ], + "extra": { + "workflowRendererVersion": "LG" + }, + "category": "Image generation and editing/Text to image" + } + ] + }, + "extra": { + "ds": { + "scale": 1.0, + "offset": [0, 0] + }, + "ue_links": [] + } +}