225 lines
7.3 KiB
Python
225 lines
7.3 KiB
Python
import asyncio
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from fastapi.responses import FileResponse
|
|
|
|
from config import FORMAT_META
|
|
from database import (
|
|
create_guide, delete_guide, get_guide, list_guides,
|
|
create_baustein as db_create_baustein, list_bausteine, get_baustein, delete_baustein as db_delete_baustein,
|
|
list_suggestions, get_suggestion, update_suggestion, delete_suggestion,
|
|
)
|
|
from generator import generate_guide, rework_guide, cancel_guide, generate_suggestions, generate_baustein_detail, rework_baustein, is_suggestions_generating
|
|
from models import (
|
|
GuideCreateRequest, GuideReworkRequest, GuideResponse,
|
|
BausteinCreateRequest, BausteinReworkRequest, BausteinResponse, SuggestionResponse,
|
|
)
|
|
from paths import final_paths
|
|
|
|
router = APIRouter(prefix="/api")
|
|
|
|
|
|
@router.get("/formats")
|
|
async def get_formats():
|
|
return FORMAT_META
|
|
|
|
|
|
@router.post("/guides", response_model=GuideResponse)
|
|
async def create(req: GuideCreateRequest):
|
|
now = datetime.now(timezone.utc).isoformat()
|
|
guide = {
|
|
"id": str(uuid.uuid4()),
|
|
"topic": req.topic.strip(),
|
|
"format": req.format,
|
|
"instructions": req.instructions.strip(),
|
|
"status": "queued",
|
|
"progress": None,
|
|
"created_at": now,
|
|
"updated_at": now,
|
|
}
|
|
await create_guide(guide)
|
|
asyncio.create_task(generate_guide(guide["id"], guide["topic"], guide["format"], guide["instructions"]))
|
|
return guide
|
|
|
|
|
|
@router.get("/guides", response_model=list[GuideResponse])
|
|
async def list_all():
|
|
return await list_guides()
|
|
|
|
|
|
@router.get("/guides/{guide_id}", response_model=GuideResponse)
|
|
async def get_one(guide_id: str):
|
|
guide = await get_guide(guide_id)
|
|
if guide is None:
|
|
raise HTTPException(404, "Guide nicht gefunden")
|
|
return guide
|
|
|
|
|
|
@router.get("/guides/{guide_id}/html")
|
|
async def download_html(guide_id: str):
|
|
guide = await get_guide(guide_id)
|
|
if guide is None:
|
|
raise HTTPException(404, "Guide nicht gefunden")
|
|
if guide["status"] != "done":
|
|
raise HTTPException(404, "HTML nicht verfügbar")
|
|
html_path, _ = final_paths(guide["topic"], guide["format"])
|
|
if not html_path.exists():
|
|
raise HTTPException(404, "Datei nicht gefunden")
|
|
return FileResponse(html_path, filename=html_path.name, media_type="text/html")
|
|
|
|
|
|
@router.post("/guides/{guide_id}/rework")
|
|
async def rework(guide_id: str, req: GuideReworkRequest):
|
|
guide = await get_guide(guide_id)
|
|
if guide is None:
|
|
raise HTTPException(404, "Guide nicht gefunden")
|
|
if guide["status"] != "done":
|
|
raise HTTPException(400, "Guide muss fertig sein")
|
|
asyncio.create_task(rework_guide(guide_id, guide["topic"], guide["format"], req.instructions.strip()))
|
|
return {"ok": True}
|
|
|
|
|
|
@router.post("/guides/{guide_id}/cancel")
|
|
async def cancel(guide_id: str):
|
|
cancelled = await cancel_guide(guide_id)
|
|
if not cancelled:
|
|
raise HTTPException(404, "Kein aktiver Prozess gefunden")
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/guides/{guide_id}/pdf")
|
|
async def download_pdf(guide_id: str):
|
|
guide = await get_guide(guide_id)
|
|
if guide is None:
|
|
raise HTTPException(404, "Guide nicht gefunden")
|
|
if guide["status"] != "done":
|
|
raise HTTPException(404, "PDF nicht verfügbar")
|
|
_, pdf_path = final_paths(guide["topic"], guide["format"])
|
|
if not pdf_path.exists():
|
|
raise HTTPException(404, "Datei nicht gefunden")
|
|
return FileResponse(pdf_path, filename=pdf_path.name, media_type="application/pdf")
|
|
|
|
|
|
@router.delete("/guides/{guide_id}")
|
|
async def remove(guide_id: str):
|
|
guide = await get_guide(guide_id)
|
|
if guide is None:
|
|
raise HTTPException(404, "Guide nicht gefunden")
|
|
html_path, pdf_path = final_paths(guide["topic"], guide["format"])
|
|
html_path.unlink(missing_ok=True)
|
|
pdf_path.unlink(missing_ok=True)
|
|
await delete_guide(guide_id)
|
|
return {"ok": True}
|
|
|
|
|
|
# --- Bausteine ---
|
|
|
|
@router.get("/bausteine", response_model=list[BausteinResponse])
|
|
async def get_bausteine(topic: str):
|
|
return await list_bausteine(topic)
|
|
|
|
|
|
@router.post("/bausteine", response_model=BausteinResponse)
|
|
async def add_baustein(req: BausteinCreateRequest):
|
|
now = datetime.now(timezone.utc).isoformat()
|
|
baustein = {
|
|
"id": str(uuid.uuid4()),
|
|
"topic": req.topic.strip(),
|
|
"title": req.title.strip(),
|
|
"description": "",
|
|
"purpose": "",
|
|
"example": "",
|
|
"created_at": now,
|
|
"updated_at": now,
|
|
}
|
|
await db_create_baustein(baustein)
|
|
asyncio.create_task(generate_baustein_detail(baustein["id"], baustein["topic"], baustein["title"]))
|
|
return baustein
|
|
|
|
|
|
@router.delete("/bausteine/{baustein_id}")
|
|
async def remove_baustein(baustein_id: str):
|
|
b = await get_baustein(baustein_id)
|
|
if b is None:
|
|
raise HTTPException(404, "Baustein nicht gefunden")
|
|
await db_delete_baustein(baustein_id)
|
|
return {"ok": True}
|
|
|
|
|
|
@router.post("/bausteine/{baustein_id}/rework")
|
|
async def rework_baustein_route(baustein_id: str, req: BausteinReworkRequest):
|
|
b = await get_baustein(baustein_id)
|
|
if b is None:
|
|
raise HTTPException(404, "Baustein nicht gefunden")
|
|
import json
|
|
try:
|
|
examples = json.loads(b.get("example") or "[]")
|
|
except Exception:
|
|
examples = []
|
|
current = {
|
|
"description": b.get("description", ""),
|
|
"purpose": b.get("purpose", ""),
|
|
"examples": examples,
|
|
}
|
|
asyncio.create_task(rework_baustein(baustein_id, b["topic"], b["title"], current, req.instructions.strip()))
|
|
return {"ok": True}
|
|
|
|
|
|
# --- Baustein Suggestions ---
|
|
|
|
@router.get("/bausteine/suggestions", response_model=list[SuggestionResponse])
|
|
async def get_suggestions(topic: str):
|
|
return await list_suggestions(topic)
|
|
|
|
|
|
@router.post("/bausteine/suggestions/generate")
|
|
async def trigger_suggestions(topic: str):
|
|
if is_suggestions_generating(topic):
|
|
return {"ok": True, "status": "already_generating"}
|
|
guides = await list_guides()
|
|
html_paths = []
|
|
for g in guides:
|
|
if g["topic"] == topic and g["status"] == "done":
|
|
html_path, _ = final_paths(g["topic"], g["format"])
|
|
if html_path.exists():
|
|
html_paths.append(html_path)
|
|
asyncio.create_task(generate_suggestions(topic, html_paths))
|
|
return {"ok": True}
|
|
|
|
|
|
@router.get("/bausteine/suggestions/status")
|
|
async def suggestions_status(topic: str):
|
|
return {"generating": is_suggestions_generating(topic)}
|
|
|
|
|
|
@router.post("/bausteine/suggestions/{suggestion_id}/add")
|
|
async def accept_suggestion(suggestion_id: str):
|
|
s = await get_suggestion(suggestion_id)
|
|
if s is None:
|
|
raise HTTPException(404, "Vorschlag nicht gefunden")
|
|
now = datetime.now(timezone.utc).isoformat()
|
|
baustein = {
|
|
"id": str(uuid.uuid4()),
|
|
"topic": s["topic"],
|
|
"title": s["title"],
|
|
"description": s["description"],
|
|
"purpose": s["purpose"],
|
|
"example": s["example"],
|
|
"created_at": now,
|
|
"updated_at": now,
|
|
}
|
|
await db_create_baustein(baustein)
|
|
await delete_suggestion(suggestion_id)
|
|
return baustein
|
|
|
|
|
|
@router.post("/bausteine/suggestions/{suggestion_id}/ignore")
|
|
async def ignore_suggestion(suggestion_id: str):
|
|
s = await get_suggestion(suggestion_id)
|
|
if s is None:
|
|
raise HTTPException(404, "Vorschlag nicht gefunden")
|
|
await update_suggestion(suggestion_id, status="ignored")
|
|
return {"ok": True}
|