From a7fd345bb6d30523922e2dac57277e374b6e6557 Mon Sep 17 00:00:00 2001 From: team3 Date: Fri, 12 Jun 2026 17:46:30 +0200 Subject: [PATCH] update --- backend/database.py | 45 +++++++++------ backend/lernen.py | 9 ++- backend/models.py | 5 ++ backend/routes.py | 38 +++++++----- frontend/src/api.js | 8 +-- frontend/src/components/BausteinPanel.vue | 70 +++++++++++++---------- templates/Prompt/Baustein-Deepdive.md | 19 ++++++ templates/Prompt/Baustein-Vertiefung.md | 19 +++--- 8 files changed, 136 insertions(+), 77 deletions(-) create mode 100644 templates/Prompt/Baustein-Deepdive.md diff --git a/backend/database.py b/backend/database.py index 83190e0..3e9ef76 100644 --- a/backend/database.py +++ b/backend/database.py @@ -47,14 +47,15 @@ CREATE TABLE IF NOT EXISTS elements ( ) """ -CREATE_VERTIEFUNGEN = """ -CREATE TABLE IF NOT EXISTS vertiefungen ( +CREATE_BAUSTEIN_TEXTE = """ +CREATE TABLE IF NOT EXISTS baustein_texte ( topic TEXT NOT NULL, baustein TEXT NOT NULL, + art TEXT NOT NULL, md TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, - PRIMARY KEY (topic, baustein) + PRIMARY KEY (topic, baustein, art) ) """ @@ -89,12 +90,20 @@ async def init_db(): await db.execute(CREATE_PROGRESS) await db.execute(CREATE_TOPICS) await db.execute(CREATE_ELEMENTS) - await db.execute(CREATE_VERTIEFUNGEN) + await db.execute(CREATE_BAUSTEIN_TEXTE) await db.execute(CREATE_BAUSTEIN_PROGRESS) try: # Migration für Bestands-DBs ohne step-Spalte await db.execute("ALTER TABLE guides ADD COLUMN step INTEGER") except aiosqlite.OperationalError: pass + # Migration: alte vertiefungen-Tabelle → baustein_texte (Bestand = lange Form, art 'deepdive') + cursor = await db.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'vertiefungen'") + if await cursor.fetchone(): + await db.execute( + "INSERT OR IGNORE INTO baustein_texte (topic, baustein, art, md, created_at, updated_at) " + "SELECT topic, baustein, 'deepdive', md, created_at, updated_at FROM vertiefungen" + ) + await db.execute("DROP TABLE vertiefungen") await db.execute( "UPDATE guides SET status = 'error', progress = NULL, error_msg = 'Server-Neustart' " "WHERE status IN ('queued', 'generating')" @@ -288,33 +297,37 @@ def _now() -> str: return datetime.now(timezone.utc).isoformat() -async def get_vertiefung(topic: str, baustein: str) -> str | None: +async def get_vertiefung(topic: str, baustein: str, art: str) -> str | None: db = await get_db() cursor = await db.execute( - "SELECT md FROM vertiefungen WHERE topic = ? AND baustein = ?", (topic, baustein) + "SELECT md FROM baustein_texte WHERE topic = ? AND baustein = ? AND art = ?", + (topic, baustein, art), ) row = await cursor.fetchone() return row[0] if row else None -async def set_vertiefung(topic: str, baustein: str, md: str) -> None: +async def set_vertiefung(topic: str, baustein: str, art: str, md: str) -> None: db = await get_db() now = _now() await db.execute( - """INSERT INTO vertiefungen (topic, baustein, md, created_at, updated_at) - VALUES (?, ?, ?, ?, ?) - ON CONFLICT(topic, baustein) DO UPDATE SET md = excluded.md, updated_at = excluded.updated_at""", - (topic, baustein, md, now, now), + """INSERT INTO baustein_texte (topic, baustein, art, md, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT(topic, baustein, art) DO UPDATE SET md = excluded.md, updated_at = excluded.updated_at""", + (topic, baustein, art, md, now, now), ) await db.commit() -async def list_vertiefungen(topic: str) -> set[str]: - """Baustein-Titel, zu denen eine Vertiefung existiert.""" +async def list_vertiefungen(topic: str) -> dict[str, set[str]]: + """Baustein-Titel → vorhandene Text-Arten ('vertiefung'/'deepdive').""" db = await get_db() - cursor = await db.execute("SELECT baustein FROM vertiefungen WHERE topic = ?", (topic,)) + cursor = await db.execute("SELECT baustein, art FROM baustein_texte WHERE topic = ?", (topic,)) rows = await cursor.fetchall() - return {row[0] for row in rows} + out: dict[str, set[str]] = {} + for baustein, art in rows: + out.setdefault(baustein, set()).add(art) + return out async def list_baustein_progress(topic: str) -> list[dict]: @@ -377,6 +390,6 @@ async def list_baustein_absolviert_all() -> dict[str, set[str]]: async def delete_baustein_daten(topic: str) -> None: db = await get_db() - await db.execute("DELETE FROM vertiefungen WHERE topic = ?", (topic,)) + await db.execute("DELETE FROM baustein_texte WHERE topic = ?", (topic,)) await db.execute("DELETE FROM baustein_progress WHERE topic = ?", (topic,)) await db.commit() diff --git a/backend/lernen.py b/backend/lernen.py index 722888a..6f49438 100644 --- a/backend/lernen.py +++ b/backend/lernen.py @@ -31,11 +31,14 @@ def _transcript(messages: list[dict]) -> str: ) or "(leer)" -async def vertiefung_generieren(topic: str, baustein: str, section: str, provider: str = DEFAULT_PROVIDER) -> str | None: - """Ausführliche Fassung des Bausteins als Markdown · None bei Fehler.""" +async def vertiefung_generieren(topic: str, baustein: str, section: str, art: str = "vertiefung", provider: str = DEFAULT_PROVIDER) -> str | None: + """Ausführlichere Fassung des Bausteins als Markdown · None bei Fehler. + + art "vertiefung" = kurz (~75–150 Wörter), "deepdive" = lang (300–600 Wörter). + """ try: prompt = _prompt( - "Baustein-Vertiefung", + "Baustein-Deepdive" if art == "deepdive" else "Baustein-Vertiefung", topic=topic, baustein=baustein, section_block=section.strip() or "(keine Guide-Fassung übergeben)", ) diff --git a/backend/models.py b/backend/models.py index eefe6dc..3e7c942 100644 --- a/backend/models.py +++ b/backend/models.py @@ -160,10 +160,14 @@ class ProgressResponse(BaseModel): # --- Baustein-Lernen --- +VertiefungArt = Literal["vertiefung", "deepdive"] + + class VertiefungRequest(BaseModel): topic: str = Field(min_length=1, max_length=100) baustein: str = Field(min_length=1, max_length=200) section: str = Field(default="", max_length=20000) + art: VertiefungArt = "vertiefung" provider: ProviderType = "claude" @@ -202,6 +206,7 @@ class BausteinLernstand(BaseModel): gute_antworten: int absolviert: bool vertiefung: bool + deepdive: bool class BausteinLernstandResponse(BaseModel): diff --git a/backend/routes.py b/backend/routes.py index 5811f33..363ccc7 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -147,44 +147,56 @@ async def remove_bausteine(topic: str): # --- Baustein-Lernen: Vertiefung, Chat, Prüfung --- +async def _bester_text(topic: str, baustein: str) -> str | None: + """Kontext für Chat/Prüfung: Deep Dive bevorzugt, sonst kurze Vertiefung.""" + return await get_vertiefung(topic, baustein, "deepdive") or await get_vertiefung(topic, baustein, "vertiefung") + + @router.get("/bausteine/lernstand", response_model=BausteinLernstandResponse) async def baustein_lernstand(topic: str): - """Prüfungs-Stand + Vertiefungs-Existenz pro Baustein (roher Titel als Key).""" + """Prüfungs-Stand + Vertiefungs-/Deepdive-Existenz pro Baustein (roher Titel als Key).""" progress = await list_baustein_progress(topic) - mit_vertiefung = await list_vertiefungen(topic) + texte = await list_vertiefungen(topic) bausteine = { p["baustein"]: { "gute_antworten": p["gute_antworten"], "absolviert": p["absolviert"] is not None, - "vertiefung": p["baustein"] in mit_vertiefung, + "vertiefung": "vertiefung" in texte.get(p["baustein"], set()), + "deepdive": "deepdive" in texte.get(p["baustein"], set()), } for p in progress } - for b in mit_vertiefung - set(bausteine): - bausteine[b] = {"gute_antworten": 0, "absolviert": False, "vertiefung": True} + for b, arten in texte.items(): + if b not in bausteine: + bausteine[b] = { + "gute_antworten": 0, "absolviert": False, + "vertiefung": "vertiefung" in arten, "deepdive": "deepdive" in arten, + } return {"bausteine": bausteine} @router.get("/bausteine/vertiefung", response_model=VertiefungResponse) -async def get_baustein_vertiefung(topic: str, baustein: str): - md = await get_vertiefung(topic, baustein) +async def get_baustein_vertiefung(topic: str, baustein: str, art: str = "vertiefung"): + if art not in ("vertiefung", "deepdive"): + raise HTTPException(400, "Unbekannte Art") + md = await get_vertiefung(topic, baustein, art) if md is None: - raise HTTPException(404, "Keine Vertiefung vorhanden") + raise HTTPException(404, "Kein Text vorhanden") return {"md": md} @router.post("/bausteine/vertiefung", response_model=VertiefungResponse) async def create_baustein_vertiefung(req: VertiefungRequest): - md = await vertiefung_generieren(req.topic, req.baustein, req.section, provider=req.provider) + md = await vertiefung_generieren(req.topic, req.baustein, req.section, art=req.art, provider=req.provider) if md is None: - raise HTTPException(502, "Vertiefung fehlgeschlagen — bitte erneut versuchen") - await set_vertiefung(req.topic, req.baustein, md) + raise HTTPException(502, "Generierung fehlgeschlagen — bitte erneut versuchen") + await set_vertiefung(req.topic, req.baustein, req.art, md) return {"md": md} @router.post("/bausteine/chat", response_model=BausteinChatResponse) async def baustein_chat_route(req: BausteinChatRequest): - vertiefung = await get_vertiefung(req.topic, req.baustein) + vertiefung = await _bester_text(req.topic, req.baustein) reply = await baustein_chat( req.topic, req.baustein, req.section, vertiefung, [m.model_dump() for m in req.messages], provider=req.provider, @@ -198,7 +210,7 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest): (p for p in await list_baustein_progress(req.topic) if p["baustein"] == req.baustein), {"gute_antworten": 0, "absolviert": None}, ) - vertiefung = await get_vertiefung(req.topic, req.baustein) + vertiefung = await _bester_text(req.topic, req.baustein) data = await baustein_pruefung( req.topic, req.baustein, req.section, vertiefung, [m.model_dump() for m in req.messages], stand["gute_antworten"], provider=req.provider, diff --git a/frontend/src/api.js b/frontend/src/api.js index 8378ef5..b75dc4a 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -66,18 +66,18 @@ export async function fetchBausteinLernstand(topic) { return jsonOrThrow(res) } -export async function fetchVertiefung(topic, baustein) { +export async function fetchVertiefung(topic, baustein, art = 'vertiefung') { const res = await fetch( - `${BASE}/bausteine/vertiefung?topic=${encodeURIComponent(topic)}&baustein=${encodeURIComponent(baustein)}` + `${BASE}/bausteine/vertiefung?topic=${encodeURIComponent(topic)}&baustein=${encodeURIComponent(baustein)}&art=${art}` ) return jsonOrThrow(res) } -export async function createVertiefung({ topic, baustein, section, provider }) { +export async function createVertiefung({ topic, baustein, section, art = 'vertiefung', provider }) { const res = await fetch(`${BASE}/bausteine/vertiefung`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ topic, baustein, section, provider }), + body: JSON.stringify({ topic, baustein, section, art, provider }), }) return jsonOrThrow(res) } diff --git a/frontend/src/components/BausteinPanel.vue b/frontend/src/components/BausteinPanel.vue index e9f12fb..a2539cd 100644 --- a/frontend/src/components/BausteinPanel.vue +++ b/frontend/src/components/BausteinPanel.vue @@ -15,47 +15,50 @@ const props = defineProps({ const emit = defineEmits(['statusChanged']) const NOETIG = 3 -const st = computed(() => props.status || { gute_antworten: 0, absolviert: false, vertiefung: false }) +const st = computed(() => props.status || { gute_antworten: 0, absolviert: false, vertiefung: false, deepdive: false }) // --- Toggle-Bereich --- -const activeTab = ref(null) // null | 'vertiefung' | 'chat' | 'pruefung' +const activeTab = ref(null) // null | 'vertiefung' | 'deepdive' | 'chat' | 'pruefung' function toggle(tab) { activeTab.value = activeTab.value === tab ? null : tab - if (activeTab.value === 'vertiefung') openVertiefung() + if (activeTab.value === 'vertiefung' || activeTab.value === 'deepdive') openText(activeTab.value) if (activeTab.value === 'pruefung') startPruefung() } -// --- Vertiefung (persistiert) --- -const vert = ref(null) -const vertLoading = ref(false) -const vertError = ref('') +// --- Vertiefung (kurz) + Deep Dive (lang), beide persistiert --- +const texte = ref({ + vertiefung: { md: null, loading: false, error: '' }, + deepdive: { md: null, loading: false, error: '' }, +}) -async function openVertiefung() { - if (vert.value !== null || vertLoading.value || !st.value.vertiefung) return - vertLoading.value = true - vertError.value = '' +async function openText(art) { + const t = texte.value[art] + if (t.md !== null || t.loading || !st.value[art]) return + t.loading = true + t.error = '' try { - vert.value = (await fetchVertiefung(props.topic, props.baustein)).md + t.md = (await fetchVertiefung(props.topic, props.baustein, art)).md } catch (e) { - vertError.value = e.message + t.error = e.message } finally { - vertLoading.value = false + t.loading = false } } -async function generateVertiefung() { - vertLoading.value = true - vertError.value = '' +async function generateText(art) { + const t = texte.value[art] + t.loading = true + t.error = '' try { - vert.value = (await createVertiefung({ - topic: props.topic, baustein: props.baustein, section: props.section, provider: props.provider, + t.md = (await createVertiefung({ + topic: props.topic, baustein: props.baustein, section: props.section, art, provider: props.provider, })).md - emit('statusChanged', { ...st.value, vertiefung: true }) + emit('statusChanged', { ...st.value, [art]: true }) } catch (e) { - vertError.value = e.message + t.error = e.message } finally { - vertLoading.value = false + t.loading = false } } @@ -109,6 +112,9 @@ async function startPruefung() { + @@ -120,18 +126,20 @@ async function startPruefung() {
- -
-

{{ vert === null ? 'Generiere Vertiefung…' : 'Lade…' }}

-