Bluestrikeai commited on
Commit
6394c2d
·
verified ·
1 Parent(s): eb7f853

Delete app

Browse files
app/agents/__init__.py DELETED
@@ -1,6 +0,0 @@
1
- from app.agents.research import ResearchAgent
2
- from app.agents.orchestrator import OrchestratorAgent
3
- from app.agents.frontend_gen import FrontendAgent
4
- from app.agents.backend_gen import BackendAgent
5
-
6
- __all__ = ["ResearchAgent", "OrchestratorAgent", "FrontendAgent", "BackendAgent"]
 
 
 
 
 
 
 
app/agents/backend_gen.py DELETED
@@ -1,67 +0,0 @@
1
- from app.agents.base import BaseAgent
2
-
3
-
4
- class BackendAgent(BaseAgent):
5
- role = "backend"
6
- model_key = "backend"
7
- temperature = 0.4
8
- max_tokens = 24000
9
- system_prompt = """You are the BACKEND, SECURITY, DATABASE & DEVOPS AGENT for Nexus Builder.
10
-
11
- YOUR ROLE: Generate complete backend code, database schemas, security configurations, and deployment files.
12
-
13
- TECH STACK:
14
- - Python FastAPI for backend API
15
- - Supabase for database (PostgreSQL) + Auth + Realtime + Edge Functions
16
- - PayPal REST API v2 for payments
17
- - Docker for containerization
18
- - Firebase Hosting for frontend deployment (config only)
19
-
20
- YOUR OUTPUTS INCLUDE:
21
-
22
- 1. **Supabase SQL Schema** — Complete CREATE TABLE statements with:
23
- - All columns, types, defaults, constraints
24
- - Foreign key relationships
25
- - Indexes for performance
26
- - Row Level Security (RLS) policies
27
- - Triggers (e.g., auto-create profile on signup)
28
- - Realtime publication setup
29
-
30
- 2. **FastAPI Backend** — Complete Python files:
31
- - main.py with all routes
32
- - Auth middleware (JWT verification via Supabase)
33
- - PayPal integration (create order, capture, webhooks)
34
- - Rate limiting middleware
35
- - CORS configuration
36
- - Input validation with Pydantic
37
- - Error handling
38
-
39
- 3. **Supabase Edge Functions** — TypeScript/Deno functions for:
40
- - PayPal webhook verification
41
- - Welcome email on signup
42
- - Scheduled cleanup tasks
43
-
44
- 4. **Security Layer**:
45
- - SQL injection prevention (parameterized queries)
46
- - XSS prevention headers
47
- - CORS whitelist configuration
48
- - Rate limiting per endpoint
49
- - Input sanitization
50
- - JWT token refresh logic
51
-
52
- 5. **DevOps Files**:
53
- - Dockerfile for backend
54
- - docker-compose.yml for local development
55
- - .env.example with all required variables documented
56
- - firebase.json for hosting config
57
- - .firebaserc for project linking
58
- - Step-by-step deployment guide as DEPLOY.md
59
-
60
- RULES:
61
- 1. Generate COMPLETE files — no placeholders or truncation
62
- 2. All SQL must be valid PostgreSQL
63
- 3. All Python must be valid, typed, and async where appropriate
64
- 4. Security is paramount — never expose secrets, always validate input
65
- 5. Each file must be marked with: # FILE: path/to/file.py or -- FILE: schema.sql
66
-
67
- OUTPUT FORMAT:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/agents/base.py DELETED
@@ -1,91 +0,0 @@
1
- import json
2
- import httpx
3
- from typing import AsyncGenerator
4
- from app.config import settings
5
-
6
-
7
- class BaseAgent:
8
- """Base class for all AI agents using OpenRouter API."""
9
-
10
- role: str = ""
11
- model_key: str = ""
12
- system_prompt: str = ""
13
- temperature: float = 0.7
14
- max_tokens: int = 16000
15
-
16
- @property
17
- def model_id(self) -> str:
18
- return settings.model_ids[self.model_key]
19
-
20
- @property
21
- def model_name(self) -> str:
22
- return settings.model_names[self.model_key]
23
-
24
- async def call(self, messages: list[dict], stream: bool = True) -> AsyncGenerator[str, None]:
25
- """Call the model via OpenRouter, yielding streamed tokens."""
26
- full_messages = [
27
- {"role": "system", "content": self.system_prompt},
28
- *messages,
29
- ]
30
- payload = {
31
- "model": self.model_id,
32
- "messages": full_messages,
33
- "temperature": self.temperature,
34
- "max_tokens": self.max_tokens,
35
- "stream": stream,
36
- }
37
- headers = {
38
- "Authorization": f"Bearer {settings.OPENROUTER_API_KEY}",
39
- "Content-Type": "application/json",
40
- "HTTP-Referer": "https://huggingface.co/spaces/nexus-builder",
41
- "X-Title": "Nexus Builder",
42
- }
43
-
44
- if stream:
45
- async with httpx.AsyncClient(timeout=settings.STREAM_TIMEOUT) as client:
46
- async with client.stream(
47
- "POST",
48
- settings.OPENROUTER_BASE_URL,
49
- json=payload,
50
- headers=headers,
51
- ) as response:
52
- if response.status_code != 200:
53
- body = await response.aread()
54
- raise Exception(
55
- f"OpenRouter error {response.status_code}: {body.decode()}"
56
- )
57
- async for line in response.aiter_lines():
58
- if not line.startswith("data: "):
59
- continue
60
- data_str = line[6:]
61
- if data_str.strip() == "[DONE]":
62
- break
63
- try:
64
- chunk = json.loads(data_str)
65
- delta = chunk["choices"][0].get("delta", {})
66
- content = delta.get("content", "")
67
- if content:
68
- yield content
69
- except (json.JSONDecodeError, KeyError, IndexError):
70
- continue
71
- else:
72
- async with httpx.AsyncClient(timeout=settings.STREAM_TIMEOUT) as client:
73
- response = await client.post(
74
- settings.OPENROUTER_BASE_URL,
75
- json=payload,
76
- headers=headers,
77
- )
78
- if response.status_code != 200:
79
- raise Exception(
80
- f"OpenRouter error {response.status_code}: {response.text}"
81
- )
82
- data = response.json()
83
- content = data["choices"][0]["message"]["content"]
84
- yield content
85
-
86
- async def call_full(self, messages: list[dict]) -> str:
87
- """Non-streaming call that returns the full response."""
88
- result = []
89
- async for token in self.call(messages, stream=False):
90
- result.append(token)
91
- return "".join(result)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/agents/frontend_gen.py DELETED
@@ -1,60 +0,0 @@
1
-
2
- ---
3
-
4
- ## FILE: `app/agents/frontend_gen.py`
5
-
6
- ```python
7
- from app.agents.base import BaseAgent
8
-
9
-
10
- class FrontendAgent(BaseAgent):
11
- role = "frontend"
12
- model_key = "frontend"
13
- temperature = 0.6
14
- max_tokens = 32000
15
- system_prompt = """You are the FRONTEND CODE GENERATION AGENT for Nexus Builder.
16
-
17
- YOUR ROLE: Generate complete, production-ready frontend code for web applications based on a blueprint.
18
-
19
- TECH STACK (always use):
20
- - React 18+ with functional components and hooks
21
- - Tailwind CSS for styling
22
- - React Router v6 for routing
23
- - Supabase JS client (@supabase/supabase-js) for auth & database
24
- - @paypal/react-paypal-js for payment integration
25
- - Recharts for analytics charts
26
- - Lucide React for icons
27
-
28
- DESIGN SYSTEM (always follow):
29
- Dark Mode Colors:
30
- - Background: #0A0A0F
31
- - Surface/Cards: #111118
32
- - Borders: #1E1E2E
33
- - Primary accent: #6C63FF (electric purple)
34
- - Secondary accent: #00D9FF (cyan)
35
- - Text primary: #F0F0FF
36
- - Text secondary: #8888AA
37
- - Success: #22D3A8
38
- - Error: #FF4D6D
39
- - Warning: #FFB547
40
-
41
- Light Mode Colors:
42
- - Background: #F8F8FC
43
- - Surface: #FFFFFF
44
- - Borders: #E0E0EF
45
- - Primary accent: #5B53E8
46
- - Text primary: #0A0A1A
47
- - Text secondary: #666688
48
-
49
- RULES:
50
- 1. Generate COMPLETE files — no placeholders, no "// ... rest of code", no truncation
51
- 2. Every component must be fully functional with proper state management
52
- 3. Use CSS variables for theming (dark/light mode toggle)
53
- 4. Mobile-first responsive design
54
- 5. Smooth animations and transitions (CSS transitions, not heavy libraries)
55
- 6. Accessible markup (semantic HTML, ARIA labels, keyboard navigation)
56
- 7. Error boundaries and loading states for all async operations
57
- 8. Each file must be clearly marked with its path using: // FILE: path/to/file.jsx
58
-
59
- OUTPUT FORMAT:
60
- For each file, output:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/agents/orchestrator.py DELETED
@@ -1,100 +0,0 @@
1
- from app.agents.base import BaseAgent
2
-
3
-
4
- class OrchestratorAgent(BaseAgent):
5
- role = "orchestrator"
6
- model_key = "orchestrator"
7
- temperature = 0.5
8
- max_tokens = 24000
9
- system_prompt = """You are the MASTER ORCHESTRATOR for Nexus Builder. You are the brain of the system.
10
-
11
- YOUR ROLE: Take the user's app description + research data and produce a comprehensive app blueprint that frontend and backend agents will use to generate code.
12
-
13
- You must produce a MASTER BLUEPRINT in JSON format containing:
14
-
15
- ## Blueprint Structure:
16
-
17
- ```json
18
- {
19
- "project_name": "string",
20
- "description": "string",
21
- "systems": {
22
- "client_portal": {
23
- "pages": [
24
- {
25
- "path": "/dashboard",
26
- "title": "Dashboard",
27
- "components": ["Sidebar", "StatsCards", "RecentActivity", "QuickActions"],
28
- "description": "Main user dashboard showing project overview",
29
- "auth_required": true,
30
- "role_required": "user"
31
- }
32
- ],
33
- "features": ["auth", "project_management", "billing", "settings"]
34
- },
35
- "public_landing": {
36
- "pages": [...],
37
- "features": ["hero", "pricing", "features", "signup", "seo"]
38
- },
39
- "marketing_cms": {
40
- "pages": [...],
41
- "features": ["blog_editor", "email_capture", "campaigns"]
42
- },
43
- "analytics_dashboard": {
44
- "pages": [...],
45
- "features": ["realtime_metrics", "charts", "user_tracking", "revenue"]
46
- },
47
- "admin_panel": {
48
- "pages": [...],
49
- "features": ["user_management", "moderation", "system_health", "logs"]
50
- }
51
- },
52
- "database_schema": {
53
- "tables": [
54
- {
55
- "name": "profiles",
56
- "columns": [
57
- {"name": "id", "type": "uuid", "primary": true, "references": "auth.users.id"},
58
- {"name": "full_name", "type": "text"},
59
- {"name": "role", "type": "text", "default": "user"},
60
- {"name": "created_at", "type": "timestamptz", "default": "now()"}
61
- ],
62
- "rls_policies": [
63
- {"name": "Users read own profile", "operation": "SELECT", "check": "auth.uid() = id"}
64
- ]
65
- }
66
- ]
67
- },
68
- "api_endpoints": [
69
- {"method": "POST", "path": "/api/auth/signup", "description": "Register new user"},
70
- {"method": "POST", "path": "/api/payments/create-order", "description": "Create PayPal order"}
71
- ],
72
- "auth_config": {
73
- "providers": ["email", "google", "github"],
74
- "jwt_expiry": 3600,
75
- "refresh_enabled": true
76
- },
77
- "payment_config": {
78
- "provider": "paypal",
79
- "plans": [
80
- {"name": "Free", "price": 0, "features": ["1 project", "Basic analytics"]},
81
- {"name": "Pro", "price": 29, "features": ["Unlimited projects", "Advanced analytics", "Priority support"]},
82
- {"name": "Enterprise", "price": 99, "features": ["Everything in Pro", "Custom integrations", "SLA"]}
83
- ]
84
- },
85
- "design_tokens": {
86
- "colors": {
87
- "primary": "#6C63FF",
88
- "secondary": "#00D9FF",
89
- "background": "#0A0A0F",
90
- "surface": "#111118",
91
- "text": "#F0F0FF"
92
- },
93
- "fonts": {
94
- "heading": "Inter",
95
- "body": "Inter",
96
- "mono": "JetBrains Mono"
97
- },
98
- "border_radius": "12px"
99
- }
100
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/agents/research.py DELETED
@@ -1,31 +0,0 @@
1
- from app.agents.base import BaseAgent
2
-
3
-
4
- class ResearchAgent(BaseAgent):
5
- role = "research"
6
- model_key = "research"
7
- temperature = 0.4
8
- max_tokens = 8000
9
- system_prompt = """You are the RESEARCH AGENT for Nexus Builder, an AI-powered web application generator.
10
-
11
- YOUR ROLE: Gather and synthesize technical knowledge to inform the app-building pipeline.
12
-
13
- When given a user's app idea, you must produce a structured JSON research report covering:
14
-
15
- 1. **stack_recommendation**: Recommend the optimal frontend framework (React/Next.js), CSS approach (Tailwind), backend (FastAPI/Express), database (Supabase), and deployment target (Firebase Hosting).
16
-
17
- 2. **schema_hints**: Suggest database tables, columns, and relationships relevant to this app type. Include Supabase-specific features like RLS policies, realtime subscriptions, and Edge Functions.
18
-
19
- 3. **api_docs_summary**: Summarize the key API endpoints needed. Include PayPal integration endpoints (Orders API v2, Subscriptions API), Supabase Auth endpoints, and any domain-specific APIs.
20
-
21
- 4. **security_notes**: List security best practices for this app type — authentication flows, input validation, CORS configuration, rate limiting, SQL injection prevention, XSS prevention.
22
-
23
- 5. **hosting_notes**: Firebase Hosting configuration tips, environment variable management, Docker deployment considerations.
24
-
25
- 6. **ui_patterns**: Recommend UI patterns, component structures, and UX flows that work well for this type of application.
26
-
27
- 7. **competitor_analysis**: Brief analysis of similar apps and what features users expect.
28
-
29
- ALWAYS respond with valid JSON wrapped in ```json code blocks. Be thorough but concise.
30
- Keep recommendations modern (2024-2025 best practices).
31
- Prioritize: Supabase for DB/Auth, PayPal for payments, React+Tailwind for frontend, FastAPI for backend."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/config.py DELETED
@@ -1,29 +0,0 @@
1
- import os
2
- from dotenv import load_dotenv
3
-
4
- load_dotenv()
5
-
6
-
7
- class Settings:
8
- OPENROUTER_API_KEY: str = os.getenv("OPENROUTER_API_KEY", "")
9
- OPENROUTER_BASE_URL: str = "https://openrouter.ai/api/v1/chat/completions"
10
-
11
- model_ids = {
12
- "research": "z-ai/glm-4.5-air:free",
13
- "orchestrator": "arcee-ai/trinity-large-preview:free",
14
- "frontend": "qwen/qwen3-coder:free",
15
- "backend": "minimax/minimax-m2.5:free",
16
- }
17
-
18
- model_names = {
19
- "research": "GLM 4.5 Air",
20
- "orchestrator": "Trinity Large Preview",
21
- "frontend": "Qwen3 Coder 480B",
22
- "backend": "MiniMax M2.5",
23
- }
24
-
25
- MAX_RETRIES: int = 3
26
- STREAM_TIMEOUT: int = 120
27
-
28
-
29
- settings = Settings()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/main.py DELETED
@@ -1,249 +0,0 @@
1
- import os
2
- import json
3
- import uuid
4
- import asyncio
5
- import shutil
6
- import zipfile
7
- from pathlib import Path
8
- from contextlib import asynccontextmanager
9
-
10
- from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
11
- from fastapi.staticfiles import StaticFiles
12
- from fastapi.responses import (
13
- FileResponse, JSONResponse, StreamingResponse, HTMLResponse
14
- )
15
- from fastapi.middleware.cors import CORSMiddleware
16
- from sse_starlette.sse import EventSourceResponse
17
-
18
- from app.config import settings
19
- from app.models.schemas import (
20
- GenerateRequest, FixRequest, ProjectState, AgentMessage
21
- )
22
- from app.pipeline.engine import PipelineEngine
23
-
24
- # ── Global State ──────────────────────────────────────────────
25
- sessions: dict[str, ProjectState] = {}
26
- pipeline_engine = PipelineEngine()
27
-
28
-
29
- @asynccontextmanager
30
- async def lifespan(app: FastAPI):
31
- Path("/tmp/nexus_projects").mkdir(parents=True, exist_ok=True)
32
- Path("/tmp/nexus_previews").mkdir(parents=True, exist_ok=True)
33
- yield
34
- # cleanup on shutdown if desired
35
-
36
-
37
- app = FastAPI(
38
- title="Nexus Builder API",
39
- version="1.0.0",
40
- lifespan=lifespan,
41
- )
42
-
43
- app.add_middleware(
44
- CORSMiddleware,
45
- allow_origins=["*"],
46
- allow_credentials=True,
47
- allow_methods=["*"],
48
- allow_headers=["*"],
49
- )
50
-
51
-
52
- # ── API Routes ────────────────────────────────────────────────
53
-
54
- @app.post("/api/generate")
55
- async def generate_project(req: GenerateRequest):
56
- """Start a new project generation pipeline."""
57
- session_id = str(uuid.uuid4())[:12]
58
- project_dir = Path(f"/tmp/nexus_projects/{session_id}")
59
- project_dir.mkdir(parents=True, exist_ok=True)
60
- (project_dir / "frontend").mkdir(exist_ok=True)
61
- (project_dir / "backend").mkdir(exist_ok=True)
62
-
63
- state = ProjectState(
64
- session_id=session_id,
65
- user_prompt=req.prompt,
66
- app_type=req.app_type or "saas",
67
- status="queued",
68
- project_dir=str(project_dir),
69
- systems=req.systems or [
70
- "client_portal", "public_landing",
71
- "marketing_cms", "analytics_dashboard", "admin_panel"
72
- ],
73
- )
74
- sessions[session_id] = state
75
- # Pipeline runs in background; client listens via SSE
76
- asyncio.create_task(pipeline_engine.run(state, sessions))
77
- return {"session_id": session_id, "status": "started"}
78
-
79
-
80
- @app.get("/api/stream/{session_id}")
81
- async def stream_events(request: Request, session_id: str):
82
- """SSE endpoint for real-time agent updates."""
83
- if session_id not in sessions:
84
- raise HTTPException(404, "Session not found")
85
-
86
- async def event_generator():
87
- state = sessions[session_id]
88
- last_idx = 0
89
- while True:
90
- if await request.is_disconnected():
91
- break
92
- messages = state.messages[last_idx:]
93
- for msg in messages:
94
- yield {
95
- "event": msg.event_type,
96
- "data": json.dumps(msg.model_dump(), default=str),
97
- }
98
- last_idx = len(state.messages)
99
- if state.status in ("completed", "error"):
100
- yield {
101
- "event": "done",
102
- "data": json.dumps({
103
- "status": state.status,
104
- "session_id": session_id,
105
- }),
106
- }
107
- break
108
- await asyncio.sleep(0.3)
109
-
110
- return EventSourceResponse(event_generator())
111
-
112
-
113
- @app.get("/api/status/{session_id}")
114
- async def get_status(session_id: str):
115
- if session_id not in sessions:
116
- raise HTTPException(404, "Session not found")
117
- state = sessions[session_id]
118
- return {
119
- "session_id": session_id,
120
- "status": state.status,
121
- "current_agent": state.current_agent,
122
- "file_tree": state.file_tree,
123
- "errors": state.errors,
124
- }
125
-
126
-
127
- @app.get("/api/files/{session_id}")
128
- async def get_files(session_id: str):
129
- """Return the generated file tree with contents."""
130
- if session_id not in sessions:
131
- raise HTTPException(404, "Session not found")
132
- state = sessions[session_id]
133
- return {"files": state.generated_files}
134
-
135
-
136
- @app.get("/api/file/{session_id}/{path:path}")
137
- async def get_file_content(session_id: str, path: str):
138
- if session_id not in sessions:
139
- raise HTTPException(404, "Session not found")
140
- state = sessions[session_id]
141
- content = state.generated_files.get(path)
142
- if content is None:
143
- raise HTTPException(404, "File not found")
144
- return {"path": path, "content": content}
145
-
146
-
147
- @app.post("/api/fix/{session_id}")
148
- async def fix_bug(session_id: str, req: FixRequest):
149
- """Send a bug report through the pipeline for targeted fixing."""
150
- if session_id not in sessions:
151
- raise HTTPException(404, "Session not found")
152
- state = sessions[session_id]
153
- state.status = "fixing"
154
- asyncio.create_task(pipeline_engine.fix(state, req.error_message, req.file_path))
155
- return {"status": "fix_started"}
156
-
157
-
158
- @app.get("/api/export/{session_id}")
159
- async def export_project(session_id: str):
160
- """Export the generated project as a ZIP file."""
161
- if session_id not in sessions:
162
- raise HTTPException(404, "Session not found")
163
- state = sessions[session_id]
164
- zip_path = Path(f"/tmp/nexus_projects/{session_id}.zip")
165
- with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
166
- for file_path, content in state.generated_files.items():
167
- zf.writestr(file_path, content)
168
- return FileResponse(
169
- zip_path,
170
- filename=f"nexus-project-{session_id}.zip",
171
- media_type="application/zip",
172
- )
173
-
174
-
175
- @app.get("/api/preview/{session_id}")
176
- async def preview_landing(session_id: str):
177
- """Serve a combined preview HTML of the generated app."""
178
- if session_id not in sessions:
179
- raise HTTPException(404, "Session not found")
180
- state = sessions[session_id]
181
- # Look for index.html in generated files
182
- for key in [
183
- "frontend/index.html",
184
- "public_landing/index.html",
185
- "client_portal/index.html",
186
- "index.html",
187
- ]:
188
- if key in state.generated_files:
189
- return HTMLResponse(state.generated_files[key])
190
- # Build a combined preview
191
- html = _build_combined_preview(state)
192
- return HTMLResponse(html)
193
-
194
-
195
- @app.get("/api/preview/{session_id}/{system}")
196
- async def preview_system(session_id: str, system: str):
197
- if session_id not in sessions:
198
- raise HTTPException(404, "Session not found")
199
- state = sessions[session_id]
200
- key = f"{system}/index.html"
201
- if key in state.generated_files:
202
- return HTMLResponse(state.generated_files[key])
203
- html = _build_system_preview(state, system)
204
- return HTMLResponse(html)
205
-
206
-
207
- def _build_combined_preview(state: ProjectState) -> str:
208
- pages = []
209
- for path, content in state.generated_files.items():
210
- if path.endswith(".html"):
211
- pages.append(f"<!-- {path} -->\n{content}")
212
- if not pages:
213
- return "<html><body style='background:#0A0A0F;color:#F0F0FF;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh'><h1>⏳ Preview will appear here once generation completes</h1></body></html>"
214
- return pages[0]
215
-
216
-
217
- def _build_system_preview(state: ProjectState, system: str) -> str:
218
- system_files = {
219
- k: v for k, v in state.generated_files.items()
220
- if k.startswith(f"{system}/")
221
- }
222
- for path, content in system_files.items():
223
- if path.endswith(".html"):
224
- return content
225
- return f"<html><body style='background:#0A0A0F;color:#F0F0FF;font-family:sans-serif;padding:40px'><h1>System: {system}</h1><p>No preview available yet.</p></body></html>"
226
-
227
-
228
- @app.get("/api/health")
229
- async def health():
230
- return {"status": "ok", "models": settings.model_ids}
231
-
232
-
233
- # ── Serve Frontend ────────────────────────────────────────────
234
- static_dir = Path("/app/static")
235
- if static_dir.exists():
236
- app.mount("/assets", StaticFiles(directory=static_dir / "assets"), name="assets")
237
-
238
- @app.get("/{path:path}")
239
- async def serve_frontend(path: str):
240
- file_path = static_dir / path
241
- if file_path.is_file():
242
- return FileResponse(file_path)
243
- return FileResponse(static_dir / "index.html")
244
- else:
245
- @app.get("/")
246
- async def root():
247
- return HTMLResponse(
248
- "<h1>Nexus Builder</h1><p>Frontend not built. Run npm build in /frontend</p>"
249
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/models/__init__.py DELETED
@@ -1 +0,0 @@
1
- undefined
 
 
app/models/schemas.py DELETED
@@ -1,43 +0,0 @@
1
- from __future__ import annotations
2
- from pydantic import BaseModel, Field
3
- from typing import Optional
4
- from datetime import datetime
5
-
6
-
7
- class GenerateRequest(BaseModel):
8
- prompt: str
9
- app_type: Optional[str] = "saas"
10
- systems: Optional[list[str]] = None
11
-
12
-
13
- class FixRequest(BaseModel):
14
- error_message: str
15
- file_path: Optional[str] = None
16
-
17
-
18
- class AgentMessage(BaseModel):
19
- event_type: str # agent_start, token, code_block, agent_done, error, file_created
20
- agent: str # research, orchestrator, frontend, backend, system
21
- content: str = ""
22
- file_path: Optional[str] = None
23
- timestamp: datetime = Field(default_factory=datetime.utcnow)
24
- metadata: dict = Field(default_factory=dict)
25
-
26
-
27
- class ProjectState(BaseModel):
28
- session_id: str
29
- user_prompt: str
30
- app_type: str = "saas"
31
- status: str = "queued" # queued, researching, orchestrating, building_frontend, building_backend, merging, completed, error, fixing
32
- current_agent: str = ""
33
- project_dir: str = ""
34
- systems: list[str] = Field(default_factory=list)
35
- messages: list[AgentMessage] = Field(default_factory=list)
36
- generated_files: dict[str, str] = Field(default_factory=dict)
37
- file_tree: list[str] = Field(default_factory=list)
38
- errors: list[str] = Field(default_factory=list)
39
- research_output: dict = Field(default_factory=dict)
40
- blueprint: dict = Field(default_factory=dict)
41
-
42
- class Config:
43
- arbitrary_types_allowed = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/pipeline/__init__.py DELETED
@@ -1 +0,0 @@
1
- undefined
 
 
app/pipeline/engine.py DELETED
@@ -1,555 +0,0 @@
1
- import json
2
- import re
3
- import asyncio
4
- import traceback
5
- from datetime import datetime
6
-
7
- from app.agents import (
8
- ResearchAgent, OrchestratorAgent, FrontendAgent, BackendAgent
9
- )
10
- from app.models.schemas import ProjectState, AgentMessage
11
-
12
-
13
- class PipelineEngine:
14
- """Orchestrates the 4-agent pipeline for project generation."""
15
-
16
- def __init__(self):
17
- self.research_agent = ResearchAgent()
18
- self.orchestrator_agent = OrchestratorAgent()
19
- self.frontend_agent = FrontendAgent()
20
- self.backend_agent = BackendAgent()
21
-
22
- def _emit(self, state: ProjectState, event_type: str, agent: str,
23
- content: str = "", file_path: str | None = None, metadata: dict | None = None):
24
- msg = AgentMessage(
25
- event_type=event_type,
26
- agent=agent,
27
- content=content,
28
- file_path=file_path,
29
- metadata=metadata or {},
30
- )
31
- state.messages.append(msg)
32
-
33
- async def run(self, state: ProjectState, sessions: dict):
34
- """Execute the full 4-agent pipeline."""
35
- try:
36
- # ── Phase 1: Research ──────────────────────────────
37
- state.status = "researching"
38
- state.current_agent = "research"
39
- self._emit(state, "agent_start", "research",
40
- "🔍 Starting research phase...")
41
-
42
- research_output = await self._run_research(state)
43
- state.research_output = research_output
44
-
45
- self._emit(state, "agent_done", "research",
46
- "✅ Research complete",
47
- metadata={"summary": str(research_output)[:500]})
48
-
49
- # ── Phase 2: Orchestration ─────────────────────────
50
- state.status = "orchestrating"
51
- state.current_agent = "orchestrator"
52
- self._emit(state, "agent_start", "orchestrator",
53
- "🧠 Creating master blueprint...")
54
-
55
- blueprint = await self._run_orchestrator(state)
56
- state.blueprint = blueprint
57
-
58
- self._emit(state, "agent_done", "orchestrator",
59
- "✅ Blueprint created",
60
- metadata={"systems": list(blueprint.get("systems", {}).keys())})
61
-
62
- # ── Phase 3 & 4: Frontend + Backend (parallel) ─────
63
- state.status = "building"
64
- self._emit(state, "agent_start", "system",
65
- "🚀 Starting parallel code generation...")
66
-
67
- state.current_agent = "frontend+backend"
68
- await asyncio.gather(
69
- self._run_frontend(state),
70
- self._run_backend(state),
71
- )
72
-
73
- # ── Phase 5: Merge & Finalize ──────────────────────
74
- state.status = "merging"
75
- state.current_agent = "system"
76
- self._emit(state, "agent_start", "system",
77
- "📦 Merging outputs and building preview...")
78
-
79
- self._build_file_tree(state)
80
- self._generate_combined_preview(state)
81
-
82
- state.status = "completed"
83
- state.current_agent = ""
84
- self._emit(state, "agent_done", "system",
85
- f"🎉 Project complete! {len(state.generated_files)} files generated.")
86
-
87
- except Exception as e:
88
- state.status = "error"
89
- state.errors.append(str(e))
90
- self._emit(state, "error", state.current_agent or "system",
91
- f"❌ Error: {str(e)}",
92
- metadata={"traceback": traceback.format_exc()})
93
-
94
- async def _run_research(self, state: ProjectState) -> dict:
95
- prompt = f"""Research the following app idea and produce a comprehensive technical report:
96
-
97
- APP IDEA: {state.user_prompt}
98
- APP TYPE: {state.app_type}
99
- REQUIRED SYSTEMS: {', '.join(state.systems)}
100
-
101
- Produce your report as a JSON object with keys: stack_recommendation, schema_hints, api_docs_summary, security_notes, hosting_notes, ui_patterns, competitor_analysis"""
102
-
103
- full_response = []
104
- async for token in self.research_agent.call(
105
- [{"role": "user", "content": prompt}], stream=True
106
- ):
107
- full_response.append(token)
108
- if len(full_response) % 20 == 0:
109
- self._emit(state, "token", "research", token)
110
-
111
- text = "".join(full_response)
112
- self._emit(state, "token", "research", "\n[Research output received]")
113
-
114
- # Parse JSON from response
115
- return self._extract_json(text)
116
-
117
- async def _run_orchestrator(self, state: ProjectState) -> dict:
118
- prompt = f"""Create a master blueprint for this application:
119
-
120
- USER REQUEST: {state.user_prompt}
121
- APP TYPE: {state.app_type}
122
- SYSTEMS TO BUILD: {', '.join(state.systems)}
123
-
124
- RESEARCH DATA:
125
- {json.dumps(state.research_output, indent=2, default=str)[:8000]}
126
-
127
- Generate the complete master blueprint as a JSON object following the exact structure from your instructions. Customize everything for this specific app idea."""
128
-
129
- full_response = []
130
- async for token in self.orchestrator_agent.call(
131
- [{"role": "user", "content": prompt}], stream=True
132
- ):
133
- full_response.append(token)
134
- if len(full_response) % 15 == 0:
135
- self._emit(state, "token", "orchestrator", token)
136
-
137
- text = "".join(full_response)
138
- return self._extract_json(text)
139
-
140
- async def _run_frontend(self, state: ProjectState):
141
- self._emit(state, "agent_start", "frontend",
142
- "🎨 Generating frontend code...")
143
-
144
- for system in state.systems:
145
- system_blueprint = state.blueprint.get("systems", {}).get(system, {})
146
- design_tokens = state.blueprint.get("design_tokens", {})
147
- payment_config = state.blueprint.get("payment_config", {})
148
-
149
- prompt = f"""Generate the complete frontend code for the "{system}" system.
150
-
151
- SYSTEM BLUEPRINT:
152
- {json.dumps(system_blueprint, indent=2, default=str)}
153
-
154
- DESIGN TOKENS:
155
- {json.dumps(design_tokens, indent=2, default=str)}
156
-
157
- PAYMENT CONFIG:
158
- {json.dumps(payment_config, indent=2, default=str)}
159
-
160
- DATABASE SCHEMA (for reference):
161
- {json.dumps(state.blueprint.get('database_schema', {}), indent=2, default=str)[:4000]}
162
-
163
- Generate ALL files needed for this system. Use React + Tailwind CSS.
164
- Include: all page components, shared components, routing, Supabase client setup, theme system.
165
- Mark each file with: // FILE: {system}/src/ComponentName.jsx
166
-
167
- The app should look premium with the specified dark mode colors. Include animations and responsive design."""
168
-
169
- full_response = []
170
- async for token in self.frontend_agent.call(
171
- [{"role": "user", "content": prompt}], stream=True
172
- ):
173
- full_response.append(token)
174
- if len(full_response) % 10 == 0:
175
- self._emit(state, "token", "frontend", token)
176
-
177
- text = "".join(full_response)
178
- files = self._extract_files(text)
179
-
180
- for path, content in files.items():
181
- state.generated_files[path] = content
182
- self._emit(state, "file_created", "frontend",
183
- f"Created {path}", file_path=path)
184
-
185
- self._emit(state, "agent_done", "frontend",
186
- f"✅ Frontend generation complete")
187
-
188
- async def _run_backend(self, state: ProjectState):
189
- self._emit(state, "agent_start", "backend",
190
- "🔐 Generating backend code...")
191
-
192
- db_schema = state.blueprint.get("database_schema", {})
193
- api_endpoints = state.blueprint.get("api_endpoints", [])
194
- auth_config = state.blueprint.get("auth_config", {})
195
- payment_config = state.blueprint.get("payment_config", {})
196
-
197
- prompt = f"""Generate the complete backend code for this application.
198
-
199
- DATABASE SCHEMA:
200
- {json.dumps(db_schema, indent=2, default=str)}
201
-
202
- API ENDPOINTS:
203
- {json.dumps(api_endpoints, indent=2, default=str)}
204
-
205
- AUTH CONFIG:
206
- {json.dumps(auth_config, indent=2, default=str)}
207
-
208
- PAYMENT CONFIG:
209
- {json.dumps(payment_config, indent=2, default=str)}
210
-
211
- APP DESCRIPTION: {state.user_prompt}
212
-
213
- Generate ALL files:
214
- 1. Complete Supabase SQL schema (CREATE TABLE, RLS, triggers, indexes)
215
- 2. FastAPI backend with all routes, auth middleware, PayPal integration
216
- 3. Supabase Edge Functions (TypeScript)
217
- 4. Docker configuration (Dockerfile, docker-compose.yml)
218
- 5. Firebase hosting config
219
- 6. .env.example
220
- 7. DEPLOY.md with step-by-step deployment guide
221
-
222
- Mark each file clearly with: # FILE: backend/filename.py or -- FILE: database/schema.sql"""
223
-
224
- full_response = []
225
- async for token in self.backend_agent.call(
226
- [{"role": "user", "content": prompt}], stream=True
227
- ):
228
- full_response.append(token)
229
- if len(full_response) % 10 == 0:
230
- self._emit(state, "token", "backend", token)
231
-
232
- text = "".join(full_response)
233
- files = self._extract_files(text)
234
-
235
- for path, content in files.items():
236
- state.generated_files[path] = content
237
- self._emit(state, "file_created", "backend",
238
- f"Created {path}", file_path=path)
239
-
240
- self._emit(state, "agent_done", "backend",
241
- "✅ Backend generation complete")
242
-
243
- async def fix(self, state: ProjectState, error_message: str,
244
- file_path: str | None = None):
245
- """Run a targeted bug fix through the appropriate agent."""
246
- self._emit(state, "agent_start", "backend",
247
- f"🔧 Fixing bug: {error_message[:100]}...")
248
-
249
- relevant_code = ""
250
- if file_path and file_path in state.generated_files:
251
- relevant_code = state.generated_files[file_path]
252
-
253
- prompt = f"""FIX THIS BUG:
254
-
255
- ERROR: {error_message}
256
-
257
- {"FILE: " + file_path if file_path else ""}
258
- {"CODE:" + chr(10) + relevant_code[:8000] if relevant_code else ""}
259
-
260
- Provide the COMPLETE fixed file content. Mark it with the original file path."""
261
-
262
- # Determine which agent to use based on file type
263
- if file_path and any(file_path.endswith(ext) for ext in [".jsx", ".tsx", ".css", ".html"]):
264
- agent = self.frontend_agent
265
- agent_name = "frontend"
266
- else:
267
- agent = self.backend_agent
268
- agent_name = "backend"
269
-
270
- full_response = []
271
- async for token in agent.call(
272
- [{"role": "user", "content": prompt}], stream=True
273
- ):
274
- full_response.append(token)
275
-
276
- text = "".join(full_response)
277
- files = self._extract_files(text)
278
-
279
- for path, content in files.items():
280
- state.generated_files[path] = content
281
- self._emit(state, "file_created", agent_name,
282
- f"Fixed {path}", file_path=path)
283
-
284
- state.status = "completed"
285
- self._emit(state, "agent_done", agent_name, "✅ Bug fix applied")
286
-
287
- def _extract_json(self, text: str) -> dict:
288
- """Extract JSON from a model response that may contain markdown."""
289
- # Try to find JSON in code blocks
290
- patterns = [
291
- r'```json\s*([\s\S]*?)\s*```',
292
- r'```\s*([\s\S]*?)\s*```',
293
- r'\{[\s\S]*\}',
294
- ]
295
- for pattern in patterns:
296
- matches = re.findall(pattern, text)
297
- for match in matches:
298
- try:
299
- return json.loads(match)
300
- except json.JSONDecodeError:
301
- continue
302
-
303
- # If no valid JSON found, create a minimal structure
304
- return {
305
- "project_name": "generated-app",
306
- "description": text[:200],
307
- "systems": {
308
- "client_portal": {"pages": [{"path": "/dashboard", "title": "Dashboard", "components": ["Layout", "Stats"], "auth_required": True}], "features": ["auth", "dashboard"]},
309
- "public_landing": {"pages": [{"path": "/", "title": "Home", "components": ["Hero", "Features", "Pricing"], "auth_required": False}], "features": ["hero", "pricing"]},
310
- "marketing_cms": {"pages": [{"path": "/blog", "title": "Blog", "components": ["BlogList", "Editor"], "auth_required": True}], "features": ["blog"]},
311
- "analytics_dashboard": {"pages": [{"path": "/analytics", "title": "Analytics", "components": ["Charts", "Metrics"], "auth_required": True}], "features": ["charts"]},
312
- "admin_panel": {"pages": [{"path": "/admin", "title": "Admin", "components": ["UserTable", "Settings"], "auth_required": True}], "features": ["user_management"]},
313
- },
314
- "database_schema": {"tables": []},
315
- "api_endpoints": [],
316
- "auth_config": {"providers": ["email"]},
317
- "payment_config": {"provider": "paypal", "plans": []},
318
- "design_tokens": {
319
- "colors": {"primary": "#6C63FF", "secondary": "#00D9FF", "background": "#0A0A0F", "surface": "#111118", "text": "#F0F0FF"},
320
- "fonts": {"heading": "Inter", "body": "Inter", "mono": "JetBrains Mono"},
321
- },
322
- }
323
-
324
- def _extract_files(self, text: str) -> dict[str, str]:
325
- """Extract file contents from model output marked with FILE: comments."""
326
- files = {}
327
- # Match patterns like: // FILE: path/to/file.jsx or # FILE: path/to/file.py or -- FILE: path/to/file.sql
328
- pattern = r'(?://|#|--)\s*FILE:\s*(.+?)(?:\n)([\s\S]*?)(?=(?://|#|--)\s*FILE:|$)'
329
- matches = re.findall(pattern, text)
330
-
331
- if matches:
332
- for file_path, content in matches:
333
- path = file_path.strip()
334
- # Clean content — remove trailing code block markers
335
- content = re.sub(r'\s*```\s*$', '', content.strip())
336
- content = re.sub(r'^```\w*\s*', '', content.strip())
337
- files[path] = content.strip()
338
- else:
339
- # Fallback: try to extract from code blocks with filenames
340
- code_blocks = re.findall(
341
- r'(?:#+\s*)?(?:`([^`]+)`|(\S+\.\w+))\s*\n```\w*\n([\s\S]*?)```',
342
- text
343
- )
344
- for name1, name2, content in code_blocks:
345
- name = name1 or name2
346
- if name and content.strip():
347
- files[name.strip()] = content.strip()
348
-
349
- # If still no files extracted, save as raw output
350
- if not files:
351
- if "def " in text or "import " in text:
352
- files["backend/generated_output.py"] = text
353
- elif "function " in text or "const " in text or "import " in text:
354
- files["frontend/generated_output.jsx"] = text
355
- elif "CREATE TABLE" in text.upper():
356
- files["database/schema.sql"] = text
357
- else:
358
- files["output/raw_output.txt"] = text
359
-
360
- return files
361
-
362
- def _build_file_tree(self, state: ProjectState):
363
- """Build a sorted file tree from generated files."""
364
- state.file_tree = sorted(state.generated_files.keys())
365
-
366
- def _generate_combined_preview(self, state: ProjectState):
367
- """Generate a combined HTML preview page for the app."""
368
- design = state.blueprint.get("design_tokens", {})
369
- colors = design.get("colors", {})
370
- project_name = state.blueprint.get("project_name", "Generated App")
371
- systems = state.blueprint.get("systems", {})
372
-
373
- system_cards = ""
374
- for sys_name, sys_data in systems.items():
375
- pages = sys_data.get("pages", [])
376
- features = sys_data.get("features", [])
377
- page_list = "".join(
378
- f'<li>{p.get("title", p.get("path", "Page"))}</li>'
379
- for p in pages[:5]
380
- )
381
- feature_list = "".join(f'<span class="tag">{f}</span>' for f in features[:6])
382
- nice_name = sys_name.replace("_", " ").title()
383
- system_cards += f"""
384
- <div class="system-card">
385
- <h3>{nice_name}</h3>
386
- <div class="tags">{feature_list}</div>
387
- <ul>{page_list}</ul>
388
- <div class="page-count">{len(pages)} pages</div>
389
- </div>"""
390
-
391
- file_count = len(state.generated_files)
392
- file_types = {}
393
- for f in state.generated_files:
394
- ext = f.rsplit(".", 1)[-1] if "." in f else "other"
395
- file_types[ext] = file_types.get(ext, 0) + 1
396
- file_stats = " • ".join(f"{count} .{ext}" for ext, count in sorted(file_types.items()))
397
-
398
- preview_html = f"""<!DOCTYPE html>
399
- <html lang="en">
400
- <head>
401
- <meta charset="UTF-8">
402
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
403
- <title>{project_name} — Preview</title>
404
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
405
- <style>
406
- * {{ margin: 0; padding: 0; box-sizing: border-box; }}
407
- body {{
408
- font-family: 'Inter', sans-serif;
409
- background: {colors.get('background', '#0A0A0F')};
410
- color: {colors.get('text', '#F0F0FF')};
411
- min-height: 100vh;
412
- padding: 2rem;
413
- }}
414
- .container {{ max-width: 1200px; margin: 0 auto; }}
415
- .hero {{
416
- text-align: center;
417
- padding: 4rem 2rem;
418
- background: linear-gradient(135deg, {colors.get('surface', '#111118')} 0%, {colors.get('background', '#0A0A0F')} 100%);
419
- border-radius: 24px;
420
- border: 1px solid #1E1E2E;
421
- margin-bottom: 2rem;
422
- }}
423
- .hero h1 {{
424
- font-size: 3rem;
425
- font-weight: 700;
426
- background: linear-gradient(135deg, {colors.get('primary', '#6C63FF')}, {colors.get('secondary', '#00D9FF')});
427
- -webkit-background-clip: text;
428
- -webkit-text-fill-color: transparent;
429
- margin-bottom: 1rem;
430
- }}
431
- .hero p {{ color: #8888AA; font-size: 1.2rem; max-width: 600px; margin: 0 auto; }}
432
- .stats {{
433
- display: flex;
434
- gap: 1rem;
435
- justify-content: center;
436
- margin-top: 2rem;
437
- flex-wrap: wrap;
438
- }}
439
- .stat {{
440
- background: #111118;
441
- border: 1px solid #1E1E2E;
442
- border-radius: 12px;
443
- padding: 1rem 1.5rem;
444
- text-align: center;
445
- }}
446
- .stat-value {{
447
- font-size: 2rem;
448
- font-weight: 700;
449
- color: {colors.get('primary', '#6C63FF')};
450
- }}
451
- .stat-label {{ color: #8888AA; font-size: 0.85rem; margin-top: 0.25rem; }}
452
- .systems-grid {{
453
- display: grid;
454
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
455
- gap: 1.5rem;
456
- margin-top: 2rem;
457
- }}
458
- .system-card {{
459
- background: #111118;
460
- border: 1px solid #1E1E2E;
461
- border-radius: 16px;
462
- padding: 1.5rem;
463
- transition: all 0.3s ease;
464
- }}
465
- .system-card:hover {{
466
- border-color: {colors.get('primary', '#6C63FF')}44;
467
- transform: translateY(-2px);
468
- box-shadow: 0 8px 32px {colors.get('primary', '#6C63FF')}11;
469
- }}
470
- .system-card h3 {{
471
- font-size: 1.2rem;
472
- font-weight: 600;
473
- margin-bottom: 0.75rem;
474
- color: {colors.get('text', '#F0F0FF')};
475
- }}
476
- .tags {{ display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 1rem; }}
477
- .tag {{
478
- background: {colors.get('primary', '#6C63FF')}22;
479
- color: {colors.get('primary', '#6C63FF')};
480
- padding: 0.2rem 0.6rem;
481
- border-radius: 6px;
482
- font-size: 0.75rem;
483
- font-weight: 500;
484
- }}
485
- ul {{ list-style: none; margin-bottom: 0.75rem; }}
486
- li {{
487
- padding: 0.3rem 0;
488
- color: #8888AA;
489
- font-size: 0.9rem;
490
- border-bottom: 1px solid #1E1E2E;
491
- }}
492
- li:last-child {{ border-bottom: none; }}
493
- .page-count {{
494
- color: {colors.get('secondary', '#00D9FF')};
495
- font-size: 0.8rem;
496
- font-weight: 500;
497
- }}
498
- .file-info {{
499
- text-align: center;
500
- margin-top: 2rem;
501
- padding: 1rem;
502
- color: #8888AA;
503
- font-family: 'JetBrains Mono', monospace;
504
- font-size: 0.85rem;
505
- }}
506
- .section-title {{
507
- font-size: 1.5rem;
508
- font-weight: 600;
509
- margin-bottom: 0.5rem;
510
- }}
511
- @keyframes fadeIn {{
512
- from {{ opacity: 0; transform: translateY(10px); }}
513
- to {{ opacity: 1; transform: translateY(0); }}
514
- }}
515
- .system-card {{ animation: fadeIn 0.5s ease forwards; }}
516
- .system-card:nth-child(2) {{ animation-delay: 0.1s; }}
517
- .system-card:nth-child(3) {{ animation-delay: 0.2s; }}
518
- .system-card:nth-child(4) {{ animation-delay: 0.3s; }}
519
- .system-card:nth-child(5) {{ animation-delay: 0.4s; }}
520
- </style>
521
- </head>
522
- <body>
523
- <div class="container">
524
- <div class="hero">
525
- <h1>{project_name}</h1>
526
- <p>{state.user_prompt[:200]}</p>
527
- <div class="stats">
528
- <div class="stat">
529
- <div class="stat-value">{len(systems)}</div>
530
- <div class="stat-label">Systems</div>
531
- </div>
532
- <div class="stat">
533
- <div class="stat-value">{file_count}</div>
534
- <div class="stat-label">Files Generated</div>
535
- </div>
536
- <div class="stat">
537
- <div class="stat-value">{sum(len(s.get('pages',[])) for s in systems.values())}</div>
538
- <div class="stat-label">Total Pages</div>
539
- </div>
540
- </div>
541
- </div>
542
-
543
- <h2 class="section-title">Generated Systems</h2>
544
- <div class="systems-grid">
545
- {system_cards}
546
- </div>
547
-
548
- <div class="file-info">
549
- {file_stats}
550
- </div>
551
- </div>
552
- </body>
553
- </html>"""
554
-
555
- state.generated_files["preview/index.html"] = preview_html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/preview/__init__.py DELETED
@@ -1 +0,0 @@
1
- undefined
 
 
app/preview/builder.py DELETED
@@ -1,45 +0,0 @@
1
- """Preview builder utilities — generates live previews of generated apps."""
2
-
3
- import subprocess
4
- from pathlib import Path
5
-
6
-
7
- def build_frontend_preview(project_dir: str, session_id: str) -> str | None:
8
- """Attempt to build a frontend project for live preview.
9
-
10
- Returns the path to built files, or None if build is not possible
11
- (e.g., no package.json, or npm not available on CPU tier).
12
- """
13
- frontend_dir = Path(project_dir) / "frontend"
14
- package_json = frontend_dir / "package.json"
15
-
16
- if not package_json.exists():
17
- return None
18
-
19
- try:
20
- # Install deps
21
- subprocess.run(
22
- ["npm", "install"],
23
- cwd=str(frontend_dir),
24
- timeout=120,
25
- capture_output=True,
26
- )
27
- # Build
28
- result = subprocess.run(
29
- ["npm", "run", "build"],
30
- cwd=str(frontend_dir),
31
- timeout=120,
32
- capture_output=True,
33
- )
34
- if result.returncode == 0:
35
- dist_dir = frontend_dir / "dist"
36
- if dist_dir.exists():
37
- preview_dir = f"/tmp/nexus_previews/{session_id}"
38
- Path(preview_dir).mkdir(parents=True, exist_ok=True)
39
- import shutil
40
- shutil.copytree(str(dist_dir), preview_dir, dirs_exist_ok=True)
41
- return preview_dir
42
- except (subprocess.TimeoutExpired, FileNotFoundError):
43
- pass
44
-
45
- return None