admin08077 commited on
Commit
92b356a
·
verified ·
1 Parent(s): 60a5248

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +31 -635
app.py CHANGED
@@ -1,661 +1,57 @@
1
-
2
  import gradio as gr
3
- import os
4
- # Updated import to use the new 'google.genai' SDK
5
  from google import genai
6
  from google.genai import types
7
- import json
8
  from PIL import Image
9
  from io import BytesIO
10
- import base64
11
- import uuid
12
- import concurrent.futures
13
- import re
14
-
15
- # --- Configuration and Initialization ---
16
 
17
- # Get the API key from environment variables
18
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
19
  if not GEMINI_API_KEY:
20
- raise ValueError("GEMINI_API_KEY not found in environment variables. Please set it in your Hugging Face Space secrets.")
21
 
22
  # Initialize Gemini client
23
  client = genai.Client(api_key=GEMINI_API_KEY)
24
 
25
-
26
- # --- Default Data Structures (Ported from TypeScript) ---
27
-
28
- DEFAULT_WEBSITE_SETTINGS = {
29
- "theme": {
30
- "appBg": "#111827", "panelBg": "#1f2937", "headerBg": "#1f2937",
31
- "footerBg": "#1f2937", "primaryTextColor": "#f9fafb", "secondaryTextColor": "#d1d5db",
32
- "primaryAccent": "#4f46e5", "secondaryAccent": "#10b981", "fontFamily": "font-sans",
33
- },
34
- "header": { "title": "My Awesome Site", "logoUrl": "", "navLinks": [] },
35
- "footer": {
36
- "text": "© 2024 My Awesome Site. All rights reserved.",
37
- "links": [
38
- {"text": "Privacy Policy", "href": "#"},
39
- {"text": "Terms of Service", "href": "#"},
40
- ],
41
- },
42
- "pages": [{
43
- "path": "index.html", "name": "Home",
44
- "contentBlocks": [{
45
- "id": str(uuid.uuid4()), "type": "hero", "headline": "Welcome to Your AI-Generated Website",
46
- "subheadline": "Describe your vision and watch it come to life.",
47
- "bgImage": "", "headlineColor": "#ffffff", "subheadlineColor": "#d1d5db",
48
- }],
49
- }],
50
- }
51
-
52
- # --- Core AI and Helper Functions ---
53
-
54
- def create_prompt_from_answers(answers):
55
- # This prompt is a direct port from the React app, instructing the AI on how to generate the website layout.
56
- return f"""
57
- You are a world-class creative director, UI/UX designer, and content strategist tasked with building a complete multi-page website.
58
- The user has provided the following details through a questionnaire. Your task is to interpret their answers and generate a cohesive and complete website design.
59
-
60
- **User's Website Requirements:**
61
- - **Website Name:** {answers.get('name', 'AI Generated Website')}
62
- - **Core Purpose:** {answers.get('purpose', 'Not specified')}
63
- - **Target Audience:** {answers.get('audience', 'General audience')}
64
- - **Desired Style/Vibe:** {answers.get('style', 'A modern, clean design')}
65
- - **Color Palette:** {answers.get('colors', 'A balanced color scheme based on the style')}
66
- - **Theme Preference:** {answers.get('theme', 'dark')}
67
- - **Required Pages:** {answers.get('pages', 'Home, About, Contact')}
68
- - **Home Page Headline/Message:** {answers.get('homeMessage', f"Welcome to {answers.get('name', 'Our Website')}")}
69
- - **Key Features/Services:** {answers.get('features', 'Feature 1, Feature 2, Feature 3')}
70
- - **About Us Content:** {answers.get('about', 'A brief description of the company.')}
71
- - **Contact Info:** {answers.get('contact', 'A contact form.')}
72
- - **Brand Tone of Voice:** {answers.get('tone', 'Professional and friendly')}
73
- - **Tagline:** {answers.get('tagline', '')}
74
- - **Additional Instructions:** {answers.get('extra', 'None')}
75
-
76
- **Your Mission:**
77
- Based *only* on the requirements above, generate a single, comprehensive JSON object for this website. Adhere strictly to the provided JSON schema.
78
-
79
- **Mandatory Requirements:**
80
- 1. **Page Creation:** Create pages based on the user's request ({answers.get('pages')}). If not specified, create a logical set of pages (e.g., Home, About, Services, Contact).
81
- 2. **Cohesive Branding:** The theme (colors, fonts) and all generated content (text, image prompts) must be consistent with the user's described style ({answers.get('style')}, {answers.get('colors')}, {answers.get('theme')}).
82
- 3. **Content Generation:** Write compelling copy for all text blocks, headlines, and features, adopting the user's desired tone ({answers.get('tone')}). The content should directly relate to the website's purpose ({answers.get('purpose')}) and features ({answers.get('features')}).
83
- 4. **Creative Asset Prompts:** Generate 5 distinct, creative prompts for logos that match the brand identity. Also, create a unique, descriptive image prompt for every single image required in the content blocks.
84
- 5. **Navigation:** Populate the 'navLinks' array in the header settings. The links must correspond to the pages you create.
85
- 6. **Data Integrity:** All paths must be unique and end in '.html'. All block IDs must be unique. Use valid hex color codes with good accessibility contrast. For forms, include relevant fields.
86
- """
87
-
88
- def generate_image(prompt: str, aspect_ratio: str):
89
- """Generates an image using Gemini and returns a base64 data URL."""
90
- print(f"Generating image for prompt: {prompt}")
91
  try:
92
- # Refactored to use the new client.models.generate_content method
93
- full_prompt = f'Generate a single, high-quality image. Description: "{prompt}". The image should have a {aspect_ratio} aspect ratio.'
94
-
95
  response = client.models.generate_content(
96
  model="gemini-2.0-flash-preview-image-generation",
97
- contents=full_prompt,
98
- config=types.GenerateContentConfig(response_modalities=['TEXT', 'IMAGE'])
99
- )
100
-
101
- image_part = next((part for part in response.candidates[0].content.parts if part.inline_data), None)
102
-
103
- if image_part:
104
- mime_type = image_part.inline_data.mime_type
105
- data = base64.b64encode(image_part.inline_data.data).decode("utf-8")
106
- return f"data:{mime_type};base64,{data}"
107
- else:
108
- print(f"Warning: Image generation failed for prompt: {prompt}. No image data in response.")
109
- return ""
110
- except Exception as e:
111
- print(f"Error generating image for prompt '{prompt}': {e}")
112
- return ""
113
-
114
- def generate_page_html(page, settings):
115
- """Generates the HTML for a single page based on the website settings."""
116
- if not page or not settings:
117
- return "<p>Error: Page or settings not found.</p>"
118
-
119
- theme = settings.get("theme", {})
120
- header = settings.get("header", {})
121
- footer = settings.get("footer", {})
122
- nav_links = header.get("navLinks", [])
123
-
124
- # Header HTML
125
- header_html = f"""
126
- <header style="background-color: {theme.get('headerBg')}; color: {theme.get('primaryTextColor')}; padding: 1rem 2rem;">
127
- <div class="container mx-auto flex justify-between items-center">
128
- <div class="flex items-center space-x-4">
129
- {'<img src="' + header.get('logoUrl', '') + '" alt="Logo" class="h-10 w-10 object-contain bg-white rounded-full p-1">' if header.get('logoUrl') else ''}
130
- <a href="index.html" class="text-2xl font-bold">{header.get('title', '')}</a>
131
- </div>
132
- <div class="hidden md:flex items-center space-x-6">
133
- {''.join([f'<a href="{link.get("href")}" style="color: {theme.get("secondaryTextColor")};" class="hover:text-white transition-colors {"font-bold !text-white" if page.get("path") == link.get("href") else ""}">{link.get("text")}</a>' for link in nav_links])}
134
- </div>
135
- </div>
136
- </header>
137
- """
138
-
139
- # Content Blocks HTML
140
- main_content_html = ""
141
- for block in page.get("contentBlocks", []):
142
- block_type = block.get("type")
143
- if block_type == 'hero':
144
- main_content_html += f"""
145
- <section class="text-center flex flex-col items-center justify-center min-h-[50vh] p-8" style="background-image: linear-gradient(rgba(0,0,0,0.4), rgba(0,0,0,0.4)), url({block.get('bgImage', '')}); background-size: cover; background-position: center;">
146
- <h2 class="text-4xl md:text-6xl font-bold" style="color: {block.get('headlineColor', '#ffffff')};">{block.get('headline', '')}</h2>
147
- <p class="mt-4 text-lg md:text-xl max-w-2xl" style="color: {block.get('subheadlineColor', '#d1d5db')};">{block.get('subheadline', '')}</p>
148
- </section>"""
149
- elif block_type == 'textWithImage':
150
- text_html = (block.get('text', '') or '').replace('\n', '<br/>')
151
- main_content_html += f"""
152
- <section class="container mx-auto py-12 md:py-20 px-6">
153
- <div class="grid grid-cols-1 md:grid-cols-2 gap-12 items-center">
154
- <div class="{'md:order-1' if block.get('imagePosition') == 'right' else 'md:order-2'}">
155
- <h3 class="text-3xl font-bold mb-4" style="color: {theme.get('primaryTextColor')};">{block.get('headline', '')}</h3>
156
- <p style="color: {theme.get('secondaryTextColor')};">{text_html}</p>
157
- </div>
158
- <div class="{'md:order-2' if block.get('imagePosition') == 'right' else 'md:order-1'}">
159
- <img src="{block.get('image', '')}" alt="{block.get('headline', 'content image')}" class="rounded-lg shadow-xl w-full h-auto object-cover aspect-square">
160
- </div>
161
- </div>
162
- </section>"""
163
- elif block_type == 'featureList':
164
- features_html = ''.join([f"""
165
- <div class="p-6 rounded-lg" style="background-color: {theme.get('panelBg')};">
166
- <h4 class="text-xl font-bold mb-2" style="color: {theme.get('primaryAccent')};">{f.get('title', '')}</h4>
167
- <p style="color: {theme.get('secondaryTextColor')};">{f.get('description', '')}</p>
168
- </div>""" for f in block.get('features', [])])
169
- main_content_html += f"""
170
- <section class="container mx-auto py-12 md:py-20 px-6">
171
- <h2 class="text-4xl font-bold text-center mb-12" style="color: {theme.get('primaryTextColor')};">{block.get('headline', '')}</h2>
172
- <div class="grid grid-cols-1 md:grid-cols-3 gap-8">{features_html}</div>
173
- </section>"""
174
- elif block_type == 'text':
175
- paragraphs_html = ''.join([f'<p>{p}</p>' for p in block.get('paragraphs', [])])
176
- main_content_html += f"""
177
- <section class="container mx-auto py-12 md:py-20 px-6 max-w-4xl">
178
- <h2 class="text-4xl font-bold text-center mb-12" style="color: {theme.get('primaryTextColor')};">{block.get('headline', '')}</h2>
179
- <div class="prose prose-invert lg:prose-xl mx-auto" style="color: {theme.get('secondaryTextColor')};">{paragraphs_html}</div>
180
- </section>
181
- """
182
- elif block_type == 'form':
183
- fields_html = ''.join([f"""
184
- <div>
185
- <label for="{f.get('name')}" class="block text-sm font-medium mb-1" style="color: {theme.get('secondaryTextColor')};">{f.get('label', '')}</label>
186
- <input type="{f.get('type')}" name="{f.get('name')}" id="{f.get('name')}" class="w-full rounded-md p-2 text-sm" style="background-color: {theme.get('appBg')}; color: {theme.get('primaryTextColor')}; border: 1px solid {theme.get('panelBg')};" />
187
- </div>""" for f in block.get('fields', [])])
188
- main_content_html += f"""
189
- <section class="container mx-auto py-12 md:py-20 px-6 flex justify-center">
190
- <div class="w-full max-w-md p-8 rounded-lg" style="background-color: {theme.get('panelBg')};">
191
- <h2 class="text-3xl font-bold text-center mb-8" style="color: {theme.get('primaryTextColor')};">{block.get('headline', '')}</h2>
192
- <form class="space-y-6" onsubmit="event.preventDefault(); alert('Form submitted! (This is a preview)');">
193
- {fields_html}
194
- <div><button type="submit" class="w-full flex items-center justify-center p-2 text-white font-semibold rounded-md transition-colors" style="background-color: {theme.get('secondaryAccent')};">{block.get('submitButtonText', 'Submit')}</button></div>
195
- </form>
196
- </div>
197
- </section>"""
198
-
199
- # Footer HTML
200
- footer_html = f"""
201
- <footer style="background-color: {theme.get('footerBg')}; color: {theme.get('secondaryTextColor')};" class="py-6 px-4 mt-auto">
202
- <div class="container mx-auto text-center">
203
- <p>{footer.get('text', '')}</p>
204
- <div class="mt-2 flex justify-center space-x-4">
205
- {''.join([f'<a href="{link.get("href")}" class="hover:text-white transition-colors">{link.get("text")}</a>' for link in footer.get("links", [])])}
206
- </div>
207
- </div>
208
- </footer>
209
- """
210
-
211
- # Final Page Assembly
212
- return f"""
213
- <!DOCTYPE html>
214
- <html lang="en" class="{theme.get('fontFamily', 'font-sans')}">
215
- <head>
216
- <meta charset="UTF-8">
217
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
218
- <title>{header.get('title', '')} - {page.get('name', '')}</title>
219
- <script src="https://cdn.tailwindcss.com"></script>
220
- <script>
221
- tailwind.config = {{
222
- theme: {{
223
- extend: {{
224
- typography: ({{ theme }}) => ({{
225
- invert: {{
226
- css: {{
227
- '--tw-prose-body': theme('colors.gray[300]'),
228
- '--tw-prose-headings': theme('colors.white'),
229
- '--tw-prose-links': theme('colors.white'),
230
- '--tw-prose-bold': theme('colors.white'),
231
- }},
232
- }},
233
- }}),
234
- }},
235
- }}
236
- }}
237
- </script>
238
- </head>
239
- <body style="background-color: {theme.get('appBg')};" class="flex flex-col min-h-screen">
240
- {header_html}
241
- <main>{main_content_html}</main>
242
- {footer_html}
243
- <script>
244
- // Prevent iframe navigation from redirecting the whole page
245
- document.addEventListener('click', function (e) {{
246
- let target = e.target;
247
- while (target && target.tagName !== 'A') {{
248
- target = target.parentElement;
249
- }}
250
- if (target && target.tagName === 'A') {{
251
- const href = target.getAttribute('href');
252
- if (href && !href.startsWith('#') && !href.startsWith('http')) {{
253
- e.preventDefault();
254
- // In a real scenario, you'd postMessage to the parent,
255
- // but Gradio's buttons will handle navigation instead.
256
- alert(`Preview navigation to: ${href}`);
257
- }}
258
- }}
259
- }});
260
- </script>
261
- </body>
262
- </html>
263
- """.strip()
264
-
265
-
266
- # --- Gradio UI Definition ---
267
-
268
- def create_gradio_app():
269
-
270
- # --- UI Helper Functions ---
271
- def get_page_by_path(path, settings):
272
- return next((p for p in settings.get("pages", []) if p.get("path") == path), None)
273
-
274
- def get_block_by_id(page, block_id):
275
- if not page: return None
276
- return next((b for b in page.get("contentBlocks", []) if b.get("id") == block_id), None)
277
-
278
- # --- Event Handlers ---
279
-
280
- def handle_start():
281
- return {
282
- welcome_view: gr.update(visible=False),
283
- questionnaire_view: gr.update(visible=True),
284
- }
285
-
286
- def handle_generate_layout(*questionnaire_answers):
287
- yield {
288
- questionnaire_view: gr.update(visible=False),
289
- generating_view: gr.update(visible=True),
290
- status_text: "Generating website structure..."
291
- }
292
-
293
- # Map flat answer list to dictionary
294
- question_ids = [
295
- 'name', 'purpose', 'audience', 'style', 'colors', 'theme', 'pages',
296
- 'homeMessage', 'features', 'about', 'tone', 'tagline', 'extra'
297
- ]
298
- answers = dict(zip(question_ids, questionnaire_answers))
299
-
300
- # --- Step 1: Generate Layout ---
301
- try:
302
- # Refactored to use the new client.models.generate_content method
303
- prompt = create_prompt_from_answers(answers)
304
- schema = {
305
- "type": "object",
306
- "properties": {
307
- "websiteSettings": {
308
- "type": "object",
309
- "properties": {
310
- "theme": {"type": "object", "properties": {"appBg": {"type": "string"},"panelBg": {"type": "string"},"headerBg": {"type": "string"},"footerBg": {"type": "string"},"primaryTextColor": {"type": "string"},"secondaryTextColor": {"type": "string"},"primaryAccent": {"type": "string"},"secondaryAccent": {"type": "string"},"fontFamily": {"type": "string"}}},
311
- "header": {"type": "object", "properties": {"title": {"type": "string"},"navLinks": {"type": "array","items": {"type": "object","properties": {"text": {"type": "string"},"href": {"type": "string"}}}}}},
312
- "footer": {"type": "object", "properties": {"text": {"type": "string"},"links": {"type": "array","items": {"type": "object","properties": {"text": {"type": "string"},"href": {"type": "string"}}}}}},
313
- "pages": {"type": "array","items": {"type": "object","properties": {"path": {"type": "string"},"name": {"type": "string"},"contentBlocks": {"type": "array","items": {"type": "object", "properties": { "id": {"type": "string"}, "type": {"type": "string"}, "headline": {"type": "string"}, "subheadline": {"type": "string"}, "headlineColor": {"type": "string"}, "subheadlineColor": {"type": "string"}, "text": {"type": "string"}, "imagePosition": {"type": "string"}, "features": {"type": "array", "items": {"type": "object", "properties": {"title": {"type": "string"}, "description": {"type": "string"}}}},"fields": {"type": "array","items": {"type": "object","properties": {"label": {"type": "string"},"type": {"type": "string"},"name": {"type": "string"}}}},"submitButtonText": {"type": "string"}, "paragraphs": {"type": "array", "items": {"type": "string"}}}}}}}},
314
- },
315
- },
316
- "imagePrompts": {
317
- "type": "object",
318
- "properties": {
319
- "logoPrompts": {"type": "array", "items": {"type": "string"}},
320
- "blockImagePrompts": {"type": "array", "items": {"type": "object", "properties": {"pagePath": {"type": "string"},"blockId": {"type": "string"},"prompt": {"type": "string"}}}}
321
- }
322
- }
323
- }
324
- }
325
-
326
- response = client.models.generate_content(
327
- model='gemini-2.5-flash',
328
- contents=prompt,
329
- config=types.GenerateContentConfig(
330
- response_mime_type="application/json",
331
- response_schema=schema,
332
- safety_settings={
333
- types.HarmCategory.HARM_CATEGORY_HARASSMENT: types.HarmBlockThreshold.BLOCK_NONE,
334
- types.HarmCategory.HARM_CATEGORY_HATE_SPEECH: types.HarmBlockThreshold.BLOCK_NONE,
335
- types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: types.HarmBlockThreshold.BLOCK_NONE,
336
- types.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: types.HarmBlockThreshold.BLOCK_NONE,
337
- }
338
- )
339
  )
340
- design_plan = json.loads(response.text)
341
- except Exception as e:
342
- print(f"Layout generation failed: {e}")
343
- yield {
344
- generating_view: gr.update(visible=False),
345
- questionnaire_view: gr.update(visible=True),
346
- error_box: gr.update(value=f"Error generating layout: {e}", visible=True)
347
- }
348
- return
349
-
350
- new_settings = design_plan['websiteSettings']
351
- image_prompts = design_plan['imagePrompts']
352
-
353
- # Add unique IDs and empty image fields to the settings
354
- original_id_map = {}
355
- for page in new_settings.get('pages', []):
356
- new_blocks = []
357
- for block in page.get('contentBlocks', []):
358
- new_id = str(uuid.uuid4())
359
- original_id = block.get('id')
360
- if original_id:
361
- original_id_map[original_id] = new_id
362
-
363
- block['id'] = new_id
364
- if block.get('type') == 'hero':
365
- block['bgImage'] = ''
366
- if block.get('type') == 'textWithImage':
367
- block['image'] = ''
368
- new_blocks.append(block)
369
- page['contentBlocks'] = new_blocks
370
- new_settings['header']['logoUrl'] = ''
371
-
372
- active_page_path = new_settings['pages'][0]['path'] if new_settings.get('pages') else ''
373
-
374
- # --- Step 2: Generate Images in Parallel ---
375
- tasks = []
376
- logo_prompts = image_prompts.get('logoPrompts', [])
377
- block_prompts = image_prompts.get('blockImagePrompts', [])
378
-
379
- if logo_prompts:
380
- yield {status_text: f"Generating {len(logo_prompts)} logos..."}
381
- for prompt in logo_prompts:
382
- tasks.append(("logo", prompt))
383
-
384
- if block_prompts:
385
- yield {status_text: f"Generating {len(block_prompts)} content images..."}
386
- for p_info in block_prompts:
387
- tasks.append(("block", p_info))
388
-
389
- with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
390
- future_to_task = {executor.submit(generate_image, task[1]['prompt'] if task[0]=='block' else task[1], '16:9' if task[0]=='block' and task[1].get('type')=='hero' else '1:1'): task for task in tasks}
391
-
392
- logo_results = []
393
- for i, future in enumerate(concurrent.futures.as_completed(future_to_task)):
394
- task_type, task_data = future_to_task[future]
395
- try:
396
- image_url = future.result()
397
- if not image_url: continue
398
-
399
- if task_type == "logo":
400
- logo_results.append(image_url)
401
-
402
- elif task_type == "block":
403
- original_block_id = task_data['blockId']
404
- new_block_id = original_id_map.get(original_block_id)
405
- if not new_block_id: continue
406
-
407
- page_path = task_data['pagePath']
408
- page_to_update = get_page_by_path(page_path, new_settings)
409
- if not page_to_update: continue
410
-
411
- block_to_update = get_block_by_id(page_to_update, new_block_id)
412
- if not block_to_update: continue
413
-
414
- key_to_update = 'bgImage' if block_to_update.get('type') == 'hero' else 'image'
415
- block_to_update[key_to_update] = image_url
416
-
417
- yield {status_text: f"Processing images... ({i+1}/{len(tasks)})"}
418
-
419
- except Exception as exc:
420
- print(f'{task_data} generated an exception: {exc}')
421
-
422
- yield {
423
- generating_view: gr.update(visible=False),
424
- logo_picker_view: gr.update(visible=True, value=logo_results if logo_results else None),
425
- website_settings_state: new_settings,
426
- active_page_path_state: active_page_path
427
- }
428
-
429
- def handle_logo_select(evt: gr.SelectData, settings, active_path):
430
- logo_url = evt.value
431
- settings['header']['logoUrl'] = logo_url
432
-
433
- page = get_page_by_path(active_path, settings)
434
- html = generate_page_html(page, settings)
435
-
436
- # Update controls with the new settings
437
- page_names = [p['name'] for p in settings['pages']]
438
- page_paths = [p['path'] for p in settings['pages']]
439
- active_page_name = get_page_by_path(active_path, settings)['name']
440
-
441
- return {
442
- logo_picker_view: gr.update(visible=False),
443
- preview_view: gr.update(visible=True),
444
- website_settings_state: settings,
445
- html_preview: html,
446
- code_preview: html,
447
- # Update controls
448
- page_selector: gr.update(choices=page_names, value=active_page_name),
449
- header_title_input: settings['header']['title'],
450
- footer_text_input: settings['footer']['text'],
451
- theme_app_bg_input: settings['theme']['appBg'],
452
- theme_panel_bg_input: settings['theme']['panelBg'],
453
- theme_header_bg_input: settings['theme']['headerBg'],
454
- theme_footer_bg_input: settings['theme']['footerBg'],
455
- theme_primary_text_input: settings['theme']['primaryTextColor'],
456
- theme_secondary_text_input: settings['theme']['secondaryTextColor'],
457
- theme_primary_accent_input: settings['theme']['primaryAccent'],
458
- theme_secondary_accent_input: settings['theme']['secondaryAccent'],
459
- theme_font_family_input: settings['theme']['fontFamily'],
460
- }
461
-
462
- def handle_page_change(page_name, settings):
463
- path = next((p['path'] for p in settings['pages'] if p['name'] == page_name), None)
464
- if not path: return {}
465
-
466
- page = get_page_by_path(path, settings)
467
- html = generate_page_html(page, settings)
468
-
469
- # Create dynamic controls for the selected page's content blocks
470
- content_controls = []
471
- for i, block in enumerate(page.get('contentBlocks', [])):
472
- with gr.Accordion(f"{block.get('type', 'Block').capitalize()} Block #{i+1}", open=True):
473
- if 'headline' in block:
474
- gr.Textbox(value=block.get('headline'), label="Headline", elem_id=f"{path}_{block['id']}_headline")
475
- if 'subheadline' in block:
476
- gr.Textbox(value=block.get('subheadline'), label="Subheadline", elem_id=f"{path}_{block['id']}_subheadline")
477
- if 'text' in block:
478
- gr.Textbox(value=block.get('text'), label="Text", lines=4, elem_id=f"{path}_{block['id']}_text")
479
- # Add more controls for other block types (features, paragraphs, etc.) if needed
480
-
481
- return {
482
- active_page_path_state: path,
483
- html_preview: html,
484
- code_preview: html,
485
- page_content_controls: gr.update(value=content_controls)
486
- }
487
-
488
- def handle_setting_change(settings, path, *values):
489
- # This is a simplified handler. It assumes the order of values matches the controls.
490
- # A more robust solution would use elem_id to map values to settings.
491
- settings['header']['title'] = values[0]
492
- settings['footer']['text'] = values[1]
493
- theme_keys = list(settings['theme'].keys())
494
- for i, key in enumerate(theme_keys):
495
- settings['theme'][key] = values[2 + i]
496
-
497
- page = get_page_by_path(path, settings)
498
- html = generate_page_html(page, settings)
499
- return settings, html, html
500
-
501
- def handle_content_change(request: gr.Request, settings, active_path, *content_values):
502
- # A very basic content handler based on elem_id.
503
- # Gradio's current API makes granular updates complex.
504
- # This is a placeholder for a more robust implementation.
505
-
506
- # Example of how one would update:
507
- # changed_elem_id = request.elem_id
508
- # new_value = content_values[??]
509
- # path, block_id, field = changed_elem_id.split('_')
510
- # page = get_page_by_path(path, settings)
511
- # block = get_block_by_id(page, block_id)
512
- # block[field] = new_value
513
-
514
- page = get_page_by_path(active_path, settings)
515
- html = generate_page_html(page, settings)
516
- return settings, html, html
517
-
518
- # --- Gradio Blocks ---
519
- with gr.Blocks(theme=gr.themes.Default(primary_hue="blue", secondary_hue="indigo"), css="""
520
- .gradio-container { background-color: #0d1117; }
521
- #logo-picker .thumbnail-item { height: 120px !important; width: 120px !important; }
522
- """) as demo:
523
- # --- State Management ---
524
- website_settings_state = gr.State(DEFAULT_WEBSITE_SETTINGS)
525
- active_page_path_state = gr.State("index.html")
526
-
527
- # --- View: Welcome ---
528
- with gr.Column(visible=True) as welcome_view:
529
- gr.Markdown("# 🚀 AI Website Architect\nWelcome! Instead of code, you'll answer a series of questions about your vision. Our AI will then act as your creative director, designer, and content strategist to generate a complete, multi-page website tailored to your needs—from brand identity and color schemes to page layouts and content.",)
530
- start_button = gr.Button("Start Building Your Website", variant="primary")
531
-
532
- # --- View: Questionnaire ---
533
- with gr.Column(visible=False) as questionnaire_view:
534
- gr.Markdown("# 📝 Build Your Website\nAnswer these questions to give the AI a creative brief for your project.")
535
- error_box = gr.Textbox(label="Error", visible=False, interactive=False)
536
-
537
- # Pre-filled answers for quick testing
538
- default_answers = {
539
- 'name': 'QuantumBank', 'purpose': 'A high-tech financial company exploring the intersection of quantum computing and finance.',
540
- 'audience': 'Investors, researchers, and tech enthusiasts interested in fintech.', 'style': 'Dark, futuristic, and professional with a high-tech feel.',
541
- 'colors': 'Deep blues, purples, with bright cyan accents.', 'theme': 'dark', 'pages': 'Home, About Us, Research, Philosophy, Contact',
542
- 'homeMessage': 'QuantumBank: The Future of Finance is Here.', 'features': '- Quantum-secured transactions\n- AI-powered investment analysis\n- Decentralized financial instruments',
543
- 'about': 'Founded by leading quantum physicists and financial experts, QuantumBank is pioneering the next generation of financial technology.',
544
- 'tone': 'Authoritative, innovative, and forward-thinking.', 'tagline': 'Banking at the speed of light.', 'extra': 'The design should feel sleek and sophisticated, almost like science fiction made real.'
545
- }
546
 
547
- with gr.Accordion("Core Identity", open=True):
548
- q_name = gr.Textbox(label="Website/Company Name", value=default_answers['name'])
549
- q_purpose = gr.Textbox(label="Primary Purpose", lines=3, value=default_answers['purpose'])
550
- q_audience = gr.Textbox(label="Target Audience", lines=3, value=default_answers['audience'])
551
- with gr.Accordion("Aesthetics", open=True):
552
- q_style = gr.Textbox(label="Overall Style/Vibe", value=default_answers['style'])
553
- q_colors = gr.Textbox(label="Preferred Colors", value=default_answers['colors'])
554
- q_theme = gr.Radio(label="Theme", choices=["dark", "light"], value=default_answers['theme'])
555
- with gr.Accordion("Content & Structure", open=True):
556
- q_pages = gr.Textbox(label="Required Pages", value=default_answers['pages'])
557
- q_homeMessage = gr.Textbox(label="Home Page Message/Headline", value=default_answers['homeMessage'])
558
- q_features = gr.Textbox(label="Key Features/Services", lines=3, value=default_answers['features'])
559
- q_about = gr.Textbox(label="About Us Content", lines=4, value=default_answers['about'])
560
- with gr.Accordion("Brand Voice & Final Touches", open=True):
561
- q_tone = gr.Textbox(label="Tone of Voice", value=default_answers['tone'])
562
- q_tagline = gr.Textbox(label="Tagline/Slogan", value=default_answers['tagline'])
563
- q_extra = gr.Textbox(label="Additional Instructions", lines=3, value=default_answers['extra'])
564
 
565
- questionnaire_inputs = [q_name, q_purpose, q_audience, q_style, q_colors, q_theme, q_pages, q_homeMessage, q_features, q_about, q_tone, q_tagline, q_extra]
566
- generate_button = gr.Button("Generate My Website", variant="primary")
 
 
 
567
 
568
- # --- View: Generating ---
569
- with gr.Column(visible=False, elem_id="generating-view") as generating_view:
570
- gr.Markdown("## ⏳ Generating Your Website...")
571
- status_text = gr.Textbox("Initializing...", label="Status", interactive=False)
572
 
573
- # --- View: Logo Picker ---
574
- with gr.Column(visible=False) as logo_picker_view:
575
- gr.Markdown("## ✨ Choose Your Logo\nThe AI has generated these logo options. Pick your favorite to continue.")
576
- logo_gallery = gr.Gallery(label="Logo Options", columns=5, object_fit="contain", elem_id="logo-picker")
577
 
578
- # --- View: Preview & Editor ---
579
- with gr.Row(visible=False) as preview_view:
580
- with gr.Column(scale=1):
581
- gr.Markdown("## 🛠️ Website Editor")
582
- with gr.Tabs():
583
- with gr.TabItem("Pages & Content"):
584
- page_selector = gr.Radio(label="Select Page to Edit", choices=["Home"], value="Home")
585
- with gr.Column() as page_content_controls:
586
- gr.Markdown("Page content controls will appear here.")
587
- with gr.TabItem("Global Settings"):
588
- with gr.Accordion("Header & Footer", open=True):
589
- header_title_input = gr.Textbox(label="Header: Site Title")
590
- footer_text_input = gr.Textbox(label="Footer: Copyright Text")
591
- with gr.TabItem("Theme"):
592
- with gr.Accordion("Colors", open=True):
593
- theme_app_bg_input = gr.ColorPicker(label="App Background")
594
- theme_panel_bg_input = gr.ColorPicker(label="Panel Background")
595
- theme_header_bg_input = gr.ColorPicker(label="Header Background")
596
- theme_footer_bg_input = gr.ColorPicker(label="Footer Background")
597
- theme_primary_text_input = gr.ColorPicker(label="Primary Text")
598
- theme_secondary_text_input = gr.ColorPicker(label="Secondary Text")
599
- theme_primary_accent_input = gr.ColorPicker(label="Primary Accent")
600
- theme_secondary_accent_input = gr.ColorPicker(label="Secondary Accent")
601
- with gr.Accordion("Font", open=True):
602
- theme_font_family_input = gr.Dropdown(label="Font Family", choices=['font-sans', 'font-serif', 'font-mono'])
603
- with gr.Column(scale=3):
604
- with gr.Tabs():
605
- with gr.TabItem("Live Preview"):
606
- html_preview = gr.HTML(value="<p>Your website preview will appear here.</p>",)
607
- with gr.TabItem("Embed Code"):
608
- code_preview = gr.Code(language="html", label="HTML Code")
609
 
610
- # --- Event Wiring ---
611
- start_button.click(handle_start, outputs=[welcome_view, questionnaire_view])
612
-
613
- generate_button.click(
614
- handle_generate_layout,
615
- inputs=questionnaire_inputs,
616
- outputs=[
617
- questionnaire_view, generating_view, status_text, error_box,
618
- logo_picker_view, website_settings_state, active_page_path_state
619
- ]
620
- )
621
-
622
- logo_gallery.select(
623
- handle_logo_select,
624
- inputs=[website_settings_state, active_page_path_state],
625
- outputs=[
626
- logo_picker_view, preview_view, website_settings_state, html_preview, code_preview,
627
- page_selector, header_title_input, footer_text_input,
628
- theme_app_bg_input, theme_panel_bg_input, theme_header_bg_input, theme_footer_bg_input,
629
- theme_primary_text_input, theme_secondary_text_input, theme_primary_accent_input,
630
- theme_secondary_accent_input, theme_font_family_input
631
- ]
632
- )
633
-
634
- page_selector.change(
635
- handle_page_change,
636
- inputs=[page_selector, website_settings_state],
637
- outputs=[active_page_path_state, html_preview, code_preview, page_content_controls]
638
- )
639
-
640
- # Consolidate setting controls for easier handling
641
- setting_controls = [
642
- header_title_input, footer_text_input,
643
- theme_app_bg_input, theme_panel_bg_input, theme_header_bg_input, theme_footer_bg_input,
644
- theme_primary_text_input, theme_secondary_text_input, theme_primary_accent_input,
645
- theme_secondary_accent_input, theme_font_family_input
646
- ]
647
-
648
- for control in setting_controls:
649
- control.change(
650
- handle_setting_change,
651
- inputs=[website_settings_state, active_page_path_state] + setting_controls,
652
- outputs=[website_settings_state, html_preview, code_preview]
653
- )
654
 
655
- return demo
 
656
 
 
657
 
 
658
  if __name__ == "__main__":
659
- app = create_gradio_app()
660
- # To run in a Hugging Face Space, the port must be 7860
661
- app.launch(server_name="0.0.0.0", server_port=7860)
 
 
1
  import gradio as gr
 
 
2
  from google import genai
3
  from google.genai import types
 
4
  from PIL import Image
5
  from io import BytesIO
6
+ import os
 
 
 
 
 
7
 
8
+ # Make sure your GEMINI_API_KEY is set in Hugging Face secrets/environment
9
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
10
  if not GEMINI_API_KEY:
11
+ raise ValueError("GEMINI_API_KEY not found in environment variables.")
12
 
13
  # Initialize Gemini client
14
  client = genai.Client(api_key=GEMINI_API_KEY)
15
 
16
+ # Gradio function
17
+ def generate_image_and_caption(prompt):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  try:
 
 
 
19
  response = client.models.generate_content(
20
  model="gemini-2.0-flash-preview-image-generation",
21
+ contents=prompt,
22
+ config=types.GenerateContentConfig(
23
+ response_modalities=['TEXT', 'IMAGE']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  )
25
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ caption = "No text response."
28
+ img = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ for part in response.candidates[0].content.parts:
31
+ if part.text:
32
+ caption = part.text
33
+ elif part.inline_data:
34
+ img = Image.open(BytesIO(part.inline_data.data))
35
 
36
+ return caption, img
 
 
 
37
 
38
+ except Exception as e:
39
+ return f"Error: {str(e)}", None
 
 
40
 
41
+ # Gradio UI
42
+ with gr.Blocks(title="Gemini 2.0 Flash Preview Image Generator") as demo:
43
+ gr.Markdown("## 🧠✨ Gemini 2.0 Flash Preview Image Generator")
44
+ gr.Markdown("Enter a prompt and get a 3D rendered image + text generated by Google Gemini.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
+ with gr.Row():
47
+ prompt = gr.Textbox(label="Your Prompt", placeholder="Describe the scene...")
48
+ submit = gr.Button("Generate")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ output_text = gr.Textbox(label="Generated Description")
51
+ output_image = gr.Image(label="Generated Image")
52
 
53
+ submit.click(fn=generate_image_and_caption, inputs=prompt, outputs=[output_text, output_image])
54
 
55
+ # Run the app
56
  if __name__ == "__main__":
57
+ demo.launch()