seawolf2357 commited on
Commit
173d64f
Β·
verified Β·
1 Parent(s): 77b129f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +275 -488
app.py CHANGED
@@ -1,546 +1,333 @@
1
  import gradio as gr
2
- import numpy as np
3
- import random
4
- import torch
5
- import spaces
6
- from PIL import Image
7
- from diffusers import QwenImageEditPipeline
8
- from diffusers.utils import is_xformers_available
9
  import os
 
 
 
 
 
10
  import base64
11
- import json
12
- from huggingface_hub import InferenceClient
13
- import logging
14
 
15
- #############################
16
- os.environ.setdefault('GRADIO_ANALYTICS_ENABLED', 'False')
17
- os.environ.setdefault('HF_HUB_DISABLE_TELEMETRY', '1')
18
- logging.basicConfig(level=logging.DEBUG)
19
- logger = logging.getLogger(__name__)
20
- #############################
21
 
22
- def get_caption_language(prompt):
23
- """Detects if the prompt contains Chinese characters."""
24
- ranges = [
25
- ('\u4e00', '\u9fff'), # CJK Unified Ideographs
26
- ]
27
- for char in prompt:
28
- if any(start <= char <= end for start, end in ranges):
29
- return 'zh'
30
- return 'en'
31
-
32
- def polish_prompt(original_prompt, system_prompt, hf_token):
33
  """
34
- Rewrites the prompt using a Hugging Face InferenceClient.
35
- Requires user-provided HF token for API access.
36
  """
37
- if not hf_token or not hf_token.strip():
38
- gr.Warning("HF Token is required for prompt rewriting but was not provided!")
39
- return original_prompt
40
- client = InferenceClient(
41
- provider="cerebras",
42
- api_key=hf_token,
43
- )
44
- messages = [
45
- {"role": "system", "content": system_prompt},
46
- {"role": "user", "content": original_prompt}
47
- ]
48
  try:
49
- completion = client.chat.completions.create(
50
- model="Qwen/Qwen3-235B-A22B-Instruct-2507",
51
- messages=messages,
52
- max_tokens=512,
 
 
 
 
 
 
 
53
  )
54
- polished_prompt = completion.choices[0].message.content
55
- polished_prompt = polished_prompt.strip().replace("\n", " ")
56
- return polished_prompt
57
- except Exception as e:
58
- print(f"Error during Hugging Face API call: {e}")
59
- gr.Warning("Failed to rewrite prompt. Using original.")
60
- return original_prompt
61
-
62
- SYSTEM_PROMPT_EDIT = '''
63
- # Edit Instruction Rewriter
64
- You are a professional edit instruction rewriter. Your task is to generate a precise, concise, and visually achievable instruction based on the user's intent and the input image.
65
- ## 1. General Principles
66
- - Keep the rewritten instruction **concise** and clear.
67
- - Avoid contradictions, vagueness, or unachievable instructions.
68
- - Maintain the core logic of the original instruction; only enhance clarity and feasibility.
69
- - Ensure new added elements or modifications align with the image's original context and art style.
70
- ## 2. Task Types
71
- ### Add, Delete, Replace:
72
- - When the input is detailed, only refine grammar and clarity.
73
- - For vague instructions, infer minimal but sufficient details.
74
- - For replacement, use the format: `"Replace X with Y"`.
75
- ### Text Editing (e.g., text replacement):
76
- - Enclose text content in quotes, e.g., `Replace "abc" with "xyz"`.
77
- - Preserving the original structure and languageβ€”**do not translate** or alter style.
78
- ### Human Editing (e.g., change a person's face/hair):
79
- - Preserve core visual identity (gender, ethnic features).
80
- - Describe expressions in subtle and natural terms.
81
- - Maintain key clothing or styling details unless explicitly replaced.
82
- ### Style Transformation:
83
- - If a style is specified, e.g., `Disco style`, rewrite it to encapsulate the essential visual traits.
84
- - Use a fixed template for **coloring/restoration**:
85
- `"Restore old photograph, remove scratches, reduce noise, enhance details, high resolution, realistic, natural skin tones, clear facial features, no distortion, vintage photo restoration"`
86
- if applicable.
87
- ## 4. Output Format
88
- Please provide the rewritten instruction in a clean `json` format as:
89
- {
90
- "Rewritten": "..."
91
- }
92
- '''
93
-
94
- dtype = torch.bfloat16
95
- device = "cuda" if torch.cuda.is_available() else "cpu"
96
- pipe = QwenImageEditPipeline.from_pretrained("Qwen/Qwen-Image-Edit", torch_dtype=dtype).to(device)
97
-
98
- # Load LoRA weights for acceleration
99
- pipe.load_lora_weights(
100
- "lightx2v/Qwen-Image-Lightning", weight_name="Qwen-Image-Lightning-8steps-V1.1.safetensors"
101
- )
102
- pipe.fuse_lora()
103
-
104
- if is_xformers_available():
105
- pipe.enable_xformers_memory_efficient_attention()
106
- else:
107
- print("xformers not available or failed to load.")
108
-
109
- @spaces.GPU(duration=60)
110
- def infer(
111
- image,
112
- prompt,
113
- seed=42,
114
- randomize_seed=False,
115
- true_guidance_scale=1.0,
116
- num_inference_steps=8,
117
- rewrite_prompt=False,
118
- hf_token="",
119
- num_images_per_prompt=1,
120
- progress=gr.Progress(track_tqdm=True),
121
- ):
122
- """
123
- Requires user-provided HF token for prompt rewriting.
124
- """
125
- original_prompt = prompt # Save original prompt for display
126
- negative_prompt = " "
127
- prompt_info = "" # Initialize info text
128
 
129
- # Handle prompt rewriting with status messages
130
- if rewrite_prompt:
131
- if not hf_token.strip():
132
- gr.Warning("HF Token is required for prompt rewriting but was not provided!")
133
- prompt_info = f"""<div class="prompt-info-box warning">
134
- <h3>⚠️ Prompt Rewriting Skipped</h3>
135
- <p><strong>Original:</strong> {original_prompt}</p>
136
- <p class="note">HF Token required for enhancement</p>
137
- </div>"""
138
- rewritten_prompt = original_prompt
139
- else:
140
- try:
141
- rewritten_prompt = polish_prompt(original_prompt, SYSTEM_PROMPT_EDIT, hf_token)
142
- prompt_info = f"""<div class="prompt-info-box success">
143
- <h3>✨ Enhanced Successfully</h3>
144
- <p><strong>Original:</strong> {original_prompt}</p>
145
- <p><strong>Enhanced:</strong> {rewritten_prompt}</p>
146
- </div>"""
147
- except Exception as e:
148
- gr.Warning(f"Prompt rewriting failed: {str(e)}")
149
- rewritten_prompt = original_prompt
150
- prompt_info = f"""<div class="prompt-info-box error">
151
- <h3>❌ Enhancement Failed</h3>
152
- <p><strong>Original:</strong> {original_prompt}</p>
153
- <p class="note">Error: {str(e)}</p>
154
- </div>"""
155
- else:
156
- rewritten_prompt = original_prompt
157
- prompt_info = f"""<div class="prompt-info-box default">
158
- <h3>πŸ“ Original Prompt</h3>
159
- <p>{original_prompt}</p>
160
- </div>"""
161
 
162
- # Generate images
163
- if randomize_seed:
164
- seed = random.randint(0, MAX_SEED)
165
- generator = torch.Generator(device=device).manual_seed(seed)
 
 
 
 
 
 
 
 
 
166
 
167
- edited_images = pipe(
168
- image,
169
- prompt=rewritten_prompt,
170
- negative_prompt=negative_prompt,
171
- num_inference_steps=num_inference_steps,
172
- generator=generator,
173
- true_cfg_scale=true_guidance_scale,
174
- num_images_per_prompt=num_images_per_prompt,
175
- ).images
176
 
177
- return edited_images, seed, prompt_info
178
-
179
- MAX_SEED = np.iinfo(np.int32).max
180
-
181
- examples = [
182
- "Replace the cat with a friendly golden retriever. Make it look happier, and add more background details.",
183
- "Add text 'Qwen - AI for image editing' in Chinese at the bottom center with a small shadow.",
184
- "Change the style to 1970s vintage, add old photo effect, restore any scratches on the wall or window.",
185
- "Remove the blue sky and replace it with a dark night cityscape.",
186
- """Replace "Qwen" with "ι€šδΉ‰" in the Image. Ensure Chinese font is used for "ι€šδΉ‰" and position it to the top left with a light heading-style font."""
187
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
- # Custom CSS for enhanced visual design
190
- custom_css = """
191
- /* Gradient background */
192
  .gradio-container {
193
- background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #fecfef 75%, #fecfef 100%);
 
194
  min-height: 100vh;
195
  }
196
-
197
- /* Main container styling */
198
- .container {
199
- max-width: 1400px !important;
200
- margin: 0 auto !important;
201
- padding: 2rem !important;
202
  }
203
-
204
- /* Card-like sections */
205
- .gr-box {
206
- background: rgba(255, 255, 255, 0.95) !important;
207
- backdrop-filter: blur(10px) !important;
208
- border-radius: 20px !important;
209
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important;
210
- border: 1px solid rgba(255, 255, 255, 0.5) !important;
211
- padding: 1.5rem !important;
212
- margin-bottom: 1.5rem !important;
213
  }
214
-
215
- /* Header styling */
216
- h1 {
217
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
218
- -webkit-background-clip: text;
219
- -webkit-text-fill-color: transparent;
220
- background-clip: text;
221
- font-size: 3rem !important;
222
- font-weight: 800 !important;
223
  text-align: center;
224
- margin-bottom: 0.5rem !important;
225
- text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
 
226
  }
227
-
228
- h2 {
229
- color: #4a5568 !important;
230
- font-size: 1.5rem !important;
231
- font-weight: 600 !important;
232
- margin-bottom: 1rem !important;
233
  }
234
-
235
- /* Button styling */
236
  .gr-button-primary {
237
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
238
  border: none !important;
239
- color: white !important;
240
- font-weight: 600 !important;
241
  font-size: 1.1rem !important;
242
- padding: 0.8rem 2rem !important;
243
- border-radius: 12px !important;
244
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
245
- transition: all 0.3s ease !important;
 
 
 
246
  }
247
-
248
  .gr-button-primary:hover {
249
- transform: translateY(-2px) !important;
250
- box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5) !important;
251
- }
252
-
253
- /* Input fields styling */
254
- .gr-input, .gr-text-input, .gr-slider, .gr-dropdown {
255
- border-radius: 10px !important;
256
- border: 2px solid #e2e8f0 !important;
257
- background: white !important;
258
- transition: all 0.3s ease !important;
259
  }
260
-
261
- .gr-input:focus, .gr-text-input:focus {
262
- border-color: #667eea !important;
263
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
 
 
 
264
  }
265
-
266
- /* Accordion styling */
267
- .gr-accordion {
268
- background: rgba(255, 255, 255, 0.8) !important;
269
- border-radius: 12px !important;
270
- border: 1px solid rgba(102, 126, 234, 0.2) !important;
271
- overflow: hidden !important;
272
  }
273
-
274
- /* Gallery styling */
275
- .gr-gallery {
276
- border-radius: 12px !important;
277
- overflow: hidden !important;
278
- }
279
-
280
- /* Prompt info boxes */
281
- .prompt-info-box {
282
- padding: 1.5rem;
283
- border-radius: 12px;
284
- margin: 1rem 0;
285
- animation: fadeIn 0.5s ease;
286
- }
287
-
288
- .prompt-info-box h3 {
289
- margin: 0 0 0.75rem 0;
290
- font-size: 1.2rem;
291
- font-weight: 600;
292
- }
293
-
294
- .prompt-info-box p {
295
- margin: 0.5rem 0;
296
- line-height: 1.6;
297
- }
298
-
299
- .prompt-info-box.success {
300
- background: linear-gradient(135deg, #d4f4dd 0%, #e3f9e5 100%);
301
- border-left: 4px solid #48bb78;
302
  }
303
-
304
- .prompt-info-box.warning {
305
- background: linear-gradient(135deg, #fef5e7 0%, #fff9ec 100%);
306
- border-left: 4px solid #f6ad55;
 
307
  }
308
-
309
- .prompt-info-box.error {
310
- background: linear-gradient(135deg, #fed7d7 0%, #fee5e5 100%);
311
- border-left: 4px solid #fc8181;
312
  }
313
-
314
- .prompt-info-box.default {
315
- background: linear-gradient(135deg, #e6f3ff 0%, #f0f7ff 100%);
316
- border-left: 4px solid #667eea;
 
 
 
317
  }
318
-
319
- .prompt-info-box .note {
 
320
  font-size: 0.9rem;
321
- color: #718096;
322
- font-style: italic;
323
- }
324
-
325
- /* Checkbox styling */
326
- .gr-checkbox {
327
- background: white !important;
328
- border-radius: 8px !important;
329
- padding: 0.5rem !important;
330
- }
331
-
332
- /* Token input field */
333
- input[type="password"] {
334
- font-family: monospace !important;
335
- letter-spacing: 0.05em !important;
336
- }
337
-
338
- /* Info badges */
339
- .gr-markdown p {
340
- color: #4a5568;
341
- line-height: 1.6;
342
- }
343
-
344
- .gr-markdown a {
345
- color: #667eea !important;
346
- text-decoration: none !important;
347
- font-weight: 500 !important;
348
- transition: color 0.3s ease !important;
349
- }
350
-
351
- .gr-markdown a:hover {
352
- color: #764ba2 !important;
353
- text-decoration: underline !important;
354
  }
355
-
356
- /* Animation */
357
- @keyframes fadeIn {
358
- from {
359
- opacity: 0;
360
- transform: translateY(10px);
361
- }
362
- to {
363
- opacity: 1;
364
- transform: translateY(0);
365
- }
366
  }
367
-
368
- /* Slider styling */
369
- .gr-slider input[type="range"] {
370
- background: linear-gradient(90deg, #667eea 0%, #764ba2 100%) !important;
371
  }
372
-
373
- /* Group styling */
374
- .gr-group {
375
- background: rgba(249, 250, 251, 0.8) !important;
376
- border-radius: 12px !important;
377
- padding: 1rem !important;
378
- margin-top: 1rem !important;
379
  }
380
-
381
- /* Loading spinner customization */
382
- .gr-loading {
383
- color: #667eea !important;
384
  }
385
-
386
- /* Example buttons */
387
- .gr-examples button {
388
- background: white !important;
389
- border: 2px solid #e2e8f0 !important;
390
- border-radius: 8px !important;
391
- padding: 0.5rem 1rem !important;
392
- transition: all 0.3s ease !important;
393
  }
394
-
395
- .gr-examples button:hover {
396
- border-color: #667eea !important;
397
- background: rgba(102, 126, 234, 0.05) !important;
398
  }
399
  """
400
 
401
-
402
- with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
403
- gr.Markdown("# 🎨 Nano-Banana")
404
- gr.Markdown("✨ **Ultra-fast 8-step image editing with AI-powered prompt enhancement**")
405
- gr.Markdown("πŸ” **Secure prompt rewriting with your [Hugging Face token](https://huggingface.co/settings/tokens)**")
406
-
407
- # λ°°μ§€λ₯Ό κ°€μš΄λ° μ •λ ¬ν•˜μ—¬ λ‚˜λž€νžˆ 배치
408
- gr.HTML("""
409
- <div style="display: flex; justify-content: center; align-items: center; gap: 20px; margin: 20px 0;">
410
- <a href="https://huggingface.co/spaces/Heartsync/Nano-Banana" target="_blank">
411
- <img src="https://img.shields.io/static/v1?label=OPEN%20NANO-BANANA&message=Image%20EDITOR&color=%230000ff&labelColor=%23800080&logo=huggingface&logoColor=white&style=for-the-badge" alt="badge">
412
- </a>
413
-
414
- </div>
415
- """)
416
-
417
- with gr.Row():
418
- with gr.Column(scale=1):
419
- with gr.Group():
420
- input_image = gr.Image(
421
- label="πŸ“Έ Input Image",
422
- type="pil",
423
- elem_classes="gr-box"
424
- )
425
- prompt = gr.Text(
426
- label="✏️ Edit Instruction",
427
- placeholder="e.g. Add a dog to the right side, change the sky to sunset...",
 
428
  lines=3,
429
- elem_classes="gr-box"
 
430
  )
431
 
432
- with gr.Accordion("βš™οΈ Advanced Settings", open=False):
433
- seed = gr.Slider(
434
- label="Seed",
435
- minimum=0,
436
- maximum=MAX_SEED,
437
- step=1,
438
- value=0
439
  )
440
- randomize_seed = gr.Checkbox(label="🎲 Randomize Seed", value=True)
441
-
442
- with gr.Row():
443
- true_guidance_scale = gr.Slider(
444
- label="Guidance Scale",
445
- minimum=1.0,
446
- maximum=5.0,
447
- step=0.1,
448
- value=4.0
449
- )
450
- num_inference_steps = gr.Slider(
451
- label="Inference Steps",
452
- minimum=4,
453
- maximum=16,
454
- step=1,
455
- value=8
456
- )
457
-
458
- num_images_per_prompt = gr.Slider(
459
- label="Images per Prompt",
460
- minimum=1,
461
- maximum=4,
462
- step=1,
463
- value=1
464
  )
465
 
466
- run_button = gr.Button("πŸš€ Generate Edit", variant="primary", size="lg")
467
-
468
- with gr.Column(scale=1):
469
- result = gr.Gallery(
470
- label="πŸ–ΌοΈ Output Images",
471
- show_label=True,
472
- columns=2,
473
- rows=2,
474
- elem_classes="gr-box"
475
- )
476
-
477
- # Prompt display component
478
- prompt_info = gr.HTML(visible=False)
479
-
480
- with gr.Group():
481
- rewrite_toggle = gr.Checkbox(
482
- label="πŸ€– Enable AI Prompt Enhancement",
483
- value=False,
484
- interactive=True
485
  )
486
- hf_token_input = gr.Textbox(
487
- label="πŸ”‘ Hugging Face API Token",
488
- type="password",
489
- placeholder="hf_xxxxxxxxxxxxxxxx",
490
- visible=False,
491
- info="Your token is secure and only used for API calls. Get yours from HuggingFace settings.",
492
- elem_classes="gr-box"
 
493
  )
494
 
495
- def toggle_token_visibility(checked):
496
- return gr.update(visible=checked)
497
-
498
- rewrite_toggle.change(
499
- toggle_token_visibility,
500
- inputs=[rewrite_toggle],
501
- outputs=[hf_token_input]
502
  )
503
 
504
- # Examples section
505
- gr.Examples(
506
- examples=examples,
507
- inputs=prompt,
508
- label="πŸ’‘ Example Prompts"
509
- )
510
-
511
- gr.on(
512
- triggers=[run_button.click, prompt.submit],
513
- fn=infer,
514
- inputs=[
515
- input_image,
516
- prompt,
517
- seed,
518
- randomize_seed,
519
- true_guidance_scale,
520
- num_inference_steps,
521
- rewrite_toggle,
522
- hf_token_input,
523
- num_images_per_prompt
524
- ],
525
- outputs=[result, seed, prompt_info]
526
- )
527
-
528
- # Show prompt info box after processing
529
- def set_prompt_visible():
530
- return gr.update(visible=True)
531
-
532
- run_button.click(
533
- fn=set_prompt_visible,
534
- inputs=None,
535
- outputs=[prompt_info],
536
- queue=False
537
- )
538
- prompt.submit(
539
- fn=set_prompt_visible,
540
- inputs=None,
541
- outputs=[prompt_info],
542
- queue=False
543
  )
544
 
 
545
  if __name__ == "__main__":
546
- demo.launch()
 
 
 
 
 
1
  import gradio as gr
2
+ import replicate
 
 
 
 
 
 
3
  import os
4
+ from PIL import Image
5
+ import requests
6
+ from io import BytesIO
7
+ import time
8
+ import tempfile
9
  import base64
 
 
 
10
 
11
+ # Set up Replicate API key from environment variable
12
+ os.environ['REPLICATE_API_TOKEN'] = os.getenv('REPLICATE_API_TOKEN')
 
 
 
 
13
 
14
+ def upload_image_to_hosting(image):
 
 
 
 
 
 
 
 
 
 
15
  """
16
+ Upload image to multiple hosting services with fallback
 
17
  """
18
+ # Method 1: Try imgbb.com (most reliable)
 
 
 
 
 
 
 
 
 
 
19
  try:
20
+ buffered = BytesIO()
21
+ image.save(buffered, format="PNG")
22
+ buffered.seek(0)
23
+ img_base64 = base64.b64encode(buffered.getvalue()).decode()
24
+
25
+ response = requests.post(
26
+ "https://api.imgbb.com/1/upload",
27
+ data={
28
+ 'key': '6d207e02198a847aa98d0a2a901485a5',
29
+ 'image': img_base64,
30
+ }
31
  )
32
+
33
+ if response.status_code == 200:
34
+ data = response.json()
35
+ if data.get('success'):
36
+ return data['data']['url']
37
+ except:
38
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ # Method 2: Try 0x0.st (simple and reliable)
41
+ try:
42
+ buffered = BytesIO()
43
+ image.save(buffered, format="PNG")
44
+ buffered.seek(0)
45
+
46
+ files = {'file': ('image.png', buffered, 'image/png')}
47
+ response = requests.post("https://0x0.st", files=files)
48
+
49
+ if response.status_code == 200:
50
+ return response.text.strip()
51
+ except:
52
+ pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ # Method 3: Fallback to base64
55
+ buffered = BytesIO()
56
+ image.save(buffered, format="PNG")
57
+ buffered.seek(0)
58
+ img_base64 = base64.b64encode(buffered.getvalue()).decode()
59
+ return f"data:image/png;base64,{img_base64}"
60
+
61
+ def process_images(prompt, image1, image2=None):
62
+ """
63
+ Process uploaded images with Replicate API
64
+ """
65
+ if not image1:
66
+ return None, "Please upload at least one image"
67
 
68
+ if not os.getenv('REPLICATE_API_TOKEN'):
69
+ return None, "Please set REPLICATE_API_TOKEN"
 
 
 
 
 
 
 
70
 
71
+ try:
72
+ image_urls = []
73
+
74
+ # Upload images
75
+ url1 = upload_image_to_hosting(image1)
76
+ image_urls.append(url1)
77
+
78
+ if image2:
79
+ url2 = upload_image_to_hosting(image2)
80
+ image_urls.append(url2)
81
+
82
+ # Run the model
83
+ output = replicate.run(
84
+ "google/nano-banana",
85
+ input={
86
+ "prompt": prompt,
87
+ "image_input": image_urls
88
+ }
89
+ )
90
+
91
+ if output is None:
92
+ return None, "No output received"
93
+
94
+ # Get the generated image
95
+ try:
96
+ if hasattr(output, 'read'):
97
+ img_data = output.read()
98
+ img = Image.open(BytesIO(img_data))
99
+ return img, "✨ Generated successfully!"
100
+ except:
101
+ pass
102
+
103
+ try:
104
+ if hasattr(output, 'url'):
105
+ output_url = output.url()
106
+ response = requests.get(output_url, timeout=30)
107
+ if response.status_code == 200:
108
+ img = Image.open(BytesIO(response.content))
109
+ return img, "✨ Generated successfully!"
110
+ except:
111
+ pass
112
+
113
+ output_url = None
114
+ if isinstance(output, str):
115
+ output_url = output
116
+ elif isinstance(output, list) and len(output) > 0:
117
+ output_url = output[0]
118
+
119
+ if output_url:
120
+ response = requests.get(output_url, timeout=30)
121
+ if response.status_code == 200:
122
+ img = Image.open(BytesIO(response.content))
123
+ return img, "✨ Generated successfully!"
124
+
125
+ return None, "Could not process output"
126
+
127
+ except Exception as e:
128
+ return None, f"Error: {str(e)[:100]}"
129
 
130
+ # Enhanced CSS with modern, minimal design
131
+ css = """
 
132
  .gradio-container {
133
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
134
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
135
  min-height: 100vh;
136
  }
137
+ .header-container {
138
+ background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%);
139
+ padding: 2.5rem;
140
+ border-radius: 24px;
141
+ margin-bottom: 2.5rem;
142
+ box-shadow: 0 20px 60px rgba(255, 179, 71, 0.25);
143
  }
144
+ .logo-text {
145
+ font-size: 3.5rem;
146
+ font-weight: 900;
147
+ color: #2d3436;
148
+ text-align: center;
149
+ margin: 0;
150
+ letter-spacing: -2px;
 
 
 
151
  }
152
+ .subtitle {
153
+ color: #2d3436;
 
 
 
 
 
 
 
154
  text-align: center;
155
+ font-size: 1rem;
156
+ margin-top: 0.5rem;
157
+ opacity: 0.8;
158
  }
159
+ .main-content {
160
+ background: rgba(255, 255, 255, 0.95);
161
+ backdrop-filter: blur(20px);
162
+ border-radius: 24px;
163
+ padding: 2.5rem;
164
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
165
  }
 
 
166
  .gr-button-primary {
167
+ background: linear-gradient(135deg, #ffd93d 0%, #ffb347 100%) !important;
168
  border: none !important;
169
+ color: #2d3436 !important;
170
+ font-weight: 700 !important;
171
  font-size: 1.1rem !important;
172
+ padding: 1.2rem 2rem !important;
173
+ border-radius: 14px !important;
174
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
175
+ text-transform: uppercase;
176
+ letter-spacing: 1px;
177
+ width: 100%;
178
+ margin-top: 1rem !important;
179
  }
 
180
  .gr-button-primary:hover {
181
+ transform: translateY(-3px) !important;
182
+ box-shadow: 0 15px 40px rgba(255, 179, 71, 0.35) !important;
 
 
 
 
 
 
 
 
183
  }
184
+ .gr-input, .gr-textarea {
185
+ background: #ffffff !important;
186
+ border: 2px solid #e1e8ed !important;
187
+ border-radius: 14px !important;
188
+ color: #2d3436 !important;
189
+ font-size: 1rem !important;
190
+ padding: 0.8rem 1rem !important;
191
  }
192
+ .gr-input:focus, .gr-textarea:focus {
193
+ border-color: #ffd93d !important;
194
+ box-shadow: 0 0 0 4px rgba(255, 217, 61, 0.15) !important;
 
 
 
 
195
  }
196
+ .gr-form {
197
+ background: transparent !important;
198
+ border: none !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }
200
+ .gr-panel {
201
+ background: #ffffff !important;
202
+ border: 2px solid #e1e8ed !important;
203
+ border-radius: 16px !important;
204
+ padding: 1.5rem !important;
205
  }
206
+ .gr-box {
207
+ border-radius: 14px !important;
208
+ border-color: #e1e8ed !important;
 
209
  }
210
+ label {
211
+ color: #636e72 !important;
212
+ font-weight: 600 !important;
213
+ font-size: 0.85rem !important;
214
+ text-transform: uppercase;
215
+ letter-spacing: 0.5px;
216
+ margin-bottom: 0.5rem !important;
217
  }
218
+ .status-text {
219
+ font-family: 'SF Mono', 'Monaco', monospace;
220
+ color: #00b894;
221
  font-size: 0.9rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
223
+ .image-container {
224
+ border-radius: 14px !important;
225
+ overflow: hidden;
226
+ border: 2px solid #e1e8ed !important;
227
+ background: #fafbfc !important;
 
 
 
 
 
 
228
  }
229
+ footer {
230
+ display: none !important;
 
 
231
  }
232
+ /* Equal sizing for all image containers */
233
+ .image-upload {
234
+ min-height: 200px !important;
235
+ max-height: 200px !important;
 
 
 
236
  }
237
+ .output-image {
238
+ min-height: 420px !important;
239
+ max-height: 420px !important;
 
240
  }
241
+ /* Ensure consistent spacing */
242
+ .gr-row {
243
+ gap: 1rem !important;
 
 
 
 
 
244
  }
245
+ .gr-column {
246
+ gap: 1rem !important;
 
 
247
  }
248
  """
249
 
250
+ with gr.Blocks(css=css, theme=gr.themes.Base()) as demo:
251
+ with gr.Column(elem_classes="header-container"):
252
+ gr.HTML("""
253
+ <h1 class="logo-text">🍌 Open Nano Banana</h1>
254
+ <p class="subtitle">AI-Powered Image Style Transfer</p>
255
+ <div style="display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 20px;">
256
+ <a href="https://huggingface.co/spaces/openfree/Nano-Banana-Upscale" target="_blank">
257
+ <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=UPSCALE&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Nano Banana Upscale">
258
+ </a>
259
+ <a href="https://huggingface.co/spaces/openfree/Free-Nano-Banana" target="_blank">
260
+ <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=FREE&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Free Nano Banana">
261
+ </a>
262
+ <a href="https://huggingface.co/spaces/aiqtech/Nano-Banana-API" target="_blank">
263
+ <img src="https://img.shields.io/static/v1?label=NANO%20BANANA&message=API&color=%230000ff&labelColor=%23800080&logo=GOOGLE&logoColor=white&style=for-the-badge" alt="Nano Banana API">
264
+ </a>
265
+ <a href="https://discord.gg/openfreeai" target="_blank">
266
+ <img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="Discord Openfree AI">
267
+ </a>
268
+ </div>
269
+ """)
270
+
271
+ with gr.Column(elem_classes="main-content"):
272
+ with gr.Row(equal_height=True):
273
+ # Left Column - Inputs
274
+ with gr.Column(scale=1):
275
+ prompt = gr.Textbox(
276
+ label="Style Description",
277
+ placeholder="Describe your style...",
278
  lines=3,
279
+ value="Make the sheets in the style of the logo. Make the scene natural.",
280
+ elem_classes="prompt-input"
281
  )
282
 
283
+ with gr.Row(equal_height=True):
284
+ image1 = gr.Image(
285
+ label="Primary Image",
286
+ type="pil",
287
+ height=200,
288
+ elem_classes="image-container image-upload"
 
289
  )
290
+ image2 = gr.Image(
291
+ label="Secondary Image",
292
+ type="pil",
293
+ height=200,
294
+ elem_classes="image-container image-upload"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  )
296
 
297
+ generate_btn = gr.Button(
298
+ "Generate Magic ✨",
299
+ variant="primary",
300
+ size="lg"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  )
302
+
303
+ # Right Column - Output
304
+ with gr.Column(scale=1):
305
+ output_image = gr.Image(
306
+ label="Generated Result",
307
+ type="pil",
308
+ height=420,
309
+ elem_classes="image-container output-image"
310
  )
311
 
312
+ status = gr.Textbox(
313
+ label="Status",
314
+ interactive=False,
315
+ lines=1,
316
+ elem_classes="status-text",
317
+ value="Ready to generate..."
 
318
  )
319
 
320
+ # Event handler
321
+ generate_btn.click(
322
+ fn=process_images,
323
+ inputs=[prompt, image1, image2],
324
+ outputs=[output_image, status]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  )
326
 
327
+ # Launch
328
  if __name__ == "__main__":
329
+ demo.launch(
330
+ share=True,
331
+ server_name="0.0.0.0",
332
+ server_port=7860
333
+ )