akhaliq HF Staff Claude commited on
Commit
891e4b6
·
1 Parent(s): 2f7efaf

Switch to Space node calling ideogram-ai/ideogram4 /generate

Browse files

A 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>

Files changed (3) hide show
  1. README.md +11 -8
  2. app.py +12 -81
  3. 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
- ## Secrets
21
 
22
- This Space calls Ideogram 4 through the **fal** inference provider, which
23
- exposes more controls than the stock text-to-image call. Set at least one of:
 
 
 
 
24
 
25
- - `FAL_KEY` a fal API key (fal's recommended auth). Get one at https://fal.com/dashboard/keys
26
- - `HF_TOKEN` — a Hugging Face token with Inference access (used as a fallback
27
- and to let the Workflow canvas authenticate each visitor via OAuth).
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
- # Load the pre-wired topology (Prompt -> Ideogram 4 (fal) -> Output Gallery)
79
- # from workflow.json and expose the generator as a callable node on the canvas
80
- # so it can also be re-added / rewired after edits.
81
- gr.Workflow(
82
- graph="workflow.json",
83
- bind={"Ideogram 4 (fal)": ideogram_v4},
84
- ).launch()
 
 
 
 
 
 
 
 
 
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 (fal)",
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": 160,
14
- "width": 220,
15
  "height": 124,
16
- "data": { "value": "a serene mountain lake at sunrise, cinematic, highly detailed" }
17
  }
18
  ],
19
  "operators": [
20
  {
21
- "id": "fn_Ideogram 4 (fal)",
22
- "source": "fn",
23
- "fn": "Ideogram 4 (fal)",
24
- "kind": "transform",
25
- "label": "Ideogram 4 (fal)",
26
- "x": 360,
27
- "y": 80,
28
- "width": 280,
29
- "height": 460,
30
  "inputs": [
31
  { "id": "in_prompt", "label": "prompt", "type": "text", "required": true },
32
- { "id": "in_rendering_speed", "label": "rendering_speed", "type": "text" },
33
- { "id": "in_image_size", "label": "image_size", "type": "text" },
34
- { "id": "in_expansion_model", "label": "expansion_model", "type": "text" },
35
- { "id": "in_acceleration", "label": "acceleration", "type": "text" },
36
- { "id": "in_num_images", "label": "num_images", "type": "number" },
37
  { "id": "in_seed", "label": "seed", "type": "number" },
38
- { "id": "in_output_format", "label": "output_format", "type": "text" },
39
- { "id": "in_enable_safety_checker", "label": "enable_safety_checker", "type": "boolean" }
40
  ],
41
  "outputs": [
42
- { "id": "out_0", "label": "gallery", "type": "gallery" }
 
 
43
  ],
 
 
 
 
44
  "data": {
45
- "in_rendering_speed": "BALANCED",
46
- "in_image_size": "square_hd",
47
- "in_expansion_model": "Medium",
48
- "in_acceleration": "none",
49
- "in_num_images": 1,
50
- "in_seed": null,
51
- "in_output_format": "jpeg",
52
- "in_enable_safety_checker": true
53
  }
54
  }
55
  ],
56
  "subjects": [
57
  {
58
- "id": "sub_gallery",
59
- "label": "Output Gallery",
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  "role": "subject",
61
- "asset_type": "gallery",
62
- "inputs": [{ "id": "in", "label": "Gallery", "type": "gallery" }],
63
- "outputs": [{ "id": "out", "label": "Gallery", "type": "gallery" }],
64
- "x": 720,
65
- "y": 160,
66
  "width": 240,
67
- "height": 140,
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  "data": {}
69
  }
70
  ],
71
  "edges": [
72
  {
73
- "id": "e_prompt_to_ideogram",
74
  "from_node_id": "ref_prompt",
75
  "from_port_id": "out",
76
- "to_node_id": "fn_Ideogram 4 (fal)",
77
  "to_port_id": "in_prompt",
78
  "type": "text"
79
  },
80
  {
81
- "id": "e_ideogram_to_gallery",
82
- "from_node_id": "fn_Ideogram 4 (fal)",
83
- "from_port_id": "out_0",
84
- "to_node_id": "sub_gallery",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  "to_port_id": "in",
86
- "type": "gallery"
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
  }