A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
271 lines
6.9 KiB
Python
271 lines
6.9 KiB
Python
"""
|
|
AI Content Generator Service - Main Application
|
|
FastAPI Service für automatische H5P Content-Generierung
|
|
"""
|
|
|
|
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, BackgroundTasks
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from typing import List, Optional
|
|
from pydantic import BaseModel
|
|
import os
|
|
from datetime import datetime
|
|
|
|
from app.services.claude_service import ClaudeService
|
|
from app.services.youtube_service import YouTubeService
|
|
from app.services.content_generator import ContentGenerator
|
|
from app.services.material_analyzer import MaterialAnalyzer
|
|
from app.models.generation_job import GenerationJob, JobStatus
|
|
from app.utils.job_store import JobStore
|
|
|
|
app = FastAPI(
|
|
title="BreakPilot AI Content Generator",
|
|
description="Automatische H5P Content-Generierung mit Claude AI",
|
|
version="1.0.0"
|
|
)
|
|
|
|
# CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Services
|
|
claude_service = ClaudeService()
|
|
youtube_service = YouTubeService()
|
|
content_generator = ContentGenerator(claude_service, youtube_service)
|
|
material_analyzer = MaterialAnalyzer()
|
|
job_store = JobStore()
|
|
|
|
# Models
|
|
class GenerateContentRequest(BaseModel):
|
|
topic: str
|
|
description: Optional[str] = None
|
|
target_grade: Optional[str] = None # z.B. "5-6"
|
|
language: str = "de"
|
|
|
|
class GenerationResponse(BaseModel):
|
|
job_id: str
|
|
status: str
|
|
message: str
|
|
|
|
class JobStatusResponse(BaseModel):
|
|
job_id: str
|
|
status: str
|
|
progress: int
|
|
message: str
|
|
result: Optional[dict] = None
|
|
error: Optional[str] = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
"""Service Info"""
|
|
return {
|
|
"service": "AI Content Generator",
|
|
"version": "1.0.0",
|
|
"status": "running",
|
|
"features": [
|
|
"Material Analysis",
|
|
"Claude AI Integration",
|
|
"YouTube Video Crawler",
|
|
"8 H5P Content Types",
|
|
"Background Jobs"
|
|
]
|
|
}
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
"""Health Check"""
|
|
return {
|
|
"status": "healthy",
|
|
"claude_api": claude_service.is_configured(),
|
|
"youtube_api": youtube_service.is_configured()
|
|
}
|
|
|
|
|
|
@app.post("/api/generate-content", response_model=GenerationResponse)
|
|
async def generate_content(
|
|
background_tasks: BackgroundTasks,
|
|
topic: str = Form(...),
|
|
description: Optional[str] = Form(None),
|
|
target_grade: Optional[str] = Form("5-6"),
|
|
files: List[UploadFile] = File(default=[])
|
|
):
|
|
"""
|
|
Generiere H5P Content aus hochgeladenen Materialien
|
|
|
|
Args:
|
|
topic: Thema (z.B. "Das menschliche Auge")
|
|
description: Zusätzliche Beschreibung/Lernziele
|
|
target_grade: Klassenstufe (z.B. "5-6")
|
|
files: Hochgeladene Materialien (PDF, Images, DOCX)
|
|
|
|
Returns:
|
|
job_id und Status für Tracking
|
|
"""
|
|
|
|
try:
|
|
# Job erstellen
|
|
job = GenerationJob(
|
|
topic=topic,
|
|
description=description,
|
|
target_grade=target_grade,
|
|
material_count=len(files)
|
|
)
|
|
job_store.save(job)
|
|
|
|
# Background Task starten
|
|
background_tasks.add_task(
|
|
process_content_generation,
|
|
job.job_id,
|
|
topic,
|
|
description,
|
|
target_grade,
|
|
files
|
|
)
|
|
|
|
return GenerationResponse(
|
|
job_id=job.job_id,
|
|
status="processing",
|
|
message=f"Content-Generierung gestartet für Thema: {topic}"
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@app.get("/api/generation-status/{job_id}", response_model=JobStatusResponse)
|
|
async def get_generation_status(job_id: str):
|
|
"""
|
|
Hole Status eines Content-Generierungs-Jobs
|
|
|
|
Args:
|
|
job_id: Job ID aus der generate-content Response
|
|
|
|
Returns:
|
|
Aktueller Status, Progress und ggf. Ergebnis
|
|
"""
|
|
job = job_store.get(job_id)
|
|
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="Job not found")
|
|
|
|
return JobStatusResponse(
|
|
job_id=job.job_id,
|
|
status=job.status.value,
|
|
progress=job.progress,
|
|
message=job.message,
|
|
result=job.result,
|
|
error=job.error,
|
|
created_at=job.created_at,
|
|
updated_at=job.updated_at
|
|
)
|
|
|
|
|
|
@app.get("/api/generated-content/{job_id}")
|
|
async def get_generated_content(job_id: str):
|
|
"""
|
|
Hole das generierte Content-Paket
|
|
|
|
Returns:
|
|
Alle 8 generierten H5P Content-Typen
|
|
"""
|
|
job = job_store.get(job_id)
|
|
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="Job not found")
|
|
|
|
if job.status != JobStatus.COMPLETED:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail=f"Job not completed yet. Current status: {job.status.value}"
|
|
)
|
|
|
|
return {
|
|
"job_id": job_id,
|
|
"topic": job.topic,
|
|
"content": job.result
|
|
}
|
|
|
|
|
|
@app.post("/api/youtube-search")
|
|
async def search_youtube_videos(
|
|
query: str = Form(...),
|
|
max_results: int = Form(5)
|
|
):
|
|
"""
|
|
Suche passende YouTube Videos zum Thema
|
|
|
|
Args:
|
|
query: Suchbegriff
|
|
max_results: Maximale Anzahl Ergebnisse
|
|
|
|
Returns:
|
|
Liste von Video-Infos mit Transkript-Verfügbarkeit
|
|
"""
|
|
try:
|
|
videos = await youtube_service.search_videos(query, max_results)
|
|
return {"videos": videos}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
async def process_content_generation(
|
|
job_id: str,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
files: List[UploadFile]
|
|
):
|
|
"""Background Task für Content-Generierung"""
|
|
|
|
job = job_store.get(job_id)
|
|
|
|
try:
|
|
# 1. Materialien analysieren
|
|
job.update_progress(10, "Analysiere hochgeladene Materialien...")
|
|
job_store.save(job)
|
|
|
|
materials = []
|
|
for file in files:
|
|
content = await file.read()
|
|
analysis = await material_analyzer.analyze(file.filename, content)
|
|
materials.append(analysis)
|
|
|
|
# 2. YouTube Videos suchen
|
|
job.update_progress(30, "Suche passende YouTube Videos...")
|
|
job_store.save(job)
|
|
|
|
videos = await youtube_service.search_videos(topic, max_results=3)
|
|
|
|
# 3. Content generieren
|
|
job.update_progress(50, "Generiere H5P Content mit Claude AI...")
|
|
job_store.save(job)
|
|
|
|
generated_content = await content_generator.generate_all_content_types(
|
|
topic=topic,
|
|
description=description,
|
|
target_grade=target_grade,
|
|
materials=materials,
|
|
videos=videos
|
|
)
|
|
|
|
# 4. Fertig
|
|
job.complete(generated_content)
|
|
job_store.save(job)
|
|
|
|
except Exception as e:
|
|
job.fail(str(e))
|
|
job_store.save(job)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=8004)
|