Add staging api.

This commit is contained in:
Robin Huang 2025-04-15 13:57:08 -07:00
parent 6f5caa0879
commit 7290683905

View File

@ -1,5 +1,10 @@
# Add API base URL at the top of the file
API_BASE = "https://stagingapi.comfy.org"
from inspect import cleandoc from inspect import cleandoc
class IdeogramTextToImage: from comfy.comfy_types.node_typing import ComfyNodeABC, InputTypeDict, IO
class IdeogramTextToImage(ComfyNodeABC):
""" """
Generates images synchronously based on a given prompt and optional parameters. Generates images synchronously based on a given prompt and optional parameters.
@ -9,7 +14,7 @@ class IdeogramTextToImage:
pass pass
@classmethod @classmethod
def INPUT_TYPES(s): def INPUT_TYPES(cls) -> InputTypeDict:
""" """
Return a dictionary which contains config for all input fields. Return a dictionary which contains config for all input fields.
Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT". Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
@ -26,58 +31,61 @@ class IdeogramTextToImage:
""" """
return { return {
"required": { "required": {
"prompt": ("STRING", {"multiline": True, "prompt": (IO.STRING, {
"default": "", "tooltip": "Prompt for the image generation"}), "multiline": True,
"model": (["V_2", "V_2_TURBO", "V_1", "V_1_TURBO"], {"default": "V_2"}), "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": { "optional": {
"aspect_ratio": (["ASPECT_1_1", "ASPECT_4_3", "ASPECT_3_4", "ASPECT_16_9", "ASPECT_9_16", "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"
"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": (["1024x1024", "1024x1792", "1792x1024"], { "resolution": (IO.COMBO, { "options": ["1024x1024", "1024x1792", "1792x1024"],
"default": "1024x1024", "default": "1024x1024",
"tooltip": "The resolution for image generation (V2 only). Cannot be used with aspect_ratio" "tooltip": "The resolution for image generation (V2 only). Cannot be used with aspect_ratio"
}), }),
"magic_prompt_option": (["AUTO", "ON", "OFF"], { "magic_prompt_option": (IO.COMBO, { "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"
}), }),
"seed": ("INT", { "seed": (IO.INT, {
"default": 0, "default": 0,
"min": 0, "min": 0,
"max": 2147483647, "max": 2147483647,
"step": 1, "step": 1,
"display": "number" "display": "number"
}), }),
"style_type": (["NONE", "ANIME", "CINEMATIC", "CREATIVE", "DIGITAL_ART", "PHOTOGRAPHIC"], { "style_type": (IO.COMBO, { "options": ["NONE", "ANIME", "CINEMATIC", "CREATIVE", "DIGITAL_ART", "PHOTOGRAPHIC"],
"default": "NONE", "default": "NONE",
"tooltip": "Style type for generation (V2+ only)" "tooltip": "Style type for generation (V2+ only)"
}), }),
"negative_prompt": ("STRING", { "negative_prompt": (IO.STRING, {
"multiline": True, "multiline": True,
"default": "", "default": "",
"tooltip": "Description of what to exclude from the image (V1/V2 only)" "tooltip": "Description of what to exclude from the image (V1/V2 only)"
}), }),
"num_images": ("INT", { "num_images": (IO.INT, {
"default": 1, "default": 1,
"min": 1, "min": 1,
"max": 8, "max": 8,
"step": 1, "step": 1,
"display": "number" "display": "number"
}), }),
"color_palette": ("STRING", { "color_palette": (IO.STRING, {
"multiline": False, "multiline": False,
"default": "", "default": "",
"tooltip": "Color palette preset name or hex colors with weights (V2/V2_TURBO only)" "tooltip": "Color palette preset name or hex colors with weights (V2/V2_TURBO only)"
}), }),
},
"hidden": {
"auth_token": "AUTH_TOKEN_COMFY_ORG"
} }
} }
RETURN_TYPES = ("IMAGE",) RETURN_TYPES = (IO.IMAGE,)
#RETURN_NAMES = ("image_output_name",) #RETURN_NAMES = ("image_output_name",)
DESCRIPTION = cleandoc(__doc__) DESCRIPTION = cleandoc(__doc__ or "") # Handle potential None value
FUNCTION = "api_call" FUNCTION = "api_call"
#OUTPUT_NODE = False #OUTPUT_NODE = False
@ -85,15 +93,14 @@ class IdeogramTextToImage:
CATEGORY = "Example" CATEGORY = "Example"
def api_call(self, prompt, model, aspect_ratio=None, resolution=None, def api_call(self, prompt, model, aspect_ratio=None, resolution=None,
magic_prompt_option="AUTO", seed=0, style_type="NONE", magic_prompt_option="AUTO", seed=0, style_type="NONE",
negative_prompt="", num_images=1, color_palette=""): negative_prompt="", num_images=1, color_palette="", auth_token=None):
import requests import requests
import torch import torch
from PIL import Image from PIL import Image
import io import io
import numpy as np import numpy as np
import time
# Build payload with all available parameters # Build payload with all available parameters
payload = { payload = {
@ -107,12 +114,12 @@ class IdeogramTextToImage:
# Make API request # Make API request
headers = { headers = {
"Authorization": "Bearer TBD", # TODO(robin): add authorization key "Authorization": f"Bearer {auth_token}",
"Content-Type": "application/json" "Content-Type": "application/json"
} }
response = requests.post( response = requests.post(
"http://localhost:8080/proxy/ideogram/generate", f"{API_BASE}/proxy/ideogram/generate",
headers=headers, headers=headers,
json=payload json=payload
) )
@ -122,30 +129,22 @@ class IdeogramTextToImage:
# Parse response # Parse response
response_data = response.json() response_data = response.json()
# Get the image URL from the response # Get the image URL from the response
image_url = response_data["data"][0]["url"] image_url = response_data["data"][0]["url"]
# Time the image download
download_start = time.time()
img_response = requests.get(image_url) img_response = requests.get(image_url)
if img_response.status_code != 200: if img_response.status_code != 200:
raise Exception("Failed to download the image") raise Exception("Failed to download the image")
download_time = (time.time() - download_start) * 1000 # Convert to milliseconds
print(f"Image download time: {download_time:.2f}ms")
# Time the conversion process
conversion_start = time.time()
img = Image.open(io.BytesIO(img_response.content)) img = Image.open(io.BytesIO(img_response.content))
img = img.convert("RGB") # Ensure RGB format img = img.convert("RGB") # Ensure RGB format
# Convert to numpy array, normalize to float32 between 0 and 1 # Convert to numpy array, normalize to float32 between 0 and 1
img_array = np.array(img).astype(np.float32) / 255.0 img_array = np.array(img).astype(np.float32) / 255.0
# Convert to torch tensor and add batch dimension # Convert to torch tensor and add batch dimension
img_tensor = torch.from_numpy(img_array)[None,] img_tensor = torch.from_numpy(img_array)[None,]
conversion_time = (time.time() - conversion_start) * 1000 # Convert to milliseconds
print(f"Image conversion time: {conversion_time:.2f}ms")
return (img_tensor,) return (img_tensor,)
@ -208,7 +207,10 @@ class RunwayVideoNode:
"default": False, "default": False,
"tooltip": "Whether to include watermark in the output" "tooltip": "Whether to include watermark in the output"
}), }),
} },
"hidden": {
"auth_token": "AUTH_TOKEN_COMFY_ORG"
},
} }
RETURN_TYPES = ("VIDEO",) RETURN_TYPES = ("VIDEO",)
@ -216,15 +218,9 @@ class RunwayVideoNode:
FUNCTION = "generate_video" FUNCTION = "generate_video"
CATEGORY = "video" CATEGORY = "video"
def generate_video(self, prompt_image, prompt_text, seed=0, model="gen3a_turbo", def generate_video(self, prompt_image, prompt_text, seed=0, model="gen3a_turbo",
duration=5.0, ratio="1280:768", watermark=False): duration=5.0, ratio="1280:768", watermark=False, auth_token=None):
import requests import requests
import torch
import time
import os
# Hardcoded API key (temporary solution)
api_key = "key_e861661aa0b307e07e8cc269c1f42cf56fcce876ed6511a507e185ee51f695291da21f4777be1326b4467c34be5a6498b72dc27c9780e483250c692aa410d4c6" # Replace with actual API key
# Convert torch tensor image to URL (you'll need to implement this part) # Convert torch tensor image to URL (you'll need to implement this part)
# This is a placeholder - you'll need to either save the image temporarily # This is a placeholder - you'll need to either save the image temporarily
@ -244,27 +240,22 @@ class RunwayVideoNode:
# Make API request # Make API request
headers = { headers = {
"Authorization": f"Bearer {api_key}", "Authorization": f"Bearer {auth_token}",
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Runway-Version": "2024-11-06"
} }
# Time the API request
api_start = time.time()
response = requests.post( response = requests.post(
"https://api.dev.runwayml.com/v1/image_to_video", f"{API_BASE}/proxy/runway/image_to_video",
headers=headers, headers=headers,
json=payload json=payload
) )
api_time = (time.time() - api_start) * 1000 # Convert to milliseconds
print(f"API request time: {api_time:.2f}ms")
if response.status_code != 200: if response.status_code != 200:
raise Exception(f"API request failed: {response.text}") raise Exception(f"API request failed: {response.text}")
# Parse response # Parse response
response_data = response.json() # response_data = response.json()
# Note: You'll need to implement the actual video handling here # Note: You'll need to implement the actual video handling here
# This is a placeholder return # This is a placeholder return
return (None,) return (None,)