import { useCallback, useEffect, useRef, useState } from 'react'; import Mark from '../ui/Mark'; import ChatMessage from './ChatMessage'; import { useChat } from './useChat'; import { kbStore } from '../../lib/kbStore'; import { BOT_NAME, STRINGS } from './prompts'; export default function ChatWindow({ user, isAdmin, onClose }) { const { messages, isThinking, send } = useChat({ user, isAdmin }); const [draft, setDraft] = useState(''); const bodyRef = useRef(null); const inputRef = useRef(null); const [decided, setDecided] = useState({}); // { [msgId]: 'applied'|'queued'|'rejected' } // Scroll to bottom when new messages arrive useEffect(() => { if (bodyRef.current) { bodyRef.current.scrollTop = bodyRef.current.scrollHeight; } }, [messages, isThinking]); // Focus the input on open useEffect(() => { inputRef.current?.focus(); }, []); // Close on Escape useEffect(() => { const onKey = (e) => { if (e.key === 'Escape') onClose?.(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [onClose]); const handleSubmit = (e) => { e.preventDefault(); if (!draft.trim() || isThinking) return; send(draft); setDraft(''); }; const handleAccept = useCallback(async (msgId) => { const msg = messages.find(m => m.id === msgId); if (!msg?.suggestion) return; if (isAdmin) { await kbStore.applyDelta(msg.suggestion); setDecided(prev => ({ ...prev, [msgId]: 'applied' })); } else { kbStore.appendSuggestion({ ...msg.suggestion, proposedBy: user?.id, proposedByName: user?.name, }); setDecided(prev => ({ ...prev, [msgId]: 'queued' })); } }, [messages, isAdmin, user]); const handleReject = useCallback((msgId) => { setDecided(prev => ({ ...prev, [msgId]: 'rejected' })); }, []); const renderedMessages = messages.map(m => { if (!m.suggestion) return m; const status = decided[m.id] || m.suggestion.status || 'pending'; return { ...m, suggestion: { ...m.suggestion, status } }; }); return (
{BOT_NAME}
{STRINGS.status}
{renderedMessages.map(m => ( ))} {isThinking && (
)}
setDraft(e.target.value)} placeholder={STRINGS.placeholder} disabled={isThinking} aria-label={STRINGS.placeholder} />
); }