From 2eea88b841422156ebae778afd4dea2172e66765 Mon Sep 17 00:00:00 2001 From: Jimmy Wong Date: Sun, 27 Oct 2024 15:11:15 +0800 Subject: [PATCH] support flux and ideogram nodes --- flowy/nodes.py | 14 +++- flowy/nodes_flux.py | 149 ++++++++++++++++++++++++++++++++++++++++ flowy/nodes_ideogram.py | 131 +++++++++++++++++++++++++++++++++++ flowy/nodes_upscale.py | 148 +++++++++++++++++++++++++++++++++++++++ flowy/types.py | 3 +- 5 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 flowy/nodes_flux.py create mode 100644 flowy/nodes_ideogram.py create mode 100644 flowy/nodes_upscale.py diff --git a/flowy/nodes.py b/flowy/nodes.py index 7a0670c..e6ba0f5 100644 --- a/flowy/nodes.py +++ b/flowy/nodes.py @@ -19,6 +19,10 @@ from .nodes_omost import ( from .nodes_json import FlowyPreviewJSON, FlowyExtractJSON, ComflowyLoadJSON from .nodes_http import FlowyHttpRequest from .nodes_llm import FlowyLLM +from .nodes_upscale import FlowyUpscale +from .nodes_flux import ComflowyFlux +from .nodes_ideogram import FlowyIdeogram + API_KEY_FILE = os.path.join(os.path.dirname(__file__), "api_key.json") @@ -65,7 +69,10 @@ NODE_CLASS_MAPPINGS = { "Comflowy_Omost_Preview": ComflowyOmostPreviewNode, "Comflowy_Omost_Load_Canvas_Python_Code": ComflowyOmostLoadCanvasPythonCodeNode, "Comflowy_Omost_Load_Canvas_Conditioning": ComflowyOmostLoadCanvasConditioningNode, - "Comflowy_Set_API_Key": ComflowySetAPIKey + "Comflowy_Set_API_Key": ComflowySetAPIKey, + "Comflowy_Upscale": FlowyUpscale, + "Comflowy_Ideogram": FlowyIdeogram, + "Comflowy_Flux": ComflowyFlux, } NODE_DISPLAY_NAME_MAPPINGS = { @@ -79,5 +86,8 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Comflowy_Omost_Preview": "Comflowy Omost Preview", "Comflowy_Omost_Load_Canvas_Python_Code": "Comflowy Omost Load Canvas Python Code", "Comflowy_Omost_Load_Canvas_Conditioning": "Comflowy Omost Load Canvas Conditioning", - "Comflowy_Set_API_Key": "Comflowy Set API Key" + "Comflowy_Set_API_Key": "Comflowy Set API Key", + "Comflowy_Upscale": "Comflowy Upscale", + "Comflowy_Ideogram": "Comflowy Ideogram", + "Comflowy_Flux": "Comflowy Flux", } diff --git a/flowy/nodes_flux.py b/flowy/nodes_flux.py new file mode 100644 index 0000000..a9153fd --- /dev/null +++ b/flowy/nodes_flux.py @@ -0,0 +1,149 @@ +import time +import requests +import base64 +import io +from PIL import Image +import torch +import numpy as np +import logging +import json +from .types import STRING, INT, API_HOST +from .utils import logger, get_nested_value +from .api_key_manager import load_api_key + +logger = logging.getLogger(__name__) + +class ComflowyFlux: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "version": (["flux-1.1-pro", "flux-pro"],), + "aspect_ratio": ([ + "custom", + "1:1", + "16:9", + "2:3", + "3:2", + "4:5", + "5:4", + "9:16", + "3:4", + "4:3" + ],), + "height": ("INT", {"default": 256, "min": 256, "max": 1440}), + "width": ("INT", {"default": 256, "min": 256, "max": 1440}), + "prompt_upsampling": (["Off", "On"],), + "safety_tolerance": ([ + "1", + "2", + "3", + "4", + "5" + ]), + "seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "generate_image_with_flux" + CATEGORY = "Comflowy" + DESCRIPTION = """ +Nodes from https://comflowy.com: +- Description: A service to generate images using Flux AI. +- How to use: + - Provide a prompt to generate an image. + - Choose version, aspect ratio, height, width, and seed. + - Height and width are only used when aspect_ratio=custom. Must be a multiple of 32 (if it's not, it will be rounded to nearest multiple of 32). + - Prompt Upsampling: Automatically modify the prompt for more creative generation. + - Safety tolerance, 1 is most strict and 5 is most permissive. + - Make sure to set your API Key using the 'Comflowy Set API Key' node before using this node. +- Output: Returns the generated image. +""" + + def generate_image_with_flux(self, prompt, version, aspect_ratio, height, width, seed, prompt_upsampling, safety_tolerance): + api_key = load_api_key() + + if not api_key: + error_msg = "API Key is not set. Please use the 'Comflowy Set API Key' node to set a global API Key before using this node." + logger.error(error_msg) + raise ValueError(error_msg) + + logger.info(f"开始处理 Flux 图像生成请求。prompt: {prompt}, version: {version}, aspect_ratio: {aspect_ratio}, height: {height}, width: {width}, seed: {seed}, prompt_upsampling: {prompt_upsampling}, safety_tolerance: {safety_tolerance}") + + try: + response = requests.post( + f"{API_HOST}/api/open/v0/flux", + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json={ + "prompt": prompt, + "version": version, + "aspect_ratio": aspect_ratio, + "height": height, + "width": width, + "prompt_upsampling": prompt_upsampling, + "safety_tolerance": safety_tolerance, + "seed": seed, + } + ) + response.raise_for_status() + result = response.json() + + logger.info(f"API 请求完成。状态码: {response.status_code}") + logger.debug(f"API 响应内容: {json.dumps(result, indent=2)}") + + if not result.get('success'): + logger.error(f"API 请求失败。响应内容: {json.dumps(result, indent=2)}") + raise Exception(f"API 请求失败。响应内容: {json.dumps(result, indent=2)}") + + output_url = result.get('data', {}).get('output') + if not output_url or not isinstance(output_url, str): + logger.error(f"完整的 API 响应: {json.dumps(result, indent=2)}") + raise Exception(f"无法获取有效的输出图像 URL。API 响应中没有预期的数据结构。完整响应: {json.dumps(result, indent=2)}") + + logger.info(f"获取到的输出 URL: {output_url}") + + # 验证 URL 是否可访问 + try: + url_check = requests.head(output_url) + url_check.raise_for_status() + except requests.RequestException as e: + logger.error(f"无法访问输出 URL: {str(e)}") + raise Exception(f"无法访问输出 URL: {str(e)}") + + # 添加延迟,等待 Replicate 处理完成 + time.sleep(10) + + img_response = requests.get(output_url, stream=True) + img_response.raise_for_status() + + # 将图像数据转换为 PIL Image + img = Image.open(img_response.raw) + + # 转换为 numpy 数组 + img_np = np.array(img) + + # 确保图像是 3 通道 RGB + if len(img_np.shape) == 2: # 灰度图像 + img_np = np.stack([img_np] * 3, axis=-1) + elif img_np.shape[-1] == 4: # RGBA 图像 + img_np = img_np[:, :, :3] + + # 转换为 float32 并归一化到 0-1 范围 + img_np = img_np.astype(np.float32) / 255.0 + + # 转换为 torch tensor,确保形状为 [B,H,W,C] + img_tensor = torch.from_numpy(img_np).unsqueeze(0) # 添加批次维度 + + logger.info(f"图像处理完成。输出张量形状: {img_tensor.shape}") + + return (img_tensor,) + + except Exception as e: + error_msg = f"图像生成过程中出错: {str(e)}" + logger.error(error_msg) + logger.exception("详细错误信息:") + # 返回一个错误标记图像,确保形状为 [B,H,W,C] + error_image = torch.zeros((1, 100, 400, 3), dtype=torch.float32) + return (error_image,) diff --git a/flowy/nodes_ideogram.py b/flowy/nodes_ideogram.py new file mode 100644 index 0000000..ca14a38 --- /dev/null +++ b/flowy/nodes_ideogram.py @@ -0,0 +1,131 @@ +import time +import requests +import base64 +import io +from PIL import Image +import torch +import numpy as np +import logging +import json +from .types import STRING, INT, API_HOST +from .utils import logger, get_nested_value +from .api_key_manager import load_api_key + +logger = logging.getLogger(__name__) + +class FlowyIdeogram: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "prompt": ("STRING", {"multiline": True}), + "negative_prompt": ("STRING", {"multiline": True}), + "version": (["ideogram-v2-turbo", "ideogram-v2"],), + "resolution": (["None", "512x1536", "576x1408", "576x1472", "576x1536", "640x1024", "640x1344", "640x1408", "640x1472", "640x1536", "704x1152", "704x1216", "704x1280", "704x1344", "704x1408", "704x1472", "720x1280", "736x1312", "768x1024", "768x1088", "768x1152", "768x1216", "768x1232", "768x1280", "768x1344", "832x960", "832x1024", "832x1088", "832x1152", "832x1216", "832x1248", "864x1152", "896x960", "896x1024", "896x1088", "896x1120", "896x1152", "960x832", "960x896", "960x1024", "960x1088", "1024x640", "1024x768", "1024x832", "1024x896", "1024x960", "1024x1024", "1088x768", "1088x832", "1088x896", "1088x960", "1120x896", "1152x704", "1152x768", "1152x832", "1152x864", "1152x896", "1216x704", "1216x768", "1216x832", "1232x768", "1248x832", "1280x704", "1280x720", "1280x768", "1280x800", "1312x736", "1344x640", "1344x704", "1344x768", "1408x576", "1408x640", "1408x704", "1472x576", "1472x640", "1472x704", "1536x512", "1536x576", "1536x640"],), + "style_type": (["None", "Auto", "Realistic", "Design", "Anime", "Render 3D"],), + "aspect_ratio": (["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "16:10", "10:16", "3:1", "1:3"],), + "magic_prompt_option": (["On", "Off"],), + "seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "generate_image_with_ideogram" + CATEGORY = "Comflowy" + DESCRIPTION = """ +Nodes from https://comflowy.com: +- Description: A service to generate images using Ideogram AI. +- How to use: + - Provide a prompt to generate an image. + - Choose resolution, style type, aspect ratio, and magic prompt option. + - Resolution overrides aspect ratio. + - Magic Prompt will interpret your prompt and optimize it to maximize variety and quality of the images generated. You can also use it to write prompts in different languages. + - Make sure to set your API Key using the 'Comflowy Set API Key' node before using this node. +- Output: Returns the generated image. +""" + + def generate_image_with_ideogram(self, prompt, negative_prompt, version, resolution, style_type, aspect_ratio, magic_prompt_option, seed): + api_key = load_api_key() + + if not api_key: + error_msg = "API Key is not set. Please use the 'Comflowy Set API Key' node to set a global API Key before using this node." + logger.error(error_msg) + raise ValueError(error_msg) + + logger.info(f"开始处理 Ideogram 图像生成请求。prompt: {prompt}, negative_prompt: {negative_prompt}, resolution: {resolution}, style_type: {style_type}, aspect_ratio: {aspect_ratio}, magic_prompt_option: {magic_prompt_option}, seed: {seed}") + + try: + response = requests.post( + f"{API_HOST}/api/open/v0/ideogram", + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json={ + "prompt": prompt, + "negative_prompt": negative_prompt, + "version": version, + "resolution": resolution if resolution != "None" else None, + "style_type": style_type if style_type != "None" else None, + "aspect_ratio": aspect_ratio, + "magic_prompt_option": magic_prompt_option, + "seed": seed, + } + ) + response.raise_for_status() + result = response.json() + + logger.info(f"API 请求完成。状态码: {response.status_code}") + logger.debug(f"API 响应内容: {json.dumps(result, indent=2)}") + + if not result.get('success'): + logger.error(f"API 请求失败。响应内容: {json.dumps(result, indent=2)}") + raise Exception(f"API 请求失败。响应内容: {json.dumps(result, indent=2)}") + + output_url = result.get('data', {}).get('output') + if not output_url or not isinstance(output_url, str): + logger.error(f"完整的 API 响应: {json.dumps(result, indent=2)}") + raise Exception(f"无法获取有效的输出图像 URL。API 响应中没有预期的数据结构。完整响应: {json.dumps(result, indent=2)}") + + logger.info(f"获取到的输出 URL: {output_url}") + + # 验证 URL 是否可访问 + try: + url_check = requests.head(output_url) + url_check.raise_for_status() + except requests.RequestException as e: + logger.error(f"无法访问输出 URL: {str(e)}") + raise Exception(f"无法访问输出 URL: {str(e)}") + + # 添加延迟,等待 Replicate 处理完成 + time.sleep(10) + + img_response = requests.get(output_url, stream=True) + img_response.raise_for_status() + + # 将图像数据转换为 PIL Image + img = Image.open(img_response.raw) + + # 转换为 numpy 数组 + img_np = np.array(img) + + # 确保图像是 3 通道 RGB + if len(img_np.shape) == 2: # 灰度图像 + img_np = np.stack([img_np] * 3, axis=-1) + elif img_np.shape[-1] == 4: # RGBA 图像 + img_np = img_np[:, :, :3] + + # 转换为 float32 并归一化到 0-1 范围 + img_np = img_np.astype(np.float32) / 255.0 + + # 转换为 torch tensor,确保形状为 [B,H,W,C] + img_tensor = torch.from_numpy(img_np).unsqueeze(0) # 添加批次维度 + + logger.info(f"图像处理完成。输出张量形状: {img_tensor.shape}") + + return (img_tensor,) + + except Exception as e: + error_msg = f"图像生成过程中出错: {str(e)}" + logger.error(error_msg) + logger.exception("详细错误信息:") + # 返回一个错误标记图像,确保形状为 [B,H,W,C] + error_image = torch.zeros((1, 100, 400, 3), dtype=torch.float32) + return (error_image,) diff --git a/flowy/nodes_upscale.py b/flowy/nodes_upscale.py new file mode 100644 index 0000000..d14cc8d --- /dev/null +++ b/flowy/nodes_upscale.py @@ -0,0 +1,148 @@ +import time +import requests +import base64 +import io +from PIL import Image +import torch +import numpy as np +import logging +import json +from .types import STRING, INT, API_HOST +from .utils import logger, get_nested_value +from .api_key_manager import load_api_key + +logger = logging.getLogger(__name__) + +class FlowyUpscale: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("IMAGE",), + "scale_factor": ("INT", {"default": 2, "min": 1, "max": 4, "step": 1}), + "model": (["clarity-upscaler"],), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "upscale" + CATEGORY = "Comflowy" + DESCRIPTION = """ +Nodes from https://comflowy.com: +- Description: A service to upscale images using AI models. +- How to use: + - Provide an image to upscale. + - Choose the scale factor and the upscaling model. + - Make sure to set your API Key using the 'Comflowy Set API Key' node before using this node. +- Output: Returns the upscaled image. +""" + + def upscale(self, image, scale_factor, model): + api_key = load_api_key() + + if not api_key: + error_msg = "API Key is not set. Please use the 'Comflowy Set API Key' node to set a global API Key before using this node." + logger.error(error_msg) + raise ValueError(error_msg) + + logger.info(f"开始处理图像放大请求。scale_factor: {scale_factor}, model: {model}") + + # 处理输入图像 + if isinstance(image, torch.Tensor): + if image.dim() == 4: + image = image.squeeze(0) # 移除批次维度 + if image.shape[-1] == 3: + image = (image.cpu().numpy() * 255).astype(np.uint8) + elif image.shape[0] == 3: + image = (image.permute(1, 2, 0).cpu().numpy() * 255).astype(np.uint8) + else: + raise ValueError(f"Unsupported image shape: {image.shape}") + elif isinstance(image, np.ndarray): + if image.ndim == 2: + image = np.stack([image] * 3, axis=-1) + elif image.shape[-1] == 1: + image = np.repeat(image, 3, axis=-1) + elif image.shape[-1] != 3: + raise ValueError(f"Unsupported number of channels: {image.shape[-1]}") + image = (image * 255).astype(np.uint8) + else: + raise ValueError(f"Unsupported image type: {type(image)}") + + # 将输入图像转换为 JPEG 格式并压缩 + buffered = io.BytesIO() + Image.fromarray(image).save(buffered, format="JPEG", quality=85) + img_str = base64.b64encode(buffered.getvalue()).decode() + + try: + # 使用 API_HOST 构建 API 请求的 URL + response = requests.post( + f"{API_HOST}/api/open/v0/upscale", + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json={ + "image": f"data:image/jpeg;base64,{img_str}", + "scale_factor": scale_factor, + "model": model + } + ) + response.raise_for_status() + result = response.json() + + logger.info(f"API 请求完成。状态码: {response.status_code}") + logger.debug(f"API 响应内容: {json.dumps(result, indent=2)}") + + if not result.get('success'): + logger.error(f"API 请求失败。响应内容: {json.dumps(result, indent=2)}") + raise Exception(f"API 请求失败。响应内容: {json.dumps(result, indent=2)}") + + output_url = result.get('data', {}).get('output', [None])[0] + if not output_url: + logger.error(f"完整的 API 响应: {json.dumps(result, indent=2)}") + raise Exception(f"无法获取输出图像 URL。API 响应中没有预期的数据结构。完整响应: {json.dumps(result, indent=2)}") + + logger.info(f"获取到的输出 URL: {output_url}") + + # 验证 URL 是否可访问 + try: + url_check = requests.head(output_url) + url_check.raise_for_status() + except requests.RequestException as e: + logger.error(f"无法访问输出 URL: {str(e)}") + raise Exception(f"无法访问输出 URL: {str(e)}") + + # 添加延迟,等待 Replicate 处理完成 + time.sleep(10) + + img_response = requests.get(output_url, stream=True) + img_response.raise_for_status() + + # 将图像数据转换为 PIL Image + img = Image.open(img_response.raw) + + # 转换为 numpy 数组 + img_np = np.array(img) + + # 确保图像是 3 通道 RGB + if len(img_np.shape) == 2: # 灰度图像 + img_np = np.stack([img_np] * 3, axis=-1) + elif img_np.shape[-1] == 4: # RGBA 图像 + img_np = img_np[:, :, :3] + + # 转换为 float32 并归一化到 0-1 范围 + img_np = img_np.astype(np.float32) / 255.0 + + # 转换为 torch tensor,确保形状为 [B,H,W,C] + img_tensor = torch.from_numpy(img_np).unsqueeze(0) # 添加批次维度 + + logger.info(f"图像处理完成。输出张量形状: {img_tensor.shape}") + logger.info(f"API 请求完成。状态码: {response.status_code}") + logger.debug(f"API 响应内容: {response.text}") + + return (img_tensor,) + + except Exception as e: + error_msg = f"放大过程中出错: {str(e)}" + logger.error(error_msg) + logger.exception("详细错误信息:") + # 返回一个错误标记图像,确保形状为 [B,H,W,C] + error_image = torch.zeros((1, 100, 400, 3), dtype=torch.float32) + return (error_image,) diff --git a/flowy/types.py b/flowy/types.py index 0baacb8..80cb63e 100644 --- a/flowy/types.py +++ b/flowy/types.py @@ -1,6 +1,7 @@ import sys -API_HOST = "https://app.comflowy.com" # "http://127.0.0.1:3000" # +# API_HOST = "https://app.comflowy.com" +API_HOST = "http://127.0.0.1:3000" FLOAT = ( "FLOAT", {"default": 1, "min": -sys.float_info.max, "max": sys.float_info.max, "step": 0.01},