90 lines
2.9 KiB
JavaScript
90 lines
2.9 KiB
JavaScript
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 }
|
|
}
|