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. export function useChat(performRequest) { const messages = ref([]) const input = ref('') 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 && stick.value) messagesEl.value.scrollTop = messagesEl.value.scrollHeight } function autoGrow(max = 140) { const el = inputEl.value if (!el) return el.style.height = 'auto' el.style.height = Math.min(el.scrollHeight, max) + 'px' } function cancel() { run++ loading.value = false messages.value.push({ role: 'assistant', content: 'Abgebrochen.' }) } function reset() { run++ loading.value = false messages.value = [] input.value = '' } async function send() { if (loading.value) { // zweiter Klick = abbrechen cancel() return null } const text = input.value.trim() if (!text) return null stick.value = true // eigenes Senden = ans Ende; Hochscrollen während des Wartens setzt es wieder false const current = ++run messages.value.push({ role: 'user', content: text }) input.value = '' nextTick(() => autoGrow()) loading.value = true scrollToBottom() try { const res = await performRequest(messages.value) if (current !== run) return null // Prüfung liefert `frage` (+ getrenntes `feedback`); andere Chats `reply`. messages.value.push({ role: 'assistant', content: res.frage ?? res.reply ?? '…', feedback: res.feedback ?? null, bewertung: res.bewertung ?? null, }) return res } catch { if (current !== run) return null messages.value.push({ role: 'assistant', content: 'Fehler bei der Anfrage.' }) return null } finally { if (current === run) { loading.value = false scrollToBottom() nextTick(() => inputEl.value?.focus()) } } } return { messages, input, loading, messagesEl, inputEl, stick, onScroll, send, cancel, reset, scrollToBottom, autoGrow } }