Spaces:
Running
Running
Delete app-backup1.py
Browse files- app-backup1.py +0 -602
app-backup1.py
DELETED
@@ -1,602 +0,0 @@
|
|
1 |
-
import gradio as gr
|
2 |
-
import replicate
|
3 |
-
import os
|
4 |
-
from typing import Optional, List
|
5 |
-
from huggingface_hub import whoami
|
6 |
-
from PIL import Image
|
7 |
-
import requests
|
8 |
-
from io import BytesIO
|
9 |
-
import tempfile
|
10 |
-
import base64
|
11 |
-
|
12 |
-
# --- Replicate API Configuration ---
|
13 |
-
REPLICATE_API_TOKEN = os.getenv("REPLICATE_API_TOKEN")
|
14 |
-
|
15 |
-
if not REPLICATE_API_TOKEN:
|
16 |
-
raise ValueError("REPLICATE_API_TOKEN environment variable is not set.")
|
17 |
-
|
18 |
-
# Initialize Replicate client
|
19 |
-
os.environ["REPLICATE_API_TOKEN"] = REPLICATE_API_TOKEN
|
20 |
-
|
21 |
-
def verify_login_status(token: Optional[gr.OAuthToken]) -> bool:
|
22 |
-
"""Verifies if the user is logged in to Hugging Face."""
|
23 |
-
if not token:
|
24 |
-
return False
|
25 |
-
try:
|
26 |
-
user_info = whoami(token=token.token)
|
27 |
-
return True if user_info else False
|
28 |
-
except Exception as e:
|
29 |
-
print(f"Could not verify user's login status: {e}")
|
30 |
-
return False
|
31 |
-
|
32 |
-
def upload_image_to_hosting(image_path: str) -> str:
|
33 |
-
"""
|
34 |
-
Upload image to hosting service and return URL.
|
35 |
-
Using multiple fallback methods for reliability.
|
36 |
-
"""
|
37 |
-
# Open the image
|
38 |
-
img = Image.open(image_path)
|
39 |
-
|
40 |
-
# Method 1: Try imgbb.com (most reliable)
|
41 |
-
try:
|
42 |
-
buffered = BytesIO()
|
43 |
-
img.save(buffered, format="PNG")
|
44 |
-
buffered.seek(0)
|
45 |
-
img_base64 = base64.b64encode(buffered.getvalue()).decode()
|
46 |
-
|
47 |
-
response = requests.post(
|
48 |
-
"https://api.imgbb.com/1/upload",
|
49 |
-
data={
|
50 |
-
'key': '6d207e02198a847aa98d0a2a901485a5', # Free API key
|
51 |
-
'image': img_base64,
|
52 |
-
}
|
53 |
-
)
|
54 |
-
|
55 |
-
if response.status_code == 200:
|
56 |
-
data = response.json()
|
57 |
-
if data.get('success'):
|
58 |
-
return data['data']['url']
|
59 |
-
except Exception as e:
|
60 |
-
print(f"imgbb upload failed: {e}")
|
61 |
-
|
62 |
-
# Method 2: Try 0x0.st (simple and reliable)
|
63 |
-
try:
|
64 |
-
buffered = BytesIO()
|
65 |
-
img.save(buffered, format="PNG")
|
66 |
-
buffered.seek(0)
|
67 |
-
|
68 |
-
files = {'file': ('image.png', buffered, 'image/png')}
|
69 |
-
response = requests.post("https://0x0.st", files=files)
|
70 |
-
|
71 |
-
if response.status_code == 200:
|
72 |
-
url = response.text.strip()
|
73 |
-
if url.startswith('http'):
|
74 |
-
return url
|
75 |
-
except Exception as e:
|
76 |
-
print(f"0x0.st upload failed: {e}")
|
77 |
-
|
78 |
-
# Method 3: Fallback to data URI (last resort)
|
79 |
-
buffered = BytesIO()
|
80 |
-
img.save(buffered, format="PNG")
|
81 |
-
buffered.seek(0)
|
82 |
-
img_base64 = base64.b64encode(buffered.getvalue()).decode()
|
83 |
-
return f"data:image/png;base64,{img_base64}"
|
84 |
-
|
85 |
-
def image_to_data_uri(image_path: str) -> str:
|
86 |
-
"""Convert local image file to data URI format (kept for backwards compatibility)."""
|
87 |
-
with open(image_path, "rb") as img_file:
|
88 |
-
img_data = img_file.read()
|
89 |
-
img_base64 = base64.b64encode(img_data).decode('utf-8')
|
90 |
-
|
91 |
-
# Get the image format
|
92 |
-
img = Image.open(image_path)
|
93 |
-
img_format = img.format.lower() if img.format else 'png'
|
94 |
-
|
95 |
-
# Create data URI
|
96 |
-
data_uri = f"data:image/{img_format};base64,{img_base64}"
|
97 |
-
return data_uri
|
98 |
-
|
99 |
-
def process_output(output, progress=gr.Progress()) -> str:
|
100 |
-
"""Process the output from Replicate API and return a local file path."""
|
101 |
-
try:
|
102 |
-
# Check if output has a url attribute (FileObject)
|
103 |
-
if hasattr(output, 'url'):
|
104 |
-
# If url is a method, call it; if it's a property, just access it
|
105 |
-
image_url = output.url() if callable(output.url) else output.url
|
106 |
-
# If output is already a string URL
|
107 |
-
elif isinstance(output, str):
|
108 |
-
image_url = output
|
109 |
-
# If output is a list of URLs
|
110 |
-
elif isinstance(output, list) and len(output) > 0:
|
111 |
-
# Check first item in list
|
112 |
-
first_item = output[0]
|
113 |
-
if hasattr(first_item, 'url'):
|
114 |
-
image_url = first_item.url() if callable(first_item.url) else first_item.url
|
115 |
-
else:
|
116 |
-
image_url = first_item
|
117 |
-
else:
|
118 |
-
raise ValueError(f"Unexpected output format from Replicate: {type(output)}")
|
119 |
-
|
120 |
-
# Download the image from URL
|
121 |
-
response = requests.get(image_url)
|
122 |
-
response.raise_for_status()
|
123 |
-
|
124 |
-
# Save to temporary file
|
125 |
-
img = Image.open(BytesIO(response.content))
|
126 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmpfile:
|
127 |
-
img.save(tmpfile.name)
|
128 |
-
progress(1.0, desc="✅ Complete!")
|
129 |
-
return tmpfile.name
|
130 |
-
|
131 |
-
except Exception as e:
|
132 |
-
print(f"Error processing output: {e}")
|
133 |
-
raise ValueError(f"Failed to process output: {str(e)}")
|
134 |
-
|
135 |
-
def run_single_image_logic(prompt: str, image_path: Optional[str] = None, progress=gr.Progress()) -> str:
|
136 |
-
"""Handles text-to-image or single image-to-image using Replicate's Nano Banana."""
|
137 |
-
try:
|
138 |
-
progress(0.2, desc="🎨 Preparing...")
|
139 |
-
|
140 |
-
# Prepare input for Replicate API
|
141 |
-
input_data = {
|
142 |
-
"prompt": prompt
|
143 |
-
}
|
144 |
-
|
145 |
-
# If there's an input image, upload it to get a proper URL
|
146 |
-
if image_path:
|
147 |
-
progress(0.3, desc="📤 Uploading image...")
|
148 |
-
# Upload to hosting service for proper URL
|
149 |
-
image_url = upload_image_to_hosting(image_path)
|
150 |
-
|
151 |
-
if image_url.startswith('http'):
|
152 |
-
print(f"Image uploaded successfully: {image_url[:50]}...")
|
153 |
-
else:
|
154 |
-
print("Using data URI fallback")
|
155 |
-
|
156 |
-
input_data["image_input"] = [image_url]
|
157 |
-
|
158 |
-
progress(0.5, desc="✨ Generating...")
|
159 |
-
|
160 |
-
# Run the model on Replicate
|
161 |
-
output = replicate.run(
|
162 |
-
"google/nano-banana",
|
163 |
-
input=input_data
|
164 |
-
)
|
165 |
-
|
166 |
-
progress(0.8, desc="🖼️ Finalizing...")
|
167 |
-
|
168 |
-
# Handle the output - output is already a URL string or FileObject
|
169 |
-
if output:
|
170 |
-
return process_output(output, progress)
|
171 |
-
else:
|
172 |
-
raise ValueError("No output received from Replicate API")
|
173 |
-
|
174 |
-
except Exception as e:
|
175 |
-
print(f"Error details: {e}")
|
176 |
-
print(f"Error type: {type(e)}")
|
177 |
-
if 'output' in locals():
|
178 |
-
print(f"Output value: {output}")
|
179 |
-
print(f"Output type: {type(output)}")
|
180 |
-
raise gr.Error(f"Image generation failed: {str(e)[:200]}")
|
181 |
-
|
182 |
-
def run_multi_image_logic(prompt: str, images: List[str], progress=gr.Progress()) -> str:
|
183 |
-
"""
|
184 |
-
Handles multi-image editing by sending a list of images and a prompt.
|
185 |
-
"""
|
186 |
-
if not images:
|
187 |
-
raise gr.Error("Please upload at least one image in the 'Multiple Images' tab.")
|
188 |
-
|
189 |
-
try:
|
190 |
-
progress(0.2, desc="🎨 Preparing images...")
|
191 |
-
|
192 |
-
# Upload all images to get proper URLs
|
193 |
-
image_urls = []
|
194 |
-
for idx, image_path in enumerate(images):
|
195 |
-
if isinstance(image_path, (list, tuple)):
|
196 |
-
image_path = image_path[0]
|
197 |
-
|
198 |
-
progress(0.2 + (0.2 * idx / len(images)), desc=f"📤 Uploading image {idx+1}/{len(images)}...")
|
199 |
-
image_url = upload_image_to_hosting(image_path)
|
200 |
-
image_urls.append(image_url)
|
201 |
-
|
202 |
-
# Prepare input for Replicate API with multiple images
|
203 |
-
input_data = {
|
204 |
-
"prompt": prompt,
|
205 |
-
"image_input": image_urls
|
206 |
-
}
|
207 |
-
|
208 |
-
progress(0.5, desc="✨ Generating...")
|
209 |
-
|
210 |
-
# Run the model on Replicate
|
211 |
-
output = replicate.run(
|
212 |
-
"google/nano-banana",
|
213 |
-
input=input_data
|
214 |
-
)
|
215 |
-
|
216 |
-
progress(0.8, desc="🖼️ Finalizing...")
|
217 |
-
|
218 |
-
# Handle the output using the process_output function
|
219 |
-
if output:
|
220 |
-
return process_output(output, progress)
|
221 |
-
else:
|
222 |
-
raise ValueError("No output received from Replicate API")
|
223 |
-
|
224 |
-
except Exception as e:
|
225 |
-
print(f"Multi-image error details: {e}")
|
226 |
-
print(f"Output value: {output if 'output' in locals() else 'Not set'}")
|
227 |
-
print(f"Output type: {type(output) if 'output' in locals() else 'Not set'}")
|
228 |
-
raise gr.Error(f"Image generation failed: {e}")
|
229 |
-
|
230 |
-
# --- Gradio App UI ---
|
231 |
-
css = '''
|
232 |
-
/* Header Styling */
|
233 |
-
.main-header {
|
234 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
235 |
-
padding: 2rem;
|
236 |
-
border-radius: 1rem;
|
237 |
-
margin-bottom: 2rem;
|
238 |
-
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
239 |
-
}
|
240 |
-
.header-title {
|
241 |
-
font-size: 2.5rem !important;
|
242 |
-
font-weight: bold;
|
243 |
-
color: white;
|
244 |
-
text-align: center;
|
245 |
-
margin: 0 !important;
|
246 |
-
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
247 |
-
}
|
248 |
-
.header-subtitle {
|
249 |
-
color: rgba(255,255,255,0.9);
|
250 |
-
text-align: center;
|
251 |
-
margin-top: 0.5rem !important;
|
252 |
-
font-size: 1.1rem;
|
253 |
-
}
|
254 |
-
/* Card Styling */
|
255 |
-
.card {
|
256 |
-
background: white;
|
257 |
-
border-radius: 1rem;
|
258 |
-
padding: 1.5rem;
|
259 |
-
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
260 |
-
border: 1px solid rgba(0,0,0,0.05);
|
261 |
-
}
|
262 |
-
.dark .card {
|
263 |
-
background: #1f2937;
|
264 |
-
border: 1px solid #374151;
|
265 |
-
}
|
266 |
-
/* Tab Styling */
|
267 |
-
.tabs {
|
268 |
-
border-radius: 0.5rem;
|
269 |
-
overflow: hidden;
|
270 |
-
margin-bottom: 1rem;
|
271 |
-
}
|
272 |
-
.tabitem {
|
273 |
-
padding: 1rem !important;
|
274 |
-
}
|
275 |
-
button.selected {
|
276 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
277 |
-
color: white !important;
|
278 |
-
}
|
279 |
-
/* Button Styling */
|
280 |
-
.generate-btn {
|
281 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
282 |
-
border: none !important;
|
283 |
-
color: white !important;
|
284 |
-
font-size: 1.1rem !important;
|
285 |
-
font-weight: 600 !important;
|
286 |
-
padding: 0.8rem 2rem !important;
|
287 |
-
border-radius: 0.5rem !important;
|
288 |
-
cursor: pointer !important;
|
289 |
-
transition: all 0.3s ease !important;
|
290 |
-
width: 100% !important;
|
291 |
-
margin-top: 1rem !important;
|
292 |
-
}
|
293 |
-
.generate-btn:hover {
|
294 |
-
transform: translateY(-2px) !important;
|
295 |
-
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4) !important;
|
296 |
-
}
|
297 |
-
.use-btn {
|
298 |
-
background: linear-gradient(135deg, #10b981 0%, #059669 100%) !important;
|
299 |
-
border: none !important;
|
300 |
-
color: white !important;
|
301 |
-
font-weight: 600 !important;
|
302 |
-
padding: 0.6rem 1.5rem !important;
|
303 |
-
border-radius: 0.5rem !important;
|
304 |
-
cursor: pointer !important;
|
305 |
-
transition: all 0.3s ease !important;
|
306 |
-
width: 100% !important;
|
307 |
-
}
|
308 |
-
.use-btn:hover {
|
309 |
-
transform: translateY(-1px) !important;
|
310 |
-
box-shadow: 0 5px 15px rgba(16, 185, 129, 0.4) !important;
|
311 |
-
}
|
312 |
-
/* Input Styling */
|
313 |
-
.prompt-input textarea {
|
314 |
-
border-radius: 0.5rem !important;
|
315 |
-
border: 2px solid #e5e7eb !important;
|
316 |
-
padding: 0.8rem !important;
|
317 |
-
font-size: 1rem !important;
|
318 |
-
transition: border-color 0.3s ease !important;
|
319 |
-
}
|
320 |
-
.prompt-input textarea:focus {
|
321 |
-
border-color: #667eea !important;
|
322 |
-
outline: none !important;
|
323 |
-
}
|
324 |
-
.dark .prompt-input textarea {
|
325 |
-
border-color: #374151 !important;
|
326 |
-
background: #1f2937 !important;
|
327 |
-
}
|
328 |
-
/* Image Output Styling */
|
329 |
-
#output {
|
330 |
-
border-radius: 0.5rem !important;
|
331 |
-
overflow: hidden !important;
|
332 |
-
box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important;
|
333 |
-
}
|
334 |
-
/* Progress Bar Styling */
|
335 |
-
.progress-bar {
|
336 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
337 |
-
}
|
338 |
-
/* Examples Styling */
|
339 |
-
.examples {
|
340 |
-
background: #f9fafb;
|
341 |
-
border-radius: 0.5rem;
|
342 |
-
padding: 1rem;
|
343 |
-
margin-top: 1rem;
|
344 |
-
}
|
345 |
-
.dark .examples {
|
346 |
-
background: #1f2937;
|
347 |
-
}
|
348 |
-
/* Login Message Styling */
|
349 |
-
.login-message {
|
350 |
-
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
|
351 |
-
border-radius: 1rem;
|
352 |
-
padding: 2rem;
|
353 |
-
text-align: center;
|
354 |
-
border: 2px solid #f59e0b;
|
355 |
-
}
|
356 |
-
.dark .login-message {
|
357 |
-
background: linear-gradient(135deg, #7c2d12 0%, #92400e 100%);
|
358 |
-
border-color: #f59e0b;
|
359 |
-
}
|
360 |
-
/* Emoji Animations */
|
361 |
-
@keyframes bounce {
|
362 |
-
0%, 100% { transform: translateY(0); }
|
363 |
-
50% { transform: translateY(-10px); }
|
364 |
-
}
|
365 |
-
.emoji-icon {
|
366 |
-
display: inline-block;
|
367 |
-
animation: bounce 2s infinite;
|
368 |
-
}
|
369 |
-
/* Responsive Design */
|
370 |
-
@media (max-width: 768px) {
|
371 |
-
.header-title {
|
372 |
-
font-size: 2rem !important;
|
373 |
-
}
|
374 |
-
|
375 |
-
.main-container {
|
376 |
-
padding: 1rem !important;
|
377 |
-
}
|
378 |
-
}
|
379 |
-
'''
|
380 |
-
|
381 |
-
with gr.Blocks(theme=gr.themes.Soft(), css=css) as demo:
|
382 |
-
# Header
|
383 |
-
gr.HTML('''
|
384 |
-
<div class="main-header">
|
385 |
-
<h1 class="header-title">
|
386 |
-
🍌 Real Nano Banana
|
387 |
-
</h1>
|
388 |
-
<p class="header-subtitle">
|
389 |
-
AI Image Generator powered by Google Nano Banana
|
390 |
-
</p>
|
391 |
-
<div style="display: flex; justify-content: center; align-items: center; gap: 10px; margin-top: 20px;">
|
392 |
-
<a href="https://huggingface.co/spaces/openfree/Nano-Banana-Upscale" target="_blank">
|
393 |
-
<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">
|
394 |
-
</a>
|
395 |
-
<a href="https://huggingface.co/spaces/openfree/Free-Nano-Banana" target="_blank">
|
396 |
-
<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">
|
397 |
-
</a>
|
398 |
-
<a href="https://huggingface.co/spaces/aiqtech/Nano-Banana-API" target="_blank">
|
399 |
-
<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">
|
400 |
-
</a>
|
401 |
-
<a href="https://discord.gg/openfreeai" target="_blank">
|
402 |
-
<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">
|
403 |
-
</a>
|
404 |
-
</div>
|
405 |
-
</div>
|
406 |
-
''')
|
407 |
-
|
408 |
-
# Login Notice
|
409 |
-
gr.HTML('''
|
410 |
-
<div style="background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
|
411 |
-
border-radius: 0.5rem; padding: 1rem; margin-bottom: 1.5rem;
|
412 |
-
border-left: 4px solid #0284c7;">
|
413 |
-
<p style="margin: 0; color: #075985; font-weight: 600;">
|
414 |
-
🔐 Please sign in with your Hugging Face account to use this service.
|
415 |
-
</p>
|
416 |
-
</div>
|
417 |
-
''')
|
418 |
-
|
419 |
-
login_message = gr.Markdown(visible=False)
|
420 |
-
main_interface = gr.Column(visible=False, elem_classes="main-container")
|
421 |
-
|
422 |
-
with main_interface:
|
423 |
-
with gr.Row():
|
424 |
-
with gr.Column(scale=1):
|
425 |
-
gr.HTML('<div class="card">')
|
426 |
-
|
427 |
-
# Mode Selection
|
428 |
-
gr.HTML('<h3 style="margin-top: 0;">📸 Select Mode</h3>')
|
429 |
-
active_tab_state = gr.State(value="single")
|
430 |
-
|
431 |
-
with gr.Tabs(elem_classes="tabs") as tabs:
|
432 |
-
with gr.TabItem("🖼️ Single Image", id="single") as single_tab:
|
433 |
-
image_input = gr.Image(
|
434 |
-
type="filepath",
|
435 |
-
label="Input Image (Optional)",
|
436 |
-
elem_classes="image-input"
|
437 |
-
)
|
438 |
-
gr.HTML('''
|
439 |
-
<p style="text-align: center; color: #6b7280; font-size: 0.9rem; margin-top: 0.5rem;">
|
440 |
-
💡 Leave empty for text-to-image generation
|
441 |
-
</p>
|
442 |
-
''')
|
443 |
-
|
444 |
-
with gr.TabItem("🎨 Multiple Images", id="multiple") as multi_tab:
|
445 |
-
gallery_input = gr.Gallery(
|
446 |
-
label="Input Images (Max 2 images)",
|
447 |
-
file_types=["image"],
|
448 |
-
elem_classes="gallery-input"
|
449 |
-
)
|
450 |
-
gr.HTML('''
|
451 |
-
<p style="text-align: center; color: #6b7280; font-size: 0.9rem; margin-top: 0.5rem;">
|
452 |
-
💡 Upload up to 2 images for combination/editing
|
453 |
-
</p>
|
454 |
-
''')
|
455 |
-
|
456 |
-
# Prompt Input
|
457 |
-
gr.HTML('<h3>✍️ Prompt</h3>')
|
458 |
-
prompt_input = gr.Textbox(
|
459 |
-
label="",
|
460 |
-
info="Describe what you want the AI to generate",
|
461 |
-
placeholder="e.g., A delicious pizza, a cat in space, futuristic cityscape...",
|
462 |
-
lines=3,
|
463 |
-
elem_classes="prompt-input"
|
464 |
-
)
|
465 |
-
|
466 |
-
# Generate Button
|
467 |
-
generate_button = gr.Button(
|
468 |
-
"🚀 Generate",
|
469 |
-
variant="primary",
|
470 |
-
elem_classes="generate-btn"
|
471 |
-
)
|
472 |
-
|
473 |
-
# Examples
|
474 |
-
with gr.Accordion("💡 Example Prompts", open=False):
|
475 |
-
gr.Examples(
|
476 |
-
examples=[
|
477 |
-
["A delicious looking pizza with melting cheese"],
|
478 |
-
["A cat in a spacesuit walking on the moon surface"],
|
479 |
-
["Cyberpunk city at night with neon lights"],
|
480 |
-
["Japanese garden with cherry blossoms in spring"],
|
481 |
-
["Fantasy wizard tower in a magical world"],
|
482 |
-
["Make the scene more dramatic and cinematic"],
|
483 |
-
["Transform this into a watercolor painting style"],
|
484 |
-
],
|
485 |
-
inputs=prompt_input
|
486 |
-
)
|
487 |
-
|
488 |
-
gr.HTML('</div>')
|
489 |
-
|
490 |
-
with gr.Column(scale=1):
|
491 |
-
gr.HTML('<div class="card">')
|
492 |
-
gr.HTML('<h3 style="margin-top: 0;">🎨 Generated Result</h3>')
|
493 |
-
|
494 |
-
output_image = gr.Image(
|
495 |
-
label="",
|
496 |
-
interactive=False,
|
497 |
-
elem_id="output"
|
498 |
-
)
|
499 |
-
|
500 |
-
use_image_button = gr.Button(
|
501 |
-
"♻️ Use this image for next edit",
|
502 |
-
elem_classes="use-btn",
|
503 |
-
visible=False
|
504 |
-
)
|
505 |
-
|
506 |
-
# Tips
|
507 |
-
gr.HTML('''
|
508 |
-
<div style="background: #f0f9ff; border-radius: 0.5rem; padding: 1rem; margin-top: 1rem;">
|
509 |
-
<h4 style="margin-top: 0; color: #0369a1;">💡 Tips</h4>
|
510 |
-
<ul style="margin: 0; padding-left: 1.5rem; color: #0c4a6e;">
|
511 |
-
<li>Use specific and detailed prompts for better results</li>
|
512 |
-
<li>You can reuse generated images for iterative improvements</li>
|
513 |
-
<li>Multiple image mode supports up to 2 images for combination</li>
|
514 |
-
<li>English prompts tend to produce better results</li>
|
515 |
-
</ul>
|
516 |
-
</div>
|
517 |
-
''')
|
518 |
-
|
519 |
-
gr.HTML('</div>')
|
520 |
-
|
521 |
-
# Footer
|
522 |
-
gr.HTML('''
|
523 |
-
<div style="text-align: center; margin-top: 2rem; padding: 1rem;
|
524 |
-
border-top: 1px solid #e5e7eb;">
|
525 |
-
<p style="color: #6b7280;">
|
526 |
-
Made with 💜 using Replicate API | Powered by Google Nano Banana
|
527 |
-
</p>
|
528 |
-
</div>
|
529 |
-
''')
|
530 |
-
|
531 |
-
login_button = gr.LoginButton()
|
532 |
-
|
533 |
-
# --- Event Handlers ---
|
534 |
-
def unified_generator(
|
535 |
-
prompt: str,
|
536 |
-
single_image: Optional[str],
|
537 |
-
multi_images: Optional[List[str]],
|
538 |
-
active_tab: str,
|
539 |
-
oauth_token: Optional[gr.OAuthToken] = None,
|
540 |
-
):
|
541 |
-
if not verify_login_status(oauth_token):
|
542 |
-
raise gr.Error("Login required. Please click the 'Sign in with Hugging Face' button at the top.")
|
543 |
-
if not prompt:
|
544 |
-
raise gr.Error("Please enter a prompt.")
|
545 |
-
if active_tab == "multiple" and multi_images:
|
546 |
-
result = run_multi_image_logic(prompt, multi_images)
|
547 |
-
else:
|
548 |
-
result = run_single_image_logic(prompt, single_image)
|
549 |
-
return result, gr.update(visible=True)
|
550 |
-
|
551 |
-
single_tab.select(lambda: "single", None, active_tab_state)
|
552 |
-
multi_tab.select(lambda: "multiple", None, active_tab_state)
|
553 |
-
|
554 |
-
generate_button.click(
|
555 |
-
unified_generator,
|
556 |
-
inputs=[prompt_input, image_input, gallery_input, active_tab_state],
|
557 |
-
outputs=[output_image, use_image_button],
|
558 |
-
)
|
559 |
-
|
560 |
-
use_image_button.click(
|
561 |
-
lambda img: (img, gr.update(visible=False)),
|
562 |
-
inputs=[output_image],
|
563 |
-
outputs=[image_input, use_image_button]
|
564 |
-
)
|
565 |
-
|
566 |
-
# --- Access Control Logic ---
|
567 |
-
def control_access(
|
568 |
-
profile: Optional[gr.OAuthProfile] = None,
|
569 |
-
oauth_token: Optional[gr.OAuthToken] = None
|
570 |
-
):
|
571 |
-
if not profile:
|
572 |
-
return gr.update(visible=False), gr.update(visible=False)
|
573 |
-
if verify_login_status(oauth_token):
|
574 |
-
return gr.update(visible=True), gr.update(visible=False)
|
575 |
-
else:
|
576 |
-
message = '''
|
577 |
-
<div class="login-message">
|
578 |
-
<h2>🔐 Login Required</h2>
|
579 |
-
<p style="font-size: 1.1rem; margin: 1rem 0;">
|
580 |
-
Please sign in with your Hugging Face account to use this AI image generation tool.
|
581 |
-
</p>
|
582 |
-
<p style="margin: 1rem 0;">
|
583 |
-
After logging in, you can access:
|
584 |
-
</p>
|
585 |
-
<ul style="text-align: left; display: inline-block; margin: 1rem 0;">
|
586 |
-
<li>🚀 High-quality image generation via Google Nano Banana</li>
|
587 |
-
<li>⚡ Fast image generation and editing</li>
|
588 |
-
<li>🎨 Text-to-image conversion</li>
|
589 |
-
<li>🔧 Multiple image editing and combining</li>
|
590 |
-
</ul>
|
591 |
-
<p style="margin-top: 1.5rem; font-weight: bold;">
|
592 |
-
Click the "Sign in with Hugging Face" button at the top to get started!
|
593 |
-
</p>
|
594 |
-
</div>
|
595 |
-
'''
|
596 |
-
return gr.update(visible=False), gr.update(visible=True, value=message)
|
597 |
-
|
598 |
-
demo.load(control_access, inputs=None, outputs=[main_interface, login_message])
|
599 |
-
|
600 |
-
if __name__ == "__main__":
|
601 |
-
demo.queue(max_size=None, default_concurrency_limit=None)
|
602 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|