Initial commit: prompt template manager
React + Tailwind frontend, FastAPI backend, settings.json storage. Supports preset CRUD, live variable filling, and AI rewrite via OpenAI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+120
@@ -0,0 +1,120 @@
|
||||
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")
|
||||
Reference in New Issue
Block a user