samiya-data-scientist commited on
Commit
7cb634c
Β·
verified Β·
1 Parent(s): dc3bba7

Upload 4 files

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