samiya-data-scientist commited on
Commit
2c83861
Β·
verified Β·
1 Parent(s): 38f21d6

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +1371 -0
  2. best.pt +3 -0
  3. requirements.txt +17 -0
app.py ADDED
@@ -0,0 +1,1371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import numpy as np
4
+ from ultralytics import YOLO
5
+ from sahi import AutoDetectionModel
6
+ from sahi.predict import get_sliced_prediction
7
+ import easyocr
8
+ from sklearn.cluster import DBSCAN
9
+ from PIL import Image
10
+ import json
11
+ import tempfile
12
+ import shutil
13
+ import base64
14
+ import io
15
+ import gradio as gr
16
+ import sys
17
+ import torch
18
+ import openai
19
+ import google.generativeai as genai
20
+ from dotenv import load_dotenv
21
+
22
+ # Fix for gradio-client schema generation bug
23
+ # This patches the TypeError: argument of type 'bool' is not iterable
24
+ # and APIInfoParseError: Cannot parse schema True
25
+ try:
26
+ from gradio_client import utils as gradio_client_utils
27
+
28
+ # Patch get_type to handle boolean schemas
29
+ original_get_type = gradio_client_utils.get_type
30
+
31
+ def patched_get_type(schema):
32
+ # Handle case where schema is a boolean instead of dict
33
+ if isinstance(schema, bool):
34
+ return "bool"
35
+ return original_get_type(schema)
36
+
37
+ gradio_client_utils.get_type = patched_get_type
38
+
39
+ # Patch _json_schema_to_python_type to handle boolean additionalProperties
40
+ original_json_schema_to_python_type = gradio_client_utils._json_schema_to_python_type
41
+
42
+ def patched_json_schema_to_python_type(schema, defs=None):
43
+ # Handle case where schema itself is a boolean
44
+ if isinstance(schema, bool):
45
+ return "bool"
46
+ # Handle case where additionalProperties is a boolean
47
+ if isinstance(schema, dict) and 'additionalProperties' in schema:
48
+ if isinstance(schema['additionalProperties'], bool):
49
+ # If additionalProperties is True, it means any additional properties are allowed
50
+ # Return a generic dict type
51
+ return "dict"
52
+ return original_json_schema_to_python_type(schema, defs)
53
+
54
+ gradio_client_utils._json_schema_to_python_type = patched_json_schema_to_python_type
55
+ print("βœ… Applied gradio-client schema generation fix (comprehensive)")
56
+ except Exception as e:
57
+ print(f"⚠️ Could not apply gradio-client fix: {e}")
58
+ import traceback
59
+ traceback.print_exc()
60
+
61
+ # Force UTF-8 encoding
62
+ try: sys.stdout.reconfigure(encoding='utf-8')
63
+ except: pass
64
+
65
+ # Check if running on Hugging Face Spaces
66
+ is_spaces_env = os.getenv('SPACE_ID') or os.getenv('SYSTEM') == 'spaces' or os.path.exists('/.dockerenv')
67
+
68
+ # Load environment variables
69
+ # Note: Don't use load_dotenv() on Spaces as it might interfere with HF secrets
70
+ if not is_spaces_env:
71
+ load_dotenv() # Only load .env file locally
72
+
73
+ # --- Configuration (Simulating Colab Secrets) ---
74
+ # Try multiple possible environment variable names
75
+ OPENAI_API_KEY = os.getenv('OPENAI_API_KEY') or os.getenv('OPENAI_KEY')
76
+ GEMINI_API_KEY = os.getenv('GEMINI_API_KEY') or os.getenv('GEMINI_KEY')
77
+ HF_TOKEN = os.getenv('HF_TOKEN')
78
+
79
+ # Strip whitespace if keys exist
80
+ if OPENAI_API_KEY:
81
+ OPENAI_API_KEY = OPENAI_API_KEY.strip()
82
+ if GEMINI_API_KEY:
83
+ GEMINI_API_KEY = GEMINI_API_KEY.strip()
84
+
85
+ # Validate API keys - OpenAI keys start with "sk-", Gemini keys start with "AIza"
86
+ if OPENAI_API_KEY and not OPENAI_API_KEY.startswith('sk-'):
87
+ if OPENAI_API_KEY.startswith('AIza'):
88
+ print("⚠️ WARNING: OPENAI_API_KEY appears to be a Gemini API key (starts with 'AIza')")
89
+ print(" β†’ OpenAI API keys should start with 'sk-'")
90
+ print(" β†’ Please get your OpenAI API key from: https://platform.openai.com/account/api-keys")
91
+ print(" β†’ For now, OPENAI_API_KEY will be ignored. Using Gemini only.")
92
+ OPENAI_API_KEY = None # Clear invalid key
93
+ else:
94
+ print(f"⚠️ WARNING: OPENAI_API_KEY format looks incorrect (should start with 'sk-')")
95
+ print(f" β†’ Current key starts with: {OPENAI_API_KEY[:5]}...")
96
+ print(" β†’ Please verify your OpenAI API key at: https://platform.openai.com/account/api-keys")
97
+ OPENAI_API_KEY = None # Clear invalid key
98
+
99
+ # Validate API keys - OpenAI keys start with "sk-", Gemini keys start with "AIza"
100
+ if OPENAI_API_KEY and not OPENAI_API_KEY.startswith('sk-'):
101
+ if OPENAI_API_KEY.startswith('AIza'):
102
+ print("⚠️ WARNING: OPENAI_API_KEY appears to be a Gemini API key (starts with 'AIza')")
103
+ print(" β†’ OpenAI API keys should start with 'sk-'")
104
+ print(" β†’ Please get your OpenAI API key from: https://platform.openai.com/account/api-keys")
105
+ print(" β†’ For now, OPENAI_API_KEY will be ignored. Using Gemini only.")
106
+ OPENAI_API_KEY = None # Clear invalid key
107
+ else:
108
+ print(f"⚠️ WARNING: OPENAI_API_KEY format looks incorrect (should start with 'sk-')")
109
+ OPENAI_API_KEY = None # Clear invalid key
110
+
111
+ # Debug: Print API key status (without showing actual keys)
112
+ print("=" * 60)
113
+ print("πŸ”‘ API Keys Status Check (Startup):")
114
+ print(f"OPENAI_API_KEY: {'βœ… Found' if OPENAI_API_KEY else '❌ Not Found'}")
115
+ print(f"GEMINI_API_KEY: {'βœ… Found' if GEMINI_API_KEY else '❌ Not Found'}")
116
+ if OPENAI_API_KEY:
117
+ print(f" OpenAI Key Length: {len(OPENAI_API_KEY)} characters")
118
+ print(f" Preview: {OPENAI_API_KEY[:10]}...{OPENAI_API_KEY[-5:]}")
119
+ if GEMINI_API_KEY:
120
+ print(f" Gemini Key Length: {len(GEMINI_API_KEY)} characters")
121
+ print(f" Preview: {GEMINI_API_KEY[:10]}...{GEMINI_API_KEY[-5:]}")
122
+
123
+ # Debug: List all environment variables containing API/KEY
124
+ if is_spaces_env:
125
+ print("\nπŸ” All Environment Variables with 'API' or 'KEY':")
126
+ api_env_vars = {k: v for k, v in os.environ.items() if 'API' in k.upper() or 'KEY' in k.upper()}
127
+ if api_env_vars:
128
+ for key in sorted(api_env_vars.keys()):
129
+ val = api_env_vars[key]
130
+ print(f" {key}: {'*' * min(20, len(val))} (length: {len(val)})")
131
+ else:
132
+ print(" ⚠️ No environment variables found with 'API' or 'KEY' in name!")
133
+ print(" β†’ Make sure secrets are named EXACTLY: OPENAI_API_KEY and GEMINI_API_KEY")
134
+ print(" β†’ Go to Space Settings β†’ Secrets to verify")
135
+ print(" β†’ **RESTART THE SPACE** after adding secrets")
136
+
137
+ print("=" * 60)
138
+
139
+ print(f"Device available: {'cuda:0' if torch.cuda.is_available() else 'cpu'}")
140
+
141
+ class GodModeDetector:
142
+ def __init__(self, model_path):
143
+ print("Loading YOLO + SAHI...")
144
+
145
+ # Check if model file exists and is valid
146
+ if not os.path.exists(model_path):
147
+ raise FileNotFoundError(f"Model file not found: {model_path}")
148
+
149
+ # Check file size (should be at least 1MB for a valid model)
150
+ file_size = os.path.getsize(model_path)
151
+ print(f"Model file size: {file_size / (1024*1024):.2f} MB")
152
+
153
+ if file_size < 1024 * 1024: # Less than 1MB is suspicious
154
+ raise ValueError(f"Model file too small ({file_size} bytes). File might be corrupted or incomplete.")
155
+
156
+ # Check if file starts with valid PyTorch magic bytes
157
+ with open(model_path, 'rb') as f:
158
+ first_bytes = f.read(4)
159
+ # PyTorch files typically start with specific bytes
160
+ if first_bytes[:2] != b'PK': # ZIP format (PyTorch models are ZIP archives)
161
+ print(f"⚠️ Warning: File doesn't appear to be a valid PyTorch model (first bytes: {first_bytes.hex()})")
162
+
163
+ # Local adaptation: Auto-select device to prevent crash
164
+ device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
165
+
166
+ try:
167
+ self.yolo = AutoDetectionModel.from_pretrained(
168
+ model_type='yolov8', model_path=model_path,
169
+ confidence_threshold=0.2, device=device
170
+ )
171
+ print("βœ… YOLO model loaded successfully")
172
+ except Exception as e:
173
+ error_msg = f"Failed to load YOLO model from {model_path}.\n"
174
+ error_msg += f"Error: {str(e)}\n\n"
175
+ error_msg += "Possible causes:\n"
176
+ error_msg += "1. Model file is corrupted or incomplete\n"
177
+ error_msg += "2. Model file is not a valid PyTorch .pt file\n"
178
+ error_msg += "3. File upload was incomplete\n\n"
179
+ error_msg += "Solution: Re-upload the best.pt file to your Space."
180
+ raise RuntimeError(error_msg) from e
181
+ print("Loading EasyOCR...")
182
+ # Local adaptation: Auto-select GPU
183
+ self.ocr = easyocr.Reader(['en'], gpu=torch.cuda.is_available())
184
+
185
+ def enhance_image(self, image):
186
+ """CLAHE: Contrast Limited Adaptive Histogram Equalization"""
187
+ if len(image.shape) == 3:
188
+ lab = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
189
+ l, a, b = cv2.split(lab)
190
+ clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
191
+ l = clahe.apply(l)
192
+ enhanced = cv2.merge([l, a, b])
193
+ return cv2.cvtColor(enhanced, cv2.COLOR_LAB2BGR)
194
+ return image
195
+
196
+ def merge_paragraphs(self, text_detections, eps=30, min_samples=2):
197
+ """DBSCAN: Merges fragmented text into paragraphs"""
198
+ if len(text_detections) < 2: return text_detections
199
+
200
+ centroids = [[(d['bbox'][0]+d['bbox'][2])/2, (d['bbox'][1]+d['bbox'][3])/2] for d in text_detections]
201
+ clustering = DBSCAN(eps=eps, min_samples=min_samples).fit(centroids)
202
+
203
+ merged = []
204
+ for label in set(clustering.labels_):
205
+ if label == -1: # Noise
206
+ for i, l in enumerate(clustering.labels_):
207
+ if l == -1: merged.append(text_detections[i])
208
+ else:
209
+ cluster_texts = [text_detections[i] for i, l in enumerate(clustering.labels_) if l == label]
210
+ x1 = min([t['bbox'][0] for t in cluster_texts])
211
+ y1 = min([t['bbox'][1] for t in cluster_texts])
212
+ x2 = max([t['bbox'][2] for t in cluster_texts])
213
+ y2 = max([t['bbox'][3] for t in cluster_texts])
214
+ text = " ".join([t['text'] for t in cluster_texts])
215
+ merged.append({'class': 'paragraph', 'text': text, 'bbox': [x1, y1, x2, y2]})
216
+ return merged
217
+
218
+ def add_cognition(self, detections, img_w, img_h):
219
+ """Cognition Engine: Adds Size, Shape, Position logic"""
220
+ for det in detections:
221
+ x1, y1, x2, y2 = det['bbox']
222
+ w, h = x2-x1, y2-y1
223
+ area = w * h
224
+
225
+ # Size
226
+ if area > (img_w * img_h * 0.3): det['size'] = "Large Container"
227
+ elif area > (img_w * img_h * 0.05): det['size'] = "Medium Element"
228
+ else: det['size'] = "Small/Icon"
229
+
230
+ # Shape
231
+ ratio = w / h if h > 0 else 1
232
+ if 0.9 <= ratio <= 1.1: det['shape'] = "Square"
233
+ elif ratio > 2: det['shape'] = "Wide Rectangle"
234
+ else: det['shape'] = "Rectangle"
235
+
236
+ # Position
237
+ cx, cy = (x1+x2)/2, (y1+y2)/2
238
+ det['position'] = "Top" if cy < img_h*0.33 else "Bottom" if cy > img_h*0.66 else "Middle"
239
+ det['position'] += " Left" if cx < img_w*0.33 else " Right" if cx > img_w*0.66 else " Center"
240
+ return detections
241
+
242
+ def detect(self, img_path):
243
+ img = cv2.imread(img_path)
244
+ img = self.enhance_image(img) # CLAHE
245
+ h, w = img.shape[:2]
246
+
247
+ # 1. YOLO + SAHI
248
+ res = get_sliced_prediction(img, self.yolo, slice_height=640, slice_width=640, overlap_height_ratio=0.2, overlap_width_ratio=0.2)
249
+ dets = [{'class': p.category.name, 'bbox': [int(p.bbox.minx), int(p.bbox.miny), int(p.bbox.maxx), int(p.bbox.maxy)]} for p in res.object_prediction_list]
250
+
251
+ # 2. OCR
252
+ text_dets = []
253
+ for bbox, text, conf in self.ocr.readtext(img):
254
+ x = [p[0] for p in bbox]; y = [p[1] for p in bbox]
255
+ text_dets.append({'class': 'text', 'text': text, 'bbox': [int(min(x)), int(min(y)), int(max(x)), int(max(y))]})
256
+
257
+ # 3. DBSCAN Merging
258
+ merged_text = self.merge_paragraphs(text_dets)
259
+
260
+ # 4. Combine & Cognition
261
+ all_dets = dets + merged_text
262
+ final_dets = self.add_cognition(all_dets, w, h)
263
+
264
+ return final_dets
265
+
266
+ def visualize(self, img_path, dets):
267
+ img = cv2.imread(img_path)
268
+ for d in dets:
269
+ color = (0,255,0) if d.get('class') == 'paragraph' else (255,0,0)
270
+ cv2.rectangle(img, (d['bbox'][0], d['bbox'][1]), (d['bbox'][2], d['bbox'][3]), color, 2)
271
+ return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
272
+
273
+ class GodModeGenerator:
274
+ def __init__(self):
275
+ # Check OpenAI first - if available, prefer it over Gemini
276
+ # User can set prefer_gemini = False to use OpenAI directly
277
+ self.use_openai = False
278
+ self.prefer_gemini = False # Changed to False - use OpenAI if available
279
+
280
+ # Check for OpenAI key (try multiple sources)
281
+ openai_key = OPENAI_API_KEY or os.getenv('OPENAI_API_KEY') or os.getenv('OPENAI_KEY')
282
+ if openai_key:
283
+ openai_key = openai_key.strip()
284
+ if len(openai_key) >= 10: # Valid key length
285
+ try:
286
+ self.openai_available = True
287
+ self.client = openai.OpenAI(api_key=openai_key)
288
+ print(f"βœ… OpenAI initialized (key length: {len(openai_key)})")
289
+ except Exception as e:
290
+ print(f"❌ OpenAI initialization failed: {e}")
291
+ self.openai_available = False
292
+ else:
293
+ print(f"⚠️ OpenAI key too short ({len(openai_key)} chars), ignoring")
294
+ self.openai_available = False
295
+ else:
296
+ self.openai_available = False
297
+ print("❌ OpenAI key not found at initialization")
298
+
299
+ # Fallback Gemini setup
300
+ self.models_to_try = []
301
+ try:
302
+ if GEMINI_API_KEY: genai.configure(api_key=GEMINI_API_KEY)
303
+
304
+ available = [m.name.replace("models/", "") for m in genai.list_models()]
305
+ # Priority: gemini-1.5-flash has the BEST free tier limits (60 requests/minute)
306
+ # STRICTLY avoid ALL experimental models (exp, pro-exp, etc.)
307
+ priority = [
308
+ "gemini-1.5-flash", # BEST free tier: 60 req/min, 1500 req/day
309
+ "gemini-1.5-pro", # Good free tier: 2 req/min, 50 req/day
310
+ ]
311
+ # STRICT filtering: Only add stable models, EXCLUDE all experimental ones
312
+ for p in priority:
313
+ if p in available:
314
+ # Double check: no "exp" anywhere in name
315
+ if "exp" not in p.lower() and "experimental" not in p.lower():
316
+ self.models_to_try.append(p)
317
+ if not self.models_to_try:
318
+ self.models_to_try = ["gemini-1.5-flash"]
319
+
320
+ # If OpenAI is available, we can skip Gemini on quota errors
321
+ print(f"Available Gemini models to try: {self.models_to_try}")
322
+ if self.openai_available:
323
+ print("βœ… OpenAI available as fallback if Gemini quota exceeded")
324
+
325
+ print(f"Available Gemini models to try: {self.models_to_try}")
326
+ except:
327
+ self.models_to_try = ["gemini-1.5-flash"]
328
+
329
+ def encode_image(self, image_path):
330
+ with open(image_path, "rb") as image_file:
331
+ return base64.b64encode(image_file.read()).decode('utf-8')
332
+
333
+ def generate(self, img_path, dets, fw):
334
+ # Declare global variables at the start of function
335
+ global GEMINI_API_KEY
336
+
337
+ print(f"Starting Generation for {fw}...")
338
+
339
+ # Re-check API keys at runtime (in case they were added after app start)
340
+ # Try multiple methods to get keys
341
+ current_openai_key = os.getenv('OPENAI_API_KEY') or os.getenv('OPENAI_KEY') or OPENAI_API_KEY
342
+ current_gemini_key = os.getenv('GEMINI_API_KEY') or os.getenv('GEMINI_KEY') or GEMINI_API_KEY
343
+
344
+ # Strip whitespace if keys exist
345
+ if current_openai_key:
346
+ current_openai_key = current_openai_key.strip()
347
+ if current_gemini_key:
348
+ current_gemini_key = current_gemini_key.strip()
349
+
350
+ # Filter out empty strings
351
+ if current_openai_key and len(current_openai_key) < 10:
352
+ current_openai_key = None
353
+ if current_gemini_key and len(current_gemini_key) < 10:
354
+ current_gemini_key = None
355
+
356
+ print(f"πŸ” Runtime API Key Check:")
357
+ print(f" OPENAI_API_KEY: {'βœ… Found' if current_openai_key else '❌ Not Found'}")
358
+ if current_openai_key:
359
+ print(f" Length: {len(current_openai_key)} chars")
360
+ print(f" Preview: {current_openai_key[:10]}...{current_openai_key[-5:]}")
361
+ print(f" GEMINI_API_KEY: {'βœ… Found' if current_gemini_key else '❌ Not Found'}")
362
+ if current_gemini_key:
363
+ print(f" Length: {len(current_gemini_key)} chars")
364
+ print(f" Preview: {current_gemini_key[:10]}...{current_gemini_key[-5:]}")
365
+
366
+ # Also check self.openai_available (from initialization)
367
+ print(f" self.openai_available: {self.openai_available}")
368
+ print(f" Global OPENAI_API_KEY: {'βœ…' if OPENAI_API_KEY else '❌'}")
369
+ print(f" Global GEMINI_API_KEY: {'βœ…' if GEMINI_API_KEY else '❌'}")
370
+
371
+ # Update availability if keys are now present
372
+ openai_available_now = bool(current_openai_key) or self.openai_available
373
+ gemini_available_now = bool(current_gemini_key) or bool(GEMINI_API_KEY)
374
+
375
+ if current_openai_key and not self.openai_available:
376
+ print("πŸ”„ OpenAI key detected at runtime, initializing...")
377
+ try:
378
+ self.openai_available = True
379
+ self.client = openai.OpenAI(api_key=current_openai_key)
380
+ print("βœ… OpenAI client initialized successfully")
381
+ except Exception as e:
382
+ print(f"❌ Failed to initialize OpenAI client: {e}")
383
+ openai_available_now = False
384
+
385
+ if current_gemini_key:
386
+ if not GEMINI_API_KEY:
387
+ print("πŸ”„ Gemini key detected at runtime...")
388
+ try:
389
+ genai.configure(api_key=current_gemini_key)
390
+ print("βœ… Gemini configured successfully")
391
+ except Exception as e:
392
+ print(f"❌ Failed to configure Gemini: {e}")
393
+ # Update global variable for this session
394
+ GEMINI_API_KEY = current_gemini_key
395
+
396
+ # Final check - use the best available option
397
+ print(f"\nπŸ“Š Final API Availability:")
398
+ print(f" OpenAI: {openai_available_now} (self.openai_available: {self.openai_available})")
399
+ print(f" Gemini: {gemini_available_now} (GEMINI_API_KEY: {bool(GEMINI_API_KEY)})")
400
+
401
+ # Check if API keys are available (use current runtime values)
402
+ if not openai_available_now and not gemini_available_now:
403
+ error_msg = "❌ No API keys found!\n\n"
404
+ error_msg += "πŸ” Debugging Info:\n"
405
+ error_msg += f"- OPENAI_API_KEY: {'βœ… Found' if current_openai_key else '❌ Not Found'}\n"
406
+ error_msg += f"- GEMINI_API_KEY: {'βœ… Found' if current_gemini_key else '❌ Not Found'}\n\n"
407
+ error_msg += "πŸ’‘ Solutions:\n"
408
+ error_msg += "1. Go to Space Settings β†’ Secrets\n"
409
+ error_msg += "2. Verify keys are named EXACTLY: OPENAI_API_KEY and GEMINI_API_KEY\n"
410
+ error_msg += "3. Make sure keys don't have extra spaces\n"
411
+ error_msg += "4. **RESTART THE SPACE** after adding secrets (Settings β†’ Restart this Space)\n"
412
+ error_msg += "5. Check Logs tab to see if keys are detected at startup\n\n"
413
+ error_msg += "πŸ“ Note: After adding secrets, you MUST restart the Space for them to load!"
414
+ return {"error": error_msg}
415
+
416
+ # Track which API was used
417
+ self.api_used = None
418
+
419
+ # Construct Ultimate Prompt
420
+ prompt = f"""
421
+ You are a **World-Class Senior Full-Stack Engineer**.
422
+ Convert this UI Image and JSON detections into **100% Complete, Pixel-Perfect, Fully Interactive {fw} Code**.
423
+
424
+ ### πŸ”΄ CRITICAL - FRAMEWORK REQUIREMENT:
425
+ **SELECTED FRAMEWORK: {fw}**
426
+
427
+ You MUST generate code ONLY for {fw}. Do NOT mix frameworks.
428
+
429
+ **IF SELECTED = "HTML/CSS":**
430
+ - Generate: `index.html`, `style.css`, `script.js`
431
+ - Use: Vanilla JavaScript, FontAwesome icons
432
+ - NO React, NO Vue, NO other frameworks
433
+
434
+ **IF SELECTED = "React":**
435
+ - Generate: ONLY React components (`src/components/*.jsx` files)
436
+ - DO NOT generate: package.json, App.jsx, public/index.html, or any other project files
437
+ - Use: React hooks, lucide-react icons, Tailwind CSS classes
438
+ - MANDATORY: Use Tailwind CSS utility classes ONLY (NO separate CSS files, NO inline styles)
439
+ - All styling MUST be done with Tailwind classes like: `className="flex items-center justify-between p-4 bg-white rounded-lg shadow-md"`
440
+ - NO vanilla HTML files with `<script>` tags
441
+
442
+ **IF SELECTED = "Vue":**
443
+ - Generate: `package.json`, `src/App.vue`, `src/components/*.vue`
444
+ - Use: Vue composition API, Vue icons
445
+
446
+ **IF SELECTED = "Angular":**
447
+ - Generate: `package.json`, `src/app/app.component.ts`, `src/app/app.component.html`, `src/app/app.component.css`
448
+ - Use: Angular components, Angular Material icons
449
+
450
+ **IF SELECTED = "Flutter":**
451
+ - Generate: `pubspec.yaml`, `lib/main.dart`, `lib/screens/*.dart`, `lib/widgets/*.dart`
452
+ - Use: Flutter widgets, Material icons
453
+
454
+ **IF SELECTED = "Next.js":**
455
+ - Generate: `package.json`, `pages/index.js`, `components/*.jsx`, `styles/globals.css`
456
+ - Use: Next.js pages, lucide-react icons
457
+
458
+ **IF SELECTED = "Svelte":**
459
+ - Generate: `package.json`, `src/App.svelte`, `src/components/*.svelte`
460
+ - Use: Svelte reactive syntax
461
+
462
+ **IF SELECTED = "SolidJS":**
463
+ - Generate: `package.json`, `src/App.jsx`, `src/components/*.jsx`
464
+ - Use: Solid.js reactive primitives
465
+
466
+ ### ⚠️ CRITICAL REQUIREMENT - CLICKABILITY:
467
+ **ABSOLUTELY MANDATORY:** EVERY interactive element MUST have a working click handler. NO EXCEPTIONS.
468
+
469
+ ### 🎯 CLICKABILITY IMPLEMENTATION BY FRAMEWORK:
470
+
471
+ **FOR HTML/CSS/JavaScript:**
472
+ ```html
473
+ <!-- Button Example -->
474
+ <button onclick="handleLogin()" class="btn-primary">Login</button>
475
+
476
+ <!-- Text Link Example -->
477
+ <a href="#" onclick="showForgotPassword(); return false;">Forgot Password?</a>
478
+
479
+ <!-- Icon Example -->
480
+ <i class="fa-solid fa-bell" onclick="showNotifications()" style="cursor: pointer;"></i>
481
+
482
+ <!-- Card Example -->
483
+ <div class="card" onclick="openDetails(1)" style="cursor: pointer;">
484
+ <h3>Product Title</h3>
485
+ </div>
486
+ ```
487
+
488
+ **JavaScript MUST include all handlers:**
489
+ ```javascript
490
+ function handleLogin() {{
491
+ document.getElementById('loginBtn').innerHTML = 'Loading...';
492
+ setTimeout(() => {{
493
+ alert('Login Successful!');
494
+ window.location.href = '#dashboard';
495
+ }}, 1500);
496
+ }}
497
+
498
+ function showForgotPassword() {{
499
+ document.getElementById('forgotModal').style.display = 'block';
500
+ }}
501
+
502
+ function showNotifications() {{
503
+ alert('Notifications clicked');
504
+ }}
505
+
506
+ function openDetails(id) {{
507
+ alert('Opening details for item ' + id);
508
+ }}
509
+ ```
510
+
511
+ **FOR REACT (Tailwind CSS ONLY):**
512
+ ```jsx
513
+ import {{ useState }} from 'react';
514
+ import {{ Bell, Menu, User, Heart }} from 'lucide-react';
515
+
516
+ export default function LoginComponent() {{
517
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
518
+ const [showModal, setShowModal] = useState(false);
519
+
520
+ const handleLogin = () => {{
521
+ setIsLoading(true);
522
+ setTimeout(() => {{
523
+ setIsLoggedIn(true);
524
+ alert('Login Successful!');
525
+ }}, 1500);
526
+ }};
527
+
528
+ return (
529
+ <div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 p-6">
530
+ {{/* Button with handler - Tailwind classes only */}}
531
+ <button
532
+ onClick={{handleLogin}}
533
+ className="px-6 py-3 bg-blue-500 text-white font-semibold rounded-lg hover:bg-blue-600 transition-colors cursor-pointer shadow-md hover:shadow-lg"
534
+ >
535
+ Login
536
+ </button>
537
+
538
+ {{/* Icon with handler - Tailwind classes only */}}
539
+ <button
540
+ onClick={{() => alert('Notifications')}}
541
+ className="p-2 hover:bg-gray-100 rounded-full transition-colors cursor-pointer"
542
+ >
543
+ <Bell size={{20}} className="text-gray-700" />
544
+ </button>
545
+
546
+ {{/* Text link with handler - Tailwind classes only */}}
547
+ <span
548
+ onClick={{() => setShowModal(true)}}
549
+ className="text-blue-500 hover:text-blue-600 cursor-pointer underline text-sm font-medium"
550
+ >
551
+ Forgot Password?
552
+ </span>
553
+
554
+ {{/* Card with handler - Tailwind classes only */}}
555
+ <div
556
+ onClick={{() => alert('Card clicked')}}
557
+ className="p-4 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow cursor-pointer border border-gray-200"
558
+ >
559
+ <h3 className="text-lg font-semibold text-gray-800">Product Title</h3>
560
+ </div>
561
+ </div>
562
+ );
563
+ }}
564
+ ```
565
+
566
+ ### 🎯 MANDATORY CLICKABILITY CHECKLIST:
567
+ Go through EVERY element and ask:
568
+
569
+ 1. βœ… **Is it a button?** β†’ Add `onclick` (HTML) or `onClick` (React) handler
570
+ 2. βœ… **Is it text that looks like a link?** β†’ Wrap in clickable element with handler
571
+ 3. βœ… **Is it an icon?** β†’ Wrap in button or add click handler with `cursor: pointer`
572
+ 4. βœ… **Is it a card/list item?** β†’ Add click handler to open details
573
+ 5. βœ… **Is it a profile picture?** β†’ Add click handler
574
+ 6. βœ… **Is it ANY navigation element?** β†’ Add handler to switch views
575
+
576
+ **IF ANY ELEMENT COULD POSSIBLY BE CLICKED, IT MUST HAVE A HANDLER.**
577
+
578
+ ### 🎨 ICONS (WITH CLICK HANDLERS) - CRITICAL:
579
+
580
+ **⚠️ MANDATORY: Every icon MUST be visible and properly loaded. Use these EXACT implementations:**
581
+
582
+ **HTML/CSS - FontAwesome 6.4.0 (MUST USE CDN):**
583
+ ```html
584
+ <!DOCTYPE html>
585
+ <html lang="en">
586
+ <head>
587
+ <meta charset="UTF-8">
588
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
589
+ <title>App</title>
590
+ <!-- CRITICAL: FontAwesome CDN - MUST BE IN <head> -->
591
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
592
+ </head>
593
+ <body>
594
+ <!-- Every icon MUST be clickable and use fa-solid class -->
595
+ <button onclick="toggleMenu()" class="icon-btn" style="cursor: pointer; border: none; background: transparent; padding: 8px;">
596
+ <i class="fa-solid fa-bars" style="font-size: 20px; color: #333;"></i>
597
+ </button>
598
+
599
+ <button onclick="showProfile()" class="icon-btn" style="cursor: pointer; border: none; background: transparent; padding: 8px;">
600
+ <i class="fa-solid fa-user" style="font-size: 20px; color: #333;"></i>
601
+ </button>
602
+
603
+ <button onclick="showNotifications()" style="cursor: pointer; border: none; background: transparent; padding: 8px;">
604
+ <i class="fa-solid fa-bell" style="font-size: 20px; color: #333;"></i>
605
+ </button>
606
+
607
+ <button onclick="search()" style="cursor: pointer; border: none; background: transparent; padding: 8px;">
608
+ <i class="fa-solid fa-magnifying-glass" style="font-size: 20px; color: #333;"></i>
609
+ </button>
610
+
611
+ <!-- Common icons: fa-heart, fa-shopping-cart, fa-trash, fa-edit, fa-share, fa-xmark, fa-plus, fa-minus -->
612
+ </body>
613
+ </html>
614
+ ```
615
+
616
+ **React - Lucide React with Tailwind (NO inline styles):**
617
+ ```jsx
618
+ import {{ Bell, Menu, User, Search, Heart, ShoppingCart, Trash2, Edit, Share2, X, Plus, Minus, Home, Settings, LogOut }} from 'lucide-react';
619
+
620
+ export default function IconButtons() {{
621
+ return (
622
+ <div className="flex items-center gap-4 p-4">
623
+ <button
624
+ onClick={{() => setShowMenu(!showMenu)}}
625
+ className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
626
+ >
627
+ <Menu size={{20}} className="text-gray-700" />
628
+ </button>
629
+
630
+ <button
631
+ onClick={{() => setShowProfile(true)}}
632
+ className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
633
+ >
634
+ <User size={{20}} className="text-gray-700" />
635
+ </button>
636
+
637
+ <button
638
+ onClick={{() => alert('Notifications')}}
639
+ className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
640
+ >
641
+ <Bell size={{20}} className="text-gray-700" />
642
+ </button>
643
+
644
+ <button
645
+ onClick={{() => handleSearch()}}
646
+ className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
647
+ >
648
+ <Search size={{20}} className="text-gray-700" />
649
+ </button>
650
+ </div>
651
+ );
652
+ }}
653
+ ```
654
+
655
+ **⚠️ ICON MAPPING GUIDE:**
656
+ - Menu/Hamburger β†’ `fa-bars` (HTML) or `<Menu />` (React)
657
+ - User/Profile β†’ `fa-user` (HTML) or `<User />` (React)
658
+ - Notifications β†’ `fa-bell` (HTML) or `<Bell />` (React)
659
+ - Search β†’ `fa-magnifying-glass` (HTML) or `<Search />` (React)
660
+ - Heart/Like β†’ `fa-heart` (HTML) or `<Heart />` (React)
661
+ - Cart β†’ `fa-shopping-cart` (HTML) or `<ShoppingCart />` (React)
662
+ - Delete β†’ `fa-trash` (HTML) or `<Trash2 />` (React)
663
+ - Edit β†’ `fa-edit` (HTML) or `<Edit />` (React)
664
+ - Share β†’ `fa-share` (HTML) or `<Share2 />` (React)
665
+ - Close β†’ `fa-xmark` (HTML) or `<X />` (React)
666
+ - Add β†’ `fa-plus` (HTML) or `<Plus />` (React)
667
+
668
+ **βœ… VERIFICATION CHECKLIST:**
669
+ - [ ] FontAwesome CDN link is in <head> section (HTML)
670
+ - [ ] All icons use `fa-solid` class (HTML)
671
+ - [ ] All icons are imported at top of file (React)
672
+ - [ ] Icons have proper size attributes (size={{20}} for React)
673
+ - [ ] Icons have click handlers
674
+ - [ ] Icons have `cursor-pointer` class (Tailwind) or `cursor: pointer` style (HTML)
675
+ - [ ] **React: NO inline styles, ONLY Tailwind classes**
676
+ - [ ] **React: NO separate CSS files, all styling with Tailwind**
677
+ - [ ] **React: Components only, NO full project files**
678
+ - [ ] **Labels are exact size as shown in image**
679
+ - [ ] **Positioning is exact (pixel-perfect match)**
680
+
681
+ ### πŸ“‚ COMPLETE FILE STRUCTURE (MANDATORY):
682
+
683
+ **For HTML/CSS - Generate ALL 3 files:**
684
+
685
+ 1. **index.html** - Must include:
686
+ - Proper DOCTYPE and meta tags
687
+ - **CRITICAL: FontAwesome 6.4.0 CDN link in <head> section:**
688
+ ```html
689
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
690
+ ```
691
+ - All HTML structure with proper icon usage (`<i class="fa-solid fa-*"></i>`)
692
+ - Embedded or linked CSS/JS
693
+ - **VERIFY: All icons use `fa-solid` class, NOT `fa` or `fas`**
694
+
695
+ 2. **style.css** - Must include:
696
+ - Reset styles
697
+ - All component styles
698
+ - Hover effects (`:hover`)
699
+ - Responsive breakpoints (`@media`)
700
+ - Cursor pointer for clickable elements
701
+
702
+ 3. **script.js** - Must include:
703
+ - ALL event handler functions
704
+ - State management variables
705
+ - Modal open/close functions
706
+ - Form validation
707
+ - View switching logic
708
+
709
+ **For React - Generate ONLY Components (NOT full project):**
710
+
711
+ **CRITICAL: Generate ONLY component files, NO project structure files!**
712
+
713
+ 1. **src/components/Navbar.jsx** - Navbar component with Tailwind classes
714
+ 2. **src/components/Card.jsx** - Card component with Tailwind classes
715
+ 3. **src/components/Button.jsx** - Button component with Tailwind classes
716
+ 4. **src/components/Modal.jsx** - Modal component with Tailwind classes
717
+ (Generate only the components that exist in the UI image)
718
+
719
+ **MANDATORY TAILWIND REQUIREMENTS:**
720
+ - Every component MUST use Tailwind CSS utility classes
721
+ - NO separate CSS files
722
+ - NO inline styles (style={{}})
723
+ - NO className with custom CSS classes
724
+ - Use Tailwind classes like: `className="flex items-center justify-center p-4 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"`
725
+ - For exact positioning: Use `absolute`, `relative`, `top-*`, `left-*`, `right-*`, `bottom-*` classes
726
+ - For exact sizing: Use `w-*`, `h-*`, `min-w-*`, `max-w-*` classes
727
+ - For exact spacing: Use `p-*`, `m-*`, `gap-*`, `space-*` classes
728
+ - For exact colors: Use Tailwind color classes like `bg-[#hex]` or standard Tailwind colors
729
+ - For fonts: Use `text-sm`, `text-base`, `text-lg`, `text-xl`, `font-bold`, `font-semibold`, etc.
730
+
731
+ **Example React Component with Tailwind:**
732
+ ```jsx
733
+ import {{ Bell, Menu, User }} from 'lucide-react';
734
+
735
+ export default function Navbar() {{
736
+ return (
737
+ <nav className="flex items-center justify-between px-6 py-4 bg-white shadow-md">
738
+ <button
739
+ onClick={{() => alert('Menu clicked')}}
740
+ className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
741
+ >
742
+ <Menu size={{20}} className="text-gray-700" />
743
+ </button>
744
+ <h1 className="text-xl font-bold text-gray-800">App Title</h1>
745
+ <button
746
+ onClick={{() => alert('Notifications')}}
747
+ className="p-2 hover:bg-gray-100 rounded-lg transition-colors cursor-pointer"
748
+ >
749
+ <Bell size={{20}} className="text-gray-700" />
750
+ </button>
751
+ </nav>
752
+ );
753
+ }}
754
+ ```
755
+
756
+ ### 🎨 PIXEL-PERFECT STYLING REQUIREMENTS (CRITICAL):
757
+
758
+ **1. EXACT UI MATCH - MANDATORY:**
759
+ - Extract EXACT colors from the image (use color picker values)
760
+ - Match EXACT positioning (use absolute/relative positioning with exact pixel values)
761
+ - Match EXACT font sizes (labels should be exact size, not too small or too large)
762
+ - Match EXACT spacing, padding, margins (measure from image)
763
+ - Match EXACT border radius, shadows, borders
764
+ - Match EXACT layout structure (flex, grid with exact gaps)
765
+
766
+ **2. LABELS AND TEXT - EXACT SIZE:**
767
+ - Labels MUST be the exact size as shown in the image
768
+ - Use Tailwind text size classes: `text-xs`, `text-sm`, `text-base`, `text-lg`, `text-xl`, `text-2xl`, etc.
769
+ - If exact size needed, use: `text-[14px]` for custom sizes
770
+ - Font weights: `font-normal`, `font-medium`, `font-semibold`, `font-bold`
771
+ - NO labels should be too small or too large - match the image exactly
772
+
773
+ **3. POSITIONING - EXACT LOCATION:**
774
+ - Use Tailwind positioning classes: `absolute`, `relative`, `fixed`
775
+ - Use exact positioning: `top-[20px]`, `left-[30px]`, `right-[40px]`, `bottom-[50px]`
776
+ - Use flexbox/grid for layouts: `flex`, `grid`, `items-center`, `justify-between`, `gap-4`
777
+ - Match exact alignment from the image
778
+
779
+ **4. TAILWIND CSS ONLY (React):**
780
+ - Add `cursor-pointer` class to EVERY clickable element
781
+ - Add hover effects: `hover:bg-gray-100`, `hover:shadow-lg`, `hover:scale-105`
782
+ - Use exact colors: `bg-[#hexcode]` or standard Tailwind colors
783
+ - Add smooth transitions: `transition-all`, `transition-colors`, `duration-200`
784
+ - Make it responsive: `sm:`, `md:`, `lg:`, `xl:` breakpoints
785
+ - NO inline styles, NO separate CSS files
786
+
787
+ **5. HTML/CSS (if selected):**
788
+ - Add `cursor: pointer` to EVERY clickable element
789
+ - Add `:hover` effects (shadow, scale, color change)
790
+ - Use exact colors from the image
791
+ - Make it responsive
792
+ - Add smooth transitions
793
+
794
+ ### πŸ“‹ JSON OUTPUT FORMAT:
795
+
796
+ **For React (Components Only):**
797
+ ```json
798
+ {{
799
+ "files": [
800
+ {{ "path": "src/components/Navbar.jsx", "content": "import {{ ... }} from 'lucide-react';\\n\\nexport default function Navbar() {{ ... }}" }},
801
+ {{ "path": "src/components/Card.jsx", "content": "import {{ ... }} from 'lucide-react';\\n\\nexport default function Card() {{ ... }}" }},
802
+ {{ "path": "src/components/Button.jsx", "content": "import {{ ... }} from 'lucide-react';\\n\\nexport default function Button() {{ ... }}" }}
803
+ ],
804
+ "instructions": "1. These are React components only\\n2. Use Tailwind CSS classes (no separate CSS files)\\n3. Import and use in your React project\\n4. Make sure Tailwind CSS is configured in your project"
805
+ }}
806
+ ```
807
+
808
+ **For HTML/CSS:**
809
+ ```json
810
+ {{
811
+ "files": [
812
+ {{ "path": "index.html", "content": "<!DOCTYPE html>..." }},
813
+ {{ "path": "style.css", "content": "/* Complete CSS */" }},
814
+ {{ "path": "script.js", "content": "// All event handlers" }}
815
+ ],
816
+ "instructions": "1. Extract all files\\n2. Open index.html in browser\\n3. All elements are clickable"
817
+ }}
818
+ ```
819
+
820
+ ### DETECTED UI ELEMENTS:
821
+ ```json
822
+ {json.dumps(dets, indent=2)}
823
+ ```
824
+
825
+ ### 🚨 ULTRA-STRICT FINAL REQUIREMENTS:
826
+
827
+ **1. CLICKABILITY - ABSOLUTE RULE:**
828
+ EVERY element that a human could possibly click MUST have a handler:
829
+ - Buttons β†’ `onclick`/`onClick` with specific actions (login, submit, cancel, etc.)
830
+ - Icons β†’ Wrapped in clickable elements with handlers
831
+ - Text links ("Forgot Password?", "Sign Up", "Terms", etc.) β†’ Click handlers
832
+ - Cards, items, thumbnails β†’ Click to show details/modal
833
+ - Profile pictures β†’ Click to view profile or enlarge
834
+ - Navigation items β†’ Click to switch views
835
+
836
+ **2. EVENT MESSAGES - SHOW USER FEEDBACK:**
837
+ Every click must give feedback:
838
+ - Login button β†’ Show "Logging in...", then "Login Successful!" alert or message
839
+ - Register button β†’ Show "Creating account...", then redirect or show success
840
+ - "Forgot Password?" β†’ Open modal or show reset form
841
+ - Card click β†’ Open modal with item details or show alert
842
+ - Icon clicks β†’ Show appropriate message ("Opening notifications", "Profile settings", etc.)
843
+
844
+ **3. MODERN ICONS - MANDATORY (MUST BE VISIBLE):**
845
+ - **HTML/CSS:** Use FontAwesome 6.4.0 CDN (MUST include in <head>):
846
+ ```html
847
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
848
+ ```
849
+ - Use `fa-solid` class for all icons
850
+ - Example: `<i class="fa-solid fa-bars"></i>`
851
+ - Icons MUST have size: `style="font-size: 20px;"`
852
+
853
+ - **React/Next.js:** Use lucide-react (MUST be in package.json):
854
+ ```json
855
+ "lucide-react": "^0.263.1"
856
+ ```
857
+ - Import icons: `import {{ Bell, Menu, User }} from 'lucide-react';`
858
+ - Use with size: `<Bell size={{20}} />`
859
+ - Icons MUST be imported, not used as strings
860
+
861
+ - **Flutter:** Use Material Icons from `flutter/material.dart`
862
+ - **Vue:** Use Heroicons or lucide-vue
863
+
864
+ **ALL icons MUST:**
865
+ - βœ… Be properly imported/loaded (CDN for HTML, npm package for React)
866
+ - βœ… Have visible size (font-size: 20px for HTML, size={{20}} for React)
867
+ - βœ… Have color attributes (color="#333" or style="color: #333")
868
+ - βœ… Be clickable (wrapped in buttons or have click handlers)
869
+ - βœ… Have cursor: pointer style
870
+ - βœ… Have hover effects (opacity change, scale, etc.)
871
+ - βœ… NEVER use placeholder text like "[icon]" or "πŸ”" - use actual icon components
872
+
873
+ **4. EXACT UI MATCH:**
874
+ - Extract exact colors from image (use color picker logic)
875
+ - Match fonts (use Google Fonts if needed)
876
+ - Match spacing, padding, margins precisely
877
+ - Match shadows, borders, border-radius
878
+ - Make it pixel-perfect
879
+
880
+ **5. COMPLETE CODE - NO SHORTCUTS:**
881
+ - Generate EVERY file needed
882
+ - Include ALL functions referenced in HTML/JSX
883
+ - NO placeholder comments like "// Add more here"
884
+ - NO TODO comments
885
+ - COMPLETE, working, production-ready code
886
+
887
+ **EXAMPLE - Perfect Login Button (HTML):**
888
+ ```html
889
+ <button onclick="handleLogin()" class="login-btn" style="cursor: pointer;">
890
+ Login
891
+ </button>
892
+
893
+ <script>
894
+ function handleLogin() {{
895
+ const btn = event.target;
896
+ btn.disabled = true;
897
+ btn.innerHTML = 'Logging in...';
898
+
899
+ // Simulate API call
900
+ setTimeout(() => {{
901
+ alert('Login Successful!');
902
+ btn.innerHTML = 'Login';
903
+ btn.disabled = false;
904
+ // Redirect or change view
905
+ window.location.href = '#dashboard';
906
+ }}, 1500);
907
+ }}
908
+ </script>
909
+ ```
910
+
911
+ **EXAMPLE - Perfect Icon Button (React):**
912
+ ```jsx
913
+ // MUST import at top of file
914
+ import {{ Bell }} from 'lucide-react';
915
+
916
+ function NotificationButton() {{
917
+ return (
918
+ <button
919
+ onClick={{() => {{
920
+ alert('You have 3 new notifications');
921
+ setShowNotifications(true);
922
+ }}}}
923
+ style={{{{
924
+ padding: '8px',
925
+ border: 'none',
926
+ background: 'transparent',
927
+ cursor: 'pointer',
928
+ borderRadius: '50%',
929
+ transition: 'all 0.2s'
930
+ }}}}
931
+ onMouseEnter={{e => e.currentTarget.style.backgroundColor = '#f3f4f6'}}
932
+ onMouseLeave={{e => e.currentTarget.style.backgroundColor = 'transparent'}}
933
+ aria-label="Notifications"
934
+ >
935
+ <Bell size={{20}} color="#374151" />
936
+ </button>
937
+ );
938
+ }}
939
+ ```
940
+
941
+ **EXAMPLE - Perfect Icon Button (HTML):**
942
+ ```html
943
+ <head>
944
+ <!-- MUST include FontAwesome CDN -->
945
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
946
+ </head>
947
+ <body>
948
+ <button
949
+ onclick="showNotifications()"
950
+ style="padding: 8px; border: none; background: transparent; cursor: pointer; border-radius: 50%; transition: all 0.2s;"
951
+ onmouseover="this.style.backgroundColor='#f3f4f6'"
952
+ onmouseout="this.style.backgroundColor='transparent'"
953
+ aria-label="Notifications"
954
+ >
955
+ <i class="fa-solid fa-bell" style="font-size: 20px; color: #374151;"></i>
956
+ </button>
957
+ </body>
958
+ ```
959
+
960
+ **EXAMPLE - Perfect "Forgot Password" Link (HTML):**
961
+ ```html
962
+ <a href="#" onclick="showForgotPassword(); return false;" class="forgot-link">
963
+ Forgot Password?
964
+ </a>
965
+
966
+ <script>
967
+ function showForgotPassword() {{
968
+ document.getElementById('forgotModal').style.display = 'block';
969
+ // Or show inline form
970
+ document.getElementById('forgotForm').classList.remove('hidden');
971
+ }}
972
+ </script>
973
+ ```
974
+
975
+ ### βœ… PRE-OUTPUT VALIDATION CHECKLIST:
976
+ Before generating output, verify you have:
977
+ - [ ] Identified ALL clickable elements in the UI
978
+ - [ ] Added click handlers to EVERY button
979
+ - [ ] **ICONS: Made EVERY icon clickable and VISIBLE**
980
+ - [ ] **ICONS (HTML): FontAwesome CDN link is in <head>, all icons use `fa-solid` class**
981
+ - [ ] **ICONS (React): lucide-react is in package.json, all icons are imported and used as components**
982
+ - [ ] **ICONS: All icons have proper size (font-size: 20px or size={{20}})**
983
+ - [ ] **ICONS: All icons have color attributes**
984
+ - [ ] Added handlers to ALL text links
985
+ - [ ] Made ALL cards/items clickable
986
+ - [ ] Used modern icon libraries (FontAwesome 6.4.0 CDN for HTML, lucide-react ^0.263.1 for React)
987
+ - [ ] Added feedback messages for ALL user actions
988
+ - [ ] Included ALL CSS with cursor:pointer for clickable elements
989
+ - [ ] Included ALL JavaScript/React handler functions
990
+ - [ ] Generated complete file structure for selected framework
991
+ - [ ] Matched UI colors, fonts, spacing exactly
992
+ - [ ] Added hover effects to clickable elements
993
+ - [ ] NO incomplete code, NO TODOs, NO placeholders
994
+ - [ ] **NO icon placeholders like "[icon]" or emoji - use actual icon components**
995
+
996
+ ### 🎯 FINAL CRITICAL REQUIREMENTS:
997
+
998
+ **FOR REACT:**
999
+ 1. βœ… Generate ONLY component files (NO package.json, NO App.jsx, NO project structure)
1000
+ 2. βœ… Use Tailwind CSS classes ONLY (NO inline styles, NO separate CSS files)
1001
+ 3. βœ… Exact pixel-perfect match (colors, positioning, sizing, spacing)
1002
+ 4. βœ… Labels exact size (not too small, not too large)
1003
+ 5. βœ… All components in `src/components/` folder
1004
+ 6. βœ… Each component is a separate .jsx file
1005
+ 7. βœ… All styling with Tailwind utility classes
1006
+
1007
+ **FOR HTML/CSS:**
1008
+ 1. βœ… Generate index.html, style.css, script.js
1009
+ 2. βœ… Exact pixel-perfect match
1010
+ 3. βœ… Labels exact size
1011
+ 4. βœ… All styling in CSS file
1012
+
1013
+ **GENERATE PERFECT, COMPLETE, FULLY FUNCTIONAL CODE NOW.**
1014
+ """
1015
+
1016
+ # --- OPTION 1: OPENAI GPT-4o (Paid - Preferred if available) ---
1017
+ # Try OpenAI first if available (better accuracy)
1018
+ if self.openai_available:
1019
+ try:
1020
+ print("Using OpenAI GPT-4o (High Accuracy)...")
1021
+ base64_image = self.encode_image(img_path)
1022
+ response = self.client.chat.completions.create(
1023
+ model="gpt-4o",
1024
+ messages=[
1025
+ {
1026
+ "role": "user",
1027
+ "content": [
1028
+ {"type": "text", "text": prompt},
1029
+ {
1030
+ "type": "image_url",
1031
+ "image_url": {"url": f"data:image/png;base64,{base64_image}"}
1032
+ }
1033
+ ]
1034
+ }
1035
+ ],
1036
+ response_format={"type": "json_object"}, # Guarantees valid JSON
1037
+ max_tokens=4096
1038
+ )
1039
+ self.api_used = "OpenAI GPT-4o (Paid)"
1040
+ return json.loads(response.choices[0].message.content)
1041
+ except Exception as e:
1042
+ error_str = str(e)
1043
+ print(f"⚠️ OpenAI Failed: {error_str}")
1044
+
1045
+ # Check if it's an invalid API key error
1046
+ if "401" in error_str or "invalid_api_key" in error_str.lower() or "incorrect api key" in error_str.lower():
1047
+ error_msg = "❌ OpenAI API Key is Invalid!\n\n"
1048
+ error_msg += "πŸ” Problem Detected:\n"
1049
+ error_msg += "- Your OPENAI_API_KEY appears to be incorrect\n"
1050
+ error_msg += "- OpenAI keys should start with 'sk-'\n"
1051
+ error_msg += "- If your key starts with 'AIza', that's a Gemini key, not OpenAI\n\n"
1052
+ error_msg += "πŸ’‘ Solutions:\n"
1053
+ error_msg += "1. Get a valid OpenAI API key from: https://platform.openai.com/account/api-keys\n"
1054
+ error_msg += "2. Update OPENAI_API_KEY in .env file or environment variables\n"
1055
+ error_msg += "3. Restart the application\n\n"
1056
+ error_msg += "βœ… Falling back to Gemini (if available)."
1057
+ # Continue to Gemini fallback
1058
+ else:
1059
+ print(f"⚠️ OpenAI error, falling back to Gemini: {error_str}")
1060
+ # Continue to Gemini fallback
1061
+
1062
+ # --- OPTION 2: GEMINI (Free - Fallback if OpenAI not available) ---
1063
+ # Try Gemini if OpenAI is not available or failed
1064
+ if GEMINI_API_KEY:
1065
+ last_error = ""
1066
+ import time
1067
+
1068
+ for model_name in self.models_to_try:
1069
+ # Safety check: NEVER use experimental models
1070
+ if "exp" in model_name.lower() or "experimental" in model_name.lower():
1071
+ print(f"⚠️ Skipping experimental model: {model_name}")
1072
+ continue
1073
+
1074
+ try:
1075
+ print(f"Using Gemini Model: {model_name} (Free)...")
1076
+ model = genai.GenerativeModel(model_name)
1077
+ res = model.generate_content([prompt, Image.open(img_path)])
1078
+
1079
+ if not res.text: raise ValueError("Empty response (Safety Filter?)")
1080
+ print(f"Gemini Raw Output (First 100 chars): {res.text[:100]}...")
1081
+
1082
+ # Robust JSON Extraction
1083
+ import re
1084
+ json_match = re.search(r'\{.*\}', res.text, re.DOTALL)
1085
+ if json_match:
1086
+ txt = json_match.group(0)
1087
+ else:
1088
+ # Fallback cleanup
1089
+ txt = res.text.replace("```json", "").replace("```", "").strip()
1090
+
1091
+ self.api_used = f"Gemini {model_name} (Free)"
1092
+ return json.loads(txt)
1093
+ except Exception as e:
1094
+ error_str = str(e)
1095
+ print(f"Failed with {model_name}: {error_str}")
1096
+
1097
+ # Check if it's a quota error - if OpenAI available, skip retry and go straight to OpenAI
1098
+ if "429" in error_str or "quota" in error_str.lower() or "exceeded" in error_str.lower():
1099
+ print(f"❌ Quota exceeded for {model_name}")
1100
+ if self.openai_available:
1101
+ print("πŸš€ Skipping Gemini retry, using OpenAI immediately...")
1102
+ last_error = f"Gemini quota exceeded. Switching to OpenAI."
1103
+ break # Break out of Gemini loop, go to OpenAI
1104
+ else:
1105
+ # Only retry if OpenAI is not available
1106
+ import re
1107
+ retry_match = re.search(r'retry.*?(\d+\.?\d*)\s*s', error_str, re.IGNORECASE)
1108
+ if retry_match:
1109
+ wait_time = float(retry_match.group(1)) + 2
1110
+ print(f"⏳ Waiting {wait_time:.1f} seconds before retry (no OpenAI fallback)...")
1111
+ time.sleep(min(wait_time, 60))
1112
+ try:
1113
+ print(f"πŸ”„ Retrying with {model_name}...")
1114
+ model = genai.GenerativeModel(model_name)
1115
+ res = model.generate_content([prompt, Image.open(img_path)])
1116
+ if res.text:
1117
+ import re
1118
+ json_match = re.search(r'\{.*\}', res.text, re.DOTALL)
1119
+ if json_match:
1120
+ txt = json_match.group(0)
1121
+ else:
1122
+ txt = res.text.replace("```json", "").replace("```", "").strip()
1123
+ self.api_used = f"Gemini {model_name} (Free)"
1124
+ return json.loads(txt)
1125
+ except Exception as retry_e:
1126
+ print(f"Retry also failed: {retry_e}")
1127
+ last_error = f"Quota exceeded. Please wait or add OPENAI_API_KEY."
1128
+ else:
1129
+ last_error = f"Quota exceeded for {model_name}. Please wait or add OPENAI_API_KEY."
1130
+ else:
1131
+ import traceback
1132
+ traceback.print_exc()
1133
+ last_error = error_str
1134
+
1135
+ # If Gemini fails and OpenAI is available, fallback to OpenAI
1136
+ if self.openai_available:
1137
+ print("⚠️ Gemini quota exceeded, switching to OpenAI GPT-4o immediately...")
1138
+ else:
1139
+ error_msg = f"Gemini quota exceeded. Last error: {last_error}\n\n"
1140
+ error_msg += "πŸ’‘ Solutions:\n"
1141
+ error_msg += "1. Wait a few minutes and try again\n"
1142
+ error_msg += "2. Check your Gemini API quota: https://ai.dev/usage?tab=rate-limit\n"
1143
+ error_msg += "3. Add OPENAI_API_KEY as fallback option"
1144
+ return {"error": error_msg}
1145
+
1146
+ # If we reach here, no API keys are available
1147
+ return {"error": "No API keys available. Please configure OPENAI_API_KEY or GEMINI_API_KEY in .env file or environment variables."}
1148
+
1149
+ def zip_project(self, data):
1150
+ if 'error' in data:
1151
+ return None
1152
+
1153
+ tmp = tempfile.mkdtemp()
1154
+ if 'files' in data:
1155
+ for f in data['files']:
1156
+ # Ensure path is safe and correct extension
1157
+ clean_path = f['path'].lstrip('/').replace('\\', '/')
1158
+ p = os.path.join(tmp, clean_path)
1159
+ os.makedirs(os.path.dirname(p), exist_ok=True)
1160
+ with open(p, 'w', encoding='utf-8') as o: o.write(f['content'])
1161
+ shutil.make_archive("project", 'zip', tmp)
1162
+ return "project.zip"
1163
+
1164
+ # --- Rate Limiting & Security ---
1165
+ from collections import defaultdict
1166
+ from datetime import datetime, timedelta
1167
+ import time
1168
+
1169
+ # Simple rate limiting: track requests per IP/session
1170
+ request_tracker = defaultdict(list)
1171
+ MAX_REQUESTS_PER_HOUR = 10 # Maximum requests per hour per user
1172
+ MAX_REQUESTS_PER_DAY = 50 # Maximum requests per day per user
1173
+
1174
+ def check_rate_limit():
1175
+ """Check if user has exceeded rate limits"""
1176
+ # For simplicity, use timestamp-based tracking
1177
+ # In production, use proper session/IP tracking
1178
+ current_time = time.time()
1179
+ hour_ago = current_time - 3600
1180
+ day_ago = current_time - 86400
1181
+
1182
+ # Clean old entries
1183
+ for key in list(request_tracker.keys()):
1184
+ request_tracker[key] = [t for t in request_tracker[key] if t > day_ago]
1185
+
1186
+ # Simple session-based tracking (in production, use IP address)
1187
+ session_id = "default" # In real app, get from Gradio request
1188
+
1189
+ recent_requests = [t for t in request_tracker[session_id] if t > hour_ago]
1190
+ daily_requests = [t for t in request_tracker[session_id] if t > day_ago]
1191
+
1192
+ if len(recent_requests) >= MAX_REQUESTS_PER_HOUR:
1193
+ return False, f"⚠️ Rate limit exceeded! Maximum {MAX_REQUESTS_PER_HOUR} requests per hour. Please wait and try again later."
1194
+
1195
+ if len(daily_requests) >= MAX_REQUESTS_PER_DAY:
1196
+ return False, f"⚠️ Daily limit exceeded! Maximum {MAX_REQUESTS_PER_DAY} requests per day. Please try again tomorrow."
1197
+
1198
+ # Record this request
1199
+ request_tracker[session_id].append(current_time)
1200
+ return True, None
1201
+
1202
+ # --- Main Application ---
1203
+ if __name__ == "__main__":
1204
+ if os.path.exists("best.pt"):
1205
+ print("Initializing God Mode System...")
1206
+ detector = GodModeDetector("best.pt")
1207
+ generator = GodModeGenerator()
1208
+
1209
+ def run_god_mode(img, fw):
1210
+ print("\n--- NEW REQUEST ---")
1211
+
1212
+ # Rate limiting check
1213
+ allowed, error_msg = check_rate_limit()
1214
+ if not allowed:
1215
+ print(f"Rate limit exceeded: {error_msg}")
1216
+ return None, error_msg, None
1217
+
1218
+ if not (detector and generator):
1219
+ print("System not initialized.")
1220
+ return None, "System not initialized. Check console logs.", None
1221
+
1222
+ if not img:
1223
+ print("No image provided.")
1224
+ return None, "Upload Image!", None
1225
+
1226
+ try:
1227
+ print("Saving temp.png...")
1228
+ img.save("temp.png")
1229
+
1230
+ # 1. Detect
1231
+ print("Starting Detection...")
1232
+ dets = detector.detect("temp.png")
1233
+ print(f"Detection Done. Found {len(dets)} items.")
1234
+ vis = detector.visualize("temp.png", dets)
1235
+
1236
+ # 2. Generate
1237
+ print(f"Starting Generation with Framework: {fw}")
1238
+ code_data = generator.generate("temp.png", dets, fw)
1239
+
1240
+ if "error" in code_data:
1241
+ print(f"Generation Error: {code_data['error']}")
1242
+ return vis, f"Error Generating Code:\n{code_data['error']}", None
1243
+
1244
+ # 3. Zip
1245
+ print("Zipping project...")
1246
+ zip_path = generator.zip_project(code_data)
1247
+
1248
+ mode_used = getattr(generator, 'api_used', 'Unknown')
1249
+ print(f"Success! Mode: {mode_used}")
1250
+
1251
+ report = f"βœ… Generated {fw} Project!\n\nFound {len(dets)} elements.\nAPI Used: {mode_used}"
1252
+ return vis, report, zip_path
1253
+ except Exception as e:
1254
+ import traceback
1255
+ print("!!! CRITICAL EXCEPTION !!!")
1256
+ traceback.print_exc()
1257
+ return None, f"Critical Error: {str(e)}\nSee Console for details.", None
1258
+
1259
+ with gr.Blocks(title="God Mode") as app:
1260
+ gr.Markdown(f"""
1261
+ # πŸš€ God Mode: Ultimate UI to Code System
1262
+
1263
+ **Convert any UI screenshot into fully functional, pixel-perfect code!**
1264
+
1265
+ <div style="background-color: #fff3cd; border: 1px solid #ffc107; padding: 15px; border-radius: 5px; margin: 20px 0;">
1266
+ <strong>⚠️ Usage Limits:</strong><br>
1267
+ β€’ Maximum {MAX_REQUESTS_PER_HOUR} requests per hour per user<br>
1268
+ β€’ Maximum {MAX_REQUESTS_PER_DAY} requests per day per user<br>
1269
+ β€’ Free Gemini API is preferred to save costs<br>
1270
+ β€’ OpenAI (paid) is used only as fallback if Gemini fails
1271
+ </div>
1272
+
1273
+ ### ✨ Features:
1274
+ - **100% Element Detection:** YOLO11 + SAHI + EasyOCR + DBSCAN
1275
+ - **90%+ Code Accuracy:** Gemini (Free) preferred, GPT-4o (Paid) as fallback
1276
+ - **Universal Semantics:** Understands EVERY interaction (Icons, Cards, Lists)
1277
+ - **Interactive Clickable UI:** JS Logic + State Management
1278
+ - **Cognition Engine:** Understands Size, Shape, Position & Semantics
1279
+ - **Full Project ZIP:** Download ready-to-use code
1280
+
1281
+ ### πŸ“‹ How to Use:
1282
+ 1. Upload a UI screenshot/image
1283
+ 2. Select your framework (React, HTML/CSS, Flutter, etc.)
1284
+ 3. Click "Generate Code"
1285
+ 4. Download the complete project ZIP file
1286
+
1287
+ ### ⚠️ API Quota Notes:
1288
+ - **Gemini Free Tier:** 60 requests/minute, 1500 requests/day (gemini-1.5-flash)
1289
+ - If you see quota errors, wait a few minutes or add OPENAI_API_KEY as fallback
1290
+ - Check your quota: https://ai.dev/usage?tab=rate-limit
1291
+ """)
1292
+ with gr.Row():
1293
+ with gr.Column():
1294
+ i = gr.Image(type="pil", label="UI Image")
1295
+ f = gr.Dropdown(
1296
+ ["React", "HTML/CSS", "Flutter", "Vue", "Angular", "Next.js", "Svelte", "SolidJS"],
1297
+ value="React", label="Frontend Framework"
1298
+ )
1299
+ btn = gr.Button("Generate Code")
1300
+ with gr.Column():
1301
+ o_i = gr.Image(label="Detections")
1302
+ o_t = gr.Textbox(label="Status")
1303
+ o_f = gr.File(label="Download ZIP")
1304
+ btn.click(run_god_mode, [i, f], [o_i, o_t, o_f])
1305
+
1306
+ # For Hugging Face Spaces compatibility
1307
+ import os
1308
+ # Check if running on Hugging Face Spaces
1309
+ is_spaces = os.getenv('SPACE_ID') or os.getenv('SYSTEM') == 'spaces' or os.path.exists('/.dockerenv')
1310
+
1311
+ if is_spaces:
1312
+ print("πŸš€ Running on Hugging Face Spaces")
1313
+ print("=" * 70)
1314
+ print("πŸ”‘ API Keys Status at Startup:")
1315
+ print(f"Environment: SPACE_ID={os.getenv('SPACE_ID')}, SYSTEM={os.getenv('SYSTEM')}")
1316
+
1317
+ # Check API keys status
1318
+ openai_found = bool(OPENAI_API_KEY and len(OPENAI_API_KEY) >= 10)
1319
+ gemini_found = bool(GEMINI_API_KEY and len(GEMINI_API_KEY) >= 10)
1320
+
1321
+ if openai_found:
1322
+ print(f"βœ… OpenAI API Key found (Length: {len(OPENAI_API_KEY)} chars)")
1323
+ print(f" Preview: {OPENAI_API_KEY[:15]}...{OPENAI_API_KEY[-5:]}")
1324
+ else:
1325
+ print("❌ OpenAI API Key NOT found")
1326
+ if OPENAI_API_KEY:
1327
+ print(f" ⚠️ Key exists but too short: {len(OPENAI_API_KEY)} chars")
1328
+
1329
+ if gemini_found:
1330
+ print(f"βœ… Gemini API Key found (Length: {len(GEMINI_API_KEY)} chars)")
1331
+ print(f" Preview: {GEMINI_API_KEY[:15]}...{GEMINI_API_KEY[-5:]}")
1332
+ else:
1333
+ print("❌ Gemini API Key NOT found")
1334
+ if GEMINI_API_KEY:
1335
+ print(f" ⚠️ Key exists but too short: {len(GEMINI_API_KEY)} chars")
1336
+
1337
+ if not openai_found and not gemini_found:
1338
+ print("\n⚠️ WARNING: No valid API keys found!")
1339
+ print(" β†’ Go to Space Settings β†’ Secrets")
1340
+ print(" β†’ Verify names are EXACTLY: OPENAI_API_KEY and GEMINI_API_KEY")
1341
+ print(" β†’ Check keys don't have extra spaces")
1342
+ print(" β†’ **RESTART THE SPACE** (Settings β†’ Restart this Space)")
1343
+ print("\nπŸ” Debugging: Listing all env vars with 'API' or 'KEY':")
1344
+ for key in sorted(os.environ.keys()):
1345
+ if 'API' in key.upper() or 'KEY' in key.upper():
1346
+ val = os.environ[key]
1347
+ print(f" {key}: {'*' * min(30, len(val))} (length: {len(val)})")
1348
+ else:
1349
+ print("\nβœ… At least one API key is available!")
1350
+ print("=" * 70)
1351
+
1352
+ # For Spaces, let Gradio handle configuration automatically
1353
+ # The monkey patch above should fix the schema generation bug
1354
+ print("πŸš€ Launching app on Hugging Face Spaces...")
1355
+ app.launch()
1356
+ else: # Running locally
1357
+ print("πŸ’» Running locally")
1358
+ app.launch(share=False, debug=True, server_name="127.0.0.1", server_port=7860, inbrowser=True)
1359
+ else:
1360
+ print("=" * 70)
1361
+ print("❌ ERROR: Model file 'best.pt' not found or corrupted!")
1362
+ print("=" * 70)
1363
+ print("Please upload 'best.pt' to your Hugging Face Space:")
1364
+ print("1. Go to Space β†’ Files tab")
1365
+ print("2. Click 'Add file' β†’ 'Upload files'")
1366
+ print("3. Upload the best.pt file (should be ~50-200 MB)")
1367
+ print("4. Wait for upload to complete")
1368
+ print("5. Space will automatically restart")
1369
+ print("\n⚠️ If file exists but shows error, it might be corrupted.")
1370
+ print(" Try re-uploading the file.")
1371
+ print("=" * 70)
best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0eb375bd5bc9eb6bd11f9138c09accc94b0b8e95eb91fbd9caf05a218fb2b511
3
+ size 19247002
requirements.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pydantic==2.10.6
2
+ ultralytics
3
+ sahi
4
+ easyocr
5
+ google-generativeai
6
+ openai
7
+ gradio==4.44.1
8
+ gradio-client==1.3.0
9
+ scikit-learn
10
+ opencv-python-headless
11
+ huggingface_hub>=0.19.3,<0.21.0
12
+ python-dotenv
13
+ pillow
14
+ numpy
15
+ torch
16
+ torchvision
17
+ spaces