Spaces:
Running
Running
| 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<Message[]>([ | |
| { | |
| role: "assistant", | |
| content: "Hello! I'm your AI assistant. How can I help you today?" | |
| } | |
| ]); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [error, setError] = useState<string | null>(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<HTMLDivElement>(null); | |
| useEffect(() => { | |
| if (ref.current) { | |
| ref.current.scrollTop = ref.current.scrollHeight; | |
| } | |
| }, [dependency]); | |
| return ref; | |
| } |