File size: 6,184 Bytes
6156d2f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | #!/usr/bin/env python3
"""PromptGarage UI - FastAPI web application."""
import os
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, Form, Query, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import db_client as db
app = FastAPI(title="PromptGarage", description="Web UI for your prompts")
# Setup templates
BASE_DIR = Path(__file__).parent
templates = Jinja2Templates(directory=BASE_DIR / "templates")
# Mount static files
app.mount("/static", StaticFiles(directory=BASE_DIR / "static"), name="static")
# --- Routes ---
@app.get("/", response_class=RedirectResponse)
async def root():
return RedirectResponse("/prompts")
@app.get("/prompts", name="prompts_list")
async def prompts_list(
request: Request,
q: Optional[str] = Query(None, description="Search query"),
tag: Optional[str] = Query(None, description="Filter by tag"),
starred: Optional[int] = Query(None, description="Filter starred (1)"),
page: int = Query(1, ge=1, description="Page number"),
limit: int = Query(20, ge=1, le=100, description="Items per page"),
):
offset = (page - 1) * limit
# Get all tags for filter dropdown
all_tags = db.get_stats()["by_tag"]
# Search or list
if q:
prompts = db.search_fts(q)
# Apply tag/star filters manually after FTS search
filtered = []
for p in prompts:
if tag and tag not in (p["tags"] or ""):
continue
if starred is not None and p["is_starred"] != starred:
continue
filtered.append(p)
prompts = filtered[offset:offset + limit]
total = len(filtered)
else:
# Build query params for list_prompts
prompts = db.list_prompts(
starred=bool(starred) if starred is not None else None,
tag=tag,
limit=limit + offset
)
if offset > 0:
prompts = prompts[offset:]
total = len(db.list_prompts(
starred=bool(starred) if starred is not None else None,
tag=tag,
limit=100000
))
total_pages = (total + limit - 1) // limit if total > 0 else 1
return templates.TemplateResponse("prompts/list.html", {
"request": request,
"prompts": prompts,
"all_tags": all_tags,
"q": q,
"selected_tag": tag,
"starred_filter": starred,
"page": page,
"limit": limit,
"total": total,
"total_pages": total_pages,
})
@app.get("/prompts/new", name="prompts_new")
async def prompts_new(request: Request):
stats = db.get_stats()
return templates.TemplateResponse("prompts/new.html", {
"request": request,
"all_tags": stats["by_tag"],
})
@app.post("/prompts/new")
async def prompts_create(
title: str = Form(...),
content: str = Form(...),
tags: Optional[str] = Form(None),
model: Optional[str] = Form(None),
):
prompt_id = db.add_prompt(title=title, content=content, tags=tags, model=model)
return RedirectResponse(f"/prompts/{prompt_id}", status_code=303)
@app.get("/prompts/{prompt_id}", name="prompts_detail")
async def prompts_detail(request: Request, prompt_id: int):
prompt = db.get_prompt(prompt_id)
if not prompt:
return templates.TemplateResponse("error.html", {
"request": request,
"error": f"Prompt #{prompt_id} not found",
}, status_code=404)
return templates.TemplateResponse("prompts/detail.html", {
"request": request,
"prompt": prompt,
})
@app.post("/prompts/{prompt_id}/star")
async def prompts_toggle_star(prompt_id: int):
result = db.toggle_star(prompt_id)
if result is None:
return {"error": "Prompt not found"}
return {"is_starred": result}
@app.post("/prompts/{prompt_id}/rate")
async def prompts_set_rating(prompt_id: int, rating: int = Query(..., ge=1, le=5)):
db.set_rating(prompt_id, rating)
return {"rating": rating}
@app.post("/prompts/{prompt_id}/use")
async def prompts_use(prompt_id: int):
db.increment_use(prompt_id)
prompt = db.get_prompt(prompt_id)
if prompt:
return {"use_count": prompt["use_count"]}
return {"error": "Prompt not found"}
@app.get("/prompts/{prompt_id}/edit", name="prompts_edit")
async def prompts_edit(request: Request, prompt_id: int):
prompt = db.get_prompt(prompt_id)
if not prompt:
return templates.TemplateResponse("error.html", {
"request": request,
"error": f"Prompt #{prompt_id} not found",
}, status_code=404)
stats = db.get_stats()
return templates.TemplateResponse("prompts/edit.html", {
"request": request,
"prompt": prompt,
"all_tags": stats["by_tag"],
})
@app.post("/prompts/{prompt_id}/edit")
async def prompts_update(
prompt_id: int,
title: str = Form(...),
content: str = Form(...),
tags: Optional[str] = Form(None),
model: Optional[str] = Form(None),
):
db.update_prompt(prompt_id, title=title, content=content, tags=tags, model=model)
return RedirectResponse(f"/prompts/{prompt_id}", status_code=303)
@app.post("/prompts/{prompt_id}/delete")
async def prompts_delete(prompt_id: int):
db.delete_prompt(prompt_id)
return RedirectResponse("/prompts", status_code=303)
@app.get("/stats", name="stats")
async def stats_page(request: Request):
stats = db.get_stats()
# Calculate rating distribution
all_prompts = db.list_prompts(limit=100000)
rating_dist = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
for p in all_prompts:
r = p["rating"] or 0
rating_dist[r] = rating_dist.get(r, 0) + 1
# Get top tags
top_tags = sorted(stats["by_tag"].items(), key=lambda x: -x[1])[:15]
return templates.TemplateResponse("stats.html", {
"request": request,
"stats": stats,
"rating_dist": rating_dist,
"top_tags": top_tags,
})
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
|