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:
364
ai-content-generator/app/services/claude_service.py
Normal file
364
ai-content-generator/app/services/claude_service.py
Normal file
@@ -0,0 +1,364 @@
|
||||
"""
|
||||
Claude Service
|
||||
Integration mit Claude API für Content-Generierung
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import List, Dict, Any, Optional
|
||||
from anthropic import Anthropic
|
||||
|
||||
|
||||
class ClaudeService:
|
||||
"""Claude API Service"""
|
||||
|
||||
def __init__(self):
|
||||
self.api_key = os.getenv("ANTHROPIC_API_KEY")
|
||||
self.client = Anthropic(api_key=self.api_key) if self.api_key else None
|
||||
self.model = "claude-sonnet-4-5-20251101" # Latest model
|
||||
|
||||
def is_configured(self) -> bool:
|
||||
"""Check if API key is configured"""
|
||||
return self.client is not None
|
||||
|
||||
async def generate_content(
|
||||
self,
|
||||
prompt: str,
|
||||
system_prompt: Optional[str] = None,
|
||||
max_tokens: int = 4000,
|
||||
temperature: float = 1.0
|
||||
) -> str:
|
||||
"""
|
||||
Generate content with Claude
|
||||
|
||||
Args:
|
||||
prompt: User prompt
|
||||
system_prompt: System prompt (optional)
|
||||
max_tokens: Maximum tokens to generate
|
||||
temperature: Sampling temperature
|
||||
|
||||
Returns:
|
||||
Generated text
|
||||
"""
|
||||
if not self.client:
|
||||
raise ValueError("Claude API not configured. Set ANTHROPIC_API_KEY environment variable.")
|
||||
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
|
||||
kwargs = {
|
||||
"model": self.model,
|
||||
"max_tokens": max_tokens,
|
||||
"temperature": temperature,
|
||||
"messages": messages
|
||||
}
|
||||
|
||||
if system_prompt:
|
||||
kwargs["system"] = system_prompt
|
||||
|
||||
response = self.client.messages.create(**kwargs)
|
||||
return response.content[0].text
|
||||
|
||||
async def generate_quiz_questions(
|
||||
self,
|
||||
topic: str,
|
||||
materials: List[Dict[str, Any]],
|
||||
target_grade: str,
|
||||
num_questions: int = 10
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Generate Quiz questions"""
|
||||
|
||||
material_text = self._format_materials(materials)
|
||||
|
||||
prompt = f"""Erstelle {num_questions} Multiple-Choice-Fragen zum Thema "{topic}" für Klassenstufe {target_grade}.
|
||||
|
||||
Materialien:
|
||||
{material_text}
|
||||
|
||||
Erstelle Fragen die:
|
||||
1. Das Verständnis testen
|
||||
2. Auf den Materialien basieren
|
||||
3. Altersgerecht sind
|
||||
4. 4 Antwortmöglichkeiten haben (1 richtig, 3 falsch)
|
||||
|
||||
Formatiere die Ausgabe als JSON-Array:
|
||||
[
|
||||
{{
|
||||
"question": "Frage text?",
|
||||
"options": ["Option A", "Option B", "Option C", "Option D"],
|
||||
"correct_answer": 0,
|
||||
"explanation": "Erklärung warum die Antwort richtig ist"
|
||||
}}
|
||||
]
|
||||
|
||||
Nur das JSON-Array zurückgeben, keine zusätzlichen Texte."""
|
||||
|
||||
response = await self.generate_content(
|
||||
prompt=prompt,
|
||||
system_prompt="Du bist ein pädagogischer Experte der Quizfragen erstellt."
|
||||
)
|
||||
|
||||
# Parse JSON
|
||||
import json
|
||||
try:
|
||||
questions = json.loads(response)
|
||||
return questions
|
||||
except json.JSONDecodeError:
|
||||
# Try to extract JSON from response
|
||||
import re
|
||||
json_match = re.search(r'\[.*\]', response, re.DOTALL)
|
||||
if json_match:
|
||||
questions = json.loads(json_match.group())
|
||||
return questions
|
||||
raise ValueError("Could not parse quiz questions from Claude response")
|
||||
|
||||
async def generate_flashcards(
|
||||
self,
|
||||
topic: str,
|
||||
materials: List[Dict[str, Any]],
|
||||
target_grade: str,
|
||||
num_cards: int = 15
|
||||
) -> List[Dict[str, str]]:
|
||||
"""Generate Flashcards"""
|
||||
|
||||
material_text = self._format_materials(materials)
|
||||
|
||||
prompt = f"""Erstelle {num_cards} Lernkarten (Flashcards) zum Thema "{topic}" für Klassenstufe {target_grade}.
|
||||
|
||||
Materialien:
|
||||
{material_text}
|
||||
|
||||
Erstelle Karten die:
|
||||
1. Wichtige Begriffe und Konzepte abdecken
|
||||
2. Kurz und prägnant sind
|
||||
3. Zum Wiederholen geeignet sind
|
||||
|
||||
Formatiere die Ausgabe als JSON-Array:
|
||||
[
|
||||
{{
|
||||
"front": "Begriff oder Frage",
|
||||
"back": "Definition oder Antwort"
|
||||
}}
|
||||
]
|
||||
|
||||
Nur das JSON-Array zurückgeben."""
|
||||
|
||||
response = await self.generate_content(
|
||||
prompt=prompt,
|
||||
system_prompt="Du bist ein Experte für Lernkarten-Design."
|
||||
)
|
||||
|
||||
import json
|
||||
import re
|
||||
json_match = re.search(r'\[.*\]', response, re.DOTALL)
|
||||
if json_match:
|
||||
return json.loads(json_match.group())
|
||||
return json.loads(response)
|
||||
|
||||
async def generate_fill_blanks_text(
|
||||
self,
|
||||
topic: str,
|
||||
materials: List[Dict[str, Any]],
|
||||
target_grade: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate Fill-in-the-Blanks exercise"""
|
||||
|
||||
material_text = self._format_materials(materials)
|
||||
|
||||
prompt = f"""Erstelle einen Lückentext zum Thema "{topic}" für Klassenstufe {target_grade}.
|
||||
|
||||
Materialien:
|
||||
{material_text}
|
||||
|
||||
Erstelle einen Text mit 10-15 Lücken. Markiere Lücken mit *Wort*.
|
||||
|
||||
Formatiere als JSON:
|
||||
{{
|
||||
"title": "Titel des Lückentexts",
|
||||
"text": "Der Text mit *Lücken* markiert...",
|
||||
"hints": "Hilfreiche Hinweise"
|
||||
}}
|
||||
|
||||
Nur JSON zurückgeben."""
|
||||
|
||||
response = await self.generate_content(
|
||||
prompt=prompt,
|
||||
system_prompt="Du bist ein Experte für Lückentexte."
|
||||
)
|
||||
|
||||
import json
|
||||
import re
|
||||
json_match = re.search(r'\{.*\}', response, re.DOTALL)
|
||||
if json_match:
|
||||
return json.loads(json_match.group())
|
||||
return json.loads(response)
|
||||
|
||||
async def generate_drag_drop_exercise(
|
||||
self,
|
||||
topic: str,
|
||||
materials: List[Dict[str, Any]],
|
||||
target_grade: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate Drag-and-Drop exercise"""
|
||||
|
||||
material_text = self._format_materials(materials)
|
||||
|
||||
prompt = f"""Erstelle eine Drag-and-Drop Zuordnungsaufgabe zum Thema "{topic}" für Klassenstufe {target_grade}.
|
||||
|
||||
Materialien:
|
||||
{material_text}
|
||||
|
||||
Erstelle 3-4 Kategorien (Zonen) und 8-12 Elemente zum Zuordnen.
|
||||
|
||||
Formatiere als JSON:
|
||||
{{
|
||||
"title": "Titel der Aufgabe",
|
||||
"question": "Aufgabenstellung",
|
||||
"zones": [
|
||||
{{ "id": 1, "name": "Kategorie 1" }},
|
||||
{{ "id": 2, "name": "Kategorie 2" }}
|
||||
],
|
||||
"draggables": [
|
||||
{{ "id": 1, "text": "Element 1", "correctZoneId": 1 }},
|
||||
{{ "id": 2, "text": "Element 2", "correctZoneId": 2 }}
|
||||
]
|
||||
}}
|
||||
|
||||
Nur JSON zurückgeben."""
|
||||
|
||||
response = await self.generate_content(
|
||||
prompt=prompt,
|
||||
system_prompt="Du bist ein Experte für interaktive Lernaufgaben."
|
||||
)
|
||||
|
||||
import json
|
||||
import re
|
||||
json_match = re.search(r'\{.*\}', response, re.DOTALL)
|
||||
if json_match:
|
||||
return json.loads(json_match.group())
|
||||
return json.loads(response)
|
||||
|
||||
async def generate_memory_pairs(
|
||||
self,
|
||||
topic: str,
|
||||
materials: List[Dict[str, Any]],
|
||||
target_grade: str,
|
||||
num_pairs: int = 8
|
||||
) -> List[Dict[str, str]]:
|
||||
"""Generate Memory Game pairs"""
|
||||
|
||||
material_text = self._format_materials(materials)
|
||||
|
||||
prompt = f"""Erstelle {num_pairs} Memory-Paare zum Thema "{topic}" für Klassenstufe {target_grade}.
|
||||
|
||||
Materialien:
|
||||
{material_text}
|
||||
|
||||
Jedes Paar besteht aus zwei zusammengehörigen Begriffen/Konzepten.
|
||||
|
||||
Formatiere als JSON-Array:
|
||||
[
|
||||
{{ "card1": "Begriff 1", "card2": "Zugehöriger Begriff" }}
|
||||
]
|
||||
|
||||
Nur JSON zurückgeben."""
|
||||
|
||||
response = await self.generate_content(
|
||||
prompt=prompt,
|
||||
system_prompt="Du bist ein Experte für Memory-Spiele."
|
||||
)
|
||||
|
||||
import json
|
||||
import re
|
||||
json_match = re.search(r'\[.*\]', response, re.DOTALL)
|
||||
if json_match:
|
||||
return json.loads(json_match.group())
|
||||
return json.loads(response)
|
||||
|
||||
async def generate_timeline_events(
|
||||
self,
|
||||
topic: str,
|
||||
materials: List[Dict[str, Any]],
|
||||
target_grade: str
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Generate Timeline events"""
|
||||
|
||||
material_text = self._format_materials(materials)
|
||||
|
||||
prompt = f"""Erstelle eine Timeline mit 5-8 Ereignissen zum Thema "{topic}" für Klassenstufe {target_grade}.
|
||||
|
||||
Materialien:
|
||||
{material_text}
|
||||
|
||||
Formatiere als JSON-Array:
|
||||
[
|
||||
{{
|
||||
"year": "Jahr oder Zeitpunkt",
|
||||
"title": "Ereignis Titel",
|
||||
"description": "Kurze Beschreibung"
|
||||
}}
|
||||
]
|
||||
|
||||
Nur JSON zurückgeben."""
|
||||
|
||||
response = await self.generate_content(
|
||||
prompt=prompt,
|
||||
system_prompt="Du bist ein Experte für chronologische Darstellungen."
|
||||
)
|
||||
|
||||
import json
|
||||
import re
|
||||
json_match = re.search(r'\[.*\]', response, re.DOTALL)
|
||||
if json_match:
|
||||
return json.loads(json_match.group())
|
||||
return json.loads(response)
|
||||
|
||||
async def generate_presentation_slides(
|
||||
self,
|
||||
topic: str,
|
||||
materials: List[Dict[str, Any]],
|
||||
target_grade: str,
|
||||
num_slides: int = 5
|
||||
) -> List[Dict[str, str]]:
|
||||
"""Generate Presentation slides"""
|
||||
|
||||
material_text = self._format_materials(materials)
|
||||
|
||||
prompt = f"""Erstelle {num_slides} Präsentationsfolien zum Thema "{topic}" für Klassenstufe {target_grade}.
|
||||
|
||||
Materialien:
|
||||
{material_text}
|
||||
|
||||
Formatiere als JSON-Array:
|
||||
[
|
||||
{{
|
||||
"title": "Folien Titel",
|
||||
"content": "Folien Inhalt (2-4 Sätze)",
|
||||
"backgroundColor": "#ffffff"
|
||||
}}
|
||||
]
|
||||
|
||||
Nur JSON zurückgeben."""
|
||||
|
||||
response = await self.generate_content(
|
||||
prompt=prompt,
|
||||
system_prompt="Du bist ein Experte für Präsentationen."
|
||||
)
|
||||
|
||||
import json
|
||||
import re
|
||||
json_match = re.search(r'\[.*\]', response, re.DOTALL)
|
||||
if json_match:
|
||||
return json.loads(json_match.group())
|
||||
return json.loads(response)
|
||||
|
||||
def _format_materials(self, materials: List[Dict[str, Any]]) -> str:
|
||||
"""Format materials for prompt"""
|
||||
if not materials:
|
||||
return "Keine Materialien vorhanden."
|
||||
|
||||
formatted = []
|
||||
for i, material in enumerate(materials, 1):
|
||||
formatted.append(f"Material {i} ({material.get('type', 'unknown')}):")
|
||||
formatted.append(material.get('content', '')[:2000]) # Limit content
|
||||
formatted.append("")
|
||||
|
||||
return "\n".join(formatted)
|
||||
Reference in New Issue
Block a user