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>
342 lines
11 KiB
Python
342 lines
11 KiB
Python
"""
|
|
Content Generator
|
|
Orchestriert die Generierung aller 8 H5P Content-Typen
|
|
"""
|
|
|
|
from typing import List, Dict, Any, Optional
|
|
from datetime import datetime
|
|
import json
|
|
|
|
|
|
class ContentGenerator:
|
|
"""H5P Content Generator - Orchestrator"""
|
|
|
|
def __init__(self, claude_service, youtube_service):
|
|
self.claude = claude_service
|
|
self.youtube = youtube_service
|
|
|
|
async def generate_all_content_types(
|
|
self,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
materials: List[Dict[str, Any]],
|
|
videos: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Generate all 8 H5P content types
|
|
|
|
Returns:
|
|
Dictionary with all generated content
|
|
"""
|
|
|
|
result = {
|
|
"topic": topic,
|
|
"description": description,
|
|
"target_grade": target_grade,
|
|
"generated_at": datetime.utcnow().isoformat(),
|
|
"content_types": {}
|
|
}
|
|
|
|
# 1. Quiz
|
|
try:
|
|
quiz_data = await self._generate_quiz(topic, description, target_grade, materials)
|
|
result["content_types"]["quiz"] = quiz_data
|
|
except Exception as e:
|
|
result["content_types"]["quiz"] = {"error": str(e)}
|
|
|
|
# 2. Interactive Video
|
|
try:
|
|
video_data = await self._generate_interactive_video(topic, description, target_grade, materials, videos)
|
|
result["content_types"]["interactive_video"] = video_data
|
|
except Exception as e:
|
|
result["content_types"]["interactive_video"] = {"error": str(e)}
|
|
|
|
# 3. Course Presentation
|
|
try:
|
|
presentation_data = await self._generate_presentation(topic, description, target_grade, materials)
|
|
result["content_types"]["course_presentation"] = presentation_data
|
|
except Exception as e:
|
|
result["content_types"]["course_presentation"] = {"error": str(e)}
|
|
|
|
# 4. Flashcards
|
|
try:
|
|
flashcards_data = await self._generate_flashcards(topic, description, target_grade, materials)
|
|
result["content_types"]["flashcards"] = flashcards_data
|
|
except Exception as e:
|
|
result["content_types"]["flashcards"] = {"error": str(e)}
|
|
|
|
# 5. Timeline
|
|
try:
|
|
timeline_data = await self._generate_timeline(topic, description, target_grade, materials)
|
|
result["content_types"]["timeline"] = timeline_data
|
|
except Exception as e:
|
|
result["content_types"]["timeline"] = {"error": str(e)}
|
|
|
|
# 6. Drag and Drop
|
|
try:
|
|
dragdrop_data = await self._generate_drag_drop(topic, description, target_grade, materials)
|
|
result["content_types"]["drag_drop"] = dragdrop_data
|
|
except Exception as e:
|
|
result["content_types"]["drag_drop"] = {"error": str(e)}
|
|
|
|
# 7. Fill in the Blanks
|
|
try:
|
|
fillblanks_data = await self._generate_fill_blanks(topic, description, target_grade, materials)
|
|
result["content_types"]["fill_blanks"] = fillblanks_data
|
|
except Exception as e:
|
|
result["content_types"]["fill_blanks"] = {"error": str(e)}
|
|
|
|
# 8. Memory Game
|
|
try:
|
|
memory_data = await self._generate_memory(topic, description, target_grade, materials)
|
|
result["content_types"]["memory"] = memory_data
|
|
except Exception as e:
|
|
result["content_types"]["memory"] = {"error": str(e)}
|
|
|
|
return result
|
|
|
|
async def _generate_quiz(
|
|
self,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
materials: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Generate Quiz content"""
|
|
|
|
questions = await self.claude.generate_quiz_questions(
|
|
topic=topic,
|
|
materials=materials,
|
|
target_grade=target_grade,
|
|
num_questions=10
|
|
)
|
|
|
|
return {
|
|
"type": "quiz",
|
|
"title": f"Quiz: {topic}",
|
|
"description": description or f"Teste dein Wissen über {topic}",
|
|
"questions": questions
|
|
}
|
|
|
|
async def _generate_interactive_video(
|
|
self,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
materials: List[Dict[str, Any]],
|
|
videos: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Generate Interactive Video content"""
|
|
|
|
# Wähle bestes Video (falls vorhanden)
|
|
if not videos or len(videos) == 0:
|
|
return {
|
|
"error": "Keine Videos gefunden",
|
|
"note": "Lehrer muss manuell Video-URL eingeben"
|
|
}
|
|
|
|
best_video = videos[0] # Nimm erstes Video
|
|
|
|
# Hole Transkript
|
|
video_id = best_video.get("video_id")
|
|
if not video_id or video_id == "EXAMPLE_VIDEO_ID":
|
|
# Fallback: Generiere generische Interaktionen
|
|
return {
|
|
"type": "interactive-video",
|
|
"title": f"Interaktives Video: {topic}",
|
|
"videoUrl": "https://www.youtube.com/watch?v=EXAMPLE",
|
|
"description": description or f"Lerne über {topic} mit diesem interaktiven Video",
|
|
"interactions": [
|
|
{
|
|
"time": "01:00",
|
|
"type": "question",
|
|
"title": "Verständnisfrage",
|
|
"content": f"Was ist das Hauptthema dieses Videos über {topic}?"
|
|
},
|
|
{
|
|
"time": "03:00",
|
|
"type": "info",
|
|
"title": "Wichtiger Hinweis",
|
|
"content": "Achte auf die wichtigsten Konzepte, die jetzt erklärt werden."
|
|
}
|
|
],
|
|
"note": "Generische Interaktionen - Lehrer sollte echte Video-URL eingeben"
|
|
}
|
|
|
|
# Echtes Video mit Transkript
|
|
transcript_data = await self.youtube.get_video_transcript(video_id)
|
|
|
|
if transcript_data:
|
|
# Generate interactions using Claude
|
|
interactions = await self.youtube.generate_video_interactions_with_claude(
|
|
video_id=video_id,
|
|
topic=topic,
|
|
transcript_data=transcript_data["transcript"],
|
|
claude_service=self.claude,
|
|
num_interactions=5
|
|
)
|
|
else:
|
|
# Fallback ohne Transkript
|
|
interactions = []
|
|
|
|
return {
|
|
"type": "interactive-video",
|
|
"title": best_video.get("title", f"Video: {topic}"),
|
|
"videoUrl": best_video.get("url"),
|
|
"description": description or f"Interaktives Video über {topic}",
|
|
"interactions": interactions
|
|
}
|
|
|
|
async def _generate_presentation(
|
|
self,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
materials: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Generate Course Presentation content"""
|
|
|
|
slides = await self.claude.generate_presentation_slides(
|
|
topic=topic,
|
|
materials=materials,
|
|
target_grade=target_grade,
|
|
num_slides=6
|
|
)
|
|
|
|
# Add IDs to slides
|
|
for i, slide in enumerate(slides, 1):
|
|
slide["id"] = i
|
|
|
|
return {
|
|
"type": "course-presentation",
|
|
"title": f"Präsentation: {topic}",
|
|
"description": description or f"Lerne alles über {topic}",
|
|
"slides": slides
|
|
}
|
|
|
|
async def _generate_flashcards(
|
|
self,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
materials: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Generate Flashcards content"""
|
|
|
|
cards = await self.claude.generate_flashcards(
|
|
topic=topic,
|
|
materials=materials,
|
|
target_grade=target_grade,
|
|
num_cards=15
|
|
)
|
|
|
|
# Add IDs to cards
|
|
for i, card in enumerate(cards, 1):
|
|
card["id"] = i
|
|
|
|
return {
|
|
"type": "flashcards",
|
|
"title": f"Lernkarten: {topic}",
|
|
"description": description or f"Wiederhole wichtige Begriffe zu {topic}",
|
|
"cards": cards
|
|
}
|
|
|
|
async def _generate_timeline(
|
|
self,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
materials: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Generate Timeline content"""
|
|
|
|
events = await self.claude.generate_timeline_events(
|
|
topic=topic,
|
|
materials=materials,
|
|
target_grade=target_grade
|
|
)
|
|
|
|
# Add IDs to events
|
|
for i, event in enumerate(events, 1):
|
|
event["id"] = i
|
|
|
|
return {
|
|
"type": "timeline",
|
|
"title": f"Zeitleiste: {topic}",
|
|
"description": description or f"Chronologie von {topic}",
|
|
"events": events
|
|
}
|
|
|
|
async def _generate_drag_drop(
|
|
self,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
materials: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Generate Drag and Drop content"""
|
|
|
|
exercise = await self.claude.generate_drag_drop_exercise(
|
|
topic=topic,
|
|
materials=materials,
|
|
target_grade=target_grade
|
|
)
|
|
|
|
return {
|
|
"type": "drag-drop",
|
|
"title": exercise.get("title", f"Zuordnung: {topic}"),
|
|
"question": exercise.get("question", "Ziehe die Elemente in die richtigen Kategorien."),
|
|
"zones": exercise.get("zones", []),
|
|
"draggables": exercise.get("draggables", [])
|
|
}
|
|
|
|
async def _generate_fill_blanks(
|
|
self,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
materials: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Generate Fill in the Blanks content"""
|
|
|
|
exercise = await self.claude.generate_fill_blanks_text(
|
|
topic=topic,
|
|
materials=materials,
|
|
target_grade=target_grade
|
|
)
|
|
|
|
return {
|
|
"type": "fill-blanks",
|
|
"title": exercise.get("title", f"Lückentext: {topic}"),
|
|
"text": exercise.get("text", ""),
|
|
"hints": exercise.get("hints", "")
|
|
}
|
|
|
|
async def _generate_memory(
|
|
self,
|
|
topic: str,
|
|
description: Optional[str],
|
|
target_grade: str,
|
|
materials: List[Dict[str, Any]]
|
|
) -> Dict[str, Any]:
|
|
"""Generate Memory Game content"""
|
|
|
|
pairs = await self.claude.generate_memory_pairs(
|
|
topic=topic,
|
|
materials=materials,
|
|
target_grade=target_grade,
|
|
num_pairs=8
|
|
)
|
|
|
|
# Add IDs to pairs
|
|
for i, pair in enumerate(pairs, 1):
|
|
pair["id"] = i
|
|
|
|
return {
|
|
"type": "memory",
|
|
"title": f"Memory: {topic}",
|
|
"description": description or f"Finde die passenden Paare zu {topic}",
|
|
"pairs": pairs
|
|
}
|