"""µFrame compile, DSL metadata, and image upload endpoints.""" import os from pathlib import Path from fastapi import APIRouter, HTTPException, UploadFile, File from pydantic import BaseModel import uframe import uframe.keywords # noqa: F401 — triggers keyword registration from uframe.errors import UFrameError from uframe.registry import get_dsl_meta router = APIRouter() UPLOAD_DIR = Path(os.environ.get("SOURCES_DIR", "/data/sources")) / "images" class CompileRequest(BaseModel): source: str width: int = 64 class CompileResponse(BaseModel): ascii: str micron: str script: str is_dynamic: bool warnings: list[str] @router.post("/compile", response_model=CompileResponse) async def compile_source(req: CompileRequest): """Compile µFrame .uf source into ASCII and Micron output.""" try: result = uframe.compile(req.source, width=req.width) return CompileResponse( ascii=result.ascii, micron=result.micron, script=result.script, is_dynamic=result.is_dynamic, warnings=[w.message for w in result.warnings], ) except UFrameError as e: raise HTTPException(status_code=422, detail=str(e)) @router.get("/dsl-meta") async def dsl_meta(): """Return DSL metadata for frontend syntax highlighting and autocomplete.""" return get_dsl_meta() @router.post("/upload-image") async def upload_image(file: UploadFile = File(...)): """Upload an image for use in .uf pages.""" UPLOAD_DIR.mkdir(parents=True, exist_ok=True) # Sanitize filename name = file.filename or "upload.png" safe_name = "".join(c for c in name if c.isalnum() or c in "._-").rstrip(".") if not safe_name: safe_name = "upload.png" dest = UPLOAD_DIR / safe_name content = await file.read() dest.write_bytes(content) # Return the path relative to backend working directory rel_path = str(dest) return {"path": rel_path, "filename": safe_name, "size": len(content)} @router.get("/images") async def list_images(): """List uploaded images available for embedding.""" if not UPLOAD_DIR.is_dir(): return [] images = [] for f in sorted(UPLOAD_DIR.iterdir()): if f.suffix.lower() in (".png", ".jpg", ".jpeg", ".bmp", ".webp", ".gif"): images.append({ "filename": f.name, "path": str(f), "size": f.stat().st_size, }) return images