Added Ideogram and Minimax back in.

This commit is contained in:
Robin Huang 2025-04-24 15:40:34 -07:00
parent ac559a2b76
commit 1d24f0a68f
3 changed files with 249 additions and 14 deletions

View File

@ -308,6 +308,12 @@ class IdeogramGenerateRequest(BaseModel):
)
class IdeogramGenerateRequest(BaseModel):
image_request: ImageRequest = Field(
..., description='The image generation request parameters.'
)
class Datum(BaseModel):
prompt: Optional[str] = Field(
None, description='The prompt used to generate this image.'
@ -845,16 +851,75 @@ class KlingVirtualTryOnResponse(BaseModel):
data: Optional[Data6] = None
class ResourcePackType(str, Enum):
decreasing_total = 'decreasing_total'
constant_period = 'constant_period'
class KlingRequestError(KlingErrorResponse):
code: Optional[Code2] = Field(
None,
description='- 1200: Invalid request parameters\n- 1201: Invalid parameters\n- 1202: Invalid request method\n- 1203: Requested resource does not exist\n',
)
class Code3(Enum):
int_5000 = 5000
int_5001 = 5001
int_5002 = 5002
class KlingServerError(KlingErrorResponse):
code: Optional[Code3] = Field(
None,
description='- 5000: Internal server error\n- 5001: Service temporarily unavailable\n- 5002: Server internal timeout\n',
)
class Code4(Enum):
int_1300 = 1300
int_1301 = 1301
int_1302 = 1302
int_1303 = 1303
int_1304 = 1304
class KlingStrategyError(KlingErrorResponse):
code: Optional[Code4] = Field(
None,
description='- 1300: Trigger platform strategy\n- 1301: Trigger content security policy\n- 1302: API request too frequent\n- 1303: Concurrency/QPS exceeds limit\n- 1304: Trigger IP whitelist policy\n',
)
class MinimaxBaseResponse(BaseModel):
status_code: int = Field(
...,
description='Status code. 0 indicates success, other values indicate errors.',
)
status_msg: str = Field(
..., description='Specific error details or success message.'
)
class File(BaseModel):
bytes: Optional[int] = Field(None, description='File size in bytes')
created_at: Optional[int] = Field(
None, description='Unix timestamp when the file was created, in seconds'
)
download_url: Optional[str] = Field(
None, description='The URL to download the video'
)
file_id: Optional[int] = Field(None, description='Unique identifier for the file')
filename: Optional[str] = Field(None, description='The name of the file')
purpose: Optional[str] = Field(None, description='The purpose of using the file')
class MinimaxFileRetrieveResponse(BaseModel):
base_resp: MinimaxBaseResponse
file: File
class Status(str, Enum):
toBeOnline = 'toBeOnline'
online = 'online'
expired = 'expired'
runOut = 'runOut'
Queueing = 'Queueing'
Preparing = 'Preparing'
Processing = 'Processing'
Success = 'Success'
Fail = 'Fail'
class ResourcePackSubscribeInfo(BaseModel):

View File

@ -97,7 +97,6 @@ import io
import socket
from typing import Dict, Type, Optional, Any, TypeVar, Generic, Callable, Tuple
from enum import Enum
import time
import json
import requests
from urllib.parse import urljoin, urlparse

View File

@ -1,14 +1,23 @@
import io
from inspect import cleandoc
from comfy.comfy_types.node_typing import FileLocator
from typing import Literal
from comfy.utils import common_upscale
from comfy.comfy_types.node_typing import IO, ComfyNodeABC, InputTypeDict
from comfy_api_nodes.apis import (
OpenAIImageGenerationRequest,
OpenAIImageEditRequest,
OpenAIImageGenerationResponse
OpenAIImageGenerationResponse,
MinimaxVideoGenerationRequest,
MinimaxVideoGenerationResponse,
MinimaxFileRetrieveResponse,
MinimaxTaskResultResponse,
IdeogramGenerateRequest,
IdeogramGenerateResponse,
ImageRequest,
Model
)
from comfy_api_nodes.apis.client import ApiEndpoint, HttpMethod, SynchronousOperation
from comfy_api_nodes.apis.client import ApiEndpoint, HttpMethod, SynchronousOperation, PollingOperation, EmptyRequest
import numpy as np
from PIL import Image
@ -16,6 +25,11 @@ import requests
import torch
import math
import base64
import logging
import json
import av
import os
import folder_paths
def downscale_input(image):
samples = image.movedim(-1,1)
@ -428,14 +442,168 @@ class OpenAIGPTImage1(ComfyNodeABC):
return (img_tensor,)
class MinimaxVideoNode:
class IdeogramTextToImage(ComfyNodeABC):
"""
Generates images synchronously based on a given prompt and optional parameters.
Images links are available for a limited period of time; if you would like to keep the image, you must download it.
"""
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls) -> InputTypeDict:
"""
Return a dictionary which contains config for all input fields.
Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
Input types "INT", "STRING" or "FLOAT" are special values for fields on the node.
The type can be a list for selection.
Returns: `dict`:
- Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required`
- Value input_fields (`dict`): Contains input fields config:
* Key field_name (`string`): Name of a entry-point method's argument
* Value field_config (`tuple`):
+ First value is a string indicate the type of field or a list for selection.
+ Secound value is a config for type "INT", "STRING" or "FLOAT".
"""
return {
"required": {
"prompt": (IO.STRING, {
"multiline": True,
"default": "",
"tooltip": "Prompt for the image generation",
}),
"model": (IO.COMBO, { "options": ["V_2", "V_2_TURBO", "V_1", "V_1_TURBO"], "default": "V_2", "tooltip": "Model to use for image generation"}),
},
"optional": {
"aspect_ratio": (IO.COMBO, { "options": ["ASPECT_1_1", "ASPECT_4_3", "ASPECT_3_4", "ASPECT_16_9", "ASPECT_9_16", "ASPECT_2_1", "ASPECT_1_2", "ASPECT_3_2", "ASPECT_2_3", "ASPECT_4_5", "ASPECT_5_4"], "default": "ASPECT_1_1", "tooltip": "The aspect ratio for image generation. Cannot be used with resolution"
}),
"resolution": (IO.COMBO, { "options": ["1024x1024", "1024x1792", "1792x1024"],
"default": "1024x1024",
"tooltip": "The resolution for image generation (V2 only). Cannot be used with aspect_ratio"
}),
"magic_prompt_option": (IO.COMBO, { "options": ["AUTO", "ON", "OFF"],
"default": "AUTO",
"tooltip": "Determine if MagicPrompt should be used in generation"
}),
"seed": (IO.INT, {
"default": 0,
"min": 0,
"max": 2147483647,
"step": 1,
"display": "number"
}),
"style_type": (IO.COMBO, { "options": ["NONE", "ANIME", "CINEMATIC", "CREATIVE", "DIGITAL_ART", "PHOTOGRAPHIC"],
"default": "NONE",
"tooltip": "Style type for generation (V2+ only)"
}),
"negative_prompt": (IO.STRING, {
"multiline": True,
"default": "",
"tooltip": "Description of what to exclude from the image (V1/V2 only)"
}),
"num_images": (IO.INT, {
"default": 1,
"min": 1,
"max": 8,
"step": 1,
"display": "number"
}),
"color_palette": (IO.STRING, {
"multiline": False,
"default": "",
"tooltip": "Color palette preset name or hex colors with weights (V2/V2_TURBO only)"
}),
},
"hidden": {
"auth_token": "AUTH_TOKEN_COMFY_ORG"
}
}
RETURN_TYPES = (IO.IMAGE,)
DESCRIPTION = cleandoc(__doc__ or "") # Handle potential None value
FUNCTION = "api_call"
API_NODE = True
CATEGORY = "Example"
def api_call(self, prompt, model, aspect_ratio=None, resolution=None,
magic_prompt_option="AUTO", seed=0, style_type="NONE",
negative_prompt="", num_images=1, color_palette="", auth_token=None):
import torch
from PIL import Image
import io
import numpy as np
import requests
operation = SynchronousOperation(
endpoint=ApiEndpoint(
path="/proxy/ideogram/generate",
method=HttpMethod.POST,
request_model=IdeogramGenerateRequest,
response_model=IdeogramGenerateResponse
),
request=IdeogramGenerateRequest(
image_request=ImageRequest(
prompt=prompt,
model=model,
num_images=num_images,
seed=seed,
aspect_ratio=aspect_ratio if aspect_ratio != "ASPECT_1_1" else None,
resolution=resolution if resolution != "1024x1024" else None,
magic_prompt_option=magic_prompt_option if magic_prompt_option != "AUTO" else None,
style_type=style_type if style_type != "NONE" else None,
negative_prompt=negative_prompt if negative_prompt else None,
color_palette=None
)
),
auth_token=auth_token
)
response = operation.execute()
if not response.data or len(response.data) == 0:
raise Exception("No images were generated in the response")
image_url = response.data[0].url
if not image_url:
raise Exception("No image URL was generated in the response")
img_response = requests.get(image_url)
if img_response.status_code != 200:
raise Exception("Failed to download the image")
img = Image.open(io.BytesIO(img_response.content))
img = img.convert("RGB") # Ensure RGB format
# Convert to numpy array, normalize to float32 between 0 and 1
img_array = np.array(img).astype(np.float32) / 255.0
# Convert to torch tensor and add batch dimension
img_tensor = torch.from_numpy(img_array)[None,]
return (img_tensor,)
"""
The node will always be re executed if any of the inputs change but
this method can be used to force the node to execute again even when the inputs don't change.
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
executed, if it is different the node will be executed again.
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
changes between executions the LoadImage node is executed again.
"""
#@classmethod
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
# return ""
class MinimaxTextToVideoNode:
"""
Generates videos synchronously based on a prompt, and optional parameters using Minimax's API.
"""
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.type: Literal["output"] = "output"
@classmethod
def INPUT_TYPES(s):
@ -597,13 +765,14 @@ class MinimaxVideoNode:
return {"ui": {"images": results, "animated": (True,)}}
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {
"OpenAIDalle2": OpenAIDalle2,
"OpenAIDalle3": OpenAIDalle3,
"OpenAIGPTImage1": OpenAIGPTImage1,
"IdeogramTextToImage": IdeogramTextToImage,
"MinimaxTextToVideoNode": MinimaxTextToVideoNode,
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
@ -611,4 +780,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"OpenAIDalle2": "OpenAI DALL·E 2",
"OpenAIDalle3": "OpenAI DALL·E 3",
"OpenAIGPTImage1": "OpenAI GPT Image 1",
"IdeogramTextToImage": "Ideogram Text to Image",
"MinimaxTextToVideoNode": "Minimax Text to Video",
}