Spaces:
Running
Running
Switch to Space node calling ideogram-ai/ideogram4 /generate
Browse filesA kind:'space' node authenticates with each visitor's HF OAuth token
(gradio@6.19.0 call_space passes the resolved token to gradio_client),
so no server-side key is needed and the fal bound-function workaround
is unnecessary. The /generate endpoint already exposes all controls
(prompt, mode, upsampler, width, height, seed, randomize_seed) and
returns (image, seed, caption), mapped to input/output ports by order.
Co-Authored-By: Claude <noreply@anthropic.com>
- README.md +11 -8
- app.py +12 -81
- workflow.json +85 -44
README.md
CHANGED
|
@@ -17,13 +17,16 @@ hf_oauth_scopes:
|
|
| 17 |
|
| 18 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 19 |
|
| 20 |
-
##
|
| 21 |
|
| 22 |
-
This
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
If neither is set the generator node will fail with an auth error at run time.
|
|
|
|
| 17 |
|
| 18 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 19 |
|
| 20 |
+
## How it works
|
| 21 |
|
| 22 |
+
This is a Gradio **Workflow** app. The canvas calls the official
|
| 23 |
+
[Ideogram 4 Space](https://huggingface.co/spaces/ideogram-ai/ideogram4)
|
| 24 |
+
(`ideogram-ai/ideogram4`, endpoint `/generate`) as a node, exposing all of its
|
| 25 |
+
inputs — `prompt`, `mode` (speed ↔ quality), `upsampler`, `width`, `height`,
|
| 26 |
+
`seed`, `randomize_seed` — as ports you can wire and tweak on the canvas. The
|
| 27 |
+
node returns the image, the seed used, and the (upsampled) caption.
|
| 28 |
|
| 29 |
+
No server-side secrets are required: each visitor authenticates with their own
|
| 30 |
+
Hugging Face OAuth token (granted via `hf_oauth` below), so they run the
|
| 31 |
+
pipeline on their own access. The Space owner gets write access to edit and save
|
| 32 |
+
the workflow; visitors get a read-only canvas they can still run.
|
|
|
app.py
CHANGED
|
@@ -1,84 +1,15 @@
|
|
| 1 |
-
import os
|
| 2 |
-
from typing import Optional
|
| 3 |
-
|
| 4 |
import gradio as gr
|
| 5 |
-
from huggingface_hub import InferenceClient
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
# Fal's Ideogram 4 endpoint exposes more controls than the stock text-to-image
|
| 9 |
-
# call. gradio's built-in `kind: "model"` node only forwards the prompt to
|
| 10 |
-
# `InferenceClient.text_to_image(prompt)` (verified in gradio@6.19.0, where the
|
| 11 |
-
# `text-to-image` branch is literally `client.text_to_image(a0)` — no
|
| 12 |
-
# `extra_body`), so fal-specific fields would be silently dropped. We therefore
|
| 13 |
-
# bind this Python function as a node and forward the extra controls ourselves
|
| 14 |
-
# via `extra_body`, which is exactly how huggingface_hub passes provider-specific
|
| 15 |
-
# parameters to fal.
|
| 16 |
-
def ideogram_v4(
|
| 17 |
-
prompt: str,
|
| 18 |
-
rendering_speed: str = "BALANCED",
|
| 19 |
-
image_size: str = "square_hd",
|
| 20 |
-
expansion_model: str = "Medium",
|
| 21 |
-
acceleration: str = "none",
|
| 22 |
-
num_images: int = 1,
|
| 23 |
-
seed: Optional[int] = None,
|
| 24 |
-
output_format: str = "jpeg",
|
| 25 |
-
enable_safety_checker: bool = True,
|
| 26 |
-
):
|
| 27 |
-
"""Generate image(s) with Ideogram 4 via the fal inference provider.
|
| 28 |
-
|
| 29 |
-
Drag this node onto the canvas and wire the `prompt` port from a text
|
| 30 |
-
reference; tweak the other ports for control over quality, aspect ratio,
|
| 31 |
-
prompt expansion, count, seed and output format.
|
| 32 |
-
"""
|
| 33 |
-
client = InferenceClient(
|
| 34 |
-
provider="fal-ai",
|
| 35 |
-
# Prefer a fal key when present (fal's recommended auth), else fall back
|
| 36 |
-
# to the HF token (HF routes fal traffic through the visitor's HF
|
| 37 |
-
# inference entitlement on the Space).
|
| 38 |
-
token=os.environ.get("FAL_KEY") or os.environ.get("HF_TOKEN") or None,
|
| 39 |
-
)
|
| 40 |
-
|
| 41 |
-
# fal accepts image_size as either a preset string ("square_hd", "landscape_16_9",
|
| 42 |
-
# ...) or a {"width", "height"} object. We pass the string through unchanged
|
| 43 |
-
# unless the user gave "WxH", which we convert to the object form.
|
| 44 |
-
size_field = image_size
|
| 45 |
-
if image_size and "x" in image_size and image_size.replace("x", "").isdigit():
|
| 46 |
-
w, h = (int(x) for x in image_size.split("x"))
|
| 47 |
-
size_field = {"width": w, "height": h}
|
| 48 |
-
|
| 49 |
-
extra_body = {
|
| 50 |
-
"rendering_speed": rendering_speed,
|
| 51 |
-
"image_size": size_field,
|
| 52 |
-
"expansion_model": expansion_model,
|
| 53 |
-
"acceleration": acceleration,
|
| 54 |
-
"num_images": max(1, int(num_images or 1)),
|
| 55 |
-
"enable_safety_checker": bool(enable_safety_checker),
|
| 56 |
-
"output_format": output_format,
|
| 57 |
-
}
|
| 58 |
-
if seed is not None:
|
| 59 |
-
extra_body["seed"] = int(seed)
|
| 60 |
-
|
| 61 |
-
images = client.text_to_image(
|
| 62 |
-
prompt or "",
|
| 63 |
-
model="ideogram-ai/ideogram-4-fp8",
|
| 64 |
-
extra_body=extra_body,
|
| 65 |
-
)
|
| 66 |
-
|
| 67 |
-
# text_to_image returns a single PIL image; fal's `num_images` > 1 is honored
|
| 68 |
-
# by the provider, but the HF helper only surfaces one image per call, so we
|
| 69 |
-
# always return a one-element gallery list. Return as a list-of-image-dicts
|
| 70 |
-
# so the canvas `gallery` output port renders it correctly.
|
| 71 |
-
if isinstance(images, (list, tuple)):
|
| 72 |
-
items = list(images)
|
| 73 |
-
else:
|
| 74 |
-
items = [images]
|
| 75 |
-
return items
|
| 76 |
|
| 77 |
|
| 78 |
-
#
|
| 79 |
-
#
|
| 80 |
-
#
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
|
| 4 |
+
# This Workflow calls the official Ideogram 4 Space
|
| 5 |
+
# (https://huggingface.co/spaces/ideogram-ai/ideogram4) as a `kind: "space"`
|
| 6 |
+
# operator node. Gradio's Workflow runner authenticates each Space node with the
|
| 7 |
+
# visitor's own Hugging Face OAuth token (gradio@6.19.0 call_space passes the
|
| 8 |
+
# resolved token to `gradio_client.Client(...)`), so no server-side API key is
|
| 9 |
+
# required — visitors run the pipeline on their own inference entitlement.
|
| 10 |
+
#
|
| 11 |
+
# The /generate endpoint accepts 7 positional parameters (prompt, mode,
|
| 12 |
+
# upsampler, width, height, seed, randomize_seed) and returns a 3-tuple of
|
| 13 |
+
# (image, seed, caption). The node in workflow.json declares an input port per
|
| 14 |
+
# parameter and an output port per returned value, in that exact order.
|
| 15 |
+
gr.Workflow(graph="workflow.json").launch()
|
workflow.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"schema_version": "2",
|
| 3 |
-
"name": "Ideogram 4
|
| 4 |
"references": [
|
| 5 |
{
|
| 6 |
"id": "ref_prompt",
|
|
@@ -10,80 +10,121 @@
|
|
| 10 |
"inputs": [{ "id": "in", "label": "Text", "type": "text" }],
|
| 11 |
"outputs": [{ "id": "out", "label": "Text", "type": "text" }],
|
| 12 |
"x": 60,
|
| 13 |
-
"y":
|
| 14 |
-
"width":
|
| 15 |
"height": 124,
|
| 16 |
-
"data": { "value": "a
|
| 17 |
}
|
| 18 |
],
|
| 19 |
"operators": [
|
| 20 |
{
|
| 21 |
-
"id": "
|
| 22 |
-
"
|
| 23 |
-
"
|
| 24 |
-
"kind": "
|
| 25 |
-
"
|
| 26 |
-
"
|
| 27 |
-
"y": 80,
|
| 28 |
-
"width": 280,
|
| 29 |
-
"height": 460,
|
| 30 |
"inputs": [
|
| 31 |
{ "id": "in_prompt", "label": "prompt", "type": "text", "required": true },
|
| 32 |
-
{ "id": "
|
| 33 |
-
{ "id": "
|
| 34 |
-
{ "id": "
|
| 35 |
-
{ "id": "
|
| 36 |
-
{ "id": "in_num_images", "label": "num_images", "type": "number" },
|
| 37 |
{ "id": "in_seed", "label": "seed", "type": "number" },
|
| 38 |
-
{ "id": "
|
| 39 |
-
{ "id": "in_enable_safety_checker", "label": "enable_safety_checker", "type": "boolean" }
|
| 40 |
],
|
| 41 |
"outputs": [
|
| 42 |
-
{ "id": "
|
|
|
|
|
|
|
| 43 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
"data": {
|
| 45 |
-
"
|
| 46 |
-
"
|
| 47 |
-
"
|
| 48 |
-
"
|
| 49 |
-
"
|
| 50 |
-
"
|
| 51 |
-
"in_output_format": "jpeg",
|
| 52 |
-
"in_enable_safety_checker": true
|
| 53 |
}
|
| 54 |
}
|
| 55 |
],
|
| 56 |
"subjects": [
|
| 57 |
{
|
| 58 |
-
"id": "
|
| 59 |
-
"label": "Output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
"role": "subject",
|
| 61 |
-
"asset_type": "
|
| 62 |
-
"inputs": [{ "id": "in", "label": "
|
| 63 |
-
"outputs": [{ "id": "out", "label": "
|
| 64 |
-
"x":
|
| 65 |
-
"y":
|
| 66 |
"width": 240,
|
| 67 |
-
"height":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
"data": {}
|
| 69 |
}
|
| 70 |
],
|
| 71 |
"edges": [
|
| 72 |
{
|
| 73 |
-
"id": "
|
| 74 |
"from_node_id": "ref_prompt",
|
| 75 |
"from_port_id": "out",
|
| 76 |
-
"to_node_id": "
|
| 77 |
"to_port_id": "in_prompt",
|
| 78 |
"type": "text"
|
| 79 |
},
|
| 80 |
{
|
| 81 |
-
"id": "
|
| 82 |
-
"from_node_id": "
|
| 83 |
-
"from_port_id": "
|
| 84 |
-
"to_node_id": "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
"to_port_id": "in",
|
| 86 |
-
"type": "
|
| 87 |
}
|
| 88 |
]
|
| 89 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"schema_version": "2",
|
| 3 |
+
"name": "Ideogram 4 Pipeline",
|
| 4 |
"references": [
|
| 5 |
{
|
| 6 |
"id": "ref_prompt",
|
|
|
|
| 10 |
"inputs": [{ "id": "in", "label": "Text", "type": "text" }],
|
| 11 |
"outputs": [{ "id": "out", "label": "Text", "type": "text" }],
|
| 12 |
"x": 60,
|
| 13 |
+
"y": 180,
|
| 14 |
+
"width": 240,
|
| 15 |
"height": 124,
|
| 16 |
+
"data": { "value": "a ginger cat wearing a tiny wizard hat reading a spellbook" }
|
| 17 |
}
|
| 18 |
],
|
| 19 |
"operators": [
|
| 20 |
{
|
| 21 |
+
"id": "op_ideogram4",
|
| 22 |
+
"label": "Ideogram 4",
|
| 23 |
+
"role": "operator",
|
| 24 |
+
"kind": "space",
|
| 25 |
+
"space_id": "ideogram-ai/ideogram4",
|
| 26 |
+
"endpoint": "/generate",
|
|
|
|
|
|
|
|
|
|
| 27 |
"inputs": [
|
| 28 |
{ "id": "in_prompt", "label": "prompt", "type": "text", "required": true },
|
| 29 |
+
{ "id": "in_mode", "label": "mode", "type": "text" },
|
| 30 |
+
{ "id": "in_upsampler", "label": "upsampler", "type": "text" },
|
| 31 |
+
{ "id": "in_width", "label": "width", "type": "number" },
|
| 32 |
+
{ "id": "in_height", "label": "height", "type": "number" },
|
|
|
|
| 33 |
{ "id": "in_seed", "label": "seed", "type": "number" },
|
| 34 |
+
{ "id": "in_randomize_seed", "label": "randomize_seed", "type": "boolean" }
|
|
|
|
| 35 |
],
|
| 36 |
"outputs": [
|
| 37 |
+
{ "id": "out_image", "label": "Image", "type": "image", "output_index": 0 },
|
| 38 |
+
{ "id": "out_seed", "label": "Seed", "type": "number", "output_index": 1 },
|
| 39 |
+
{ "id": "out_caption", "label": "Caption", "type": "json", "output_index": 2 }
|
| 40 |
],
|
| 41 |
+
"x": 380,
|
| 42 |
+
"y": 80,
|
| 43 |
+
"width": 280,
|
| 44 |
+
"height": 460,
|
| 45 |
"data": {
|
| 46 |
+
"in_mode": "Default · 20 steps",
|
| 47 |
+
"in_upsampler": "Ideogram (remote)",
|
| 48 |
+
"in_width": 1024,
|
| 49 |
+
"in_height": 1024,
|
| 50 |
+
"in_seed": 0,
|
| 51 |
+
"in_randomize_seed": true
|
|
|
|
|
|
|
| 52 |
}
|
| 53 |
}
|
| 54 |
],
|
| 55 |
"subjects": [
|
| 56 |
{
|
| 57 |
+
"id": "sub_image",
|
| 58 |
+
"label": "Output Image",
|
| 59 |
+
"role": "subject",
|
| 60 |
+
"asset_type": "image",
|
| 61 |
+
"inputs": [{ "id": "in", "label": "Image", "type": "image" }],
|
| 62 |
+
"outputs": [{ "id": "out", "label": "Image", "type": "image" }],
|
| 63 |
+
"x": 740,
|
| 64 |
+
"y": 60,
|
| 65 |
+
"width": 240,
|
| 66 |
+
"height": 107,
|
| 67 |
+
"data": {}
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"id": "sub_seed",
|
| 71 |
+
"label": "Seed",
|
| 72 |
"role": "subject",
|
| 73 |
+
"asset_type": "number",
|
| 74 |
+
"inputs": [{ "id": "in", "label": "Number", "type": "number" }],
|
| 75 |
+
"outputs": [{ "id": "out", "label": "Number", "type": "number" }],
|
| 76 |
+
"x": 740,
|
| 77 |
+
"y": 200,
|
| 78 |
"width": 240,
|
| 79 |
+
"height": 107,
|
| 80 |
+
"data": {}
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"id": "sub_caption",
|
| 84 |
+
"label": "Caption",
|
| 85 |
+
"role": "subject",
|
| 86 |
+
"asset_type": "json",
|
| 87 |
+
"inputs": [{ "id": "in", "label": "JSON", "type": "json" }],
|
| 88 |
+
"outputs": [{ "id": "out", "label": "JSON", "type": "json" }],
|
| 89 |
+
"x": 740,
|
| 90 |
+
"y": 340,
|
| 91 |
+
"width": 240,
|
| 92 |
+
"height": 107,
|
| 93 |
"data": {}
|
| 94 |
}
|
| 95 |
],
|
| 96 |
"edges": [
|
| 97 |
{
|
| 98 |
+
"id": "e_prompt",
|
| 99 |
"from_node_id": "ref_prompt",
|
| 100 |
"from_port_id": "out",
|
| 101 |
+
"to_node_id": "op_ideogram4",
|
| 102 |
"to_port_id": "in_prompt",
|
| 103 |
"type": "text"
|
| 104 |
},
|
| 105 |
{
|
| 106 |
+
"id": "e_image",
|
| 107 |
+
"from_node_id": "op_ideogram4",
|
| 108 |
+
"from_port_id": "out_image",
|
| 109 |
+
"to_node_id": "sub_image",
|
| 110 |
+
"to_port_id": "in",
|
| 111 |
+
"type": "image"
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"id": "e_seed",
|
| 115 |
+
"from_node_id": "op_ideogram4",
|
| 116 |
+
"from_port_id": "out_seed",
|
| 117 |
+
"to_node_id": "sub_seed",
|
| 118 |
+
"to_port_id": "in",
|
| 119 |
+
"type": "number"
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"id": "e_caption",
|
| 123 |
+
"from_node_id": "op_ideogram4",
|
| 124 |
+
"from_port_id": "out_caption",
|
| 125 |
+
"to_node_id": "sub_caption",
|
| 126 |
"to_port_id": "in",
|
| 127 |
+
"type": "json"
|
| 128 |
}
|
| 129 |
]
|
| 130 |
}
|