Files
creator/backend/routes.py
2026-06-06 00:14:43 +02:00

152 lines
4.8 KiB
Python

import asyncio
import shutil
import uuid
from datetime import datetime, timezone
from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse
from agents import provider_available
from config import FORMAT_META, PROJECTS_DIR, PROVIDERS
from database import (
create_guide, delete_guide, get_guide, list_guides,
list_progress, set_progress, delete_progress,
)
from generator import generate_guide, cancel_guide, chat_with_guide
from models import (
GuideCreateRequest, GuideResponse,
GuideChatRequest, GuideChatResponse,
ProgressUpdate, ProgressResponse, ProjectResponse, ProviderInfo,
)
from paths import final_html_path, project_dir
router = APIRouter(prefix="/api")
@router.get("/formats")
async def get_formats():
return FORMAT_META
@router.get("/providers", response_model=list[ProviderInfo])
async def get_providers():
return [{"id": pid, "available": provider_available(pid)} for pid in PROVIDERS]
def _safe_project_name(name: str) -> str:
if not name or "/" in name or "\\" in name or ".." in name or "\x00" in name:
raise HTTPException(400, "Ungültiger Projektname")
return name
@router.get("/projects", response_model=list[ProjectResponse])
async def list_projects():
if not PROJECTS_DIR.is_dir():
return []
return [{"name": entry.name} for entry in sorted(PROJECTS_DIR.iterdir()) if entry.is_dir()]
@router.delete("/projects/{name}")
async def remove_project(name: str):
_safe_project_name(name)
pdir = project_dir(name)
if not pdir.is_dir():
raise HTTPException(404, "Projekt nicht gefunden")
shutil.rmtree(pdir)
return {"ok": True}
@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"], req.provider))
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_html_path(guide["topic"], guide["format"])
if not html_path.exists():
raise HTTPException(404, "Datei nicht gefunden")
return FileResponse(html_path, media_type="text/html", content_disposition_type="inline")
@router.post("/guides/{guide_id}/chat", response_model=GuideChatResponse)
async def guide_chat(guide_id: str, req: GuideChatRequest):
guide = await get_guide(guide_id)
if guide is None:
raise HTTPException(404, "Guide nicht gefunden")
reply = await chat_with_guide(
guide["topic"], guide["format"], req.section, req.outline,
[m.model_dump() for m in req.messages],
provider=req.provider,
)
return {"reply": reply}
@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.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 = final_html_path(guide["topic"], guide["format"])
html_path.unlink(missing_ok=True)
html_path.with_suffix(".bausteine.md").unlink(missing_ok=True)
await delete_progress(guide_id)
await delete_guide(guide_id)
return {"ok": True}
@router.get("/guides/{guide_id}/progress", response_model=ProgressResponse)
async def get_progress(guide_id: str):
guide = await get_guide(guide_id)
if guide is None:
raise HTTPException(404, "Guide nicht gefunden")
return {"chapters": await list_progress(guide_id)}
@router.post("/guides/{guide_id}/progress", response_model=ProgressResponse)
async def update_progress(guide_id: str, req: ProgressUpdate):
guide = await get_guide(guide_id)
if guide is None:
raise HTTPException(404, "Guide nicht gefunden")
await set_progress(guide_id, req.chapter, req.done)
return {"chapters": await list_progress(guide_id)}