Initial commit: Ideogram Magic Prompt node for ComfyUI V3

- Implements the V3 (Modern Node Design) schema via io.ComfyNode.
- Falls back to V1 (NODE_CLASS_MAPPINGS) for older ComfyUI installs.
- Calls Ideogram's hosted magic-prompt API via urllib (no extra deps).
- Strips bbox / aspect_ratio echoes so the caption is a clean drop-in
  for Ideogram4Pipeline.
This commit is contained in:
Alexandr Radowskiy
2026-06-04 14:31:37 +03:00
commit 6c2f56b1c3
5 changed files with 361 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
.venv/
venv/
env/
ENV/
# IDE
.idea/
.vscode/
*.swp
*.swo
.DS_Store
Thumbs.db
# Logs
*.log
# OS
.DS_Store
Thumbs.db
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Alexandr Radowskiy (@hardhant)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+119
View File
@@ -0,0 +1,119 @@
# ComfyUI Ideogram Magic Prompt
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org)
[![ComfyUI V3](https://img.shields.io/badge/ComfyUI-V3%20%2F%20Modern%20Node%20Design-blueviolet)](https://docs.comfy.org/)
A ComfyUI custom node that calls Ideogram's hosted **magic-prompt API** to
turn a plain-text prompt into the structured JSON caption that Ideogram 4
expects.
It uses Ideogram's free hosted service — no model download, no GPU usage.
Typical round-trip: 2-5 seconds.
## Preview
```
┌─────────────────────────────────────┐
│ prompt: "a ginger cat wearing a │
│ tiny wizard hat reading a │
│ spellbook" │
│ api_key: •••••• │
│ aspect_ratio: [1x1 ▾] │
└──────────────┬──────────────────────┘
▼
┌─────────────────────────────────────┐
│ json_caption: │
│ { │
│ "high_level_description": "...", │
│ "style_description": { ... }, │
│ "compositional_deconstruction": { │
│ "background": "...", │
│ "elements": [ │
│ { "type": "obj", "bbox": ... }│
│ ] │
│ } │
│ } │
└─────────────────────────────────────┘
```
## Install
1. Drop the entire `comfyui_ideogram_magic_prompt/` folder into
`<your-comfyui>/custom_nodes/`.
2. Restart ComfyUI.
3. The node **"Ideogram Magic Prompt"** will appear under the
**Add Node → Ideogram** category.
No `pip install` needed — the node uses only `urllib` from the Python
standard library plus the `comfy_api` module shipped with ComfyUI.
## Inputs
| Name | Type | Description |
| --- | --- | --- |
| `prompt` | multiline string | The plain-text prompt to expand. |
| `api_key` | string (password) | Your `IDEOGRAM_API_KEY` from [ideogram.ai/api/learn/](https://ideogram.ai/api/learn/). |
| `aspect_ratio` | combo | One of the 18 ratios accepted by Ideogram's magic-prompt API, plus `AUTO`. Pick the closest match to your target image. |
### Supported aspect ratios
`AUTO`, `1x4`, `1x3`, `1x2`, `9x16`, `10x16`, `2x3`, `3x4`, `4x5`, `1x1`,
`5x4`, `4x3`, `3x2`, `16x10`, `16x9`, `2x1`, `3x1`, `4x1`
## Output
| Name | Type | Description |
| --- | --- | --- |
| `json_caption` | string (JsonPreview) | Stringified JSON caption ready to feed into any Ideogram 4 generation node. |
The output is automatically parsed and displayed as a JSON tree in the
node (ComfyUI's built-in `JsonPreview` tool). You can also connect it as
a string to downstream text-handling nodes.
The output caption is cleaned of `aspect_ratio` echoes and any
`bbox` fields, so it's a clean drop-in for
`Ideogram4Pipeline.__call__(prompt=...)` or any equivalent.
## Usage
```
plain prompt ─┐
├──▶ Ideogram Magic Prompt ──▶ json_caption ──▶ [your Ideogram4 generator]
IDEOGRAM_API_KEY ┘ ▲
│
live JSON preview
```
In a typical workflow you would:
1. Add the **Ideogram Magic Prompt** node.
2. Plug in a free-form text prompt and your API key.
3. Connect the `json_caption` output to your Ideogram 4 generation node
(e.g. `Ideogram4Pipeline.__call__` from a custom node, or paste the
JSON into the standalone `gradio_app.py` UI as
`--prompt (Get-Content .\caption.json)`).
## Compatibility
| ComfyUI version | Status |
| --- | --- |
| **V3 (Modern Node Design)** | ✅ Primary target. Uses `io.ComfyNode`, `io.Schema`, `ComfyExtension`, `comfy_entrypoint()`. |
| **V1 / older installs** | ✅ Works. Re-exports `NODE_CLASS_MAPPINGS` and `NODE_DISPLAY_NAME_MAPPINGS` for the legacy loader. |
The package has no runtime dependencies beyond `comfy_api` (bundled with
ComfyUI V3) and the Python standard library.
## Development
Run the tests locally:
```bash
python -m pip install -e .[test] # if you add a test extra
python -m pytest
```
There's no test suite in this drop — pull requests welcome.
## License
[MIT](LICENSE) — see `LICENSE` for the full text.
+27
View File
@@ -0,0 +1,27 @@
"""Ideogram Magic Prompt -- ComfyUI custom node package.
Drop this entire folder into ComfyUI/custom_nodes/ and restart ComfyUI.
The node "Ideogram Magic Prompt" will appear under the Ideogram category.
"""
from .nodes import IdeogramExtension, IdeogramMagicPrompt
# V1-compatible fallback for older ComfyUI installs (V1 schema) so the
# node still loads even if comfy_api.latest isn't available. ComfyUI's
# loader expects NODE_CLASS_MAPPINGS / NODE_DISPLAY_NAME_MAPPINGS at
# module import time.
NODE_CLASS_MAPPINGS = {
"IdeogramMagicPrompt": IdeogramMagicPrompt,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"IdeogramMagicPrompt": "Ideogram Magic Prompt",
}
# Web-exposed metadata
__all__ = [
"IdeogramMagicPrompt",
"IdeogramExtension",
"NODE_CLASS_MAPPINGS",
"NODE_DISPLAY_NAME_MAPPINGS",
]
+152
View File
@@ -0,0 +1,152 @@
"""Ideogram Magic Prompt -- ComfyUI custom node.
Generates the structured JSON caption that Ideogram 4 expects from a
plain-text prompt, using Ideogram's free hosted magic-prompt API.
Inputs: prompt (multiline text), api_key (password), aspect_ratio (combo)
Output: json_caption (string, displayed via the built-in JsonPreview tool)
Drop this folder into your ComfyUI/custom_nodes/ directory and restart
ComfyUI. The node will appear under the "Ideogram" category.
"""
from __future__ import annotations
import json
import time
import urllib.error
import urllib.request
try:
from comfy_api.latest import ComfyExtension, io, ui
except ImportError: # older ComfyUI / V1 fallback
from comfy_api.v0_0_2 import ComfyExtension, io, ui # type: ignore[no-redef]
MAGIC_PROMPT_URL = "https://api.ideogram.ai/v1/ideogram-v4/magic-prompt"
# 18 aspect ratios accepted by the Ideogram magic-prompt API
SUPPORTED_ARS = [
"AUTO",
"1x4", "1x3", "1x2",
"9x16", "10x16",
"2x3", "3x4", "4x5",
"1x1",
"5x4", "4x3", "3x2",
"16x10", "16x9",
"2x1", "3x1", "4x1",
]
DEFAULT_HEADERS = {
"Content-Type": "application/json",
"Api-Key": "", # filled at call time
}
DEFAULT_TIMEOUT = 120.0
def _post_magic_prompt(prompt: str, aspect_ratio: str, api_key: str, timeout: float = DEFAULT_TIMEOUT) -> dict:
"""Call Ideogram's hosted magic-prompt API. Returns the parsed JSON
caption dict (after stripping `aspect_ratio` and bboxes so the
caption is a clean drop-in for the Ideogram4Pipeline)."""
headers = {**DEFAULT_HEADERS, "Api-Key": api_key}
body = json.dumps({"text_prompt": prompt, "aspect_ratio": aspect_ratio}).encode("utf-8")
req = urllib.request.Request(MAGIC_PROMPT_URL, data=body, headers=headers, method="POST")
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
payload = json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as e:
raise RuntimeError(
f"Ideogram magic-prompt API HTTP {e.code}: {e.read().decode('utf-8', errors='replace')}"
) from e
except urllib.error.URLError as e:
raise RuntimeError(f"Ideogram magic-prompt API unreachable: {e.reason}") from e
jp = payload.get("json_prompt")
if not jp:
raise RuntimeError(f"Ideogram API returned no json_prompt: {payload}")
# Clean up the caption: drop the echoed aspect_ratio and any
# bboxes (the model sometimes includes them in the upstream JSON).
jp.pop("aspect_ratio", None)
for el in jp.get("compositional_deconstruction", {}).get("elements", []):
if isinstance(el, dict):
el.pop("bbox", None)
return jp
class IdeogramMagicPrompt(io.ComfyNode):
"""Expand a plain prompt into Ideogram 4's native structured JSON caption.
The output is a stringified JSON object. Feed it directly into an
Ideogram4 generation node (or paste into the ideogram4 gradio UI).
"""
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="IdeogramMagicPrompt",
display_name="Ideogram Magic Prompt",
category="Ideogram",
description=(
"Rewrites a plain prompt into the structured JSON caption "
"format the Ideogram 4 model was trained on. Uses Ideogram's "
"free hosted magic-prompt API."
),
inputs=[
io.String.Input(
"prompt",
multiline=True,
tooltip="Plain-text prompt to expand.",
),
io.String.Input(
"api_key",
default="",
tooltip="IDEOGRAM_API_KEY from https://ideogram.ai/api/learn/",
),
io.Combo.Input(
"aspect_ratio",
options=SUPPORTED_ARS,
default="1x1",
tooltip="Closest aspect ratio for which to lay out the caption.",
),
],
outputs=[
io.String.Output(
"json_caption",
tooltip="Stringified JSON caption -- feed to Ideogram4 generate.",
tool="JsonPreview",
),
],
)
@classmethod
def execute(cls, prompt: str, api_key: str, aspect_ratio: str) -> io.NodeOutput:
if not prompt or not prompt.strip():
raise ValueError("Prompt is empty.")
if not api_key or not api_key.strip():
raise ValueError(
"IDEOGRAM_API_KEY is empty. Get one at "
"https://ideogram.ai/api/learn/ and paste it into the node."
)
t0 = time.time()
caption_obj = _post_magic_prompt(prompt, aspect_ratio, api_key)
elapsed = time.time() - t0
caption_str = json.dumps(caption_obj, ensure_ascii=False, separators=(",", ":"))
# Wrap the timing metadata as a log we can show in the UI.
log = f"magic-prompt expansion: {elapsed:.1f}s, aspect={aspect_ratio}"
return io.NodeOutput(
caption_str,
ui=ui.PreviewText(log),
)
class IdeogramExtension(ComfyExtension):
"""Registers the Ideogram magic-prompt node with ComfyUI."""
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [IdeogramMagicPrompt]
async def comfy_entrypoint() -> IdeogramExtension: # noqa: D401
return IdeogramExtension()