Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, WebSocket, WebSocketDisconnect | |
| from fastapi.middleware.cors import CORSMiddleware | |
| import json | |
| import asyncio | |
| import os | |
| import uuid | |
| import shutil | |
| from runner import ProcessRunner | |
| app = FastAPI() | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| PROJECTS_DIR = os.path.join(os.path.dirname(__file__), "projects") | |
| if not os.path.exists(PROJECTS_DIR): | |
| os.makedirs(PROJECTS_DIR) | |
| active_runners = {} | |
| def cleanup_old_projects(): | |
| try: | |
| projects = [] | |
| for name in os.listdir(PROJECTS_DIR): | |
| path = os.path.join(PROJECTS_DIR, name) | |
| if os.path.isdir(path): | |
| mtime = os.stat(path).st_mtime | |
| projects.append((mtime, name, path)) | |
| # Sort by modification time, newest first | |
| projects.sort(reverse=True) | |
| # Keep the 4 newest, delete the rest so we have max 5 after creating a new one | |
| for _, name, path in projects[4:]: | |
| if name not in active_runners: | |
| shutil.rmtree(path, ignore_errors=True) | |
| except Exception as e: | |
| print(f"Error cleaning up projects: {e}") | |
| async def websocket_endpoint(websocket: WebSocket): | |
| await websocket.accept() | |
| cleanup_old_projects() | |
| project_id = str(uuid.uuid4()) | |
| runner = ProcessRunner(project_id, PROJECTS_DIR) | |
| active_runners[project_id] = runner | |
| try: | |
| while True: | |
| data = await websocket.receive_text() | |
| payload = json.loads(data) | |
| files = payload.get("files", []) | |
| if not files: | |
| await websocket.send_text(json.dumps({"type": "error", "message": "No files provided"})) | |
| continue | |
| async def log_callback(msg: str): | |
| await websocket.send_text(json.dumps({"type": "log", "content": msg})) | |
| await log_callback(f"✦ Preparing environment for project {project_id}...\n") | |
| await runner.setup(files) | |
| await log_callback("✦ Synchronizing dependencies...\n") | |
| await runner.install_dependencies(log_callback) | |
| await log_callback("🚀 Launching application...\n") | |
| await runner.run(log_callback) | |
| # Keep connection open and monitor for timeout or closure | |
| # In a real production app, we'd have more complex process management here | |
| try: | |
| # Wait for disconnect or some signal from client | |
| while True: | |
| await asyncio.wait_for(websocket.receive_text(), timeout=3600) | |
| except asyncio.TimeoutError: | |
| await log_callback("\n[System] Session heartbeat timeout (1 hour).\n") | |
| except WebSocketDisconnect: | |
| print(f"Client disconnected: {project_id}") | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| try: | |
| await websocket.send_text(json.dumps({"type": "error", "message": str(e)})) | |
| except: | |
| pass | |
| finally: | |
| await runner.stop() | |
| # runner.cleanup() # Optional: keep files for debugging or clean them up | |
| if project_id in active_runners: | |
| del active_runners[project_id] | |
| if __name__ == "__main__": | |
| import uvicorn | |
| # Hugging Face Spaces expose port 7860 by default | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |