import os import httpx from pathlib import Path from fastapi import FastAPI, Request, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import JSONResponse from pydantic import BaseModel app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "") SESSIONS_DIR = Path("/tmp/sessions") SESSIONS_DIR.mkdir(parents=True, exist_ok=True) def session_dir(session_id: str) -> Path: safe = "".join(c for c in session_id if c.isalnum() or c == "-")[:64] d = SESSIONS_DIR / safe d.mkdir(exist_ok=True) return d @app.get("/api/files/{session_id}") async def list_files(session_id: str): d = session_dir(session_id) return {"files": sorted(f.name for f in d.iterdir() if f.is_file())} class FileBody(BaseModel): filename: str content: str @app.post("/api/files/{session_id}") async def create_file(session_id: str, body: FileBody): d = session_dir(session_id) name = Path(body.filename).name if not name: raise HTTPException(400, "invalid filename") (d / name).write_text(body.content, encoding="utf-8") return {"ok": True, "filename": name, "bytes": len(body.content.encode())} @app.get("/api/files/{session_id}/{filename}") async def read_file(session_id: str, filename: str): d = session_dir(session_id) name = Path(filename).name f = d / name if not f.exists(): raise HTTPException(404, f"'{name}' not found") return {"content": f.read_text(encoding="utf-8")} @app.delete("/api/files/{session_id}/{filename}") async def delete_file(session_id: str, filename: str): d = session_dir(session_id) name = Path(filename).name f = d / name if not f.exists(): raise HTTPException(404, f"'{name}' not found") f.unlink() return {"ok": True} @app.post("/api/chat") async def chat(req: Request): if not ANTHROPIC_API_KEY: return JSONResponse( status_code=500, content={"error": {"type": "config_error", "message": "ANTHROPIC_API_KEY not set on server."}}, ) payload = await req.json() async with httpx.AsyncClient(timeout=60) as client: try: r = await client.post( "https://api.anthropic.com/v1/messages", headers={ "x-api-key": ANTHROPIC_API_KEY, "anthropic-version": "2023-06-01", "content-type": "application/json", }, json=payload, ) return r.json() except httpx.TimeoutException: return JSONResponse( status_code=504, content={"error": {"type": "timeout", "message": "Request to AI timed out. Try again."}}, ) except Exception as e: return JSONResponse( status_code=502, content={"error": {"type": "proxy_error", "message": str(e)}}, ) @app.get("/api/health") async def health(): return {"status": "ok", "key_set": bool(ANTHROPIC_API_KEY)} app.mount("/", StaticFiles(directory="frontend", html=True), name="frontend")