'use client' import { useState, useEffect, useRef, useCallback } from 'react' import { useChat } from '@ai-sdk/react' import { DefaultChatTransport } from 'ai' import { ChatSidebar } from './chat-sidebar' import { ChatMessages } from './chat-messages' import { ChatInput } from './chat-input' import { createConversation, getConversationDetails, getConversations, deleteConversation } from '@/app/actions/chat-actions' import { toast } from 'sonner' import type { UIMessage } from 'ai' import { useLanguage } from '@/lib/i18n' interface ChatContainerProps { initialConversations: any[] notebooks: any[] webSearchAvailable?: boolean } export function ChatContainer({ initialConversations, notebooks, webSearchAvailable }: ChatContainerProps) { const { t, language } = useLanguage() const [conversations, setConversations] = useState(initialConversations) const [currentId, setCurrentId] = useState(null) const [selectedNotebook, setSelectedNotebook] = useState(undefined) const [webSearchEnabled, setWebSearchEnabled] = useState(false) const [historyMessages, setHistoryMessages] = useState([]) const [isLoadingHistory, setIsLoadingHistory] = useState(false) // Prevents the useEffect from loading an empty conversation // when we just created one via createConversation() const skipHistoryLoad = useRef(false) const scrollRef = useRef(null) const transport = useRef(new DefaultChatTransport({ api: '/api/chat', })).current const { messages, sendMessage, status, setMessages, } = useChat({ transport, onError: (error) => { toast.error(error.message || t('chat.assistantError')) }, }) const isLoading = status === 'submitted' || status === 'streaming' // Sync historyMessages after each completed streaming response // so the display doesn't revert to stale history useEffect(() => { if (status === 'ready' && messages.length > 0) { setHistoryMessages([...messages]) } }, [status, messages]) // Load conversation details when the user selects a different conversation useEffect(() => { // Skip if we just created the conversation — useChat already has the messages if (skipHistoryLoad.current) { skipHistoryLoad.current = false return } if (currentId) { const loadMessages = async () => { setIsLoadingHistory(true) try { const details = await getConversationDetails(currentId) if (details) { const loaded: UIMessage[] = details.messages.map((m: any, i: number) => ({ id: m.id || `hist-${i}`, role: m.role as 'user' | 'assistant', parts: [{ type: 'text' as const, text: m.content }], })) setHistoryMessages(loaded) setMessages(loaded) } } catch (error) { toast.error(t('chat.loadError')) } finally { setIsLoadingHistory(false) } } loadMessages() } else { setMessages([]) setHistoryMessages([]) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentId]) const refreshConversations = useCallback(async () => { try { const updated = await getConversations() setConversations(updated) } catch {} }, []) const handleSendMessage = async (content: string, notebookId?: string) => { if (notebookId) { setSelectedNotebook(notebookId) } // If no active conversation, create one BEFORE streaming let convId = currentId if (!convId) { try { const result = await createConversation(content, notebookId || selectedNotebook) convId = result.id // Tell the useEffect to skip — we don't want to load an empty conversation skipHistoryLoad.current = true setCurrentId(convId) setHistoryMessages([]) setConversations((prev) => [ { id: result.id, title: result.title, updatedAt: new Date() }, ...prev, ]) } catch { toast.error(t('chat.createError')) return } } await sendMessage( { text: content }, { body: { conversationId: convId, notebookId: notebookId || selectedNotebook || undefined, language, webSearch: webSearchEnabled, }, } ) } const handleNewChat = () => { setCurrentId(null) setMessages([]) setHistoryMessages([]) setSelectedNotebook(undefined) setWebSearchEnabled(false) } const handleDeleteConversation = async (id: string) => { try { await deleteConversation(id) if (currentId === id) { handleNewChat() } await refreshConversations() } catch { toast.error(t('chat.deleteError')) } } // During streaming or if useChat has more messages than history, prefer useChat const displayMessages = isLoading || messages.length > historyMessages.length ? messages : historyMessages // Auto-scroll to bottom when messages change useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight } }, [displayMessages]) return (
setWebSearchEnabled(prev => !prev)} webSearchAvailable={webSearchAvailable} />
) }