import { useCallback, useEffect, useRef } from 'react' import type { FocusEvent, KeyboardEvent } from 'react' import { useAgentChatStore } from '../../stores/agentChatStore' import { useToastStore } from '../../stores/toastStore' /** * Markiert beide Eingabefelder des Agentenchats. Wandert der Fokus vom einen * zum anderen, ist das ein Zustandswechsel und kein Verlassen des Chats — ohne * diese Markierung löschte das `blur` des alten Feldes die Fokusnotiz, die das * neue Feld gerade braucht. */ export const AGENT_CHAT_MARKER = 'data-agent-chat' interface Options { /** * Ob diese Darstellung gerade die sichtbare ist. Beide Zustände hängen * gleichzeitig im Baum; nur der sichtbare darf den Fokus an sich ziehen. */ active?: boolean } /** * Gemeinsame Chatlogik für den ausführlichen und den kompakten Zustand * (Runde 5, §5). * * Beide Darstellungen senden dieselbe Nachricht, reagieren gleich auf Enter und * reichen den Fokus über den Zustandswechsel hinweg weiter. Läge das in beiden * Komponenten, wäre jede Korrektur zweimal zu machen — und beim zweiten Mal * vergessen. * * Das Absenden bleibt Frontend-Simulation: es existiert kein Chat-Backend, und * eine erfundene Antwort wäre schlimmer als eine ehrliche Rückmeldung. */ export function useAgentChat({ active = true }: Options = {}) { const agent = useAgentChatStore(s => s.agent) const draft = useAgentChatStore(s => s.draft) const focused = useAgentChatStore(s => s.focused) const setDraft = useAgentChatStore(s => s.setDraft) const setFocused = useAgentChatStore(s => s.setFocused) const showToast = useToastStore(s => s.showToast) const inputRef = useRef(null) /** * Wer beim Scrollen mitten im Satz ist, tippt nach dem Zustandswechsel * weiter — im jeweils sichtbaren Feld, mit dem Cursor am Ende des Textes. */ useEffect(() => { if (!active || !focused) return const el = inputRef.current if (!el || document.activeElement === el) return el.focus() const end = el.value.length el.setSelectionRange(end, end) }, [active, focused]) const send = useCallback(() => { const text = draft.trim() if (!text || !agent) return setDraft('') showToast( `Nachricht an ${agent.name} vorgemerkt — der Chat ist in dieser Demo noch nicht angebunden.`, 'info', ) }, [draft, agent, setDraft, showToast]) const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() send() } }, [send], ) const onBlur = useCallback( (e: FocusEvent) => { // Fokus wandert zum anderen Chatfeld → der Nutzer ist weiterhin im Chat. const next = e.relatedTarget if (next instanceof HTMLElement && next.closest(`[${AGENT_CHAT_MARKER}]`)) return // Das Feld verschwindet gerade aus dem Baum → Folge des Zustandswechsels, // keine Entscheidung des Nutzers. if (!e.currentTarget.isConnected) return setFocused(false) }, [setFocused], ) return { agent, draft, inputRef, setDraft, send, handleKeyDown, onFocus: useCallback(() => setFocused(true), [setFocused]), onBlur, } }