From 0eb1726b41334397d38e57e16edba7da36c246f0 Mon Sep 17 00:00:00 2001 From: Dreaming Computers Date: Fri, 30 Jan 2026 12:02:08 +0700 Subject: [PATCH] feat: add flux 2 i2i node --- README.md | 6 ++ docs/flux_i2i_plan.md | 47 +++++++++++ docs/flux_i2i_status.md | 9 ++ docs/flux_i2i_tasks.md | 32 ++++++++ docs/implementation_plan.md | 107 ++++++++++++++++++++++++ docs/node_from_spec.md | 159 ++++++++++++++++++++++++++++++++++++ kie_api/flux2_i2i.py | 150 ++++++++++++++++++++++++++++++++++ nodes.py | 70 ++++++++++++++++ web/docs/KIE_Flux2_I2I.md | 21 +++++ 9 files changed, 601 insertions(+) create mode 100644 docs/flux_i2i_plan.md create mode 100644 docs/flux_i2i_status.md create mode 100644 docs/flux_i2i_tasks.md create mode 100644 docs/implementation_plan.md create mode 100644 docs/node_from_spec.md create mode 100644 kie_api/flux2_i2i.py create mode 100644 web/docs/KIE_Flux2_I2I.md diff --git a/README.md b/README.md index 9812547..c746909 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ This node pack currently includes the following nodes: - Text-to-image and image-editing node for Seedream models. - Supports prompt-based generation and edits. +- **Flux 2 Image-to-Image (Pro/Flex)** + - Image-to-image node with a model dropdown for Pro or Flex. + - Accepts 1–8 input images via ComfyUI batch. + - **Kling 2.6 Image-to-Video** - Generates video from a single input image. - Uses the Kling 2.6 image-to-video model. @@ -132,6 +136,8 @@ This pattern is useful for: Both workflows are meant as **examples**, not strict requirements. Feel free to adapt, simplify, or remix them to fit your own pipelines. +## Changelog +- 2026-01-30: Added Flux 2 Image-to-Image node (Pro/Flex) with model dropdown. ## About Kie.ai Kie.ai is a unified API and model marketplace for image, video, and audio generation. This project is community-maintained and not affiliated with Kie.ai. Learn more at [https://kie.ai](https://kie.ai?ref=e7565cf24a7fad4586341a87eaf21e42). diff --git a/docs/flux_i2i_plan.md b/docs/flux_i2i_plan.md new file mode 100644 index 0000000..32c02c8 --- /dev/null +++ b/docs/flux_i2i_plan.md @@ -0,0 +1,47 @@ +# Flux 2 I2I Nodes — Plan + +## Spec scan (source of truth) +- `_internal/docs/KIE_API_FLUX-2-PRO-I2I_SPEC.md` +- `_internal/docs/KIE_AI-FLUX-2-FLEX-I2I_SPEC.MD` + +## Field comparison (Pro vs Flex) +Same fields in both specs: +- `model` (string; only value differs) +- `input.input_urls` (required, array URL, 1–8 images) +- `input.prompt` (required, 3–5000 chars) +- `input.aspect_ratio` (required enum) +- `input.resolution` (required enum) +- `callBackUrl` (optional) + +Enumerations (identical): +- `aspect_ratio`: 1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3, auto +- `resolution`: 1K, 2K + +Conclusion: inputs/outputs are identical; only `model` differs. + +## Node architecture decision +We can implement a **single node** with a `model` dropdown: +- `flux-2/pro-image-to-image` +- `flux-2/flex-image-to-image` + +Conditions satisfied: +- All other parameters are the same in both specs. +- Output type is the same (IMAGE). + +## Helper vs node split +Helper: +- `kie_api/flux2_i2i.py` (new) +- Accept `model` as a required argument. +- Handle upload, createTask, poll, download, decode. + +Node: +- `KIE_Flux2_I2I` in `nodes.py` +- `model` dropdown + inputs from spec +- Calls `run_flux2_i2i(...)` + +Docs: +- `web/docs/KIE_Flux2_I2I.md` (new) + +## Open questions (confirm before code) +1) Should we allow 1–8 images (batch) or force 1 image? +2) Do you want `model` in node name or just in a dropdown? diff --git a/docs/flux_i2i_status.md b/docs/flux_i2i_status.md new file mode 100644 index 0000000..0845027 --- /dev/null +++ b/docs/flux_i2i_status.md @@ -0,0 +1,9 @@ +# Flux 2 I2I — Status + +Last updated: 2026-01-30 + +- [x] Helper added (`kie_api/flux2_i2i.py`) +- [x] Node wrapper added (`nodes.py`) +- [x] Node registered in mappings (`nodes.py`) +- [x] Node docs added (`web/docs/KIE_Flux2_I2I.md`) +- [x] Compile check (`python3 -m py_compile nodes.py kie_api/flux2_i2i.py`) diff --git a/docs/flux_i2i_tasks.md b/docs/flux_i2i_tasks.md new file mode 100644 index 0000000..6cb2f80 --- /dev/null +++ b/docs/flux_i2i_tasks.md @@ -0,0 +1,32 @@ +# Flux 2 I2I — Task List + +## Phase 0: Agree on scope +- Confirm single node with model dropdown is acceptable. +- Confirm allowed image count (1 vs 1–8). + +## Phase 1: Spec-to-helper +- Add `kie_api/flux2_i2i.py` + - constants: MODEL_OPTIONS, ASPECT_RATIO_OPTIONS, RESOLUTION_OPTIONS + - validate prompt length (3–5000) + - validate images tensor shape and count + - upload images (1..8) + - createTask + poll + extract result URLs + - download + decode to tensor + +## Phase 2: Node wrapper +- Add class `KIE_Flux2_I2I` to `nodes.py` +- `INPUT_TYPES` reflects spec (plus `model` dropdown, `log`) +- Call `run_flux2_i2i(...)` +- Add HELP text + +## Phase 3: Register node +- Import new helper in `nodes.py` +- Add to `NODE_CLASS_MAPPINGS` +- Add to `NODE_DISPLAY_NAME_MAPPINGS` + +## Phase 4: Docs +- Add `web/docs/KIE_Flux2_I2I.md` +- Include inputs/outputs + enums + defaults + +## Phase 5: Sanity checks +- `python3 -m py_compile nodes.py kie_api/flux2_i2i.py` diff --git a/docs/implementation_plan.md b/docs/implementation_plan.md new file mode 100644 index 0000000..ef4a9b1 --- /dev/null +++ b/docs/implementation_plan.md @@ -0,0 +1,107 @@ +# ComfyUI Node + Helper Separation Plan + +## Goals +- Keep model-specific API logic in helpers (`kie_api/*`). +- Keep ComfyUI node definitions in `nodes.py` (or a future `nodes/` package). +- Ensure every node relies on helpers instead of re-implementing API calls. +- Document best practices and track follow-up fixes. + +## Current Nodes (as of Jan 30, 2026) +Image: +- Nano Banana Pro (image) +- Seedream 4.5 (text-to-image) +- Seedream 4.5 (edit) + +Video: +- Kling 2.5 I2V Pro +- Kling 2.6 I2V +- Kling 2.6 T2V +- Kling 2.6 Motion I2V +- Seedance V1 Pro Fast I2V +- Seedance 1.5 Pro I2V/T2V + +Helpers: +- auth, credits, http, log, jobs, results +- upload, images, video, validation, grid, prompt_lists +- model-specific helpers: nanobanana, seedream45_t2i, seedream45_edit, seedance*, kling* + +## Best Practices Implementation (Current Standard) +1) **Nodes are thin wrappers.** + - Node `generate()` methods delegate to helper functions (`run_*`). + - Node I/O uses ComfyUI types (`IMAGE`, `VIDEO`, `STRING`, `INT`, `BOOLEAN`). + +2) **Helpers encapsulate the full job lifecycle.** + - Validation → upload → create task → poll → download/decode → credits logging. + - Shared behavior is already centralized in `jobs.py`, `upload.py`, `images.py`, `video.py`, `validation.py`. + +3) **Consistent error handling.** + - `TransientKieError` signals retryable failures (HTTP 429 or 5xx, transient task failure states). + - Non-retry errors raise `RuntimeError` with clear messages. + +4) **Logging is centralized and predictable.** + - `_log()` is used for all progress messages. + - Nodes expose a `log` flag and pass it through. + +5) **Enum inputs are pinned.** + - ComfyUI node options use COMBOs that map to pinned server enums (`duration`, `aspect_ratio`, etc.). + +6) **Helpers own logic; nodes own UI.** + - No API calls, uploads, or polling logic inside nodes. + - Nodes should only translate ComfyUI inputs → helper inputs and return helper outputs. + +7) **Docs reflect actual behavior.** + - Node docs in `web/docs/` mirror the helper inputs/outputs and current defaults. + - HELP text and `INPUT_TYPES` must match. + +## Things To Review / Fix (Cleanup Targets) +1) **Duplicate `createTask` logic across helpers.** + - `nanobanana.py`, `kling25_i2v.py`, `kling26_i2v.py`, `kling26_t2v.py`, `kling26motion_i2v.py`, + `seedance*`, `seedream*` all implement request + error handling for `createTask`. + - Candidate: move to `jobs.py` or a `requests.py` helper for shared API calls. + +2) **Duplicate image upload + validation patterns.** + - Each model re-validates image tensor shapes, slices batches, and uploads via `_upload_image`. + - Candidate: a shared helper (e.g. `_upload_first_image`, `_upload_images_limit`). + +3) **Node inputs do not expose all documented parameters.** + - Several node HELP blocks mention `poll_interval_s`, `timeout_s`, and retry settings, + but these are not exposed in `INPUT_TYPES`. + - Decide whether to: + - expose them via `INPUT_TYPES`, or + - remove from HELP to avoid confusion. + +4) **Model docs vs helper docs drift risk.** + - `web/docs/` contains node pages; helper behavior lives in `kie_api/*`. + - Add a short helper summary section per model doc or generate them from helpers. + +5) **Category naming consistency.** + - Some helpers appear as nodes in `kie/helpers` (grid, prompt parsing), while others are `kie/api`. + - Clarify if helper nodes should live under a dedicated category and be separated from API nodes in UI. + +## Cleanup Task List (Initial) +### 2) Normalize helper APIs (createTask + upload + validation) +- Extract a shared `create_task()` helper (or move into `jobs.py`) to remove per-model duplication. +- Add shared helpers for common upload patterns: + - upload first image + - upload optional tail image + - upload list of images with a max limit +- Consolidate image shape validation helpers (single image vs batch) into `validation.py`. + +### 3) Align node HELP with `INPUT_TYPES` +- Audit each node in `nodes.py` for: + - missing inputs referenced in HELP + - defaults or ranges that differ between HELP and `INPUT_TYPES` +- Either expose these inputs or remove them from HELP to avoid mismatch. + +### 4) Keep docs in sync with helpers +- For each model in `web/docs/`, add a short “Helper behavior” section: + - validation rules + - retry behavior + - polling defaults + - output format +- Add a checklist to update docs when helper defaults change. + +## Next Steps (if approved) +1) Create a dedicated “Best Practices + Implementation Checklist” doc for adding new models. +2) Start Cleanup Task List item 3 (HELP vs INPUT_TYPES) as the first low-hanging task. +3) After cleanup item 3, draft a new node using the standard pattern. diff --git a/docs/node_from_spec.md b/docs/node_from_spec.md new file mode 100644 index 0000000..54eba50 --- /dev/null +++ b/docs/node_from_spec.md @@ -0,0 +1,159 @@ +# Building ComfyUI Nodes From KIE API Specs + +This is the ground-truth process for turning a pinned KIE API spec into: +- a helper (model runner) in `kie_api/` +- a ComfyUI node wrapper in `nodes.py` +- a matching user-facing doc in `web/docs/` + +## Source Material (What We Actually Use) +Primary inputs: +- `_internal/docs/KIE_API_*_SPEC.md` and other spec files in `_internal/docs/` +- `_internal/docs/COMFYUI_NODE_CHEATSHEET.md` +- `_internal/docs/REPO_RULES.md` +- Existing node + helper implementations (`nodes.py`, `kie_api/*.py`) + +Note: There is no `codecs/` folder in this repo. If one appears later, add it to this list. + +## Non-Negotiable Rules (Do Not Hallucinate) +- Never invent fields or options that are not in a spec. +- If a spec is incomplete or ambiguous, stop and request clarification. +- The helper owns API logic. The node is a thin wrapper only. +- ComfyUI image tensors are **BHWC float32** in **[0..1]**. +- Video outputs must remain compatible with ComfyUI `SaveVideo`. + +## Step 1: Parse the Spec (Pin the Facts) +From the spec file, extract these fields exactly: +- **Model name** (string used in the payload `model`) +- **Input fields** (required vs optional) +- **Allowed options** (enumerations for combos) +- **Input constraints** (max lengths, valid ranges, max count) +- **Response shape** (task id, result URLs, failure fields) + +If you cannot find a field in the spec, do not add it to the node. + +## Step 2: Decide Helper vs Node Responsibilities +Helpers (model runners) MUST do: +- prompt validation (length/range) +- image/video upload (if input URLs are required) +- task creation and polling +- result parsing and download/decoding +- logging + credit logging + +Nodes MUST do: +- define ComfyUI inputs/outputs +- call exactly one helper function +- return the helper output without modification + +## Step 3: Map Spec Inputs to ComfyUI Inputs +Use these mapping rules (always prefer existing patterns): + +Spec field type -> ComfyUI type: +- string prompt -> `STRING` (multiline) +- boolean -> `BOOLEAN` +- number -> `FLOAT` (with min/max/step if in spec) +- integer -> `INT` (with min/max if in spec) +- enum -> `COMBO` with the exact options list +- image URL(s) -> `IMAGE` (ComfyUI tensors), then upload in helper +- video URL(s) -> `VIDEO`, then upload in helper + +Required vs optional: +- Spec **required** -> `required` section of `INPUT_TYPES` +- Spec **optional** -> `optional` section of `INPUT_TYPES` + +Image and video rules: +- If spec allows 1..N images, accept a batch `IMAGE` input and enforce max N. +- If spec allows optional image list, permit `images=None` and skip upload. +- Video inputs are passed through the existing video helpers. + +## Step 4: Implement the Helper (Model Runner) +Location: `kie_api/.py` + +Required responsibilities: +1) Validate inputs +2) Upload any images/videos (convert tensors to bytes) +3) Create task (`createTask`) +4) Poll until completion (`recordInfo`) +5) Parse result URLs +6) Download output and convert to ComfyUI types + +Existing helpers to reuse: +- `kie_api/auth.py` (`_load_api_key`) +- `kie_api/jobs.py` (`_poll_task_until_complete`) +- `kie_api/upload.py` (image/video upload + conversions) +- `kie_api/images.py` (image download + tensor decode) +- `kie_api/video.py` (video download + SaveVideo-compatible output) +- `kie_api/results.py` (result URL extraction) +- `kie_api/validation.py` (prompt validation) +- `kie_api/log.py` (logging) +- `kie_api/credits.py` (credits logging) + +Helper output contract: +- Image models return `torch.Tensor` with shape [B, H, W, C], float32, 0..1 +- Video models return a SaveVideo-compatible object (match existing video nodes) + +## Step 5: Implement the Node Wrapper +Location: `nodes.py` + +Requirements: +- Keep node `generate()` minimal: just call helper and return results. +- `INPUT_TYPES`, `RETURN_TYPES`, `RETURN_NAMES`, `FUNCTION`, `CATEGORY` are required. +- `HELP` must match the actual node inputs and defaults. +- Expose enums as `COMBO` options exactly as listed in the spec. +- If spec includes optional inputs, include them under `optional`. + +## Step 6: Register the Node +Ensure: +- The node class is imported in `nodes.py` +- It appears in `NODE_CLASS_MAPPINGS` +- It has a name in `NODE_DISPLAY_NAME_MAPPINGS` + +## Step 7: Document the Node (web/docs) +Create or update `web/docs/.md`: +- Match the exact ComfyUI inputs and outputs +- Include defaults and enums +- Add a brief “Helper behavior” summary: + - validation rules + - polling defaults + - retry behavior (if any) + +## Helper/Node Consistency Checklist +Before declaring done: +- Node HELP matches INPUT_TYPES (no hidden inputs). +- Helper validates all required fields and enum options. +- Spec-required inputs are not optional in the node. +- All options in the spec are represented as COMBOs. +- Output type is correct (IMAGE or VIDEO). + +## Spec-to-Node Quick Example (Image-to-Image) +Spec fields: +- model: "flux-2/pro-image-to-image" +- input.input_urls (required, 1..8) +- input.prompt (required, max 5000) +- input.aspect_ratio (required enum) +- input.resolution (required enum) + +ComfyUI node inputs: +- `images: IMAGE` (required) +- `prompt: STRING` (required) +- `aspect_ratio: COMBO` (required) +- `resolution: COMBO` (required) +- `log: BOOLEAN` (optional, local-only) + +Helper responsibilities: +- Validate prompt length +- Validate `images` tensor shape and count (max 8) +- Upload images to URLs +- Build payload with `input_urls` +- Create task + poll +- Download result URL and decode to tensor + +## When Specs Are Incomplete +If a spec: +- omits an option list +- lacks a max length/range +- conflicts with existing helper behavior + +Then: +1) Do not guess defaults. +2) Add a TODO in the spec doc. +3) Ask for clarification or source doc before coding. diff --git a/kie_api/flux2_i2i.py b/kie_api/flux2_i2i.py new file mode 100644 index 0000000..6efe6cc --- /dev/null +++ b/kie_api/flux2_i2i.py @@ -0,0 +1,150 @@ +"""Flux 2 Pro/Flex image-to-image helper.""" + +import json +import time +from typing import Any + +import torch + +from .auth import _load_api_key +from .credits import _log_remaining_credits +from .http import TransientKieError, requests +from .jobs import _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 .images import _download_image, _image_bytes_to_tensor +from .validation import _validate_prompt + +CREATE_TASK_URL = "https://api.kie.ai/api/v1/jobs/createTask" +MODEL_OPTIONS = ["flux-2/pro-image-to-image", "flux-2/flex-image-to-image"] +ASPECT_RATIO_OPTIONS = ["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3", "auto"] +RESOLUTION_OPTIONS = ["1K", "2K"] +PROMPT_MIN_LENGTH = 3 +PROMPT_MAX_LENGTH = 5000 +MAX_IMAGE_COUNT = 8 + + +def _validate_images(images: torch.Tensor | None) -> torch.Tensor: + if images is None: + raise RuntimeError("images input is required.") + if not isinstance(images, torch.Tensor): + raise RuntimeError("images input must be a tensor batch.") + if images.dim() != 4 or images.shape[-1] != 3: + raise RuntimeError("images input must have shape [B, H, W, 3].") + if images.shape[0] < 1: + raise RuntimeError("images input batch is empty.") + return images + + +def _validate_prompt_range(prompt: str) -> None: + _validate_prompt(prompt, max_length=PROMPT_MAX_LENGTH) + if len(prompt.strip()) < PROMPT_MIN_LENGTH: + raise RuntimeError(f"Prompt must be at least {PROMPT_MIN_LENGTH} characters.") + + +def _create_flux_task(api_key: str, payload: dict[str, Any]) -> tuple[str, str]: + try: + response = requests.post( + CREATE_TASK_URL, + headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, + json=payload, + timeout=30, + ) + except requests.RequestException as exc: + raise RuntimeError(f"Failed to call createTask endpoint: {exc}") from exc + + if response.status_code == 429 or response.status_code >= 500: + raise TransientKieError( + f"createTask returned HTTP {response.status_code}: {response.text}", status_code=response.status_code + ) + + try: + payload_json: Any = response.json() + except json.JSONDecodeError as exc: + raise RuntimeError("createTask endpoint did not return valid JSON.") from exc + + if payload_json.get("code") != 200: + message = payload_json.get("message") or payload_json.get("msg") + raise RuntimeError(f"createTask endpoint returned error code {payload_json.get('code')}: {message}") + + task_id = (payload_json.get("data") or {}).get("taskId") + if not task_id: + raise RuntimeError("createTask endpoint did not return a taskId.") + + return task_id, response.text + + +def run_flux2_i2i( + *, + model: str, + prompt: str, + images: torch.Tensor, + aspect_ratio: str, + resolution: str, + poll_interval_s: float = 10.0, + timeout_s: int = 300, + log: bool = True, +) -> torch.Tensor: + """Run Flux 2 Pro/Flex image-to-image job end-to-end.""" + if model not in MODEL_OPTIONS: + raise RuntimeError("Invalid model. Use the pinned enum options.") + 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.") + + _validate_prompt_range(prompt) + images = _validate_images(images) + + api_key = _load_api_key() + total_images = images.shape[0] + if total_images > MAX_IMAGE_COUNT and log: + _log(log, f"More than {MAX_IMAGE_COUNT} images provided ({total_images}); only first {MAX_IMAGE_COUNT} used.") + + upload_count = min(total_images, MAX_IMAGE_COUNT) + image_urls: list[str] = [] + if upload_count > 0: + _log(log, f"Uploading {upload_count} image(s) for Flux 2 I2I...") + + for idx in range(upload_count): + png_bytes = _image_tensor_to_png_bytes(images[idx]) + url = _upload_image(api_key, png_bytes) + image_urls.append(url) + _log(log, f"Image {idx + 1} upload success: {_truncate_url(url)}") + + payload = { + "model": model, + "input": { + "input_urls": image_urls, + "prompt": prompt, + "aspect_ratio": aspect_ratio, + "resolution": resolution, + }, + } + + _log(log, "Creating Flux 2 I2I task...") + start_time = time.time() + task_id, create_response_text = _create_flux_task(api_key, payload) + _log(log, f"createTask response (elapsed={time.time() - start_time:.1f}s): {create_response_text}") + _log(log, f"Task created with ID {task_id}. Polling for completion...") + + record_data = _poll_task_until_complete( + api_key, + task_id, + poll_interval_s, + timeout_s, + log, + start_time, + ) + + result_urls = _extract_result_urls(record_data) + _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) + _log(log, "Image downloaded and decoded.") + + _log_remaining_credits(log, record_data, api_key, _log) + return image_tensor diff --git a/nodes.py b/nodes.py index 59d6ed1..ddcca7d 100644 --- a/nodes.py +++ b/nodes.py @@ -41,6 +41,12 @@ from .kie_api.kling26_t2v import ( DURATION_OPTIONS as KLING26_T2V_DURATION_OPTIONS, run_kling26_t2v_video, ) +from .kie_api.flux2_i2i import ( + ASPECT_RATIO_OPTIONS as FLUX2_ASPECT_RATIO_OPTIONS, + MODEL_OPTIONS as FLUX2_MODEL_OPTIONS, + RESOLUTION_OPTIONS as FLUX2_RESOLUTION_OPTIONS, + run_flux2_i2i, +) from .kie_api.prompt_lists import parse_prompts_json from .kie_api.grid import slice_grid_tensor from .kie_api.http import TransientKieError @@ -560,6 +566,68 @@ Outputs: time.sleep(backoff) +class KIE_Flux2_I2I: + HELP = """ +KIE Flux 2 (Image-to-Image) + +Generate an image from one or more input images using Flux 2 Pro or Flux 2 Flex. + +Inputs: +- images: Source image batch (1-8 images; all uploaded) +- prompt: Text prompt (required, 3–5000 chars) +- model: flux-2/pro-image-to-image or flux-2/flex-image-to-image +- aspect_ratio: Output aspect ratio (enum) +- resolution: 1K or 2K +- log: Console logging on/off + +Outputs: +- IMAGE: ComfyUI image tensor (BHWC float32 0–1) +""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE",), + "prompt": ("STRING", {"multiline": True}), + "model": ("COMBO", {"options": FLUX2_MODEL_OPTIONS, "default": "flux-2/pro-image-to-image"}), + "aspect_ratio": ("COMBO", {"options": FLUX2_ASPECT_RATIO_OPTIONS, "default": "1:1"}), + "resolution": ("COMBO", {"options": FLUX2_RESOLUTION_OPTIONS, "default": "1K"}), + }, + "optional": { + "log": ("BOOLEAN", {"default": True}), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = "generate" + CATEGORY = "kie/api" + + def generate( + self, + images: torch.Tensor, + prompt: str, + model: str = "flux-2/pro-image-to-image", + aspect_ratio: str = "1:1", + resolution: str = "1K", + log: bool = True, + poll_interval_s: float = 10.0, + timeout_s: int = 300, + ): + image_tensor = run_flux2_i2i( + model=model, + prompt=prompt, + images=images, + aspect_ratio=aspect_ratio, + resolution=resolution, + poll_interval_s=poll_interval_s, + timeout_s=timeout_s, + log=log, + ) + return (image_tensor,) + + class KIE_GridSlice: HELP = """ KIE Grid Slice @@ -745,6 +813,7 @@ NODE_CLASS_MAPPINGS = { "KIE_Kling26_I2V": KIE_Kling26_I2V, "KIE_Kling26_T2V": KIE_Kling26_T2V, "KIE_Kling26Motion_I2V": KIE_Kling26Motion_I2V, + "KIE_Flux2_I2I": KIE_Flux2_I2I, "KIE_GridSlice": KIE_GridSlice, "KIEParsePromptGridJSON": KIEParsePromptGridJSON, } @@ -759,6 +828,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "KIE_Kling26_I2V": "KIE Kling 2.6 (I2V)", "KIE_Kling26_T2V": "KIE Kling 2.6 (T2V)", "KIE_Kling26Motion_I2V": "KIE Kling 2.6 Motion-Control (I2V)", + "KIE_Flux2_I2I": "KIE Flux 2 (Image-to-Image)", "KIE_GridSlice": "KIE Grid Slice", "KIEParsePromptGridJSON": "KIE Parse Prompt Grid JSON (1..9)", } diff --git a/web/docs/KIE_Flux2_I2I.md b/web/docs/KIE_Flux2_I2I.md new file mode 100644 index 0000000..834c406 --- /dev/null +++ b/web/docs/KIE_Flux2_I2I.md @@ -0,0 +1,21 @@ +# KIE Flux 2 (Image-to-Image) + +Generate an image from one or more input images using Flux 2 Pro or Flux 2 Flex. + +## Inputs +- **images** (IMAGE, required): Input image batch. 1–8 images supported; all are uploaded. +- **prompt** (STRING, required): Text prompt (3–5000 characters). +- **model** (COMBO, required): + - `flux-2/pro-image-to-image` + - `flux-2/flex-image-to-image` +- **aspect_ratio** (COMBO, required): `1:1`, `4:3`, `3:4`, `16:9`, `9:16`, `3:2`, `2:3`, `auto` +- **resolution** (COMBO, required): `1K`, `2K` +- **log** (BOOLEAN, optional): Enable console logging. + +## Outputs +- **IMAGE**: ComfyUI image tensor (BHWC, float32, 0–1) + +## Helper behavior +- Validates prompt length and image tensor shape. +- Uploads up to 8 images and passes their URLs in `input_urls`. +- Polls the task until completion, then downloads and decodes the output image.