Frontend: Composables useConfirm/useChat/usePolling; Guide-Chat abbrechbar
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
71
frontend/src/composables/useChat.js
Normal file
71
frontend/src/composables/useChat.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { ref, nextTick } from 'vue'
|
||||
|
||||
// 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
|
||||
let run = 0 // laufende Anfrage identifizieren; Abbruch ignoriert ihr Ergebnis
|
||||
|
||||
async function scrollToBottom() {
|
||||
await nextTick()
|
||||
if (messagesEl.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
|
||||
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
|
||||
messages.value.push({ role: 'assistant', content: res.reply || '…' })
|
||||
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, send, cancel, reset, scrollToBottom, autoGrow }
|
||||
}
|
||||
Reference in New Issue
Block a user