Add GPT Image 2.5 models and release 0.1.17

This commit is contained in:
Dreaming Computers
2026-10-06 10:26:25 +07:00
parent 41ce94bf78
commit 9c8bd258de
15 changed files with 557 additions and 181 deletions
+8 -5
View File
@@ -45,8 +45,10 @@ This node pack currently includes the following nodes:
- Image generation node using Nano Banana 2.
- Supports up to 14 optional input/reference images.
- Supports optional Google web-search grounding.
- **GPT Image 2 Text-to-Image / Image-to-Image**
- Text-to-image and image-to-image nodes using GPT Image 2.
- **GPT Image Text-to-Image / Image-to-Image**
- Two nodes with a model selector: GPT Image 2, GPT Image 2.5 Flare, and GPT Image 2.5 Sunburst.
- Model-aware aspect ratio/resolution dropdowns and GPT Image 2.5 background controls.
- RGB IMAGE plus transparency MASK outputs; existing GPT Image 2 workflow IDs are preserved.
- Image-to-image accepts up to 16 input/reference images via ComfyUI batch.
- **Seedream Text-to-Image / Edit**
- Text-to-image and image-editing node for Seedream models.
@@ -140,12 +142,12 @@ This node pack currently includes the following nodes:
Use this section after reviewing Current Available Nodes.
- Full docs index (all nodes): [`web/docs/README.md`](web/docs/README.md)
- GPT Image 2 status: `Text-to-Image` and `Image-to-Image` are implemented and documented in this repo.
- GPT Image 2 / 2.5 status: both modes are implemented and documented. Windows ComfyUI/live verification of 2.5 is pending.
- Kling 3.0 motion status: implemented in this repo with both the node doc and API spec linked below.
- Grok Imagine status: `T2I`, `I2I`, `T2V`, and `I2V` are implemented in this repo.
- Image generation node docs:
- [`web/docs/KIE_GPTImage2_TextToImage.md`](web/docs/KIE_GPTImage2_TextToImage.md) - GPT Image 2 text-to-image node reference.
- [`web/docs/KIE_GPTImage2_ImageToImage.md`](web/docs/KIE_GPTImage2_ImageToImage.md) - GPT Image 2 image-to-image node reference.
- [`web/docs/KIE_GPTImage2_TextToImage.md`](web/docs/KIE_GPTImage2_TextToImage.md) - GPT Image 2 / 2.5 text-to-image node reference.
- [`web/docs/KIE_GPTImage2_ImageToImage.md`](web/docs/KIE_GPTImage2_ImageToImage.md) - GPT Image 2 / 2.5 image-to-image node reference.
- [`web/docs/KIE_GrokImagine_T2I.md`](web/docs/KIE_GrokImagine_T2I.md) - Grok Imagine text-to-image node reference.
- [`web/docs/KIE_GrokImagine_I2I.md`](web/docs/KIE_GrokImagine_I2I.md) - Grok Imagine image-to-image node reference.
- [`web/docs/KIE_GrokImagine_T2V.md`](web/docs/KIE_GrokImagine_T2V.md) - Grok Imagine text-to-video node reference.
@@ -253,6 +255,7 @@ Related docs:
- [`web/docs/KIE_Kling3_Motion_I2V_Spec.md`](web/docs/KIE_Kling3_Motion_I2V_Spec.md)
## Changelog
- 2026-10-06 (0.1.17): Added GPT Image 2.5 Flare/Sunburst selection to both GPT Image nodes, model-aware dropdowns/validation, and transparency MASK output. Existing GPT Image 2 workflow IDs are preserved. Windows ComfyUI/live verification remains pending.
- 2026-05-08: Bumped package version to 0.1.15 to trigger the ComfyUI build after GPT Image 2 verification.
- 2026-05-04: Bumped package version to 0.1.14 and added GPT Image 2 text-to-image and image-to-image nodes, including 16-image I2I upload support and KIE resolution compatibility validation.
- 2026-04-17: Bumped package version to 0.1.13, added a Seedance 2.0 model selector (`seedance-2-fast` / `seedance-2`), and marked Seedance 2.0 as experimental in the README.
+3 -1
View File
@@ -1,3 +1,5 @@
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
WEB_DIRECTORY = "./js"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
+9 -1
View File
@@ -1 +1,9 @@
Frontend JS extensions go here (optional).
# Frontend extensions
`gpt_image.js` refreshes GPT Image aspect-ratio, resolution and background choices from Python's `kie_model_options` metadata. It uses ComfyUI extension hooks and per-widget callbacks, without replacing node prototypes.
The package exports `WEB_DIRECTORY = "./js"`. Restart ComfyUI and refresh the browser after updating. Python remains authoritative for connected inputs and headless API workflows.
Per-instance configure and connection handlers refresh cloned nodes and offer all potentially valid options when a controlling input is linked. Existing handlers and saved values are preserved.
Widget regression cases: `node --test tests/gpt_image_widgets.test.mjs`. These cases are syntax-checked and statically reviewed on the Mac; execution and actual ComfyUI clone, save/reload, and connect/disconnect checks remain pending.
+71
View File
@@ -0,0 +1,71 @@
import { app } from "../../scripts/app.js";
const nodeNames = new Set(["KIE_GPTImage2_TextToImage", "KIE_GPTImage2_ImageToImage"]);
const definitions = new Map();
function syncOptions(node, normalize = false) {
const rules = definitions.get(node.comfyClass);
if (!rules) return;
const widgets = Object.fromEntries((node.widgets ?? []).map((widget) => [widget.name, widget]));
const isLinked = (name) => node.inputs?.some((input) => input.name === name && input.link != null);
// A connected selector is unknown until execution: offer the union of valid choices.
const candidates = isLinked("model") ? Object.values(rules) : [rules[widgets.model?.value]];
if (candidates.some((options) => !options)) return;
const union = (values) => [...new Set(values.flat())];
const update = (name, values) => {
const widget = widgets[name];
if (!widget || !values || isLinked(name)) return;
widget.options.values = [...values];
if (normalize && !values.includes(widget.value)) {
widget.value = values[0];
widget.callback?.(widget.value);
}
};
update("aspect_ratio", union(candidates.map((options) => options.aspect_ratios)));
const resolutions = union(candidates.flatMap((options) => isLinked("aspect_ratio")
? Object.values(options.resolutions)
: [options.resolutions[widgets.aspect_ratio?.value] ?? []]));
if (resolutions.length) update("resolution", resolutions);
update("background", union(candidates.map((options) => options.backgrounds)));
node.setDirtyCanvas?.(true, true);
}
app.registerExtension({
name: "KIE.GPTImageOptions",
beforeRegisterNodeDef(_nodeType, nodeData) {
if (nodeNames.has(nodeData.name)) {
definitions.set(nodeData.name, nodeData.input.optional.model[1].kie_model_options);
}
},
nodeCreated(node) {
if (!nodeNames.has(node.comfyClass)) return;
const onConfigure = node.onConfigure;
node.onConfigure = function (...args) {
const result = onConfigure?.apply(this, args);
// Covers clone/paste as well as full graph loading, without rewriting values.
syncOptions(this);
return result;
};
const onConnectionsChange = node.onConnectionsChange;
node.onConnectionsChange = function (...args) {
const result = onConnectionsChange?.apply(this, args);
// Wait for connection bookkeeping to finish before reading link state.
queueMicrotask(() => syncOptions(this));
return result;
};
for (const widget of node.widgets ?? []) {
if (!["model", "aspect_ratio"].includes(widget.name)) continue;
const original = widget.callback;
widget.callback = function (...args) {
const result = original?.apply(this, args);
syncOptions(node, true);
return result;
};
}
syncOptions(node);
},
loadedGraphNode(node) {
// Never silently rewrite a saved workflow's parameters on load.
syncOptions(node);
},
});
+32 -57
View File
@@ -1,4 +1,4 @@
"""GPT Image 2 text-to-image and image-to-image helpers."""
"""GPT Image 2 / 2.5 text-to-image and image-to-image helpers."""
import time
@@ -7,31 +7,13 @@ import torch
from .auth import _load_api_key
from .credits import _log_remaining_credits
from .http import TransientKieError
from .images import _download_image, _image_bytes_to_tensor
from .images import _download_image, _image_bytes_to_tensor_and_mask
from .gpt_image_options import DEFAULT_MODEL, MAX_IMAGE_COUNT, build_gpt_image_payload
from .jobs import _create_task, _poll_task_until_complete
from .log import _log
from .results import _extract_result_urls
from .upload import _image_tensor_to_png_bytes, _truncate_url, _upload_image
from .validation import _validate_image_tensor_batch, _validate_prompt
TEXT_TO_IMAGE_MODEL_NAME = "gpt-image-2-text-to-image"
IMAGE_TO_IMAGE_MODEL_NAME = "gpt-image-2-image-to-image"
ASPECT_RATIO_OPTIONS = ["auto", "1:1", "9:16", "16:9", "4:3", "3:4"]
RESOLUTION_OPTIONS = ["1K", "2K", "4K"]
PROMPT_MAX_LENGTH = 20000
MAX_IMAGE_COUNT = 16
def _validate_options(aspect_ratio: str, resolution: str) -> None:
if aspect_ratio not in ASPECT_RATIO_OPTIONS:
raise RuntimeError("Invalid aspect_ratio. Use the pinned enum options.")
if resolution not in RESOLUTION_OPTIONS:
raise RuntimeError("Invalid resolution. Use the pinned enum options.")
if aspect_ratio == "auto" and resolution != "1K":
raise RuntimeError('GPT Image 2 requires resolution "1K" when aspect_ratio is "auto".')
if aspect_ratio == "1:1" and resolution == "4K":
raise RuntimeError('GPT Image 2 does not support resolution "4K" with aspect_ratio "1:1".')
from .validation import _validate_image_tensor_batch
def _run_gpt_image2_payload(
@@ -41,7 +23,7 @@ def _run_gpt_image2_payload(
timeout_s: int,
log: bool,
create_label: str,
) -> torch.Tensor:
) -> tuple[torch.Tensor, torch.Tensor]:
api_key = _load_api_key()
_log(log, f"Creating {create_label} task...")
start_time = time.time()
@@ -62,10 +44,10 @@ def _run_gpt_image2_payload(
_log(log, f"Result URLs: {result_urls}")
_log(log, f"Downloading result image from {result_urls[0]}...")
image_bytes = _download_image(result_urls[0])
image_tensor = _image_bytes_to_tensor(image_bytes)
image_tensor, mask_tensor = _image_bytes_to_tensor_and_mask(image_bytes)
_log(log, "Image downloaded and decoded.")
_log_remaining_credits(log, record_data, api_key, _log)
return image_tensor
return image_tensor, mask_tensor
def run_gpt_image2_text_to_image(
@@ -79,19 +61,14 @@ def run_gpt_image2_text_to_image(
retry_on_fail: bool = True,
max_retries: int = 2,
retry_backoff_s: float = 3.0,
) -> torch.Tensor:
"""Run a GPT Image 2 text-to-image job end-to-end."""
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
_validate_options(aspect_ratio, resolution)
payload = {
"model": TEXT_TO_IMAGE_MODEL_NAME,
"input": {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"resolution": resolution,
},
}
model: str = DEFAULT_MODEL,
background: str = "opaque",
) -> tuple[torch.Tensor, torch.Tensor]:
"""Run the selected GPT Image text-to-image model; return RGB and mask."""
payload = build_gpt_image_payload(
model=model, mode="text-to-image", prompt=prompt,
aspect_ratio=aspect_ratio, resolution=resolution, background=background,
)
attempts = max_retries + 1 if retry_on_fail else 1
attempts = max(attempts, 1)
@@ -104,7 +81,7 @@ def run_gpt_image2_text_to_image(
poll_interval_s=poll_interval_s,
timeout_s=timeout_s,
log=log,
create_label="GPT Image 2 text-to-image",
create_label=f"{model} text-to-image",
)
except TransientKieError:
if not retry_on_fail or attempt >= attempts:
@@ -112,7 +89,7 @@ def run_gpt_image2_text_to_image(
_log(log, f"Retrying (attempt {attempt + 1}/{attempts}) after {backoff}s")
time.sleep(backoff)
raise RuntimeError("GPT Image 2 text-to-image job failed after retry attempts.")
raise RuntimeError(f"{model} text-to-image job failed after retry attempts.")
def run_gpt_image2_image_to_image(
@@ -127,11 +104,18 @@ def run_gpt_image2_image_to_image(
retry_on_fail: bool = True,
max_retries: int = 2,
retry_backoff_s: float = 3.0,
) -> torch.Tensor:
"""Run a GPT Image 2 image-to-image job end-to-end."""
_validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH)
_validate_options(aspect_ratio, resolution)
model: str = DEFAULT_MODEL,
background: str = "opaque",
) -> tuple[torch.Tensor, torch.Tensor]:
"""Run the selected GPT Image editing model; return RGB and mask."""
payload = build_gpt_image_payload(
model=model, mode="image-to-image", prompt=prompt,
aspect_ratio=aspect_ratio, resolution=resolution, background=background,
)
images = _validate_image_tensor_batch(images)
total_images = images.shape[0]
if total_images > MAX_IMAGE_COUNT and model != DEFAULT_MODEL:
raise RuntimeError(f"{model} accepts at most {MAX_IMAGE_COUNT} source images; received {total_images}.")
attempts = max_retries + 1 if retry_on_fail else 1
attempts = max(attempts, 1)
@@ -140,7 +124,6 @@ def run_gpt_image2_image_to_image(
for attempt in range(1, attempts + 1):
try:
api_key = _load_api_key()
total_images = images.shape[0]
if total_images > MAX_IMAGE_COUNT:
_log(
log,
@@ -150,29 +133,21 @@ def run_gpt_image2_image_to_image(
upload_count = min(total_images, MAX_IMAGE_COUNT)
image_urls: list[str] = []
_log(log, f"Uploading {upload_count} image(s) for GPT Image 2 I2I...")
_log(log, f"Uploading {upload_count} image(s) for {model} I2I...")
for idx in range(upload_count):
png_bytes = _image_tensor_to_png_bytes(images[idx])
image_url = _upload_image(api_key, png_bytes)
image_urls.append(image_url)
_log(log, f"Image {idx + 1} upload success: {_truncate_url(image_url)}")
payload = {
"model": IMAGE_TO_IMAGE_MODEL_NAME,
"input": {
"prompt": prompt,
"input_urls": image_urls,
"aspect_ratio": aspect_ratio,
"resolution": resolution,
},
}
payload["input"]["input_urls"] = image_urls
return _run_gpt_image2_payload(
payload=payload,
poll_interval_s=poll_interval_s,
timeout_s=timeout_s,
log=log,
create_label="GPT Image 2 image-to-image",
create_label=f"{model} image-to-image",
)
except TransientKieError:
if not retry_on_fail or attempt >= attempts:
@@ -180,4 +155,4 @@ def run_gpt_image2_image_to_image(
_log(log, f"Retrying (attempt {attempt + 1}/{attempts}) after {backoff}s")
time.sleep(backoff)
raise RuntimeError("GPT Image 2 image-to-image job failed after retry attempts.")
raise RuntimeError(f"{model} image-to-image job failed after retry attempts.")
+69
View File
@@ -0,0 +1,69 @@
"""Pinned Kie GPT Image contracts, shared by validation and widget metadata."""
DEFAULT_MODEL = "GPT Image 2"
MODEL_PREFIXES = {
DEFAULT_MODEL: "gpt-image-2",
"GPT Image 2.5 Flare": "gpt-image-2-5-flare",
"GPT Image 2.5 Sunburst": "gpt-image-2-5-sunburst",
}
MODEL_OPTIONS = list(MODEL_PREFIXES)
LEGACY_ASPECT_RATIOS = ["auto", "1:1", "9:16", "16:9", "4:3", "3:4"]
ASPECT_RATIO_OPTIONS = [
*LEGACY_ASPECT_RATIOS, "3:2", "2:3", "21:9", "27:16", "16:27", "9:8", "8:9",
]
RESOLUTION_OPTIONS = ["1K", "2K", "4K"]
BACKGROUND_OPTIONS = ["opaque", "transparent", "auto"]
ONE_K_ONLY_RATIOS = {"27:16", "16:27", "9:8", "8:9"}
PROMPT_MAX_LENGTH = 20000
MAX_IMAGE_COUNT = 16
def model_widget_options() -> dict:
"""Expose the same compatibility rules to the frontend without a second table."""
options = {}
for model in MODEL_OPTIONS:
legacy = model == DEFAULT_MODEL
ratios = LEGACY_ASPECT_RATIOS if legacy else ASPECT_RATIO_OPTIONS
resolutions = {}
for ratio in ratios:
if (legacy and ratio == "auto") or (not legacy and ratio in ONE_K_ONLY_RATIOS):
resolutions[ratio] = ["1K"]
elif legacy and ratio == "1:1":
resolutions[ratio] = ["1K", "2K"]
else:
resolutions[ratio] = list(RESOLUTION_OPTIONS)
options[model] = {
"aspect_ratios": list(ratios),
"resolutions": resolutions,
"backgrounds": ["opaque"] if legacy else list(BACKGROUND_OPTIONS),
}
return options
def build_gpt_image_payload(
*, model: str, mode: str, prompt: str, aspect_ratio: str,
resolution: str, background: str,
) -> dict:
"""Validate before any upload/submission; I2I URLs are attached after upload."""
if model not in MODEL_PREFIXES:
raise RuntimeError(f"Unknown GPT Image model: {model!r}. Choose a model from the dropdown.")
if mode not in ("text-to-image", "image-to-image"):
raise RuntimeError(f"Unknown GPT Image mode: {mode!r}.")
if not isinstance(prompt, str) or not prompt.strip():
raise RuntimeError("Prompt is required.")
if len(prompt) > PROMPT_MAX_LENGTH:
raise RuntimeError(f"Prompt exceeds the maximum length of {PROMPT_MAX_LENGTH} characters.")
options = model_widget_options()[model]
if aspect_ratio not in options["aspect_ratios"]:
raise RuntimeError(f"{model} does not support aspect_ratio {aspect_ratio!r}.")
allowed = options["resolutions"][aspect_ratio]
if resolution not in allowed:
raise RuntimeError(
f"{model} with aspect_ratio {aspect_ratio!r} requires resolution: {', '.join(allowed)}."
)
if background not in options["backgrounds"]:
raise RuntimeError(f"{model} supports background: {', '.join(options['backgrounds'])}.")
inputs = {"prompt": prompt, "aspect_ratio": aspect_ratio, "resolution": resolution}
if model != DEFAULT_MODEL:
inputs["background"] = background
return {"model": f"{MODEL_PREFIXES[model]}-{mode}", "input": inputs}
+12
View File
@@ -29,6 +29,18 @@ def _image_bytes_to_tensor(image_bytes: bytes) -> torch.Tensor:
raise RuntimeError("Failed to decode result image.") from exc
def _image_bytes_to_tensor_and_mask(image_bytes: bytes) -> tuple[torch.Tensor, torch.Tensor]:
"""Decode RGB plus ComfyUI's inverse-alpha mask; opaque images yield zeros."""
try:
with Image.open(BytesIO(image_bytes)) as img:
rgba = np.array(img.convert("RGBA"), dtype=np.float32) / 255.0
image = torch.from_numpy(rgba[:, :, :3].copy()).unsqueeze(0)
mask = torch.from_numpy(1.0 - rgba[:, :, 3]).unsqueeze(0)
return image, mask
except Exception as exc:
raise RuntimeError("Failed to decode result image.") from exc
def _download_image(url: str) -> bytes:
"""Download a result image and return its raw bytes."""
try:
+64 -32
View File
@@ -19,9 +19,15 @@ from .kie_api.nanobanana2 import (
RESOLUTION_OPTIONS as NANOBANANA2_RESOLUTION_OPTIONS,
run_nanobanana2_image_job,
)
from .kie_api.gpt_image2 import (
from .kie_api.gpt_image_options import (
ASPECT_RATIO_OPTIONS as GPT_IMAGE2_ASPECT_RATIO_OPTIONS,
RESOLUTION_OPTIONS as GPT_IMAGE2_RESOLUTION_OPTIONS,
BACKGROUND_OPTIONS as GPT_IMAGE_BACKGROUND_OPTIONS,
DEFAULT_MODEL as GPT_IMAGE_DEFAULT_MODEL,
MODEL_OPTIONS as GPT_IMAGE_MODEL_OPTIONS,
model_widget_options as gpt_image_widget_options,
)
from .kie_api.gpt_image2 import (
run_gpt_image2_image_to_image,
run_gpt_image2_text_to_image,
)
@@ -345,24 +351,26 @@ Outputs:
class KIE_GPTImage2_TextToImage:
HELP = """
KIE GPT Image 2 (Text-to-Image)
KIE GPT Image (Text-to-Image)
Generate an image from a text prompt using GPT Image 2.
Generate an image from a text prompt using GPT Image 2, GPT Image 2.5 Flare, or GPT Image 2.5 Sunburst.
Inputs:
- prompt: Text prompt (required, up to 20,000 chars)
- aspect_ratio: auto, 1:1, 9:16, 16:9, 4:3, or 3:4
- model: GPT Image 2 / GPT Image 2.5 Flare / GPT Image 2.5 Sunburst
- background: opaque, transparent, auto (2.5 only)
- aspect_ratio: Choices depend on the model
- resolution: 1K, 2K, or 4K
- poll_interval_s: Status check interval
- timeout_s: Max wait time
- log: Console logging on/off
Outputs:
- IMAGE: ComfyUI image tensor (BHWC float32 0-1)
- IMAGE: RGB image tensor (BHWC float32 0-1)
- MASK: Transparency mask (BHW, 1 = transparent, 0 = opaque)
Notes:
- KIE requires 1K resolution when aspect_ratio is auto.
- KIE does not support 4K output with 1:1 aspect ratio.
- GPT Image 2: auto requires 1K; 1:1 does not support 4K.
- GPT Image 2.5: 27:16, 16:27, 9:8 and 8:9 require 1K.
- Use Join Image with Alpha with IMAGE and MASK to preserve transparency.
"""
@classmethod
@@ -372,14 +380,22 @@ Notes:
"prompt": ("STRING", {"multiline": True}),
},
"optional": {
"aspect_ratio": ("COMBO", {"options": GPT_IMAGE2_ASPECT_RATIO_OPTIONS, "default": "auto"}),
"resolution": ("COMBO", {"options": GPT_IMAGE2_RESOLUTION_OPTIONS, "default": "1K"}),
"aspect_ratio": (GPT_IMAGE2_ASPECT_RATIO_OPTIONS, {"default": "auto"}),
"resolution": (GPT_IMAGE2_RESOLUTION_OPTIONS, {"default": "1K"}),
"log": ("BOOLEAN", {"default": True}),
# Append widgets: old workflows serialize values in this order.
"model": (GPT_IMAGE_MODEL_OPTIONS, {
"default": GPT_IMAGE_DEFAULT_MODEL,
"kie_model_options": gpt_image_widget_options(),
}),
"background": (GPT_IMAGE_BACKGROUND_OPTIONS, {
"default": "opaque", "tooltip": "GPT Image 2.5 only. Use MASK to preserve transparency.",
}),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("image", "mask")
FUNCTION = "generate"
CATEGORY = "kie/api"
@@ -394,9 +410,13 @@ Notes:
retry_on_fail: bool = True,
max_retries: int = 2,
retry_backoff_s: float = 3.0,
model: str = GPT_IMAGE_DEFAULT_MODEL,
background: str = "opaque",
):
image_tensor = run_gpt_image2_text_to_image(
return run_gpt_image2_text_to_image(
prompt=prompt,
model=model,
background=background,
aspect_ratio=aspect_ratio,
resolution=resolution,
poll_interval_s=poll_interval_s,
@@ -406,30 +426,31 @@ Notes:
max_retries=max_retries,
retry_backoff_s=retry_backoff_s,
)
return (image_tensor,)
class KIE_GPTImage2_ImageToImage:
HELP = """
KIE GPT Image 2 (Image-to-Image)
KIE GPT Image (Image-to-Image)
Generate an edited or transformed image from a prompt and source images using GPT Image 2.
Generate an edited or transformed image from a prompt and source images using GPT Image 2, GPT Image 2.5 Flare, or GPT Image 2.5 Sunburst.
Inputs:
- prompt: Text prompt (required, up to 20,000 chars)
- images: Source image batch (up to 16 images; all uploaded)
- aspect_ratio: auto, 1:1, 9:16, 16:9, 4:3, or 3:4
- images: Source image batch (up to 16 images)
- model: GPT Image 2 / GPT Image 2.5 Flare / GPT Image 2.5 Sunburst
- background: opaque, transparent, auto (2.5 only)
- aspect_ratio: Choices depend on the model
- resolution: 1K, 2K, or 4K
- poll_interval_s: Status check interval
- timeout_s: Max wait time
- log: Console logging on/off
Outputs:
- IMAGE: ComfyUI image tensor (BHWC float32 0-1)
- IMAGE: RGB image tensor (BHWC float32 0-1)
- MASK: Transparency mask (BHW, 1 = transparent, 0 = opaque)
Notes:
- KIE requires 1K resolution when aspect_ratio is auto.
- KIE does not support 4K output with 1:1 aspect ratio.
- GPT Image 2: auto requires 1K; 1:1 does not support 4K.
- GPT Image 2.5: 27:16, 16:27, 9:8 and 8:9 require 1K.
- Use Join Image with Alpha with IMAGE and MASK to preserve transparency.
"""
@classmethod
@@ -440,14 +461,22 @@ Notes:
"images": ("IMAGE",),
},
"optional": {
"aspect_ratio": ("COMBO", {"options": GPT_IMAGE2_ASPECT_RATIO_OPTIONS, "default": "auto"}),
"resolution": ("COMBO", {"options": GPT_IMAGE2_RESOLUTION_OPTIONS, "default": "1K"}),
"aspect_ratio": (GPT_IMAGE2_ASPECT_RATIO_OPTIONS, {"default": "auto"}),
"resolution": (GPT_IMAGE2_RESOLUTION_OPTIONS, {"default": "1K"}),
"log": ("BOOLEAN", {"default": True}),
# Append widgets: old workflows serialize values in this order.
"model": (GPT_IMAGE_MODEL_OPTIONS, {
"default": GPT_IMAGE_DEFAULT_MODEL,
"kie_model_options": gpt_image_widget_options(),
}),
"background": (GPT_IMAGE_BACKGROUND_OPTIONS, {
"default": "opaque", "tooltip": "GPT Image 2.5 only. Use MASK to preserve transparency.",
}),
},
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("image", "mask")
FUNCTION = "generate"
CATEGORY = "kie/api"
@@ -463,9 +492,13 @@ Notes:
retry_on_fail: bool = True,
max_retries: int = 2,
retry_backoff_s: float = 3.0,
model: str = GPT_IMAGE_DEFAULT_MODEL,
background: str = "opaque",
):
image_tensor = run_gpt_image2_image_to_image(
return run_gpt_image2_image_to_image(
prompt=prompt,
model=model,
background=background,
images=images,
aspect_ratio=aspect_ratio,
resolution=resolution,
@@ -476,7 +509,6 @@ Notes:
max_retries=max_retries,
retry_backoff_s=retry_backoff_s,
)
return (image_tensor,)
class KIE_Seedream45_TextToImage:
@@ -2450,8 +2482,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"KIE_GetRemainingCredits": "KIE Get Remaining Credits",
"KIE_NanoBananaPro_Image": "KIE Nano Banana Pro (Image)",
"KIE_NanoBanana2_Image": "Nano Banana 2",
"KIE_GPTImage2_TextToImage": "KIE GPT Image 2 (Text-to-Image)",
"KIE_GPTImage2_ImageToImage": "KIE GPT Image 2 (Image-to-Image)",
"KIE_GPTImage2_TextToImage": "KIE GPT Image (Text-to-Image)",
"KIE_GPTImage2_ImageToImage": "KIE GPT Image (Image-to-Image)",
"KIE_Seedream45_TextToImage": "KIE Seedream 4.5 Text-To-Image",
"KIE_Seedream45_Edit": "KIE Seedream 4.5 Edit",
"KIE_GrokImagine_T2I": "KIE Grok Imagine (T2I)",
+2 -2
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-kie-api"
version = "0.1.16"
description = "Node pack integrating Kie.ai API nodes for Nano Banana Pro, GPT Image 2, Kling, Seedance, Gemini and (Suno) models, with helper utilities for uploads, grid slicing, prompt parsing, and API polling."
version = "0.1.17"
description = "Node pack integrating Kie.ai API nodes for Nano Banana Pro, GPT Image 2 and 2.5 (Flare/Sunburst), Kling, Seedance, Gemini and (Suno) models, with helper utilities for uploads, grid slicing, prompt parsing, and API polling."
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
+83
View File
@@ -0,0 +1,83 @@
// Headless widget lifecycle regressions; run with node --test tests/gpt_image_widgets.test.mjs.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import vm from "node:vm";
const source = readFileSync(new URL("../js/gpt_image.js", import.meta.url), "utf8")
.replace('import { app } from "../../scripts/app.js";', "");
function fixture() {
let extension;
vm.runInNewContext(source, {
app: { registerExtension(value) { extension = value; } }, queueMicrotask,
});
const rules = {
"GPT Image 2": {
aspect_ratios: ["auto", "1:1"],
resolutions: { auto: ["1K"], "1:1": ["1K", "2K"] },
backgrounds: ["opaque"],
},
"GPT Image 2.5 Flare": {
aspect_ratios: ["auto", "1:1", "27:16"],
resolutions: { auto: ["1K", "2K", "4K"], "1:1": ["1K", "2K", "4K"], "27:16": ["1K"] },
backgrounds: ["opaque", "transparent", "auto"],
},
};
const name = "KIE_GPTImage2_TextToImage";
extension.beforeRegisterNodeDef(null, {
name, input: { optional: { model: [Object.keys(rules), { kie_model_options: rules }] } },
});
const widgets = Object.entries({ aspect_ratio: "auto", resolution: "1K", model: "GPT Image 2", background: "opaque" })
.map(([name, value]) => ({ name, value, options: {} }));
let configured = false;
const node = { comfyClass: name, widgets, inputs: [], onConfigure() { configured = true; } };
extension.nodeCreated(node);
const get = (name) => widgets.find((widget) => widget.name === name);
return { node, get, configured: () => configured };
}
test("cloned 2.5 values refresh menus without normalizing saved values", () => {
const { node, get, configured } = fixture();
get("model").value = "GPT Image 2.5 Flare";
get("resolution").value = "4K";
get("background").value = "transparent";
node.onConfigure({});
assert.equal(configured(), true);
assert.ok(get("resolution").options.values.includes("4K"));
assert.ok(get("background").options.values.includes("transparent"));
assert.equal(get("resolution").value, "4K");
assert.equal(get("background").value, "transparent");
});
test("connected selectors restore all potentially valid choices", async () => {
for (const input of ["model", "aspect_ratio"]) {
const { node, get } = fixture();
if (input === "aspect_ratio") {
get("model").value = "GPT Image 2.5 Flare";
get("aspect_ratio").value = "27:16";
node.onConfigure({});
}
assert.equal(get("resolution").options.values.length, 1);
node.inputs.push({ name: input, link: 1 });
node.onConnectionsChange();
await new Promise(queueMicrotask);
assert.ok(get("resolution").options.values.includes("4K"));
node.inputs[0].link = null;
node.onConnectionsChange();
await new Promise(queueMicrotask);
assert.equal(get("resolution").options.values.length, 1);
}
});
test("explicit model changes normalize incompatible selections", () => {
const { node, get } = fixture();
get("model").value = "GPT Image 2.5 Flare";
get("aspect_ratio").value = "27:16";
get("background").value = "transparent";
node.onConfigure({});
get("model").value = "GPT Image 2";
get("model").callback("GPT Image 2");
assert.equal(get("aspect_ratio").value, "auto");
assert.equal(get("background").value, "opaque");
});
+57
View File
@@ -0,0 +1,57 @@
"""Contract regressions; no network calls or ComfyUI imports."""
import unittest
from kie_api.gpt_image_options import build_gpt_image_payload
class GPTImageOptionsTests(unittest.TestCase):
def payload(self, **overrides):
args = dict(model="GPT Image 2", mode="text-to-image", prompt="A poster",
aspect_ratio="auto", resolution="1K", background="opaque")
args.update(overrides)
return build_gpt_image_payload(**args)
def test_legacy_payload_is_unchanged(self):
self.assertEqual(self.payload(), {
"model": "gpt-image-2-text-to-image",
"input": {"prompt": "A poster", "aspect_ratio": "auto", "resolution": "1K"},
})
def test_all_four_new_routes_and_background(self):
for variant in ("Flare", "Sunburst"):
for mode in ("text-to-image", "image-to-image"):
with self.subTest(variant=variant, mode=mode):
payload = self.payload(model=f"GPT Image 2.5 {variant}", mode=mode,
resolution="4K", background="transparent")
self.assertEqual(payload["model"], f"gpt-image-2-5-{variant.lower()}-{mode}")
self.assertEqual(payload["input"]["background"], "transparent")
self.assertNotIn("callBackUrl", payload)
def test_restrictions_are_model_specific(self):
for ratio, resolution in (("auto", "2K"), ("1:1", "4K"), ("3:2", "1K")):
with self.subTest(ratio=ratio, resolution=resolution):
with self.assertRaises(RuntimeError):
self.payload(aspect_ratio=ratio, resolution=resolution)
for variant in ("Flare", "Sunburst"):
self.payload(model=f"GPT Image 2.5 {variant}",
aspect_ratio=ratio, resolution=resolution)
for variant in ("Flare", "Sunburst"):
for ratio in ("27:16", "16:27", "9:8", "8:9"):
self.payload(model=f"GPT Image 2.5 {variant}", aspect_ratio=ratio)
for resolution in ("2K", "4K"):
with self.assertRaises(RuntimeError):
self.payload(model=f"GPT Image 2.5 {variant}",
aspect_ratio=ratio, resolution=resolution)
def test_invalid_parameters_fail_closed(self):
for override in (dict(model="unknown"), dict(mode="edit"), dict(prompt=" "),
dict(prompt="a" * 20001), dict(resolution="8K"),
dict(background="transparent"), dict(aspect_ratio="5:4")):
with self.subTest(override=list(override)):
with self.assertRaises(RuntimeError):
self.payload(**override)
if __name__ == "__main__":
unittest.main()
+60
View File
@@ -0,0 +1,60 @@
"""Run in the Windows ComfyUI Python environment; paid services are mocked."""
from io import BytesIO
import unittest
from unittest.mock import patch
import torch
from PIL import Image
from kie_api import gpt_image2
from kie_api.images import _image_bytes_to_tensor_and_mask
class GPTImageRuntimeTests(unittest.TestCase):
def test_alpha_is_inverse_mask_and_rgb_is_preserved(self):
img = Image.new("RGBA", (3, 1))
img.putdata([(255, 0, 0, 0), (0, 255, 0, 128), (0, 0, 255, 255)])
data = BytesIO()
img.save(data, format="PNG")
image, mask = _image_bytes_to_tensor_and_mask(data.getvalue())
self.assertEqual(tuple(image.shape), (1, 1, 3, 3))
self.assertEqual(tuple(mask.shape), (1, 1, 3))
self.assertEqual(image.dtype, torch.float32)
torch.testing.assert_close(image[0, 0], torch.eye(3))
torch.testing.assert_close(mask, torch.tensor([[[1.0, 1.0 - 128 / 255, 0.0]]]))
def test_opaque_image_yields_full_size_zero_mask(self):
data = BytesIO()
Image.new("RGB", (3, 2), "white").save(data, format="PNG")
_, mask = _image_bytes_to_tensor_and_mask(data.getvalue())
torch.testing.assert_close(mask, torch.zeros((1, 2, 3)))
def test_invalid_i2i_fails_before_auth_or_upload(self):
with patch.object(gpt_image2, "_load_api_key") as auth:
for overrides in (dict(aspect_ratio="27:16", resolution="4K"),
dict(images=torch.zeros((17, 1, 1, 3)))):
args = dict(prompt="Edit", images=torch.zeros((1, 1, 1, 3)),
model="GPT Image 2.5 Flare", aspect_ratio="auto", resolution="1K")
args.update(overrides)
with self.assertRaises(RuntimeError):
gpt_image2.run_gpt_image2_image_to_image(**args)
auth.assert_not_called()
def test_i2i_uploads_references_into_selected_model_payload(self):
with patch.object(gpt_image2, "_load_api_key", return_value="test"), \
patch.object(gpt_image2, "_image_tensor_to_png_bytes", return_value=b"png"), \
patch.object(gpt_image2, "_upload_image", side_effect=["https://a", "https://b"]), \
patch.object(gpt_image2, "_run_gpt_image2_payload", return_value=("image", "mask")) as run:
result = gpt_image2.run_gpt_image2_image_to_image(
prompt="Edit", images=torch.zeros((2, 1, 1, 3)),
model="GPT Image 2.5 Sunburst", aspect_ratio="auto", resolution="4K", log=False,
)
self.assertEqual(result, ("image", "mask"))
payload = run.call_args.kwargs["payload"]
self.assertEqual(payload["model"], "gpt-image-2-5-sunburst-image-to-image")
self.assertEqual(payload["input"]["input_urls"], ["https://a", "https://b"])
if __name__ == "__main__":
unittest.main()
+43 -44
View File
@@ -1,61 +1,60 @@
# KIE GPT Image 2 (Image-to-Image)
# KIE GPT Image (Image-to-Image)
Generate an edited or transformed image from a prompt and source images using KIE's GPT Image 2 image-to-image endpoint.
Provider model: `gpt-image-2-image-to-image`
Source docs: https://docs.kie.ai/market/gpt/gpt-image-2-image-to-image
---
Generate or edit images with GPT Image 2, GPT Image 2.5 Flare, or GPT Image 2.5 Sunburst.
## Inputs
- **Prompt**
Required text prompt. Maximum length: 20,000 characters.
- **Prompt:** Required multiline text, up to 20,000 characters.
- **Images:** Required RGB image batch, up to 16 reference images. Uploaded URLs are sent as `input_urls`.
- **Model:** `GPT Image 2` (default), `GPT Image 2.5 Flare`, or `GPT Image 2.5 Sunburst`.
- **Aspect ratio:** Model-dependent choices, default `auto`.
- **Resolution:** `1K` (default), `2K`, or `4K`, restricted by aspect ratio.
- **Background:** `opaque` (default), `transparent`, or `auto` for 2.5. GPT Image 2 offers only `opaque`; its API payload omits background.
- **Log:** Console progress output.
- **Images**
Required ComfyUI image batch. The node uploads up to 16 images and sends those URLs through KIE's `input_urls` field.
GPT Image 2 supports `auto`, `1:1`, `9:16`, `16:9`, `4:3`, and `3:4`.
GPT Image 2.5 also supports `3:2`, `2:3`, `21:9`, `27:16`, `16:27`, `9:8`, and `8:9`.
- **Aspect Ratio**
`auto`, `1:1`, `9:16`, `16:9`, `4:3`, or `3:4`.
## Validation
- **Resolution**
`1K`, `2K`, or `4K`.
- **Log**
Enable progress output in the console.
---
- GPT Image 2: `auto` requires 1K; `1:1` permits 1K and 2K only.
- GPT Image 2.5: `27:16`, `16:27`, `9:8`, and `8:9` require 1K; other listed ratios permit all three resolutions.
- Changing model or aspect ratio refreshes dropdowns and resets incompatible widget selections to the first allowed value. Loading saved workflows does not normalize saved values.
- Python validates before uploads or submission, including connected inputs and headless API workflows.
- GPT Image 2.5 rejects batches over 16 before upload. GPT Image 2 retains its existing warning and first-16 truncation behavior.
- Reuses the shared task, upload, polling, download and transient retry helpers. No callback widget is exposed.
## Outputs
- **IMAGE**
ComfyUI image tensor (BHWC, float32, range 0-1).
- **IMAGE / image:** RGB `[B,H,W,3]`, float32, range 0–1. Remains at output slot 0.
- **MASK / mask:** Inverse alpha `[B,H,W]`, float32, range 0–1. 1 is transparent, 0 is opaque. Opaque results yield zeros at image resolution.
---
For transparent PNG output, connect IMAGE and MASK to ComfyUI's **Join Image with Alpha**, then save its output. Saving RGB alone discards transparency. MASK describes output transparency, not an input editing mask.
## Validation Notes
## Compatibility
- Maximum source image inputs: **16**.
- `auto` aspect ratio is only valid with `1K` resolution.
- `1:1` aspect ratio is not valid with `4K` resolution.
- The node uses `input_urls`, matching the live KIE GPT Image 2 I2I API schema.
- The node omits callback/webhook UI and uses the shared async polling flow.
Internal ID `KIE_GPTImage2_ImageToImage` is unchanged. Model and background widgets are appended after existing widgets; missing values default to GPT Image 2 and opaque. Existing image connections retain slot 0. The visible name is now **KIE GPT Image (Image-to-Image)**.
---
Restart ComfyUI and refresh the browser after updating.
## Payload Shape
## Provider models and sources
```json
{
"model": "gpt-image-2-image-to-image",
"input": {
"prompt": "Transform this product image into a premium e-commerce poster style.",
"input_urls": [
"https://example.com/source.png"
],
"aspect_ratio": "auto",
"resolution": "1K"
}
}
Verified against Kie documentation on 2026-10-06:
- [GPT Image 2](https://docs.kie.ai/market/gpt/gpt-image-2-image-to-image): `gpt-image-2-image-to-image`
- [Flare](https://docs.kie.ai/market/gpt/gpt-image-2-5-flare-image-to-image): `gpt-image-2-5-flare-image-to-image`
- [Sunburst](https://docs.kie.ai/market/gpt/gpt-image-2-5-sunburst-image-to-image): `gpt-image-2-5-sunburst-image-to-image`
Pinned ratios follow the actual schema `enum` and Kie model page, not extra labels in I2I documentation's `x-apidog-enum` metadata.
## Verification
Mac checks cover syntax and static review only. No paid generation or ComfyUI runtime verification has been performed here.
On Windows, from this repository using the ComfyUI Python environment:
```text
python -m unittest discover -s tests -p "test_gpt_image*.py"
```
Load an older GPT Image 2 workflow and verify its prompt, ratio, resolution, logging and image connection. Generate with each 2.5 model in both modes; exercise auto at 2K/4K, 1K-only ratios, invalid connected inputs, and transparency via Join Image with Alpha. Save/reload and verify selections. These UI/live checks remain pending.
+41 -36
View File
@@ -1,53 +1,58 @@
# KIE GPT Image 2 (Text-to-Image)
# KIE GPT Image (Text-to-Image)
Generate an image from a text prompt using KIE's GPT Image 2 text-to-image endpoint.
Provider model: `gpt-image-2-text-to-image`
Source docs: https://docs.kie.ai/market/gpt/gpt-image-2-text-to-image
---
Generate or edit images with GPT Image 2, GPT Image 2.5 Flare, or GPT Image 2.5 Sunburst.
## Inputs
- **Prompt**
Required text prompt. Maximum length: 20,000 characters.
- **Prompt:** Required multiline text, up to 20,000 characters.
- **Model:** `GPT Image 2` (default), `GPT Image 2.5 Flare`, or `GPT Image 2.5 Sunburst`.
- **Aspect ratio:** Model-dependent choices, default `auto`.
- **Resolution:** `1K` (default), `2K`, or `4K`, restricted by aspect ratio.
- **Background:** `opaque` (default), `transparent`, or `auto` for 2.5. GPT Image 2 offers only `opaque`; its API payload omits background.
- **Log:** Console progress output.
- **Aspect Ratio**
`auto`, `1:1`, `9:16`, `16:9`, `4:3`, or `3:4`.
GPT Image 2 supports `auto`, `1:1`, `9:16`, `16:9`, `4:3`, and `3:4`.
GPT Image 2.5 also supports `3:2`, `2:3`, `21:9`, `27:16`, `16:27`, `9:8`, and `8:9`.
- **Resolution**
`1K`, `2K`, or `4K`.
## Validation
- **Log**
Enable progress output in the console.
---
- GPT Image 2: `auto` requires 1K; `1:1` permits 1K and 2K only.
- GPT Image 2.5: `27:16`, `16:27`, `9:8`, and `8:9` require 1K; other listed ratios permit all three resolutions.
- Changing model or aspect ratio refreshes dropdowns and resets incompatible widget selections to the first allowed value. Loading saved workflows does not normalize saved values.
- Python validates before uploads or submission, including connected inputs and headless API workflows.
- Reuses the shared task, upload, polling, download and transient retry helpers. No callback widget is exposed.
## Outputs
- **IMAGE**
ComfyUI image tensor (BHWC, float32, range 0-1).
- **IMAGE / image:** RGB `[B,H,W,3]`, float32, range 0–1. Remains at output slot 0.
- **MASK / mask:** Inverse alpha `[B,H,W]`, float32, range 0–1. 1 is transparent, 0 is opaque. Opaque results yield zeros at image resolution.
---
For transparent PNG output, connect IMAGE and MASK to ComfyUI's **Join Image with Alpha**, then save its output. Saving RGB alone discards transparency. MASK describes output transparency, not an input editing mask.
## Validation Notes
## Compatibility
- `auto` aspect ratio is only valid with `1K` resolution.
- `1:1` aspect ratio is not valid with `4K` resolution.
- The node omits callback/webhook UI and uses the shared async polling flow.
Internal ID `KIE_GPTImage2_TextToImage` is unchanged. Model and background widgets are appended after existing widgets; missing values default to GPT Image 2 and opaque. Existing image connections retain slot 0. The visible name is now **KIE GPT Image (Text-to-Image)**.
---
Restart ComfyUI and refresh the browser after updating.
## Payload Shape
## Provider models and sources
```json
{
"model": "gpt-image-2-text-to-image",
"input": {
"prompt": "A cinematic night city poster with neon reflections on a rainy street.",
"aspect_ratio": "auto",
"resolution": "1K"
}
}
Verified against Kie documentation on 2026-10-06:
- [GPT Image 2](https://docs.kie.ai/market/gpt/gpt-image-2-text-to-image): `gpt-image-2-text-to-image`
- [Flare](https://docs.kie.ai/market/gpt/gpt-image-2-5-flare-text-to-image): `gpt-image-2-5-flare-text-to-image`
- [Sunburst](https://docs.kie.ai/market/gpt/gpt-image-2-5-sunburst-text-to-image): `gpt-image-2-5-sunburst-text-to-image`
Pinned ratios follow the actual schema `enum` and Kie model page, not extra labels in I2I documentation's `x-apidog-enum` metadata.
## Verification
Mac checks cover syntax and static review only. No paid generation or ComfyUI runtime verification has been performed here.
On Windows, from this repository using the ComfyUI Python environment:
```text
python -m unittest discover -s tests -p "test_gpt_image*.py"
```
Load an older GPT Image 2 workflow and verify its prompt, ratio, resolution, logging and image connection. Generate with each 2.5 model in both modes; exercise auto at 2K/4K, 1K-only ratios, invalid connected inputs, and transparency via Join Image with Alpha. Save/reload and verify selections. These UI/live checks remain pending.
+3 -3
View File
@@ -2,7 +2,7 @@
This folder contains node documentation for the ComfyUI KIE API pack.
GPT Image 2 status: `Text-to-Image` and `Image-to-Image` are implemented and documented in this folder.
GPT Image 2 / 2.5 status: both modes support GPT Image 2, Flare, and Sunburst. Windows ComfyUI/live verification of 2.5 is pending.
Grok Imagine status: `T2I`, `I2I`, `T2V`, and `I2V` are implemented and documented in this folder.
@@ -37,8 +37,8 @@ Grok Imagine status: `T2I`, `I2I`, `T2V`, and `I2V` are implemented and document
## Image Nodes
- Nano Banana Pro: [`KIE_NanoBananaPro_Image.md`](KIE_NanoBananaPro_Image.md)
- Nano Banana 2: [`KIE_NanoBanana2_Image.md`](KIE_NanoBanana2_Image.md)
- GPT Image 2 Text-to-Image: [`KIE_GPTImage2_TextToImage.md`](KIE_GPTImage2_TextToImage.md)
- GPT Image 2 Image-to-Image: [`KIE_GPTImage2_ImageToImage.md`](KIE_GPTImage2_ImageToImage.md)
- GPT Image 2 / 2.5 Text-to-Image: [`KIE_GPTImage2_TextToImage.md`](KIE_GPTImage2_TextToImage.md)
- GPT Image 2 / 2.5 Image-to-Image: [`KIE_GPTImage2_ImageToImage.md`](KIE_GPTImage2_ImageToImage.md)
- Flux 2 I2I: [`KIE_Flux2_I2I.md`](KIE_Flux2_I2I.md)
- Grok Imagine T2I: [`KIE_GrokImagine_T2I.md`](KIE_GrokImagine_T2I.md)
- Grok Imagine I2I: [`KIE_GrokImagine_I2I.md`](KIE_GrokImagine_I2I.md)