[V3] convert Ideogram API nodes to the V3 schema (#9278)

* convert Ideogram API nodes to the V3 schema

* use auth_kwargs instead of auth_token/comfy_api_key
This commit is contained in:
Alexander Piskun
2025-08-22 05:06:51 +03:00
committed by GitHub
parent eb39019daa
commit 7ed73d12d1

View File

@@ -1,8 +1,8 @@
from comfy.comfy_types.node_typing import IO, ComfyNodeABC, InputTypeDict from io import BytesIO
from inspect import cleandoc from typing_extensions import override
from comfy_api.latest import ComfyExtension, io as comfy_io
from PIL import Image from PIL import Image
import numpy as np import numpy as np
import io
import torch import torch
from comfy_api_nodes.apis import ( from comfy_api_nodes.apis import (
IdeogramGenerateRequest, IdeogramGenerateRequest,
@@ -246,90 +246,81 @@ def display_image_urls_on_node(image_urls, node_id):
PromptServer.instance.send_progress_text(urls_text, node_id) PromptServer.instance.send_progress_text(urls_text, node_id)
class IdeogramV1(ComfyNodeABC): class IdeogramV1(comfy_io.ComfyNode):
"""
Generates images using the Ideogram V1 model.
"""
def __init__(self):
pass
@classmethod @classmethod
def INPUT_TYPES(cls) -> InputTypeDict: def define_schema(cls):
return { return comfy_io.Schema(
"required": { node_id="IdeogramV1",
"prompt": ( display_name="Ideogram V1",
IO.STRING, category="api node/image/Ideogram",
{ description="Generates images using the Ideogram V1 model.",
"multiline": True, inputs=[
"default": "", comfy_io.String.Input(
"tooltip": "Prompt for the image generation", "prompt",
}, multiline=True,
default="",
tooltip="Prompt for the image generation",
), ),
"turbo": ( comfy_io.Boolean.Input(
IO.BOOLEAN, "turbo",
{ default=False,
"default": False, tooltip="Whether to use turbo mode (faster generation, potentially lower quality)",
"tooltip": "Whether to use turbo mode (faster generation, potentially lower quality)",
}
), ),
}, comfy_io.Combo.Input(
"optional": { "aspect_ratio",
"aspect_ratio": ( options=list(V1_V2_RATIO_MAP.keys()),
IO.COMBO, default="1:1",
{ tooltip="The aspect ratio for image generation.",
"options": list(V1_V2_RATIO_MAP.keys()), optional=True,
"default": "1:1",
"tooltip": "The aspect ratio for image generation.",
},
), ),
"magic_prompt_option": ( comfy_io.Combo.Input(
IO.COMBO, "magic_prompt_option",
{ options=["AUTO", "ON", "OFF"],
"options": ["AUTO", "ON", "OFF"], default="AUTO",
"default": "AUTO", tooltip="Determine if MagicPrompt should be used in generation",
"tooltip": "Determine if MagicPrompt should be used in generation", optional=True,
},
), ),
"seed": ( comfy_io.Int.Input(
IO.INT, "seed",
{ default=0,
"default": 0, min=0,
"min": 0, max=2147483647,
"max": 2147483647, step=1,
"step": 1, control_after_generate=True,
"control_after_generate": True, display_mode=comfy_io.NumberDisplay.number,
"display": "number", optional=True,
},
), ),
"negative_prompt": ( comfy_io.String.Input(
IO.STRING, "negative_prompt",
{ multiline=True,
"multiline": True, default="",
"default": "", tooltip="Description of what to exclude from the image",
"tooltip": "Description of what to exclude from the image", optional=True,
},
), ),
"num_images": ( comfy_io.Int.Input(
IO.INT, "num_images",
{"default": 1, "min": 1, "max": 8, "step": 1, "display": "number"}, default=1,
min=1,
max=8,
step=1,
display_mode=comfy_io.NumberDisplay.number,
optional=True,
), ),
}, ],
"hidden": { outputs=[
"auth_token": "AUTH_TOKEN_COMFY_ORG", comfy_io.Image.Output(),
"comfy_api_key": "API_KEY_COMFY_ORG", ],
"unique_id": "UNIQUE_ID", hidden=[
}, comfy_io.Hidden.auth_token_comfy_org,
} comfy_io.Hidden.api_key_comfy_org,
comfy_io.Hidden.unique_id,
],
)
RETURN_TYPES = (IO.IMAGE,) @classmethod
FUNCTION = "api_call" async def execute(
CATEGORY = "api node/image/Ideogram" cls,
DESCRIPTION = cleandoc(__doc__ or "")
API_NODE = True
async def api_call(
self,
prompt, prompt,
turbo=False, turbo=False,
aspect_ratio="1:1", aspect_ratio="1:1",
@@ -337,13 +328,15 @@ class IdeogramV1(ComfyNodeABC):
seed=0, seed=0,
negative_prompt="", negative_prompt="",
num_images=1, num_images=1,
unique_id=None,
**kwargs,
): ):
# Determine the model based on turbo setting # Determine the model based on turbo setting
aspect_ratio = V1_V2_RATIO_MAP.get(aspect_ratio, None) aspect_ratio = V1_V2_RATIO_MAP.get(aspect_ratio, None)
model = "V_1_TURBO" if turbo else "V_1" model = "V_1_TURBO" if turbo else "V_1"
auth = {
"auth_token": cls.hidden.auth_token_comfy_org,
"comfy_api_key": cls.hidden.api_key_comfy_org,
}
operation = SynchronousOperation( operation = SynchronousOperation(
endpoint=ApiEndpoint( endpoint=ApiEndpoint(
path="/proxy/ideogram/generate", path="/proxy/ideogram/generate",
@@ -364,7 +357,7 @@ class IdeogramV1(ComfyNodeABC):
negative_prompt=negative_prompt if negative_prompt else None, negative_prompt=negative_prompt if negative_prompt else None,
) )
), ),
auth_kwargs=kwargs, auth_kwargs=auth,
) )
response = await operation.execute() response = await operation.execute()
@@ -377,93 +370,85 @@ class IdeogramV1(ComfyNodeABC):
if not image_urls: if not image_urls:
raise Exception("No image URLs were generated in the response") raise Exception("No image URLs were generated in the response")
display_image_urls_on_node(image_urls, unique_id) display_image_urls_on_node(image_urls, cls.hidden.unique_id)
return (await download_and_process_images(image_urls),) return comfy_io.NodeOutput(await download_and_process_images(image_urls))
class IdeogramV2(ComfyNodeABC): class IdeogramV2(comfy_io.ComfyNode):
"""
Generates images using the Ideogram V2 model.
"""
def __init__(self):
pass
@classmethod @classmethod
def INPUT_TYPES(cls) -> InputTypeDict: def define_schema(cls):
return { return comfy_io.Schema(
"required": { node_id="IdeogramV2",
"prompt": ( display_name="Ideogram V2",
IO.STRING, category="api node/image/Ideogram",
{ description="Generates images using the Ideogram V2 model.",
"multiline": True, inputs=[
"default": "", comfy_io.String.Input(
"tooltip": "Prompt for the image generation", "prompt",
}, multiline=True,
default="",
tooltip="Prompt for the image generation",
), ),
"turbo": ( comfy_io.Boolean.Input(
IO.BOOLEAN, "turbo",
{ default=False,
"default": False, tooltip="Whether to use turbo mode (faster generation, potentially lower quality)",
"tooltip": "Whether to use turbo mode (faster generation, potentially lower quality)",
}
), ),
}, comfy_io.Combo.Input(
"optional": { "aspect_ratio",
"aspect_ratio": ( options=list(V1_V2_RATIO_MAP.keys()),
IO.COMBO, default="1:1",
{ tooltip="The aspect ratio for image generation. Ignored if resolution is not set to AUTO.",
"options": list(V1_V2_RATIO_MAP.keys()), optional=True,
"default": "1:1",
"tooltip": "The aspect ratio for image generation. Ignored if resolution is not set to AUTO.",
},
), ),
"resolution": ( comfy_io.Combo.Input(
IO.COMBO, "resolution",
{ options=list(V1_V1_RES_MAP.keys()),
"options": list(V1_V1_RES_MAP.keys()), default="Auto",
"default": "Auto", tooltip="The resolution for image generation. "
"tooltip": "The resolution for image generation. If not set to AUTO, this overrides the aspect_ratio setting.", "If not set to AUTO, this overrides the aspect_ratio setting.",
}, optional=True,
), ),
"magic_prompt_option": ( comfy_io.Combo.Input(
IO.COMBO, "magic_prompt_option",
{ options=["AUTO", "ON", "OFF"],
"options": ["AUTO", "ON", "OFF"], default="AUTO",
"default": "AUTO", tooltip="Determine if MagicPrompt should be used in generation",
"tooltip": "Determine if MagicPrompt should be used in generation", optional=True,
},
), ),
"seed": ( comfy_io.Int.Input(
IO.INT, "seed",
{ default=0,
"default": 0, min=0,
"min": 0, max=2147483647,
"max": 2147483647, step=1,
"step": 1, control_after_generate=True,
"control_after_generate": True, display_mode=comfy_io.NumberDisplay.number,
"display": "number", optional=True,
},
), ),
"style_type": ( comfy_io.Combo.Input(
IO.COMBO, "style_type",
{ options=["AUTO", "GENERAL", "REALISTIC", "DESIGN", "RENDER_3D", "ANIME"],
"options": ["AUTO", "GENERAL", "REALISTIC", "DESIGN", "RENDER_3D", "ANIME"], default="NONE",
"default": "NONE", tooltip="Style type for generation (V2 only)",
"tooltip": "Style type for generation (V2 only)", optional=True,
},
), ),
"negative_prompt": ( comfy_io.String.Input(
IO.STRING, "negative_prompt",
{ multiline=True,
"multiline": True, default="",
"default": "", tooltip="Description of what to exclude from the image",
"tooltip": "Description of what to exclude from the image", optional=True,
},
), ),
"num_images": ( comfy_io.Int.Input(
IO.INT, "num_images",
{"default": 1, "min": 1, "max": 8, "step": 1, "display": "number"}, default=1,
min=1,
max=8,
step=1,
display_mode=comfy_io.NumberDisplay.number,
optional=True,
), ),
#"color_palette": ( #"color_palette": (
# IO.STRING, # IO.STRING,
@@ -473,22 +458,20 @@ class IdeogramV2(ComfyNodeABC):
# "tooltip": "Color palette preset name or hex colors with weights", # "tooltip": "Color palette preset name or hex colors with weights",
# }, # },
#), #),
}, ],
"hidden": { outputs=[
"auth_token": "AUTH_TOKEN_COMFY_ORG", comfy_io.Image.Output(),
"comfy_api_key": "API_KEY_COMFY_ORG", ],
"unique_id": "UNIQUE_ID", hidden=[
}, comfy_io.Hidden.auth_token_comfy_org,
} comfy_io.Hidden.api_key_comfy_org,
comfy_io.Hidden.unique_id,
],
)
RETURN_TYPES = (IO.IMAGE,) @classmethod
FUNCTION = "api_call" async def execute(
CATEGORY = "api node/image/Ideogram" cls,
DESCRIPTION = cleandoc(__doc__ or "")
API_NODE = True
async def api_call(
self,
prompt, prompt,
turbo=False, turbo=False,
aspect_ratio="1:1", aspect_ratio="1:1",
@@ -499,8 +482,6 @@ class IdeogramV2(ComfyNodeABC):
negative_prompt="", negative_prompt="",
num_images=1, num_images=1,
color_palette="", color_palette="",
unique_id=None,
**kwargs,
): ):
aspect_ratio = V1_V2_RATIO_MAP.get(aspect_ratio, None) aspect_ratio = V1_V2_RATIO_MAP.get(aspect_ratio, None)
resolution = V1_V1_RES_MAP.get(resolution, None) resolution = V1_V1_RES_MAP.get(resolution, None)
@@ -517,6 +498,10 @@ class IdeogramV2(ComfyNodeABC):
else: else:
final_aspect_ratio = aspect_ratio if aspect_ratio != "ASPECT_1_1" else None final_aspect_ratio = aspect_ratio if aspect_ratio != "ASPECT_1_1" else None
auth = {
"auth_token": cls.hidden.auth_token_comfy_org,
"comfy_api_key": cls.hidden.api_key_comfy_org,
}
operation = SynchronousOperation( operation = SynchronousOperation(
endpoint=ApiEndpoint( endpoint=ApiEndpoint(
path="/proxy/ideogram/generate", path="/proxy/ideogram/generate",
@@ -540,7 +525,7 @@ class IdeogramV2(ComfyNodeABC):
color_palette=color_palette if color_palette else None, color_palette=color_palette if color_palette else None,
) )
), ),
auth_kwargs=kwargs, auth_kwargs=auth,
) )
response = await operation.execute() response = await operation.execute()
@@ -553,108 +538,99 @@ class IdeogramV2(ComfyNodeABC):
if not image_urls: if not image_urls:
raise Exception("No image URLs were generated in the response") raise Exception("No image URLs were generated in the response")
display_image_urls_on_node(image_urls, unique_id) display_image_urls_on_node(image_urls, cls.hidden.unique_id)
return (await download_and_process_images(image_urls),) return comfy_io.NodeOutput(await download_and_process_images(image_urls))
class IdeogramV3(ComfyNodeABC):
"""
Generates images using the Ideogram V3 model. Supports both regular image generation from text prompts and image editing with mask.
"""
def __init__(self): class IdeogramV3(comfy_io.ComfyNode):
pass
@classmethod @classmethod
def INPUT_TYPES(cls) -> InputTypeDict: def define_schema(cls):
return { return comfy_io.Schema(
"required": { node_id="IdeogramV3",
"prompt": ( display_name="Ideogram V3",
IO.STRING, category="api node/image/Ideogram",
{ description="Generates images using the Ideogram V3 model. "
"multiline": True, "Supports both regular image generation from text prompts and image editing with mask.",
"default": "", inputs=[
"tooltip": "Prompt for the image generation or editing", comfy_io.String.Input(
}, "prompt",
multiline=True,
default="",
tooltip="Prompt for the image generation or editing",
), ),
}, comfy_io.Image.Input(
"optional": { "image",
"image": ( tooltip="Optional reference image for image editing.",
IO.IMAGE, optional=True,
{
"default": None,
"tooltip": "Optional reference image for image editing.",
},
), ),
"mask": ( comfy_io.Mask.Input(
IO.MASK, "mask",
{ tooltip="Optional mask for inpainting (white areas will be replaced)",
"default": None, optional=True,
"tooltip": "Optional mask for inpainting (white areas will be replaced)",
},
), ),
"aspect_ratio": ( comfy_io.Combo.Input(
IO.COMBO, "aspect_ratio",
{ options=list(V3_RATIO_MAP.keys()),
"options": list(V3_RATIO_MAP.keys()), default="1:1",
"default": "1:1", tooltip="The aspect ratio for image generation. Ignored if resolution is not set to Auto.",
"tooltip": "The aspect ratio for image generation. Ignored if resolution is not set to Auto.", optional=True,
},
), ),
"resolution": ( comfy_io.Combo.Input(
IO.COMBO, "resolution",
{ options=V3_RESOLUTIONS,
"options": V3_RESOLUTIONS, default="Auto",
"default": "Auto", tooltip="The resolution for image generation. "
"tooltip": "The resolution for image generation. If not set to Auto, this overrides the aspect_ratio setting.", "If not set to Auto, this overrides the aspect_ratio setting.",
}, optional=True,
), ),
"magic_prompt_option": ( comfy_io.Combo.Input(
IO.COMBO, "magic_prompt_option",
{ options=["AUTO", "ON", "OFF"],
"options": ["AUTO", "ON", "OFF"], default="AUTO",
"default": "AUTO", tooltip="Determine if MagicPrompt should be used in generation",
"tooltip": "Determine if MagicPrompt should be used in generation", optional=True,
},
), ),
"seed": ( comfy_io.Int.Input(
IO.INT, "seed",
{ default=0,
"default": 0, min=0,
"min": 0, max=2147483647,
"max": 2147483647, step=1,
"step": 1, control_after_generate=True,
"control_after_generate": True, display_mode=comfy_io.NumberDisplay.number,
"display": "number", optional=True,
},
), ),
"num_images": ( comfy_io.Int.Input(
IO.INT, "num_images",
{"default": 1, "min": 1, "max": 8, "step": 1, "display": "number"}, default=1,
min=1,
max=8,
step=1,
display_mode=comfy_io.NumberDisplay.number,
optional=True,
), ),
"rendering_speed": ( comfy_io.Combo.Input(
IO.COMBO, "rendering_speed",
{ options=["BALANCED", "TURBO", "QUALITY"],
"options": ["BALANCED", "TURBO", "QUALITY"], default="BALANCED",
"default": "BALANCED", tooltip="Controls the trade-off between generation speed and quality",
"tooltip": "Controls the trade-off between generation speed and quality", optional=True,
},
), ),
}, ],
"hidden": { outputs=[
"auth_token": "AUTH_TOKEN_COMFY_ORG", comfy_io.Image.Output(),
"comfy_api_key": "API_KEY_COMFY_ORG", ],
"unique_id": "UNIQUE_ID", hidden=[
}, comfy_io.Hidden.auth_token_comfy_org,
} comfy_io.Hidden.api_key_comfy_org,
comfy_io.Hidden.unique_id,
],
)
RETURN_TYPES = (IO.IMAGE,) @classmethod
FUNCTION = "api_call" async def execute(
CATEGORY = "api node/image/Ideogram" cls,
DESCRIPTION = cleandoc(__doc__ or "")
API_NODE = True
async def api_call(
self,
prompt, prompt,
image=None, image=None,
mask=None, mask=None,
@@ -664,9 +640,11 @@ class IdeogramV3(ComfyNodeABC):
seed=0, seed=0,
num_images=1, num_images=1,
rendering_speed="BALANCED", rendering_speed="BALANCED",
unique_id=None,
**kwargs,
): ):
auth = {
"auth_token": cls.hidden.auth_token_comfy_org,
"comfy_api_key": cls.hidden.api_key_comfy_org,
}
# Check if both image and mask are provided for editing mode # Check if both image and mask are provided for editing mode
if image is not None and mask is not None: if image is not None and mask is not None:
# Edit mode # Edit mode
@@ -686,7 +664,7 @@ class IdeogramV3(ComfyNodeABC):
# Process image # Process image
img_np = (input_tensor.numpy() * 255).astype(np.uint8) img_np = (input_tensor.numpy() * 255).astype(np.uint8)
img = Image.fromarray(img_np) img = Image.fromarray(img_np)
img_byte_arr = io.BytesIO() img_byte_arr = BytesIO()
img.save(img_byte_arr, format="PNG") img.save(img_byte_arr, format="PNG")
img_byte_arr.seek(0) img_byte_arr.seek(0)
img_binary = img_byte_arr img_binary = img_byte_arr
@@ -695,7 +673,7 @@ class IdeogramV3(ComfyNodeABC):
# Process mask - white areas will be replaced # Process mask - white areas will be replaced
mask_np = (mask.squeeze().cpu().numpy() * 255).astype(np.uint8) mask_np = (mask.squeeze().cpu().numpy() * 255).astype(np.uint8)
mask_img = Image.fromarray(mask_np) mask_img = Image.fromarray(mask_np)
mask_byte_arr = io.BytesIO() mask_byte_arr = BytesIO()
mask_img.save(mask_byte_arr, format="PNG") mask_img.save(mask_byte_arr, format="PNG")
mask_byte_arr.seek(0) mask_byte_arr.seek(0)
mask_binary = mask_byte_arr mask_binary = mask_byte_arr
@@ -729,7 +707,7 @@ class IdeogramV3(ComfyNodeABC):
"mask": mask_binary, "mask": mask_binary,
}, },
content_type="multipart/form-data", content_type="multipart/form-data",
auth_kwargs=kwargs, auth_kwargs=auth,
) )
elif image is not None or mask is not None: elif image is not None or mask is not None:
@@ -770,7 +748,7 @@ class IdeogramV3(ComfyNodeABC):
response_model=IdeogramGenerateResponse, response_model=IdeogramGenerateResponse,
), ),
request=gen_request, request=gen_request,
auth_kwargs=kwargs, auth_kwargs=auth,
) )
# Execute the operation and process response # Execute the operation and process response
@@ -784,18 +762,18 @@ class IdeogramV3(ComfyNodeABC):
if not image_urls: if not image_urls:
raise Exception("No image URLs were generated in the response") raise Exception("No image URLs were generated in the response")
display_image_urls_on_node(image_urls, unique_id) display_image_urls_on_node(image_urls, cls.hidden.unique_id)
return (await download_and_process_images(image_urls),) return comfy_io.NodeOutput(await download_and_process_images(image_urls))
NODE_CLASS_MAPPINGS = { class IdeogramExtension(ComfyExtension):
"IdeogramV1": IdeogramV1, @override
"IdeogramV2": IdeogramV2, async def get_node_list(self) -> list[type[comfy_io.ComfyNode]]:
"IdeogramV3": IdeogramV3, return [
} IdeogramV1,
IdeogramV2,
IdeogramV3,
]
NODE_DISPLAY_NAME_MAPPINGS = { async def comfy_entrypoint() -> IdeogramExtension:
"IdeogramV1": "Ideogram V1", return IdeogramExtension()
"IdeogramV2": "Ideogram V2",
"IdeogramV3": "Ideogram V3",
}