update
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { chatBaustein, createVertiefung, fetchVertiefung, pruefeBaustein } from '../api.js'
|
import { chatBaustein, createVertiefung, fetchVertiefung, pruefeBaustein } from '../api.js'
|
||||||
import { getPruef, setPruef } from '../pruefungCache.js'
|
import { usePruefSlot } from '../pruefungCache.js'
|
||||||
import { renderMarkdown } from '../markdown.js'
|
import { renderMarkdown } from '../markdown.js'
|
||||||
import { useChat, istUnten } from '../composables/useChat.js'
|
import { useChat, istUnten } from '../composables/useChat.js'
|
||||||
|
|
||||||
@@ -82,48 +82,24 @@ const chat = useChat((msgs) => chatBaustein({
|
|||||||
messages: msgs, provider: props.provider,
|
messages: msgs, provider: props.provider,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// --- Prüfung: gesteuerter Dialog (Verlauf flüchtig, Zähler serverseitig) ---
|
// --- Prüfung: gesteuerter Dialog ---
|
||||||
// Phasen: 'idle' (Frage anfordern) | 'frage_offen' (antworten/nachfragen) | 'bewertet' (diskutieren/neu bewerten/weiter)
|
// Phasen: 'idle' (Frage anfordern) | 'frage_offen' (antworten/nachfragen) | 'bewertet' (diskutieren/neu bewerten/weiter)
|
||||||
const pruefMessages = ref([]) // {role, kind: 'frage'|'nachfrage'|'antwort'|'feedback'|'diskussion'|'fehler', content, bewertung?}
|
// Durabler State liegt in einem GETEILTEN Slot pro Baustein (Modul-Store): überlebt
|
||||||
|
// das Remounten und eine spät eintreffende Frage ist sofort reaktiv sichtbar.
|
||||||
|
const pruefKey = `${props.topic}::${props.baustein}`
|
||||||
|
const slot = usePruefSlot(pruefKey)
|
||||||
|
const pruefMessages = slot.messages // {role, kind: 'frage'|'nachfrage'|'antwort'|'feedback'|'diskussion'|'fehler', content, bewertung?}
|
||||||
|
const pruefPhase = slot.phase
|
||||||
|
const aktuelleFrage = slot.aktuelleFrage // ankert Bewertung/Diskussion
|
||||||
|
const letztesFeedback = slot.letztesFeedback // Kontext für die Diskussion über eine Bewertung
|
||||||
|
const scoreVorFrage = slot.scoreVorFrage // Score, als die Frage gestellt wurde → driftfreies (Re-)Bewerten
|
||||||
|
const streakVorFrage = slot.streakVorFrage // Streak, als die Frage gestellt wurde (driftfrei)
|
||||||
|
// Transient (pro Instanz): Eingabe-Entwurf, Lade-Spinner, Prefetch.
|
||||||
const pruefInput = ref('')
|
const pruefInput = ref('')
|
||||||
const pruefPhase = ref('idle')
|
|
||||||
const pruefLoading = ref(false)
|
const pruefLoading = ref(false)
|
||||||
const aktuelleFrage = ref('') // ankert Bewertung/Diskussion
|
|
||||||
const letztesFeedback = ref('') // Kontext für die Diskussion über eine Bewertung
|
|
||||||
const scoreVorFrage = ref(0) // Score, als die aktuelle Frage gestellt wurde → driftfreies (Re-)Bewerten
|
|
||||||
const streakVorFrage = ref(0) // Streak, als die aktuelle Frage gestellt wurde (driftfrei)
|
|
||||||
// Die aktuelle Streak lebt persistent in st.value.streak (DB → props.status, round-trip via applyPruefung).
|
|
||||||
const naechsteFrage = ref(null) // im Hintergrund vorbereitete nächste Frage (Prefetch)
|
const naechsteFrage = ref(null) // im Hintergrund vorbereitete nächste Frage (Prefetch)
|
||||||
let prefetchPromise = null // laufender Hintergrund-Abruf (verhindert Doppel-Prefetch)
|
let prefetchPromise = null // laufender Hintergrund-Abruf (verhindert Doppel-Prefetch)
|
||||||
|
|
||||||
// Session-Cache: Prüfungs-Verlauf pro Baustein über Remounts hinweg halten (nur im Fokus,
|
|
||||||
// dort läuft die Prüfung). Wiederherstellen beim Mount, bei jeder Änderung sichern.
|
|
||||||
const pruefKey = `${props.topic}::${props.baustein}`
|
|
||||||
function cacheSave() {
|
|
||||||
if (props.mode !== 'full') return
|
|
||||||
setPruef(pruefKey, {
|
|
||||||
messages: pruefMessages.value, phase: pruefPhase.value,
|
|
||||||
aktuelleFrage: aktuelleFrage.value, letztesFeedback: letztesFeedback.value,
|
|
||||||
scoreVorFrage: scoreVorFrage.value, streakVorFrage: streakVorFrage.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (props.mode === 'full') {
|
|
||||||
const c = getPruef(pruefKey)
|
|
||||||
if (c) {
|
|
||||||
pruefMessages.value = c.messages
|
|
||||||
pruefPhase.value = c.phase
|
|
||||||
aktuelleFrage.value = c.aktuelleFrage
|
|
||||||
letztesFeedback.value = c.letztesFeedback
|
|
||||||
scoreVorFrage.value = c.scoreVorFrage
|
|
||||||
streakVorFrage.value = c.streakVorFrage
|
|
||||||
}
|
|
||||||
// watch sichert live; cacheSave() in pruefSenden fängt spät (post-unmount) eintreffende Antworten.
|
|
||||||
watch(
|
|
||||||
[pruefMessages, pruefPhase, aktuelleFrage, letztesFeedback, scoreVorFrage, streakVorFrage],
|
|
||||||
cacheSave,
|
|
||||||
{ deep: true },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const pruefMessagesEl = ref(null)
|
const pruefMessagesEl = ref(null)
|
||||||
const pruefInputEl = ref(null)
|
const pruefInputEl = ref(null)
|
||||||
const pruefStick = ref(true) // nur auto-scrollen, wenn der Nutzer (fast) unten ist
|
const pruefStick = ref(true) // nur auto-scrollen, wenn der Nutzer (fast) unten ist
|
||||||
@@ -163,11 +139,10 @@ async function pruefSenden(payload, onOk) {
|
|||||||
if (run !== pruefRun) return
|
if (run !== pruefRun) return
|
||||||
onOk(res)
|
onOk(res)
|
||||||
applyPruefung(res)
|
applyPruefung(res)
|
||||||
cacheSave() // auch wenn die Komponente schon unmountet ist (Watcher tot)
|
|
||||||
pruefScroll()
|
pruefScroll()
|
||||||
nextTick(() => pruefInputEl.value?.focus())
|
nextTick(() => pruefInputEl.value?.focus())
|
||||||
} catch {
|
} catch {
|
||||||
if (run === pruefRun) { pruefMessages.value.push({ role: 'assistant', kind: 'fehler', content: 'Hat nicht geklappt — bitte erneut.' }); cacheSave() }
|
if (run === pruefRun) pruefMessages.value.push({ role: 'assistant', kind: 'fehler', content: 'Hat nicht geklappt — bitte erneut.' })
|
||||||
} finally {
|
} finally {
|
||||||
if (run === pruefRun) pruefLoading.value = false
|
if (run === pruefRun) pruefLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
// Session-Cache für den Prüfungs-Verlauf pro Baustein.
|
// Geteilter Prüfungs-State pro Baustein.
|
||||||
// Lebt als Modul-Map: überlebt das Remounten von BausteinPanel (Baustein-Wechsel,
|
// Die durablen Refs leben hier als Modul-Map: alle BausteinPanel-Instanzen desselben
|
||||||
// Fokus schließen/öffnen), geht aber bei Page-Reload verloren — bewusst kein DB-Aufwand.
|
// Bausteins nutzen DIESELBEN Refs. So überlebt der Verlauf das Remounten (Baustein-
|
||||||
|
// Wechsel, Fokus zu/auf) UND eine spät eintreffende Frage ist sofort reaktiv sichtbar.
|
||||||
|
// Geht bei Page-Reload verloren (Modul re-init) — bewusst kein DB-Aufwand.
|
||||||
|
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const store = new Map() // key "topic::baustein" -> { messages, phase, aktuelleFrage, letztesFeedback, scoreVorFrage, streakVorFrage }
|
const store = new Map() // key "topic::baustein" -> { messages, phase, aktuelleFrage, letztesFeedback, scoreVorFrage, streakVorFrage }
|
||||||
|
|
||||||
export const getPruef = (key) => store.get(key)
|
export function usePruefSlot(key) {
|
||||||
export const setPruef = (key, val) => store.set(key, val)
|
if (!store.has(key)) {
|
||||||
|
store.set(key, {
|
||||||
|
messages: ref([]),
|
||||||
|
phase: ref('idle'),
|
||||||
|
aktuelleFrage: ref(''),
|
||||||
|
letztesFeedback: ref(''),
|
||||||
|
scoreVorFrage: ref(0),
|
||||||
|
streakVorFrage: ref(0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return store.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
export const clearPruef = (key) => store.delete(key)
|
export const clearPruef = (key) => store.delete(key)
|
||||||
|
|||||||
Reference in New Issue
Block a user