""" AI Processor - Mindmap Generator Generate mindmaps for learning posters. """ from pathlib import Path import json import logging import math import os import requests from ..config import BEREINIGT_DIR, get_openai_api_key logger = logging.getLogger(__name__) def generate_mindmap_data(analysis_path: Path) -> dict: """ Extract technical terms from analysis and group them for a mindmap. Args: analysis_path: Path to *_analyse.json file Returns: Dictionary with mindmap structure: { "topic": "Main topic", "subject": "Subject", "categories": [ { "name": "Category", "color": "#hexcolor", "emoji": "🔬", "terms": [ {"term": "Term", "explanation": "Short explanation"} ] } ] } """ if not analysis_path.exists(): raise FileNotFoundError(f"Analysedatei nicht gefunden: {analysis_path}") try: data = json.loads(analysis_path.read_text(encoding="utf-8")) except json.JSONDecodeError as e: raise RuntimeError(f"Analyse-Datei enthaelt kein gueltiges JSON: {analysis_path}\n{e}") from e title = data.get("title") or "Arbeitsblatt" subject = data.get("subject") or "" canonical_text = data.get("canonical_text") or "" tasks = data.get("tasks", []) or [] # Collect all text for analysis all_text = canonical_text for task in tasks: if task.get("description"): all_text += "\n" + task.get("description") if task.get("text_with_gaps"): all_text += "\n" + task.get("text_with_gaps") if not all_text.strip(): return { "topic": title, "subject": subject, "categories": [] } # AI-based extraction of technical terms api_key = get_openai_api_key() prompt = f"""Analysiere diesen Schultext und extrahiere alle Fachbegriffe fuer eine kindgerechte Lern-Mindmap. THEMA: {title} FACH: {subject} TEXT: {all_text[:3000]} AUFGABE: 1. Identifiziere das Hauptthema (ein einzelnes Wort oder kurzer Begriff) 2. Finde ALLE Fachbegriffe und gruppiere sie in 3-6 sinnvolle Kategorien 3. Gib fuer jeden Begriff eine kurze, kindgerechte Erklaerung (max 10 Woerter) 4. Waehle fuer jede Kategorie ein passendes Emoji und eine Farbe Antworte NUR mit diesem JSON-Format: {{ "topic": "Hauptthema (z.B. 'Das Auge')", "categories": [ {{ "name": "Kategoriename", "emoji": "passendes Emoji", "color": "#Hexfarbe (bunt, kindgerecht)", "terms": [ {{"term": "Fachbegriff", "explanation": "Kurze Erklaerung"}} ] }} ] }} WICHTIG: - Verwende kindgerechte, einfache Sprache - Bunte, froehliche Farben: #FF6B6B, #4ECDC4, #45B7D1, #96CEB4, #FFEAA7, #DDA0DD, #98D8C8 - Passende Emojis fuer jede Kategorie - Mindestens 3 Begriffe pro Kategorie wenn moeglich - Maximal 6 Kategorien""" try: # Try Claude first claude_key = os.environ.get("ANTHROPIC_API_KEY") if claude_key: import anthropic client = anthropic.Anthropic(api_key=claude_key) response = client.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=2000, messages=[{"role": "user", "content": prompt}] ) result_text = response.content[0].text else: # Fallback to OpenAI logger.info("Claude Mindmap-Generierung fehlgeschlagen, nutze OpenAI: ANTHROPIC_API_KEY ist nicht gesetzt.") url = "https://api.openai.com/v1/chat/completions" headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"} payload = { "model": "gpt-4o-mini", "messages": [ {"role": "system", "content": "Du bist ein Experte fuer kindgerechte Lernmaterialien."}, {"role": "user", "content": prompt} ], "max_tokens": 2000, "temperature": 0.7 } resp = requests.post(url, headers=headers, json=payload, timeout=60) resp.raise_for_status() result_text = resp.json()["choices"][0]["message"]["content"] # Extract JSON result_text = result_text.strip() if result_text.startswith("```"): result_text = result_text.split("```")[1] if result_text.startswith("json"): result_text = result_text[4:] result_text = result_text.strip() mindmap_data = json.loads(result_text) mindmap_data["subject"] = subject return mindmap_data except Exception as e: logger.error(f"Mindmap-Generierung fehlgeschlagen: {e}") return { "topic": title, "subject": subject, "categories": [] } def generate_mindmap_html(mindmap_data: dict, format: str = "a3") -> str: """ Generate a child-friendly HTML/SVG mindmap poster. Args: mindmap_data: Dictionary from generate_mindmap_data() format: "a3" for A3 poster (default) or "a4" for A4 view Returns: HTML string with SVG mindmap """ topic = mindmap_data.get("topic", "Thema") subject = mindmap_data.get("subject", "") categories = mindmap_data.get("categories", []) # Format-specific settings if format.lower() == "a4": page_size = "A4 landscape" svg_width = 1100 svg_height = 780 radius = 250 else: # a3 (default) page_size = "A3 landscape" svg_width = 1400 svg_height = 990 radius = 320 # If no categories, show placeholder if not categories: return f"""
Noch keine Daten vorhanden. Bitte zuerst das Arbeitsblatt analysieren.
""" num_categories = len(categories) center_x = svg_width // 2 center_y = svg_height // 2 # Calculate positions of categories in a circle category_positions = [] for i, cat in enumerate(categories): angle = (2 * math.pi * i / num_categories) - (math.pi / 2) # Start at top x = center_x + radius * math.cos(angle) y = center_y + radius * math.sin(angle) category_positions.append({ "x": x, "y": y, "angle": angle, "data": cat }) html = _get_mindmap_html_header(topic, subject, page_size, svg_width, svg_height) # Draw connection lines for pos in category_positions: color = pos["data"].get("color", "#4ECDC4") html += f"""