Bluestrikeai commited on
Commit
409ebb3
Β·
verified Β·
1 Parent(s): b724e07

Create main.py

Browse files
Files changed (1) hide show
  1. main.py +194 -0
main.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import uuid
3
+ import asyncio
4
+ import zipfile
5
+ from pathlib import Path
6
+ from contextlib import asynccontextmanager
7
+
8
+ from fastapi import FastAPI, Request, HTTPException
9
+ from fastapi.responses import (
10
+ HTMLResponse, JSONResponse, FileResponse, StreamingResponse,
11
+ )
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+ from sse_starlette.sse import EventSourceResponse
14
+
15
+ from config import settings
16
+ from schemas import GenerateRequest, FixRequest, ProjectState
17
+ from pipeline import PipelineEngine
18
+
19
+ # ─── state ────────────────────────────────────────────────
20
+ sessions: dict[str, ProjectState] = {}
21
+ engine = PipelineEngine()
22
+
23
+
24
+ @asynccontextmanager
25
+ async def lifespan(app: FastAPI):
26
+ Path("/tmp/nexus_projects").mkdir(parents=True, exist_ok=True)
27
+ yield
28
+
29
+
30
+ app = FastAPI(title="Nexus Builder", version="1.0.0", lifespan=lifespan)
31
+ app.add_middleware(
32
+ CORSMiddleware,
33
+ allow_origins=["*"],
34
+ allow_credentials=True,
35
+ allow_methods=["*"],
36
+ allow_headers=["*"],
37
+ )
38
+
39
+ DEFAULT_SYSTEMS = [
40
+ "client_portal",
41
+ "public_landing",
42
+ "marketing_cms",
43
+ "analytics_dashboard",
44
+ "admin_panel",
45
+ ]
46
+
47
+
48
+ # ═══════════════════════════════════════════════════════════
49
+ # API ROUTES
50
+ # ═══════════════════════════════════════════════════════════
51
+
52
+ @app.post("/api/generate")
53
+ async def generate(req: GenerateRequest):
54
+ sid = str(uuid.uuid4())[:12]
55
+ state = ProjectState(
56
+ session_id=sid,
57
+ user_prompt=req.prompt,
58
+ app_type=req.app_type or "saas",
59
+ status="queued",
60
+ systems=req.systems or DEFAULT_SYSTEMS,
61
+ )
62
+ sessions[sid] = state
63
+ asyncio.create_task(engine.run(state))
64
+ return {"session_id": sid, "status": "started"}
65
+
66
+
67
+ @app.get("/api/stream/{sid}")
68
+ async def stream(request: Request, sid: str):
69
+ if sid not in sessions:
70
+ raise HTTPException(404, "Session not found")
71
+
72
+ async def gen():
73
+ state = sessions[sid]
74
+ cursor = 0
75
+ while True:
76
+ if await request.is_disconnected():
77
+ break
78
+ msgs = state.messages[cursor:]
79
+ for m in msgs:
80
+ yield {
81
+ "event": m.event_type,
82
+ "data": json.dumps(m.model_dump(), default=str),
83
+ }
84
+ cursor = len(state.messages)
85
+ if state.status in ("completed", "error"):
86
+ yield {
87
+ "event": "done",
88
+ "data": json.dumps(
89
+ {"status": state.status, "session_id": sid}
90
+ ),
91
+ }
92
+ break
93
+ await asyncio.sleep(0.25)
94
+
95
+ return EventSourceResponse(gen())
96
+
97
+
98
+ @app.get("/api/status/{sid}")
99
+ async def status(sid: str):
100
+ if sid not in sessions:
101
+ raise HTTPException(404)
102
+ s = sessions[sid]
103
+ return {
104
+ "session_id": sid,
105
+ "status": s.status,
106
+ "current_agent": s.current_agent,
107
+ "file_tree": s.file_tree,
108
+ "errors": s.errors,
109
+ }
110
+
111
+
112
+ @app.get("/api/files/{sid}")
113
+ async def files(sid: str):
114
+ if sid not in sessions:
115
+ raise HTTPException(404)
116
+ return {"files": sessions[sid].generated_files}
117
+
118
+
119
+ @app.get("/api/file/{sid}/{path:path}")
120
+ async def file_content(sid: str, path: str):
121
+ if sid not in sessions:
122
+ raise HTTPException(404)
123
+ c = sessions[sid].generated_files.get(path)
124
+ if c is None:
125
+ raise HTTPException(404, "File not found")
126
+ return {"path": path, "content": c}
127
+
128
+
129
+ @app.post("/api/fix/{sid}")
130
+ async def fix(sid: str, req: FixRequest):
131
+ if sid not in sessions:
132
+ raise HTTPException(404)
133
+ state = sessions[sid]
134
+ state.status = "fixing"
135
+ asyncio.create_task(engine.fix(state, req.error_message, req.file_path))
136
+ return {"status": "fix_started"}
137
+
138
+
139
+ @app.get("/api/export/{sid}")
140
+ async def export(sid: str):
141
+ if sid not in sessions:
142
+ raise HTTPException(404)
143
+ zp = Path(f"/tmp/nexus_projects/{sid}.zip")
144
+ with zipfile.ZipFile(zp, "w", zipfile.ZIP_DEFLATED) as zf:
145
+ for fp, content in sessions[sid].generated_files.items():
146
+ zf.writestr(fp, content)
147
+ return FileResponse(zp, filename=f"nexus-{sid}.zip", media_type="application/zip")
148
+
149
+
150
+ @app.get("/api/preview/{sid}")
151
+ async def preview(sid: str):
152
+ if sid not in sessions:
153
+ raise HTTPException(404)
154
+ s = sessions[sid]
155
+ for k in ("preview/index.html", "frontend/index.html", "index.html"):
156
+ if k in s.generated_files:
157
+ return HTMLResponse(s.generated_files[k])
158
+ return HTMLResponse(
159
+ "<html><body style='background:#0A0A0F;color:#F0F0FF;font-family:sans-serif;"
160
+ "display:flex;align-items:center;justify-content:center;height:100vh'>"
161
+ "<h2>⏳ Preview building…</h2></body></html>"
162
+ )
163
+
164
+
165
+ @app.get("/api/preview/{sid}/{system}")
166
+ async def preview_system(sid: str, system: str):
167
+ if sid not in sessions:
168
+ raise HTTPException(404)
169
+ s = sessions[sid]
170
+ key = f"{system}/index.html"
171
+ if key in s.generated_files:
172
+ return HTMLResponse(s.generated_files[key])
173
+ return HTMLResponse(
174
+ f"<html><body style='background:#0A0A0F;color:#F0F0FF;font-family:sans-serif;padding:40px'>"
175
+ f"<h2>{system.replace('_',' ').title()}</h2><p>No preview yet.</p></body></html>"
176
+ )
177
+
178
+
179
+ @app.get("/api/health")
180
+ async def health():
181
+ return {
182
+ "status": "ok",
183
+ "key_set": bool(settings.OPENROUTER_API_KEY),
184
+ "models": settings.MODEL_IDS,
185
+ }
186
+
187
+
188
+ # ═══════════════════════════════════════════════════════════
189
+ # SERVE FRONTEND (index.html at root)
190
+ # ═══════════════════════════════════════════════════════════
191
+
192
+ @app.get("/")
193
+ async def root():
194
+ return FileResponse("index.html", media_type="text/html")