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:
341
ai-content-generator/app/services/content_generator.py
Normal file
341
ai-content-generator/app/services/content_generator.py
Normal file
@@ -0,0 +1,341 @@
|
||||
"""
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user