mini-claw / app.py
YidingQiu's picture
add real files, token chart, system prompt, loop viz, sub-agents
dab0410
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")