AkashKumarave commited on
Commit
272e95e
·
verified ·
1 Parent(s): 3d31d25

Upload 13 files

Browse files
Files changed (13) hide show
  1. .gitattributes +38 -35
  2. README.md +12 -12
  3. app.py +375 -380
  4. data/1.jpg +3 -0
  5. data/1.webp +0 -0
  6. data/1777043.jpg +3 -0
  7. data/2.webp +0 -0
  8. data/2807615.jpg +3 -0
  9. data/3.webp +0 -0
  10. data/76860.jpg +0 -0
  11. index.html +34 -0
  12. requirements.txt +10 -10
  13. style.css +408 -0
.gitattributes CHANGED
@@ -1,35 +1,38 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ data/1.jpg filter=lfs diff=lfs merge=lfs -text
37
+ data/1777043.jpg filter=lfs diff=lfs merge=lfs -text
38
+ data/2807615.jpg filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,12 +1,12 @@
1
- ---
2
- title: My3
3
- emoji: 🦀
4
- colorFrom: blue
5
- colorTo: pink
6
- sdk: gradio
7
- sdk_version: 5.20.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: Gemini-2.5-Flash-Nano-Banana
3
+ emoji: 📚
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: 5.34.0
8
+ app_file: app.py
9
+ pinned: true
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -1,381 +1,376 @@
1
- import logging
2
- from fastapi import FastAPI, UploadFile, File, HTTPException, Form, Request
3
- from fastapi.middleware.cors import CORSMiddleware
4
- from fastapi.responses import FileResponse, JSONResponse
5
- import requests
6
- import base64
7
- import os
8
- import time
9
- import jwt
10
- from pathlib import Path
11
- from typing import List
12
- import io
13
- import razorpay
14
- from razorpay.errors import SignatureVerificationError
15
- from supabase import create_client, Client
16
- from pydantic import BaseModel
17
- from typing import Optional
18
-
19
- # Configure logging
20
- logging.basicConfig(level=logging.INFO)
21
- logger = logging.getLogger(__name__)
22
-
23
- # Initialize FastAPI app
24
- app = FastAPI(title="Kling AI Multi-Image Generator API with Razorpay")
25
-
26
- # Enable CORS for the frontend - Updated for Lovable domains
27
- app.add_middleware(
28
- CORSMiddleware,
29
- allow_origins=[
30
- "https://hivili.web.app",
31
- "http://localhost:3000",
32
- "https://*.lovable.dev",
33
- "https://*.sandbox.lovable.dev",
34
- # Add pattern matching for Lovable subdomains
35
- ],
36
- allow_origin_regex=r"https://.*\.lovable\.dev|https://.*\.sandbox\.lovable\.dev",
37
- allow_credentials=True,
38
- allow_methods=["*"],
39
- allow_headers=["*"],
40
- )
41
-
42
- # ===== API CONFIGURATION =====
43
- ACCESS_KEY_ID = os.getenv("ACCESS_KEY_ID", "AFyHfnQATghFdCMyAG3gRPbNY4TNKFGB")
44
- ACCESS_KEY_SECRET = os.getenv("ACCESS_KEY_SECRET", "TTepeLyBterLNM3brYPGmdndBnnyKJBA")
45
- API_BASE_URL = "https://api-singapore.klingai.com"
46
- CREATE_TASK_ENDPOINT = f"{API_BASE_URL}/v1/images/multi-image2image"
47
-
48
- # ===== RAZORPAY CONFIGURATION =====
49
- RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID")
50
- RAZORPAY_KEY_SECRET = os.getenv("RAZORPAY_KEY_SECRET")
51
- razorpay_client = razorpay.Client(auth=(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET))
52
-
53
- # ===== SUPABASE CONFIGURATION =====
54
- SUPABASE_URL = os.getenv("SUPABASE_URL")
55
- SUPABASE_KEY = os.getenv("SUPABASE_KEY")
56
- supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) if SUPABASE_URL and SUPABASE_KEY else None
57
-
58
- # Pydantic models for JSON input validation
59
- class CreateOrderRequest(BaseModel):
60
- amount: int
61
-
62
- class VerifyPaymentRequest(BaseModel):
63
- razorpay_order_id: str
64
- razorpay_payment_id: str
65
- razorpay_signature: str
66
- user_id: Optional[str] = None
67
-
68
- # ===== AUTHENTICATION =====
69
- def generate_jwt_token():
70
- """Generate JWT token for API authentication"""
71
- payload = {
72
- "iss": ACCESS_KEY_ID,
73
- "exp": int(time.time()) + 1800, # 30 minutes expiration
74
- "nbf": int(time.time()) - 5 # Not before 5 seconds ago
75
- }
76
- return jwt.encode(payload, ACCESS_KEY_SECRET, algorithm="HS256")
77
-
78
- # ===== IMAGE PROCESSING =====
79
- def prepare_image_base64(image_content: bytes):
80
- """Convert image bytes to base64 without prefix"""
81
- try:
82
- return base64.b64encode(image_content).decode('utf-8')
83
- except Exception as e:
84
- logger.error(f"Image processing failed: {str(e)}")
85
- raise HTTPException(status_code=500, detail=f"Image processing failed: {str(e)}")
86
-
87
- def validate_image(image_content: bytes):
88
- """Validate image meets API requirements"""
89
- try:
90
- size_mb = len(image_content) / (1024 * 1024)
91
- if size_mb > 10:
92
- raise HTTPException(status_code=400, detail="Image too large (max 10MB)")
93
- return True, ""
94
- except Exception as e:
95
- raise HTTPException(status_code=400, detail=f"Image validation error: {str(e)}")
96
-
97
- # ===== API FUNCTIONS =====
98
- def create_multi_image_task(subject_images: List[bytes], prompt: str):
99
- """Create multi-image generation task"""
100
- headers = {
101
- "Authorization": f"Bearer {generate_jwt_token()}",
102
- "Content-Type": "application/json"
103
- }
104
-
105
- subject_image_list = []
106
- for img_content in subject_images:
107
- if img_content:
108
- base64_img = prepare_image_base64(img_content)
109
- if base64_img:
110
- subject_image_list.append({"subject_image": base64_img})
111
-
112
- if len(subject_image_list) < 2:
113
- raise HTTPException(status_code=400, detail="At least 2 subject images required")
114
-
115
- payload = {
116
- "model_name": "kling-v2",
117
- "prompt": prompt,
118
- "subject_image_list": subject_image_list,
119
- "n": 1,
120
- "aspect_ratio": "1:1"
121
- }
122
-
123
- try:
124
- response = requests.post(CREATE_TASK_ENDPOINT, json=payload, headers=headers)
125
- response.raise_for_status()
126
- return response.json()
127
- except requests.exceptions.RequestException as e:
128
- logger.error(f"API request failed: {str(e)}")
129
- if hasattr(e, 'response') and e.response:
130
- logger.error(f"API response: {e.response.text}")
131
- raise HTTPException(status_code=500, detail=f"API Error: {str(e)}")
132
-
133
- def check_task_status(task_id: str):
134
- """Check task completion status"""
135
- headers = {"Authorization": f"Bearer {generate_jwt_token()}"}
136
- status_url = f"{API_BASE_URL}/v1/images/multi-image2image/{task_id}"
137
-
138
- try:
139
- response = requests.get(status_url, headers=headers)
140
- response.raise_for_status()
141
- return response.json()
142
- except requests.exceptions.RequestException as e:
143
- raise HTTPException(status_code=500, detail=f"Status check failed: {str(e)}")
144
-
145
- # ===== RAZORPAY FUNCTIONS =====
146
- def create_razorpay_order(amount: int):
147
- """Create a Razorpay order"""
148
- try:
149
- if amount <= 0:
150
- raise ValueError("Amount must be a positive integer")
151
- order_data = {
152
- "amount": amount * 100, # Convert INR to paise
153
- "currency": "INR",
154
- "payment_capture": 1 # Auto-capture payment
155
- }
156
- order = razorpay_client.order.create(data=order_data)
157
- logger.info(f"Razorpay order created successfully: {order['id']}")
158
- return order
159
- except Exception as e:
160
- logger.error(f"Failed to create Razorpay order: {str(e)}")
161
- raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
162
-
163
- def verify_payment_signature(order_id: str, payment_id: str, signature: str):
164
- """Verify Razorpay payment signature"""
165
- try:
166
- params_dict = {
167
- "razorpay_order_id": order_id,
168
- "razorpay_payment_id": payment_id,
169
- "razorpay_signature": signature
170
- }
171
- razorpay_client.utility.verify_payment_signature(params_dict)
172
- logger.info(f"Payment signature verified successfully for order: {order_id}")
173
- return True
174
- except SignatureVerificationError as e:
175
- logger.error(f"Payment signature verification failed: {str(e)}")
176
- return False
177
- except Exception as e:
178
- logger.error(f"Error verifying payment signature: {str(e)}")
179
- raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
180
-
181
- # ===== MAIN PROCESSING =====
182
- async def generate_image(subject_images: List[bytes], prompt: str):
183
- """Handle complete image generation workflow"""
184
- for img_content in subject_images:
185
- if img_content:
186
- validate_image(img_content)
187
-
188
- task_response = create_multi_image_task(subject_images, prompt)
189
- if task_response.get("code") != 0:
190
- raise HTTPException(status_code=500, detail=f"API error: {task_response.get('message', 'Unknown error')}")
191
-
192
- task_id = task_response["data"]["task_id"]
193
- logger.info(f"Task created: {task_id}")
194
-
195
- for _ in range(60):
196
- task_data = check_task_status(task_id)
197
- status = task_data["data"]["task_status"]
198
-
199
- if status == "succeed":
200
- image_url = task_data["data"]["task_result"]["images"][0]["url"]
201
- try:
202
- response = requests.get(image_url)
203
- response.raise_for_status()
204
- output_dir = Path("/tmp")
205
- output_dir.mkdir(exist_ok=True)
206
- output_path = output_dir / f"kling_output_{task_id}.png"
207
- with open(output_path, "wb") as f:
208
- f.write(response.content)
209
- return output_path
210
- except Exception as e:
211
- raise HTTPException(status_code=500, detail=f"Failed to download result: {str(e)}")
212
-
213
- elif status in ("failed", "canceled"):
214
- error_msg = task_data["data"].get("task_status_msg", "Unknown error")
215
- raise HTTPException(status_code=500, detail=f"Task failed: {error_msg}")
216
-
217
- time.sleep(10)
218
-
219
- raise HTTPException(status_code=500, detail="Task timed out after 10 minutes")
220
-
221
- # ===== API ENDPOINTS =====
222
- @app.post("/generate")
223
- async def generate_image_endpoint(
224
- prompt: str = Form(...),
225
- images: List[UploadFile] = File(...)
226
- ):
227
- """Endpoint to generate an image from multiple input images and a prompt"""
228
- try:
229
- if len(images) < 2:
230
- raise HTTPException(status_code=400, detail="At least 2 images are required")
231
- if len(images) > 4:
232
- raise HTTPException(status_code=400, detail="Maximum 4 images allowed")
233
-
234
- image_contents = [await image.read() for image in images]
235
- output_path = await generate_image(image_contents, prompt)
236
-
237
- return FileResponse(
238
- path=output_path,
239
- media_type="image/png",
240
- filename=f"kling_output_{Path(output_path).stem}.png"
241
- )
242
- except Exception as e:
243
- logger.error(f"Error in /generate: {str(e)}")
244
- raise HTTPException(status_code=500, detail=str(e))
245
-
246
- @app.post("/create-razorpay-order")
247
- async def create_order_endpoint(
248
- request: Request,
249
- amount: Optional[int] = Form(None),
250
- body: Optional[CreateOrderRequest] = None
251
- ):
252
- """Create a Razorpay order (supports form-data and JSON)"""
253
- logger.info("Received create order request")
254
-
255
- if not RAZORPAY_KEY_ID or not RAZORPAY_KEY_SECRET:
256
- logger.error("Razorpay configuration missing")
257
- raise HTTPException(status_code=500, detail="Razorpay configuration missing")
258
-
259
- # Handle JSON body if provided
260
- try:
261
- if body and body.amount:
262
- amount = body.amount
263
- elif not amount:
264
- # Try to parse JSON from request body
265
- try:
266
- json_body = await request.json()
267
- amount = json_body.get('amount')
268
- except:
269
- pass
270
-
271
- if not amount or amount <= 0:
272
- raise HTTPException(status_code=422, detail="Missing or invalid 'amount' parameter")
273
-
274
- logger.info(f"Creating order with amount: {amount}")
275
- order = create_razorpay_order(amount)
276
-
277
- response_data = {
278
- "id": order["id"],
279
- "amount": order["amount"],
280
- "currency": order["currency"],
281
- "key_id": RAZORPAY_KEY_ID
282
- }
283
-
284
- logger.info(f"Order created successfully: {order['id']}")
285
- return JSONResponse(content=response_data)
286
-
287
- except HTTPException:
288
- raise
289
- except Exception as e:
290
- logger.error(f"Error creating order: {str(e)}")
291
- raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
292
-
293
- @app.post("/verify-razorpay-payment")
294
- async def verify_payment_endpoint(
295
- request: Request,
296
- razorpay_order_id: Optional[str] = Form(None),
297
- razorpay_payment_id: Optional[str] = Form(None),
298
- razorpay_signature: Optional[str] = Form(None),
299
- user_id: Optional[str] = Form(None),
300
- body: Optional[VerifyPaymentRequest] = None
301
- ):
302
- """Verify Razorpay payment signature (supports form-data and JSON)"""
303
- logger.info("Received payment verification request")
304
-
305
- try:
306
- # Handle JSON body if provided
307
- if body:
308
- razorpay_order_id = razorpay_order_id or body.razorpay_order_id
309
- razorpay_payment_id = razorpay_payment_id or body.razorpay_payment_id
310
- razorpay_signature = razorpay_signature or body.razorpay_signature
311
- user_id = user_id or body.user_id
312
- else:
313
- # Try to parse JSON from request body
314
- try:
315
- json_body = await request.json()
316
- razorpay_order_id = razorpay_order_id or json_body.get('razorpay_order_id')
317
- razorpay_payment_id = razorpay_payment_id or json_body.get('razorpay_payment_id')
318
- razorpay_signature = razorpay_signature or json_body.get('razorpay_signature')
319
- user_id = user_id or json_body.get('user_id')
320
- except:
321
- pass
322
-
323
- # Validate required fields
324
- if not all([razorpay_order_id, razorpay_payment_id, razorpay_signature]):
325
- missing_fields = []
326
- if not razorpay_order_id: missing_fields.append("razorpay_order_id")
327
- if not razorpay_payment_id: missing_fields.append("razorpay_payment_id")
328
- if not razorpay_signature: missing_fields.append("razorpay_signature")
329
-
330
- logger.error(f"Missing required fields: {missing_fields}")
331
- raise HTTPException(
332
- status_code=422,
333
- detail=f"Missing required fields: {', '.join(missing_fields)}"
334
- )
335
-
336
- logger.info(f"Verifying payment for order_id: {razorpay_order_id}")
337
- is_valid = verify_payment_signature(razorpay_order_id, razorpay_payment_id, razorpay_signature)
338
-
339
- if is_valid:
340
- if user_id and supabase:
341
- logger.info(f"Updating Supabase for user_id: {user_id}")
342
- try:
343
- supabase.table("users").update({"is_premium": True}).eq("user_id", user_id).execute()
344
- logger.info(f"Successfully updated premium status for user: {user_id}")
345
- except Exception as e:
346
- logger.error(f"Failed to update Supabase: {str(e)}")
347
- # Don't fail the payment verification if Supabase update fails
348
-
349
- return JSONResponse(content={"success": True, "message": "Payment verified successfully"})
350
- else:
351
- logger.warning(f"Payment verification failed for order: {razorpay_order_id}")
352
- return JSONResponse(content={"success": False, "message": "Payment verification failed"}, status_code=400)
353
-
354
- except HTTPException:
355
- raise
356
- except Exception as e:
357
- logger.error(f"Error verifying payment: {str(e)}")
358
- raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
359
-
360
- @app.get("/")
361
- async def index():
362
- return {
363
- "status": "Kling AI Multi-Image Generator API with Razorpay is running",
364
- "endpoints": {
365
- "generate": "POST /generate",
366
- "create_order": "POST /create-razorpay-order",
367
- "verify_payment": "POST /verify-razorpay-payment"
368
- }
369
- }
370
-
371
- @app.get("/health")
372
- async def health_check():
373
- return {
374
- "status": "healthy",
375
- "razorpay_configured": bool(RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET),
376
- "supabase_configured": bool(SUPABASE_URL and SUPABASE_KEY)
377
- }
378
-
379
- if __name__ == "__main__":
380
- import uvicorn
381
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ import logging
2
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Form, Request
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.responses import FileResponse, JSONResponse
5
+ import base64
6
+ import os
7
+ import time
8
+ import jwt
9
+ from pathlib import Path
10
+ from typing import List
11
+ import io
12
+ import razorpay
13
+ from razorpay.errors import SignatureVerificationError
14
+ from supabase import create_client, Client
15
+ from pydantic import BaseModel
16
+ from typing import Optional
17
+ from PIL import Image
18
+ from io import BytesIO
19
+ import google.generativeai as genai
20
+ import tempfile
21
+
22
+ # Configure logging
23
+ logging.basicConfig(level=logging.INFO)
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Initialize FastAPI app
27
+ app = FastAPI(title="Gemini Image Editing API with Razorpay")
28
+
29
+ # Enable CORS for frontend
30
+ app.add_middleware(
31
+ CORSMiddleware,
32
+ allow_origins=[
33
+ "https://hivili.web.app",
34
+ "http://localhost:3000",
35
+ "https://*.lovable.dev",
36
+ "https://*.sandbox.lovable.dev",
37
+ ],
38
+ allow_origin_regex=r"https://.*\.lovable\.dev|https://.*\.sandbox\.lovable\.dev",
39
+ allow_credentials=True,
40
+ allow_methods=["*"],
41
+ allow_headers=["*"],
42
+ )
43
+
44
+ # ===== API CONFIGURATION =====
45
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyDL5Rilo7ptJpUOZdY6wy8PJYUcVcnDADs")
46
+ GEMINI_MODEL = "gemini-2.5-flash-image-preview"
47
+
48
+ # Configure Gemini API
49
+ genai.configure(api_key=GEMINI_API_KEY)
50
+
51
+ # ===== RAZORPAY CONFIGURATION =====
52
+ RAZORPAY_KEY_ID = os.getenv("RAZORPAY_KEY_ID")
53
+ RAZORPAY_KEY_SECRET = os.getenv("RAZORPAY_KEY_SECRET")
54
+ razorpay_client = razorpay.Client(auth=(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET)) if RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET else None
55
+
56
+ # ===== SUPABASE CONFIGURATION =====
57
+ SUPABASE_URL = os.getenv("SUPABASE_URL")
58
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY")
59
+ supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY) if SUPABASE_URL and SUPABASE_KEY else None
60
+
61
+ # Pydantic models for JSON input validation
62
+ class CreateOrderRequest(BaseModel):
63
+ amount: int
64
+
65
+ class VerifyPaymentRequest(BaseModel):
66
+ razorpay_order_id: str
67
+ razorpay_payment_id: str
68
+ razorpay_signature: str
69
+ user_id: Optional[str] = None
70
+
71
+ class GenerateImageRequest(BaseModel):
72
+ prompt: str
73
+ user_id: Optional[str] = None
74
+
75
+ # ===== AUTHENTICATION =====
76
+ def generate_jwt_token():
77
+ """Generate JWT token for API authentication"""
78
+ payload = {
79
+ "iss": "gemini-image-editor",
80
+ "exp": int(time.time()) + 1800, # 30 minutes expiration
81
+ "nbf": int(time.time()) - 5 # Not before 5 seconds ago
82
+ }
83
+ return jwt.encode(payload, GEMINI_API_KEY, algorithm="HS256")
84
+
85
+ # ===== IMAGE PROCESSING =====
86
+ def prepare_image_base64(image_content: bytes):
87
+ """Convert image bytes to base64 without prefix"""
88
+ try:
89
+ return base64.b64encode(image_content).decode('utf-8')
90
+ except Exception as e:
91
+ logger.error(f"Image processing failed: {str(e)}")
92
+ raise HTTPException(status_code=500, detail=f"Image processing failed: {str(e)}")
93
+
94
+ def validate_image(image_content: bytes):
95
+ """Validate image meets API requirements"""
96
+ try:
97
+ size_mb = len(image_content) / (1024 * 1024)
98
+ if size_mb > 10:
99
+ raise HTTPException(status_code=400, detail="Image too large (max 10MB)")
100
+ img = Image.open(BytesIO(image_content))
101
+ if img.format.lower() not in ['png', 'jpg', 'jpeg', 'webp']:
102
+ raise HTTPException(status_code=400, detail="Unsupported image format. Use PNG, JPG, JPEG, or WEBP")
103
+ return True, ""
104
+ except Exception as e:
105
+ raise HTTPException(status_code=400, detail=f"Image validation error: {str(e)}")
106
+
107
+ def generate_gemini_image(images: List[Image.Image], prompt: str):
108
+ """Generate or edit image using Gemini API"""
109
+ try:
110
+ contents = images + [prompt]
111
+ response = genai.GenerativeModel(GEMINI_MODEL).generate_content(contents)
112
+
113
+ text_response = ""
114
+ image_path = None
115
+
116
+ for part in response.candidates[0].content.parts:
117
+ if part.text:
118
+ text_response += part.text + "\n"
119
+ elif hasattr(part, 'inline_data') and part.inline_data:
120
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
121
+ temp_path = tmp.name
122
+ generated_image = Image.open(BytesIO(part.inline_data.data))
123
+ generated_image.save(temp_path)
124
+ image_path = temp_path
125
+ logger.info(f"Generated image saved to: {temp_path} with prompt: {prompt}")
126
+
127
+ if image_path:
128
+ return image_path, ""
129
+ else:
130
+ return None, text_response.strip()
131
+ except Exception as e:
132
+ logger.error(f"Gemini API error: {str(e)}")
133
+ raise HTTPException(status_code=500, detail=f"Gemini API error: {str(e)}")
134
+
135
+ # ===== RAZORPAY FUNCTIONS =====
136
+ def create_razorpay_order(amount: int):
137
+ """Create a Razorpay order"""
138
+ try:
139
+ if not razorpay_client:
140
+ raise HTTPException(status_code=500, detail="Razorpay configuration missing")
141
+ if amount <= 0:
142
+ raise ValueError("Amount must be a positive integer")
143
+ order_data = {
144
+ "amount": amount * 100, # Convert INR to paise
145
+ "currency": "INR",
146
+ "payment_capture": 1 # Auto-capture payment
147
+ }
148
+ order = razorpay_client.order.create(data=order_data)
149
+ logger.info(f"Razorpay order created successfully: {order['id']}")
150
+ return order
151
+ except Exception as e:
152
+ logger.error(f"Failed to create Razorpay order: {str(e)}")
153
+ raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
154
+
155
+ def verify_payment_signature(order_id: str, payment_id: str, signature: str):
156
+ """Verify Razorpay payment signature"""
157
+ try:
158
+ if not razorpay_client:
159
+ raise HTTPException(status_code=500, detail="Razorpay configuration missing")
160
+ params_dict = {
161
+ "razorpay_order_id": order_id,
162
+ "razorpay_payment_id": payment_id,
163
+ "razorpay_signature": signature
164
+ }
165
+ razorpay_client.utility.verify_payment_signature(params_dict)
166
+ logger.info(f"Payment signature verified successfully for order: {order_id}")
167
+ return True
168
+ except SignatureVerificationError as e:
169
+ logger.error(f"Payment signature verification failed: {str(e)}")
170
+ return False
171
+ except Exception as e:
172
+ logger.error(f"Error verifying payment signature: {str(e)}")
173
+ raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
174
+
175
+ # ===== MAIN PROCESSING =====
176
+ async def generate_image(images: List[bytes], prompt: str, user_id: Optional[str] = None):
177
+ """Handle complete image generation workflow"""
178
+ # Validate images
179
+ for img_content in images:
180
+ if img_content:
181
+ validate_image(img_content)
182
+
183
+ # Convert bytes to PIL Images
184
+ pil_images = []
185
+ for img_content in images:
186
+ try:
187
+ img = Image.open(BytesIO(img_content))
188
+ if img.mode == "RGBA":
189
+ img = img.convert("RGBA")
190
+ pil_images.append(img)
191
+ except Exception as e:
192
+ logger.error(f"Image conversion failed: {str(e)}")
193
+ raise HTTPException(status_code=400, detail=f"Image conversion failed: {str(e)}")
194
+
195
+ if len(pil_images) < 1:
196
+ raise HTTPException(status_code=400, detail="At least one image required")
197
+
198
+ # Check user premium status if Supabase is configured
199
+ if user_id and supabase:
200
+ try:
201
+ user_data = supabase.table("users").select("is_premium").eq("user_id", user_id).execute()
202
+ if not user_data.data or not user_data.data[0].get("is_premium"):
203
+ raise HTTPException(status_code=403, detail="Premium subscription required")
204
+ except Exception as e:
205
+ logger.error(f"Supabase user check failed: {str(e)}")
206
+ raise HTTPException(status_code=500, detail=f"User verification failed: {str(e)}")
207
+
208
+ # Generate image using Gemini API
209
+ image_path, text_response = generate_gemini_image(pil_images, prompt)
210
+
211
+ if image_path:
212
+ return image_path
213
+ else:
214
+ raise HTTPException(status_code=500, detail=f"Image generation failed: {text_response or 'Unknown error'}")
215
+
216
+ # ===== API ENDPOINTS =====
217
+ @app.post("/generate")
218
+ async def generate_image_endpoint(
219
+ prompt: str = Form(...),
220
+ images: List[UploadFile] = File(...),
221
+ user_id: Optional[str] = Form(None)
222
+ ):
223
+ """Endpoint to generate or edit an image using Gemini API"""
224
+ try:
225
+ if len(images) < 1:
226
+ raise HTTPException(status_code=400, detail="At least one image required")
227
+ if len(images) > 4:
228
+ raise HTTPException(status_code=400, detail="Maximum 4 images allowed")
229
+
230
+ image_contents = [await image.read() for image in images]
231
+ output_path = await generate_image(image_contents, prompt, user_id)
232
+
233
+ return FileResponse(
234
+ path=output_path,
235
+ media_type="image/png",
236
+ filename=f"gemini_output_{Path(output_path).stem}.png"
237
+ )
238
+ except HTTPException as e:
239
+ raise
240
+ except Exception as e:
241
+ logger.error(f"Error in /generate: {str(e)}")
242
+ raise HTTPException(status_code=500, detail=str(e))
243
+
244
+ @app.post("/create-razorpay-order")
245
+ async def create_order_endpoint(
246
+ request: Request,
247
+ amount: Optional[int] = Form(None),
248
+ body: Optional[CreateOrderRequest] = None
249
+ ):
250
+ """Create a Razorpay order (supports form-data and JSON)"""
251
+ logger.info("Received create order request")
252
+
253
+ try:
254
+ if not razorpay_client:
255
+ raise HTTPException(status_code=500, detail="Razorpay configuration missing")
256
+
257
+ # Handle JSON body if provided
258
+ if body and body.amount:
259
+ amount = body.amount
260
+ elif not amount:
261
+ try:
262
+ json_body = await request.json()
263
+ amount = json_body.get('amount')
264
+ except:
265
+ pass
266
+
267
+ if not amount or amount <= 0:
268
+ raise HTTPException(status_code=422, detail="Missing or invalid 'amount' parameter")
269
+
270
+ logger.info(f"Creating order with amount: {amount}")
271
+ order = create_razorpay_order(amount)
272
+
273
+ response_data = {
274
+ "id": order["id"],
275
+ "amount": order["amount"],
276
+ "currency": order["currency"],
277
+ "key_id": RAZORPAY_KEY_ID
278
+ }
279
+
280
+ logger.info(f"Order created successfully: {order['id']}")
281
+ return JSONResponse(content=response_data)
282
+
283
+ except HTTPException:
284
+ raise
285
+ except Exception as e:
286
+ logger.error(f"Error creating order: {str(e)}")
287
+ raise HTTPException(status_code=500, detail=f"Failed to create order: {str(e)}")
288
+
289
+ @app.post("/verify-razorpay-payment")
290
+ async def verify_payment_endpoint(
291
+ request: Request,
292
+ razorpay_order_id: Optional[str] = Form(None),
293
+ razorpay_payment_id: Optional[str] = Form(None),
294
+ razorpay_signature: Optional[str] = Form(None),
295
+ user_id: Optional[str] = Form(None),
296
+ body: Optional[VerifyPaymentRequest] = None
297
+ ):
298
+ """Verify Razorpay payment signature (supports form-data and JSON)"""
299
+ logger.info("Received payment verification request")
300
+
301
+ try:
302
+ # Handle JSON body if provided
303
+ if body:
304
+ razorpay_order_id = razorpay_order_id or body.razorpay_order_id
305
+ razorpay_payment_id = razorpay_payment_id or body.razorpay_payment_id
306
+ razorpay_signature = razorpay_signature or body.razorpay_signature
307
+ user_id = user_id or body.user_id
308
+ else:
309
+ try:
310
+ json_body = await request.json()
311
+ razorpay_order_id = razorpay_order_id or json_body.get('razorpay_order_id')
312
+ razorpay_payment_id = razorpay_payment_id or json_body.get('razorpay_payment_id')
313
+ razorpay_signature = razorpay_signature or json_body.get('razorpay_signature')
314
+ user_id = user_id or json_body.get('user_id')
315
+ except:
316
+ pass
317
+
318
+ # Validate required fields
319
+ if not all([razorpay_order_id, razorpay_payment_id, razorpay_signature]):
320
+ missing_fields = []
321
+ if not razorpay_order_id: missing_fields.append("razorpay_order_id")
322
+ if not razorpay_payment_id: missing_fields.append("razorpay_payment_id")
323
+ if not razorpay_signature: missing_fields.append("razorpay_signature")
324
+
325
+ logger.error(f"Missing required fields: {missing_fields}")
326
+ raise HTTPException(
327
+ status_code=422,
328
+ detail=f"Missing required fields: {', '.join(missing_fields)}"
329
+ )
330
+
331
+ logger.info(f"Verifying payment for order_id: {razorpay_order_id}")
332
+ is_valid = verify_payment_signature(razorpay_order_id, razorpay_payment_id, razorpay_signature)
333
+
334
+ if is_valid:
335
+ if user_id and supabase:
336
+ logger.info(f"Updating Supabase for user_id: {user_id}")
337
+ try:
338
+ supabase.table("users").update({"is_premium": True}).eq("user_id", user_id).execute()
339
+ logger.info(f"Successfully updated premium status for user: {user_id}")
340
+ except Exception as e:
341
+ logger.error(f"Failed to update Supabase: {str(e)}")
342
+
343
+ return JSONResponse(content={"success": True, "message": "Payment verified successfully"})
344
+ else:
345
+ logger.warning(f"Payment verification failed for order: {razorpay_order_id}")
346
+ return JSONResponse(content={"success": False, "message": "Payment verification failed"}, status_code=400)
347
+
348
+ except HTTPException:
349
+ raise
350
+ except Exception as e:
351
+ logger.error(f"Error verifying payment: {str(e)}")
352
+ raise HTTPException(status_code=500, detail=f"Verification error: {str(e)}")
353
+
354
+ @app.get("/")
355
+ async def index():
356
+ return {
357
+ "status": "Gemini Image Editing API with Razorpay is running",
358
+ "endpoints": {
359
+ "generate": "POST /generate",
360
+ "create_order": "POST /create-razorpay-order",
361
+ "verify_payment": "POST /verify-razorpay-payment"
362
+ }
363
+ }
364
+
365
+ @app.get("/health")
366
+ async def health_check():
367
+ return {
368
+ "status": "healthy",
369
+ "razorpay_configured": bool(RAZORPAY_KEY_ID and RAZORPAY_KEY_SECRET),
370
+ "supabase_configured": bool(SUPABASE_URL and SUPABASE_KEY),
371
+ "gemini_configured": bool(GEMINI_API_KEY)
372
+ }
373
+
374
+ if __name__ == "__main__":
375
+ import uvicorn
 
 
 
 
 
376
  uvicorn.run(app, host="0.0.0.0", port=7860)
data/1.jpg ADDED

Git LFS Details

  • SHA256: 9ed6db97eca38db724e4144a9507ef07ee531dd324f3ddd97a9c78d8d58d0131
  • Pointer size: 131 Bytes
  • Size of remote file: 149 kB
data/1.webp ADDED
data/1777043.jpg ADDED

Git LFS Details

  • SHA256: 57fa80bcac85aa2af14193857983047edde29b016971221d71a6f11cf1d1d72d
  • Pointer size: 131 Bytes
  • Size of remote file: 237 kB
data/2.webp ADDED
data/2807615.jpg ADDED

Git LFS Details

  • SHA256: f2592b7ec591e6797450c2749c450d28057eb0b538b6510c7b8cb3f7df7fe989
  • Pointer size: 131 Bytes
  • Size of remote file: 244 kB
data/3.webp ADDED
data/76860.jpg ADDED
index.html ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Transformers.js - Background Removal</title>
8
+ <script type="module" crossorigin src="/assets/index-E_M5nW8h.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-SojaDS6z.css">
10
+ </head>
11
+
12
+ <body>
13
+ <h1>Background Removal w/ <a href="http://github.com/xenova/transformers.js" target="_blank">🤗 Transformers.js</a>
14
+ </h1>
15
+ <h4>Runs locally in your browser, powered by the <a href="https://huggingface.co/briaai/RMBG-1.4" target="_blank">RMBG V1.4 model</a> from <a
16
+ href="https://bria.ai/" target="_blank">BRIA AI</a>
17
+ </h4>
18
+ <div id="container">
19
+ <label id="upload-button" for="upload">
20
+ <svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
21
+ <path fill="#000"
22
+ d="M3.5 24.3a3 3 0 0 1-1.9-.8c-.5-.5-.8-1.2-.8-1.9V2.9c0-.7.3-1.3.8-1.9.6-.5 1.2-.7 2-.7h18.6c.7 0 1.3.2 1.9.7.5.6.7 1.2.7 2v18.6c0 .7-.2 1.4-.7 1.9a3 3 0 0 1-2 .8H3.6Zm0-2.7h18.7V2.9H3.5v18.7Zm2.7-2.7h13.3c.3 0 .5 0 .6-.3v-.7l-3.7-5a.6.6 0 0 0-.6-.2c-.2 0-.4 0-.5.3l-3.5 4.6-2.4-3.3a.6.6 0 0 0-.6-.3c-.2 0-.4.1-.5.3l-2.7 3.6c-.1.2-.2.4 0 .7.1.2.3.3.6.3Z">
23
+ </path>
24
+ </svg>
25
+ Click to upload image
26
+ <label id="example">(or try example)</label>
27
+ </label>
28
+ </div>
29
+ <label id="status"></label>
30
+ <input id="upload" type="file" accept="image/*" />
31
+
32
+ </body>
33
+
34
+ </html>
requirements.txt CHANGED
@@ -1,10 +1,10 @@
1
- fastapi==0.115.0
2
- uvicorn==0.30.6
3
- python-multipart==0.0.9
4
- requests==2.32.3
5
- PyJWT==2.9.0
6
- Pillow==10.4.0
7
- razorpay==1.4.1
8
- supabase==2.9.1
9
- pydantic==2.8.2
10
- google-generativeai
 
1
+ google-generativeai==0.8.3
2
+ fastapi==0.115.0
3
+ uvicorn==0.30.6
4
+ python-multipart==0.0.9
5
+ requests==2.32.3
6
+ PyJWT==2.9.0
7
+ Pillow==10.4.0
8
+ razorpay==1.4.1
9
+ supabase==2.9.1
10
+ pydantic==2.8.2
style.css ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Creating a more modern looking UI for the Gemini Image Editing App */
2
+ /* Global Styles */
3
+ :root {
4
+ --primary-color: #4f46e5;
5
+ --primary-light: #6366f1;
6
+ --primary-dark: #3730a3;
7
+ --secondary-color: #9333ea;
8
+ --accent-color: #ec4899;
9
+ --text-color: #0f172a;
10
+ --text-light: #64748b;
11
+ --bg-color: #f8fafc;
12
+ --card-bg: #ffffff;
13
+ --border-color: #e2e8f0;
14
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
15
+ --transition: all 0.3s ease;
16
+ --radius: 12px;
17
+ --header-gradient: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
18
+ }
19
+
20
+ /* Dark mode support */
21
+ .dark {
22
+ --text-color: #e2e8f0;
23
+ --text-light: #94a3b8;
24
+ --bg-color: #0f172a;
25
+ --card-bg: #1e293b;
26
+ --border-color: #334155;
27
+ }
28
+
29
+ /* Base container styling */
30
+ .gradio-container {
31
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
32
+ background-color: var(--bg-color);
33
+ color: var(--text-color);
34
+ max-width: 1200px;
35
+ margin: 0 auto;
36
+ padding: 2rem;
37
+ box-shadow: var(--shadow);
38
+ border-radius: var(--radius);
39
+ }
40
+
41
+ /* Header styling */
42
+ .header-container {
43
+ background: var(--header-gradient);
44
+ border-radius: var(--radius);
45
+ padding: 2rem;
46
+ margin-bottom: 2rem;
47
+ box-shadow: var(--shadow);
48
+ color: white;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ gap: 20px;
53
+ }
54
+
55
+ .header-container img {
56
+ width: 100px;
57
+ height: 100px;
58
+ filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.5));
59
+ transition: var(--transition);
60
+ }
61
+
62
+ .header-container img:hover {
63
+ transform: scale(1.05) rotate(5deg);
64
+ }
65
+
66
+ .header-container h1 {
67
+ font-size: 2.5rem;
68
+ font-weight: 700;
69
+ margin-bottom: 0.5rem;
70
+ background: linear-gradient(45deg, #fff, #f0f0ff);
71
+ -webkit-background-clip: text;
72
+ background-clip: text;
73
+ color: transparent;
74
+ display: inline-block;
75
+ }
76
+
77
+ .header-container a {
78
+ color: white;
79
+ text-decoration: none;
80
+ font-weight: 600;
81
+ border-bottom: 2px solid rgba(255, 255, 255, 0.5);
82
+ transition: var(--transition);
83
+ padding-bottom: 2px;
84
+ }
85
+
86
+ .header-container a:hover {
87
+ border-color: white;
88
+ }
89
+
90
+ /* Accordion styling */
91
+ .gr-accordion {
92
+ border: none !important;
93
+ background: var(--card-bg);
94
+ border-radius: var(--radius);
95
+ overflow: hidden;
96
+ box-shadow: var(--shadow);
97
+ margin-bottom: 1.5rem;
98
+ transition: var(--transition);
99
+ }
100
+
101
+ .gr-accordion:hover {
102
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
103
+ }
104
+
105
+ .gr-accordion-title {
106
+ background-color: var(--primary-color);
107
+ color: white !important;
108
+ padding: 1rem 1.5rem;
109
+ font-weight: 600;
110
+ cursor: pointer;
111
+ }
112
+
113
+
114
+ /* Input fields */
115
+ .gr-input, .gr-textarea {
116
+ width: 100%;
117
+ padding: 0.75rem 1rem;
118
+ border: 1px solid var(--border-color);
119
+ border-radius: var(--radius);
120
+ background-color: var(--card-bg);
121
+ transition: var(--transition);
122
+ font-size: 1rem;
123
+ color: var(--text-color);
124
+ }
125
+
126
+ .gr-input:focus, .gr-textarea:focus {
127
+ border-color: var(--primary-color);
128
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.2);
129
+ outline: none;
130
+ }
131
+
132
+ .gr-form .gr-form-label {
133
+ font-weight: 600;
134
+ color: var(--text-color);
135
+ margin-bottom: 0.5rem;
136
+ }
137
+
138
+ /* Adding Dark mode handling */
139
+ /* Generate button - Modified to use header gradient in both light and dark mode */
140
+ .gr-button.gr-button-primary,
141
+ button.generate-btn,
142
+ #component-54 > div.svelte-1pf7t34 > div > button {
143
+ background: var(--header-gradient) !important;
144
+ color: white !important;
145
+ border: none !important;
146
+ border-radius: var(--radius) !important;
147
+ padding: 0.75rem 1.5rem !important;
148
+ font-size: 1rem !important;
149
+ font-weight: 600 !important;
150
+ cursor: pointer !important;
151
+ transition: var(--transition) !important;
152
+ width: 100% !important;
153
+ text-transform: uppercase !important;
154
+ letter-spacing: 1px !important;
155
+ box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.4) !important;
156
+ }
157
+
158
+ .gr-button.gr-button-primary:hover,
159
+ button.generate-btn:hover,
160
+ #component-54 > div.svelte-1pf7t34 > div > button:hover {
161
+ transform: translateY(-2px) !important;
162
+ box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.4) !important;
163
+ }
164
+
165
+ .gr-button.gr-button-primary:active,
166
+ button.generate-btn:active,
167
+ #component-54 > div.svelte-1pf7t34 > div > button:active {
168
+ transform: translateY(1px) !important;
169
+ }
170
+
171
+ /* Gallery output */
172
+ .gr-gallery {
173
+ background-color: var(--card-bg);
174
+ border-radius: var(--radius);
175
+ padding: 1rem;
176
+ box-shadow: var(--shadow);
177
+ min-height: 300px;
178
+ }
179
+
180
+ .gr-gallery img {
181
+ border-radius: calc(var(--radius) - 4px);
182
+ transition: var(--transition);
183
+ object-fit: contain !important;
184
+ max-height: 100% !important;
185
+ max-width: 100% !important;
186
+ }
187
+
188
+ .gr-gallery img:hover {
189
+ transform: scale(1.02);
190
+ }
191
+
192
+ /* Output text area */
193
+ .output-text {
194
+ background-color: var(--card-bg);
195
+ border-radius: var(--radius);
196
+ padding: 1.5rem;
197
+ box-shadow: var(--shadow);
198
+ line-height: 1.6;
199
+ min-height: 100px;
200
+ color: var(--text-color);
201
+ }
202
+
203
+ /* Examples section - Fixed for dark mode */
204
+ .gr-examples-header {
205
+ font-weight: 600 !important;
206
+ margin: 2rem 0 1rem !important;
207
+ padding-bottom: 0.5rem !important;
208
+ /* Removed border-bottom to avoid duplicate lines */
209
+ color: var(--primary-light) !important;
210
+ font-size: 1.5rem !important;
211
+ display: block !important;
212
+ visibility: visible !important;
213
+ }
214
+
215
+ /* Handling duplicate lines coming up below the examples header */
216
+ .gradio-container hr {
217
+ display: none !important;
218
+ }
219
+
220
+ /* Fix horizontal separator styling if needed */
221
+ .gradio-container hr:first-of-type {
222
+ display: block !important;
223
+ border-top: 2px solid var(--primary-light) !important;
224
+ margin: 1rem 0 2rem 0 !important;
225
+ }
226
+
227
+ /* Fix dark mode examples grid */
228
+ .gr-examples {
229
+ display: grid;
230
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
231
+ gap: 1rem;
232
+ margin-top: 1.5rem;
233
+ }
234
+
235
+ /* Fix dark mode examples background */
236
+ .gr-sample {
237
+ background-color: var(--card-bg) !important;
238
+ border-radius: var(--radius);
239
+ overflow: hidden;
240
+ box-shadow: var(--shadow);
241
+ transition: var(--transition);
242
+ cursor: pointer;
243
+ }
244
+
245
+ .gr-sample:hover {
246
+ transform: translateY(-2px);
247
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
248
+ }
249
+
250
+ .gr-sample img {
251
+ width: 100%;
252
+ height: 150px;
253
+ object-fit: cover;
254
+ }
255
+
256
+ /* Examples table styling fixes for dark mode */
257
+ table {
258
+ background-color: transparent !important;
259
+ color: var(--text-color) !important;
260
+ }
261
+
262
+ table tr {
263
+ background-color: var(--card-bg) !important;
264
+ color: var(--text-color) !important;
265
+ }
266
+
267
+ table td {
268
+ background-color: var(--card-bg) !important;
269
+ color: var(--text-color) !important;
270
+ border-color: var(--border-color) !important;
271
+ }
272
+
273
+ /* Responsive adjustments */
274
+ @media (max-width: 768px) {
275
+ .gradio-container {
276
+ padding: 1rem;
277
+ }
278
+
279
+ .header-container {
280
+ flex-direction: column;
281
+ text-align: center;
282
+ padding: 1.5rem;
283
+ }
284
+
285
+ .header-container h1 {
286
+ font-size: 2rem;
287
+ }
288
+ }
289
+
290
+ /* Custom scrollbar */
291
+ ::-webkit-scrollbar {
292
+ width: 8px;
293
+ height: 8px;
294
+ }
295
+
296
+ ::-webkit-scrollbar-track {
297
+ background: var(--bg-color);
298
+ }
299
+
300
+ ::-webkit-scrollbar-thumb {
301
+ background: var(--primary-light);
302
+ border-radius: 4px;
303
+ }
304
+
305
+ ::-webkit-scrollbar-thumb:hover {
306
+ background: var(--primary-color);
307
+ }
308
+
309
+ /* Ensure proper viewport handling for mobile */
310
+ @media screen and (max-width: 767px) {
311
+ body, html {
312
+ overflow-x: hidden !important;
313
+ width: 100vw !important;
314
+ max-width: 100% !important;
315
+ }
316
+
317
+ /* Force central alignment for the entire app container */
318
+ .gradio-container {
319
+ padding: 1rem !important;
320
+ margin: 0 auto !important;
321
+ max-width: 100% !important;
322
+ width: 100% !important;
323
+ left: 0 !important;
324
+ right: 0 !important;
325
+ box-sizing: border-box !important;
326
+ overflow-x: hidden !important;
327
+ }
328
+
329
+ /* Fix header container on mobile */
330
+ .header-container {
331
+ flex-direction: column !important;
332
+ text-align: center !important;
333
+ padding: 1rem !important;
334
+ width: 100% !important;
335
+ box-sizing: border-box !important;
336
+ margin-left: auto !important;
337
+ margin-right: auto !important;
338
+ }
339
+
340
+ /* Ensure all content blocks are properly centered */
341
+ .main-content,
342
+ .input-column,
343
+ .output-column,
344
+ .gr-form,
345
+ .gr-panel,
346
+ .gr-box,
347
+ .gr-input,
348
+ .gr-text-input,
349
+ .gr-gallery,
350
+ .gr-button,
351
+ #component-54 > div.svelte-1pf7t34 > div > button {
352
+ width: 100% !important;
353
+ max-width: 100% !important;
354
+ margin-left: auto !important;
355
+ margin-right: auto !important;
356
+ box-sizing: border-box !important;
357
+ }
358
+
359
+
360
+ /* Fix examples area */
361
+ .gr-examples {
362
+ grid-template-columns: 1fr !important;
363
+ width: 100% !important;
364
+ }
365
+
366
+ /* Fix example tables on mobile */
367
+ table {
368
+ table-layout: fixed !important;
369
+ width: 100% !important;
370
+ }
371
+
372
+ table td {
373
+ word-break: break-word !important;
374
+ }
375
+ }
376
+
377
+ /* Fix for extreme narrow screens (small Android phones) */
378
+ @media screen and (max-width: 380px) {
379
+ .header-container h1 {
380
+ font-size: 1.8rem !important;
381
+ }
382
+
383
+ .header-container p {
384
+ font-size: 0.9rem !important;
385
+ }
386
+
387
+ /* Stack everything vertically */
388
+ .gr-panel {
389
+ display: flex !important;
390
+ flex-direction: column !important;
391
+ }
392
+ }
393
+
394
+ /* Custom fix for the container to always stay centered on any device */
395
+ #gradio-app {
396
+ display: flex !important;
397
+ justify-content: center !important;
398
+ width: 100% !important;
399
+ max-width: 100% !important;
400
+ margin: 0 auto !important;
401
+ overflow-x: hidden !important;
402
+ }
403
+
404
+ /* Ensure the root container doesn't cause horizontal scroll */
405
+ .root {
406
+ max-width: 100% !important;
407
+ overflow-x: hidden !important;
408
+ }