import { useState, useEffect } from 'react'; import Sidebar from './components/Sidebar'; import ChatInterface from './components/ChatInterface'; import { api } from './api'; import './App.css'; function App() { const [conversations, setConversations] = useState([]); const [currentConversationId, setCurrentConversationId] = useState(null); const [currentConversation, setCurrentConversation] = useState(null); const [isLoading, setIsLoading] = useState(false); // Load conversations on mount useEffect(() => { loadConversations(); }, []); // Load conversation details when selected useEffect(() => { if (currentConversationId) { loadConversation(currentConversationId); } }, [currentConversationId]); const loadConversations = async () => { try { const convs = await api.listConversations(); setConversations(convs); } catch (error) { console.error('Failed to load conversations:', error); } }; const loadConversation = async (id) => { try { const conv = await api.getConversation(id); setCurrentConversation(conv); } catch (error) { console.error('Failed to load conversation:', error); } }; const handleNewConversation = async () => { try { const newConv = await api.createConversation(); setConversations([ { id: newConv.id, created_at: newConv.created_at, message_count: 0 }, ...conversations, ]); setCurrentConversationId(newConv.id); } catch (error) { console.error('Failed to create conversation:', error); } }; const handleSelectConversation = (id) => { setCurrentConversationId(id); }; const handleSendMessage = async (content) => { if (!currentConversationId) return; setIsLoading(true); try { // Optimistically add user message to UI const userMessage = { role: 'user', content }; setCurrentConversation((prev) => ({ ...prev, messages: [...prev.messages, userMessage], })); // Create a partial assistant message that will be updated progressively const assistantMessage = { role: 'assistant', stage1: null, stage2: null, stage3: null, metadata: null, loading: { stage1: false, stage2: false, stage3: false, }, }; // Add the partial assistant message setCurrentConversation((prev) => ({ ...prev, messages: [...prev.messages, assistantMessage], })); // Send message with streaming await api.sendMessageStream(currentConversationId, content, (eventType, event) => { switch (eventType) { case 'stage1_start': setCurrentConversation((prev) => { const messages = [...prev.messages]; const lastMsg = messages[messages.length - 1]; lastMsg.loading.stage1 = true; return { ...prev, messages }; }); break; case 'stage1_complete': setCurrentConversation((prev) => { const messages = [...prev.messages]; const lastMsg = messages[messages.length - 1]; lastMsg.stage1 = event.data; lastMsg.loading.stage1 = false; return { ...prev, messages }; }); break; case 'stage2_start': setCurrentConversation((prev) => { const messages = [...prev.messages]; const lastMsg = messages[messages.length - 1]; lastMsg.loading.stage2 = true; return { ...prev, messages }; }); break; case 'stage2_complete': setCurrentConversation((prev) => { const messages = [...prev.messages]; const lastMsg = messages[messages.length - 1]; lastMsg.stage2 = event.data; lastMsg.metadata = event.metadata; lastMsg.loading.stage2 = false; return { ...prev, messages }; }); break; case 'stage3_start': setCurrentConversation((prev) => { const messages = [...prev.messages]; const lastMsg = messages[messages.length - 1]; lastMsg.loading.stage3 = true; return { ...prev, messages }; }); break; case 'stage3_complete': setCurrentConversation((prev) => { const messages = [...prev.messages]; const lastMsg = messages[messages.length - 1]; lastMsg.stage3 = event.data; lastMsg.loading.stage3 = false; return { ...prev, messages }; }); break; case 'title_complete': // Reload conversations to get updated title loadConversations(); break; case 'complete': // Stream complete, reload conversations list loadConversations(); setIsLoading(false); break; case 'error': console.error('Stream error:', event.message); setIsLoading(false); break; default: console.log('Unknown event type:', eventType); } }); } catch (error) { console.error('Failed to send message:', error); // Remove optimistic messages on error setCurrentConversation((prev) => ({ ...prev, messages: prev.messages.slice(0, -2), })); setIsLoading(false); } }; return (
); } export default App;