multimodalart HF Staff commited on
Commit
3f356dc
·
verified ·
1 Parent(s): bc03619

Music3-style composer UI + optional MiniMax-H3-Image-PE prompt enhancement

Browse files
Files changed (1) hide show
  1. app.py +252 -75
app.py CHANGED
@@ -9,10 +9,12 @@ for _wheel in glob.glob(os.path.join(os.path.dirname(os.path.abspath(__file__)),
9
 
10
  import spaces # before torch
11
 
 
12
  import math
13
  import os
14
  import random
15
  import tempfile
 
16
  from functools import cache
17
 
18
  import gradio as gr
@@ -28,38 +30,10 @@ MAX_IMAGES = 8
28
  MAX_SEED = 2**31 - 1
29
  MULTIPLE = 32
30
 
31
- SIZES = {
32
- "4 MP": {
33
- "1:1": (2048, 2048),
34
- "4:3": (2368, 1760),
35
- "3:4": (1760, 2368),
36
- "3:2": (2496, 1664),
37
- "2:3": (1664, 2496),
38
- "16:9": (2720, 1536),
39
- "9:16": (1536, 2720),
40
- },
41
- "8 MP": {
42
- "1:1": (2912, 2912),
43
- "4:3": (3360, 2496),
44
- "3:4": (2496, 3360),
45
- "3:2": (3552, 2368),
46
- "2:3": (2368, 3552),
47
- "16:9": (3872, 2176),
48
- "9:16": (2176, 3872),
49
- },
50
- "16 MP": {
51
- "1:1": (4096, 4096),
52
- "4:3": (4736, 3552),
53
- "3:4": (3552, 4736),
54
- "3:2": (5024, 3360),
55
- "2:3": (3360, 5024),
56
- "16:9": (5472, 3072),
57
- "9:16": (3072, 5472),
58
- },
59
- }
60
  MEGAPIXELS = {"4 MP": 4, "8 MP": 8, "16 MP": 16}
61
- MATCH = "Match Picture 1"
62
- ASPECTS = [MATCH] + list(SIZES["4 MP"])
63
 
64
  # Everything but the Qwen3-VL text encoder, which runs on ENCODER_SPACE: `prompt_embeds` + `text_token_tags` become
65
  # inputs of the pipeline.
@@ -90,12 +64,34 @@ def size_at_area(width, height, pixels):
90
 
91
 
92
  def resolve_size(paths, aspect, resolution):
93
- if aspect == MATCH:
94
- if not paths:
95
- return SIZES[resolution]["1:1"]
96
- width, height = Image.open(paths[0]).size
97
- return size_at_area(width, height, MEGAPIXELS[resolution] * 1024 * 1024)
98
- return SIZES[resolution][aspect]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
 
101
  def prepare(prompt, images, aspect, resolution, seed, randomize):
@@ -143,51 +139,232 @@ def _generate(embeds_path, images, width, height, seed):
143
  return path
144
 
145
 
146
- def generate(prompt, images, aspect, resolution, seed, randomize, progress=gr.Progress(track_tqdm=True)):
 
147
  """Text-to-image, or an edit of up to 8 images referred to as `<Picture 1>`, `<Picture 2>`, ... in the prompt.
148
 
149
- Returns the image path, the seed used, and the size.
 
150
  """
151
- progress(0, desc="Encoding the prompt ...")
152
- embeds_path, seed, width, height = prepare(prompt, images, aspect, resolution, seed, randomize)
153
- progress(0.05, desc=f"Generating {width}x{height} ...")
154
- return _generate(embeds_path, images, width, height, seed), seed
155
-
156
-
157
- EXAMPLES = [
158
- "A German Expressionist oil painting of a Berlin street at night in the manner of Ernst Ludwig Kirchner, with gaunt figures hurrying past a glowing gas lamp.",
159
- "A two-color risograph print of a fox reading a newspaper on a park bench, the headline reads \"FOX NEWS\", no other text.",
160
- "A 1970s film frame of a woman waiting at a rainy bus stop in Tokyo, lit by a vending machine.",
161
- "A photorealistic overhead food photograph of a rustic farm-to-table feast on a weathered dark wooden table: a scored sourdough loaf on a jute placemat, a copper bowl of seasonal vegetables, a cheese board, heirloom tomatoes, and a glass of red wine beside a lit taper candle, warm sunlight from the left.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  ]
163
 
164
- with gr.Blocks(title="MiniMax-H3-Image") as demo:
165
- gr.Markdown(
166
- "# MiniMax-H3-Image\n"
167
- "Text-to-image and editing with up to 8 images, in 6 steps, through the diffusers modular pipeline. "
168
- "Add images to edit: refer to them in the prompt as `<Picture 1>`, `<Picture 2>`, …"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  )
170
- with gr.Row():
171
- with gr.Column():
172
- prompt = gr.Textbox(label="Prompt", lines=4, placeholder="Describe the image, or the edit")
173
- images = gr.Gallery(label="Images to edit (optional, up to 8)", type="filepath", columns=4, height=220)
174
- with gr.Row():
175
- aspect = gr.Dropdown(ASPECTS, value="1:1", label="Aspect ratio")
176
- resolution = gr.Radio(list(SIZES), value="4 MP", label="Resolution")
177
- with gr.Row():
178
- seed = gr.Number(value=0, label="Seed", precision=0)
179
- randomize = gr.Checkbox(value=True, label="Randomize seed")
180
- button = gr.Button("Generate", variant="primary")
181
- with gr.Column():
182
- output = gr.Image(label="Result", type="filepath", format="png")
183
- gr.Examples(EXAMPLES, inputs=prompt)
184
-
185
- images.change(lambda imgs: gr.update(value=MATCH) if imgs else gr.update(value="1:1"), images, aspect)
186
- gr.on(
187
- [button.click, prompt.submit],
188
  generate,
189
- [prompt, images, aspect, resolution, seed, randomize],
190
- [output, seed],
191
  api_name="generate",
192
  )
193
 
 
9
 
10
  import spaces # before torch
11
 
12
+ import json
13
  import math
14
  import os
15
  import random
16
  import tempfile
17
+ import time
18
  from functools import cache
19
 
20
  import gradio as gr
 
30
  MAX_SEED = 2**31 - 1
31
  MULTIPLE = 32
32
 
33
+ RATIOS = ["1:1", "5:4", "4:3", "3:2", "16:9", "21:9", "4:5", "3:4", "2:3", "9:16"] # MiniMax-H3-Image-PE's set
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  MEGAPIXELS = {"4 MP": 4, "8 MP": 8, "16 MP": 16}
35
+ AUTO = "auto" # PE's recommendation for text-to-image, Picture 1's ratio for edits, else 1:1
36
+ PE_SPACE = os.environ.get("H3_IMAGE_PE", "multimodalart/minimax-h3-image-pe")
37
 
38
  # Everything but the Qwen3-VL text encoder, which runs on ENCODER_SPACE: `prompt_embeds` + `text_token_tags` become
39
  # inputs of the pipeline.
 
64
 
65
 
66
  def resolve_size(paths, aspect, resolution):
67
+ pixels = MEGAPIXELS.get(resolution, 4) * 1024 * 1024
68
+ if aspect not in RATIOS:
69
+ if paths:
70
+ width, height = Image.open(paths[0]).size
71
+ return size_at_area(width, height, pixels)
72
+ aspect = "1:1"
73
+ rw, rh = (int(v) for v in aspect.split(":"))
74
+ return size_at_area(rw, rh, pixels)
75
+
76
+
77
+ @cache
78
+ def pe_client():
79
+ from gradio_client import Client
80
+
81
+ return Client(PE_SPACE, token=os.environ.get("HF_TOKEN"), httpx_kwargs={"timeout": 900})
82
+
83
+
84
+ def enhance_stream(prompt, effort, seed):
85
+ """Streams MiniMax-H3-Image-PE: yields (thinking, None, None) while it reasons, then (thinking, caption, ratio)."""
86
+ job = pe_client().submit(prompt, effort, int(seed) % (2**31), api_name="/expand")
87
+ shown = 0
88
+ for update in job:
89
+ thinking = update[2] or ""
90
+ if len(thinking) - shown > 200:
91
+ shown = len(thinking)
92
+ yield thinking, None, None
93
+ caption, ratio, thinking, _ = job.result()
94
+ yield thinking, caption, ratio
95
 
96
 
97
  def prepare(prompt, images, aspect, resolution, seed, randomize):
 
139
  return path
140
 
141
 
142
+ def generate(prompt, images=None, aspect=AUTO, resolution="4 MP", seed=0, randomize=True, enhance=False,
143
+ reasoning_effort="low", progress=gr.Progress(track_tqdm=True)):
144
  """Text-to-image, or an edit of up to 8 images referred to as `<Picture 1>`, `<Picture 2>`, ... in the prompt.
145
 
146
+ `aspect`: one of 1:1, 5:4, 4:3, 3:2, 16:9, 21:9, 4:5, 3:4, 2:3, 9:16, or "auto". `enhance`: expand the prompt with
147
+ MiniMax-H3-Image-PE first (text-to-image only). Returns (image, seed, prompt used).
148
  """
149
+ if not prompt or not prompt.strip():
150
+ raise gr.Error("Enter a prompt.")
151
+ final = prompt.strip()
152
+ if enhance and not image_paths(images):
153
+ progress(0, desc="Enhancing the prompt ...")
154
+ *_, (thinking, caption, ratio) = enhance_stream(final, reasoning_effort, seed)
155
+ final = caption or final
156
+ if aspect == AUTO and ratio:
157
+ aspect = ratio
158
+ progress(0.05, desc="Encoding the prompt ...")
159
+ embeds_path, seed, width, height = prepare(final, images, aspect, resolution, seed, randomize)
160
+ progress(0.1, desc=f"Generating {width}x{height} ...")
161
+ return _generate(embeds_path, images, width, height, seed), seed, final
162
+
163
+
164
+ def studio(state, images, seed, randomize, effort):
165
+ """The composer's Generate: optional PE (streamed), then the image."""
166
+ state = state or {}
167
+ prompt = (state.get("prompt") or "").strip()
168
+ aspect = state.get("aspect") or AUTO
169
+ resolution = state.get("resolution") or "4 MP"
170
+ if not prompt:
171
+ raise gr.Error("Describe the image first.")
172
+ paths = image_paths(images)
173
+ if randomize:
174
+ seed = random.randint(0, MAX_SEED)
175
+ final, thinking = prompt, ""
176
+ if state.get("enhance") and paths:
177
+ gr.Info("Prompt enhancement is text-to-image only for now; edits use your prompt as written.")
178
+ elif state.get("enhance"):
179
+ yield gr.update(), "✨ Enhancing the prompt with MiniMax-H3-Image-PE ...", gr.update(), "", seed
180
+ t0 = time.time()
181
+ caption = ratio = None
182
+ for thinking, caption, ratio in enhance_stream(prompt, effort, seed):
183
+ if caption is None:
184
+ yield gr.update(), f"✨ Enhancing the prompt · thinking ({len(thinking.split())} words) ...", gr.update(), thinking, seed
185
+ if caption:
186
+ final = caption
187
+ if aspect == AUTO and ratio:
188
+ aspect = ratio
189
+ yield gr.update(), f"✨ Prompt enhanced in {time.time() - t0:.0f}s · encoding ...", final, thinking, seed
190
+ width, height = resolve_size(paths, aspect, resolution)
191
+ yield gr.update(), f"Encoding the prompt · {width}×{height} ...", final, thinking, seed
192
+ embeds_path, seed, width, height = prepare(final, images, aspect, resolution, seed, False)
193
+ yield gr.update(), f"Generating {width}×{height} ...", final, thinking, seed
194
+ t0 = time.time()
195
+ path = _generate(embeds_path, images, width, height, seed)
196
+ yield path, f"{width}×{height} · seed {seed} · {time.time() - t0:.0f}s", final, thinking, seed
197
+
198
+
199
+ IDEAS = [
200
+ "a two-color risograph print of a fox reading a newspaper on a park bench, headline \"FOX NEWS\"",
201
+ "a 1970s film frame of a woman waiting at a rainy Tokyo bus stop, lit by a vending machine",
202
+ "isometric cutaway of a tiny underground bakery",
203
+ "a poster for a jazz festival in Lisbon",
204
  ]
205
 
206
+ COMPOSER_DEFAULTS = {"prompt": "", "aspect": AUTO, "resolution": "4 MP", "enhance": True}
207
+
208
+ COMPOSER_HTML = """
209
+ <div class="hx-card">
210
+ <div class="hx-head">
211
+ <div class="hx-seg" data-role="resolution" role="tablist">
212
+ <button type="button" class="hx-seg-btn" data-value="4 MP">4 MP</button>
213
+ <button type="button" class="hx-seg-btn" data-value="8 MP">8 MP</button>
214
+ <button type="button" class="hx-seg-btn" data-value="16 MP">16 MP</button>
215
+ </div>
216
+ <span class="hx-headhint">text-to-image &middot; or edit up to 8 pictures</span>
217
+ </div>
218
+ <textarea class="hx-desc" data-role="prompt" rows="4"
219
+ placeholder="Describe your image&#8230; e.g. a poster for a jazz festival in Lisbon. To edit, add pictures below and refer to them as <Picture 1>, <Picture 2>&#8230;"></textarea>
220
+ <div class="hx-chips" data-role="idea-chips"></div>
221
+ <div class="hx-row">
222
+ <span class="hx-label">Aspect</span>
223
+ <div class="hx-ratios" data-role="aspect"></div>
224
+ </div>
225
+ <div class="hx-foot">
226
+ <label class="hx-toggle" title="Expand the prompt into a detailed caption (and pick the ratio when Aspect is Auto) with MiniMax-H3-Image-PE">
227
+ <input type="checkbox" data-role="enhance">&#10024; Enhance prompt
228
+ </label>
229
+ <span class="hx-spacer"></span>
230
+ <button type="button" class="hx-primary" data-role="generate">Generate</button>
231
+ </div>
232
+ </div>
233
+ """
234
+
235
+ COMPOSER_CSS = """
236
+ .hx-card { background: var(--block-background-fill); border: var(--block-border-width, 1px) solid var(--block-border-color, var(--border-color-primary)); border-radius: var(--block-radius, 12px); box-shadow: var(--block-shadow, none); padding: 16px; display: flex; flex-direction: column; gap: 12px; }
237
+ .hx-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
238
+ .hx-seg { display: inline-flex; background: var(--background-fill-secondary); border: 1px solid var(--border-color-primary); border-radius: 999px; padding: 3px; gap: 2px; }
239
+ .hx-seg-btn { border: none; background: transparent; color: var(--body-text-color-subdued); padding: 5px 14px; border-radius: 999px; font-family: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer; transition: background .15s, color .15s; }
240
+ .hx-seg-btn[aria-selected="true"] { background: var(--button-primary-background-fill); color: var(--button-primary-text-color); }
241
+ .hx-headhint { color: var(--body-text-color-subdued); font-size: 12.5px; }
242
+ .hx-card textarea { width: 100%; box-sizing: border-box; background: var(--input-background-fill); border: var(--input-border-width, 1px) solid var(--input-border-color, var(--border-color-primary)); border-radius: var(--input-radius, 8px); padding: 10px 12px; color: var(--body-text-color); font-family: inherit; font-size: 16px; line-height: 1.5; min-height: 118px; resize: vertical; transition: border-color .15s; }
243
+ .hx-card textarea::placeholder { color: var(--input-placeholder-color, var(--body-text-color-subdued)); }
244
+ .hx-card textarea:focus { outline: none; border-color: var(--input-border-color-focus, var(--color-accent)); }
245
+ .hx-chips { display: flex; flex-wrap: wrap; gap: 6px; }
246
+ .hx-chip { background: var(--button-secondary-background-fill); color: var(--button-secondary-text-color); border: 1px solid var(--button-secondary-border-color, var(--border-color-primary)); border-radius: 999px; padding: 4px 12px; font-family: inherit; font-size: 12.5px; cursor: pointer; transition: border-color .15s; text-align: left; }
247
+ .hx-chip:hover { border-color: var(--color-accent); }
248
+ .hx-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
249
+ .hx-label { color: var(--block-title-text-color, var(--body-text-color)); font-size: 13px; font-weight: 600; }
250
+ .hx-ratios { display: flex; flex-wrap: wrap; gap: 4px; }
251
+ .hx-ratio { background: transparent; color: var(--body-text-color-subdued); border: 1px dashed var(--border-color-primary); border-radius: 6px; padding: 3px 9px; font-family: var(--font-mono, ui-monospace, monospace); font-size: 12px; cursor: pointer; transition: color .15s, border-color .15s, background .15s; }
252
+ .hx-ratio:hover { color: var(--color-accent); border-color: var(--color-accent); }
253
+ .hx-ratio[aria-selected="true"] { border-style: solid; border-color: var(--color-accent); color: var(--color-accent); background: var(--background-fill-secondary); }
254
+ .hx-foot { display: flex; align-items: center; gap: 10px; }
255
+ .hx-spacer { flex: 1; }
256
+ .hx-toggle { display: inline-flex; align-items: center; gap: 7px; color: var(--body-text-color); font-size: 14px; cursor: pointer; user-select: none; }
257
+ .hx-toggle input { width: 16px; height: 16px; accent-color: var(--color-accent); cursor: pointer; }
258
+ .hx-primary { background: var(--button-primary-background-fill); color: var(--button-primary-text-color); border: var(--button-border-width, 1px) solid var(--button-primary-border-color, transparent); border-radius: var(--button-large-radius, var(--radius-lg, 8px)); padding: 10px 28px; font-family: inherit; font-size: var(--button-large-text-size, 16px); font-weight: 600; cursor: pointer; box-shadow: var(--button-primary-shadow, none); transition: background .15s, transform .05s; }
259
+ .hx-primary:hover { background: var(--button-primary-background-fill-hover, var(--button-primary-background-fill)); }
260
+ .hx-primary:active { transform: translateY(1px); }
261
+ """
262
+
263
+ COMPOSER_JS = """
264
+ const $ = function(s) { return element.querySelector(s); };
265
+ const $$ = function(s) { return Array.from(element.querySelectorAll(s)); };
266
+ const IDEAS = __IDEAS__;
267
+ const RATIOS = ['auto'].concat(__RATIOS__);
268
+ const prompt = $('[data-role="prompt"]');
269
+ const enhance = $('[data-role="enhance"]');
270
+ let aspect = 'auto', resolution = '4 MP', lastPushed = '';
271
+
272
+ RATIOS.forEach(function(r) {
273
+ const b = document.createElement('button');
274
+ b.type = 'button'; b.className = 'hx-ratio'; b.dataset.value = r;
275
+ b.textContent = r === 'auto' ? 'Auto' : r;
276
+ if (r === 'auto') b.title = 'Enhanced: the ratio PE recommends. Editing: Picture 1\\'s ratio. Otherwise 1:1.';
277
+ b.addEventListener('click', function() { setAspect(r); });
278
+ $('[data-role="aspect"]').appendChild(b);
279
+ });
280
+ IDEAS.forEach(function(t) {
281
+ const b = document.createElement('button');
282
+ b.type = 'button'; b.className = 'hx-chip'; b.textContent = t;
283
+ b.addEventListener('click', function() { prompt.value = t; prompt.focus(); });
284
+ $('[data-role="idea-chips"]').appendChild(b);
285
+ });
286
+ function setAspect(r) { aspect = r; $$('.hx-ratio').forEach(function(b) { b.setAttribute('aria-selected', String(b.dataset.value === r)); }); }
287
+ function setResolution(r) { resolution = r; $$('.hx-seg-btn').forEach(function(b) { b.setAttribute('aria-selected', String(b.dataset.value === r)); }); }
288
+ $$('.hx-seg-btn').forEach(function(b) { b.addEventListener('click', function() { setResolution(b.dataset.value); }); });
289
+ function readState() { return { prompt: prompt.value, aspect: aspect, resolution: resolution, enhance: enhance.checked }; }
290
+ function gather() { const s = readState(); lastPushed = JSON.stringify(s); props.value = s; }
291
+ function applyState(v) {
292
+ if (!v) return;
293
+ if (prompt.value !== (v.prompt || '')) prompt.value = v.prompt || '';
294
+ enhance.checked = !!v.enhance;
295
+ setAspect(v.aspect || 'auto');
296
+ setResolution(v.resolution || '4 MP');
297
+ }
298
+ $('[data-role="generate"]').addEventListener('click', function() { gather(); trigger('submit'); });
299
+ prompt.addEventListener('keydown', function(e) { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); gather(); trigger('submit'); } });
300
+ watch('value', function() { if (JSON.stringify(props.value) !== lastPushed) applyState(props.value); });
301
+ applyState(props.value);
302
+ """.replace("__IDEAS__", json.dumps(IDEAS)).replace("__RATIOS__", json.dumps(RATIOS))
303
+
304
+ CSS = """
305
+ #col-container { max-width: 1300px; margin: 0 auto; }
306
+ .dark .gradio-container { color: var(--body-text-color); }
307
+ .html-container { padding: 0; }
308
+ .hx-title { text-align: center; margin: 14px 0 0; font-size: 34px; font-weight: 800; letter-spacing: -0.02em; }
309
+ .hx-title span { background: linear-gradient(90deg, var(--color-accent), var(--secondary-500, var(--color-accent))); -webkit-background-clip: text; background-clip: text; color: transparent; }
310
+ #hx-links { text-align: center; }
311
+ """
312
+
313
+ with gr.Blocks(theme=gr.themes.Citrus(), css=CSS, title="MiniMax H3 Image") as demo:
314
+ with gr.Column(elem_id="col-container"):
315
+ gr.HTML('<h1 class="hx-title">MiniMax <span>H3 Image</span></h1>', container=False, padding=False)
316
+ gr.Markdown(
317
+ "Text-to-image and editing of up to 8 pictures in 6 steps, with optional prompt enhancement by "
318
+ "MiniMax-H3-Image-PE "
319
+ "[[model]](https://huggingface.co/MiniMaxAI/MiniMax-H3-Image) | "
320
+ "[[prompt enhancer]](https://huggingface.co/MiniMaxAI/MiniMax-H3-Image-PE) | "
321
+ "[[diffusers PR]](https://github.com/huggingface/diffusers-new-model-addition-minimax/pull/1)",
322
+ elem_id="hx-links",
323
+ )
324
+ with gr.Row():
325
+ with gr.Column():
326
+ composer = gr.HTML(
327
+ value=dict(COMPOSER_DEFAULTS),
328
+ html_template=COMPOSER_HTML,
329
+ css_template=COMPOSER_CSS,
330
+ js_on_load=COMPOSER_JS,
331
+ container=False,
332
+ padding=False,
333
+ )
334
+ images = gr.Gallery(
335
+ label="Pictures to edit (optional, up to 8) — refer to them as <Picture 1>, <Picture 2>, …",
336
+ type="filepath", columns=4, height=200,
337
+ )
338
+ with gr.Accordion("Advanced", open=False):
339
+ effort = gr.Radio(["low", "medium", "xhigh"], value="low", label="Prompt enhancer reasoning effort",
340
+ info="xhigh is the released default; low thinks briefly and is several times faster")
341
+ with gr.Row():
342
+ seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
343
+ randomize = gr.Checkbox(label="Randomize seed", value=True)
344
+ with gr.Column():
345
+ output = gr.Image(label="Result", type="filepath", format="png", show_label=False, height=620)
346
+ stats = gr.Markdown()
347
+ with gr.Accordion("Prompt used", open=False):
348
+ prompt_used = gr.Textbox(show_label=False, lines=8)
349
+ with gr.Accordion("Enhancer thinking", open=False):
350
+ thinking = gr.Textbox(show_label=False, lines=10)
351
+
352
+ composer.submit(
353
+ studio, [composer, images, seed, randomize, effort], [output, stats, prompt_used, thinking, seed],
354
+ show_progress="minimal", api_visibility="private",
355
  )
356
+
357
+ # Plain API endpoint (also what the composer runs, minus the streaming UI updates).
358
+ api_prompt = gr.Textbox(visible=False)
359
+ api_aspect = gr.Textbox(visible=False, value=AUTO)
360
+ api_resolution = gr.Textbox(visible=False, value="4 MP")
361
+ api_enhance = gr.Checkbox(visible=False, value=False)
362
+ api_effort = gr.Textbox(visible=False, value="low")
363
+ api_button = gr.Button(visible=False)
364
+ api_button.click(
 
 
 
 
 
 
 
 
 
365
  generate,
366
+ [api_prompt, images, api_aspect, api_resolution, seed, randomize, api_enhance, api_effort],
367
+ [output, seed, prompt_used],
368
  api_name="generate",
369
  )
370