diff --git a/frontend/src/components/BausteinPanel.vue b/frontend/src/components/BausteinPanel.vue
index 271c98c..007d69c 100644
--- a/frontend/src/components/BausteinPanel.vue
+++ b/frontend/src/components/BausteinPanel.vue
@@ -2,7 +2,7 @@
import { computed, nextTick, ref } from 'vue'
import { chatBaustein, createVertiefung, fetchVertiefung, pruefeBaustein } from '../api.js'
import { renderMarkdown } from '../markdown.js'
-import { useChat } from '../composables/useChat.js'
+import { useChat, istUnten } from '../composables/useChat.js'
const props = defineProps({
topic: { type: String, required: true },
@@ -80,15 +80,20 @@ const letztesFeedback = ref('') // Kontext für die Diskussion über eine Bewer
const scoreVorFrage = ref(0) // Score, als die aktuelle Frage gestellt wurde → driftfreies (Re-)Bewerten
const pruefMessagesEl = ref(null)
const pruefInputEl = ref(null)
+const pruefStick = ref(true) // nur auto-scrollen, wenn der Nutzer (fast) unten ist
let pruefRun = 0
+function onPruefScroll() {
+ if (pruefMessagesEl.value) pruefStick.value = istUnten(pruefMessagesEl.value)
+}
+
function applyPruefung(res) {
emit('statusChanged', { ...st.value, gute_antworten: res.gute_antworten, absolviert: res.absolviert, verstanden: res.verstanden })
}
async function pruefScroll() {
await nextTick()
- if (pruefMessagesEl.value) pruefMessagesEl.value.scrollTop = pruefMessagesEl.value.scrollHeight
+ if (pruefMessagesEl.value && pruefStick.value) pruefMessagesEl.value.scrollTop = pruefMessagesEl.value.scrollHeight
}
// Nur echte Gesprächs-Turns ans Backend; Feedback bleibt reines UI-Artefakt.
@@ -201,7 +206,7 @@ function neuBewerten() {
-
+
Frag etwas zu diesem Baustein. Der Verlauf wird nicht gespeichert.
@@ -232,7 +237,7 @@ function neuBewerten() {
{{ Math.min(st.gute_antworten, NOETIG) }}/{{ NOETIG }} guten Antworten. Frag nach, wenn etwas unklar ist — diskutieren ist erlaubt.
-
+
{{ m.content }}
{{ m.content }}
diff --git a/frontend/src/components/TopicDetail.vue b/frontend/src/components/TopicDetail.vue
index 8dfa605..e342017 100644
--- a/frontend/src/components/TopicDetail.vue
+++ b/frontend/src/components/TopicDetail.vue
@@ -68,7 +68,7 @@ const chat = useChat((msgs) => {
section, outline, messages: msgs, provider: props.provider,
})
})
-const { messages, input, loading, messagesEl, inputEl, send } = chat
+const { messages, input, loading, messagesEl, inputEl, onScroll, send } = chat
const autoGrow = () => chat.autoGrow()
const chatOpen = ref(false)
const panelEl = ref(null)
@@ -199,7 +199,7 @@ function extractContext() {
Fragen zum Guide
-
+
Stell eine Frage zum aktuellen Abschnitt.
diff --git a/frontend/src/components/elements/ElementChatTab.vue b/frontend/src/components/elements/ElementChatTab.vue
index 487e66c..de34c2b 100644
--- a/frontend/src/components/elements/ElementChatTab.vue
+++ b/frontend/src/components/elements/ElementChatTab.vue
@@ -11,7 +11,7 @@ const props = defineProps({
const emit = defineEmits(['changes'])
const chat = useChat((msgs) => chatElement(props.element.id, msgs, props.provider))
-const { messages, input, loading, messagesEl, inputEl } = chat
+const { messages, input, loading, messagesEl, inputEl, onScroll } = chat
// Anderes Element gewählt → Verlauf verwerfen
watch(() => props.element.id, () => chat.reset())
@@ -24,7 +24,7 @@ async function send() {
-
+
Schreib, was am Element geändert werden soll.
{{ m.content }}
diff --git a/frontend/src/composables/useChat.js b/frontend/src/composables/useChat.js
index 85a23be..d93b1ef 100644
--- a/frontend/src/composables/useChat.js
+++ b/frontend/src/composables/useChat.js
@@ -1,5 +1,10 @@
import { ref, nextTick } from 'vue'
+// (Fast) am unteren Rand? Schwelle fängt Sub-Pixel und kleine Abstände ab.
+export function istUnten(el, schwelle = 60) {
+ return el.scrollHeight - el.scrollTop - el.clientHeight < schwelle
+}
+
// Gemeinsame Chat-Mechanik: senden, abbrechen (Run-Counter), scrollen, Fokus.
// performRequest(messages) → Promise<{ reply, … }>; send() gibt die Antwort
// zurück, damit der Aufrufer Extras (z. B. changes) auswerten kann.
@@ -9,11 +14,17 @@ export function useChat(performRequest) {
const loading = ref(false)
const messagesEl = ref(null) // Template-Ref: Nachrichten-Container
const inputEl = ref(null) // Template-Ref: Textarea
+ const stick = ref(true) // an den Boden „gepinnt" — nur dann auto-scrollen
let run = 0 // laufende Anfrage identifizieren; Abbruch ignoriert ihr Ergebnis
+ // @scroll-Handler: pinnt nur, wenn der Nutzer (fast) unten ist.
+ function onScroll() {
+ if (messagesEl.value) stick.value = istUnten(messagesEl.value)
+ }
+
async function scrollToBottom() {
await nextTick()
- if (messagesEl.value) messagesEl.value.scrollTop = messagesEl.value.scrollHeight
+ if (messagesEl.value && stick.value) messagesEl.value.scrollTop = messagesEl.value.scrollHeight
}
function autoGrow(max = 140) {
@@ -73,5 +84,5 @@ export function useChat(performRequest) {
}
}
- return { messages, input, loading, messagesEl, inputEl, send, cancel, reset, scrollToBottom, autoGrow }
+ return { messages, input, loading, messagesEl, inputEl, stick, onScroll, send, cancel, reset, scrollToBottom, autoGrow }
}