From b4c686f74fb04f4aaf113cc1eb284d4f7e664ea3 Mon Sep 17 00:00:00 2001 From: team3 Date: Sun, 21 Jun 2026 22:06:02 +0200 Subject: [PATCH] update --- backend/config.py | 5 +--- backend/elements.py | 2 +- backend/guide.py | 26 +++++++++---------- backend/models.py | 3 --- backend/regeln.py | 15 +++++------ frontend/src/App.vue | 2 +- frontend/src/components/BausteinPanel.vue | 3 +-- .../src/components/BausteineUebersicht.vue | 6 ++--- frontend/src/components/GraphView.vue | 12 ++++++--- frontend/src/components/TopicDetail.vue | 12 +++------ frontend/src/components/TopicSidebar.vue | 6 ----- templates/Prompt/Baustein-Grafik-Check.md | 2 +- templates/Prompt/Baustein-Grafik.md | 4 ++- 13 files changed, 41 insertions(+), 57 deletions(-) diff --git a/backend/config.py b/backend/config.py index c433520..bae2d3f 100644 --- a/backend/config.py +++ b/backend/config.py @@ -71,10 +71,7 @@ TIMEOUTS = { # Auswahl-Auftrag je Format: (Mindest-Anteil, Maximal-Anteil, Mindestanzahl, Zweck). FORMAT_ANTEIL = { - "MiniGuide": (0.05, 0.10, 8, "einen kompakten Anfänger-Guide — der schnelle Einstieg ins Thema"), - "Guide": (0.25, 0.35, 20, "einen ausführlichen Anfänger-Guide — ein solides Fundament im Thema"), - "ProGuide": (0.0, 1.0, 0, "einen fokussierten Guide — alles Relevante ohne Randthemen"), - "FullGuide": (0.90, 1.00, 0, "einen Komplett-Guide — das ganze Thema"), + "Guide": (0.0, 1.0, 0, "einen fokussierten Guide — alles Relevante ohne Randthemen"), } # Provider-Stacks: komplett unabhängig, einer kann jederzeit entfernt werden. diff --git a/backend/elements.py b/backend/elements.py index 577033e..7dfafe3 100644 --- a/backend/elements.py +++ b/backend/elements.py @@ -71,7 +71,7 @@ def _topic_context(topic: str, limit: int = 12000) -> str: bp = bausteine_path(topic) if bp.exists(): parts.append(bp.read_text(encoding="utf-8")) - for fmt in ("FullGuide", "ProGuide", "Guide", "MiniGuide", "OnePager"): + for fmt in ("Guide", "OnePager"): content = _json_datei(guide_content_path(topic, fmt)) if content: for ch in content.get("chapters", []): diff --git a/backend/guide.py b/backend/guide.py index 709324c..0d3a188 100644 --- a/backend/guide.py +++ b/backend/guide.py @@ -408,17 +408,17 @@ async def _generate_sections( "Wähle, was diesem Zweck dient — lass weg, was dafür nicht nötig ist." ) - # Subbausteine je Baustein (Sidecar) — früh geladen: ProGuide wählt darüber aus, - # Nicht-FullGuide filtert später Rand-Subbausteine. Fehlt sie → {} (Fallback). + # Subbausteine je Baustein (Sidecar) — früh geladen: „Guide" wählt darüber aus + # (Bausteine mit ≥1 relevantem Sub) und filtert später Rand-Subs. Fehlt sie → {} (Fallback). subs_raw = _load_subbausteine(topic) - # Schritt 0: Auswahl. ProGuide ist deterministisch — alle Bausteine mit ≥1 + # Schritt 0: Auswahl. „Guide" ist deterministisch — alle Bausteine mit ≥1 # relevantem Subbaustein. Fallback (keine Relevanz-Daten): alle Bausteine. - if format_name == "ProGuide": + if format_name == "Guide": auswahl = [num for num in entries if any(s.get("relevanz") == "relevant" for s in subs_raw.get(_titel(entries[num]), []))] if not auswahl: - _log(topic, "ProGuide: keine Relevanz-Daten — alle Bausteine genommen") + _log(topic, "Guide: keine Relevanz-Daten — alle Bausteine genommen") auswahl = list(entries) else: # Auswahl-Konsens (5 Agenten → Voting → Mapping → Klärungs-Loop) @@ -501,16 +501,14 @@ async def _generate_sections( # dieselbe Aufteilung: 1 Agent je ~30 Bausteine (gedeckelt). total_sections = sum(len(c["nums"]) for c in plan) chunks = _split_chunks(plan, min(WRITER_MAX, max(1, math.ceil(total_sections / WRITER_SECTIONS)))) - # Subbausteine je Baustein. Rand-Subbausteine fliegen aus allen Guides AUSSER - # FullGuide (Fokus). Subs ohne Relevanz-Feld (Alt-Themen) bleiben erhalten. + # Subbausteine je Baustein. Rand-Subbausteine fliegen aus dem Guide (Fokus auf + # Relevantes). Subs ohne Relevanz-Feld (Alt-Themen) bleiben erhalten. # Fehlt die Sidecar → leer → Inhalte/Writer laufen auf Baustein-Granularität. - subs_by_titel = subs_raw - if format_name != "FullGuide": - subs_by_titel = { - t: [s for s in subs if s.get("relevanz") != "rand"] - for t, subs in subs_raw.items() - } - subs_by_titel = {t: subs for t, subs in subs_by_titel.items() if subs} + subs_by_titel = { + t: [s for s in subs if s.get("relevanz") != "rand"] + for t, subs in subs_raw.items() + } + subs_by_titel = {t: subs for t, subs in subs_by_titel.items() if subs} zuteilungen = [_zuteilung_subs(chunk, entries, subs_by_titel) for chunk in chunks] chunk_sizes = [sum(len(c["nums"]) for c in chunk) for chunk in chunks] writer_count = len(chunks) diff --git a/backend/models.py b/backend/models.py index d60731e..8e18399 100644 --- a/backend/models.py +++ b/backend/models.py @@ -3,10 +3,7 @@ from typing import Literal FormatType = Literal[ "OnePager", - "MiniGuide", "Guide", - "ProGuide", - "FullGuide", ] ProviderType = Literal["claude", "minimax", "lokal"] diff --git a/backend/regeln.py b/backend/regeln.py index da5624e..3001156 100644 --- a/backend/regeln.py +++ b/backend/regeln.py @@ -18,12 +18,11 @@ from paths import bausteine_path, guide_content_path from textkit import _norm_titel MAX_OFFENE_GUIDES = 3 -# Lineare Progression: jedes Format schaltet das nächste frei, sobald ALLE Bausteine -# der Vorstufe das geforderte Niveau (Score-Meilenstein 10/20/30) erreicht haben. -VORSTUFE = {"Guide": "MiniGuide", "ProGuide": "Guide", "FullGuide": "ProGuide"} -# absolviert=6 · verstanden=12 · gemeistert=18 (siehe lernen.NOETIG/MASTERY/MEISTERN). -FREISCHALT_LEVEL = {"Guide": "absolviert", "ProGuide": "verstanden", "FullGuide": "gemeistert"} -FORMATE = ("MiniGuide", "Guide", "ProGuide", "FullGuide") +# Nur noch EIN Format „Guide" (alle relevanten Bausteine, Prüfung 0–30). Keine Progression, +# keine Vorstufe → der Guide ist immer freischaltbar. „Rest"/OnePager separat. +VORSTUFE: dict[str, str] = {} +FREISCHALT_LEVEL: dict[str, str] = {} +FORMATE = ("Guide",) # Geforderter Score je Freischalt-Niveau — nur für die Sperr-Meldung. _LEVEL_WORT = { @@ -106,8 +105,8 @@ def ist_absolviert(topic: str, fmt: str, guides: list[dict], progress: dict[str, def thema_abgeschlossen(topic: str, guides: list[dict], progress: dict[str, set[str]], levels: dict[str, dict[str, set[str]]]) -> bool: - """Thema fertig: neuester fertiger FullGuide, alle Bausteine gemeistert (25)?""" - return ist_level(topic, "FullGuide", guides, progress, levels["gemeistert"]) + """Thema fertig: neuester fertiger Guide, alle Bausteine gemeistert (18)?""" + return ist_level(topic, "Guide", guides, progress, levels["gemeistert"]) def formate_stats(guides: list[dict], progress: dict[str, set[str]], levels: dict[str, dict[str, set[str]]]) -> dict: diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 69adad6..6aa8067 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -181,7 +181,7 @@ async function loadBausteine() { } } -const FORMAT_ORDER = ['OnePager', 'MiniGuide', 'Guide', 'ProGuide', 'FullGuide'] +const FORMAT_ORDER = ['Guide', 'OnePager'] function autoPreview() { const map = doneByFormat.value diff --git a/frontend/src/components/BausteinPanel.vue b/frontend/src/components/BausteinPanel.vue index 4f07276..6266bf8 100644 --- a/frontend/src/components/BausteinPanel.vue +++ b/frontend/src/components/BausteinPanel.vue @@ -87,7 +87,6 @@ const bandZiel = computed(() => { return ABSOLVIERT }) const FORM_NAME = { quiz: 'Quiz', lueckentext: 'Lückentext', erklaeren: 'Erklären' } -const NAECHSTES_FORMAT = { 6: 'Guide', 12: 'ProGuide', 18: 'FullGuide' } // --- Toggle-Bereich --- const activeTab = ref(props.mode === 'full' ? props.initialTab : null) // null | 'chat' | 'pruefung' @@ -702,7 +701,7 @@ function onPruefKey(e) {

- +

diff --git a/frontend/src/components/GraphView.vue b/frontend/src/components/GraphView.vue index c1509de..829946a 100644 --- a/frontend/src/components/GraphView.vue +++ b/frontend/src/components/GraphView.vue @@ -122,12 +122,16 @@ watch(() => props.graph, build, { immediate: true }) background: var(--panel, #fff); color: var(--text, #111); font-size: 0.8rem; - line-height: 1.3; + line-height: 1.35; text-align: center; - max-width: 240px; + max-width: 300px; + /* Langer Text/Formel umbricht, statt im Knoten abgeschnitten zu werden. */ + white-space: normal; + overflow-wrap: anywhere; } -/* KaTeX im Knoten nicht umbrechen lassen */ -.gv-node :deep(.katex) { white-space: nowrap; } +/* KaTeX im Knoten umbrechen lassen + geerbtes Klippen (markdown .katex-display) neutralisieren. */ +.gv-node :deep(.katex) { white-space: normal; } +.gv-node :deep(.katex-display) { overflow: visible; margin: 0; } .graph-view :deep(.vue-flow__edge-text) { font-size: 0.72rem; fill: var(--text-muted, #666); } .graph-view :deep(.vue-flow__edge-textbg) { fill: var(--panel, #fff); } diff --git a/frontend/src/components/TopicDetail.vue b/frontend/src/components/TopicDetail.vue index b908faa..73966aa 100644 --- a/frontend/src/components/TopicDetail.vue +++ b/frontend/src/components/TopicDetail.vue @@ -93,14 +93,8 @@ const fortschritt = computed(() => { return { total: bausteine.value.length, absolviert: a, verstanden: v, gemeistert: g } }) -// Score-Deckel folgt dem höchsten erstellten Format: Mini→6, Guide→12, ProGuide→18, FullGuide→30. -const cap = computed(() => { - const d = props.doneByFormat || {} - if (d.FullGuide) return 30 - if (d.ProGuide) return 18 - if (d.Guide) return 12 - return 6 -}) +// Ein Format „Guide" → Prüfung läuft voll 0–30. Ohne fertigen Guide kein Deckel relevant (6). +const cap = computed(() => (props.doneByFormat?.Guide ? 30 : 6)) // --- Chat (Mechanik in useChat; Kontext-Extraktion bleibt hier) --- const chat = useChat((msgs) => { @@ -188,7 +182,7 @@ function extractContext() {

{{ previewGuide.topic }}

{{ previewGuide.format }} - ✓ Thema abgeschlossen + ✓ Thema abgeschlossen
{ const fmt = (k) => `${f[k]?.absolviert ?? 0}/${f[k]?.erstellt ?? 0}` return [ { label: 'Themen', value: String(props.stats.themen ?? 0), title: 'Themen inkl. Projekte' }, - { label: 'MiniGuides', value: fmt('MiniGuide'), title: 'absolviert/erstellt' }, { label: 'Guides', value: fmt('Guide'), title: 'absolviert/erstellt' }, - { label: 'ProGuides', value: fmt('ProGuide'), title: 'absolviert/erstellt' }, - { label: 'FullGuides', value: fmt('FullGuide'), title: 'absolviert/erstellt' }, ] }) const formats = [ { key: 'OnePager', label: 'OnePager' }, - { key: 'MiniGuide', label: 'MiniGuide' }, { key: 'Guide', label: 'Guide' }, - { key: 'ProGuide', label: 'ProGuide' }, - { key: 'FullGuide', label: 'FullGuide' }, ] const bausteineState = computed(() => { diff --git a/templates/Prompt/Baustein-Grafik-Check.md b/templates/Prompt/Baustein-Grafik-Check.md index 4eaec12..4903361 100644 --- a/templates/Prompt/Baustein-Grafik-Check.md +++ b/templates/Prompt/Baustein-Grafik-Check.md @@ -11,7 +11,7 @@ PRÜFE auf: - **Inhalt:** die relevanten Subbausteine kommen vor; Richtung/Kanten geben die Beziehungen korrekt wieder; nichts Erfundenes. - **Mathe:** Symbole stehen als echte KaTeX-Notation in Dollarzeichen (`$\Sigma$`, `$w \in L$`), nicht als Wort ausgeschrieben. Korrigiere Verstöße. - **Kanten-Label:** nur ein kurzes Wort (ja/nein/kodiert), **keine Formeln/Dollarzeichen** in Kanten — Formeln gehören in die Knoten. -- **Knappheit:** Knotentexte kurz. +- **Knappheit:** Knotentexte SEHR kurz (≤ 6 Wörter / eine kompakte Inline-Formel). **Verboten:** `\\`, `\text{…}`, `$$…$$`, mehrteilige Formeln. Kürze lange Knoten (z.B. volle Formaldefinitionen) auf den Kern ein. Korrigiere alle Mängel. Ist die Grafik bereits gut, gib sie unverändert zurück. {extra} diff --git a/templates/Prompt/Baustein-Grafik.md b/templates/Prompt/Baustein-Grafik.md index 091bee6..5918ad2 100644 --- a/templates/Prompt/Baustein-Grafik.md +++ b/templates/Prompt/Baustein-Grafik.md @@ -10,7 +10,9 @@ AUFGABE: - Baue EINEN gerichteten Graphen: **Knoten** = die Subbausteine (plus ggf. Verbindungs-Knoten wie Start/Ende/Verzweigung), **Kanten** = ihre Beziehungen (Reihenfolge, Abhängigkeit, Ober-/Unterbegriff, Ja/Nein). - Decke die relevanten Subbausteine ab. Erfinde keine Inhalte außerhalb des Bausteins. - Wähle die Richtung passend: `TB` (oben→unten, für Abläufe/Hierarchien) oder `LR` (links→rechts, für Ketten). -- **Knotentext kurz** (Stichwort/kurzer Satz). Mathematik schreibst du als **echte Notation** mit KaTeX in Dollarzeichen: `$\Sigma$`, `$(L, \Sigma)$`, `$w \in L$`, `$2^n$`. Niemals Symbole als Wort ausschreiben (nicht „Sigma", nicht „Vereinigung"). +- **Knotentext SEHR kurz** (ein Stichwort oder eine **kompakte** Inline-Formel, Richtwert ≤ 6 Wörter). Lange Formaldefinitionen gehören NICHT in den Graph — die stehen im Guide-Text. +- Mathematik als **echte Notation** mit KaTeX in Dollarzeichen: `$\Sigma$`, `$(L, \Sigma)$`, `$w \in L$`, `$2^n$`. Niemals Symbole als Wort ausschreiben (nicht „Sigma", nicht „Vereinigung"). +- **Verboten im Knotentext:** `\\` (Zeilenumbruch), `\text{…}`, `$$…$$` (Display-Mathe), mehrteilige/verkettete Formeln. Eine Formel pro Knoten, knapp. - Kanten-Label (`text`) ist optional und **nur ein kurzes Wort** (z.B. „kodiert", „ja", „nein") — **keine Formeln/Dollarzeichen** in Kanten. Formeln gehören in die Knoten. - Jede `id` ist ein kurzes Kürzel (a–z, 0–9), eindeutig. Jede Kante verweist mit `von`/`nach` auf existierende Knoten-`id`s. {extra}