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:
Space-Banane
2026-06-26 15:38:17 +02:00
commit 20f06d0a57
21 changed files with 3537 additions and 0 deletions
+120
View File
@@ -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")