feat: add flux 2 i2i node
This commit is contained in:
@@ -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).
|
||||
|
||||
@@ -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?
|
||||
@@ -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`)
|
||||
@@ -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`
|
||||
@@ -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.
|
||||
@@ -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/<model>.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/<NodeName>.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.
|
||||
@@ -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
|
||||
@@ -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)",
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user