Bluestrikeai's picture
Create main.py
409ebb3 verified
import json
import uuid
import asyncio
import zipfile
from pathlib import Path
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import (
HTMLResponse, JSONResponse, FileResponse, StreamingResponse,
)
from fastapi.middleware.cors import CORSMiddleware
from sse_starlette.sse import EventSourceResponse
from config import settings
from schemas import GenerateRequest, FixRequest, ProjectState
from pipeline import PipelineEngine
# ─── state ────────────────────────────────────────────────
sessions: dict[str, ProjectState] = {}
engine = PipelineEngine()
@asynccontextmanager
async def lifespan(app: FastAPI):
Path("/tmp/nexus_projects").mkdir(parents=True, exist_ok=True)
yield
app = FastAPI(title="Nexus Builder", version="1.0.0", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
DEFAULT_SYSTEMS = [
"client_portal",
"public_landing",
"marketing_cms",
"analytics_dashboard",
"admin_panel",
]
# ═══════════════════════════════════════════════════════════
# API ROUTES
# ═══════════════════════════════════════════════════════════
@app.post("/api/generate")
async def generate(req: GenerateRequest):
sid = str(uuid.uuid4())[:12]
state = ProjectState(
session_id=sid,
user_prompt=req.prompt,
app_type=req.app_type or "saas",
status="queued",
systems=req.systems or DEFAULT_SYSTEMS,
)
sessions[sid] = state
asyncio.create_task(engine.run(state))
return {"session_id": sid, "status": "started"}
@app.get("/api/stream/{sid}")
async def stream(request: Request, sid: str):
if sid not in sessions:
raise HTTPException(404, "Session not found")
async def gen():
state = sessions[sid]
cursor = 0
while True:
if await request.is_disconnected():
break
msgs = state.messages[cursor:]
for m in msgs:
yield {
"event": m.event_type,
"data": json.dumps(m.model_dump(), default=str),
}
cursor = len(state.messages)
if state.status in ("completed", "error"):
yield {
"event": "done",
"data": json.dumps(
{"status": state.status, "session_id": sid}
),
}
break
await asyncio.sleep(0.25)
return EventSourceResponse(gen())
@app.get("/api/status/{sid}")
async def status(sid: str):
if sid not in sessions:
raise HTTPException(404)
s = sessions[sid]
return {
"session_id": sid,
"status": s.status,
"current_agent": s.current_agent,
"file_tree": s.file_tree,
"errors": s.errors,
}
@app.get("/api/files/{sid}")
async def files(sid: str):
if sid not in sessions:
raise HTTPException(404)
return {"files": sessions[sid].generated_files}
@app.get("/api/file/{sid}/{path:path}")
async def file_content(sid: str, path: str):
if sid not in sessions:
raise HTTPException(404)
c = sessions[sid].generated_files.get(path)
if c is None:
raise HTTPException(404, "File not found")
return {"path": path, "content": c}
@app.post("/api/fix/{sid}")
async def fix(sid: str, req: FixRequest):
if sid not in sessions:
raise HTTPException(404)
state = sessions[sid]
state.status = "fixing"
asyncio.create_task(engine.fix(state, req.error_message, req.file_path))
return {"status": "fix_started"}
@app.get("/api/export/{sid}")
async def export(sid: str):
if sid not in sessions:
raise HTTPException(404)
zp = Path(f"/tmp/nexus_projects/{sid}.zip")
with zipfile.ZipFile(zp, "w", zipfile.ZIP_DEFLATED) as zf:
for fp, content in sessions[sid].generated_files.items():
zf.writestr(fp, content)
return FileResponse(zp, filename=f"nexus-{sid}.zip", media_type="application/zip")
@app.get("/api/preview/{sid}")
async def preview(sid: str):
if sid not in sessions:
raise HTTPException(404)
s = sessions[sid]
for k in ("preview/index.html", "frontend/index.html", "index.html"):
if k in s.generated_files:
return HTMLResponse(s.generated_files[k])
return HTMLResponse(
"<html><body style='background:#0A0A0F;color:#F0F0FF;font-family:sans-serif;"
"display:flex;align-items:center;justify-content:center;height:100vh'>"
"<h2>⏳ Preview building…</h2></body></html>"
)
@app.get("/api/preview/{sid}/{system}")
async def preview_system(sid: str, system: str):
if sid not in sessions:
raise HTTPException(404)
s = sessions[sid]
key = f"{system}/index.html"
if key in s.generated_files:
return HTMLResponse(s.generated_files[key])
return HTMLResponse(
f"<html><body style='background:#0A0A0F;color:#F0F0FF;font-family:sans-serif;padding:40px'>"
f"<h2>{system.replace('_',' ').title()}</h2><p>No preview yet.</p></body></html>"
)
@app.get("/api/health")
async def health():
return {
"status": "ok",
"key_set": bool(settings.OPENROUTER_API_KEY),
"models": settings.MODEL_IDS,
}
# ═══════════════════════════════════════════════════════════
# SERVE FRONTEND (index.html at root)
# ═══════════════════════════════════════════════════════════
@app.get("/")
async def root():
return FileResponse("index.html", media_type="text/html")