import { useState, useEffect, useRef, useCallback } from 'react'; import { apiRequest } from './queryClient'; import { Message, ChatResponse, ModelInfo } from './types'; // Hook for managing chat state export function useChat(initialConversationId = "default") { const [messages, setMessages] = useState([ { role: "assistant", content: "Hello! I'm your AI assistant. How can I help you today?" } ]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [conversationId, setConversationId] = useState(initialConversationId); const [currentModel, setCurrentModel] = useState<'openai' | 'qwen' | 'unavailable'>('openai'); const [isConnected, setIsConnected] = useState(true); // Load message history for a conversation const loadMessages = useCallback(async (convId: string) => { try { setIsLoading(true); setError(null); const response = await fetch(`/api/conversations/${convId}/messages`, { credentials: 'include', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); if (!response.ok) { if (response.status === 404) { // If conversation not found, reset to welcome message setMessages([ { role: "assistant", content: "Hello! I'm your AI assistant. How can I help you today?" } ]); return; } throw new Error('Failed to load message history'); } const data = await response.json() as Message[]; if (data.length > 0) { setMessages(data); } else { // Reset to welcome message if no messages setMessages([ { role: "assistant", content: "Hello! I'm your AI assistant. How can I help you today?" } ]); } } catch (err: any) { setError(err.message); console.error('Error loading messages:', err); } finally { setIsLoading(false); } }, []); // Auto-load messages when conversation ID changes useEffect(() => { const fetchMessages = async () => { if (conversationId) { try { await loadMessages(conversationId); } catch (error) { console.error("Failed to load messages:", error); } } }; fetchMessages(); }, [conversationId]); // Send a message to the API const sendMessage = useCallback(async (content: string) => { if (!content.trim()) return; try { setIsLoading(true); setError(null); // Add user message to state immediately for UI const userMessage: Message = { role: "user", content }; setMessages(prev => [...prev, userMessage]); // Prepare message history for API // Filter out any messages without content or role const messageHistory = messages .filter(msg => msg.content && msg.role) .map(msg => ({ role: msg.role, content: msg.content })); // Add the new user message messageHistory.push({ role: userMessage.role, content: userMessage.content }); // Make API request const response = await apiRequest('POST', '/api/chat', { messages: messageHistory, conversationId }); const data = await response.json() as ChatResponse; // Add the assistant's response to state setMessages(prev => [...prev, data.message]); // Update current model if provided if (data.modelInfo) { setCurrentModel(data.modelInfo.model); } // Auto-refresh message history to sync with server setTimeout(() => { loadMessages(conversationId); }, 1000); } catch (err: any) { let errorMessage = err.message || 'Failed to send message'; // Check if it's a quota exceeded error and provide a more friendly message if (errorMessage.includes('quota exceeded') || errorMessage.includes('insufficient_quota')) { errorMessage = "The OpenAI API quota has been exceeded. This often happens with free accounts. The system will attempt to use the Qwen fallback model."; } setError(errorMessage); console.error('Error sending message:', err); } finally { setIsLoading(false); } }, [messages, conversationId, loadMessages]); // Clear the current conversation const clearConversation = useCallback(() => { setMessages([ { role: "assistant", content: "Hello! I'm your AI assistant. How can I help you today?" } ]); }, []); // Check connection status useEffect(() => { const checkConnection = async () => { try { const response = await fetch('/api/health'); setIsConnected(response.ok); } catch (err) { setIsConnected(false); } }; checkConnection(); const interval = setInterval(checkConnection, 30000); return () => clearInterval(interval); }, []); // Also check model status periodically useEffect(() => { const checkModelStatus = async () => { try { const response = await fetch('/api/model-status'); if (response.ok) { const data = await response.json(); setCurrentModel(data.model); } } catch (err) { console.error('Error checking model status:', err); } }; checkModelStatus(); const interval = setInterval(checkModelStatus, 5 * 60 * 1000); return () => clearInterval(interval); }, []); return { messages, isLoading, error, conversationId, isConnected, currentModel, sendMessage, clearConversation, loadMessages, setConversationId }; } // Hook to scroll to bottom of chat export function useScrollToBottom(dependency: any) { const ref = useRef(null); useEffect(() => { if (ref.current) { ref.current.scrollTop = ref.current.scrollHeight; } }, [dependency]); return ref; }