""" BuildAI - AI Engine v7 — ULTRA UPGRADE New providers: Cerebras (ultra-fast), Gemini Flash (smart), DeepSeek (reasoning) Pipeline: Cerebras → Gemini → Groq, with deep fallback chains Higher quality output: v0/Lovable-level websites """ import os import json import asyncio import httpx from typing import AsyncGenerator try: from google import genai as genai_new _GENAI_AVAILABLE = True _GENAI_NEW = True except ImportError: try: import google.generativeai as genai _GENAI_AVAILABLE = True _GENAI_NEW = False except ImportError: _GENAI_AVAILABLE = False _GENAI_NEW = False # ───────────────────────────────────────────────────────────────── # REFERENCE FILE LOADER # ───────────────────────────────────────────────────────────────── _ref_cache = {} def load_ref(filename: str) -> str: global _ref_cache if filename in _ref_cache: return _ref_cache[filename] # Search root, designs subfolder, and common app paths paths = [ filename, f"./{filename}", f"/home/user/app/{filename}", f"./designs/{filename}", f"/home/user/app/designs/{filename}", ] for path in paths: try: with open(path, "r", encoding="utf-8") as f: content = f.read() _ref_cache[filename] = content print(f"[BuildAI] ✓ Loaded {filename} from {path} ({len(content)} chars)") return content except FileNotFoundError: continue print(f"[BuildAI] ⚠ Not found: {filename} (checked {len(paths)} paths)") return "" def get_references(prompt: str) -> tuple[str, str]: p = prompt.lower() # ── E-Commerce & Retail ────────────────────────────────────────── if any(k in p for k in ['ecommerce','e-commerce','store','shop','product','cart','buy','sell', 'marketplace','clothing','fashion','sneaker','brand','retail','boutique']): return load_ref("ref_ecommerce.html"), "E-COMMERCE" # ── Restaurant & Food ──────────────────────────────────────────── elif any(k in p for k in ['restaurant','cafe','coffee','food','menu','kitchen','chef','dining', 'pizza','burger','sushi','bakery','bistro','bar','catering','eatery']): return load_ref("ref_restaurant.html"), "RESTAURANT" # ── Gym, Fitness & Wellness ────────────────────────────────────── elif any(k in p for k in ['gym','fitness','workout','sport','yoga','spa','wellness','pilates', 'crossfit','training','health club','athletic','bodybuilding']): return load_ref("ref_gym.html"), "FITNESS & WELLNESS" # ── Music, Band & Artist ───────────────────────────────────────── elif any(k in p for k in ['music','band','artist','concert','album','singer','rapper','dj', 'podcast','studio','sound','beats','producer','musician']): return load_ref("ref_music.html"), "MUSIC & ARTIST" # ── Photography & Visual Arts ──────────────────────────────────── elif any(k in p for k in ['photography','photographer','photo','gallery','wedding photo', 'portrait','shoot','lens','lightroom','visual artist']): return load_ref("ref_photography.html"), "PHOTOGRAPHY" # ── Travel & Hospitality ───────────────────────────────────────── elif any(k in p for k in ['travel','tourism','hotel','resort','airbnb','hostel','vacation', 'trip','destination','adventure','tour','booking','flights']): return load_ref("ref_travel.html"), "TRAVEL & HOSPITALITY" # ── Crypto, Web3 & Fintech ─────────────────────────────────────── elif any(k in p for k in ['crypto','blockchain','defi','nft','web3','token','bitcoin','ethereum', 'fintech','trading','wallet','exchange','dao','solana']): return load_ref("ref_crypto.html"), "CRYPTO & WEB3" # ── Medical, Healthcare & Clinic ───────────────────────────────── elif any(k in p for k in ['medical','clinic','hospital','doctor','healthcare','dentist','therapy', 'mental health','pharmacy','wellness clinic','telehealth','patient']): return load_ref("ref_medical.html"), "HEALTHCARE" # ── Education & E-Learning ─────────────────────────────────────── elif any(k in p for k in ['education','school','university','course','learning','teaching', 'elearning','tutoring','bootcamp','academy','lms','classroom']): return load_ref("ref_education.html"), "EDUCATION" # ── Real Estate & Property ─────────────────────────────────────── elif any(k in p for k in ['real estate','property','realty','housing','apartment','mortgage', 'home listing','interior design','architecture','construction']): return load_ref("ref_real_estate.html"), "REAL ESTATE" # ── Agency, Studio & Creative ──────────────────────────────────── elif any(k in p for k in ['agency','creative agency','design studio','marketing agency', 'advertising','branding','production house','creative studio']): return load_ref("ref_agency.html"), "CREATIVE AGENCY" # ── Startup & SaaS App ─────────────────────────────────────────── elif any(k in p for k in ['startup','app','saas app','mobile app','platform','tool','software', 'product launch','mvp','tech startup','b2b']): return load_ref("ref_startup.html"), "STARTUP" # ── Blog & Content ─────────────────────────────────────────────── elif any(k in p for k in ['blog','news','magazine','newsletter','content','editorial', 'publication','media','journal','articles','writing']): return load_ref("ref_blog.html"), "BLOG & MEDIA" # ── Portfolio & Personal ───────────────────────────────────────── elif any(k in p for k in ['portfolio','designer','developer','cv','resume','case study', 'my work','showcase','freelance','personal site','personal brand']): return load_ref("ref_portfolio.html"), "PORTFOLIO" # ── Dashboard & Analytics ──────────────────────────────────────── elif any(k in p for k in ['dashboard','admin','analytics','crm','chart','kpi','metric', 'report','data','fintech dashboard','saas dashboard']): return load_ref("ref_dashboard.html"), "DASHBOARD" # ── Default: SaaS Landing ──────────────────────────────────────── else: return load_ref("ref_saas_app.html") or load_ref("ref_landing.html"), "SAAS LANDING PAGE" # ───────────────────────────────────────────────────────────────── # SMART IMAGE KEYWORD EXTRACTOR # ───────────────────────────────────────────────────────────────── IMAGE_KEYWORDS = { "cafe": ["coffee,cafe,cup", "barista,coffee,making", "cafe,interior,cozy", "coffee,latte,art"], "coffee": ["coffee,espresso,cup", "coffee,beans,roasted", "barista,coffee,brewing", "coffee,cafe,table"], "restaurant": ["restaurant,food,plating", "restaurant,interior,dining", "chef,cooking,kitchen", "food,gourmet,dish"], "pizza": ["pizza,italian,fresh", "pizza,oven,baking", "pizza,toppings,cheese", "pizzeria,italian,food"], "burger": ["burger,beef,fresh", "hamburger,restaurant,juicy", "burger,fries,meal", "fast,food,burger"], "sushi": ["sushi,japanese,fresh", "sushi,roll,seafood", "sushi,chef,making", "japanese,food,restaurant"], "bakery": ["bakery,bread,fresh", "pastry,croissant,baked", "cake,decoration,bakery", "bread,oven,baking"], "tea": ["tea,cup,hot", "tea,leaves,herbal", "teapot,ceramic,drink", "tea,ceremony,japan"], "bar": ["bar,cocktail,drinks", "bartender,mixing,cocktail", "whiskey,glass,bar", "nightlife,bar,drinks"], "fashion": ["fashion,clothing,style", "model,outfit,trendy", "clothes,boutique,store", "fashion,designer,wear"], "sneaker": ["sneakers,shoes,white", "athletic,shoes,sport", "sneaker,collection,display", "shoes,fashion,urban"], "clothing": ["clothing,fashion,store", "outfit,stylish,model", "wardrobe,clothes,fashion", "apparel,shopping,retail"], "jewelry": ["jewelry,gold,elegant", "necklace,diamond,luxury", "ring,jewelry,sparkle", "bracelet,fashion,accessories"], "saas": ["technology,software,laptop", "office,team,collaboration", "startup,tech,modern", "dashboard,analytics,screen"], "startup": ["startup,office,modern", "team,meeting,business", "entrepreneur,laptop,coffee", "tech,office,workspace"], "agency": ["office,creative,team", "agency,design,creative", "meeting,business,professional", "workspace,modern,office"], "tech": ["technology,innovation,laptop", "coding,developer,screen", "tech,startup,modern", "software,computer,code"], "gym": ["gym,workout,fitness", "exercise,weights,strong", "fitness,training,athlete", "gym,equipment,sport"], "yoga": ["yoga,meditation,peace", "yoga,pose,wellness", "mindfulness,yoga,calm", "yoga,studio,class"], "spa": ["spa,relaxation,wellness", "massage,therapy,calm", "beauty,spa,treatment", "wellness,retreat,peaceful"], "medical": ["doctor,medical,hospital", "healthcare,professional,clinic", "medical,team,care", "hospital,health,medicine"], "real estate": ["house,modern,architecture", "interior,luxury,home", "property,real,estate", "living,room,design"], "interior": ["interior,design,modern", "furniture,home,decor", "living,space,elegant", "home,decoration,style"], "education": ["education,students,learning", "classroom,school,teaching", "university,campus,study", "books,learning,knowledge"], "course": ["online,learning,laptop", "education,course,digital", "student,studying,desk", "elearning,digital,course"], "travel": ["travel,destination,adventure", "landscape,beautiful,nature", "tourism,city,explore", "travel,photography,world"], "hotel": ["hotel,luxury,room", "resort,pool,vacation", "hotel,lobby,elegant", "travel,accommodation,comfort"], "portfolio": ["designer,creative,workspace", "creative,studio,modern", "photographer,camera,art", "design,portfolio,work"], "photography": ["camera,photography,lens", "photo,shoot,studio", "photographer,nature,outdoor", "portrait,photography,light"], "music": ["music,studio,recording", "guitar,musician,performance", "concert,music,stage", "headphones,music,listening"], "gaming": ["gaming,computer,setup", "game,controller,neon", "esports,gaming,tournament", "game,developer,screen"], "finance": ["finance,investment,chart", "money,banking,professional", "stock,market,trading", "wealth,management,business"], "law": ["law,office,professional", "lawyer,justice,court", "legal,business,meeting", "attorney,desk,documents"], "default_hero": ["modern,business,professional", "office,team,success", "technology,innovation,future", "startup,growth,success"], "default_product": ["product,design,modern", "item,display,showcase", "product,photography,clean", "goods,retail,store"], "default_person": ["professional,portrait,business", "person,team,corporate", "headshot,professional,smile", "team,member,office"], } def extract_image_keywords(prompt: str, website_type: str) -> dict: p = prompt.lower() keywords = {"hero": None, "section": None, "card": None, "person": None} for topic, kw_list in IMAGE_KEYWORDS.items(): if topic in p: if not keywords["hero"]: keywords["hero"] = kw_list[0] if not keywords["section"] and len(kw_list) > 1: keywords["section"] = kw_list[1] if not keywords["card"] and len(kw_list) > 2: keywords["card"] = kw_list[2] break if not keywords["hero"]: type_defaults = { "E-COMMERCE": "product,fashion,store", "RESTAURANT": "restaurant,food,dining", "PORTFOLIO": "creative,designer,workspace", "DASHBOARD": "office,technology,business", "SAAS LANDING PAGE": "technology,startup,modern", } keywords["hero"] = type_defaults.get(website_type, "business,modern,professional") if not keywords["section"]: keywords["section"] = keywords["hero"] if not keywords["card"]: keywords["card"] = keywords["hero"] keywords["person"] = "professional,portrait,person" return keywords def get_photo_instructions(prompt: str, website_type: str) -> str: kw = extract_image_keywords(prompt, website_type) h = kw["hero"].replace(",", "%20").replace(" ", "%20") s = kw["section"].replace(",", "%20").replace(" ", "%20") c = kw["card"].replace(",", "%20").replace(" ", "%20") p = kw["person"].replace(",", "%20").replace(" ", "%20") return f""" ━━━ REAL PHOTOS — USE UNSPLASH (FREE, NO API KEY, ALWAYS LOADS) ━━━ URL FORMAT: https://source.unsplash.com/WIDTHxHEIGHT/?keyword,keyword&sig=N Hero background (full-width behind text): style="background-image: url('https://source.unsplash.com/1600x900/?{h.replace(',','%2C')}&sig=1'); background-size: cover; background-position: center; background-repeat: no-repeat;" Section/feature image: ... Card/product image: ... Person/team avatar: ... Change seed=1 seed=2 seed=3 etc for each image to get variety. STRICT RULES: - Use class NOT className on all img tags - Use style="background-image: url('...')" NOT style={{{{...}}}} - 6 to 8 images MINIMUM in the site — more is better - EVERY product card, blog card, testimonial MUST have an image - Hero MUST have background-image photo - NEVER use placeholder.com or loremflickr — gray boxes kill design - ALWAYS add loading="lazy" decoding="async" alt="description" """ # ───────────────────────────────────────────────────────────────── # PYTHON-LEVEL SCRIPT INJECTION # These scripts are GUARANTEED to be injected by Python. # Even if the AI forgets them, the site will still work. # ───────────────────────────────────────────────────────────────── _NAVIGATE_SCRIPT = """ """ _INIT_SCRIPT = """ """ def inject_required_scripts(html: str, pages: list) -> str: """ Python-level guarantee: inject navigate() and init scripts before . Called on EVERY round output. Even if AI forgets these, the site works. """ if not html or "" not in html: return html to_inject = [] # 1. navigate() — only for multi-page sites if pages and len(pages) > 1 and "function navigate" not in html: to_inject.append(_NAVIGATE_SCRIPT) print(f"[BuildAI] Injected navigate() for pages: {pages}") # 2. AOS.init() — CRITICAL: without this all data-aos elements are INVISIBLE forever if "AOS.init" not in html: to_inject.append(_INIT_SCRIPT) print("[BuildAI] Injected AOS/GSAP init script (was missing from AI output)") elif "gsap.registerPlugin" not in html and "gsap" in html: to_inject.append("""""") if to_inject: injection = "\n".join(to_inject) html = html.replace("", injection + "\n", 1) return html CDN_STACK = """ ━━━ TECHNOLOGY STACK — VANILLA HTML + GSAP + AOS ━━━ PUT IN : ━━━ DO NOT PUT ANY INIT SCRIPT — BuildAI injects it automatically ━━━ ━━━ VANILLA JS RULES ━━━ - Pure HTML5 + CSS + vanilla JavaScript — NO React, NO Babel, NO JSX - Use class NOT className; use for NOT htmlFor; inline style="..." NOT style={{}} - All interactivity via vanilla JS addEventListener or onclick="..." on buttons - GSAP and AOS will be initialized automatically — just use data-aos="fade-up" on elements - Add data-aos="fade-up" to EVERY feature card, section header, testimonial, and content div - Add data-aos-delay="0" "100" "200" for staggered grid items - Hero section: wrap content in
for GSAP animation - Mobile hamburger: id="menu-btn" button + id="mobile-menu" div.hidden - FAQ accordion: class="faq-item" > button.faq-btn + div.faq-ans.hidden + span.faq-icon - Scroll-to-top: id="scroll-top" button with style="opacity:0" - NEVER use bg-surface or bg-surface-2 — use bg-[#07080f] bg-[#0d0e1a] directly - Images: use EXACT URLs from REAL PHOTOS section below — NEVER placeholder.com - All images: loading="lazy" decoding="async" alt="description" """ # ───────────────────────────────────────────────────────────────── # ELITE DESIGN SYSTEM — v7 UPGRADE # ───────────────────────────────────────────────────────────────── DESIGN_BIBLE = """ ━━━ ELITE DESIGN SYSTEM v8 — VANILLA HTML ━━━ CSS CUSTOM PROPERTIES (add to ", html, _re.DOTALL) css_content = "\n\n".join(s.strip() for s in styles) if css_content: files["styles.css"] = f"/* {project_name} — generated by BuildAI */\n{css_content}" for match in _re.finditer(r"]*>.*?", html, _re.DOTALL): html = html.replace(match.group(), "", 1) html = html.replace("", ' \n', 1) # Extract ", replace_script, html, flags=_re.DOTALL) js_content = "\n\n".join(inline_scripts) if js_content: files["script.js"] = f"// {project_name} — generated by BuildAI\n{js_content}" html = html.replace("", ' \n', 1) # Split multi-page: look for id="page-X" sections page_divs = _re.findall(r'(]+id="page-([\w-]+)"[^>]*>)', html) if len(page_divs) >= 2: # Extract each page section into its own file parts = _re.split(r'(?=]+id="page-[\w-]+")', html) main_html = parts[0] if parts else html for part in parts[1:]: m = _re.match(r']+id="page-([\w-]+)"', part) if m: page_id = m.group(1) page_file = f"pages/{page_id}.html" # Keep it under 500 lines lines = part.split("\n") if len(lines) > 500: part = "\n".join(lines[:500]) + "\n" files[page_file] = f"\n{part}" files["index.html"] = main_html else: # Single page — just chunk into index.html, keeping under 500 lines lines = html.split("\n") if len(lines) <= 500: files["index.html"] = html else: # Split into chunks of 400 lines with proper head/body head_end = next((i for i, l in enumerate(lines) if "" in l), 10) head = "\n".join(lines[:head_end+1]) body_lines = lines[head_end+1:] chunk_size = 400 for chunk_idx, start in enumerate(range(0, len(body_lines), chunk_size)): chunk = body_lines[start:start+chunk_size] fname = "index.html" if chunk_idx == 0 else f"section_{chunk_idx}.html" files[fname] = head + "\n" + "\n".join(chunk) + "\n" # Ensure index.html always exists if "index.html" not in files: files["index.html"] = html # Count lines and report for fname, fcontent in files.items(): lc = len(fcontent.split("\n")) print(f"[BuildAI] File: {fname} ({lc} lines)") return files # ═══════════════════════════════════════════════════════════════ # SEQUENTIAL MULTI-FILE GENERATION # AI generates a manifest first, then each file separately. # Each file < 400 lines. Supports HTML, CSS, JS, TSX, JSX, TS, PY # ═══════════════════════════════════════════════════════════════ MANIFEST_SYSTEM = """You are a senior web architect. Given a website description, output ONLY a JSON manifest of files to generate. HTML site example: {"type":"html","framework":"vanilla","files":[ {"name":"index.html","desc":"Hero, nav, features, testimonials, footer — main landing"}, {"name":"pages/shop.html","desc":"Product grid, filters, cart sidebar"}, {"name":"pages/about.html","desc":"Team, story, mission"}, {"name":"styles.css","desc":"CSS variables, animations, responsive styles"}, {"name":"script.js","desc":"navigate(), cart, mobile menu, AOS init"} ]} React/TSX site example: {"type":"react","framework":"react","files":[ {"name":"App.tsx","desc":"Root component with routing between pages"}, {"name":"components/Nav.tsx","desc":"Sticky navbar with mobile hamburger"}, {"name":"components/Hero.tsx","desc":"Fullscreen hero with gradient and CTA buttons"}, {"name":"components/Products.tsx","desc":"Product cards grid with add-to-cart"}, {"name":"components/Footer.tsx","desc":"Links, social, copyright"}, {"name":"index.css","desc":"Tailwind base + custom animations + CSS vars"} ]} Rules: - MAX 7 files, EACH under 400 lines - Use HTML type for: e-commerce, restaurants, portfolios, landing pages - Use React type for: dashboards, SaaS apps, admin panels, complex UIs - Output ONLY the JSON object, nothing else, no markdown""" FILE_GEN_SYSTEM = """You are an expert web developer generating ONE file at a time. CRITICAL RULES: 1. Output ONLY the raw file content — no explanation, no markdown fences 2. Stay under 400 lines 3. For HTML: use Tailwind CDN, Unsplash images (https://source.unsplash.com/WxH/?keyword&sig=N), AOS animations 4. For CSS: use CSS variables, include all keyframes, mobile-first 5. For JS: vanilla JS only, no imports (everything in one script scope) 6. For TSX/JSX: use React hooks, Tailwind classes, no external imports except React 7. For HTML pages: include proper structure with CDN scripts 8. Images: ALWAYS use https://source.unsplash.com/WIDTHxHEIGHT/?keyword&sig=N (change sig for variety) 9. NEVER use placeholder.com or pollinations.ai""" async def generate_manifest(prompt: str, website_type: str, caller) -> dict: """Ask AI for a file manifest. Fast call, small response.""" import json as _json, re as _re msg = f"Website request: {prompt}\nDetected type: {website_type}\nGenerate the file manifest JSON." try: raw = await caller(MANIFEST_SYSTEM, msg, "") # Extract JSON from response raw = raw.strip() m = _re.search(r'\{[\s\S]*\}', raw) if m: raw = m.group() result = _json.loads(raw) if "files" in result and len(result["files"]) > 0: print(f"[BuildAI] Manifest: {result['type']} | {len(result['files'])} files") return result except Exception as e: print(f"[BuildAI] Manifest failed: {e}") # Fallback manifest return {"type":"html","framework":"vanilla","files":[ {"name":"index.html","desc":"Complete website with all sections"}, {"name":"styles.css","desc":"All styles and animations"}, {"name":"script.js","desc":"All JavaScript functionality"} ]} async def generate_single_file(fname: str, fdesc: str, prompt: str, all_files: list, existing_files: dict, project_type: str, caller) -> str: """Generate one file given context of all other files.""" # Build context of already-generated files (names + first 3 lines only to save tokens) ctx_parts = [] for fn, fc in existing_files.items(): preview = "\n".join(fc.split("\n")[:3]) ctx_parts.append(f"// Already generated: {fn}\n// {preview}...") ctx = "\n".join(ctx_parts) if ctx_parts else "// First file being generated" all_names = ", ".join(f["name"] for f in all_files) msg = f"""Project: {prompt} Project type: {project_type} All files in project: {all_names} Other files already generated: {ctx} NOW GENERATE: {fname} Purpose: {fdesc} Output ONLY the raw content of {fname}. No markdown, no explanation, no code fences.""" result = await caller(FILE_GEN_SYSTEM, msg, "") # Clean code fences if AI added them result = result.strip() if result.startswith("```"): lines = result.split("\n") result = "\n".join(lines[1:-1] if lines[-1] == "```" else lines[1:]) return result async def run_sequential_pipeline(prompt: str, caller) -> AsyncGenerator[str, None]: """ Generate multi-file project sequentially. Yields SSE events: file_start, file_done, all_done """ _, website_type = get_references(prompt) yield make_sse({"type":"status","round":1,"done":False, "message":"📋 Planning file structure..."}) manifest = await generate_manifest(prompt, website_type, caller) proj_type = manifest.get("type", "html") files_plan = manifest.get("files", []) yield make_sse({"type":"manifest","files":files_plan,"project_type":proj_type, "message":f"📁 {len(files_plan)} files to generate"}) generated = {} # filename → content for i, file_info in enumerate(files_plan): fname = file_info["name"] fdesc = file_info.get("desc", "") yield make_sse({"type":"file_start","file":fname,"index":i, "total":len(files_plan), "message":f"⚙️ Generating {fname} ({i+1}/{len(files_plan)})..."}) try: content = await generate_single_file( fname, fdesc, prompt, files_plan, generated, proj_type, caller) generated[fname] = content lines = len(content.split("\n")) print(f"[BuildAI] ✓ {fname} ({lines} lines)") yield make_sse({"type":"file_done","file":fname,"content":content, "index":i,"total":len(files_plan), "message":f"✓ {fname} ({lines} lines)"}) except Exception as e: print(f"[BuildAI] Failed to generate {fname}: {e}") yield make_sse({"type":"file_error","file":fname,"error":str(e)}) # Combine into preview HTML preview_html = _combine_for_preview(generated, proj_type, prompt) yield make_sse({"type":"code","round":3,"done":True,"final":True, "message":"🎉 Website ready!", "code": preview_html, "files": generated, "project_type": proj_type}) def _combine_for_preview(files: dict, proj_type: str, prompt: str) -> str: """Combine generated files into a single previewable HTML.""" if proj_type == "react": return _combine_react_preview(files, prompt) else: return _combine_html_preview(files) def _combine_html_preview(files: dict) -> str: """Merge HTML/CSS/JS files into one HTML file for preview.""" index_html = files.get("index.html", "") css = files.get("styles.css", files.get("style.css", "")) js = files.get("script.js", files.get("app.js", "")) if css and "", f"\n", 1) if js and index_html: index_html = index_html.replace("", f"\n", 1) return index_html or next(iter(files.values()), "") def _combine_react_preview(files: dict, prompt: str) -> str: """Bundle React/TSX files into a single HTML with Babel transpiler.""" # Combine all TSX/JSX/TS files all_tsx = [] app_file = files.get("App.tsx", files.get("App.jsx", files.get("app.tsx", ""))) for fname, content in files.items(): if fname.endswith((".tsx",".jsx",".ts",".js")) and fname not in ("index.js","main.js"): # Strip import statements (everything is global in browser) lines = [] for line in content.split("\n"): if line.startswith("import ") and ("from '" in line or 'from "' in line): continue # skip imports if line.startswith("export default ") and fname != list(files.keys())[-1]: line = line.replace("export default ", "// exported: ", 1) lines.append(line) all_tsx.append(f"// === {fname} ===\n" + "\n".join(lines)) combined_tsx = "\n\n".join(all_tsx) css_content = "" for fname, content in files.items(): if fname.endswith(".css"): css_content += content + "\n" return f""" {prompt[:40]}
""" async def run_pipeline(prompt: str, current_code: str = "") -> AsyncGenerator[str, None]: is_edit = bool(current_code and len(current_code) > 100) # ── Step 0: Orchestrator analyzes the request ───────────────── yield make_sse({"type":"status","round":0,"done":False,"message":"🎯 Analyzing your request..."}) orch = await orchestrate(prompt, is_edit) effective_prompt = orch.get("enhanced_prompt", prompt) if not effective_prompt or len(effective_prompt) < len(prompt) * 0.5: effective_prompt = prompt # never downgrade orch_pages = orch.get("pages", []) pages = orch_pages if orch_pages else (detect_multipage(prompt) if not is_edit else []) # ── ALWAYS use Gemini as primary — native SDK gives 65K tokens ── primary_model_name = "gemini" primary_caller = call_gemini # ── NEW BUILDS → sequential multi-file pipeline ────────────── if not is_edit: async for event in run_sequential_pipeline(effective_prompt, primary_caller): yield event return ref_content, website_type = get_references(effective_prompt) orch_type = orch.get("website_type", "") if orch_type and orch_type in SECTIONS_MAP: website_type = orch_type print(f"[BuildAI] Building: {website_type} | {'edit' if is_edit else 'new'} | " f"model={primary_model_name} | pages={pages} | business='{orch.get('business_name','')}'") design_skills = get_design_skills(effective_prompt, website_type) design_colors = _extract_design_colors(design_skills) # ← NEW: force colors into task builder_system = get_builder_prompt(ref_content, website_type, effective_prompt, design_skills) page_instruction = "" if pages: page_list = " / ".join(f"'{p}'" for p in pages) page_instruction = ( f"\n\n{MULTI_PAGE_PATTERN}\n" f"PAGES TO BUILD: {page_list}\n" f"Build EVERY listed page as a full
with complete content.\n" f"Use onclick=\"navigate('page')\" on ALL buttons that change pages.\n" ) quality_notes = orch.get("quality_notes", "") color_theme = orch.get("color_theme", "") extra = "" if quality_notes: extra += f"\nQUALITY FOCUS: {quality_notes}" if color_theme: extra += f"\nCOLOR THEME: {color_theme}" if is_edit: task = ( f"You are EDITING an existing website. The user wants: {prompt}\n\n" f"RULES:\n" f"1. KEEP 100% of the existing HTML structure, sections, and images.\n" f"2. ONLY apply the user's requested change.\n" f"3. Output the COMPLETE HTML file with your changes applied.\n" f"4. Preserve vanilla HTML structure — keep navigate() calls, keep all section ids.\n" f"5. Use class NOT className. Vanilla JS only — no React, no Babel.\n\n" f"Current code:\n{current_code}" ) else: task = ( f"Build a stunning {website_type} website for: {effective_prompt}\n" f"{page_instruction}{extra}\n" f"{design_colors}" f"Requirements: Syne+DM Sans fonts, Tailwind+GSAP+AOS CDN, " f"min 8 images using https://source.unsplash.com/WIDTHxHEIGHT/?keyword,keyword&sig=N (free, always works), " f"real content NO lorem ipsum, breathtaking hero with hero-content div, bento grid features, " f"data-aos='fade-up' on ALL cards and section content.\n" f"CRITICAL: Pure vanilla HTML — NO React, NO Babel, NO JSX. Use class NOT className.\n" f"CRITICAL: Use onclick=\"navigate('page')\" on all nav buttons. Wrap hero text in
.\n" f"DO NOT write any