diff --git a/frontend/src/components/BausteinPanel.vue b/frontend/src/components/BausteinPanel.vue index 5283078..ecadca8 100644 --- a/frontend/src/components/BausteinPanel.vue +++ b/frontend/src/components/BausteinPanel.vue @@ -33,24 +33,26 @@ const st = computed(() => props.status || { gute_antworten: 0, streak: 0, absolv const MODI = ['quiz_leicht', 'quiz_schwer', 'lueck_leicht', 'lueck_schwer', 'erklaeren'] const score = computed(() => st.value.gute_antworten || 0) const schwerWahl = ref(false) // leicht/schwer in den Bändern unter 19 (ALT+E) -// Form wechselt NACH dem Meilenstein (>): Quiz 0–6, Lückentext 7–12, Erklären 13–18, -// Zufall 19–30 (jede Frage ein zufälliger Modus). 'zufall' = Platzhalter; das gezogene -// Objekt trägt die echte Form/Schwierigkeit. -const aktiverModus = computed(() => { - if (!musterModus.value) return 'erklaeren' - if (score.value > GEMEISTERT) return 'zufall' - if (score.value > VERSTANDEN) return 'erklaeren' - if (score.value > ABSOLVIERT) return schwerWahl.value ? 'lueck_schwer' : 'lueck_leicht' - return schwerWahl.value ? 'quiz_schwer' : 'quiz_leicht' -}) -const imZufall = computed(() => aktiverModus.value === 'zufall') -// Reines Band aus dem aktuellen Score (ohne Schwierigkeit) — für den Abschnittswechsel. -const aktuellesBand = computed(() => { - if (score.value > GEMEISTERT) return 'zufall' - if (score.value > VERSTANDEN) return 'erklaeren' - if (score.value > ABSOLVIERT) return 'lueckentext' +// Band aus einem Score: Lückentext ab 6, Erklären ab 12, Zufall ab 18 (Meilenstein-inklusiv). +function bandFuer(s) { + if (s >= GEMEISTERT) return 'zufall' + if (s >= VERSTANDEN) return 'erklaeren' + if (s >= ABSOLVIERT) return 'lueckentext' return 'quiz' +} +// Am Format-Max: Score eingefroren („Max"). Gilt auch bei cap 30 (FullGuide, endgültig). +const amDeckel = computed(() => score.value >= props.cap) +// Aktuelles Band: am Deckel bleibt das Band des erreichten Formats (cap−1), sonst score-basiert. +// Ohne Frage-Muster (alter Baustein) gibt es nur Erklären. +const aktuellesBand = computed(() => musterModus.value ? bandFuer(amDeckel.value ? props.cap - 1 : score.value) : 'erklaeren') +// 'zufall' = Platzhalter; das gezogene Objekt trägt die echte Form/Schwierigkeit. +const aktiverModus = computed(() => { + const b = aktuellesBand.value + if (b === 'lueckentext') return schwerWahl.value ? 'lueck_schwer' : 'lueck_leicht' + if (b === 'quiz') return schwerWahl.value ? 'quiz_schwer' : 'quiz_leicht' + return b // 'erklaeren' | 'zufall' }) +const imZufall = computed(() => aktuellesBand.value === 'zufall') const aktiveForm = computed(() => { const m = aktiverModus.value if (m === 'zufall') return 'quiz' // Platzhalter fürs idle-Widget; Objekt überschreibt @@ -76,15 +78,13 @@ const modusRegel = computed(() => { if (zeigeForm.value === 'lueckentext') return aktSchwer.value ? 'frei tippen · +3/−1' : 'Begriff aus 4 · +1/−1' return '+1/+2/+3 · Nachfragen max +1 · schwach −1' }) -// Ende des aktuellen Bands (nächster Meilenstein) und ob der Score am Format-Deckel klebt. +// Ende des aktuellen Bands (nächster Meilenstein). const bandZiel = computed(() => { - if (score.value > GEMEISTERT) return VOLL - if (score.value > VERSTANDEN) return GEMEISTERT - if (score.value > ABSOLVIERT) return VERSTANDEN + if (score.value >= GEMEISTERT) return VOLL + if (score.value >= VERSTANDEN) return GEMEISTERT + if (score.value >= ABSOLVIERT) return VERSTANDEN return ABSOLVIERT }) -// Am Format-Max: Score eingefroren („Max"). Gilt auch bei cap 30 (FullGuide, endgültig). -const amDeckel = computed(() => score.value >= props.cap) const FORM_NAME = { quiz: 'Quiz', lueckentext: 'Lückentext', erklaeren: 'Erklären' } const NAECHSTES_FORMAT = { 6: 'Guide', 12: 'ProGuide', 18: 'FullGuide' } @@ -207,7 +207,15 @@ function objText(o) { return (o && (o.frage || o.satz)) || '' } // Lückensatz rendern: an „___" splitten, Teile inline (mit $…$-Mathe), Lücke als Span. // So bleibt die Lücke erhalten und Markdown fasst die Unterstriche nicht an. function lueckSatzHtml(satz) { - return String(satz || '').split('___').map((t) => renderMarkdownInline(t)).join('______') + satz = String(satz || '') + const m = satz.match(/_{3,}/) // erste Lücke (drei oder mehr Unterstriche) + if (!m) return renderMarkdownInline(satz) + // Liegt die Lücke INNERHALB einer $…$-Formel (ungerade Zahl $ davor)? Dann nicht splitten + // (das zerreißt die Formel) — stattdessen durch eine KaTeX-Linie ersetzen, Satz am Stück rendern. + const inFormel = ((satz.slice(0, m.index).match(/\$/g) || []).length % 2) === 1 + if (inFormel) return renderMarkdownInline(satz.replace(/_{3,}/, '\\rule{2.5em}{0.4pt}')) + // Lücke im Text: splitten + Lücken-Span. + return satz.split(/_{3,}/).map((t) => renderMarkdownInline(t)).join('______') } function istBekannt(o) { diff --git a/templates/Prompt/Baustein-Lueckentext.md b/templates/Prompt/Baustein-Lueckentext.md index 80878ad..c0d9c66 100644 --- a/templates/Prompt/Baustein-Lueckentext.md +++ b/templates/Prompt/Baustein-Lueckentext.md @@ -12,6 +12,9 @@ KOMPAKTE FASSUNG (Merksätze, falls vorhanden): AUFGABE: - Schreibe EINEN kurzen Aussagesatz (höchstens ~15 Wörter, keine Nebensatz-Kaskade). - Ersetze den **Schlüsselbegriff** durch `___` (genau eine Lücke). +- Die Lücke ist ein **Fachbegriff als Wort** (z. B. „Alphabet", „polynomiell") — KEIN + mathematisches Symbol oder Formelteil (nicht `L_2`, nicht `f(w)`). Sie steht **außerhalb** + von `$…$` (nie innerhalb einer Formel). - Die Lücke prüft aktives Abrufen — sie steht für einen zentralen Fachbegriff, nicht für ein Füllwort. - Der Satz gibt genug Kontext, dass die Lösung eindeutig ist (keine Raterei). - Fachbegriffe/Formeln dürfen als $…$ gesetzt werden (z. B. $V(w,c)$). diff --git a/templates/Prompt/Baustein-Lueckwahl.md b/templates/Prompt/Baustein-Lueckwahl.md index e42cbc0..52b3a06 100644 --- a/templates/Prompt/Baustein-Lueckwahl.md +++ b/templates/Prompt/Baustein-Lueckwahl.md @@ -11,6 +11,8 @@ KOMPAKTE FASSUNG (Merksätze, falls vorhanden): AUFGABE: - Schreibe EINEN kurzen Aussagesatz (höchstens ~15 Wörter). Ersetze den **Schlüsselbegriff** durch `___` (genau eine Lücke). +- Die Lücke ist ein **Fachbegriff als Wort** — KEIN mathematisches Symbol/Formelteil + (nicht `L_2`, nicht `f(w)`). Sie steht **außerhalb** von `$…$` (nie innerhalb einer Formel). - Biete GENAU VIER Begriffe als Optionen — **genau einer** passt in die Lücke. - Die drei falschen sind plausibel, aber klar falsch (Verwechslung, verwandter Begriff) — keine Albernheiten. - Optionen kurz (Begriff oder kurzer Halbsatz), ähnlich lang.