victor HF Staff commited on
Commit
9f3065c
·
1 Parent(s): 0fb3ed4

Refactor Gradio UI to match target React app design

Browse files
Files changed (2) hide show
  1. .gitignore +4 -0
  2. app.py +261 -110
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ target-app/
2
+ __pycache__/
3
+ *.pyc
4
+ .DS_Store
app.py CHANGED
@@ -20,14 +20,15 @@ device = "cuda" if torch.cuda.is_available() else "cpu"
20
  MAX_SEED = np.iinfo(np.int32).max
21
  MAX_IMAGE_SIZE = 1024
22
 
 
23
  def remote_text_encoder(prompts):
24
  response = requests.post(
25
  "https://remote-text-encoder-flux-2.huggingface.co/predict",
26
  json={"prompt": prompts},
27
  headers={
28
  "Authorization": f"Bearer {os.environ['HF_TOKEN']}",
29
- "Content-Type": "application/json"
30
- }
31
  )
32
 
33
  assert response.status_code == 200, f"{response.status_code=}"
@@ -36,20 +37,16 @@ def remote_text_encoder(prompts):
36
 
37
  return prompt_embeds
38
 
 
39
  # Load model
40
  repo_id = "black-forest-labs/FLUX.2-dev"
41
 
42
  dit = Flux2Transformer2DModel.from_pretrained(
43
- repo_id,
44
- subfolder="transformer",
45
- torch_dtype=torch.bfloat16
46
  )
47
 
48
  pipe = Flux2Pipeline.from_pretrained(
49
- repo_id,
50
- text_encoder=None,
51
- transformer=dit,
52
- torch_dtype=torch.bfloat16
53
  )
54
  pipe.to("cuda")
55
 
@@ -58,37 +55,57 @@ pipe.transformer.set_attention_backend("_flash_3_hub")
58
  optimize_pipeline_(
59
  pipe,
60
  image=[Image.new("RGB", (1024, 1024))],
61
- prompt_embeds = remote_text_encoder("prompt").to("cuda"),
62
  guidance_scale=2.5,
63
  width=1024,
64
  height=1024,
65
- num_inference_steps=1
66
  )
67
 
68
 
69
- def get_duration(prompt, input_images=None, seed=42, randomize_seed=False, width=1024, height=1024, num_inference_steps=50, guidance_scale=2.5, progress=gr.Progress(track_tqdm=True)):
 
 
 
 
 
 
 
 
 
 
70
  num_images = 0 if input_images is None else len(input_images)
71
  step_duration = 1 + 0.7 * num_images
72
  return num_inference_steps * step_duration + 10
73
 
74
 
75
  @spaces.GPU(duration=get_duration)
76
- def infer(prompt, input_images=None, seed=42, randomize_seed=False, width=1024, height=1024, num_inference_steps=50, guidance_scale=2.5, progress=gr.Progress(track_tqdm=True)):
77
-
 
 
 
 
 
 
 
 
 
 
78
  if randomize_seed:
79
  seed = random.randint(0, MAX_SEED)
80
-
81
  # Get prompt embeddings from remote text encoder
82
  progress(0.1, desc="Encoding prompt...")
83
  prompt_embeds = remote_text_encoder(prompt).to("cuda")
84
-
85
  # Prepare image list (convert None or empty gallery to None)
86
  image_list = None
87
  if input_images is not None and len(input_images) > 0:
88
  image_list = []
89
  for item in input_images:
90
  image_list.append(item[0])
91
-
92
  # Generate image
93
  progress(0.3, desc="Generating image...")
94
  generator = torch.Generator(device=device).manual_seed(seed)
@@ -101,72 +118,219 @@ def infer(prompt, input_images=None, seed=42, randomize_seed=False, width=1024,
101
  guidance_scale=guidance_scale,
102
  generator=generator,
103
  ).images[0]
104
-
105
  return image, seed
106
 
107
- examples = [
108
- ["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"],
109
- ["Photorealistic infographic showing the complete Berlin TV Tower (Fernsehturm) from ground base to antenna tip, full vertical view with entire structure visible including concrete shaft, metallic sphere, and antenna spire. Slight upward perspective angle looking up toward the iconic sphere, perfectly centered on clean white background. Left side labels with thin horizontal connector lines: the text '368m' in extra large bold dark grey numerals (#2D3748) positioned at exactly the antenna tip with 'TOTAL HEIGHT' in small caps below. The text '207m' in extra large bold with 'TELECAFÉ' in small caps below, with connector line touching the sphere precisely at the window level. Right side label with horizontal connector line touching the sphere's equator: the text '32m' in extra large bold dark grey numerals with 'SPHERE DIAMETER' in small caps below. Bottom section arranged in three balanced columns: Left - Large text '986' in extra bold dark grey with 'STEPS' in caps below. Center - 'BERLIN TV TOWER' in bold caps with 'FERNSEHTURM' in lighter weight below. Right - 'INAUGURATED' in bold caps with 'OCTOBER 3, 1969' below. All typography in modern sans-serif font (such as Inter or Helvetica), color #2D3748, clean minimal technical diagram style. Horizontal connector lines are thin, precise, and clearly visible, touching the tower structure at exact corresponding measurement points. Professional architectural elevation drawing aesthetic with dynamic low angle perspective creating sense of height and grandeur, poster-ready infographic design with perfect visual hierarchy."],
110
- ["Soaking wet capybara taking shelter under a banana leaf in the rainy jungle, close up photo"],
111
- ["A kawaii die-cut sticker of a chubby orange cat, featuring big sparkly eyes and a happy smile with paws raised in greeting and a heart-shaped pink nose. The design should have smooth rounded lines with black outlines and soft gradient shading with pink cheeks."],
112
- ]
113
 
114
- examples_images = [
115
- # ["Replace the top of the person from image 1 with the one from image 2", ["person1.webp", "woman2.webp"]],
116
- ["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"]]
117
- ]
118
 
119
- css="""
120
- #col-container {
121
- margin: 0 auto;
122
- max-width: 620px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  }
124
  """
125
 
126
- with gr.Blocks() as demo:
127
-
128
- with gr.Column(elem_id="col-container"):
129
- gr.Markdown(f"""# FLUX.2 [dev]
130
- FLUX.2 [dev] is a 32B model rectified flow capable of generating, editing and combining images based on text instructions model [[model](https://huggingface.co/black-forest-labs/FLUX.2-dev)], [[blog](https://bfl.ai/blog/flux-2)]
131
- """)
132
-
133
- with gr.Accordion("Input image(s) (optional)", open=False):
134
- input_images = gr.Gallery(
135
- label="Input Image(s)",
136
- type="pil",
137
- columns=3,
138
- rows=1,
139
- )
140
-
141
- with gr.Row():
142
-
143
- prompt = gr.Text(
144
- label="Prompt",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  show_label=False,
146
- max_lines=2,
147
- placeholder="Enter your prompt",
148
- container=False,
149
- scale=3
150
  )
151
-
152
- run_button = gr.Button("Run", scale=1)
153
-
154
- result = gr.Image(label="Result", show_label=False)
155
-
156
- with gr.Accordion("Advanced Settings", open=False):
157
-
158
- seed = gr.Slider(
159
- label="Seed",
160
- minimum=0,
161
- maximum=MAX_SEED,
162
- step=1,
163
- value=0,
 
 
 
 
 
 
 
 
 
164
  )
165
-
166
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
167
-
168
- with gr.Row():
169
-
 
 
 
 
170
  width = gr.Slider(
171
  label="Width",
172
  minimum=256,
@@ -174,7 +338,6 @@ FLUX.2 [dev] is a 32B model rectified flow capable of generating, editing and co
174
  step=32,
175
  value=1024,
176
  )
177
-
178
  height = gr.Slider(
179
  label="Height",
180
  minimum=256,
@@ -182,48 +345,36 @@ FLUX.2 [dev] is a 32B model rectified flow capable of generating, editing and co
182
  step=32,
183
  value=1024,
184
  )
185
-
186
- with gr.Row():
187
-
 
 
 
 
188
  num_inference_steps = gr.Slider(
189
- label="Number of inference steps",
190
- minimum=1,
191
- maximum=100,
192
- step=1,
193
- value=30,
194
  )
195
-
196
- guidance_scale = gr.Slider(
197
- label="Guidance scale",
198
- minimum=0.0,
199
- maximum=10.0,
200
- step=0.1,
201
- value=4,
202
  )
203
-
204
- gr.Examples(
205
- examples=examples,
206
- fn=infer,
207
- inputs=[prompt],
208
- outputs=[result, seed],
209
- cache_examples=True,
210
- cache_mode="lazy"
211
- )
212
-
213
- gr.Examples(
214
- examples=examples_images,
215
- fn=infer,
216
- inputs=[prompt, input_images],
217
- outputs=[result, seed],
218
- cache_examples=True,
219
- cache_mode="lazy"
220
- )
221
 
 
222
  gr.on(
223
  triggers=[run_button.click, prompt.submit],
224
  fn=infer,
225
- inputs=[prompt, input_images, seed, randomize_seed, width, height, num_inference_steps, guidance_scale],
226
- outputs=[result, seed]
 
 
 
 
 
 
 
 
 
227
  )
228
 
229
- demo.launch(css=css)
 
20
  MAX_SEED = np.iinfo(np.int32).max
21
  MAX_IMAGE_SIZE = 1024
22
 
23
+
24
  def remote_text_encoder(prompts):
25
  response = requests.post(
26
  "https://remote-text-encoder-flux-2.huggingface.co/predict",
27
  json={"prompt": prompts},
28
  headers={
29
  "Authorization": f"Bearer {os.environ['HF_TOKEN']}",
30
+ "Content-Type": "application/json",
31
+ },
32
  )
33
 
34
  assert response.status_code == 200, f"{response.status_code=}"
 
37
 
38
  return prompt_embeds
39
 
40
+
41
  # Load model
42
  repo_id = "black-forest-labs/FLUX.2-dev"
43
 
44
  dit = Flux2Transformer2DModel.from_pretrained(
45
+ repo_id, subfolder="transformer", torch_dtype=torch.bfloat16
 
 
46
  )
47
 
48
  pipe = Flux2Pipeline.from_pretrained(
49
+ repo_id, text_encoder=None, transformer=dit, torch_dtype=torch.bfloat16
 
 
 
50
  )
51
  pipe.to("cuda")
52
 
 
55
  optimize_pipeline_(
56
  pipe,
57
  image=[Image.new("RGB", (1024, 1024))],
58
+ prompt_embeds=remote_text_encoder("prompt").to("cuda"),
59
  guidance_scale=2.5,
60
  width=1024,
61
  height=1024,
62
+ num_inference_steps=1,
63
  )
64
 
65
 
66
+ def get_duration(
67
+ prompt,
68
+ input_images=None,
69
+ seed=42,
70
+ randomize_seed=False,
71
+ width=1024,
72
+ height=1024,
73
+ num_inference_steps=50,
74
+ guidance_scale=2.5,
75
+ progress=gr.Progress(track_tqdm=True),
76
+ ):
77
  num_images = 0 if input_images is None else len(input_images)
78
  step_duration = 1 + 0.7 * num_images
79
  return num_inference_steps * step_duration + 10
80
 
81
 
82
  @spaces.GPU(duration=get_duration)
83
+ def infer(
84
+ prompt,
85
+ input_images=None,
86
+ seed=42,
87
+ randomize_seed=False,
88
+ width=1024,
89
+ height=1024,
90
+ num_inference_steps=50,
91
+ guidance_scale=2.5,
92
+ progress=gr.Progress(track_tqdm=True),
93
+ ):
94
+
95
  if randomize_seed:
96
  seed = random.randint(0, MAX_SEED)
97
+
98
  # Get prompt embeddings from remote text encoder
99
  progress(0.1, desc="Encoding prompt...")
100
  prompt_embeds = remote_text_encoder(prompt).to("cuda")
101
+
102
  # Prepare image list (convert None or empty gallery to None)
103
  image_list = None
104
  if input_images is not None and len(input_images) > 0:
105
  image_list = []
106
  for item in input_images:
107
  image_list.append(item[0])
108
+
109
  # Generate image
110
  progress(0.3, desc="Generating image...")
111
  generator = torch.Generator(device=device).manual_seed(seed)
 
118
  guidance_scale=guidance_scale,
119
  generator=generator,
120
  ).images[0]
121
+
122
  return image, seed
123
 
 
 
 
 
 
 
124
 
125
+ # --- UI Configuration ---
 
 
 
126
 
127
+ css = """
128
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');
129
+
130
+ body, .gradio-container {
131
+ background-color: #000000 !important;
132
+ color: #ffffff !important;
133
+ font-family: 'Inter', sans-serif !important;
134
+ margin: 0;
135
+ padding: 0;
136
+ --color-background-primary: #000000;
137
+ --color-background-secondary: #050505;
138
+ --color-border-primary: #171717;
139
+ --color-text-primary: #ffffff;
140
+ --color-text-secondary: #a3a3a3;
141
+ }
142
+
143
+ /* Layout */
144
+ #main-container {
145
+ height: 100vh !important;
146
+ max-height: 100vh !important;
147
+ gap: 0 !important;
148
+ display: flex;
149
+ flex-wrap: nowrap;
150
+ overflow: hidden;
151
+ }
152
+
153
+ #left-sidebar {
154
+ background-color: #050505;
155
+ border-right: 1px solid #171717;
156
+ width: 280px !important;
157
+ max-width: 280px !important;
158
+ flex: none !important;
159
+ padding: 0 !important;
160
+ display: flex;
161
+ flex-direction: column;
162
+ height: 100%;
163
+ }
164
+
165
+ #right-sidebar {
166
+ background-color: #050505;
167
+ border-left: 1px solid #171717;
168
+ width: 320px !important;
169
+ max-width: 320px !important;
170
+ flex: none !important;
171
+ padding: 0 !important;
172
+ height: 100%;
173
+ overflow-y: auto;
174
+ }
175
+
176
+ #center-canvas {
177
+ background-color: #090909;
178
+ flex-grow: 1 !important;
179
+ display: flex;
180
+ flex-direction: column;
181
+ justify-content: center;
182
+ align-items: center;
183
+ padding: 20px;
184
+ background-image: radial-gradient(#151515 1px, transparent 1px);
185
+ background-size: 20px 20px;
186
+ height: 100%;
187
+ position: relative;
188
+ }
189
+
190
+ /* Components */
191
+ #generate-btn {
192
+ background: #ffffff !important;
193
+ color: #000000 !important;
194
+ border-radius: 6px !important;
195
+ font-weight: 600 !important;
196
+ text-transform: uppercase;
197
+ font-size: 11px !important;
198
+ border: none !important;
199
+ margin-top: 10px;
200
+ }
201
+
202
+ #prompt-input textarea {
203
+ background-color: #000000 !important;
204
+ border: 1px solid #262626 !important;
205
+ color: white !important;
206
+ border-radius: 8px !important;
207
+ }
208
+
209
+ #prompt-input span {
210
+ display: none; /* Hide default label if needed, or style it */
211
+ }
212
+
213
+ /* Accordions */
214
+ .accordion {
215
+ background: transparent !important;
216
+ border: none !important;
217
+ border-bottom: 1px solid #171717 !important;
218
+ }
219
+
220
+ .accordion-label {
221
+ font-size: 11px !important;
222
+ font-weight: 600 !important;
223
+ text-transform: uppercase;
224
+ color: #a3a3a3 !important;
225
+ }
226
+
227
+ /* Sliders */
228
+ input[type=range] {
229
+ accent-color: white !important;
230
+ }
231
+
232
+ /* Gallery in Sidebar */
233
+ #history-gallery {
234
+ flex-grow: 1;
235
+ overflow-y: auto;
236
+ padding: 10px;
237
+ }
238
+
239
+ #history-gallery .grid-wrap {
240
+ grid-template-columns: 1fr !important; /* Force list view */
241
+ }
242
+
243
+ /* Main Image */
244
+ #main-image {
245
+ background: transparent !important;
246
+ border: 1px solid #171717;
247
+ border-radius: 8px;
248
+ overflow: hidden;
249
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
250
+ }
251
+
252
+ /* Scrollbars */
253
+ ::-webkit-scrollbar {
254
+ width: 6px;
255
+ height: 6px;
256
+ }
257
+ ::-webkit-scrollbar-track {
258
+ background: #000000;
259
+ }
260
+ ::-webkit-scrollbar-thumb {
261
+ background: #333;
262
+ border-radius: 3px;
263
  }
264
  """
265
 
266
+ header_html = """
267
+ <div style="padding: 20px; border-bottom: 1px solid #171717;">
268
+ <h1 style="font-size: 18px; font-weight: 600; margin: 0; color: white;">FLUX.2 [dev]</h1>
269
+ <p style="font-size: 12px; color: #666; margin: 5px 0 0 0;">High-fidelity generation</p>
270
+ </div>
271
+ """
272
+
273
+ user_footer_html = """
274
+ <div style="padding: 15px 20px; border-top: 1px solid #171717; margin-top: auto;">
275
+ <div style="display: flex; align-items: center; gap: 10px;">
276
+ <div style="width: 24px; height: 24px; border-radius: 50%; background: #333;"></div>
277
+ <span style="font-size: 13px; color: #999;">User</span>
278
+ </div>
279
+ </div>
280
+ """
281
+
282
+ controls_header_html = """
283
+ <div style="padding: 20px 20px 10px 20px;">
284
+ <h2 style="font-size: 11px; font-weight: 600; text-transform: uppercase; color: #666; margin: 0;">Configuration</h2>
285
+ </div>
286
+ """
287
+
288
+ with gr.Blocks(css=css, theme=None, title="FLUX.2 [dev]") as demo:
289
+ with gr.Row(elem_id="main-container", variant="compact"):
290
+ # --- Left Sidebar ---
291
+ with gr.Column(elem_id="left-sidebar", min_width=280):
292
+ gr.HTML(header_html)
293
+ # Placeholder for history - using a gallery
294
+ history_gallery = gr.Gallery(
295
+ elem_id="history-gallery",
296
+ columns=1,
297
+ allow_preview=False,
298
+ interactive=False,
299
+ label="History",
300
  show_label=False,
 
 
 
 
301
  )
302
+ gr.HTML(user_footer_html)
303
+
304
+ # --- Center Canvas ---
305
+ with gr.Column(elem_id="center-canvas"):
306
+ with gr.Row(elem_id="canvas-toolbar"):
307
+ gr.Markdown("Canvas", elem_id="canvas-info")
308
+
309
+ result_image = gr.Image(
310
+ elem_id="main-image", interactive=False, show_label=False
311
+ )
312
+
313
+ # --- Right Sidebar ---
314
+ with gr.Column(elem_id="right-sidebar", min_width=320):
315
+ gr.HTML(controls_header_html)
316
+
317
+ # Prompt Section
318
+ prompt = gr.Textbox(
319
+ elem_id="prompt-input",
320
+ lines=4,
321
+ placeholder="Describe your imagination...",
322
+ label="Prompt",
323
+ show_label=True,
324
  )
325
+ run_button = gr.Button("Generate Image", elem_id="generate-btn")
326
+
327
+ # Settings
328
+ with gr.Accordion("Input Image", open=False, elem_classes="accordion"):
329
+ input_images = gr.Gallery(
330
+ label="Input Image(s)", type="pil", columns=3, rows=1
331
+ )
332
+
333
+ with gr.Accordion("Model Settings", open=True, elem_classes="accordion"):
334
  width = gr.Slider(
335
  label="Width",
336
  minimum=256,
 
338
  step=32,
339
  value=1024,
340
  )
 
341
  height = gr.Slider(
342
  label="Height",
343
  minimum=256,
 
345
  step=32,
346
  value=1024,
347
  )
348
+
349
+ with gr.Accordion(
350
+ "Advanced Settings", open=False, elem_classes="accordion"
351
+ ):
352
+ guidance_scale = gr.Slider(
353
+ label="Guidance Scale", minimum=0.0, maximum=10.0, step=0.1, value=4
354
+ )
355
  num_inference_steps = gr.Slider(
356
+ label="Inference Steps", minimum=1, maximum=100, step=1, value=30
 
 
 
 
357
  )
358
+ seed = gr.Slider(
359
+ label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0
 
 
 
 
 
360
  )
361
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
+ # Wiring
364
  gr.on(
365
  triggers=[run_button.click, prompt.submit],
366
  fn=infer,
367
+ inputs=[
368
+ prompt,
369
+ input_images,
370
+ seed,
371
+ randomize_seed,
372
+ width,
373
+ height,
374
+ num_inference_steps,
375
+ guidance_scale,
376
+ ],
377
+ outputs=[result_image, seed],
378
  )
379
 
380
+ demo.launch()