faizee07 commited on
Commit
ae01dc3
Β·
verified Β·
1 Parent(s): e645415

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -428
app.py CHANGED
@@ -5,465 +5,234 @@ import re
5
  from typing import Dict, List, Tuple
6
  import os
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  class UIDesignAgent:
 
9
  def __init__(self, api_key: str):
10
  self.api_key = api_key
11
  self.base_url = "https://openrouter.ai/api/v1/chat/completions"
 
12
  self.design_system = {}
13
-
14
- def extract_design_system(self, prompt: str) -> Dict:
15
- """Extract and maintain consistent design system across pages"""
16
-
17
- system_prompt = """You are an expert UI/UX designer. Create a consistent design system with:
18
- - Primary color: #2563eb
19
- - Secondary color: #7c3aed
20
- - Accent color: #06b6d4
21
- - Background: #ffffff
22
- - Text: #1e293b
23
- - Font: Inter, system-ui, sans-serif
24
-
25
- Use these EXACT colors for ALL pages. Return the design system as JSON."""
26
-
27
- headers = {
28
- "Authorization": f"Bearer {self.api_key}",
29
- "Content-Type": "application/json",
30
- "HTTP-Referer": "https://huggingface.co",
31
- "X-Title": "AI UI Designer"
32
- }
33
-
34
- data = {
35
- "model": "minimax/minimax-m2:free",
36
- "messages": [
37
- {"role": "system", "content": system_prompt},
38
- {"role": "user", "content": f"Create a design system for: {prompt}"}
39
- ],
40
- "temperature": 0.7,
41
- "max_tokens": 10000
42
  }
43
-
 
 
 
 
 
 
 
44
  try:
45
- response = requests.post(self.base_url, headers=headers, json=data)
46
- if response.status_code == 200:
47
- result = response.json()
48
- if 'choices' in result and len(result['choices']) > 0:
49
- design_system_text = result['choices'][0]['message']['content']
50
-
51
- # Try to extract JSON
52
- json_match = re.search(r'\{.*\}', design_system_text, re.DOTALL)
53
- if json_match:
54
- try:
55
- self.design_system = json.loads(json_match.group())
56
- except:
57
- self.design_system = self.get_default_design_system()
58
- else:
59
- self.design_system = self.get_default_design_system()
60
- else:
61
- self.design_system = self.get_default_design_system()
62
- else:
63
- print(f"API Error: {response.status_code}")
64
- self.design_system = self.get_default_design_system()
65
-
66
  return self.design_system
67
  except Exception as e:
68
- print(f"Error: {e}")
69
  return self.get_default_design_system()
70
-
71
- def get_default_design_system(self) -> Dict:
72
- """Return default design system"""
73
- return {
74
- "colors": {
75
- "primary": "#2563eb",
76
- "secondary": "#7c3aed",
77
- "accent": "#06b6d4",
78
- "background": "#ffffff",
79
- "surface": "#f8fafc",
80
- "text": "#1e293b",
81
- "textLight": "#64748b",
82
- "border": "#e2e8f0"
83
- },
84
- "typography": {
85
- "fontFamily": "'Inter', system-ui, -apple-system, sans-serif",
86
- "h1": "2.5rem",
87
- "h2": "2rem",
88
- "h3": "1.5rem",
89
- "body": "1rem",
90
- "small": "0.875rem"
91
- },
92
- "spacing": {
93
- "xs": "0.25rem",
94
- "sm": "0.5rem",
95
- "md": "1rem",
96
- "lg": "1.5rem",
97
- "xl": "2rem",
98
- "xxl": "3rem"
99
- },
100
- "borderRadius": {
101
- "sm": "0.25rem",
102
- "md": "0.5rem",
103
- "lg": "0.75rem",
104
- "xl": "1rem"
105
- }
106
- }
107
-
108
- def generate_page_html(self, page_description: str, page_number: int) -> str:
109
- """Generate HTML for a specific page using consistent design system"""
110
-
111
- colors = self.design_system.get('colors', self.get_default_design_system()['colors'])
112
- typography = self.design_system.get('typography', self.get_default_design_system()['typography'])
113
-
114
- system_prompt = f"""You are an expert UI developer. Create a complete HTML page.
115
-
116
- MANDATORY DESIGN RULES:
117
- - Primary color: {colors['primary']}
118
- - Secondary color: {colors['secondary']}
119
- - Background: {colors['background']}
120
- - Text color: {colors['text']}
121
- - Font family: {typography['fontFamily']}
122
-
123
- Use ONLY these colors. DO NOT use any other colors.
124
- Create a modern, responsive design with these exact colors.
125
- Include all CSS inline in the HTML.
126
  """
127
-
128
- headers = {
129
- "Authorization": f"Bearer {self.api_key}",
130
- "Content-Type": "application/json",
131
- "HTTP-Referer": "https://huggingface.co",
132
- "X-Title": "AI UI Designer"
133
- }
134
-
135
- data = {
136
- "model": "minimax/minimax-m2:free",
137
- "messages": [
138
- {"role": "system", "content": system_prompt},
139
- {"role": "user", "content": f"Create page {page_number}: {page_description}. Use the EXACT colors provided. Return complete HTML with inline CSS."}
140
- ],
141
- "temperature": 0.7,
142
- "max_tokens": 2000
143
- }
144
-
145
  try:
146
- response = requests.post(self.base_url, headers=headers, json=data)
147
- if response.status_code == 200:
148
- result = response.json()
149
- if 'choices' in result and len(result['choices']) > 0:
150
- html_content = result['choices'][0]['message']['content']
151
-
152
- # Extract HTML from code blocks
153
- html_match = re.search(r'```html?\n?(.*?)\n?```', html_content, re.DOTALL)
154
- if html_match:
155
- html_content = html_match.group(1)
156
-
157
- # Inject consistent CSS
158
- html_content = self.inject_design_system_css(html_content)
159
- return html_content
160
- else:
161
- return self.create_error_page(f"No response for page {page_number}")
162
- else:
163
- return self.create_error_page(f"API Error: {response.status_code}")
164
  except Exception as e:
165
- return self.create_error_page(f"Error: {str(e)}")
166
-
167
- def inject_design_system_css(self, html: str) -> str:
168
- """Inject consistent CSS variables into HTML"""
169
-
170
- colors = self.design_system.get('colors', self.get_default_design_system()['colors'])
171
- typography = self.design_system.get('typography', self.get_default_design_system()['typography'])
172
-
173
- css_injection = f"""
174
- <style>
175
- :root {{
176
- --primary: {colors['primary']};
177
- --secondary: {colors['secondary']};
178
- --accent: {colors['accent']};
179
- --background: {colors['background']};
180
- --surface: {colors['surface']};
181
- --text: {colors['text']};
182
- --text-light: {colors['textLight']};
183
- --border: {colors['border']};
184
- --font-family: {typography['fontFamily']};
185
- }}
186
-
187
- * {{
188
- margin: 0;
189
- padding: 0;
190
- box-sizing: border-box;
191
- }}
192
-
193
- body {{
194
- font-family: var(--font-family);
195
- background-color: var(--background);
196
- color: var(--text);
197
- line-height: 1.6;
198
- }}
199
-
200
- h1, h2, h3, h4, h5, h6 {{
201
- color: var(--text);
202
- margin-bottom: 1rem;
203
- }}
204
-
205
- a {{
206
- color: var(--primary);
207
- text-decoration: none;
208
- }}
209
-
210
- a:hover {{
211
- text-decoration: underline;
212
- }}
213
-
214
- button, .btn {{
215
- background-color: var(--primary);
216
- color: white;
217
- padding: 0.75rem 1.5rem;
218
- border: none;
219
- border-radius: 0.5rem;
220
- cursor: pointer;
221
- font-family: var(--font-family);
222
- transition: all 0.3s ease;
223
- }}
224
-
225
- button:hover, .btn:hover {{
226
- transform: translateY(-2px);
227
- box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
228
- }}
229
- </style>
230
- """
231
-
232
- # Add viewport meta tag if not present
233
- viewport_meta = '<meta name="viewport" content="width=device-width, initial-scale=1.0">'
234
-
235
- if '<head>' in html:
236
- html = html.replace('<head>', f'<head>\n{viewport_meta}\n{css_injection}')
237
- elif '<html>' in html:
238
- html = html.replace('<html>', f'<html>\n<head>\n{viewport_meta}\n{css_injection}\n</head>')
239
- else:
240
- html = f"""<!DOCTYPE html>
241
- <html>
242
- <head>
243
- {viewport_meta}
244
- {css_injection}
245
- </head>
246
- <body>
247
- {html}
248
- </body>
249
- </html>"""
250
-
251
- return html
252
-
253
- def create_error_page(self, error_message: str) -> str:
254
- """Create an error page with consistent styling"""
255
- return f"""
256
- <!DOCTYPE html>
257
- <html>
258
- <head>
259
- <style>
260
- body {{
261
- font-family: system-ui, -apple-system, sans-serif;
262
- display: flex;
263
- align-items: center;
264
- justify-content: center;
265
- height: 100vh;
266
- margin: 0;
267
- background: #f8fafc;
268
- }}
269
- .error {{
270
- background: white;
271
- padding: 2rem;
272
- border-radius: 0.5rem;
273
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
274
- max-width: 500px;
275
- }}
276
- h2 {{
277
- color: #ef4444;
278
- margin-bottom: 1rem;
279
- }}
280
- p {{
281
- color: #64748b;
282
- }}
283
- </style>
284
- </head>
285
- <body>
286
- <div class="error">
287
- <h2>Error Generating Page</h2>
288
- <p>{error_message}</p>
289
- </div>
290
- </body>
291
- </html>
292
- """
293
-
294
- def extract_pages_from_prompt(self, prompt: str) -> List[str]:
295
- """Extract individual page descriptions from prompt"""
296
-
297
- # Simple extraction based on common patterns
298
- pages = []
299
-
300
- # Check for numbered pages
301
- numbered_pattern = r'\d+\)([^0-9]+?)(?=\d+\)|$)'
302
- matches = re.findall(numbered_pattern, prompt)
303
-
304
- if matches:
305
- pages = [match.strip() for match in matches]
306
- else:
307
- # Check for comma-separated pages
308
- if ',' in prompt:
309
- pages = [p.strip() for p in prompt.split(',')]
310
- else:
311
- # Treat as single page
312
- pages = [prompt]
313
-
314
- # Ensure we have at least 3 pages
315
- while len(pages) < 3:
316
- pages.append(f"Additional page {len(pages) + 1}")
317
-
318
- return pages[:3] # Limit to 3 pages
319
-
320
  def generate_multi_page_design(self, prompt: str) -> Tuple[List[str], Dict]:
321
- """Generate multiple pages with consistent design"""
322
-
323
- # Extract design system first
324
  self.design_system = self.extract_design_system(prompt)
325
-
326
- # Extract pages from prompt
327
- pages = self.extract_pages_from_prompt(prompt)
328
-
329
- # Generate HTML for each page
330
- generated_pages = []
331
- for i, page_desc in enumerate(pages[:3], 1): # Limit to 3 pages
332
- html = self.generate_page_html(page_desc, i)
333
- generated_pages.append(html)
334
-
335
- # Ensure we have exactly 3 pages
336
- while len(generated_pages) < 3:
337
- generated_pages.append(self.create_error_page("Page not specified"))
338
-
339
  return generated_pages, self.design_system
340
 
341
- def create_ui_design(prompt: str, api_key: str):
342
- """Main function to create UI designs"""
343
 
344
- if not api_key or api_key.strip() == "":
345
- error_html = """
346
- <div style='padding: 20px; color: #ef4444; background: #fee2e2; border-radius: 8px;'>
347
- <h3>API Key Required</h3>
348
- <p>Please provide your OpenRouter API key to generate designs.</p>
349
- <p>Get your key from <a href='https://openrouter.ai' target='_blank'>OpenRouter.ai</a></p>
350
- </div>
351
- """
352
- return error_html, error_html, error_html, "{}"
353
-
354
- if not prompt or prompt.strip() == "":
355
- error_html = """
356
- <div style='padding: 20px; color: #f59e0b; background: #fef3c7; border-radius: 8px;'>
357
- <h3>Prompt Required</h3>
358
- <p>Please describe the website you want to create.</p>
359
- </div>
360
- """
361
- return error_html, error_html, error_html, "{}"
362
 
363
- try:
364
- agent = UIDesignAgent(api_key)
365
- pages, design_system = agent.generate_multi_page_design(prompt)
366
-
367
- # Format design system for display
368
- design_system_display = json.dumps(design_system, indent=2)
369
-
370
- return pages[0], pages[1], pages[2], design_system_display
371
-
372
- except Exception as e:
373
- error_html = f"""
374
- <div style='padding: 20px; color: #ef4444; background: #fee2e2; border-radius: 8px;'>
375
- <h3>Generation Error</h3>
376
- <p>{str(e)}</p>
377
- </div>
378
- """
379
- return error_html, error_html, error_html, "{}"
380
 
381
- # Create Gradio interface
382
  def create_interface():
383
- with gr.Blocks(title="AI UI Designer - Consistent Multi-Page", theme=gr.themes.Soft()) as demo:
384
- gr.Markdown("""
385
- # 🎨 AI UI Designer - Consistent Multi-Page Generator
386
- ### Generate multiple website pages with consistent design system
387
-
388
- This tool ensures **consistent colors and styling** across all generated pages.
389
- """)
390
-
391
  with gr.Row():
392
  with gr.Column(scale=1):
393
- api_key_input = gr.Textbox(
394
- label="OpenRouter API Key",
395
- placeholder="sk-or-...",
396
- type="password",
397
- info="Get your key from openrouter.ai"
398
- )
399
-
400
- prompt_input = gr.Textbox(
401
- label="Website Description",
402
- placeholder="Example: Create a tech startup website with: 1) Landing page with hero section and features, 2) About page with team section, 3) Contact page with form",
403
- lines=4
404
- )
405
-
406
- with gr.Row():
407
- generate_btn = gr.Button("πŸš€ Generate Design", variant="primary", scale=2)
408
- clear_btn = gr.Button("πŸ—‘οΈ Clear", scale=1)
409
 
410
- gr.Markdown("### πŸ“‹ Design System")
411
- design_system_output = gr.Code(
412
- language="json",
413
- lines=10
414
- )
415
-
 
416
  with gr.Column(scale=2):
417
  with gr.Tabs():
418
- with gr.TabItem("πŸ“„ Page 1"):
419
- page1_output = gr.HTML()
420
-
421
- with gr.TabItem("πŸ“„ Page 2"):
422
- page2_output = gr.HTML()
423
-
424
- with gr.TabItem("πŸ“„ Page 3"):
425
- page3_output = gr.HTML()
426
-
427
- gr.Markdown("""
428
- ---
429
- ### πŸ’‘ Tips for Best Results:
430
- - Clearly describe each page you want
431
- - Specify key components (hero, navbar, footer, etc.)
432
- - Mention any specific features you need
433
-
434
- ### 🎯 Features:
435
- - βœ… Consistent color scheme across all pages
436
- - βœ… Unified typography and spacing
437
- - βœ… Modern, responsive design
438
- - βœ… Professional UI components
439
- """)
440
-
441
- # Set up event handlers
442
- generate_btn.click(
443
- fn=create_ui_design,
444
- inputs=[prompt_input, api_key_input],
445
- outputs=[page1_output, page2_output, page3_output, design_system_output]
446
- )
447
-
448
- clear_btn.click(
449
- fn=lambda: ("", "", "", "", "{}"),
450
- outputs=[prompt_input, page1_output, page2_output, page3_output, design_system_output]
451
- )
452
-
453
- # Add examples - FIXED STRING LITERALS
454
- example_prompts = [
455
- "Create a SaaS website: 1) Landing page with hero and pricing cards, 2) Features page with grid layout, 3) Contact page with form",
456
- "Design a portfolio: 1) Homepage with projects showcase, 2) About page with skills, 3) Contact page",
457
- "Build an e-commerce site: 1) Homepage with products, 2) Product detail page, 3) Cart page"
458
- ]
459
 
460
  gr.Examples(
461
- examples=example_prompts,
462
  inputs=prompt_input
463
  )
464
-
 
 
 
 
 
 
465
  return demo
466
 
467
  if __name__ == "__main__":
468
  demo = create_interface()
469
- demo.launch(share=True) # Add share=True for public link
 
5
  from typing import Dict, List, Tuple
6
  import os
7
 
8
+ # Let's use a model that's good at this task and very cheap/fast
9
+ SELECTED_MODEL = "minimax/minimax-m2:free"
10
+
11
+ # <<< NEW FUNCTION TO CREATE THE VISUAL STYLE GUIDE >>>
12
+ def visualize_design_system(design_system: Dict) -> str:
13
+ """
14
+ Takes a design system dictionary and returns an HTML string
15
+ that visually represents it as a style guide.
16
+ """
17
+ if not design_system or not isinstance(design_system, dict):
18
+ return "<div class='error-box'>No valid design system to display.</div>"
19
+
20
+ # Safely get data with defaults
21
+ colors = design_system.get('colors', {})
22
+ typography = design_system.get('typography', {})
23
+ spacing = design_system.get('spacing', {})
24
+ border_radius = design_system.get('borderRadius', {})
25
+
26
+ # ---- HTML & CSS for the visualizer itself ----
27
+ html = """
28
+ <!DOCTYPE html>
29
+ <html lang="en">
30
+ <head>
31
+ <meta charset="UTF-8">
32
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
+ <title>Design System</title>
34
+ <style>
35
+ body {
36
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
37
+ background-color: #f9fafb;
38
+ color: #374151;
39
+ padding: 1.5rem;
40
+ line-height: 1.6;
41
+ }
42
+ .section { margin-bottom: 2.5rem; }
43
+ h2 {
44
+ font-size: 1.5rem; font-weight: 600; color: #111827;
45
+ border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; margin-bottom: 1.5rem;
46
+ }
47
+ .swatch-container { display: flex; flex-wrap: wrap; gap: 1.5rem; }
48
+ .swatch {
49
+ width: 120px; height: 120px; border-radius: 0.75rem;
50
+ display: flex; flex-direction: column; justify-content: flex-end;
51
+ padding: 0.75rem; font-size: 0.875rem;
52
+ box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1);
53
+ }
54
+ .swatch .name { font-weight: 600; }
55
+ .swatch .hex { opacity: 0.8; }
56
+
57
+ .type-container .sample { padding: 1rem; background-color: #fff; border: 1px solid #e5e7eb; border-radius: 0.5rem; margin-bottom: 1rem; }
58
+ .type-container .label { font-size: 0.9rem; color: #6b7280; margin-bottom: 0.25rem; }
59
+
60
+ .component-container { display: flex; flex-direction: column; gap: 2rem; background-color: #fff; padding: 2rem; border-radius: 0.75rem; border: 1px solid #e5e7eb;}
61
+ .error-box { padding: 1rem; background: #fee2e2; color: #b91c1c; border-radius: 0.5rem; }
62
+ </style>
63
+ </head>
64
+ <body>
65
+ """
66
+
67
+ # ---- 1. Color Palette Section ----
68
+ html += "<div class='section'><h2>Color Palette</h2><div class='swatch-container'>"
69
+ for name, hex_val in colors.items():
70
+ # Determine if text should be light or dark based on background
71
+ try:
72
+ r, g, b = int(hex_val[1:3], 16), int(hex_val[3:5], 16), int(hex_val[5:7], 16)
73
+ luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
74
+ text_color = '#ffffff' if luminance < 0.5 else '#000000'
75
+ except:
76
+ text_color = '#000000'
77
+
78
+ html += f"""
79
+ <div class="swatch" style="background-color: {hex_val}; color: {text_color};">
80
+ <span class="name">{name.capitalize()}</span>
81
+ <span class="hex">{hex_val}</span>
82
+ </div>
83
+ """
84
+ html += "</div></div>"
85
+
86
+ # ---- 2. Typography Section ----
87
+ font_family = typography.get('fontFamily', 'sans-serif')
88
+ html += f"<div class='section type-container'><h2>Typography</h2><p style='margin-bottom: 1.5rem;'>Font Family: <code>{font_family}</code></p>"
89
+ type_samples = {'h1': 'Heading 1', 'h2': 'Heading 2', 'body': 'This is a paragraph of body text. It is the default font size for long-form content.', 'small': 'Small utility text'}
90
+ for name, sample_text in type_samples.items():
91
+ font_size = typography.get(name, '1rem')
92
+ html += f"""
93
+ <div class="sample">
94
+ <div class="label">{name.capitalize()} ({font_size})</div>
95
+ <div style="font-family: {font_family}; font-size: {font_size};">
96
+ {sample_text}
97
+ </div>
98
+ </div>
99
+ """
100
+ html += "</div>"
101
+
102
+ # ---- 3. Components Section ----
103
+ primary_color = colors.get('primary', '#3b82f6')
104
+ surface_color = colors.get('surface', '#f9fafb')
105
+ text_color = colors.get('text', '#111827')
106
+ border_color = colors.get('border', '#e5e7eb')
107
+ radius = border_radius.get('lg', '0.75rem')
108
+
109
+ html += f"""
110
+ <div class='section'>
111
+ <h2>Components Preview</h2>
112
+ <div class="component-container" style="background-color: {surface_color};">
113
+ <div>
114
+ <h3 style="font-family:{font_family}; font-size:{typography.get('h3', '1.25rem')}; color:{text_color}; margin-bottom: 1rem;">Example Card</h3>
115
+ <div style="background-color: #fff; border: 1px solid {border_color}; border-radius: {radius}; padding: 1.5rem; box-shadow: 0 1px 3px 0 rgba(0,0,0,0.1);">
116
+ <p style="font-family:{font_family}; font-size:{typography.get('body', '1rem')}; color:{text_color}; margin-bottom: 1.5rem;">This is a sample card using the design system's surface, border, and radius values.</p>
117
+ <button style="font-family:{font_family}; background-color: {primary_color}; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: {border_radius.get('md', '0.5rem')}; cursor: pointer; font-size: 1rem;">Primary Button</button>
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ """
123
+
124
+ html += "</body></html>"
125
+ return html
126
+
127
+
128
  class UIDesignAgent:
129
+ # ... (The class code remains mostly the same, just ensuring it returns a proper dictionary) ...
130
  def __init__(self, api_key: str):
131
  self.api_key = api_key
132
  self.base_url = "https://openrouter.ai/api/v1/chat/completions"
133
+ self.model = SELECTED_MODEL
134
  self.design_system = {}
135
+
136
+ def get_default_design_system(self) -> Dict:
137
+ return {
138
+ "colors": { "primary": "#2563eb", "secondary": "#7c3aed", "accent": "#06b6d4", "background": "#ffffff", "surface": "#f8fafc", "text": "#1e293b", "textLight": "#64748b", "border": "#e2e8f0" },
139
+ "typography": { "fontFamily": "'Inter', system-ui, sans-serif", "h1": "2.5rem", "h2": "2rem", "h3": "1.5rem", "body": "1rem", "small": "0.875rem" },
140
+ "spacing": { "md": "1rem", "lg": "1.5rem", "xl": "2rem" },
141
+ "borderRadius": { "sm": "0.25rem", "md": "0.5rem", "lg": "0.75rem" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  }
143
+
144
+ def extract_design_system(self, prompt: str) -> Dict:
145
+ system_prompt = """You are an expert UI/UX designer. Create a consistent design system based on the user's prompt.
146
+ The design system must include: 'colors' (primary, secondary, background, surface, text, border),
147
+ 'typography' (fontFamily, h1, h2, body, small), and 'borderRadius' (sm, md, lg).
148
+ Return ONLY a single, valid JSON object and nothing else."""
149
+ headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" }
150
+ data = { "model": self.model, "messages": [{"role": "system", "content": system_prompt}, {"role": "user", "content": f"Create a design system for: {prompt}"}], "response_format": {"type": "json_object"}}
151
  try:
152
+ response = requests.post(self.base_url, headers=headers, json=data, timeout=30)
153
+ result = response.json()
154
+ content = result['choices'][0]['message']['content']
155
+ self.design_system = json.loads(content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  return self.design_system
157
  except Exception as e:
158
+ print(f"Error extracting design system: {e}. Using default.")
159
  return self.get_default_design_system()
160
+
161
+ def generate_page_html(self, page_description: str) -> str:
162
+ # Use the stored design system
163
+ design_system_str = json.dumps(self.design_system)
164
+ system_prompt = f"""You are an expert UI developer. Create a complete, single HTML file.
165
+ Use this exact JSON design system for all styling: {design_system_str}
166
+ RULES:
167
+ 1. Create a modern, responsive page.
168
+ 2. Embed all CSS in a `<style>` tag in the `<head>`.
169
+ 3. Your entire response must be ONLY raw HTML code, starting with `<!DOCTYPE html>`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  """
171
+ headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" }
172
+ data = { "model": self.model, "messages": [{"role": "system", "content": system_prompt}, {"role": "user", "content": f"Design page: {page_description}."}], "max_tokens": 3500 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  try:
174
+ response = requests.post(self.base_url, headers=headers, json=data, timeout=60)
175
+ return response.json()['choices'][0]['message']['content']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  except Exception as e:
177
+ return f"<html><body>Error generating page: {e}</body></html>"
178
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  def generate_multi_page_design(self, prompt: str) -> Tuple[List[str], Dict]:
 
 
 
180
  self.design_system = self.extract_design_system(prompt)
181
+ pages_desc = re.findall(r'\d+\)([^0-9]+?)(?=\d+\)|$)', prompt) or [prompt]
182
+ while len(pages_desc) < 3: pages_desc.append(f"Page {len(pages_desc)+1} placeholder")
183
+ generated_pages = [self.generate_page_html(desc) for desc in pages_desc[:3]]
 
 
 
 
 
 
 
 
 
 
 
184
  return generated_pages, self.design_system
185
 
186
+ def create_ui_design(prompt: str, api_key: str, progress=gr.Progress(track_tqdm=True)):
187
+ if not api_key: return ("Missing API Key",) * 5
188
 
189
+ agent = UIDesignAgent(api_key)
190
+ progress(0.2, desc="Generating Design System...")
191
+ pages, design_system = agent.generate_multi_page_design(prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
+ progress(0.8, desc="Creating Visualizer...")
194
+ # <<< CALL THE NEW VISUALIZER FUNCTION >>>
195
+ visualizer_html = visualize_design_system(design_system)
196
+ design_system_json_string = json.dumps(design_system, indent=2)
197
+
198
+ progress(1.0, desc="Done!")
199
+ return pages[0], pages[1], pages[2], design_system_json_string, visualizer_html
 
 
 
 
 
 
 
 
 
 
200
 
 
201
  def create_interface():
202
+ with gr.Blocks(title="AI UI Designer", theme=gr.themes.Soft()) as demo:
203
+ gr.Markdown("# 🎨 AI UI Designer - Consistent Multi-Page Generator")
 
 
 
 
 
 
204
  with gr.Row():
205
  with gr.Column(scale=1):
206
+ api_key_input = gr.Textbox(label="OpenRouter API Key", placeholder="sk-or-...", type="password")
207
+ prompt_input = gr.Textbox(label="Website Description", placeholder="e.g., A 3-page site for a coffee shop: 1) landing page, 2) menu, 3) contact us", lines=4)
208
+ generate_btn = gr.Button("πŸš€ Generate Design", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
+ # <<< UPDATED UI WITH TABS FOR JSON and VISUALIZER >>>
211
+ with gr.Tabs():
212
+ with gr.TabItem("Visualizer"):
213
+ design_system_visualizer = gr.HTML(label="Design System Visualizer")
214
+ with gr.TabItem("JSON"):
215
+ design_system_output = gr.Code(label="Design System JSON", language="json", lines=15)
216
+
217
  with gr.Column(scale=2):
218
  with gr.Tabs():
219
+ with gr.TabItem("πŸ“„ Page 1"): page1_output = gr.HTML()
220
+ with gr.TabItem("πŸ“„ Page 2"): page2_output = gr.HTML()
221
+ with gr.TabItem("πŸ“„ Page 3"): page3_output = gr.HTML()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
223
  gr.Examples(
224
+ examples=["A dark-mode site for a DJ: 1) homepage with event list, 2) music gallery, 3) booking form", "A bright, clean site for a bakery: 1) landing page with featured items, 2) full menu page, 3) online order form"],
225
  inputs=prompt_input
226
  )
227
+
228
+ # <<< UPDATE THE OUTPUTS TO INCLUDE THE NEW VISUALIZER COMPONENT >>>
229
+ generate_btn.click(
230
+ fn=create_ui_design,
231
+ inputs=[prompt_input, api_key_input],
232
+ outputs=[page1_output, page2_output, page3_output, design_system_output, design_system_visualizer]
233
+ )
234
  return demo
235
 
236
  if __name__ == "__main__":
237
  demo = create_interface()
238
+ demo.launch()