Delete app
Browse files- app/agents/__init__.py +0 -6
- app/agents/backend_gen.py +0 -67
- app/agents/base.py +0 -91
- app/agents/frontend_gen.py +0 -60
- app/agents/orchestrator.py +0 -100
- app/agents/research.py +0 -31
- app/config.py +0 -29
- app/main.py +0 -249
- app/models/__init__.py +0 -1
- app/models/schemas.py +0 -43
- app/pipeline/__init__.py +0 -1
- app/pipeline/engine.py +0 -555
- app/preview/__init__.py +0 -1
- app/preview/builder.py +0 -45
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|