muhammad1707's picture
Move master code to main
8421ec4
Raw
History Blame Contribute Delete
7.19 kB
import React, { useState, useEffect, useCallback } from 'react';
import { Chat, Message, ChatState } from './types';
import { createChat, getChats, getMessages, sendMessage, deleteChat } from './services/api';
import ChatSidebar from './components/ChatSidebar';
import ChatWindow from './components/ChatWindow';
import DocumentsModal from './components/DocumentsModal';
const App: React.FC = () => {
const [state, setState] = useState<ChatState>({
chats: [],
activeChat: null,
messages: [],
isLoading: false,
isSending: false,
error: null,
});
const [isDocsModalOpen, setIsDocsModalOpen] = useState(false);
// Listen for custom event to open docs modal
useEffect(() => {
const handleOpenModal = () => setIsDocsModalOpen(true);
window.addEventListener('open-documents-modal', handleOpenModal);
return () => window.removeEventListener('open-documents-modal', handleOpenModal);
}, []);
// Load chats on mount
useEffect(() => {
loadChats();
}, []);
// Load all chats from API
const loadChats = async () => {
try {
setState(prev => ({ ...prev, isLoading: true, error: null }));
const chats = await getChats();
setState(prev => ({ ...prev, chats, isLoading: false }));
} catch (err) {
setState(prev => ({
...prev,
isLoading: false,
error: err instanceof Error ? err.message : 'Failed to load chats',
}));
}
};
// Load messages for a chat
const loadMessages = async (chatId: string) => {
try {
setState(prev => ({ ...prev, isLoading: true, error: null }));
const messages = await getMessages(chatId);
setState(prev => ({ ...prev, messages, isLoading: false }));
} catch (err) {
setState(prev => ({
...prev,
isLoading: false,
error: err instanceof Error ? err.message : 'Failed to load messages',
}));
}
};
// Handle selecting a chat
const handleSelectChat = useCallback(async (chat: Chat) => {
setState(prev => ({ ...prev, activeChat: chat, messages: [] }));
await loadMessages(chat.id);
}, []);
// Handle creating a new chat
const handleNewChat = useCallback(async () => {
try {
setState(prev => ({ ...prev, isLoading: true, error: null }));
const chat = await createChat();
setState(prev => ({
...prev,
chats: [chat, ...prev.chats],
activeChat: chat,
messages: [],
isLoading: false,
}));
} catch (err) {
setState(prev => ({
...prev,
isLoading: false,
error: err instanceof Error ? err.message : 'Failed to create chat',
}));
}
}, []);
// Handle deleting a chat
const handleDeleteChat = useCallback(async (chatId: string) => {
try {
await deleteChat(chatId);
setState(prev => {
const newChats = prev.chats.filter(c => c.id !== chatId);
const newActiveChat = prev.activeChat?.id === chatId ? null : prev.activeChat;
const newMessages = prev.activeChat?.id === chatId ? [] : prev.messages;
return {
...prev,
chats: newChats,
activeChat: newActiveChat,
messages: newMessages,
};
});
} catch (err) {
setState(prev => ({
...prev,
error: err instanceof Error ? err.message : 'Failed to delete chat',
}));
}
}, []);
// Handle sending a message
const handleSendMessage = useCallback(async (content: string) => {
if (!state.activeChat) return;
const chatId = state.activeChat.id;
// Optimistically add user message
const tempUserMsg: Message = {
id: `temp-${Date.now()}`,
chat_id: chatId,
role: 'user',
content,
timestamp: new Date().toISOString(),
};
setState(prev => ({
...prev,
messages: [...prev.messages, tempUserMsg],
isSending: true,
error: null,
}));
try {
const response = await sendMessage(chatId, content);
setState(prev => {
// Replace temp message with real messages
const messagesWithoutTemp = prev.messages.filter(m => m.id !== tempUserMsg.id);
const newMessages = [
...messagesWithoutTemp,
response.user_message,
response.assistant_message,
];
// Update chat title if it changed (first message)
const updatedChats = prev.chats.map(chat => {
if (chat.id === chatId && chat.title === 'New Chat') {
const newTitle = content.length > 50 ? content.slice(0, 50) + '...' : content;
return { ...chat, title: newTitle };
}
return chat;
});
// Update active chat title too
const updatedActiveChat = prev.activeChat && prev.activeChat.title === 'New Chat'
? { ...prev.activeChat, title: content.length > 50 ? content.slice(0, 50) + '...' : content }
: prev.activeChat;
return {
...prev,
messages: newMessages,
chats: updatedChats,
activeChat: updatedActiveChat,
isSending: false,
};
});
} catch (err) {
setState(prev => ({
...prev,
messages: prev.messages.filter(m => m.id !== tempUserMsg.id),
isSending: false,
error: err instanceof Error ? err.message : 'Failed to send message',
}));
}
}, [state.activeChat]);
return (
<div className="h-screen flex bg-gray-100 overflow-hidden">
{/* Sidebar */}
<ChatSidebar
chats={state.chats}
activeChat={state.activeChat}
onSelectChat={handleSelectChat}
onNewChat={handleNewChat}
onDeleteChat={handleDeleteChat}
isLoading={state.isLoading}
/>
{/* Main Chat Area */}
<ChatWindow
activeChat={state.activeChat}
messages={state.messages}
onSendMessage={handleSendMessage}
isSending={state.isSending}
isLoading={state.isLoading}
/>
{/* Error Toast */}
{state.error && (
<div className="fixed bottom-4 right-4 max-w-md bg-red-500 text-white px-6 py-4 rounded-xl shadow-2xl flex items-center gap-3 animate-in slide-in-from-bottom-4 duration-300">
<svg className="w-5 h-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-sm font-medium">{state.error}</span>
<button
onClick={() => setState(prev => ({ ...prev, error: null }))}
className="ml-2 hover:bg-red-600 p-1 rounded transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
)}
{/* Documents Management Modal */}
<DocumentsModal
isOpen={isDocsModalOpen}
onClose={() => setIsDocsModalOpen(false)}
/>
</div>
);
};
export default App;