Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 27s
CI / test-go-edu-search (push) Successful in 40s
CI / test-python-klausur (push) Failing after 2m30s
CI / test-python-agent-core (push) Successful in 28s
CI / test-nodejs-website (push) Successful in 20s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
188 lines
5.8 KiB
Python
188 lines
5.8 KiB
Python
"""
|
|
Recording API - Meeting Minutes Routes.
|
|
|
|
Generate, retrieve, and export KI-based meeting minutes.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from fastapi.responses import PlainTextResponse, HTMLResponse
|
|
|
|
from .helpers import (
|
|
_recordings_store,
|
|
_transcriptions_store,
|
|
_minutes_store,
|
|
log_audit,
|
|
)
|
|
|
|
router = APIRouter(tags=["Recordings"])
|
|
|
|
|
|
# ==========================================
|
|
# MEETING MINUTES ENDPOINTS
|
|
# ==========================================
|
|
|
|
@router.post("/{recording_id}/minutes")
|
|
async def generate_meeting_minutes(
|
|
recording_id: str,
|
|
title: Optional[str] = Query(None, description="Meeting-Titel"),
|
|
model: str = Query("breakpilot-teacher-8b", description="LLM Modell")
|
|
):
|
|
"""
|
|
Generiert KI-basierte Meeting Minutes aus der Transkription.
|
|
|
|
Nutzt das LLM Gateway (Ollama/vLLM) fuer lokale Verarbeitung.
|
|
"""
|
|
from meeting_minutes_generator import get_minutes_generator, MeetingMinutes
|
|
|
|
# Check recording exists
|
|
recording = _recordings_store.get(recording_id)
|
|
if not recording:
|
|
raise HTTPException(status_code=404, detail="Recording not found")
|
|
|
|
# Check transcription exists and is completed
|
|
transcription = next(
|
|
(t for t in _transcriptions_store.values() if t["recording_id"] == recording_id),
|
|
None
|
|
)
|
|
if not transcription:
|
|
raise HTTPException(status_code=400, detail="No transcription found. Please transcribe first.")
|
|
|
|
if transcription["status"] != "completed":
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Transcription not ready. Status: {transcription['status']}"
|
|
)
|
|
|
|
# Check if minutes already exist
|
|
existing = _minutes_store.get(recording_id)
|
|
if existing and existing.get("status") == "completed":
|
|
# Return existing minutes
|
|
return existing
|
|
|
|
# Get transcript text
|
|
transcript_text = transcription.get("full_text", "")
|
|
if not transcript_text:
|
|
raise HTTPException(status_code=400, detail="Transcription has no text content")
|
|
|
|
# Generate meeting minutes
|
|
generator = get_minutes_generator()
|
|
|
|
try:
|
|
minutes = await generator.generate(
|
|
transcript=transcript_text,
|
|
recording_id=recording_id,
|
|
transcription_id=transcription["id"],
|
|
title=title,
|
|
date=recording.get("recorded_at", "")[:10] if recording.get("recorded_at") else None,
|
|
duration_minutes=recording.get("duration_seconds", 0) // 60 if recording.get("duration_seconds") else None,
|
|
participant_count=recording.get("participant_count", 0),
|
|
model=model
|
|
)
|
|
|
|
# Store minutes
|
|
minutes_dict = minutes.model_dump()
|
|
minutes_dict["generated_at"] = minutes.generated_at.isoformat()
|
|
_minutes_store[recording_id] = minutes_dict
|
|
|
|
# Log action
|
|
log_audit(
|
|
action="minutes_generated",
|
|
recording_id=recording_id,
|
|
metadata={"model": model, "generation_time": minutes.generation_time_seconds}
|
|
)
|
|
|
|
return minutes_dict
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Minutes generation failed: {str(e)}")
|
|
|
|
|
|
@router.get("/{recording_id}/minutes")
|
|
async def get_meeting_minutes(recording_id: str):
|
|
"""
|
|
Ruft generierte Meeting Minutes ab.
|
|
"""
|
|
minutes = _minutes_store.get(recording_id)
|
|
if not minutes:
|
|
raise HTTPException(status_code=404, detail="No meeting minutes found. Generate them first with POST.")
|
|
|
|
return minutes
|
|
|
|
|
|
def _load_minutes(recording_id: str):
|
|
"""Load and convert stored minutes dict back to MeetingMinutes."""
|
|
from meeting_minutes_generator import MeetingMinutes
|
|
|
|
minutes_dict = _minutes_store.get(recording_id)
|
|
if not minutes_dict:
|
|
raise HTTPException(status_code=404, detail="No meeting minutes found")
|
|
|
|
minutes_dict_copy = minutes_dict.copy()
|
|
if isinstance(minutes_dict_copy.get("generated_at"), str):
|
|
minutes_dict_copy["generated_at"] = datetime.fromisoformat(minutes_dict_copy["generated_at"])
|
|
|
|
return MeetingMinutes(**minutes_dict_copy)
|
|
|
|
|
|
@router.get("/{recording_id}/minutes/markdown")
|
|
async def get_minutes_markdown(recording_id: str):
|
|
"""
|
|
Exportiert Meeting Minutes als Markdown.
|
|
"""
|
|
from meeting_minutes_generator import minutes_to_markdown
|
|
|
|
minutes = _load_minutes(recording_id)
|
|
markdown = minutes_to_markdown(minutes)
|
|
|
|
return PlainTextResponse(
|
|
content=markdown,
|
|
media_type="text/markdown",
|
|
headers={"Content-Disposition": f"attachment; filename=protokoll_{recording_id}.md"}
|
|
)
|
|
|
|
|
|
@router.get("/{recording_id}/minutes/html")
|
|
async def get_minutes_html(recording_id: str):
|
|
"""
|
|
Exportiert Meeting Minutes als HTML.
|
|
"""
|
|
from meeting_minutes_generator import minutes_to_html
|
|
|
|
minutes = _load_minutes(recording_id)
|
|
html = minutes_to_html(minutes)
|
|
|
|
return HTMLResponse(content=html)
|
|
|
|
|
|
@router.get("/{recording_id}/minutes/pdf")
|
|
async def get_minutes_pdf(recording_id: str):
|
|
"""
|
|
Exportiert Meeting Minutes als PDF.
|
|
|
|
Benoetigt WeasyPrint (pip install weasyprint).
|
|
"""
|
|
from meeting_minutes_generator import minutes_to_html
|
|
|
|
minutes = _load_minutes(recording_id)
|
|
html = minutes_to_html(minutes)
|
|
|
|
try:
|
|
from weasyprint import HTML
|
|
from fastapi.responses import Response
|
|
|
|
pdf_bytes = HTML(string=html).write_pdf()
|
|
|
|
return Response(
|
|
content=pdf_bytes,
|
|
media_type="application/pdf",
|
|
headers={"Content-Disposition": f"attachment; filename=protokoll_{recording_id}.pdf"}
|
|
)
|
|
except ImportError:
|
|
raise HTTPException(
|
|
status_code=501,
|
|
detail="PDF export not available. Install weasyprint: pip install weasyprint"
|
|
)
|