import os from pathlib import Path from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from openai import AsyncOpenAI from pydantic import BaseModel import backend.storage as storage from backend.models import extract_vars DIST = Path(__file__).parent.parent / "frontend" / "dist" OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4.1") app = FastAPI() # ── Models ──────────────────────────────────────────────────────────────────── class PresetBody(BaseModel): name: str description: str = "" template: str class SettingsBody(BaseModel): openai_api_key: str class RewriteBody(BaseModel): prompt: str # ── Presets ─────────────────────────────────────────────────────────────────── @app.get("/api/presets") def get_presets(): return {"presets": storage.list_presets()} @app.post("/api/presets", status_code=201) def create_preset(body: PresetBody): variables = extract_vars(body.template) return storage.create_preset(body.name, body.description, body.template, variables) @app.put("/api/presets/{preset_id}") def update_preset(preset_id: str, body: PresetBody): variables = extract_vars(body.template) result = storage.update_preset(preset_id, { "name": body.name, "description": body.description, "template": body.template, "variables": variables, }) if result is None: raise HTTPException(status_code=404, detail="Preset not found") return result @app.delete("/api/presets/{preset_id}") def delete_preset(preset_id: str): if not storage.delete_preset(preset_id): raise HTTPException(status_code=404, detail="Preset not found") return {"ok": True} # ── Settings ────────────────────────────────────────────────────────────────── @app.get("/api/settings") def get_settings(): key = storage.get_openai_key() return {"openai_api_key_set": bool(key)} @app.post("/api/settings") def update_settings(body: SettingsBody): storage.set_openai_key(body.openai_api_key) return {"ok": True} # ── AI Rewrite ──────────────────────────────────────────────────────────────── @app.post("/api/rewrite") async def rewrite_prompt(body: RewriteBody): key = storage.get_openai_key() if not key: raise HTTPException(status_code=400, detail="OpenAI API key not configured") client = AsyncOpenAI(api_key=key) try: response = await client.chat.completions.create( model=OPENAI_MODEL, messages=[ { "role": "system", "content": ( "You are a prompt engineer. Rewrite the user's prompt to be clearer, " "more specific, and more effective for an AI assistant. " "Return only the rewritten prompt, no explanation." ), }, {"role": "user", "content": body.prompt}, ], ) except Exception as e: raise HTTPException(status_code=502, detail=str(e)) return {"rewritten": response.choices[0].message.content} # ── SPA static serving (must be last) ──────────────────────────────────────── if DIST.exists(): app.mount("/assets", StaticFiles(directory=DIST / "assets"), name="assets") @app.get("/{full_path:path}") def serve_spa(full_path: str): return FileResponse(DIST / "index.html")