This commit is contained in:
team3
2026-06-19 12:59:13 +02:00
parent 3168ec9094
commit 26e097dc5a
9 changed files with 107 additions and 199 deletions

View File

@@ -77,13 +77,13 @@ export async function chatBaustein({ topic, baustein, section, section_kompakt =
export async function pruefeBaustein({
topic, baustein, section, section_kompakt = '', provider,
aktion = 'frage', frage = '', letzte_bewertung = '', vermeide = [], anzahl = 5,
aktion = 'frage', frage = '', letzte_bewertung = '', vermeide = [],
tier2 = false, tier3 = false, messages = [], gruendlich = false,
}) {
const res = await fetch(`${BASE}/bausteine/pruefung`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic, baustein, section, section_kompakt, aktion, frage, letzte_bewertung, vermeide, anzahl, tier2, tier3, messages, provider, gruendlich }),
body: JSON.stringify({ topic, baustein, section, section_kompakt, aktion, frage, letzte_bewertung, vermeide, tier2, tier3, messages, provider, gruendlich }),
})
return jsonOrThrow(res)
}

View File

@@ -137,24 +137,27 @@ function vermeideListe() {
return [...gestellt, ...pool.value]
}
// Pool als BATCH auffüllen: EIN Planer entwirft die fehlenden Fragen auf einmal
// (verschieden by design), parallel geprüft → schnell und ohne ähnliche Dubletten.
function poolFuellen() {
if (inflight.value > 0) return
const brauche = POOL_ZIEL - pool.value.length
if (brauche <= 0 || poolMiss >= 3) return
inflight.value++
pruefeBaustein({
function frageEinzeln() {
// Ein Agent → eine Frage (Backend wählt zufälligen Typ + Subbaustein, dann Kritiker).
return pruefeBaustein({
topic: props.topic, baustein: props.baustein, section: props.section, section_kompakt: props.sectionKompakt,
provider: props.provider, aktion: 'fragen', anzahl: brauche, messages: pruefDialog(), vermeide: vermeideListe(),
})
.then((res) => {
const neu = (res.fragen || []).filter((f) => f && !istBekannt(f))
if (neu.length) { pool.value.push(...neu); poolMiss = 0 }
else poolMiss++
})
.catch(() => { poolMiss++ })
.finally(() => { inflight.value--; if (pool.value.length < POOL_ZIEL) poolFuellen() })
provider: props.provider, aktion: 'frage', messages: pruefDialog(), vermeide: vermeideListe(),
}).then((res) => res.frage)
}
// Pool PARALLEL auffüllen: pro fehlender Frage ein eigener Agent gleichzeitig.
// Initial 5 parallel; nach Verbrauch genau die fehlende(n) nach.
function poolFuellen() {
while (pool.value.length + inflight.value < POOL_ZIEL && poolMiss < 3) {
inflight.value++
frageEinzeln()
.then((text) => {
if (text && !istBekannt(text)) { pool.value.push(text); poolMiss = 0 }
else poolMiss++
})
.catch(() => { poolMiss++ })
.finally(() => { inflight.value--; poolFuellen() })
}
}
// Eine Frage aus dem Pool nehmen; ist er leer, auf die erste eintreffende warten.
@@ -211,47 +214,40 @@ function nachfragen() {
)
}
let letzteFeedbackMsg = null // Referenz auf die zuletzt gezeigte (provisorische) Bewertungs-Bubble
let pruefenRun = 0 // nur die jüngste Hintergrund-Prüfung darf die Anzeige korrigieren
let letzteFeedbackMsg = null // Referenz auf die zuletzt gezeigte Bewertungs-Bubble
let pruefenRun = 0 // nur die jüngste Bewertung darf die Anzeige setzen
function bewerten(res) {
letztesFeedback.value = res.feedback || ''
pruefMessages.value.push({ role: 'assistant', kind: 'feedback', content: res.feedback || '', bewertung: res.bewertung, geprueft: false })
letzteFeedbackMsg = pruefMessages.value[pruefMessages.value.length - 1]
pruefPhase.value = 'bewertet'
verdictPruefen() // im Hintergrund verbindlich prüfen lassen
}
function antwortPayload() {
// Basis/Streak führt der Server je Frage (frage = Anker). Frontend schickt sie nicht mehr.
return { aktion: 'antwort', frage: aktuelleFrage.value, tier2: props.tier2, tier3: props.tier3 }
}
// Hintergrund: voller Bewerter+Kritiker. Persistiert serverseitig; korrigiert die Anzeige bei Abweichung.
// gruendlich=true: starkes Modell (role guide), manuell ausgelöst → sichtbarer Ladezustand.
async function verdictPruefen(gruendlich = false) {
// Antwort verbindlich prüfen — der Prüfer (Bewerter + Kritiker) vergibt die Punkte.
// Während des Wartens zeigt das „…"-Bubble den Geprüft-Zustand. gruendlich=true:
// starkes Modell, re-bewertet dieselbe Frage und aktualisiert die bestehende Bubble.
async function bewertenLassen(gruendlich = false) {
const meine = ++pruefenRun
const ziel = letzteFeedbackMsg
if (gruendlich) pruefLoading.value = true
pruefStick.value = true
pruefLoading.value = true
pruefScroll()
try {
const res = await pruefeBaustein({
topic: props.topic, baustein: props.baustein, section: props.section, section_kompakt: props.sectionKompakt,
provider: props.provider, messages: pruefDialog(), ...antwortPayload(), aktion: 'antwort_pruefen', gruendlich,
provider: props.provider, messages: pruefDialog(),
aktion: 'antwort_pruefen', frage: aktuelleFrage.value, tier2: props.tier2, tier3: props.tier3, gruendlich,
})
// Server führt den Score (serialisiert, absolut) — IMMER übernehmen, auch wenn man
// schon bei der nächsten Frage ist. Sonst geht die Korrektur verloren.
if (meine !== pruefenRun) return
applyPruefung(res)
if (ziel) {
if (res.bewertung !== ziel.bewertung || (res.feedback && res.feedback !== ziel.content)) {
ziel.content = res.feedback || ziel.content
ziel.bewertung = res.bewertung
}
ziel.geprueft = true
letztesFeedback.value = res.feedback || ''
if (gruendlich && letzteFeedbackMsg) {
letzteFeedbackMsg.content = res.feedback || letzteFeedbackMsg.content
letzteFeedbackMsg.bewertung = res.bewertung
} else {
pruefMessages.value.push({ role: 'assistant', kind: 'feedback', content: res.feedback || '', bewertung: res.bewertung, geprueft: true })
letzteFeedbackMsg = pruefMessages.value[pruefMessages.value.length - 1]
pruefPhase.value = 'bewertet'
}
pruefScroll()
nextTick(() => pruefInputEl.value?.focus())
} catch {
if (ziel) ziel.geprueft = true
if (meine === pruefenRun) pruefMessages.value.push({ role: 'assistant', kind: 'fehler', content: 'Hat nicht geklappt — bitte erneut.' })
} finally {
if (gruendlich && meine === pruefenRun) pruefLoading.value = false
if (meine === pruefenRun) pruefLoading.value = false
}
}
@@ -260,7 +256,7 @@ function antwortAbgeben() {
if (!text || pruefLoading.value) return
pruefMessages.value.push({ role: 'user', kind: 'antwort', content: text })
pruefInput.value = ''
pruefSenden(antwortPayload(), bewerten)
bewertenLassen(false)
}
// Lief schon eine Diskussion? Dann darf der Verlauf direkt bewertet werden.
@@ -269,17 +265,17 @@ const hatDiskussion = computed(() => pruefMessages.value.some((m) => m.kind ===
// „Verlauf bewerten": den bisherigen Dialog als Antwort werten (Feld wird ignoriert).
function verlaufBewerten() {
if (pruefLoading.value) return
pruefSenden(antwortPayload(), bewerten)
bewertenLassen(false)
}
// „Gründlich prüfen": dieselbe Antwort, aber vom starken Modell (role guide) verbindlich neu bewerten.
// „Gründlich prüfen": dieselbe Antwort vom starken Modell (role guide) neu bewerten.
function gruendlichPruefen() {
if (pruefLoading.value) return
verdictPruefen(true)
bewertenLassen(true)
}
// Abbruch wie im Chat: Run-Counter hochzählen → laufendes Ergebnis (pruefSenden
// und verdictPruefen) wird verworfen, Buttons sofort frei. Agent läuft serverseitig aus.
// und bewertenLassen) wird verworfen, Buttons sofort frei. Agent läuft serverseitig aus.
function pruefAbbrechen() {
if (!pruefLoading.value) return
pruefRun++
@@ -296,7 +292,6 @@ onMounted(() => {
if (props.mode !== 'full') return // Trigger-Modus (Liste): nur Tab-Leiste, keine Prüfungs-/Text-Logik
window.addEventListener('keydown', onWindowKey)
window.addEventListener('keydown', onPruefKey)
poolFuellen() // Pool schon beim Öffnen vorfüllen — keine Wartezeit bei der ersten Frage
})
onUnmounted(() => {
window.removeEventListener('keydown', onWindowKey)