fix: Restore all files lost during destructive rebase
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>
This commit is contained in:
270
ai-content-generator/app/main.py
Normal file
270
ai-content-generator/app/main.py
Normal file
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user