import { useState, useEffect, useCallback } from 'react' import { supabase } from '@/lib/supabase' import { Chat, Message, MessageUI } from '@/lib/types/database' import { generateChatName } from '@/lib/utils/chatNaming' interface UseChatProps { bookId?: string userId?: string } interface UseChatReturn { // Current chat state currentChat: Chat | null messages: MessageUI[] isLoading: boolean error: string | null // Chat history chatHistory: Chat[] isLoadingHistory: boolean // Actions createNewChat: (title?: string) => Promise loadChat: (chatId: string) => Promise saveMessage: (message: Omit) => Promise updateChatTitle: (title: string) => Promise deleteChat: (chatId: string) => Promise loadChatHistory: () => Promise updateChatModel: (model: string) => Promise } export function useChat({ bookId, userId }: UseChatProps): UseChatReturn { const [currentChat, setCurrentChat] = useState(null) const [messages, setMessages] = useState([]) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) const [hasInitialized, setHasInitialized] = useState(false) const [chatHistory, setChatHistory] = useState([]) const [isLoadingHistory, setIsLoadingHistory] = useState(false) // Convert database message to UI message const convertMessage = useCallback((dbMessage: Message): MessageUI => ({ id: dbMessage.id, type: dbMessage.type, content: dbMessage.content, timestamp: new Date(dbMessage.created_at), model: dbMessage.model || undefined, tool_results: dbMessage.tool_results || undefined, context_info: dbMessage.context_info || undefined, }), []) // Load chat history for the current book const loadChatHistory = useCallback(async () => { if (!bookId || !userId) { setChatHistory([]) return [] } setIsLoadingHistory(true) try { const { data, error } = await supabase .from('chats') .select('*') .eq('book_id', bookId) .eq('user_id', userId) .order('updated_at', { ascending: false }) if (error) throw error setChatHistory(data || []) return data || [] } catch (err) { console.error('Error loading chat history:', err) setError('Failed to load chat history') return [] } finally { setIsLoadingHistory(false) } }, [bookId, userId]) // Load an existing chat const loadChat = useCallback(async (chatId: string) => { setIsLoading(true) setError(null) try { // Load chat metadata const { data: chatData, error: chatError } = await supabase .from('chats') .select('*') .eq('id', chatId) .single() if (chatError) throw chatError setCurrentChat(chatData as Chat) // Load messages for this chat const { data: messagesData, error: messagesError } = await supabase .from('messages') .select('*') .eq('chat_id', chatId) .order('sequence_number', { ascending: true }) if (messagesError) throw messagesError const uiMessages = (messagesData || []).map(convertMessage) setMessages(uiMessages) } catch (err) { console.error('Error loading chat:', err) setError('Failed to load chat') } finally { setIsLoading(false) } }, [convertMessage]) // Create a new chat const createNewChat = useCallback(async (title = 'New Chat'): Promise => { if (!bookId || !userId) { setError('Book ID and User ID are required') return null } try { // Check if there's already an empty chat (0 messages) const { data: existingChats, error: checkError } = await supabase .from('chats') .select('*') .eq('book_id', bookId) .eq('user_id', userId) .eq('total_messages', 0) .order('created_at', { ascending: false }) .limit(1) if (checkError) throw checkError // If there's already an empty chat, just switch to it instead of creating a new one if (existingChats && existingChats.length > 0) { const existingEmptyChat = existingChats[0] as Chat console.log('Found existing empty chat, switching to it:', existingEmptyChat.title) setCurrentChat(existingEmptyChat) setMessages([]) // Clear any old messages return existingEmptyChat } // Create a new chat only if no empty chat exists const { data, error } = await supabase .from('chats') .insert({ book_id: bookId, user_id: userId, title, }) .select() .single() if (error) throw error const newChat = data as Chat setCurrentChat(newChat) setMessages([]) // Refresh chat history to include the new chat await loadChatHistory() return newChat } catch (err) { console.error('Error creating chat:', err) setError('Failed to create new chat') return null } }, [bookId, userId, loadChatHistory]) // Load the most recent chat on initialization const loadMostRecentChat = useCallback(async () => { if (!bookId || !userId || hasInitialized) return setIsLoading(true) try { const chats = await loadChatHistory() if (chats.length > 0) { // Load the most recent chat const mostRecentChat = chats[0] await loadChat(mostRecentChat.id) } else { // If no chats exist, create an initial chat automatically console.log('No chats found for book, creating initial chat...') const initialChat = await createNewChat('New Chat') if (initialChat) { console.log('Initial chat created successfully:', initialChat.title) } } setHasInitialized(true) } catch (err) { console.error('Error loading most recent chat:', err) setHasInitialized(true) } finally { setIsLoading(false) } }, [bookId, userId, hasInitialized, loadChatHistory, loadChat, createNewChat]) // Save a new message const saveMessage = useCallback(async (message: Omit) => { if (!currentChat) { setError('No active chat') return } try { // Calculate sequence number const sequenceNumber = messages.length + 1 const { data, error } = await supabase .from('messages') .insert({ chat_id: currentChat.id, type: message.type, content: message.content, sequence_number: sequenceNumber, model: message.model || null, tool_results: message.tool_results || null, context_info: message.context_info || null, }) .select() .single() if (error) throw error // Add to local state const newMessage = convertMessage(data as Message) setMessages(prev => [...prev, newMessage]) // Generate AI-powered chat title based on first user message if it's still "New Chat" if (currentChat.title === 'New Chat' && message.type === 'user' && messages.length === 0) { console.log('Generating AI-powered chat name for:', message.content) try { const generatedTitle = await generateChatName(message.content) console.log('Generated chat name:', generatedTitle) await updateChatTitle(generatedTitle) } catch (err) { console.error('Error generating chat name, using fallback:', err) // Fallback to truncated message if AI generation fails const fallbackTitle = message.content.length > 50 ? message.content.substring(0, 50) + '...' : message.content await updateChatTitle(fallbackTitle) } } } catch (err) { console.error('Error saving message:', err) setError('Failed to save message') } }, [currentChat, messages.length, convertMessage]) // Update chat title const updateChatTitle = useCallback(async (title: string) => { if (!currentChat) return try { const { error } = await supabase .from('chats') .update({ title }) .eq('id', currentChat.id) if (error) throw error setCurrentChat(prev => prev ? { ...prev, title } : null) // Refresh chat history to show updated title await loadChatHistory() } catch (err) { console.error('Error updating chat title:', err) setError('Failed to update chat title') } }, [currentChat, loadChatHistory]) // Delete a chat const deleteChat = useCallback(async (chatId: string) => { try { const { error } = await supabase .from('chats') .delete() .eq('id', chatId) if (error) throw error // If deleting current chat, clear it if (currentChat?.id === chatId) { setCurrentChat(null) setMessages([]) } // Refresh chat history await loadChatHistory() } catch (err) { console.error('Error deleting chat:', err) setError('Failed to delete chat') } }, [currentChat, loadChatHistory]) // Update chat model const updateChatModel = useCallback(async (model: string) => { if (!currentChat) return try { const { error } = await supabase .from('chats') .update({ model }) .eq('id', currentChat.id) if (error) throw error setCurrentChat(prev => prev ? { ...prev, model } : null) // Refresh chat history to show updated model await loadChatHistory() } catch (err) { console.error('Error updating chat model:', err) setError('Failed to update chat model') } }, [currentChat, loadChatHistory]) // Initialize chat when bookId and userId are available useEffect(() => { if (bookId && userId && !hasInitialized) { loadMostRecentChat() } }, [bookId, userId, hasInitialized, loadMostRecentChat]) return { currentChat, messages, isLoading, error, chatHistory, isLoadingHistory, createNewChat, loadChat, saveMessage, updateChatTitle, deleteChat, loadChatHistory, updateChatModel, } }