seawolf2357 commited on
Commit
5bb04db
·
verified ·
1 Parent(s): 4295952

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +618 -134
app.py CHANGED
@@ -62,7 +62,6 @@ def remote_text_encoder(prompts):
62
  api_name="/encode_text"
63
  )
64
 
65
- # Load returns a tensor, usually on CPU by default
66
  prompt_embeds = torch.load(result[0])
67
  return prompt_embeds
68
 
@@ -104,10 +103,7 @@ def image_to_data_uri(img):
104
  def upsample_prompt_logic(prompt, image_list):
105
  try:
106
  if image_list and len(image_list) > 0:
107
- # Image + Text Editing Mode
108
  system_content = SYSTEM_PROMPT_WITH_IMAGES
109
-
110
- # Construct user message with text and images
111
  user_content = [{"type": "text", "text": prompt}]
112
 
113
  for img in image_list:
@@ -122,7 +118,6 @@ def upsample_prompt_logic(prompt, image_list):
122
  {"role": "user", "content": user_content}
123
  ]
124
  else:
125
- # Text Only Mode
126
  system_content = SYSTEM_PROMPT_TEXT_ONLY
127
  messages = [
128
  {"role": "system", "content": system_content},
@@ -141,46 +136,39 @@ def upsample_prompt_logic(prompt, image_list):
141
  return prompt
142
 
143
  def update_dimensions_from_image(image_list):
144
- """Update width/height sliders based on uploaded image aspect ratio.
145
- Keeps one side at 1024 and scales the other proportionally, with both sides as multiples of 8."""
146
  if image_list is None or len(image_list) == 0:
147
- return 1024, 1024 # Default dimensions
148
 
149
- # Get the first image to determine dimensions
150
- img = image_list[0][0] # Gallery returns list of tuples (image, caption)
151
  img_width, img_height = img.size
152
 
153
  aspect_ratio = img_width / img_height
154
 
155
- if aspect_ratio >= 1: # Landscape or square
156
  new_width = 1024
157
  new_height = int(1024 / aspect_ratio)
158
- else: # Portrait
159
  new_height = 1024
160
  new_width = int(1024 * aspect_ratio)
161
 
162
- # Round to nearest multiple of 8
163
  new_width = round(new_width / 8) * 8
164
  new_height = round(new_height / 8) * 8
165
 
166
- # Ensure within valid range (minimum 256, maximum 1024)
167
  new_width = max(256, min(1024, new_width))
168
  new_height = max(256, min(1024, new_height))
169
 
170
  return new_width, new_height
171
 
172
- # Updated duration function for Turbo (much faster with fewer steps)
173
  def get_duration(prompt_embeds, image_list, width, height, num_inference_steps, guidance_scale, seed, use_turbo, progress=gr.Progress(track_tqdm=True)):
174
  num_images = 0 if image_list is None else len(image_list)
175
  step_duration = 1 + 0.8 * num_images
176
- # Turbo mode uses fewer steps, so shorter duration
177
  if use_turbo:
178
- return max(30, 8 * step_duration + 10) # Fixed 8 steps for turbo
179
  return max(65, num_inference_steps * step_duration + 10)
180
 
181
  @spaces.GPU(duration=get_duration)
182
  def generate_image(prompt_embeds, image_list, width, height, num_inference_steps, guidance_scale, seed, use_turbo, progress=gr.Progress(track_tqdm=True)):
183
- # Move embeddings to GPU only when inside the GPU decorated function
184
  prompt_embeds = prompt_embeds.to(device)
185
 
186
  generator = torch.Generator(device=device).manual_seed(seed)
@@ -194,14 +182,12 @@ def generate_image(prompt_embeds, image_list, width, height, num_inference_steps
194
  "height": height,
195
  }
196
 
197
- # Use Turbo sigmas or regular inference steps
198
  if use_turbo:
199
  pipe_kwargs["sigmas"] = TURBO_SIGMAS
200
- pipe_kwargs["num_inference_steps"] = 8 # Turbo always uses 8 steps
201
  else:
202
  pipe_kwargs["num_inference_steps"] = num_inference_steps
203
 
204
- # Progress bar for the actual generation steps
205
  if progress:
206
  progress(0, desc="Starting generation...")
207
 
@@ -213,14 +199,12 @@ def infer(prompt, input_images=None, seed=42, randomize_seed=False, width=1024,
213
  if randomize_seed:
214
  seed = random.randint(0, MAX_SEED)
215
 
216
- # Prepare image list (convert None or empty gallery to None)
217
  image_list = None
218
  if input_images is not None and len(input_images) > 0:
219
  image_list = []
220
  for item in input_images:
221
  image_list.append(item[0])
222
 
223
- # 1. Upsampling (Network bound - No GPU needed)
224
  final_prompt = prompt
225
  if prompt_upsampling:
226
  progress(0.05, desc="Upsampling prompt...")
@@ -228,12 +212,9 @@ def infer(prompt, input_images=None, seed=42, randomize_seed=False, width=1024,
228
  print(f"Original Prompt: {prompt}")
229
  print(f"Upsampled Prompt: {final_prompt}")
230
 
231
- # 2. Text Encoding (Network bound - No GPU needed)
232
  progress(0.1, desc="Encoding prompt...")
233
- # This returns CPU tensors
234
  prompt_embeds = remote_text_encoder(final_prompt)
235
 
236
- # 3. Image Generation (GPU bound)
237
  progress(0.3, desc="Waiting for GPU...")
238
  image = generate_image(
239
  prompt_embeds,
@@ -247,7 +228,25 @@ def infer(prompt, input_images=None, seed=42, randomize_seed=False, width=1024,
247
  progress
248
  )
249
 
250
- return image, seed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
251
 
252
  examples = [
253
  ["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"],
@@ -257,130 +256,614 @@ examples = [
257
  ]
258
 
259
  examples_images = [
260
- # ["Replace the top of the person from image 1 with the one from image 2", ["person1.webp", "woman2.webp"]],
261
  ["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"]]
262
  ]
263
 
264
- css="""
265
- #col-container {
266
- margin: 0 auto;
267
- max-width: 1200px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  }
269
- .gallery-container img{
270
- object-fit: contain;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  }
272
  """
273
 
274
- with gr.Blocks() as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
276
- with gr.Column(elem_id="col-container"):
277
- gr.Markdown(f"""# FLUX.2 [dev] Turbo
278
- FLUX.2 [dev] with [Turbo LoRA by fal](https://huggingface.co/fal/FLUX.2-Turbo) - a 32B rectified flow model capable of generating, editing and combining images based on text instructions in just 8 steps [[model](https://huggingface.co/black-forest-labs/FLUX.2-dev)], [[blog](https://bfl.ai/blog/flux-2)]
279
- """)
280
- with gr.Row():
281
- with gr.Column():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  with gr.Row():
283
- prompt = gr.Text(
284
- label="Prompt",
285
- show_label=False,
286
- max_lines=2,
287
- placeholder="Enter your prompt",
288
- container=False,
289
- scale=3
290
  )
291
 
292
- run_button = gr.Button("Run", scale=1)
293
-
294
- with gr.Accordion("Input image(s) (optional)", open=True):
295
- input_images = gr.Gallery(
296
- label="Input Image(s)",
297
- type="pil",
298
- columns=3,
299
- rows=1,
300
  )
301
 
302
- with gr.Accordion("Advanced Settings", open=False):
303
- use_turbo = gr.Checkbox(
304
- label="Use Turbo Mode (8 steps)",
305
- value=True,
306
- info="Enable Turbo LoRA for fast 8-step generation",
307
- visible=False
308
- )
309
-
310
- prompt_upsampling = gr.Checkbox(
311
- label="Prompt Upsampling",
312
- value=False,
313
- info="Automatically enhance the prompt using a VLM"
314
- )
315
-
316
- seed = gr.Slider(
317
- label="Seed",
318
- minimum=0,
319
- maximum=MAX_SEED,
320
  step=1,
321
- value=0,
322
  )
323
 
324
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
325
-
326
- with gr.Row():
327
-
328
- width = gr.Slider(
329
- label="Width",
330
- minimum=256,
331
- maximum=MAX_IMAGE_SIZE,
332
- step=8,
333
- value=1024,
334
- )
335
-
336
- height = gr.Slider(
337
- label="Height",
338
- minimum=256,
339
- maximum=MAX_IMAGE_SIZE,
340
- step=8,
341
- value=1024,
342
- )
343
-
344
- with gr.Row():
345
-
346
- num_inference_steps = gr.Slider(
347
- label="Number of inference steps (ignored in Turbo mode)",
348
- minimum=1,
349
- maximum=100,
350
- step=1,
351
- value=30,
352
- )
353
-
354
- guidance_scale = gr.Slider(
355
- label="Guidance scale",
356
- minimum=0.0,
357
- maximum=10.0,
358
- step=0.1,
359
- value=2.5,
360
- )
361
-
362
-
363
- with gr.Column():
364
- result = gr.Image(label="Result", show_label=False)
365
 
 
 
 
 
 
 
 
 
 
366
 
367
- gr.Examples(
368
- examples=examples,
369
- fn=infer,
370
- inputs=[prompt],
371
- outputs=[result, seed],
372
- cache_examples=True,
373
- cache_mode="lazy"
374
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
375
 
376
- gr.Examples(
377
- examples=examples_images,
378
- fn=infer,
379
- inputs=[prompt, input_images],
380
- outputs=[result, seed],
381
- cache_examples=True,
382
- cache_mode="lazy"
383
- )
384
 
385
  # Auto-update dimensions when images are uploaded
386
  input_images.upload(
@@ -393,7 +876,8 @@ FLUX.2 [dev] with [Turbo LoRA by fal](https://huggingface.co/fal/FLUX.2-Turbo) -
393
  triggers=[run_button.click, prompt.submit],
394
  fn=infer,
395
  inputs=[prompt, input_images, seed, randomize_seed, width, height, num_inference_steps, guidance_scale, prompt_upsampling, use_turbo],
396
- outputs=[result, seed]
397
  )
398
 
399
- demo.launch(css=css)
 
 
62
  api_name="/encode_text"
63
  )
64
 
 
65
  prompt_embeds = torch.load(result[0])
66
  return prompt_embeds
67
 
 
103
  def upsample_prompt_logic(prompt, image_list):
104
  try:
105
  if image_list and len(image_list) > 0:
 
106
  system_content = SYSTEM_PROMPT_WITH_IMAGES
 
 
107
  user_content = [{"type": "text", "text": prompt}]
108
 
109
  for img in image_list:
 
118
  {"role": "user", "content": user_content}
119
  ]
120
  else:
 
121
  system_content = SYSTEM_PROMPT_TEXT_ONLY
122
  messages = [
123
  {"role": "system", "content": system_content},
 
136
  return prompt
137
 
138
  def update_dimensions_from_image(image_list):
139
+ """Update width/height sliders based on uploaded image aspect ratio."""
 
140
  if image_list is None or len(image_list) == 0:
141
+ return 1024, 1024
142
 
143
+ img = image_list[0][0]
 
144
  img_width, img_height = img.size
145
 
146
  aspect_ratio = img_width / img_height
147
 
148
+ if aspect_ratio >= 1:
149
  new_width = 1024
150
  new_height = int(1024 / aspect_ratio)
151
+ else:
152
  new_height = 1024
153
  new_width = int(1024 * aspect_ratio)
154
 
 
155
  new_width = round(new_width / 8) * 8
156
  new_height = round(new_height / 8) * 8
157
 
 
158
  new_width = max(256, min(1024, new_width))
159
  new_height = max(256, min(1024, new_height))
160
 
161
  return new_width, new_height
162
 
 
163
  def get_duration(prompt_embeds, image_list, width, height, num_inference_steps, guidance_scale, seed, use_turbo, progress=gr.Progress(track_tqdm=True)):
164
  num_images = 0 if image_list is None else len(image_list)
165
  step_duration = 1 + 0.8 * num_images
 
166
  if use_turbo:
167
+ return max(30, 8 * step_duration + 10)
168
  return max(65, num_inference_steps * step_duration + 10)
169
 
170
  @spaces.GPU(duration=get_duration)
171
  def generate_image(prompt_embeds, image_list, width, height, num_inference_steps, guidance_scale, seed, use_turbo, progress=gr.Progress(track_tqdm=True)):
 
172
  prompt_embeds = prompt_embeds.to(device)
173
 
174
  generator = torch.Generator(device=device).manual_seed(seed)
 
182
  "height": height,
183
  }
184
 
 
185
  if use_turbo:
186
  pipe_kwargs["sigmas"] = TURBO_SIGMAS
187
+ pipe_kwargs["num_inference_steps"] = 8
188
  else:
189
  pipe_kwargs["num_inference_steps"] = num_inference_steps
190
 
 
191
  if progress:
192
  progress(0, desc="Starting generation...")
193
 
 
199
  if randomize_seed:
200
  seed = random.randint(0, MAX_SEED)
201
 
 
202
  image_list = None
203
  if input_images is not None and len(input_images) > 0:
204
  image_list = []
205
  for item in input_images:
206
  image_list.append(item[0])
207
 
 
208
  final_prompt = prompt
209
  if prompt_upsampling:
210
  progress(0.05, desc="Upsampling prompt...")
 
212
  print(f"Original Prompt: {prompt}")
213
  print(f"Upsampled Prompt: {final_prompt}")
214
 
 
215
  progress(0.1, desc="Encoding prompt...")
 
216
  prompt_embeds = remote_text_encoder(final_prompt)
217
 
 
218
  progress(0.3, desc="Waiting for GPU...")
219
  image = generate_image(
220
  prompt_embeds,
 
228
  progress
229
  )
230
 
231
+ # 정보 로그 생성
232
+ info_log = f"""✅ GENERATION COMPLETE!
233
+ {'=' * 50}
234
+ 📝 Prompt Info:
235
+ • Original: {prompt[:50]}{'...' if len(prompt) > 50 else ''}
236
+ • Upsampled: {'Yes' if prompt_upsampling else 'No'}
237
+ {'=' * 50}
238
+ ⚙️ Generation Settings:
239
+ • Seed: {seed}
240
+ • Size: {width} x {height}
241
+ • Steps: {'8 (Turbo)' if use_turbo else num_inference_steps}
242
+ • CFG Scale: {guidance_scale}
243
+ • Input Images: {len(image_list) if image_list else 0}
244
+ {'=' * 50}
245
+ 🚀 Mode: {'⚡ TURBO (8 steps)' if use_turbo else '🎨 Standard'}
246
+ {'=' * 50}
247
+ 💾 Image ready to download!"""
248
+
249
+ return image, seed, info_log
250
 
251
  examples = [
252
  ["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"],
 
256
  ]
257
 
258
  examples_images = [
 
259
  ["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"]]
260
  ]
261
 
262
+
263
+ # ============================================
264
+ # 🎨 Comic Classic Theme - Toon Playground
265
+ # ============================================
266
+
267
+ css = """
268
+ /* ===== 🎨 Google Fonts Import ===== */
269
+ @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
270
+
271
+ /* ===== 🎨 Comic Classic 배경 - 빈티지 페이퍼 + 도트 패턴 ===== */
272
+ .gradio-container {
273
+ background-color: #FEF9C3 !important;
274
+ background-image:
275
+ radial-gradient(#1F2937 1px, transparent 1px) !important;
276
+ background-size: 20px 20px !important;
277
+ min-height: 100vh !important;
278
+ font-family: 'Comic Neue', cursive, sans-serif !important;
279
+ }
280
+
281
+ /* ===== 허깅페이스 상단 요소 숨김 ===== */
282
+ .huggingface-space-header,
283
+ #space-header,
284
+ .space-header,
285
+ [class*="space-header"],
286
+ .svelte-1ed2p3z,
287
+ .space-header-badge,
288
+ .header-badge,
289
+ [data-testid="space-header"],
290
+ .svelte-kqij2n,
291
+ .svelte-1ax1toq,
292
+ .embed-container > div:first-child {
293
+ display: none !important;
294
+ visibility: hidden !important;
295
+ height: 0 !important;
296
+ width: 0 !important;
297
+ overflow: hidden !important;
298
+ opacity: 0 !important;
299
+ pointer-events: none !important;
300
+ }
301
+
302
+ /* ===== Footer 완전 숨김 ===== */
303
+ footer,
304
+ .footer,
305
+ .gradio-container footer,
306
+ .built-with,
307
+ [class*="footer"],
308
+ .gradio-footer,
309
+ .main-footer,
310
+ div[class*="footer"],
311
+ .show-api,
312
+ .built-with-gradio,
313
+ a[href*="gradio.app"],
314
+ a[href*="huggingface.co/spaces"] {
315
+ display: none !important;
316
+ visibility: hidden !important;
317
+ height: 0 !important;
318
+ padding: 0 !important;
319
+ margin: 0 !important;
320
+ }
321
+
322
+ /* ===== 메인 컨테이너 ===== */
323
+ #col-container {
324
+ max-width: 1200px;
325
+ margin: 0 auto;
326
+ }
327
+
328
+ /* ===== 🎨 헤더 타이틀 - 코믹 스타일 ===== */
329
+ .header-text h1 {
330
+ font-family: 'Bangers', cursive !important;
331
+ color: #1F2937 !important;
332
+ font-size: 3.5rem !important;
333
+ font-weight: 400 !important;
334
+ text-align: center !important;
335
+ margin-bottom: 0.5rem !important;
336
+ text-shadow:
337
+ 4px 4px 0px #FACC15,
338
+ 6px 6px 0px #1F2937 !important;
339
+ letter-spacing: 3px !important;
340
+ -webkit-text-stroke: 2px #1F2937 !important;
341
+ }
342
+
343
+ /* ===== 🎨 서브타이틀 ===== */
344
+ .subtitle {
345
+ text-align: center !important;
346
+ font-family: 'Comic Neue', cursive !important;
347
+ font-size: 1.2rem !important;
348
+ color: #1F2937 !important;
349
+ margin-bottom: 1.5rem !important;
350
+ font-weight: 700 !important;
351
+ }
352
+
353
+ .subtitle-small {
354
+ text-align: center !important;
355
+ font-family: 'Comic Neue', cursive !important;
356
+ font-size: 1rem !important;
357
+ color: #6B7280 !important;
358
+ margin-bottom: 1rem !important;
359
+ font-weight: 400 !important;
360
+ }
361
+
362
+ /* ===== 🎨 카드/패널 - 만화 프레임 스타일 ===== */
363
+ .gr-panel,
364
+ .gr-box,
365
+ .gr-form,
366
+ .block,
367
+ .gr-group {
368
+ background: #FFFFFF !important;
369
+ border: 3px solid #1F2937 !important;
370
+ border-radius: 8px !important;
371
+ box-shadow: 6px 6px 0px #1F2937 !important;
372
+ transition: all 0.2s ease !important;
373
+ }
374
+
375
+ .gr-panel:hover,
376
+ .block:hover {
377
+ transform: translate(-2px, -2px) !important;
378
+ box-shadow: 8px 8px 0px #1F2937 !important;
379
+ }
380
+
381
+ /* ===== 🎨 입력 필드 (Textbox) ===== */
382
+ textarea,
383
+ input[type="text"],
384
+ input[type="number"] {
385
+ background: #FFFFFF !important;
386
+ border: 3px solid #1F2937 !important;
387
+ border-radius: 8px !important;
388
+ color: #1F2937 !important;
389
+ font-family: 'Comic Neue', cursive !important;
390
+ font-size: 1rem !important;
391
+ font-weight: 700 !important;
392
+ transition: all 0.2s ease !important;
393
+ }
394
+
395
+ textarea:focus,
396
+ input[type="text"]:focus,
397
+ input[type="number"]:focus {
398
+ border-color: #3B82F6 !important;
399
+ box-shadow: 4px 4px 0px #3B82F6 !important;
400
+ outline: none !important;
401
+ }
402
+
403
+ textarea::placeholder {
404
+ color: #9CA3AF !important;
405
+ font-weight: 400 !important;
406
+ }
407
+
408
+ /* ===== 🎨 프롬프트 입력창 특별 스타일 ===== */
409
+ .prompt-input textarea {
410
+ background: #FFFBEB !important;
411
+ border: 4px solid #F59E0B !important;
412
+ border-radius: 12px !important;
413
+ font-size: 1.1rem !important;
414
+ padding: 12px !important;
415
+ box-shadow: 4px 4px 0px #1F2937 !important;
416
+ }
417
+
418
+ .prompt-input textarea:focus {
419
+ border-color: #3B82F6 !important;
420
+ box-shadow: 6px 6px 0px #3B82F6 !important;
421
+ }
422
+
423
+ /* ===== 🎨 Primary 버튼 - 코믹 블루 ===== */
424
+ .gr-button-primary,
425
+ button.primary,
426
+ .gr-button.primary,
427
+ .generate-btn {
428
+ background: #3B82F6 !important;
429
+ border: 3px solid #1F2937 !important;
430
+ border-radius: 8px !important;
431
+ color: #FFFFFF !important;
432
+ font-family: 'Bangers', cursive !important;
433
+ font-weight: 400 !important;
434
+ font-size: 1.3rem !important;
435
+ letter-spacing: 2px !important;
436
+ padding: 14px 28px !important;
437
+ box-shadow: 5px 5px 0px #1F2937 !important;
438
+ transition: all 0.1s ease !important;
439
+ text-shadow: 1px 1px 0px #1F2937 !important;
440
+ }
441
+
442
+ .gr-button-primary:hover,
443
+ button.primary:hover,
444
+ .gr-button.primary:hover,
445
+ .generate-btn:hover {
446
+ background: #2563EB !important;
447
+ transform: translate(-2px, -2px) !important;
448
+ box-shadow: 7px 7px 0px #1F2937 !important;
449
+ }
450
+
451
+ .gr-button-primary:active,
452
+ button.primary:active,
453
+ .gr-button.primary:active,
454
+ .generate-btn:active {
455
+ transform: translate(3px, 3px) !important;
456
+ box-shadow: 2px 2px 0px #1F2937 !important;
457
+ }
458
+
459
+ /* ===== 🎨 Secondary 버튼 - 코믹 레드 ===== */
460
+ .gr-button-secondary,
461
+ button.secondary {
462
+ background: #EF4444 !important;
463
+ border: 3px solid #1F2937 !important;
464
+ border-radius: 8px !important;
465
+ color: #FFFFFF !important;
466
+ font-family: 'Bangers', cursive !important;
467
+ font-weight: 400 !important;
468
+ font-size: 1.1rem !important;
469
+ letter-spacing: 1px !important;
470
+ box-shadow: 4px 4px 0px #1F2937 !important;
471
+ transition: all 0.1s ease !important;
472
+ text-shadow: 1px 1px 0px #1F2937 !important;
473
+ }
474
+
475
+ .gr-button-secondary:hover,
476
+ button.secondary:hover {
477
+ background: #DC2626 !important;
478
+ transform: translate(-2px, -2px) !important;
479
+ box-shadow: 6px 6px 0px #1F2937 !important;
480
+ }
481
+
482
+ /* ===== 🎨 로그 출력 영역 ===== */
483
+ .info-log textarea {
484
+ background: #1F2937 !important;
485
+ color: #10B981 !important;
486
+ font-family: 'Courier New', monospace !important;
487
+ font-size: 0.9rem !important;
488
+ font-weight: 400 !important;
489
+ border: 3px solid #10B981 !important;
490
+ border-radius: 8px !important;
491
+ box-shadow: 4px 4px 0px #10B981 !important;
492
  }
493
+
494
+ /* ===== 🎨 이미지 업로드/갤러리 영역 ===== */
495
+ .image-upload,
496
+ .gr-gallery {
497
+ border: 4px dashed #3B82F6 !important;
498
+ border-radius: 12px !important;
499
+ background: #EFF6FF !important;
500
+ transition: all 0.2s ease !important;
501
+ }
502
+
503
+ .image-upload:hover,
504
+ .gr-gallery:hover {
505
+ border-color: #EF4444 !important;
506
+ background: #FEF2F2 !important;
507
+ }
508
+
509
+ .gr-gallery .thumbnail-item {
510
+ border: 3px solid #1F2937 !important;
511
+ border-radius: 6px !important;
512
+ transition: all 0.2s ease !important;
513
+ }
514
+
515
+ .gr-gallery .thumbnail-item:hover {
516
+ transform: scale(1.05) !important;
517
+ box-shadow: 4px 4px 0px #3B82F6 !important;
518
+ }
519
+
520
+ /* ===== 🎨 아코디언 - 말풍선 스타일 ===== */
521
+ .gr-accordion {
522
+ background: #FACC15 !important;
523
+ border: 3px solid #1F2937 !important;
524
+ border-radius: 8px !important;
525
+ box-shadow: 4px 4px 0px #1F2937 !important;
526
+ }
527
+
528
+ .gr-accordion-header {
529
+ color: #1F2937 !important;
530
+ font-family: 'Comic Neue', cursive !important;
531
+ font-weight: 700 !important;
532
+ font-size: 1.1rem !important;
533
+ }
534
+
535
+ /* ===== 🎨 결과 이미지 영역 ===== */
536
+ .result-image,
537
+ .gr-image {
538
+ border: 4px solid #1F2937 !important;
539
+ border-radius: 8px !important;
540
+ box-shadow: 8px 8px 0px #1F2937 !important;
541
+ overflow: hidden !important;
542
+ background: #FFFFFF !important;
543
+ }
544
+
545
+ /* ===== 🎨 슬라이더 스타일 ===== */
546
+ input[type="range"] {
547
+ accent-color: #3B82F6 !important;
548
+ }
549
+
550
+ .gr-slider {
551
+ background: #FFFFFF !important;
552
+ }
553
+
554
+ /* ===== 🎨 체크박스 스타일 ===== */
555
+ input[type="checkbox"] {
556
+ accent-color: #3B82F6 !important;
557
+ width: 20px !important;
558
+ height: 20px !important;
559
+ border: 2px solid #1F2937 !important;
560
+ }
561
+
562
+ /* ===== 🎨 라벨 스타일 ===== */
563
+ label,
564
+ .gr-input-label,
565
+ .gr-block-label {
566
+ color: #1F2937 !important;
567
+ font-family: 'Comic Neue', cursive !important;
568
+ font-weight: 700 !important;
569
+ font-size: 1rem !important;
570
+ }
571
+
572
+ span.gr-label {
573
+ color: #1F2937 !important;
574
+ }
575
+
576
+ /* ===== 🎨 정보 텍스트 ===== */
577
+ .gr-info,
578
+ .info {
579
+ color: #6B7280 !important;
580
+ font-family: 'Comic Neue', cursive !important;
581
+ font-size: 0.9rem !important;
582
+ }
583
+
584
+ /* ===== 🎨 프로그레스 바 ===== */
585
+ .progress-bar,
586
+ .gr-progress-bar {
587
+ background: #3B82F6 !important;
588
+ border: 2px solid #1F2937 !important;
589
+ border-radius: 4px !important;
590
+ }
591
+
592
+ /* ===== 🎨 Examples 섹션 ===== */
593
+ .gr-examples {
594
+ background: #FFFFFF !important;
595
+ border: 3px solid #1F2937 !important;
596
+ border-radius: 8px !important;
597
+ box-shadow: 6px 6px 0px #1F2937 !important;
598
+ padding: 1rem !important;
599
+ }
600
+
601
+ .gr-examples .gr-sample {
602
+ border: 2px solid #1F2937 !important;
603
+ border-radius: 6px !important;
604
+ transition: all 0.2s ease !important;
605
+ }
606
+
607
+ .gr-examples .gr-sample:hover {
608
+ transform: translate(-2px, -2px) !important;
609
+ box-shadow: 4px 4px 0px #3B82F6 !important;
610
+ }
611
+
612
+ /* ===== 🎨 Turbo 뱃지 스타일 ===== */
613
+ .turbo-badge {
614
+ display: inline-block;
615
+ background: linear-gradient(135deg, #F59E0B 0%, #EF4444 100%) !important;
616
+ color: #FFFFFF !important;
617
+ font-family: 'Bangers', cursive !important;
618
+ font-size: 1rem !important;
619
+ padding: 4px 12px !important;
620
+ border: 2px solid #1F2937 !important;
621
+ border-radius: 20px !important;
622
+ box-shadow: 2px 2px 0px #1F2937 !important;
623
+ margin-left: 8px !important;
624
+ }
625
+
626
+ /* ===== 🎨 스크롤바 - 코믹 스타일 ===== */
627
+ ::-webkit-scrollbar {
628
+ width: 12px;
629
+ height: 12px;
630
+ }
631
+
632
+ ::-webkit-scrollbar-track {
633
+ background: #FEF9C3;
634
+ border: 2px solid #1F2937;
635
+ }
636
+
637
+ ::-webkit-scrollbar-thumb {
638
+ background: #3B82F6;
639
+ border: 2px solid #1F2937;
640
+ border-radius: 0px;
641
+ }
642
+
643
+ ::-webkit-scrollbar-thumb:hover {
644
+ background: #EF4444;
645
+ }
646
+
647
+ /* ===== 🎨 선택 하이라이트 ===== */
648
+ ::selection {
649
+ background: #FACC15;
650
+ color: #1F2937;
651
+ }
652
+
653
+ /* ===== 🎨 링크 스타일 ===== */
654
+ a {
655
+ color: #3B82F6 !important;
656
+ text-decoration: none !important;
657
+ font-weight: 700 !important;
658
+ }
659
+
660
+ a:hover {
661
+ color: #EF4444 !important;
662
+ }
663
+
664
+ /* ===== 🎨 Row/Column 간격 ===== */
665
+ .gr-row {
666
+ gap: 1.5rem !important;
667
+ }
668
+
669
+ .gr-column {
670
+ gap: 1rem !important;
671
+ }
672
+
673
+ /* ===== 반응형 조정 ===== */
674
+ @media (max-width: 768px) {
675
+ .header-text h1 {
676
+ font-size: 2.2rem !important;
677
+ text-shadow:
678
+ 3px 3px 0px #FACC15,
679
+ 4px 4px 0px #1F2937 !important;
680
+ }
681
+
682
+ .gr-button-primary,
683
+ button.primary {
684
+ padding: 12px 20px !important;
685
+ font-size: 1.1rem !important;
686
+ }
687
+
688
+ .gr-panel,
689
+ .block {
690
+ box-shadow: 4px 4px 0px #1F2937 !important;
691
+ }
692
+ }
693
+
694
+ /* ===== 🎨 다크모드 비활성화 (코믹은 밝아야 함) ===== */
695
+ @media (prefers-color-scheme: dark) {
696
+ .gradio-container {
697
+ background-color: #FEF9C3 !important;
698
+ }
699
  }
700
  """
701
 
702
+ # Build the Gradio interface
703
+ with gr.Blocks(fill_height=True, css=css) as demo:
704
+
705
+ # HOME Badge
706
+ gr.HTML("""
707
+ <div style="text-align: center; margin: 20px 0 10px 0;">
708
+ <a href="https://www.humangen.ai" target="_blank" style="text-decoration: none;">
709
+ <img src="https://img.shields.io/static/v1?label=🏠 HOME&message=HUMANGEN.AI&color=0000ff&labelColor=ffcc00&style=for-the-badge" alt="HOME">
710
+ </a>
711
+ </div>
712
+ """)
713
+
714
+ # Header Title
715
+ gr.Markdown(
716
+ """
717
+ # ⚡ FLUX.2 TURBO IMAGE GENERATOR 🎨
718
+ """,
719
+ elem_classes="header-text"
720
+ )
721
 
722
+ gr.Markdown(
723
+ """
724
+ <p class="subtitle">🚀 32B Rectified Flow Model Generate, Edit & Combine Images in 8 Steps! ✨</p>
725
+ <p class="subtitle-small">Powered by <a href="https://huggingface.co/black-forest-labs/FLUX.2-dev" target="_blank">FLUX.2 [dev]</a> with <a href="https://huggingface.co/fal/FLUX.2-Turbo" target="_blank">Turbo LoRA by fal</a></p>
726
+ """,
727
+ )
728
+
729
+ with gr.Row(equal_height=False):
730
+ # Left column - Input
731
+ with gr.Column(scale=1, min_width=400):
732
+ prompt = gr.Textbox(
733
+ label="✏️ Enter Your Prompt",
734
+ placeholder="Describe the image you want to create...",
735
+ lines=3,
736
+ max_lines=5,
737
+ elem_classes="prompt-input"
738
+ )
739
+
740
+ run_button = gr.Button(
741
+ "⚡ GENERATE IMAGE! 🎨",
742
+ variant="primary",
743
+ size="lg",
744
+ elem_classes="generate-btn"
745
+ )
746
+
747
+ with gr.Accordion("🖼️ Input Images (Optional)", open=True):
748
+ input_images = gr.Gallery(
749
+ label="Upload reference images for editing/combining",
750
+ type="pil",
751
+ columns=3,
752
+ rows=1,
753
+ elem_classes="image-upload"
754
+ )
755
+
756
+ with gr.Accordion("⚙️ Advanced Settings", open=False):
757
+ use_turbo = gr.Checkbox(
758
+ label="⚡ Use Turbo Mode (8 steps)",
759
+ value=True,
760
+ info="Enable Turbo LoRA for fast 8-step generation",
761
+ visible=False
762
+ )
763
+
764
+ prompt_upsampling = gr.Checkbox(
765
+ label="🔮 Prompt Upsampling",
766
+ value=False,
767
+ info="Automatically enhance the prompt using a VLM"
768
+ )
769
+
770
+ seed = gr.Slider(
771
+ label="🎲 Seed",
772
+ minimum=0,
773
+ maximum=MAX_SEED,
774
+ step=1,
775
+ value=0,
776
+ )
777
+
778
+ randomize_seed = gr.Checkbox(label="🔀 Randomize seed", value=True)
779
+
780
  with gr.Row():
781
+ width = gr.Slider(
782
+ label="📐 Width",
783
+ minimum=256,
784
+ maximum=MAX_IMAGE_SIZE,
785
+ step=8,
786
+ value=1024,
 
787
  )
788
 
789
+ height = gr.Slider(
790
+ label="📏 Height",
791
+ minimum=256,
792
+ maximum=MAX_IMAGE_SIZE,
793
+ step=8,
794
+ value=1024,
 
 
795
  )
796
 
797
+ with gr.Row():
798
+ num_inference_steps = gr.Slider(
799
+ label="🔄 Inference Steps (ignored in Turbo)",
800
+ minimum=1,
801
+ maximum=100,
 
 
 
 
 
 
 
 
 
 
 
 
 
802
  step=1,
803
+ value=30,
804
  )
805
 
806
+ guidance_scale = gr.Slider(
807
+ label="🎯 Guidance Scale",
808
+ minimum=0.0,
809
+ maximum=10.0,
810
+ step=0.1,
811
+ value=2.5,
812
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
813
 
814
+ with gr.Accordion("📜 Generation Log", open=True):
815
+ info_log = gr.Textbox(
816
+ label="",
817
+ placeholder="Enter a prompt and click generate to see info...",
818
+ lines=14,
819
+ max_lines=20,
820
+ interactive=False,
821
+ elem_classes="info-log"
822
+ )
823
 
824
+ # Right column - Output
825
+ with gr.Column(scale=1, min_width=400):
826
+ result = gr.Image(
827
+ label="🖼️ Generated Image",
828
+ show_label=True,
829
+ height=550,
830
+ elem_classes="result-image"
831
+ )
832
+
833
+ gr.Markdown(
834
+ """
835
+ <p style="text-align: center; margin-top: 15px; font-weight: 700; color: #1F2937;">
836
+ 💡 Right-click on the image to save, or use the download button!
837
+ </p>
838
+ """
839
+ )
840
+
841
+ # Examples Section
842
+ gr.Markdown(
843
+ """
844
+ <p style="text-align: center; margin: 25px 0 15px 0; font-family: 'Bangers', cursive; font-size: 1.5rem; color: #1F2937;">
845
+ 🌟 TRY THESE EXAMPLES! 🌟
846
+ </p>
847
+ """
848
+ )
849
+
850
+ gr.Examples(
851
+ examples=examples,
852
+ fn=infer,
853
+ inputs=[prompt],
854
+ outputs=[result, seed, info_log],
855
+ cache_examples=True,
856
+ cache_mode="lazy"
857
+ )
858
 
859
+ gr.Examples(
860
+ examples=examples_images,
861
+ fn=infer,
862
+ inputs=[prompt, input_images],
863
+ outputs=[result, seed, info_log],
864
+ cache_examples=True,
865
+ cache_mode="lazy"
866
+ )
867
 
868
  # Auto-update dimensions when images are uploaded
869
  input_images.upload(
 
876
  triggers=[run_button.click, prompt.submit],
877
  fn=infer,
878
  inputs=[prompt, input_images, seed, randomize_seed, width, height, num_inference_steps, guidance_scale, prompt_upsampling, use_turbo],
879
+ outputs=[result, seed, info_log]
880
  )
881
 
882
+ if __name__ == "__main__":
883
+ demo.launch()