This commit is contained in:
onlyforwork2026 2026-04-30 22:08:51 -05:00 committed by GitHub
commit 7c57590aa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 551 additions and 0 deletions

View File

@ -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": []
}
}

View File

@ -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(...)

View File

@ -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,
]