tchung1970 Claude Opus 4.5 commited on
Commit
d46cb20
·
1 Parent(s): ac4f897

Match Z-Image UI exactly

Browse files

- Updated CSS to match Z-Image Apple-style design
- Output section now has white background
- Removed Advanced Settings accordion
- Hidden footer links
- Always use random seed (like Z-Image)
- Fixed inference parameters (30 steps, 4.0 guidance)
- Improved JavaScript for layout control

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +174 -143
app.py CHANGED
@@ -196,20 +196,27 @@ def parse_aspect_ratio(aspect_ratio_str):
196
  return int(match.group(1)), int(match.group(2))
197
  return 1024, 1024 # Default
198
 
199
- def infer(prompt, aspect_ratio="1:1 (1024x1024)", seed=42, randomize_seed=False, num_inference_steps=30, guidance_scale=4.0, progress=gr.Progress(track_tqdm=True)):
 
 
 
200
 
201
- if randomize_seed:
202
- seed = random.randint(0, MAX_SEED)
203
 
204
  # Parse aspect ratio to get width and height
205
  width, height = parse_aspect_ratio(aspect_ratio)
206
 
 
 
 
 
207
  # Text Encoding (Network bound - No GPU needed)
208
  progress(0.1, desc="Encoding prompt...")
209
  prompt_embeds = remote_text_encoder(prompt)
210
 
211
  # Image Generation (GPU bound)
212
- progress(0.3, desc="Waiting for GPU...")
213
  image = generate_image(
214
  prompt_embeds,
215
  None, # No input images
@@ -221,7 +228,7 @@ def infer(prompt, aspect_ratio="1:1 (1024x1024)", seed=42, randomize_seed=False,
221
  progress
222
  )
223
 
224
- return image, seed
225
 
226
  examples = [
227
  ["Create a vase on a table in living room, the color of the vase is a gradient of color, starting with #02eb3c color and finishing with #edfa3c. The flowers inside the vase have the color #ff0088"],
@@ -235,48 +242,24 @@ examples_images = [
235
  ["The person from image 1 is petting the cat from image 2, the bird from image 3 is next to them", ["woman1.webp", "cat_window.webp", "bird.webp"]]
236
  ]
237
 
238
- # Apple-inspired CSS styling
239
  css = """
240
- /* Global container styling */
241
  .gradio-container {
242
  max-width: 85vw !important;
243
  margin: 0 auto !important;
244
- font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'SF Pro Display', sans-serif !important;
245
- }
246
-
247
- /* Main row - horizontal layout */
248
- #main-row {
249
- display: flex !important;
250
- flex-direction: row !important;
251
- flex-wrap: nowrap !important;
252
- gap: 24px !important;
253
- align-items: flex-start !important;
254
- }
255
-
256
- /* Input section - fixed width */
257
- #input-column {
258
- background: #ffffff !important;
259
- border-radius: 18px !important;
260
- padding: 32px !important;
261
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important;
262
- width: 550px !important;
263
- min-width: 550px !important;
264
- max-width: 550px !important;
265
- flex: 0 0 550px !important;
266
  }
267
 
268
- /* Output section - flexible */
269
- #output-column {
270
- flex: 1 1 auto !important;
271
- min-height: 80vh !important;
272
- max-height: 90vh !important;
273
- display: flex !important;
274
- flex-direction: column !important;
275
  }
276
 
277
- /* Header styling */
278
  .header-container {
279
- text-align: center;
280
  margin-bottom: 24px;
281
  }
282
 
@@ -284,20 +267,45 @@ css = """
284
  font-size: 32px !important;
285
  font-weight: 600 !important;
286
  letter-spacing: -0.02em !important;
 
287
  color: #1d1d1f !important;
288
- margin: 0 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  }
290
 
291
- /* Prompt textbox */
292
  #prompt-textbox textarea {
293
- min-height: 400px !important;
294
- max-height: 500px !important;
295
  border-radius: 12px !important;
296
  border: 1px solid #d2d2d7 !important;
297
- padding: 16px !important;
298
- font-size: 15px !important;
299
- line-height: 1.5 !important;
 
 
 
300
  resize: vertical !important;
 
 
301
  }
302
 
303
  #prompt-textbox textarea:focus {
@@ -323,47 +331,53 @@ css = """
323
  color: #ff3b30;
324
  }
325
 
326
- /* Generate button */
327
  button.primary {
 
 
 
 
328
  background: #0071e3 !important;
329
  border: none !important;
330
- border-radius: 980px !important;
331
- padding: 12px 32px !important;
332
- font-size: 17px !important;
333
- font-weight: 500 !important;
334
- color: white !important;
335
  cursor: pointer !important;
336
- transition: all 0.2s ease !important;
337
  width: 100% !important;
338
  margin-top: 16px !important;
339
  }
340
 
341
  button.primary:hover {
342
- background: #0077ED !important;
343
- transform: scale(1.02) !important;
344
  }
345
 
346
- /* Accordion styling */
347
- .accordion {
348
- border: 1px solid #d2d2d7 !important;
349
- border-radius: 12px !important;
350
- margin-top: 16px !important;
 
 
 
 
 
 
 
 
351
  }
352
 
353
- /* Gallery styling */
354
- .gallery-container img {
 
 
 
 
355
  object-fit: contain !important;
356
  }
357
 
358
- /* Output image */
359
- #output-column .image-container {
360
- border-radius: 18px !important;
361
- overflow: hidden !important;
362
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important;
363
- }
364
-
365
- /* Dark mode support */
366
- .dark #input-column {
367
  background: #1d1d1f !important;
368
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4) !important;
369
  }
@@ -373,19 +387,52 @@ button.primary:hover {
373
  }
374
 
375
  .dark #prompt-textbox textarea {
376
- background: #2d2d2f !important;
377
  border-color: #424245 !important;
378
  color: #f5f5f7 !important;
379
  }
380
 
381
- .dark #prompt-textbox textarea:focus {
382
- border-color: #0071e3 !important;
383
- }
384
-
385
  .dark .char-counter {
386
  color: #a1a1a6 !important;
387
  }
388
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  /* Responsive adjustments */
390
  @media (max-width: 1200px) {
391
  #main-row {
@@ -407,69 +454,82 @@ button.primary:hover {
407
  }
408
  """
409
 
410
- # JavaScript for layout control and character counter
411
  js_code = """
412
  function() {
413
- // Force horizontal layout
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  function forceHorizontalLayout() {
 
 
 
 
 
 
415
  const mainRow = document.getElementById('main-row');
416
  if (mainRow) {
417
- mainRow.style.display = 'flex';
418
  mainRow.style.flexDirection = 'row';
419
  mainRow.style.flexWrap = 'nowrap';
 
 
420
  }
421
 
 
 
 
 
 
 
 
422
  const inputCol = document.getElementById('input-column');
423
  if (inputCol) {
424
- inputCol.style.flex = '0 0 550px';
425
  inputCol.style.width = '550px';
426
  inputCol.style.minWidth = '550px';
427
  inputCol.style.maxWidth = '550px';
 
428
  }
429
 
430
  const outputCol = document.getElementById('output-column');
431
  if (outputCol) {
432
  outputCol.style.flex = '1 1 auto';
 
433
  }
434
  }
435
 
436
- // Character counter setup
437
- function setupCharCounter() {
438
- const textbox = document.querySelector('#prompt-textbox textarea');
439
- const counterDiv = document.querySelector('.char-counter');
440
- const countSpan = document.getElementById('char-count');
441
-
442
- if (textbox && countSpan && counterDiv) {
443
- const updateCounter = () => {
444
- const len = textbox.value.length;
445
- countSpan.textContent = len;
446
-
447
- counterDiv.classList.remove('warning', 'limit');
448
- if (len >= 2000) {
449
- counterDiv.classList.add('limit');
450
- } else if (len >= 1800) {
451
- counterDiv.classList.add('warning');
452
- }
453
- };
454
-
455
- textbox.addEventListener('input', updateCounter);
456
- updateCounter();
457
- }
458
- }
459
-
460
- // Run on load and with slight delay for Gradio rendering
461
  forceHorizontalLayout();
462
  setupCharCounter();
463
 
464
- setTimeout(() => {
465
- forceHorizontalLayout();
466
- setupCharCounter();
467
- }, 500);
468
 
469
- setTimeout(() => {
470
- forceHorizontalLayout();
471
- setupCharCounter();
472
- }, 1500);
 
473
  }
474
  """
475
 
@@ -526,35 +586,6 @@ with gr.Blocks() as demo:
526
  container=True,
527
  )
528
 
529
- # Advanced Settings accordion
530
- with gr.Accordion("Advanced Settings", open=False):
531
- seed = gr.Slider(
532
- label="Seed",
533
- minimum=0,
534
- maximum=MAX_SEED,
535
- step=1,
536
- value=0,
537
- )
538
-
539
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
540
-
541
- with gr.Row():
542
- num_inference_steps = gr.Slider(
543
- label="Number of inference steps",
544
- minimum=1,
545
- maximum=100,
546
- step=1,
547
- value=30,
548
- )
549
-
550
- guidance_scale = gr.Slider(
551
- label="Guidance scale",
552
- minimum=0.0,
553
- maximum=10.0,
554
- step=0.1,
555
- value=4,
556
- )
557
-
558
  # Generate Button
559
  generate_btn = gr.Button(
560
  "Generate",
@@ -576,8 +607,8 @@ with gr.Blocks() as demo:
576
  gr.on(
577
  triggers=[generate_btn.click, prompt.submit],
578
  fn=infer,
579
- inputs=[prompt, aspect_ratio, seed, randomize_seed, num_inference_steps, guidance_scale],
580
- outputs=[result, seed],
581
  show_progress="full"
582
  )
583
 
 
196
  return int(match.group(1)), int(match.group(2))
197
  return 1024, 1024 # Default
198
 
199
+ def infer(prompt, aspect_ratio="2:3 (1344x2048)", progress=gr.Progress(track_tqdm=True)):
200
+ """Generate an image using FLUX.2 model."""
201
+ if not prompt.strip():
202
+ raise gr.Error("Please enter a prompt to generate an image.")
203
 
204
+ # Always use random seed
205
+ seed = random.randint(0, MAX_SEED)
206
 
207
  # Parse aspect ratio to get width and height
208
  width, height = parse_aspect_ratio(aspect_ratio)
209
 
210
+ # Fixed inference parameters
211
+ num_inference_steps = 30
212
+ guidance_scale = 4.0
213
+
214
  # Text Encoding (Network bound - No GPU needed)
215
  progress(0.1, desc="Encoding prompt...")
216
  prompt_embeds = remote_text_encoder(prompt)
217
 
218
  # Image Generation (GPU bound)
219
+ progress(0.3, desc="Generating image...")
220
  image = generate_image(
221
  prompt_embeds,
222
  None, # No input images
 
228
  progress
229
  )
230
 
231
+ return image
232
 
233
  examples = [
234
  ["Create a vase on a table in living room, the color of the vase is a gradient of color, starting with #02eb3c color and finishing with #edfa3c. The flowers inside the vase have the color #ff0088"],
 
242
  ["The person from image 1 is petting the cat from image 2, the bird from image 3 is next to them", ["woman1.webp", "cat_window.webp", "bird.webp"]]
243
  ]
244
 
245
+ # Apple-inspired CSS styling (matching Z-Image)
246
  css = """
247
+ /* Global Styles */
248
  .gradio-container {
249
  max-width: 85vw !important;
250
  margin: 0 auto !important;
251
+ padding: 48px 20px !important;
252
+ font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', 'Roboto', sans-serif !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  }
254
 
255
+ * {
256
+ transition: none !important;
257
+ animation: none !important;
 
 
 
 
258
  }
259
 
260
+ /* Header */
261
  .header-container {
262
+ text-align: left;
263
  margin-bottom: 24px;
264
  }
265
 
 
267
  font-size: 32px !important;
268
  font-weight: 600 !important;
269
  letter-spacing: -0.02em !important;
270
+ line-height: 1.07 !important;
271
  color: #1d1d1f !important;
272
+ margin: 0 0 16px 0 !important;
273
+ }
274
+
275
+ /* Input Section */
276
+ .input-section {
277
+ background: #ffffff;
278
+ border-radius: 18px;
279
+ padding: 32px;
280
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
281
+ }
282
+
283
+ #input-column {
284
+ background: #ffffff !important;
285
+ border-radius: 18px !important;
286
+ padding: 32px !important;
287
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important;
288
+ width: 550px !important;
289
+ min-width: 550px !important;
290
+ max-width: 550px !important;
291
+ flex: 0 0 550px !important;
292
  }
293
 
294
+ /* Textbox */
295
  #prompt-textbox textarea {
296
+ font-size: 17px !important;
297
+ line-height: 1.47 !important;
298
  border-radius: 12px !important;
299
  border: 1px solid #d2d2d7 !important;
300
+ padding: 12px 16px !important;
301
+ background: #ffffff !important;
302
+ font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif !important;
303
+ min-height: 500px !important;
304
+ max-height: 700px !important;
305
+ height: 500px !important;
306
  resize: vertical !important;
307
+ overflow-y: auto !important;
308
+ margin-bottom: 16px !important;
309
  }
310
 
311
  #prompt-textbox textarea:focus {
 
331
  color: #ff3b30;
332
  }
333
 
334
+ /* Button */
335
  button.primary {
336
+ font-size: 17px !important;
337
+ font-weight: 400 !important;
338
+ padding: 12px 32px !important;
339
+ border-radius: 980px !important;
340
  background: #0071e3 !important;
341
  border: none !important;
342
+ color: #ffffff !important;
343
+ min-height: 44px !important;
344
+ letter-spacing: -0.01em !important;
 
 
345
  cursor: pointer !important;
 
346
  width: 100% !important;
347
  margin-top: 16px !important;
348
  }
349
 
350
  button.primary:hover {
351
+ background: #0077ed !important;
 
352
  }
353
 
354
+ /* Output Section */
355
+ #output-column {
356
+ background: #ffffff !important;
357
+ border-radius: 18px !important;
358
+ padding: 32px !important;
359
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important;
360
+ overflow: hidden !important;
361
+ display: flex !important;
362
+ align-items: center !important;
363
+ justify-content: center !important;
364
+ min-height: 80vh !important;
365
+ max-height: 90vh !important;
366
+ flex: 1 1 auto !important;
367
  }
368
 
369
+ #output-column img {
370
+ border-radius: 12px !important;
371
+ max-width: 100% !important;
372
+ max-height: 85vh !important;
373
+ width: auto !important;
374
+ height: auto !important;
375
  object-fit: contain !important;
376
  }
377
 
378
+ /* Dark Mode */
379
+ .dark #input-column,
380
+ .dark #output-column {
 
 
 
 
 
 
381
  background: #1d1d1f !important;
382
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4) !important;
383
  }
 
387
  }
388
 
389
  .dark #prompt-textbox textarea {
390
+ background: #1d1d1f !important;
391
  border-color: #424245 !important;
392
  color: #f5f5f7 !important;
393
  }
394
 
 
 
 
 
395
  .dark .char-counter {
396
  color: #a1a1a6 !important;
397
  }
398
 
399
+ /* Layout - Force horizontal layout */
400
+ #main-row {
401
+ align-items: flex-start !important;
402
+ flex-direction: row !important;
403
+ display: flex !important;
404
+ flex-wrap: nowrap !important;
405
+ width: 100% !important;
406
+ gap: 24px !important;
407
+ }
408
+
409
+ .gradio-row {
410
+ align-items: flex-start !important;
411
+ flex-direction: row !important;
412
+ display: flex !important;
413
+ flex-wrap: nowrap !important;
414
+ width: 100% !important;
415
+ }
416
+
417
+ .gradio-row > .gradio-column:first-child,
418
+ .gradio-row > div:first-child {
419
+ width: 550px !important;
420
+ min-width: 550px !important;
421
+ max-width: 550px !important;
422
+ flex: 0 0 550px !important;
423
+ }
424
+
425
+ .gradio-row > .gradio-column:last-child,
426
+ .gradio-row > div:last-child {
427
+ flex: 1 1 auto !important;
428
+ min-width: 0 !important;
429
+ }
430
+
431
+ /* Hide footer */
432
+ footer {
433
+ display: none !important;
434
+ }
435
+
436
  /* Responsive adjustments */
437
  @media (max-width: 1200px) {
438
  #main-row {
 
454
  }
455
  """
456
 
457
+ # JavaScript for layout control and character counter (matching Z-Image)
458
  js_code = """
459
  function() {
460
+ function setupCharCounter() {
461
+ const textbox = document.querySelector('#prompt-textbox textarea');
462
+ const counter = document.querySelector('.char-counter');
463
+ const countSpan = document.getElementById('char-count');
464
+
465
+ if (textbox && counter && countSpan) {
466
+ function updateCount() {
467
+ const len = textbox.value.length;
468
+ countSpan.textContent = len;
469
+
470
+ counter.classList.remove('warning', 'limit');
471
+ if (len >= 2000) {
472
+ counter.classList.add('limit');
473
+ } else if (len >= 1800) {
474
+ counter.classList.add('warning');
475
+ }
476
+ }
477
+
478
+ textbox.addEventListener('input', updateCount);
479
+ updateCount();
480
+ }
481
+ }
482
+
483
  function forceHorizontalLayout() {
484
+ const container = document.querySelector('.gradio-container');
485
+ if (container) {
486
+ container.style.maxWidth = '85vw';
487
+ container.style.width = '85vw';
488
+ }
489
+
490
  const mainRow = document.getElementById('main-row');
491
  if (mainRow) {
 
492
  mainRow.style.flexDirection = 'row';
493
  mainRow.style.flexWrap = 'nowrap';
494
+ mainRow.style.display = 'flex';
495
+ mainRow.style.width = '100%';
496
  }
497
 
498
+ const rows = document.querySelectorAll('.gradio-row');
499
+ rows.forEach(row => {
500
+ row.style.flexDirection = 'row';
501
+ row.style.flexWrap = 'nowrap';
502
+ row.style.display = 'flex';
503
+ });
504
+
505
  const inputCol = document.getElementById('input-column');
506
  if (inputCol) {
 
507
  inputCol.style.width = '550px';
508
  inputCol.style.minWidth = '550px';
509
  inputCol.style.maxWidth = '550px';
510
+ inputCol.style.flex = '0 0 550px';
511
  }
512
 
513
  const outputCol = document.getElementById('output-column');
514
  if (outputCol) {
515
  outputCol.style.flex = '1 1 auto';
516
+ outputCol.style.minWidth = '0';
517
  }
518
  }
519
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
520
  forceHorizontalLayout();
521
  setupCharCounter();
522
 
523
+ setTimeout(forceHorizontalLayout, 100);
524
+ setTimeout(forceHorizontalLayout, 500);
525
+ setTimeout(forceHorizontalLayout, 1000);
526
+ setTimeout(forceHorizontalLayout, 2000);
527
 
528
+ setTimeout(setupCharCounter, 100);
529
+ setTimeout(setupCharCounter, 500);
530
+
531
+ const observer = new MutationObserver(forceHorizontalLayout);
532
+ observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
533
  }
534
  """
535
 
 
586
  container=True,
587
  )
588
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  # Generate Button
590
  generate_btn = gr.Button(
591
  "Generate",
 
607
  gr.on(
608
  triggers=[generate_btn.click, prompt.submit],
609
  fn=infer,
610
+ inputs=[prompt, aspect_ratio],
611
+ outputs=[result],
612
  show_progress="full"
613
  )
614