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
View File
+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")
+13
View File
@@ -0,0 +1,13 @@
import re
VAR_RE = re.compile(r'\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}')
def extract_vars(template: str) -> list[str]:
seen = set()
result = []
for m in VAR_RE.finditer(template):
if m.group(1) not in seen:
seen.add(m.group(1))
result.append(m.group(1))
return result
+73
View File
@@ -0,0 +1,73 @@
import json
import secrets
from datetime import datetime, timezone
from pathlib import Path
SETTINGS_PATH = Path(__file__).parent.parent / "settings.json"
def _now():
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def _read():
if not SETTINGS_PATH.exists():
return {"openai_api_key": "", "presets": []}
return json.loads(SETTINGS_PATH.read_text(encoding="utf-8"))
def _write(data):
tmp = SETTINGS_PATH.with_suffix(".tmp")
tmp.write_text(json.dumps(data, indent=2), encoding="utf-8")
tmp.replace(SETTINGS_PATH)
def list_presets():
return _read()["presets"]
def create_preset(name, description, template, variables):
data = _read()
preset = {
"id": secrets.token_hex(4),
"name": name,
"description": description,
"template": template,
"variables": variables,
"created_at": _now(),
"updated_at": _now(),
}
data["presets"].append(preset)
_write(data)
return preset
def update_preset(preset_id, fields):
data = _read()
for p in data["presets"]:
if p["id"] == preset_id:
p.update(fields)
p["updated_at"] = _now()
_write(data)
return p
return None
def delete_preset(preset_id):
data = _read()
before = len(data["presets"])
data["presets"] = [p for p in data["presets"] if p["id"] != preset_id]
if len(data["presets"]) == before:
return False
_write(data)
return True
def get_openai_key():
return _read().get("openai_api_key", "")
def set_openai_key(key: str):
data = _read()
data["openai_api_key"] = key
_write(data)