update
This commit is contained in:
@@ -69,9 +69,9 @@ BAUSTEINE_STEPS = (
|
|||||||
"Relevanz finden", "Relevanz wählen", "Relevanz klären",
|
"Relevanz finden", "Relevanz wählen", "Relevanz klären",
|
||||||
"Gliederung",
|
"Gliederung",
|
||||||
"Fragen finden", "Fragen wählen", "Fragen klären", "Fragen prüfen",
|
"Fragen finden", "Fragen wählen", "Fragen klären", "Fragen prüfen",
|
||||||
"Karteikarten", "Beispiele", "Diagramme",
|
"Karteikarten", "Beispiele",
|
||||||
)
|
)
|
||||||
ARTEFAKT_TYPEN = ("karteikarte", "beispiel", "diagramm")
|
ARTEFAKT_TYPEN = ("karteikarte", "beispiel")
|
||||||
|
|
||||||
|
|
||||||
def lade_quelle(topic: str) -> dict:
|
def lade_quelle(topic: str) -> dict:
|
||||||
@@ -206,7 +206,7 @@ def _bausteine_steps(topic: str) -> tuple:
|
|||||||
"Relevanz finden", "Relevanz wählen", "Relevanz klären",
|
"Relevanz finden", "Relevanz wählen", "Relevanz klären",
|
||||||
"Gliederung",
|
"Gliederung",
|
||||||
"Fragen finden", "Fragen wählen", "Fragen klären", "Fragen prüfen",
|
"Fragen finden", "Fragen wählen", "Fragen klären", "Fragen prüfen",
|
||||||
"Karteikarten", "Beispiele", "Diagramme",
|
"Karteikarten", "Beispiele",
|
||||||
)
|
)
|
||||||
mitte = base + (("Ergänzung",) if q["type"] == "projekt" else ()) + rest
|
mitte = base + (("Ergänzung",) if q["type"] == "projekt" else ()) + rest
|
||||||
return (("Quelle aufbereiten",) if q["type"] == "link" else ()) + mitte
|
return (("Quelle aufbereiten",) if q["type"] == "link" else ()) + mitte
|
||||||
@@ -235,7 +235,7 @@ PHASEN = (
|
|||||||
("Relevanz", ("Relevanz finden", "Relevanz wählen", "Relevanz klären")),
|
("Relevanz", ("Relevanz finden", "Relevanz wählen", "Relevanz klären")),
|
||||||
("Gliederung", ("Gliederung",)),
|
("Gliederung", ("Gliederung",)),
|
||||||
("Fragen", ("Fragen finden", "Fragen wählen", "Fragen klären", "Fragen prüfen")),
|
("Fragen", ("Fragen finden", "Fragen wählen", "Fragen klären", "Fragen prüfen")),
|
||||||
("Artefakte", ("Karteikarten", "Beispiele", "Diagramme")),
|
("Artefakte", ("Karteikarten", "Beispiele")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -330,7 +330,7 @@ def _resume_step(topic: str) -> int:
|
|||||||
# Gliederung fertig; Frage-Muster offen?
|
# Gliederung fertig; Frage-Muster offen?
|
||||||
if not _frage_muster_komplett(topic):
|
if not _frage_muster_komplett(topic):
|
||||||
return _step_idx(topic, "Fragen finden")
|
return _step_idx(topic, "Fragen finden")
|
||||||
# Fragen fertig; Lern-Artefakte (Karteikarten/Beispiele/Diagramme) offen?
|
# Fragen fertig; Lern-Artefakte (Karteikarten/Beispiele) offen?
|
||||||
if not _artefakte_komplett(files):
|
if not _artefakte_komplett(files):
|
||||||
return _step_idx(topic, "Karteikarten")
|
return _step_idx(topic, "Karteikarten")
|
||||||
return len(_bausteine_steps(topic))
|
return len(_bausteine_steps(topic))
|
||||||
@@ -402,7 +402,7 @@ def _reset_ab_phase(topic: str, label: str) -> None:
|
|||||||
p.unlink(missing_ok=True)
|
p.unlink(missing_ok=True)
|
||||||
|
|
||||||
# Phasen-Index: Quelle=0 · Inventar=1 · Subbausteine=2 · Fakten=3 · Stufen=4 · Relevanz=5 · Gliederung=6 · Fragen=7 · Artefakte=8
|
# Phasen-Index: Quelle=0 · Inventar=1 · Subbausteine=2 · Fakten=3 · Stufen=4 · Relevanz=5 · Gliederung=6 · Fragen=7 · Artefakte=8
|
||||||
if idx <= 8: # Artefakte (Karteikarten/Beispiele/Diagramme)
|
if idx <= 8: # Artefakte (Karteikarten/Beispiele)
|
||||||
files["artefakte"].unlink(missing_ok=True)
|
files["artefakte"].unlink(missing_ok=True)
|
||||||
glob_del("artefakt-*")
|
glob_del("artefakt-*")
|
||||||
if idx <= 7: # Fragen
|
if idx <= 7: # Fragen
|
||||||
@@ -1742,7 +1742,7 @@ async def _gliederung_block(ctx: GenContext, set_p, files: dict, entries: dict,
|
|||||||
return plan
|
return plan
|
||||||
|
|
||||||
|
|
||||||
# --- Lern-Artefakte (Karteikarten/Beispiele/Diagramme aus den Fakten) ---
|
# --- Lern-Artefakte (Karteikarten/Beispiele aus den Fakten) ---
|
||||||
|
|
||||||
def _karten_schema(data):
|
def _karten_schema(data):
|
||||||
"""{"karten":[{baustein,subbaustein,frage,antwort}]} → Liste (auch leer) · None bei kaputt."""
|
"""{"karten":[{baustein,subbaustein,frage,antwort}]} → Liste (auch leer) · None bei kaputt."""
|
||||||
@@ -1772,20 +1772,9 @@ def _beispiel_schema(data):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def _diagramm_schema(data):
|
_ARTEFAKT_SCHEMA = {"karteikarte": _karten_schema, "beispiel": _beispiel_schema}
|
||||||
"""{"diagramme":[{baustein,mermaid}]} → Liste (auch leer) · None bei kaputt."""
|
_ARTEFAKT_PROMPT = {"karteikarte": "Artefakt-Karteikarte", "beispiel": "Artefakt-Beispiel"}
|
||||||
if not isinstance(data, dict) or not isinstance(data.get("diagramme"), list):
|
_ARTEFAKT_SCHRITT = {"karteikarte": "Karteikarten", "beispiel": "Beispiele"}
|
||||||
return None
|
|
||||||
out = []
|
|
||||||
for e in data["diagramme"]:
|
|
||||||
if isinstance(e, dict) and (m := str(e.get("mermaid", "")).strip()):
|
|
||||||
out.append({"baustein": str(e.get("baustein", "")).strip(), "mermaid": m})
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
_ARTEFAKT_SCHEMA = {"karteikarte": _karten_schema, "beispiel": _beispiel_schema, "diagramm": _diagramm_schema}
|
|
||||||
_ARTEFAKT_PROMPT = {"karteikarte": "Artefakt-Karteikarte", "beispiel": "Artefakt-Beispiel", "diagramm": "Artefakt-Diagramm"}
|
|
||||||
_ARTEFAKT_SCHRITT = {"karteikarte": "Karteikarten", "beispiel": "Beispiele", "diagramm": "Diagramme"}
|
|
||||||
|
|
||||||
|
|
||||||
def _artefakte_komplett(files: dict) -> bool:
|
def _artefakte_komplett(files: dict) -> bool:
|
||||||
@@ -1853,10 +1842,10 @@ async def _artefakte_block(ctx: GenContext, set_p, files: dict, sidecar: dict, i
|
|||||||
|
|
||||||
|
|
||||||
async def _mirror_artefakte_db(topic: str, sidecar: dict, artefakte: dict) -> None:
|
async def _mirror_artefakte_db(topic: str, sidecar: dict, artefakte: dict) -> None:
|
||||||
"""Artefakte in die DB spiegeln. Karteikarte/Beispiel je Sub (sub_norm), Diagramm je Baustein (sub_norm='')."""
|
"""Artefakte in die DB spiegeln. Karteikarte/Beispiel je Sub (sub_norm)."""
|
||||||
await db.delete_sub_artefakte(topic)
|
await db.delete_sub_artefakte(topic)
|
||||||
btitel_list = list(sidecar.keys())
|
btitel_list = list(sidecar.keys())
|
||||||
for typ in ("karteikarte", "beispiel"):
|
for typ in ARTEFAKT_TYPEN:
|
||||||
for e in artefakte.get(typ, []):
|
for e in artefakte.get(typ, []):
|
||||||
bt = _match_sub(e.get("baustein", ""), btitel_list)
|
bt = _match_sub(e.get("baustein", ""), btitel_list)
|
||||||
bnorm, sn = _norm_titel(bt), _norm_titel(e.get("subbaustein", ""))
|
bnorm, sn = _norm_titel(bt), _norm_titel(e.get("subbaustein", ""))
|
||||||
@@ -1864,12 +1853,6 @@ async def _mirror_artefakte_db(topic: str, sidecar: dict, artefakte: dict) -> No
|
|||||||
continue
|
continue
|
||||||
daten = json.dumps({k: v for k, v in e.items() if k not in ("baustein", "subbaustein")}, ensure_ascii=False)
|
daten = json.dumps({k: v for k, v in e.items() if k not in ("baustein", "subbaustein")}, ensure_ascii=False)
|
||||||
await db.put_sub_artefakt(topic, bnorm, sn, typ, daten, bt, e.get("subbaustein", ""))
|
await db.put_sub_artefakt(topic, bnorm, sn, typ, daten, bt, e.get("subbaustein", ""))
|
||||||
for e in artefakte.get("diagramm", []):
|
|
||||||
bt = _match_sub(e.get("baustein", ""), btitel_list)
|
|
||||||
if not (bnorm := _norm_titel(bt)):
|
|
||||||
continue
|
|
||||||
daten = json.dumps({"mermaid": e.get("mermaid", "")}, ensure_ascii=False)
|
|
||||||
await db.put_sub_artefakt(topic, bnorm, "", "diagramm", daten, bt, "")
|
|
||||||
|
|
||||||
|
|
||||||
async def _mirror_sidecar_db(topic: str, sidecar: dict) -> None:
|
async def _mirror_sidecar_db(topic: str, sidecar: dict) -> None:
|
||||||
@@ -1912,7 +1895,7 @@ async def _mirror_frage_muster_db(topic: str, muster: dict) -> None:
|
|||||||
async def _reset_db_ab_phase(topic: str, label: str) -> None:
|
async def _reset_db_ab_phase(topic: str, label: str) -> None:
|
||||||
"""DB-Inhalt der Phasen ≥ `label` verwerfen (kanonische Reihenfolge Quelle…Artefakte)."""
|
"""DB-Inhalt der Phasen ≥ `label` verwerfen (kanonische Reihenfolge Quelle…Artefakte)."""
|
||||||
idx = _phase_idx(label)
|
idx = _phase_idx(label)
|
||||||
if idx <= 8: # Artefakte (Karteikarten/Beispiele/Diagramme)
|
if idx <= 8: # Artefakte (Karteikarten/Beispiele)
|
||||||
await db.delete_sub_artefakte(topic)
|
await db.delete_sub_artefakte(topic)
|
||||||
if idx <= 7: # Fragen
|
if idx <= 7: # Fragen
|
||||||
await db.delete_frage_muster(topic)
|
await db.delete_frage_muster(topic)
|
||||||
@@ -2119,7 +2102,7 @@ async def generate_bausteine(topic: str, instructions: str = "", provider: str =
|
|||||||
return # Abbruch
|
return # Abbruch
|
||||||
atomic_write_json(files["frage_muster"], muster, indent=1)
|
atomic_write_json(files["frage_muster"], muster, indent=1)
|
||||||
|
|
||||||
# Block F: Lern-Artefakte (Karteikarten/Beispiele/Diagramme) aus den Fakten — Bonus,
|
# Block F: Lern-Artefakte (Karteikarten/Beispiele) aus den Fakten — Bonus,
|
||||||
# vom Frontend präsentiert. Bricht den Lauf nicht ab (Artefakte sind optional).
|
# vom Frontend präsentiert. Bricht den Lauf nicht ab (Artefakte sind optional).
|
||||||
sidecar = _json_datei(files["sidecar"])
|
sidecar = _json_datei(files["sidecar"])
|
||||||
if _sidecar_schema(sidecar) is not None and not _artefakte_komplett(files):
|
if _sidecar_schema(sidecar) is not None and not _artefakte_komplett(files):
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ CREATE TABLE IF NOT EXISTS guide_gliederung (
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Generischer Lern-Artefakt-Layer: je (Baustein, Subbaustein, Typ) ein Artefakt (JSON in `daten`).
|
# Generischer Lern-Artefakt-Layer: je (Baustein, Subbaustein, Typ) ein Artefakt (JSON in `daten`).
|
||||||
# typ: karteikarte | beispiel | diagramm. sub_norm='' = Baustein-Ebene (z. B. Diagramm).
|
# typ: karteikarte | beispiel. sub_norm='' = Baustein-Ebene (für künftige Baustein-weite Artefakte).
|
||||||
# In der Bausteine-Phase aus den Fakten erzeugt, vom Frontend präsentiert (Trennung Artefakt/Anzeige).
|
# In der Bausteine-Phase aus den Fakten erzeugt, vom Frontend präsentiert (Trennung Artefakt/Anzeige).
|
||||||
CREATE_SUB_ARTEFAKTE = """
|
CREATE_SUB_ARTEFAKTE = """
|
||||||
CREATE TABLE IF NOT EXISTS sub_artefakte (
|
CREATE TABLE IF NOT EXISTS sub_artefakte (
|
||||||
@@ -1001,7 +1001,7 @@ async def delete_gliederung(topic: str) -> None:
|
|||||||
|
|
||||||
async def put_sub_artefakt(topic: str, baustein_norm: str, sub_norm: str, typ: str,
|
async def put_sub_artefakt(topic: str, baustein_norm: str, sub_norm: str, typ: str,
|
||||||
daten: str, baustein: str = "", sub_titel: str = "") -> None:
|
daten: str, baustein: str = "", sub_titel: str = "") -> None:
|
||||||
"""Ein Lern-Artefakt (karteikarte/beispiel/diagramm) als JSON in `daten` speichern."""
|
"""Ein Lern-Artefakt (karteikarte/beispiel) als JSON in `daten` speichern."""
|
||||||
db = await get_db()
|
db = await get_db()
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""INSERT INTO sub_artefakte (topic, baustein_norm, sub_norm, typ, baustein, sub_titel, daten, updated_at)
|
"""INSERT INTO sub_artefakte (topic, baustein_norm, sub_norm, typ, baustein, sub_titel, daten, updated_at)
|
||||||
|
|||||||
@@ -232,21 +232,18 @@ async def get_frage_muster(topic: str, baustein: str):
|
|||||||
|
|
||||||
@router.get("/bausteine/artefakte")
|
@router.get("/bausteine/artefakte")
|
||||||
async def get_artefakte(topic: str, typ: str | None = None):
|
async def get_artefakte(topic: str, typ: str | None = None):
|
||||||
"""Lern-Artefakte (Karteikarten/Beispiele/Diagramme) je Thema, gruppiert nach Baustein-Norm.
|
"""Lern-Artefakte (Karteikarten/Beispiele) je Thema, gruppiert nach Baustein-Norm — je Subbaustein."""
|
||||||
Diagramm liegt auf Baustein-Ebene (sub_norm=''), Karteikarte/Beispiel je Subbaustein."""
|
|
||||||
rows = await get_sub_artefakte(topic, typ)
|
rows = await get_sub_artefakte(topic, typ)
|
||||||
out: dict[str, dict] = {}
|
out: dict[str, dict] = {}
|
||||||
for r in rows:
|
for r in rows:
|
||||||
b = out.setdefault(r["baustein_norm"], {"baustein": r["baustein"], "karteikarte": [], "beispiel": [], "diagramm": None})
|
b = out.setdefault(r["baustein_norm"], {"baustein": r["baustein"], "karteikarte": [], "beispiel": []})
|
||||||
if r["baustein"] and not b["baustein"]:
|
if r["baustein"] and not b["baustein"]:
|
||||||
b["baustein"] = r["baustein"]
|
b["baustein"] = r["baustein"]
|
||||||
try:
|
try:
|
||||||
daten = json.loads(r["daten"])
|
daten = json.loads(r["daten"])
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
continue
|
continue
|
||||||
if r["typ"] == "diagramm":
|
if r["typ"] in ("karteikarte", "beispiel"):
|
||||||
b["diagramm"] = daten
|
|
||||||
elif r["typ"] in ("karteikarte", "beispiel"):
|
|
||||||
b[r["typ"]].append({"subbaustein": r["sub_titel"], **daten})
|
b[r["typ"]].append({"subbaustein": r["sub_titel"], **daten})
|
||||||
return {"artefakte": out}
|
return {"artefakte": out}
|
||||||
|
|
||||||
|
|||||||
1069
frontend/package-lock.json
generated
1069
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,6 @@
|
|||||||
"katex": "^0.17.0",
|
"katex": "^0.17.0",
|
||||||
"marked": "^18.0.4",
|
"marked": "^18.0.4",
|
||||||
"marked-highlight": "^2.2.4",
|
"marked-highlight": "^2.2.4",
|
||||||
"mermaid": "^11.16.0",
|
|
||||||
"vue": "^3.5.32"
|
"vue": "^3.5.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import BausteinPanel from './BausteinPanel.vue'
|
import BausteinPanel from './BausteinPanel.vue'
|
||||||
|
import FlashcardWidget from './FlashcardWidget.vue'
|
||||||
|
import WorkedExampleBlock from './WorkedExampleBlock.vue'
|
||||||
import { renderMarkdown, renderBloecke } from '../markdown.js'
|
import { renderMarkdown, renderBloecke } from '../markdown.js'
|
||||||
import { stufeFuer, STUFEN } from '../stufen.js'
|
import { stufeFuer, STUFEN } from '../stufen.js'
|
||||||
import { pruefeBlock, uebernehmeBlock, resetBausteinFortschritt } from '../api.js'
|
import { pruefeBlock, uebernehmeBlock, resetBausteinFortschritt } from '../api.js'
|
||||||
@@ -8,6 +10,7 @@ import { useConfirm } from '../composables/useConfirm.js'
|
|||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
baustein: { type: Object, required: true }, // { title, md, num }
|
baustein: { type: Object, required: true }, // { title, md, num }
|
||||||
|
artefakte: { type: Object, default: null }, // { karteikarte[], beispiel[], diagramm } für diesen Baustein
|
||||||
topic: { type: String, required: true },
|
topic: { type: String, required: true },
|
||||||
provider: { type: String, default: 'claude' },
|
provider: { type: String, default: 'claude' },
|
||||||
status: { type: Object, default: null },
|
status: { type: Object, default: null },
|
||||||
@@ -196,6 +199,10 @@ watch(() => `${props.baustein.title}|${props.baustein.md}|${props.baustein.kompa
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="artefakte">
|
||||||
|
<WorkedExampleBlock :beispiele="artefakte.beispiel || []" />
|
||||||
|
<FlashcardWidget :karten="artefakte.karteikarte || []" />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="menu.show" class="menu-overlay" @click="menueZu" @contextmenu.prevent="menueZu">
|
<div v-if="menu.show" class="menu-overlay" @click="menueZu" @contextmenu.prevent="menueZu">
|
||||||
<div class="block-menu" :style="{ top: menu.y + 'px', left: menu.x + 'px' }" @click.stop>
|
<div class="block-menu" :style="{ top: menu.y + 'px', left: menu.x + 'px' }" @click.stop>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, reactive, ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
import { computed, reactive, ref, watch, nextTick, onMounted, onUnmounted } from 'vue'
|
||||||
import { fetchGuideContent, chatGuide, fetchBausteinLernstand, fetchArtefakte } from '../api.js'
|
import { fetchGuideContent, chatGuide, fetchBausteinLernstand, fetchArtefakte } from '../api.js'
|
||||||
import { renderMarkdown, renderMermaid } from '../markdown.js'
|
import { renderMarkdown } from '../markdown.js'
|
||||||
import { stufeFuer, schwelle } from '../stufen.js'
|
import { stufeFuer, schwelle } from '../stufen.js'
|
||||||
import { useChat } from '../composables/useChat.js'
|
import { useChat } from '../composables/useChat.js'
|
||||||
import BausteinPanel from './BausteinPanel.vue'
|
import BausteinPanel from './BausteinPanel.vue'
|
||||||
@@ -70,15 +70,12 @@ function setupLazy() {
|
|||||||
if (!e.isIntersecting) continue
|
if (!e.isIntersecting) continue
|
||||||
sichtbar[Number(e.target.dataset.num)] = true
|
sichtbar[Number(e.target.dataset.num)] = true
|
||||||
mdObserver.unobserve(e.target)
|
mdObserver.unobserve(e.target)
|
||||||
nextTick(() => renderMermaid(e.target)) // Diagramme der nun sichtbaren Section rendern
|
|
||||||
}
|
}
|
||||||
}, { root: scrollEl.value, rootMargin: '800px 0px' })
|
}, { root: scrollEl.value, rootMargin: '800px 0px' })
|
||||||
for (const el of scrollEl.value.querySelectorAll('.section-card')) mdObserver.observe(el)
|
for (const el of scrollEl.value.querySelectorAll('.section-card')) mdObserver.observe(el)
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(content, () => nextTick(setupLazy))
|
watch(content, () => nextTick(setupLazy))
|
||||||
// Artefakte kommen asynchron nach dem Content — bereits sichtbare Diagramme nachrendern.
|
|
||||||
watch(artefakte, () => nextTick(() => renderMermaid(scrollEl.value)))
|
|
||||||
onUnmounted(() => mdObserver?.disconnect())
|
onUnmounted(() => mdObserver?.disconnect())
|
||||||
|
|
||||||
watch(() => props.previewGuide?.id, loadContent, { immediate: true })
|
watch(() => props.previewGuide?.id, loadContent, { immediate: true })
|
||||||
@@ -277,6 +274,11 @@ function extractContext() {
|
|||||||
<h1>{{ previewGuide.topic }}</h1>
|
<h1>{{ previewGuide.topic }}</h1>
|
||||||
<span class="guide-format">{{ previewGuide.format }}</span>
|
<span class="guide-format">{{ previewGuide.format }}</span>
|
||||||
<span v-if="themaAbgeschlossen" class="thema-done" title="Alle Bausteine auf Meister">✓ Thema abgeschlossen</span>
|
<span v-if="themaAbgeschlossen" class="thema-done" title="Alle Bausteine auf Meister">✓ Thema abgeschlossen</span>
|
||||||
|
<span class="gh-spacer"></span>
|
||||||
|
<div class="ansicht-toggle" title="Ausführlichkeit umschalten">
|
||||||
|
<button :class="{ active: ansichtModus === 'kompakt' }" @click="$emit('setAnsicht', 'kompakt')">Kompakt</button>
|
||||||
|
<button :class="{ active: ansichtModus === 'erklärend' }" @click="$emit('setAnsicht', 'erklärend')">Erklärend</button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@@ -303,7 +305,6 @@ function extractContext() {
|
|||||||
<div v-if="sichtbar[s.num]" class="section-body markdown" v-html="htmlFor(s)"></div>
|
<div v-if="sichtbar[s.num]" class="section-body markdown" v-html="htmlFor(s)"></div>
|
||||||
<div v-else class="section-body skeleton"></div>
|
<div v-else class="section-body skeleton"></div>
|
||||||
<template v-if="sichtbar[s.num] && artefakteVon(s.title)">
|
<template v-if="sichtbar[s.num] && artefakteVon(s.title)">
|
||||||
<pre v-if="artefakteVon(s.title).diagramm" class="mermaid art-diagramm">{{ artefakteVon(s.title).diagramm.mermaid }}</pre>
|
|
||||||
<WorkedExampleBlock :beispiele="artefakteVon(s.title).beispiel || []" />
|
<WorkedExampleBlock :beispiele="artefakteVon(s.title).beispiel || []" />
|
||||||
<FlashcardWidget :karten="artefakteVon(s.title).karteikarte || []" />
|
<FlashcardWidget :karten="artefakteVon(s.title).karteikarte || []" />
|
||||||
</template>
|
</template>
|
||||||
@@ -333,6 +334,7 @@ function extractContext() {
|
|||||||
<BausteinFokus
|
<BausteinFokus
|
||||||
v-if="fokusBaustein"
|
v-if="fokusBaustein"
|
||||||
:baustein="fokusBaustein"
|
:baustein="fokusBaustein"
|
||||||
|
:artefakte="artefakteVon(fokusBaustein.title)"
|
||||||
:topic="previewGuide.topic"
|
:topic="previewGuide.topic"
|
||||||
:guide-id="previewGuide.id"
|
:guide-id="previewGuide.id"
|
||||||
:provider="provider"
|
:provider="provider"
|
||||||
@@ -424,12 +426,41 @@ function extractContext() {
|
|||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
|
/* Sticky: der Ausführlichkeits-Toggle bleibt beim Scrollen erreichbar. */
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
background: var(--bg-preview);
|
||||||
|
padding: 0.6rem 0 0.5rem;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1.7rem;
|
font-size: 1.7rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-spacer { flex: 1; }
|
||||||
|
|
||||||
|
.ansicht-toggle {
|
||||||
|
align-self: center;
|
||||||
|
display: inline-flex;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.ansicht-toggle button {
|
||||||
|
border: none;
|
||||||
|
background: var(--panel);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.3rem 0.7rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.ansicht-toggle button.active {
|
||||||
|
background: var(--accent);
|
||||||
|
color: var(--on-accent);
|
||||||
|
}
|
||||||
|
|
||||||
.guide-format {
|
.guide-format {
|
||||||
color: var(--text-faint);
|
color: var(--text-faint);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
@@ -549,19 +580,6 @@ function extractContext() {
|
|||||||
damit der IntersectionObserver die folgenden Karten gestaffelt erkennt. */
|
damit der IntersectionObserver die folgenden Karten gestaffelt erkennt. */
|
||||||
.section-body.skeleton { min-height: 160px; }
|
.section-body.skeleton { min-height: 160px; }
|
||||||
|
|
||||||
/* Diagramm-Artefakt (Mermaid): zentriert, dezent gerahmt. Vor dem Rendern zeigt
|
|
||||||
<pre> den Quelltext — danach ersetzt mermaid.run ihn durch SVG. */
|
|
||||||
.art-diagramm {
|
|
||||||
margin: 0.75rem 0;
|
|
||||||
padding: 0.75rem;
|
|
||||||
background: var(--panel-soft);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 10px;
|
|
||||||
text-align: center;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
.art-diagramm :deep(svg) { max-width: 100%; height: auto; }
|
|
||||||
|
|
||||||
.empty-preview {
|
.empty-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -6,17 +6,6 @@ import katex from 'katex'
|
|||||||
import 'katex/dist/katex.min.css'
|
import 'katex/dist/katex.min.css'
|
||||||
import DOMPurify from 'dompurify'
|
import DOMPurify from 'dompurify'
|
||||||
|
|
||||||
// Mermaid lazy laden (schwere Lib): erst beim ersten Diagramm, hält das Initial-Bundle klein.
|
|
||||||
// startOnLoad:false → wir rendern gezielt; securityLevel strict: kein HTML/JS in Graphen.
|
|
||||||
let _mermaid = null
|
|
||||||
async function getMermaid() {
|
|
||||||
if (!_mermaid) {
|
|
||||||
_mermaid = (await import('mermaid')).default
|
|
||||||
_mermaid.initialize({ startOnLoad: false, securityLevel: 'strict', theme: 'default', flowchart: { htmlLabels: false } })
|
|
||||||
}
|
|
||||||
return _mermaid
|
|
||||||
}
|
|
||||||
|
|
||||||
marked.use(markedHighlight({
|
marked.use(markedHighlight({
|
||||||
langPrefix: 'hljs language-',
|
langPrefix: 'hljs language-',
|
||||||
highlight(code, lang) {
|
highlight(code, lang) {
|
||||||
@@ -60,33 +49,7 @@ const inlineMath = {
|
|||||||
renderer(token) { return renderTex(token.text, false) },
|
renderer(token) { return renderTex(token.text, false) },
|
||||||
}
|
}
|
||||||
|
|
||||||
// ```mermaid → <pre class="mermaid">…</pre> (Quelltext escaped; mermaid.run liest den textContent).
|
marked.use({ extensions: [blockMath, inlineMath] })
|
||||||
const mermaidBlock = {
|
|
||||||
name: 'mermaidBlock',
|
|
||||||
level: 'block',
|
|
||||||
start(src) { const i = src.indexOf('```mermaid'); return i < 0 ? undefined : i },
|
|
||||||
tokenizer(src) {
|
|
||||||
const m = /^```mermaid[^\n]*\n([\s\S]+?)\n```/.exec(src)
|
|
||||||
if (m) return { type: 'mermaidBlock', raw: m[0], text: m[1] }
|
|
||||||
},
|
|
||||||
renderer(token) {
|
|
||||||
const esc = token.text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
||||||
return `<pre class="mermaid">${esc}</pre>`
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
marked.use({ extensions: [blockMath, inlineMath, mermaidBlock] })
|
|
||||||
|
|
||||||
// Mermaid-Knoten in einem Container rendern (nach v-html). Idempotent: bereits
|
|
||||||
// verarbeitete (data-processed) überspringt mermaid.run; defekte Graphen bleiben als Text.
|
|
||||||
export async function renderMermaid(root) {
|
|
||||||
const nodes = root?.querySelectorAll?.('pre.mermaid:not([data-processed="true"])')
|
|
||||||
if (!nodes || !nodes.length) return
|
|
||||||
try {
|
|
||||||
const m = await getMermaid()
|
|
||||||
await m.run({ nodes })
|
|
||||||
} catch { /* defektes Diagramm → Quelltext bleibt sichtbar */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rohes HTML im Markdown (z. B. <p>, <img> ohne Backticks aus Agenten-Output)
|
// Rohes HTML im Markdown (z. B. <p>, <img> ohne Backticks aus Agenten-Output)
|
||||||
// als Text anzeigen statt rendern — sonst verschluckt der Browser den Inhalt.
|
// als Text anzeigen statt rendern — sonst verschluckt der Browser den Inhalt.
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
Baue für jeden Baustein des Themas "{topic}" EIN Übersichts-Diagramm (Concept-Map) als Mermaid-Code. Es zeigt, wie die Subbausteine/Kernbegriffe des Bausteins zusammenhängen.
|
|
||||||
|
|
||||||
BAUSTEINE MIT SUBBAUSTEINEN UND IHREN FAKTEN:
|
|
||||||
{bausteine}
|
|
||||||
|
|
||||||
Regeln für das Diagramm:
|
|
||||||
- Genau EIN Mermaid-Graph je Baustein, Typ `flowchart TD` (oben→unten) oder `flowchart LR`.
|
|
||||||
- Knoten = die Subbausteine bzw. ihre Kernbegriffe. Kanten = echte Beziehungen (baut-auf, gehört-zu, führt-zu). Keine erfundenen Beziehungen.
|
|
||||||
- 3–8 Knoten. Lieber klar als vollständig. Stütze dich auf Voraussetzungen/Kernpunkte aus den Fakten.
|
|
||||||
- **Gültige Mermaid-Syntax, sonst rendert nichts:**
|
|
||||||
- Knoten-IDs sind kurze ASCII-Wörter ohne Leerzeichen (z. B. `dfa`, `nfa1`).
|
|
||||||
- Label in eckigen Klammern, Text schlicht: `dfa[Deterministischer Automat]`. KEINE Anführungszeichen, runden Klammern, Doppelpunkte oder `#` im Label.
|
|
||||||
- Kanten mit `-->` (optional Label: `a -->|baut auf| b`).
|
|
||||||
- Zeilen im `mermaid`-Feld mit `\n` trennen (ein JSON-String).
|
|
||||||
- Lässt sich ein Baustein nicht sinnvoll als Graph zeigen (ein einziger Begriff), LASSE IHN WEG.
|
|
||||||
|
|
||||||
Schreibe die Diagramme als EIN JSON in die Datei {out_path} (nutze dein Schreib-Werkzeug), GENAU so:
|
|
||||||
{{"diagramme": [
|
|
||||||
{{"baustein": "<exakter Baustein-Titel>",
|
|
||||||
"mermaid": "flowchart TD\n a[Alphabet] --> b[Wort]\n b --> c[Sprache]"}}
|
|
||||||
]}}
|
|
||||||
|
|
||||||
Gib sonst keinen Text aus.
|
|
||||||
{extra}
|
|
||||||
Reference in New Issue
Block a user