import { useEffect, useMemo, useRef, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { ArrowDown } from 'lucide-react'; import { api } from '@/lib/api'; import type { Course } from '@/lib/types'; import { useAuth } from '@/stores/auth'; import { useChat } from '@/stores/chat'; import { useUI } from '@/stores/ui'; import { MessageBubble } from './message-bubble'; import { Composer } from './composer'; import { EmptyState } from './empty-state'; export function ChatView() { const { id } = useParams(); const nav = useNavigate(); const user = useAuth((s) => s.user); const { course, setCourse } = useUI(); const { messages, streaming, warnings, loadMessages, createConversation, send, stop, regenerate, feedback } = useChat(); const [deep, setDeep] = useState(!!user?.preferences.deep); const [atBottom, setAtBottom] = useState(true); const scrollRef = useRef(null); const { data: courses } = useQuery({ queryKey: ['courses'], queryFn: () => api('/courses'), staleTime: 300_000 }); const conv = useChat((s) => s.conversations.find((c) => c.id === id)); const list = id ? messages[id] || [] : []; const isStreaming = id ? !!streaming[id] : false; const activeCourse = conv?.course || course; const courseObj = useMemo(() => courses?.find((c) => c.code === activeCourse), [courses, activeCourse]); const courseCodes = (courses || []).map((c) => c.code); useEffect(() => { if (id && !messages[id]) loadMessages(id).catch(() => nav('/')); }, [id, messages, loadMessages, nav]); useEffect(() => { if (atBottom && list.length > 0) scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight }); }, [list, atBottom]); const onScroll = () => { const el = scrollRef.current; if (!el) return; setAtBottom(el.scrollHeight - el.scrollTop - el.clientHeight < 80); }; const handleSend = async (text: string, attachments: string[]) => { let convId = id; if (!convId) { const c = await createConversation(activeCourse); convId = c.id; nav(`/c/${c.id}`, { replace: true }); } setAtBottom(true); await send(convId, text, attachments, deep); }; const initials = (user?.display_name || user?.email || 'É').slice(0, 2).toUpperCase(); const lastAssistant = [...list].reverse().find((m) => m.role === 'assistant'); return (
{isStreaming &&