import json import os from datetime import datetime import httpx MINIMAX_API_KEY = os.environ.get("MINIMAX_API_KEY", "") MINIMAX_MODEL = "MiniMax-M2.5" MINIMAX_ENDPOINT = "https://api.minimaxi.chat/v1/text/chatcompletion_v2" SYSTEM_PROMPT = """Du bist ein ADHS-freundlicher Study Buddy. Deine Aufgabe: Zerlege Lernaufgaben in kleine, machbare 5-Minuten-Bloecke. Regeln: - Jeder Block hat einen eigenen Titel der sich wie ein FRISCHER ANFANG anfuehlt (z.B. "Oeffne die Seite und lies den ersten Absatz") - NICHT: "Weiterlesen", "Aufgabe 3 von 10" — das toetet die Motivation - Jeder Block hat einen kurzen Kontext-Satz: was genau tun, wo anfangen - Buddy-Ton: supportiv, keine Vorwuerfe, kein Druck - Im Panik-Modus (<48h): nur 3-5 Steps, nur das Absolut-Wichtigste, direkter Ton Antworte NUR als JSON array (kein Markdown, kein Text drumrum): [{"title": "...", "context": "...", "duration_minutes": 5}]""" def _dummy_steps(title: str, panic: bool) -> list[dict]: """Fallback steps when no API key is configured.""" if panic: return [ { "title": f"Oeffne deine Unterlagen zu '{title}'", "context": "Lies nur die Ueberschriften und fett markierten Begriffe. Nicht mehr.", "duration_minutes": 5, }, { "title": "Die wichtigsten 3 Punkte notieren", "context": "Schreib auf ein leeres Blatt: Was muss ich auf jeden Fall wissen?", "duration_minutes": 5, }, { "title": "Einen Durchgang — fertig", "context": "Lies deine 3 Punkte laut vor. Das war's. Du bist bereit.", "duration_minutes": 5, }, ] return [ { "title": f"Starte mit '{title}' — nur die erste Seite", "context": "Oeffne das Material und lies genau eine Seite. Kein Druck weiterzumachen.", "duration_minutes": 5, }, { "title": "Notiere eine Frage die du hast", "context": "Was ist noch unklar? Schreib es auf. Das hilft deinem Gehirn sich zu orientieren.", "duration_minutes": 5, }, { "title": "Noch eine Seite — du bist schon drin", "context": "Du hast den schwierigsten Teil hinter dir (das Anfangen). Lies weiter.", "duration_minutes": 5, }, { "title": "Kurze Zusammenfassung in eigenen Worten", "context": "Was hast du gerade gelesen? Ein Satz reicht. Kein perfekter Satz noetig.", "duration_minutes": 5, }, ] async def generate_micro_steps( deadline_title: str, deadline_description: str, due_date: datetime, hours_until_due: float, ) -> list[dict]: panic = hours_until_due < 48 if not MINIMAX_API_KEY: return _dummy_steps(deadline_title, panic) if hours_until_due < 0: mode_hint = "PANIK-MODUS: Deadline ist ueberschritten! Sofortmassnahmen. Maximal 4 Steps, nur das Absolut-Wichtigste." elif panic: mode_hint = f"PANIK-MODUS: Nur {int(hours_until_due)}h bis zur Deadline. Maximal 4 Steps, nur das Wichtigste." else: mode_hint = f"Normale Vorbereitung. Deadline in {int(hours_until_due)}h. Generiere 4-6 Steps." user_message = ( f"Aufgabe: {deadline_title}\n" f"Details: {deadline_description or 'keine weiteren Details'}\n" f"Faellig: {due_date.strftime('%d.%m.%Y %H:%M')}\n" f"{mode_hint}" ) async with httpx.AsyncClient(timeout=60.0) as client: response = await client.post( MINIMAX_ENDPOINT, headers={ "Authorization": f"Bearer {MINIMAX_API_KEY}", "Content-Type": "application/json", }, json={ "model": MINIMAX_MODEL, "max_tokens": 1024, "temperature": 0.3, "messages": [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_message}, ], }, ) response.raise_for_status() data = response.json() msg = data["choices"][0]["message"] raw = (msg.get("content") or "").strip() if not raw: # M2.5 reasoning fallback reasoning = (msg.get("reasoning_content") or "").strip() if reasoning: raw = reasoning # Strip markdown code fences if present if raw.startswith("```"): raw = raw.split("```")[1] if raw.startswith("json"): raw = raw[4:] raw = raw.strip() steps = json.loads(raw) # Ensure required fields for step in steps: step.setdefault("duration_minutes", 5) return steps