natabrizy commited on
Commit
fce8418
·
verified ·
1 Parent(s): 40dc698

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +209 -374
app.py CHANGED
@@ -16,70 +16,31 @@ from lzstring import LZString
16
  # =========================
17
  NEBIUS_BASE_URL = "https://api.studio.nebius.com/v1/"
18
 
19
- # Vision models that work well with Nebius
20
  DEFAULT_VISION_MODEL = "Qwen/Qwen2.5-VL-72B-Instruct"
21
  VISION_MODELS = [
22
  DEFAULT_VISION_MODEL,
23
  "Qwen/Qwen2.5-VL-7B-Instruct",
24
- "Qwen/Qwen2-VL-72B-Instruct",
25
- "Qwen/Qwen2-VL-7B-Instruct",
26
  ]
27
 
28
- # Code generation models with best performance on Nebius
29
- DEFAULT_CODE_MODEL = "deepseek-ai/DeepSeek-V3"
30
  CODE_MODELS = [
31
  DEFAULT_CODE_MODEL,
32
  "Qwen/Qwen2.5-Coder-32B-Instruct",
33
- "Qwen/QwQ-32B-Preview",
34
- "Qwen/Qwen2.5-72B-Instruct",
35
  "Qwen/Qwen2.5-7B-Instruct",
 
36
  "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
37
  "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",
38
- "meta-llama/Meta-Llama-3.1-405B-Instruct",
39
  "meta-llama/Meta-Llama-3.1-70B-Instruct",
40
  "meta-llama/Meta-Llama-3.1-8B-Instruct",
41
- "meta-llama/Llama-3.2-90B-Vision-Instruct",
42
- "meta-llama/Llama-3.2-11B-Vision-Instruct",
43
- "mistralai/Mixtral-8x22B-Instruct-v0.1",
44
- "mistralai/Mixtral-8x7B-Instruct-v0.1",
45
- "mistralai/Mistral-7B-Instruct-v0.3",
46
- "mistralai/Mistral-Nemo-Instruct-2407",
47
- "mistralai/Pixtral-12B-2409",
48
- "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF",
49
  ]
50
 
51
- # Model capabilities mapping (for better selection)
52
- MODEL_CAPABILITIES = {
53
- "vision": [
54
- "Qwen/Qwen2.5-VL-72B-Instruct",
55
- "Qwen/Qwen2.5-VL-7B-Instruct",
56
- "Qwen/Qwen2-VL-72B-Instruct",
57
- "Qwen/Qwen2-VL-7B-Instruct",
58
- "meta-llama/Llama-3.2-90B-Vision-Instruct",
59
- "meta-llama/Llama-3.2-11B-Vision-Instruct",
60
- "mistralai/Pixtral-12B-2409",
61
- ],
62
- "code": [
63
- "deepseek-ai/DeepSeek-V3",
64
- "Qwen/Qwen2.5-Coder-32B-Instruct",
65
- "Qwen/QwQ-32B-Preview",
66
- "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
67
- "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",
68
- ],
69
- "general": [
70
- "Qwen/Qwen2.5-72B-Instruct",
71
- "meta-llama/Meta-Llama-3.1-405B-Instruct",
72
- "meta-llama/Meta-Llama-3.1-70B-Instruct",
73
- "nvidia/Llama-3.1-Nemotron-70B-Instruct-HF",
74
- "mistralai/Mixtral-8x22B-Instruct-v0.1",
75
- ]
76
- }
77
-
78
- # Performance recommendations
79
  MODEL_RECOMMENDATIONS = {
80
- "fast": ["Qwen/Qwen2.5-7B-Instruct", "mistralai/Mistral-7B-Instruct-v0.3", "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"],
81
- "balanced": ["Qwen/Qwen2.5-Coder-32B-Instruct", "meta-llama/Meta-Llama-3.1-70B-Instruct", "Qwen/QwQ-32B-Preview"],
82
- "quality": ["deepseek-ai/DeepSeek-V3", "meta-llama/Meta-Llama-3.1-405B-Instruct", "Qwen/Qwen2.5-72B-Instruct"],
83
  }
84
 
85
  # Timeouts and retries
@@ -91,10 +52,6 @@ DEFAULT_NEBIUS_API_KEY = (
91
  "eyJhbGciOiJIUzI1NiIsImtpZCI6IlV6SXJWd1h0dnprLVRvdzlLZWstc0M1akptWXBvX1VaVkxUZlpnMDRlOFUiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiJnb29nbGUtb2F1dGgyfDEwNTA1MTQzMDg2MDMwMzIxNDEwMiIsInNjb3BlIjoib3BlbmlkIG9mZmxpbmVfYWNjZXNzIiwiaXNzIjoiYXBpX2tleV9pc3N1ZXIiLCJhdWQiOlsiaHR0cHM6Ly9uZWJpdXMtaW5mZXJlbmNlLmV1LmF1dGgwLmNvbS9hcGkvdjIvIl0sImV4cCI6MTkwNjU5ODA0NCwidXVpZCI6ImNkOGFiMWZlLTIxN2QtNDJlMy04OWUwLWM1YTg4MjcwMGVhNyIsIm5hbWUiOiJodW5nZ2luZyIsImV4cGlyZXNfYXQiOiIyMDMwLTA2LTAyVDAyOjM0OjA0KzAwMDAifQ.MA52QuIiNruK7_lX688RXAEI2TkcCOjcf_02XrpnhI8"
92
  )
93
 
94
- # Cache for available models
95
- _available_models_cache = None
96
- _cache_timestamp = 0
97
-
98
  # =========================
99
  # Helpers
100
  # =========================
@@ -108,74 +65,6 @@ def get_api_key(user_key: str = "") -> str:
108
  return (user_key or "").strip() or os.getenv("NEBIUS_API_KEY", "").strip() or DEFAULT_NEBIUS_API_KEY
109
 
110
 
111
- def get_available_models(api_key: str) -> Dict[str, List[str]]:
112
- """
113
- Fetch available models from Nebius API and categorize them.
114
- Returns a dict with 'vision' and 'code' keys.
115
- """
116
- global _available_models_cache, _cache_timestamp
117
- import time
118
-
119
- # Cache for 5 minutes
120
- if _available_models_cache and (time.time() - _cache_timestamp) < 300:
121
- return _available_models_cache
122
-
123
- try:
124
- url = f"{NEBIUS_BASE_URL}models"
125
- headers = {"Authorization": f"Bearer {api_key}"}
126
-
127
- with httpx.Client(timeout=httpx.Timeout(10.0)) as client:
128
- resp = client.get(url, headers=headers)
129
- if resp.status_code == 200:
130
- data = resp.json()
131
- models = data.get("data", [])
132
-
133
- vision_models = []
134
- code_models = []
135
-
136
- for model in models:
137
- model_id = model.get("id", "")
138
- # Categorize based on known patterns
139
- if any(v in model_id.lower() for v in ["vision", "vl", "pixtral", "llama-3.2-90b", "llama-3.2-11b"]):
140
- vision_models.append(model_id)
141
- if any(c in model_id.lower() for c in ["coder", "deepseek", "instruct", "llama", "mistral", "mixtral", "qwen"]):
142
- code_models.append(model_id)
143
-
144
- _available_models_cache = {
145
- "vision": vision_models or VISION_MODELS,
146
- "code": code_models or CODE_MODELS
147
- }
148
- _cache_timestamp = time.time()
149
- return _available_models_cache
150
- except Exception:
151
- pass
152
-
153
- # Return defaults if API call fails
154
- return {"vision": VISION_MODELS, "code": CODE_MODELS}
155
-
156
-
157
- def validate_model_availability(model: str, api_key: str, model_type: str = "general") -> bool:
158
- """
159
- Check if a model is available by making a test request.
160
- """
161
- try:
162
- test_message = "Hi" if model_type != "vision" else [{"type": "text", "text": "Hi"}]
163
- url = f"{NEBIUS_BASE_URL}chat/completions"
164
- payload = {
165
- "model": model,
166
- "messages": [{"role": "user", "content": test_message}],
167
- "max_tokens": 10,
168
- "temperature": 0.1,
169
- }
170
- headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
171
-
172
- with httpx.Client(timeout=httpx.Timeout(5.0)) as client:
173
- resp = client.post(url, headers=headers, json=payload)
174
- return resp.status_code == 200
175
- except Exception:
176
- return False
177
-
178
-
179
  def call_chat_completions(
180
  model: str,
181
  messages: list,
@@ -215,7 +104,7 @@ def call_chat_completions(
215
  # Try a fallback model
216
  fallback_models = {
217
  "vision": ["Qwen/Qwen2.5-VL-7B-Instruct"],
218
- "code": ["Qwen/Qwen2.5-7B-Instruct", "mistralai/Mistral-7B-Instruct-v0.3"],
219
  }
220
 
221
  # Detect model type and use appropriate fallback
@@ -258,8 +147,7 @@ def call_chat_completions(
258
 
259
  def _strip_fenced_code(text: str) -> str:
260
  """
261
- Removes ```html ... ``` fences from a content block if present.
262
- Also handles other code fence variations.
263
  """
264
  s = text.strip()
265
 
@@ -271,24 +159,39 @@ def _strip_fenced_code(text: str) -> str:
271
  ]
272
 
273
  for start_pattern, end_pattern in patterns:
274
- if re.match(start_pattern, s):
275
- s = re.sub(start_pattern, '', s)
276
- s = re.sub(end_pattern, '', s)
277
  break
278
 
279
  return s.strip()
280
 
281
 
282
- def ensure_complete_html(html_code: str) -> str:
283
  """
284
  Ensures the HTML code is complete with proper structure and inline CSS.
 
285
  """
286
  # Check if it's already a complete HTML document
287
- if "<!DOCTYPE html>" in html_code.upper() and "<html" in html_code.lower():
 
 
 
 
 
288
  return html_code
289
 
290
- # If it's just a fragment, wrap it in a complete HTML structure
291
- if not "<html" in html_code.lower():
 
 
 
 
 
 
 
 
 
292
  html_code = f"""<!DOCTYPE html>
293
  <html lang="en">
294
  <head>
@@ -303,17 +206,30 @@ def ensure_complete_html(html_code: str) -> str:
303
  box-sizing: border-box;
304
  }}
305
  body {{
306
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
307
  line-height: 1.6;
308
- color: #333;
309
  }}
 
 
 
 
 
 
310
  </style>
311
  </head>
312
  <body>
313
- {html_code}
314
  </body>
315
  </html>"""
316
 
 
 
 
 
 
 
 
317
  return html_code
318
 
319
 
@@ -361,8 +277,6 @@ def _split_assets(html_code: str) -> Tuple[str, str, str]:
361
  html,
362
  flags=re.IGNORECASE,
363
  )
364
- else:
365
- html = f"<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\"/>\n <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n{html}"
366
 
367
  # If JS collected, ensure script tag before </body> or at end
368
  if js_text:
@@ -373,8 +287,6 @@ def _split_assets(html_code: str) -> Tuple[str, str, str]:
373
  html,
374
  flags=re.IGNORECASE,
375
  )
376
- else:
377
- html = html.rstrip() + '\n <script src="script.js"></script>\n'
378
 
379
  return html, css_text, js_text
380
 
@@ -388,7 +300,7 @@ def analyze_image(
388
  vision_model: str = DEFAULT_VISION_MODEL,
389
  ) -> str:
390
  """
391
- Analyze an uploaded image and provide a concise description of its content and layout.
392
  """
393
  if image is None:
394
  return "Error: No image provided."
@@ -409,17 +321,19 @@ def analyze_image(
409
  img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
410
 
411
  prompt = (
412
- "Analyze this image and provide a detailed description for recreating it as a website. "
413
- "Include: "
414
- "1. Layout structure (header, sections, footer, grid/flex layouts) "
415
- "2. Color scheme (exact colors if possible) "
416
- "3. Typography (font sizes, weights, styles) "
417
- "4. UI components (buttons, forms, cards, navigation) "
418
- "5. Images and media placement "
419
- "6. Spacing and alignment "
420
- "7. Any animations or interactive elements visible "
421
- "8. Responsive design considerations "
422
- "Be specific about CSS properties and HTML structure needed."
 
 
423
  )
424
 
425
  messages = [
@@ -437,7 +351,7 @@ def analyze_image(
437
  model=vision_model,
438
  messages=messages,
439
  api_key=api_key,
440
- max_tokens=1500,
441
  temperature=0.7,
442
  retry_with_fallback=True,
443
  )
@@ -445,7 +359,7 @@ def analyze_image(
445
  except Exception as e:
446
  error_msg = str(e)
447
  if "404" in error_msg or "not found" in error_msg.lower():
448
- return f"Error: Model '{vision_model}' not available. Try using one of the recommended vision models: {', '.join(VISION_MODELS[:3])}"
449
  return f"Error analyzing image: {error_msg}"
450
 
451
 
@@ -457,7 +371,7 @@ def generate_html_code(
457
  code_temperature: float = 0.7,
458
  ) -> str:
459
  """
460
- Generate HTML/CSS/JavaScript code based on a website description.
461
  """
462
  if not description or description.startswith("Error"):
463
  return "Error: Invalid or missing description."
@@ -467,30 +381,36 @@ def generate_html_code(
467
  return "Error: Nebius API key not provided."
468
 
469
  prompt = f"""
470
- Generate a complete, single-file HTML webpage based on this description:
471
 
472
  {description}
473
 
474
- REQUIREMENTS:
475
- 1. Create a SINGLE HTML file with ALL styles and scripts INLINE
476
- 2. Use modern HTML5 semantic elements
477
- 3. Include ALL CSS inside <style> tags in the <head>
478
- 4. Include ALL JavaScript inside <script> tags before </body>
479
- 5. Use TailwindCSS via CDN: <script src="https://cdn.tailwindcss.com"></script>
480
- 6. Add custom CSS for animations and advanced styling in <style> tags
481
- 7. Make it fully responsive using Tailwind classes and/or CSS media queries
482
- 8. Use placeholder images from: https://picsum.photos/WIDTH/HEIGHT
483
- 9. Include smooth animations and transitions
484
- 10. Add interactive JavaScript for any dynamic elements
485
- 11. Use modern CSS features (flexbox, grid, custom properties)
486
- 12. Include proper meta tags for viewport and charset
487
- 13. Ensure high contrast and readability
488
- 14. DO NOT split code into separate files
489
- 15. DO NOT use external CSS files
490
- 16. ALL styles must be inline in the HTML
491
-
492
- The response must be a complete HTML document starting with <!DOCTYPE html> and ending with </html>.
493
- Include ALL styling inline. Do not reference external CSS files.
 
 
 
 
 
 
494
  """.strip()
495
 
496
  try:
@@ -506,7 +426,7 @@ Include ALL styling inline. Do not reference external CSS files.
506
 
507
  # Clean and validate the HTML
508
  html_code = _strip_fenced_code(content)
509
- html_code = ensure_complete_html(html_code)
510
 
511
  # Extract the complete HTML document
512
  if "<!DOCTYPE html>" in html_code.upper():
@@ -527,17 +447,16 @@ Include ALL styling inline. Do not reference external CSS files.
527
  except Exception as e:
528
  error_msg = str(e)
529
  if "404" in error_msg or "not found" in error_msg.lower():
530
- return f"Error: Model '{code_model}' not available. Try using one of the recommended code models: {', '.join(CODE_MODELS[:3])}"
531
  if "timeout" in error_msg.lower():
532
- return f"Error: Request timed out. Try reducing max tokens or using a faster model like: {', '.join(MODEL_RECOMMENDATIONS['fast'])}"
533
  return f"Error generating HTML code: {error_msg}"
534
 
535
 
536
  def create_codesandbox(html_code: str) -> str:
537
  """
538
  Create a CodeSandbox project from HTML code.
539
- Splits inline CSS/JS into separate files so you can open index.html and style.css directly.
540
- Returns Markdown with direct links to open index.html (and style.css/script.js if present) in the editor.
541
  """
542
  if not html_code or html_code.startswith("Error"):
543
  return "Error: No valid HTML code provided."
@@ -553,7 +472,7 @@ def create_codesandbox(html_code: str) -> str:
553
  if js_text:
554
  files["script.js"] = {"content": js_text, "isBinary": False}
555
 
556
- # package.json is optional for static template
557
  files["package.json"] = {
558
  "content": json.dumps(
559
  {
@@ -571,15 +490,13 @@ def create_codesandbox(html_code: str) -> str:
571
 
572
  parameters = {"files": files, "template": "static"}
573
 
574
- # Fallback GET URL with compressed parameters
575
  json_str = json.dumps(parameters, separators=(",", ":"))
576
  lz = LZString()
577
  compressed = lz.compressToBase64(json_str)
578
  compressed = compressed.replace("+", "-").replace("/", "_").rstrip("=")
579
  prefill_base = "https://codesandbox.io/api/v1/sandboxes/define"
580
  prefill_index = f"{prefill_base}?parameters={compressed}&file=/index.html"
581
- prefill_css = f"{prefill_base}?parameters={compressed}&file=/style.css" if "style.css" in files else ""
582
- prefill_js = f"{prefill_base}?parameters={compressed}&file=/script.js" if "script.js" in files else ""
583
 
584
  # Try POST API to get a sandbox_id
585
  url = "https://codesandbox.io/api/v1/sandboxes/define"
@@ -594,25 +511,13 @@ def create_codesandbox(html_code: str) -> str:
594
  preview_base = f"https://codesandbox.io/s/{sandbox_id}"
595
  lines = [
596
  f"**Successfully deployed to CodeSandbox!**\n",
597
- f"- Open index.html in editor: [{editor_base}?file=/index.html]({editor_base}?file=/index.html)",
 
598
  ]
599
- if "style.css" in files:
600
- lines.append(f"- Open style.css in editor: [{editor_base}?file=/style.css]({editor_base}?file=/style.css)")
601
- if "script.js" in files:
602
- lines.append(f"- Open script.js in editor: [{editor_base}?file=/script.js]({editor_base}?file=/script.js)")
603
- lines.append(f"- Live preview: [{preview_base}]({preview_base})")
604
  return "\n".join(lines)
605
 
606
- # Fallback to prefill URLs if POST fails
607
- lines = [
608
- "**CodeSandbox Links (click to deploy):**\n",
609
- f"- Open index.html: [{prefill_index}]({prefill_index})"
610
- ]
611
- if prefill_css:
612
- lines.append(f"- Open style.css: [{prefill_css}]({prefill_css})")
613
- if prefill_js:
614
- lines.append(f"- Open script.js: [{prefill_js}]({prefill_js})")
615
- return "\n".join(lines)
616
 
617
  except Exception as e:
618
  return f"Error creating CodeSandbox: {str(e)}"
@@ -671,9 +576,9 @@ def get_model_recommendations(performance_tier: str) -> Tuple[str, str]:
671
  Returns (vision_model, code_model)
672
  """
673
  tier_map = {
674
- "Fast (7B models)": ("Qwen/Qwen2.5-VL-7B-Instruct", "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"),
675
- "Balanced (32B-70B)": ("Qwen/Qwen2.5-VL-72B-Instruct", "Qwen/Qwen2.5-Coder-32B-Instruct"),
676
- "Quality (Large models)": ("Qwen/Qwen2.5-VL-72B-Instruct", "deepseek-ai/DeepSeek-V3"),
677
  }
678
  return tier_map.get(performance_tier, (DEFAULT_VISION_MODEL, DEFAULT_CODE_MODEL))
679
 
@@ -681,53 +586,53 @@ def get_model_recommendations(performance_tier: str) -> Tuple[str, str]:
681
  # =========================
682
  # Gradio UI
683
  # =========================
684
- BRIZY_PRIMARY = "#6C5CE7"
685
- BRIZY_SECONDARY = "#00C2FF"
686
- BRIZY_BG = "#F7F9FC"
687
- BRIZY_SURFACE = "#FFFFFF"
688
- BRIZY_TEXT = "#1F2937"
689
- BRIZY_MUTED = "#6B7280"
690
- BRIZY_BORDER = "#E5E7EB"
691
- BRIZY_GRADIENT = f"linear-gradient(135deg, {BRIZY_PRIMARY} 0%, {BRIZY_SECONDARY} 100%)"
692
 
693
  custom_css = f"""
694
  :root {{
695
- --app-primary: {BRIZY_PRIMARY};
696
- --app-secondary: {BRIZY_SECONDARY};
697
- --app-bg: {BRIZY_BG};
698
- --app-surface: {BRIZY_SURFACE};
699
- --app-text: {BRIZY_TEXT};
700
- --app-muted: {BRIZY_MUTED};
701
- --app-border: {BRIZY_BORDER};
702
  }}
703
 
704
  body {{
705
- background: var(--app-bg);
706
- color: var(--app-text);
707
  }}
708
 
709
  .section {{
710
- border: 1px solid var(--app-border);
711
  padding: 16px;
712
  border-radius: 12px;
713
- background: var(--app-surface);
714
  box-shadow: 0 1px 2px rgba(0,0,0,0.03);
715
  margin: 10px 0;
716
  }}
717
 
718
  .muted {{
719
- color: var(--app-muted);
720
  font-size: 0.92em;
721
  }}
722
 
723
  .footer {{
724
  text-align: center;
725
- color: var(--app-muted);
726
  padding: 8px 0;
727
  }}
728
 
729
  .title h1 {{
730
- background: {BRIZY_GRADIENT};
731
  -webkit-background-clip: text;
732
  background-clip: text;
733
  color: transparent;
@@ -736,7 +641,7 @@ custom_css = f"""
736
  }}
737
 
738
  .primary-btn button {{
739
- background: {BRIZY_GRADIENT} !important;
740
  color: #fff !important;
741
  border: none !important;
742
  font-weight: 600 !important;
@@ -750,56 +655,21 @@ custom_css = f"""
750
  }}
751
 
752
  .secondary-btn button {{
753
- background: var(--app-surface) !important;
754
- color: var(--app-text) !important;
755
- border: 1px solid var(--app-border) !important;
756
  font-weight: 500 !important;
757
  }}
758
  .secondary-btn button:hover {{
759
- border-color: {BRIZY_PRIMARY} !important;
760
- color: {BRIZY_PRIMARY} !important;
761
  }}
762
 
763
- /* Inputs focus */
764
  input:focus, textarea:focus, select:focus {{
765
- outline-color: {BRIZY_PRIMARY} !important;
766
- border-color: {BRIZY_PRIMARY} !important;
767
  box-shadow: 0 0 0 3px rgba(108,92,231,0.15) !important;
768
  }}
769
-
770
- /* Code block accents */
771
- .gr-code .cm-editor, .gr-code textarea {{
772
- border-radius: 10px !important;
773
- border: 1px solid var(--app-border) !important;
774
- }}
775
-
776
- /* Tabs accent */
777
- .gradio-container .tabs .tab-nav button[aria-selected="true"] {{
778
- color: {BRIZY_PRIMARY} !important;
779
- border-bottom: 2px solid {BRIZY_PRIMARY} !important;
780
- }}
781
-
782
- /* Model recommendation badge */
783
- .model-badge {{
784
- display: inline-block;
785
- padding: 4px 8px;
786
- border-radius: 4px;
787
- font-size: 12px;
788
- font-weight: 600;
789
- margin-left: 8px;
790
- }}
791
- .badge-fast {{
792
- background-color: #10B981;
793
- color: white;
794
- }}
795
- .badge-balanced {{
796
- background-color: #3B82F6;
797
- color: white;
798
- }}
799
- .badge-quality {{
800
- background-color: #8B5CF6;
801
- color: white;
802
- }}
803
  """
804
 
805
  with gr.Blocks(
@@ -810,27 +680,22 @@ with gr.Blocks(
810
  gr.Markdown(
811
  """
812
  # AI Website Generator (Nebius)
813
- Turn website screenshots into functional HTML using state-of-the-art Nebius-compatible models.
814
-
815
- ### Features:
816
- - **Vision Models**: Qwen VL series, Llama 3.2 Vision, Pixtral for image analysis
817
- - **Code Models**: DeepSeek V3, Qwen Coder, Llama 3.1, Mixtral for code generation
818
- - **Smart Fallbacks**: Automatic model switching if primary model is unavailable
819
- - **One-click Deployment**: Direct CodeSandbox integration
820
- - **Performance Tiers**: Choose between speed and quality
821
  """,
822
  elem_classes=["title"],
823
  )
824
 
825
- with gr.Accordion("API & Model Configuration", open=True):
826
  gr.Markdown(
827
  """
828
- Configure your Nebius API key and select models. The app includes a default key for testing.
829
-
830
- **Model Recommendations:**
831
- - **Fast**: 7B models for quick prototyping
832
- - **Balanced**: 32B-70B models for good quality/speed ratio
833
- - **Quality**: Large models for best results
834
  """,
835
  elem_classes=["muted"]
836
  )
@@ -839,65 +704,60 @@ Turn website screenshots into functional HTML using state-of-the-art Nebius-comp
839
  nebius_key = gr.Textbox(
840
  label="Nebius API Key",
841
  type="password",
842
- placeholder="Paste your Nebius API key, or use the default",
843
  value=DEFAULT_NEBIUS_API_KEY,
844
  )
845
  performance_tier = gr.Radio(
846
  label="Performance Tier",
847
- choices=["Fast (7B models)", "Balanced (32B-70B)", "Quality (Large models)"],
848
- value="Balanced (32B-70B)",
849
- info="Automatically selects optimal models for your needs"
850
  )
851
 
852
  with gr.Row():
853
  vision_model_dd = gr.Dropdown(
854
- label="Vision Model (for image analysis)",
855
  choices=VISION_MODELS,
856
  value=DEFAULT_VISION_MODEL,
857
  allow_custom_value=True,
858
- info="Select or type a custom vision-capable model",
859
  )
860
  code_model_dd = gr.Dropdown(
861
- label="Code Generation Model",
862
  choices=CODE_MODELS,
863
  value=DEFAULT_CODE_MODEL,
864
  allow_custom_value=True,
865
- info="Select or type a custom code generation model",
866
  )
867
 
868
  with gr.Row():
869
  code_max_tokens = gr.Slider(
870
- label="Max tokens (code generation)",
871
- minimum=500,
872
  maximum=8000,
873
- step=100,
874
  value=4000,
875
- info="Lower this if you experience timeouts",
876
  )
877
  code_temperature = gr.Slider(
878
  label="Temperature",
879
- minimum=0.0,
880
- maximum=1.5,
881
  step=0.1,
882
  value=0.7,
883
- info="Higher = more creative, Lower = more consistent",
884
  )
885
 
886
- # Auto-select models based on performance tier
887
- def update_models_from_tier(tier):
888
- vision, code = get_model_recommendations(tier)
889
- return vision, code
890
-
891
  performance_tier.change(
892
- fn=update_models_from_tier,
893
  inputs=[performance_tier],
894
  outputs=[vision_model_dd, code_model_dd]
895
  )
896
 
897
- with gr.Tab("Quick Generate"):
898
  with gr.Row():
899
  with gr.Column(scale=1):
900
- gr.Markdown("### Step 1: Upload Screenshot", elem_classes=["section"])
901
  image_input = gr.Image(
902
  type="pil",
903
  label="Website Screenshot",
@@ -907,30 +767,30 @@ Turn website screenshots into functional HTML using state-of-the-art Nebius-comp
907
 
908
  gr.Markdown(
909
  """
910
- **Tips for best results:**
911
- - Use clear, high-quality screenshots
912
- - Include full page layouts
913
- - Avoid complex animations in source
914
  """,
915
  elem_classes=["muted"]
916
  )
917
 
918
- generate_btn = gr.Button("Generate Website", elem_classes=["primary-btn"], size="lg")
919
 
920
  with gr.Column(scale=2):
921
- gr.Markdown("### Step 2: Review Results", elem_classes=["section"])
922
 
923
  with gr.Tabs():
924
  with gr.TabItem("Analysis"):
925
  description_output = gr.Textbox(
926
- label="Image Analysis Result",
927
  lines=8,
928
  interactive=False,
929
  )
930
 
931
- with gr.TabItem("Generated Code"):
932
  html_output = gr.Code(
933
- label="Generated HTML (copy or download)",
934
  language="html",
935
  lines=20,
936
  )
@@ -938,55 +798,55 @@ Turn website screenshots into functional HTML using state-of-the-art Nebius-comp
938
  with gr.Row():
939
  codesandbox_btn = gr.Button("Deploy to CodeSandbox", elem_classes=["secondary-btn"])
940
  download_btn = gr.Button("Download HTML", elem_classes=["secondary-btn"])
941
- copy_btn = gr.Button("Copy Code", elem_classes=["secondary-btn"])
942
 
943
- codesandbox_links = gr.Markdown(value="")
944
  download_file = gr.File(
945
- label="Download (index.html)",
946
  interactive=False,
947
  visible=False,
948
  )
949
 
950
- with gr.Tab("Individual Tools"):
951
  with gr.Row():
952
  with gr.Column():
953
- gr.Markdown("### Image Analysis Tool", elem_classes=["section"])
954
- img_tool = gr.Image(type="pil", label="Upload Image")
955
- analyze_btn = gr.Button("Analyze Image", elem_classes=["secondary-btn"])
956
- analysis_result = gr.Textbox(label="Analysis Result", lines=8)
957
 
958
  with gr.Column():
959
- gr.Markdown("### Code Generation Tool", elem_classes=["section"])
960
  desc_input = gr.Textbox(
961
- label="Website Description",
962
  lines=6,
963
- placeholder="Describe the website you want to create...\n\nExample: A modern landing page with a hero section, navigation bar, features grid, and contact form..."
964
  )
965
- code_btn = gr.Button("Generate Code", elem_classes=["secondary-btn"])
966
- code_result = gr.Code(label="Generated Code", language="html", lines=12)
967
 
968
- with gr.Tab("Model Information"):
969
  gr.Markdown(
970
  """
971
- ## Available Models on Nebius
972
 
973
- ### Vision Models (Image Analysis)
974
- - **Qwen/Qwen2.5-VL-72B-Instruct** - Best quality, latest Qwen vision model
975
- - **Qwen/Qwen2.5-VL-7B-Instruct** - Fast, efficient vision model
976
- - **meta-llama/Llama-3.2-90B-Vision-Instruct** - Meta's powerful vision model
977
- - **mistralai/Pixtral-12B-2409** - Mistral's vision model
978
 
979
- ### Code Generation Models
980
- - **deepseek-ai/DeepSeek-V3** - State-of-the-art code generation
981
- - **Qwen/Qwen2.5-Coder-32B-Instruct** - Specialized for coding tasks
982
- - **Qwen/QwQ-32B-Preview** - Advanced reasoning capabilities
983
- - **meta-llama/Meta-Llama-3.1-405B-Instruct** - Largest Llama model
984
- - **mistralai/Mixtral-8x22B-Instruct-v0.1** - MOE architecture for efficiency
 
 
 
985
 
986
- ### Performance Guidelines
987
- - **Timeouts?** Reduce max tokens or use smaller models
988
- - **Quality issues?** → Use larger models or increase temperature
989
- - **Model not found?** The app will automatically try fallback models
990
  """,
991
  elem_classes=["section"]
992
  )
@@ -994,63 +854,38 @@ Turn website screenshots into functional HTML using state-of-the-art Nebius-comp
994
  gr.Markdown(
995
  """
996
  ---
997
- Made with Gradio • Powered by Nebius AI Studio
998
-
999
- [GitHub](https://github.com) | [Documentation](https://nebius.com/docs) | [API Reference](https://nebius.com/api)
1000
  """,
1001
  elem_classes=["footer"]
1002
  )
1003
 
1004
- # Event bindings
1005
  generate_btn.click(
1006
  fn=screenshot_to_code,
1007
  inputs=[image_input, nebius_key, vision_model_dd, code_model_dd, code_max_tokens, code_temperature],
1008
  outputs=[description_output, html_output],
1009
  )
1010
 
1011
- def _deploy_to_codesandbox(html_code: str) -> str:
1012
- if not html_code or html_code.startswith("Error"):
1013
- return "**No valid code to deploy.** Generate code first."
1014
- return create_codesandbox(html_code)
1015
-
1016
  codesandbox_btn.click(
1017
- fn=_deploy_to_codesandbox,
1018
  inputs=[html_output],
1019
- outputs=[codesandbox_links],
1020
  )
1021
 
1022
- def _download_html(html_code: str):
1023
- path = export_html_to_file(html_code)
1024
- return gr.update(value=path, visible=bool(path))
1025
-
1026
  download_btn.click(
1027
- fn=_download_html,
1028
  inputs=[html_output],
1029
  outputs=[download_file],
1030
  )
1031
-
1032
- def _copy_to_clipboard(html_code: str) -> str:
1033
- if not html_code or html_code.startswith("Error"):
1034
- return "No valid code to copy"
1035
- # Note: Actual clipboard copying happens client-side
1036
- return "Code copied to clipboard!"
1037
-
1038
- copy_btn.click(
1039
- fn=_copy_to_clipboard,
1040
- inputs=[html_output],
1041
- outputs=[codesandbox_links],
1042
- )
1043
 
1044
  analyze_btn.click(
1045
- fn=lambda img, key, vmod: analyze_image(img, key, vmod),
1046
  inputs=[img_tool, nebius_key, vision_model_dd],
1047
  outputs=[analysis_result],
1048
  )
1049
 
1050
  code_btn.click(
1051
- fn=lambda desc, key, cmod, mtoks, temp: generate_html_code(
1052
- desc, key, code_model=cmod, code_max_tokens=mtoks, code_temperature=temp
1053
- ),
1054
  inputs=[desc_input, nebius_key, code_model_dd, code_max_tokens, code_temperature],
1055
  outputs=[code_result],
1056
  )
 
16
  # =========================
17
  NEBIUS_BASE_URL = "https://api.studio.nebius.com/v1/"
18
 
19
+ # Vision models that are confirmed to work with Nebius
20
  DEFAULT_VISION_MODEL = "Qwen/Qwen2.5-VL-72B-Instruct"
21
  VISION_MODELS = [
22
  DEFAULT_VISION_MODEL,
23
  "Qwen/Qwen2.5-VL-7B-Instruct",
 
 
24
  ]
25
 
26
+ # Code generation models confirmed to work on Nebius
27
+ DEFAULT_CODE_MODEL = "Qwen/Qwen2.5-72B-Instruct"
28
  CODE_MODELS = [
29
  DEFAULT_CODE_MODEL,
30
  "Qwen/Qwen2.5-Coder-32B-Instruct",
 
 
31
  "Qwen/Qwen2.5-7B-Instruct",
32
+ "deepseek-ai/DeepSeek-V3",
33
  "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
34
  "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",
 
35
  "meta-llama/Meta-Llama-3.1-70B-Instruct",
36
  "meta-llama/Meta-Llama-3.1-8B-Instruct",
 
 
 
 
 
 
 
 
37
  ]
38
 
39
+ # Performance recommendations based on working models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  MODEL_RECOMMENDATIONS = {
41
+ "fast": ["Qwen/Qwen2.5-7B-Instruct", "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", "meta-llama/Meta-Llama-3.1-8B-Instruct"],
42
+ "balanced": ["Qwen/Qwen2.5-Coder-32B-Instruct", "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"],
43
+ "quality": ["Qwen/Qwen2.5-72B-Instruct", "deepseek-ai/DeepSeek-V3", "meta-llama/Meta-Llama-3.1-70B-Instruct"],
44
  }
45
 
46
  # Timeouts and retries
 
52
  "eyJhbGciOiJIUzI1NiIsImtpZCI6IlV6SXJWd1h0dnprLVRvdzlLZWstc0M1akptWXBvX1VaVkxUZlpnMDRlOFUiLCJ0eXAiOiJKV1QifQ.eyJzdWIiOiJnb29nbGUtb2F1dGgyfDEwNTA1MTQzMDg2MDMwMzIxNDEwMiIsInNjb3BlIjoib3BlbmlkIG9mZmxpbmVfYWNjZXNzIiwiaXNzIjoiYXBpX2tleV9pc3N1ZXIiLCJhdWQiOlsiaHR0cHM6Ly9uZWJpdXMtaW5mZXJlbmNlLmV1LmF1dGgwLmNvbS9hcGkvdjIvIl0sImV4cCI6MTkwNjU5ODA0NCwidXVpZCI6ImNkOGFiMWZlLTIxN2QtNDJlMy04OWUwLWM1YTg4MjcwMGVhNyIsIm5hbWUiOiJodW5nZ2luZyIsImV4cGlyZXNfYXQiOiIyMDMwLTA2LTAyVDAyOjM0OjA0KzAwMDAifQ.MA52QuIiNruK7_lX688RXAEI2TkcCOjcf_02XrpnhI8"
53
  )
54
 
 
 
 
 
55
  # =========================
56
  # Helpers
57
  # =========================
 
65
  return (user_key or "").strip() or os.getenv("NEBIUS_API_KEY", "").strip() or DEFAULT_NEBIUS_API_KEY
66
 
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  def call_chat_completions(
69
  model: str,
70
  messages: list,
 
104
  # Try a fallback model
105
  fallback_models = {
106
  "vision": ["Qwen/Qwen2.5-VL-7B-Instruct"],
107
+ "code": ["Qwen/Qwen2.5-7B-Instruct"],
108
  }
109
 
110
  # Detect model type and use appropriate fallback
 
147
 
148
  def _strip_fenced_code(text: str) -> str:
149
  """
150
+ Removes code fences from a content block if present.
 
151
  """
152
  s = text.strip()
153
 
 
159
  ]
160
 
161
  for start_pattern, end_pattern in patterns:
162
+ if re.match(start_pattern, s, re.IGNORECASE):
163
+ s = re.sub(start_pattern, '', s, flags=re.IGNORECASE)
164
+ s = re.sub(end_pattern, '', s, flags=re.IGNORECASE)
165
  break
166
 
167
  return s.strip()
168
 
169
 
170
+ def ensure_complete_html_with_css(html_code: str) -> str:
171
  """
172
  Ensures the HTML code is complete with proper structure and inline CSS.
173
+ Forces inclusion of styles within the HTML document.
174
  """
175
  # Check if it's already a complete HTML document
176
+ has_doctype = "<!DOCTYPE html>" in html_code.upper()
177
+ has_html_tag = "<html" in html_code.lower()
178
+ has_style = "<style" in html_code.lower() or "style=" in html_code.lower()
179
+
180
+ # If it's complete and has styles, return as is
181
+ if has_doctype and has_html_tag and has_style:
182
  return html_code
183
 
184
+ # If it's missing styles or structure, create a complete document
185
+ if not has_doctype or not has_html_tag:
186
+ # Extract any existing styles
187
+ existing_styles = ""
188
+ style_matches = re.findall(r'<style[^>]*>(.*?)</style>', html_code, re.IGNORECASE | re.DOTALL)
189
+ if style_matches:
190
+ existing_styles = '\n'.join(style_matches)
191
+
192
+ # Remove style tags from body content
193
+ body_content = re.sub(r'<style[^>]*>.*?</style>', '', html_code, flags=re.IGNORECASE | re.DOTALL)
194
+
195
  html_code = f"""<!DOCTYPE html>
196
  <html lang="en">
197
  <head>
 
206
  box-sizing: border-box;
207
  }}
208
  body {{
209
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
210
  line-height: 1.6;
211
+ color: #1a202c;
212
  }}
213
+ .container {{
214
+ max-width: 1200px;
215
+ margin: 0 auto;
216
+ padding: 0 20px;
217
+ }}
218
+ {existing_styles}
219
  </style>
220
  </head>
221
  <body>
222
+ {body_content}
223
  </body>
224
  </html>"""
225
 
226
+ # Ensure TailwindCSS is included
227
+ if "tailwindcss" not in html_code.lower():
228
+ html_code = html_code.replace(
229
+ "</head>",
230
+ ' <script src="https://cdn.tailwindcss.com"></script>\n</head>'
231
+ )
232
+
233
  return html_code
234
 
235
 
 
277
  html,
278
  flags=re.IGNORECASE,
279
  )
 
 
280
 
281
  # If JS collected, ensure script tag before </body> or at end
282
  if js_text:
 
287
  html,
288
  flags=re.IGNORECASE,
289
  )
 
 
290
 
291
  return html, css_text, js_text
292
 
 
300
  vision_model: str = DEFAULT_VISION_MODEL,
301
  ) -> str:
302
  """
303
+ Analyze an uploaded image and provide a detailed description of its content and layout.
304
  """
305
  if image is None:
306
  return "Error: No image provided."
 
321
  img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
322
 
323
  prompt = (
324
+ "Analyze this image and provide a comprehensive description for recreating it as a website. "
325
+ "Include the following details:\n"
326
+ "1. Overall layout structure (header, main sections, sidebars, footer)\n"
327
+ "2. Color scheme with specific hex codes or RGB values if identifiable\n"
328
+ "3. Typography details (font families, sizes, weights, line heights)\n"
329
+ "4. UI components (navigation bars, buttons, forms, cards, modals, etc.)\n"
330
+ "5. Content sections and their arrangement\n"
331
+ "6. Images, icons, and media elements placement\n"
332
+ "7. Spacing, padding, and margins between elements\n"
333
+ "8. Any animations, hover effects, or interactive elements visible\n"
334
+ "9. Responsive design considerations and breakpoints\n"
335
+ "10. Background patterns, gradients, or textures\n"
336
+ "Be as specific as possible about CSS properties and HTML structure needed."
337
  )
338
 
339
  messages = [
 
351
  model=vision_model,
352
  messages=messages,
353
  api_key=api_key,
354
+ max_tokens=2000,
355
  temperature=0.7,
356
  retry_with_fallback=True,
357
  )
 
359
  except Exception as e:
360
  error_msg = str(e)
361
  if "404" in error_msg or "not found" in error_msg.lower():
362
+ return f"Error: Model '{vision_model}' not available. Try using: {', '.join(VISION_MODELS)}"
363
  return f"Error analyzing image: {error_msg}"
364
 
365
 
 
371
  code_temperature: float = 0.7,
372
  ) -> str:
373
  """
374
+ Generate complete HTML/CSS/JavaScript code based on a website description.
375
  """
376
  if not description or description.startswith("Error"):
377
  return "Error: Invalid or missing description."
 
381
  return "Error: Nebius API key not provided."
382
 
383
  prompt = f"""
384
+ You are an expert web developer. Generate a complete, single-file HTML webpage based on this description:
385
 
386
  {description}
387
 
388
+ CRITICAL REQUIREMENTS:
389
+ 1. Create ONE SINGLE HTML file with ALL code inline
390
+ 2. Include ALL CSS inside <style> tags within the <head> section
391
+ 3. Include ALL JavaScript inside <script> tags before the closing </body> tag
392
+ 4. Use TailwindCSS via CDN: <script src="https://cdn.tailwindcss.com"></script>
393
+ 5. Combine Tailwind utility classes with custom CSS in <style> tags
394
+ 6. Make the page fully responsive with mobile-first design
395
+ 7. Use high-quality placeholder images from: https://picsum.photos/WIDTH/HEIGHT
396
+ 8. Add smooth transitions and hover effects
397
+ 9. Include interactive JavaScript for dynamic functionality
398
+ 10. Use semantic HTML5 elements
399
+ 11. Add proper meta tags for viewport and charset
400
+ 12. Ensure accessibility with proper ARIA labels
401
+ 13. Use modern CSS (flexbox, grid, custom properties)
402
+ 14. DO NOT create separate files
403
+ 15. DO NOT reference external stylesheets except CDNs
404
+ 16. ALL custom styles MUST be in <style> tags
405
+
406
+ OUTPUT FORMAT:
407
+ - Start with: <!DOCTYPE html>
408
+ - End with: </html>
409
+ - Include complete HTML structure
410
+ - All CSS in <style> tags
411
+ - All JS in <script> tags
412
+
413
+ Provide ONLY the HTML code, no explanations or markdown fences.
414
  """.strip()
415
 
416
  try:
 
426
 
427
  # Clean and validate the HTML
428
  html_code = _strip_fenced_code(content)
429
+ html_code = ensure_complete_html_with_css(html_code)
430
 
431
  # Extract the complete HTML document
432
  if "<!DOCTYPE html>" in html_code.upper():
 
447
  except Exception as e:
448
  error_msg = str(e)
449
  if "404" in error_msg or "not found" in error_msg.lower():
450
+ return f"Error: Model '{code_model}' not available. Try using: {', '.join(CODE_MODELS[:3])}"
451
  if "timeout" in error_msg.lower():
452
+ return f"Error: Request timed out. Try reducing max tokens or using: {', '.join(MODEL_RECOMMENDATIONS['fast'])}"
453
  return f"Error generating HTML code: {error_msg}"
454
 
455
 
456
  def create_codesandbox(html_code: str) -> str:
457
  """
458
  Create a CodeSandbox project from HTML code.
459
+ Returns Markdown with direct links to open files in the editor.
 
460
  """
461
  if not html_code or html_code.startswith("Error"):
462
  return "Error: No valid HTML code provided."
 
472
  if js_text:
473
  files["script.js"] = {"content": js_text, "isBinary": False}
474
 
475
+ # package.json for static template
476
  files["package.json"] = {
477
  "content": json.dumps(
478
  {
 
490
 
491
  parameters = {"files": files, "template": "static"}
492
 
493
+ # Create compressed URL parameters
494
  json_str = json.dumps(parameters, separators=(",", ":"))
495
  lz = LZString()
496
  compressed = lz.compressToBase64(json_str)
497
  compressed = compressed.replace("+", "-").replace("/", "_").rstrip("=")
498
  prefill_base = "https://codesandbox.io/api/v1/sandboxes/define"
499
  prefill_index = f"{prefill_base}?parameters={compressed}&file=/index.html"
 
 
500
 
501
  # Try POST API to get a sandbox_id
502
  url = "https://codesandbox.io/api/v1/sandboxes/define"
 
511
  preview_base = f"https://codesandbox.io/s/{sandbox_id}"
512
  lines = [
513
  f"**Successfully deployed to CodeSandbox!**\n",
514
+ f"- Editor: [{editor_base}]({editor_base}?file=/index.html)",
515
+ f"- Live Preview: [{preview_base}]({preview_base})",
516
  ]
 
 
 
 
 
517
  return "\n".join(lines)
518
 
519
+ # Fallback to prefill URL
520
+ return f"**Click to deploy to CodeSandbox:**\n[Open in CodeSandbox]({prefill_index})"
 
 
 
 
 
 
 
 
521
 
522
  except Exception as e:
523
  return f"Error creating CodeSandbox: {str(e)}"
 
576
  Returns (vision_model, code_model)
577
  """
578
  tier_map = {
579
+ "Fast (7B-8B models)": ("Qwen/Qwen2.5-VL-7B-Instruct", "Qwen/Qwen2.5-7B-Instruct"),
580
+ "Balanced (32B models)": ("Qwen/Qwen2.5-VL-7B-Instruct", "Qwen/Qwen2.5-Coder-32B-Instruct"),
581
+ "Quality (70B+ models)": ("Qwen/Qwen2.5-VL-72B-Instruct", "Qwen/Qwen2.5-72B-Instruct"),
582
  }
583
  return tier_map.get(performance_tier, (DEFAULT_VISION_MODEL, DEFAULT_CODE_MODEL))
584
 
 
586
  # =========================
587
  # Gradio UI
588
  # =========================
589
+ THEME_PRIMARY = "#6C5CE7"
590
+ THEME_SECONDARY = "#00C2FF"
591
+ THEME_BG = "#F7F9FC"
592
+ THEME_SURFACE = "#FFFFFF"
593
+ THEME_TEXT = "#1F2937"
594
+ THEME_MUTED = "#6B7280"
595
+ THEME_BORDER = "#E5E7EB"
596
+ THEME_GRADIENT = f"linear-gradient(135deg, {THEME_PRIMARY} 0%, {THEME_SECONDARY} 100%)"
597
 
598
  custom_css = f"""
599
  :root {{
600
+ --primary: {THEME_PRIMARY};
601
+ --secondary: {THEME_SECONDARY};
602
+ --bg: {THEME_BG};
603
+ --surface: {THEME_SURFACE};
604
+ --text: {THEME_TEXT};
605
+ --muted: {THEME_MUTED};
606
+ --border: {THEME_BORDER};
607
  }}
608
 
609
  body {{
610
+ background: var(--bg);
611
+ color: var(--text);
612
  }}
613
 
614
  .section {{
615
+ border: 1px solid var(--border);
616
  padding: 16px;
617
  border-radius: 12px;
618
+ background: var(--surface);
619
  box-shadow: 0 1px 2px rgba(0,0,0,0.03);
620
  margin: 10px 0;
621
  }}
622
 
623
  .muted {{
624
+ color: var(--muted);
625
  font-size: 0.92em;
626
  }}
627
 
628
  .footer {{
629
  text-align: center;
630
+ color: var(--muted);
631
  padding: 8px 0;
632
  }}
633
 
634
  .title h1 {{
635
+ background: {THEME_GRADIENT};
636
  -webkit-background-clip: text;
637
  background-clip: text;
638
  color: transparent;
 
641
  }}
642
 
643
  .primary-btn button {{
644
+ background: {THEME_GRADIENT} !important;
645
  color: #fff !important;
646
  border: none !important;
647
  font-weight: 600 !important;
 
655
  }}
656
 
657
  .secondary-btn button {{
658
+ background: var(--surface) !important;
659
+ color: var(--text) !important;
660
+ border: 1px solid var(--border) !important;
661
  font-weight: 500 !important;
662
  }}
663
  .secondary-btn button:hover {{
664
+ border-color: {THEME_PRIMARY} !important;
665
+ color: {THEME_PRIMARY} !important;
666
  }}
667
 
 
668
  input:focus, textarea:focus, select:focus {{
669
+ outline-color: {THEME_PRIMARY} !important;
670
+ border-color: {THEME_PRIMARY} !important;
671
  box-shadow: 0 0 0 3px rgba(108,92,231,0.15) !important;
672
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
673
  """
674
 
675
  with gr.Blocks(
 
680
  gr.Markdown(
681
  """
682
  # AI Website Generator (Nebius)
683
+ Transform website screenshots into functional HTML code using Nebius AI models.
684
+
685
+ ### Key Features:
686
+ - Vision Analysis with Qwen VL models
687
+ - Code Generation with Qwen, DeepSeek, and Llama models
688
+ - Single-file HTML output with inline CSS
689
+ - Direct CodeSandbox deployment
690
+ - Automatic fallback for unavailable models
691
  """,
692
  elem_classes=["title"],
693
  )
694
 
695
+ with gr.Accordion("Configuration", open=True):
696
  gr.Markdown(
697
  """
698
+ Configure your API settings and model preferences. A default API key is provided for testing.
 
 
 
 
 
699
  """,
700
  elem_classes=["muted"]
701
  )
 
704
  nebius_key = gr.Textbox(
705
  label="Nebius API Key",
706
  type="password",
707
+ placeholder="Enter your Nebius API key or use default",
708
  value=DEFAULT_NEBIUS_API_KEY,
709
  )
710
  performance_tier = gr.Radio(
711
  label="Performance Tier",
712
+ choices=["Fast (7B-8B models)", "Balanced (32B models)", "Quality (70B+ models)"],
713
+ value="Balanced (32B models)",
714
+ info="Select based on speed vs quality preference"
715
  )
716
 
717
  with gr.Row():
718
  vision_model_dd = gr.Dropdown(
719
+ label="Vision Model",
720
  choices=VISION_MODELS,
721
  value=DEFAULT_VISION_MODEL,
722
  allow_custom_value=True,
723
+ info="Model for image analysis",
724
  )
725
  code_model_dd = gr.Dropdown(
726
+ label="Code Model",
727
  choices=CODE_MODELS,
728
  value=DEFAULT_CODE_MODEL,
729
  allow_custom_value=True,
730
+ info="Model for HTML generation",
731
  )
732
 
733
  with gr.Row():
734
  code_max_tokens = gr.Slider(
735
+ label="Max Tokens",
736
+ minimum=1000,
737
  maximum=8000,
738
+ step=500,
739
  value=4000,
740
+ info="Lower if experiencing timeouts",
741
  )
742
  code_temperature = gr.Slider(
743
  label="Temperature",
744
+ minimum=0.1,
745
+ maximum=1.0,
746
  step=0.1,
747
  value=0.7,
748
+ info="Creativity level",
749
  )
750
 
 
 
 
 
 
751
  performance_tier.change(
752
+ fn=get_model_recommendations,
753
  inputs=[performance_tier],
754
  outputs=[vision_model_dd, code_model_dd]
755
  )
756
 
757
+ with gr.Tab("Generate"):
758
  with gr.Row():
759
  with gr.Column(scale=1):
760
+ gr.Markdown("### Upload Screenshot", elem_classes=["section"])
761
  image_input = gr.Image(
762
  type="pil",
763
  label="Website Screenshot",
 
767
 
768
  gr.Markdown(
769
  """
770
+ **Best Practices:**
771
+ - Use clear, complete screenshots
772
+ - Include all important UI elements
773
+ - Higher resolution produces better results
774
  """,
775
  elem_classes=["muted"]
776
  )
777
 
778
+ generate_btn = gr.Button("Generate HTML", elem_classes=["primary-btn"], size="lg")
779
 
780
  with gr.Column(scale=2):
781
+ gr.Markdown("### Results", elem_classes=["section"])
782
 
783
  with gr.Tabs():
784
  with gr.TabItem("Analysis"):
785
  description_output = gr.Textbox(
786
+ label="Image Analysis",
787
  lines=8,
788
  interactive=False,
789
  )
790
 
791
+ with gr.TabItem("HTML Code"):
792
  html_output = gr.Code(
793
+ label="Generated HTML",
794
  language="html",
795
  lines=20,
796
  )
 
798
  with gr.Row():
799
  codesandbox_btn = gr.Button("Deploy to CodeSandbox", elem_classes=["secondary-btn"])
800
  download_btn = gr.Button("Download HTML", elem_classes=["secondary-btn"])
 
801
 
802
+ deployment_result = gr.Markdown(value="")
803
  download_file = gr.File(
804
+ label="Download",
805
  interactive=False,
806
  visible=False,
807
  )
808
 
809
+ with gr.Tab("Tools"):
810
  with gr.Row():
811
  with gr.Column():
812
+ gr.Markdown("### Image Analyzer", elem_classes=["section"])
813
+ img_tool = gr.Image(type="pil", label="Image")
814
+ analyze_btn = gr.Button("Analyze", elem_classes=["secondary-btn"])
815
+ analysis_result = gr.Textbox(label="Analysis", lines=8)
816
 
817
  with gr.Column():
818
+ gr.Markdown("### Code Generator", elem_classes=["section"])
819
  desc_input = gr.Textbox(
820
+ label="Description",
821
  lines=6,
822
+ placeholder="Describe the website to generate..."
823
  )
824
+ code_btn = gr.Button("Generate", elem_classes=["secondary-btn"])
825
+ code_result = gr.Code(label="HTML Output", language="html", lines=12)
826
 
827
+ with gr.Tab("Models"):
828
  gr.Markdown(
829
  """
830
+ ## Working Models on Nebius
831
 
832
+ ### Vision Models
833
+ - **Qwen/Qwen2.5-VL-72B-Instruct** - High quality vision analysis
834
+ - **Qwen/Qwen2.5-VL-7B-Instruct** - Fast vision processing
 
 
835
 
836
+ ### Code Generation Models
837
+ - **Qwen/Qwen2.5-72B-Instruct** - Best overall quality
838
+ - **Qwen/Qwen2.5-Coder-32B-Instruct** - Optimized for code
839
+ - **Qwen/Qwen2.5-7B-Instruct** - Fast generation
840
+ - **deepseek-ai/DeepSeek-V3** - Advanced code generation
841
+ - **deepseek-ai/DeepSeek-R1-Distill-Qwen-32B** - Balanced performance
842
+ - **deepseek-ai/DeepSeek-R1-Distill-Qwen-7B** - Fast alternative
843
+ - **meta-llama/Meta-Llama-3.1-70B-Instruct** - High quality
844
+ - **meta-llama/Meta-Llama-3.1-8B-Instruct** - Fast Llama option
845
 
846
+ ### Tips
847
+ - Start with Balanced tier for best results
848
+ - Use Fast tier for quick prototypes
849
+ - Quality tier for production-ready code
850
  """,
851
  elem_classes=["section"]
852
  )
 
854
  gr.Markdown(
855
  """
856
  ---
857
+ Powered by Nebius AI Studio | Built with Gradio
 
 
858
  """,
859
  elem_classes=["footer"]
860
  )
861
 
862
+ # Event handlers
863
  generate_btn.click(
864
  fn=screenshot_to_code,
865
  inputs=[image_input, nebius_key, vision_model_dd, code_model_dd, code_max_tokens, code_temperature],
866
  outputs=[description_output, html_output],
867
  )
868
 
 
 
 
 
 
869
  codesandbox_btn.click(
870
+ fn=lambda code: create_codesandbox(code) if code and not code.startswith("Error") else "No valid code to deploy.",
871
  inputs=[html_output],
872
+ outputs=[deployment_result],
873
  )
874
 
 
 
 
 
875
  download_btn.click(
876
+ fn=lambda code: gr.update(value=export_html_to_file(code), visible=bool(export_html_to_file(code))),
877
  inputs=[html_output],
878
  outputs=[download_file],
879
  )
 
 
 
 
 
 
 
 
 
 
 
 
880
 
881
  analyze_btn.click(
882
+ fn=analyze_image,
883
  inputs=[img_tool, nebius_key, vision_model_dd],
884
  outputs=[analysis_result],
885
  )
886
 
887
  code_btn.click(
888
+ fn=generate_html_code,
 
 
889
  inputs=[desc_input, nebius_key, code_model_dd, code_max_tokens, code_temperature],
890
  outputs=[code_result],
891
  )