update
This commit is contained in:
@@ -232,8 +232,8 @@ class BausteinPruefungResponse(BaseModel):
|
|||||||
frage: str | None = None
|
frage: str | None = None
|
||||||
reply: str | None = None
|
reply: str | None = None
|
||||||
feedback: str | None = None
|
feedback: str | None = None
|
||||||
niveau: str | None = None # kaum|teilweise|solide|stark|komplett
|
punkte: int | None = None # Punkt-Delta dieser Antwort (−1 … +3); schnell = voraussichtlich
|
||||||
bewertung: Literal["gut", "neutral", "schlecht"] | None = None # abgeleitet, nur fürs Einfärben
|
bewertung: Literal["gut", "neutral", "schlecht"] | None = None # aus Vorzeichen, fürs Einfärben
|
||||||
gute_antworten: int
|
gute_antworten: int
|
||||||
absolviert: bool
|
absolviert: bool
|
||||||
verstanden: bool = False
|
verstanden: bool = False
|
||||||
|
|||||||
@@ -254,9 +254,9 @@ def _basis(stand: dict, frage: str) -> tuple[int, bool]:
|
|||||||
return (stand["offene_basis"] if re_bewertung else stand["gute_antworten"]), re_bewertung
|
return (stand["offene_basis"] if re_bewertung else stand["gute_antworten"]), re_bewertung
|
||||||
|
|
||||||
|
|
||||||
def _farbe(niveau: str) -> str:
|
def _farbe(punkte: int) -> str:
|
||||||
"""Niveau → grobe Einfärbung der Bubble."""
|
"""Punkte-Delta → grobe Einfärbung der Bubble."""
|
||||||
return "gut" if niveau in ("solide", "stark", "komplett") else ("neutral" if niveau == "teilweise" else "schlecht")
|
return "gut" if punkte > 0 else ("neutral" if punkte == 0 else "schlecht")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/bausteine/pruefung", response_model=BausteinPruefungResponse)
|
@router.post("/bausteine/pruefung", response_model=BausteinPruefungResponse)
|
||||||
@@ -292,10 +292,22 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
|||||||
raise HTTPException(400, "Antwort braucht eine Nutzer-Antwort")
|
raise HTTPException(400, "Antwort braucht eine Nutzer-Antwort")
|
||||||
if not req.frage.strip():
|
if not req.frage.strip():
|
||||||
raise HTTPException(400, "Antwort braucht eine laufende Frage")
|
raise HTTPException(400, "Antwort braucht eine laufende Frage")
|
||||||
genau = req.aktion == "antwort_pruefen"
|
|
||||||
|
|
||||||
# Persist serialisiert je Baustein, Basis driftfrei über den offene_frage-Anker
|
if req.aktion == "antwort":
|
||||||
# (schnell setzt den Anker, genau re-bewertet dieselbe Frage). Beide vergeben Punkte.
|
# Agent 1: nur Vorschau — Niveau + voraussichtliche Punkte, NICHTS persistieren, kein Anker.
|
||||||
|
data = await pruefung_bewertung_schnell(
|
||||||
|
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, provider=req.provider,
|
||||||
|
)
|
||||||
|
if data is None:
|
||||||
|
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
|
||||||
|
basis, _ = _basis(stand, req.frage)
|
||||||
|
score = score_berechnen(basis, data["niveau"], req.nachgefragt, req.tier2, req.tier3, absolviert, verstanden, gemeistert)
|
||||||
|
punkte = score - basis
|
||||||
|
return {"feedback": data["feedback"], "punkte": punkte, "bewertung": _farbe(punkte),
|
||||||
|
"gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
||||||
|
|
||||||
|
# aktion "antwort_pruefen" (Agent 2 genau): verbindlich, persistiert. Serialisiert je Baustein,
|
||||||
|
# Basis driftfrei über den offene_frage-Anker. NUR hier ändert sich der Score.
|
||||||
async with _pruef_lock(req.topic, req.baustein):
|
async with _pruef_lock(req.topic, req.baustein):
|
||||||
stand = await get_baustein_progress(req.topic, req.baustein)
|
stand = await get_baustein_progress(req.topic, req.baustein)
|
||||||
absolviert = stand["absolviert"] is not None
|
absolviert = stand["absolviert"] is not None
|
||||||
@@ -305,20 +317,15 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
|||||||
if not re_bewertung:
|
if not re_bewertung:
|
||||||
await set_offene_frage(req.topic, req.baustein, req.frage, basis, 0)
|
await set_offene_frage(req.topic, req.baustein, req.frage, basis, 0)
|
||||||
|
|
||||||
if genau:
|
|
||||||
data = await pruefung_bewertung(
|
data = await pruefung_bewertung(
|
||||||
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, provider=req.provider,
|
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, provider=req.provider,
|
||||||
role="guide" if req.gruendlich else "judge", begruendung=req.begruendung,
|
role="guide" if req.gruendlich else "judge", begruendung=req.begruendung,
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
data = await pruefung_bewertung_schnell(
|
|
||||||
req.topic, req.baustein, req.section, kompakt, req.frage, msgs, provider=req.provider,
|
|
||||||
)
|
|
||||||
if data is None:
|
if data is None:
|
||||||
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
|
raise HTTPException(502, "Bewertung fehlgeschlagen — bitte erneut versuchen")
|
||||||
|
|
||||||
niveau = data["niveau"]
|
score = score_berechnen(basis, data["niveau"], req.nachgefragt, req.tier2, req.tier3, absolviert, verstanden, gemeistert)
|
||||||
score = score_berechnen(basis, niveau, req.nachgefragt, req.tier2, req.tier3, absolviert, verstanden, gemeistert)
|
punkte = score - basis
|
||||||
gute = await set_baustein_score(req.topic, req.baustein, score)
|
gute = await set_baustein_score(req.topic, req.baustein, score)
|
||||||
if score >= NOETIG and not absolviert:
|
if score >= NOETIG and not absolviert:
|
||||||
absolviert = True
|
absolviert = True
|
||||||
@@ -330,7 +337,8 @@ async def baustein_pruefung_route(req: BausteinPruefungRequest):
|
|||||||
if score >= MEISTERN and not gemeistert:
|
if score >= MEISTERN and not gemeistert:
|
||||||
await set_baustein_gemeistert(req.topic, req.baustein)
|
await set_baustein_gemeistert(req.topic, req.baustein)
|
||||||
gemeistert = True
|
gemeistert = True
|
||||||
return {"feedback": data["feedback"], "niveau": niveau, "bewertung": _farbe(niveau), "gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
return {"feedback": data["feedback"], "punkte": punkte, "bewertung": _farbe(punkte),
|
||||||
|
"gute_antworten": gute, "absolviert": absolviert, "verstanden": verstanden, "gemeistert": gemeistert}
|
||||||
|
|
||||||
|
|
||||||
# --- Guides ---
|
# --- Guides ---
|
||||||
|
|||||||
@@ -239,14 +239,14 @@ async function schnellBewerten() {
|
|||||||
provider: props.provider, messages: pruefDialog(), aktion: 'antwort', ...bewertungPayload(),
|
provider: props.provider, messages: pruefDialog(), aktion: 'antwort', ...bewertungPayload(),
|
||||||
})
|
})
|
||||||
if (meine !== pruefenRun) return
|
if (meine !== pruefenRun) return
|
||||||
applyPruefung(res)
|
// Agent 1 ändert den Score NICHT — nur Feedback + voraussichtliche Punkte zeigen.
|
||||||
letztesFeedback.value = res.feedback || ''
|
letztesFeedback.value = res.feedback || ''
|
||||||
pruefMessages.value.push({ role: 'assistant', kind: 'feedback', content: res.feedback || '', bewertung: res.bewertung, niveau: res.niveau, geprueft: false })
|
pruefMessages.value.push({ role: 'assistant', kind: 'feedback', content: res.feedback || '', bewertung: res.bewertung, punkte: res.punkte, geprueft: false })
|
||||||
letzteFeedbackMsg = pruefMessages.value[pruefMessages.value.length - 1]
|
letzteFeedbackMsg = pruefMessages.value[pruefMessages.value.length - 1]
|
||||||
pruefPhase.value = 'bewertet'
|
pruefPhase.value = 'bewertet'
|
||||||
pruefScroll()
|
pruefScroll()
|
||||||
nextTick(() => pruefInputEl.value?.focus())
|
nextTick(() => pruefInputEl.value?.focus())
|
||||||
genauBewerten() // Agent 2 korrigiert im Hintergrund
|
genauBewerten() // Agent 2 wertet verbindlich (setzt den Score)
|
||||||
} catch {
|
} catch {
|
||||||
if (meine === pruefenRun) pruefMessages.value.push({ role: 'assistant', kind: 'fehler', content: 'Hat nicht geklappt — bitte erneut.' })
|
if (meine === pruefenRun) pruefMessages.value.push({ role: 'assistant', kind: 'fehler', content: 'Hat nicht geklappt — bitte erneut.' })
|
||||||
} finally {
|
} finally {
|
||||||
@@ -268,7 +268,7 @@ async function genauBewerten(gruendlich = false, begruendung = '') {
|
|||||||
if (ziel) {
|
if (ziel) {
|
||||||
ziel.content = res.feedback || ziel.content
|
ziel.content = res.feedback || ziel.content
|
||||||
ziel.bewertung = res.bewertung
|
ziel.bewertung = res.bewertung
|
||||||
ziel.niveau = res.niveau
|
ziel.punkte = res.punkte
|
||||||
ziel.geprueft = true
|
ziel.geprueft = true
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -309,6 +309,11 @@ function gruendlichAbsenden() {
|
|||||||
genauBewerten(true, text)
|
genauBewerten(true, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function punkteLabel(p) {
|
||||||
|
if (p == null) return ''
|
||||||
|
return p > 0 ? `+${p}` : p < 0 ? String(p) : '±0'
|
||||||
|
}
|
||||||
|
|
||||||
// Abbruch wie im Chat: Run-Counter hochzählen → laufendes Ergebnis (pruefSenden
|
// Abbruch wie im Chat: Run-Counter hochzählen → laufendes Ergebnis (pruefSenden
|
||||||
// und schnellBewerten) wird verworfen, Buttons sofort frei. Agent läuft serverseitig aus.
|
// und schnellBewerten) wird verworfen, Buttons sofort frei. Agent läuft serverseitig aus.
|
||||||
function pruefAbbrechen() {
|
function pruefAbbrechen() {
|
||||||
@@ -414,8 +419,8 @@ function onPruefKey(e) {
|
|||||||
|
|
||||||
<div v-if="pruefMessages.length" ref="pruefMessagesEl" class="bp-messages" @scroll="onPruefScroll">
|
<div v-if="pruefMessages.length" ref="pruefMessagesEl" class="bp-messages" @scroll="onPruefScroll">
|
||||||
<template v-for="(m, i) in pruefMessages" :key="i">
|
<template v-for="(m, i) in pruefMessages" :key="i">
|
||||||
<div v-if="m.kind === 'feedback'" class="bp-feedback" :class="m.bewertung" title="Rechtsklick: gründlich prüfen" @contextmenu.prevent="gruendlichOeffnen(m)">
|
<div v-if="m.kind === 'feedback'" class="bp-feedback" :class="m.bewertung" title="Klick: gründlich prüfen" @click="gruendlichOeffnen(m)">
|
||||||
<span v-if="m.niveau" class="bp-niveau">{{ m.niveau }}</span>{{ m.content }}<span v-if="!m.geprueft" class="bp-pruefend"> · wird geprüft…</span>
|
<span v-if="m.punkte != null" class="bp-niveau">{{ punkteLabel(m.punkte) }}</span>{{ m.content }}<span v-if="!m.geprueft" class="bp-pruefend"> · wird geprüft…</span>
|
||||||
<div v-if="gruendlichMsg === m" class="bp-gruendlich" @click.stop>
|
<div v-if="gruendlichMsg === m" class="bp-gruendlich" @click.stop>
|
||||||
<input id="bp-gruendlich-input" v-model="gruendlichText" placeholder="Warum unzufrieden? (optional)" @keyup.enter="gruendlichAbsenden" />
|
<input id="bp-gruendlich-input" v-model="gruendlichText" placeholder="Warum unzufrieden? (optional)" @keyup.enter="gruendlichAbsenden" />
|
||||||
<button class="bp-action primary" @click="gruendlichAbsenden">Gründlich prüfen</button>
|
<button class="bp-action primary" @click="gruendlichAbsenden">Gründlich prüfen</button>
|
||||||
@@ -568,7 +573,7 @@ function onPruefKey(e) {
|
|||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.bp-pruefend { font-style: italic; opacity: 0.7; font-size: 0.92em; }
|
.bp-pruefend { font-style: italic; opacity: 0.7; font-size: 0.92em; }
|
||||||
.bp-feedback { cursor: context-menu; }
|
.bp-feedback { cursor: pointer; }
|
||||||
.bp-feedback.gut { background: var(--success-soft); border-color: var(--success-border); color: var(--success); }
|
.bp-feedback.gut { background: var(--success-soft); border-color: var(--success-border); color: var(--success); }
|
||||||
.bp-feedback.neutral { background: var(--warning-soft); border-color: var(--warning-border); color: var(--warning); }
|
.bp-feedback.neutral { background: var(--warning-soft); border-color: var(--warning-border); color: var(--warning); }
|
||||||
.bp-feedback.schlecht { background: var(--danger-soft, #fee2e2); border-color: var(--danger-border, #f87171); color: var(--danger); }
|
.bp-feedback.schlecht { background: var(--danger-soft, #fee2e2); border-color: var(--danger-border, #f87171); color: var(--danger); }
|
||||||
|
|||||||
Reference in New Issue
Block a user