diff --git a/app/ai/page.tsx b/app/ai/page.tsx index 3de2dfee0..b3cf8c2a2 100644 --- a/app/ai/page.tsx +++ b/app/ai/page.tsx @@ -41,7 +41,7 @@ export default function AIPage() { const router = useRouter(); const [profile, setProfile] = useState<{ first_name: string; last_name: string } | null>(null); const [profileLoading, setProfileLoading] = useState(true); - + const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); @@ -61,7 +61,7 @@ export default function AIPage() { useEffect(() => { const fetchProfile = async () => { if (!user) return; - + try { const supabase = createClient(); const { data, error } = await supabase @@ -184,11 +184,28 @@ export default function AIPage() { setMessages(prev => [...prev, userMessage]); setInput(''); setIsLoading(true); - // setIsTyping(true); // Typing indicator disabled setShowSuggestions(false); - // Don't add typing message to the messages array anymore - // We'll handle it with a separate state + // Show immediate typing indicator while waiting for stream to start + const thinkingId = `page-thinking-${Date.now()}`; + const thinkingMessage: Message = { + id: thinkingId, + text: '', + sender: 'ai', + timestamp: new Date(), + isTyping: true + }; + setMessages(prev => [...prev, thinkingMessage]); + + // Create streaming message placeholder (will replace thinking indicator) + const streamingId = `page-streaming-${Date.now()}`; + const streamingMessage: Message = { + id: streamingId, + text: '', + sender: 'ai', + timestamp: new Date(), + isTyping: false + }; try { const response = await fetch('/api/ai', { @@ -203,26 +220,87 @@ export default function AIPage() { throw new Error(`HTTP error! status: ${response.status}`); } - const data: AIResponse = await response.json(); - - // setIsTyping(false); // Typing indicator disabled - - if (data.success) { - const aiMessage: Message = { - id: `page-ai-${Date.now() + 1}`, - text: data.response, - sender: 'ai', - timestamp: new Date(), - context: data.context - }; - setMessages(prev => [...prev, aiMessage]); + // Check if response is streaming (SSE) or regular JSON + const contentType = response.headers.get('content-type'); + + if (contentType?.includes('text/event-stream')) { + // STREAMING MODE + // Replace thinking indicator with streaming message + setMessages(prev => prev.filter(m => m.id !== thinkingId).concat(streamingMessage)); + + const reader = response.body?.getReader(); + const decoder = new TextDecoder(); + let accumulatedText = ''; + let currentContext = ''; + + if (!reader) throw new Error('No reader available'); + + while (true) { + const { done, value } = await reader.read(); + + if (done) break; + + const chunk = decoder.decode(value); + const lines = chunk.split('\n'); + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6); + + try { + const parsed = JSON.parse(data); + + if (parsed.done) { + break; + } + + if (parsed.error) { + throw new Error(parsed.error); + } + + if (parsed.content) { + accumulatedText += parsed.content; + currentContext = parsed.context || currentContext; + + // Update the streaming message in real-time + setMessages(prev => + prev.map(msg => + msg.id === streamingId + ? { ...msg, text: accumulatedText, context: currentContext } + : msg + ) + ); + } + } catch { + // Ignore malformed JSON + continue; + } + } + } + } } else { - throw new Error(data.error || 'AI response was not successful'); + // NON-STREAMING MODE (fallback) + const data: AIResponse = await response.json(); + + if (data.success) { + const aiMessage: Message = { + id: `page-ai-${Date.now() + 1}`, + text: data.response, + sender: 'ai', + timestamp: new Date(), + context: data.context + }; + setMessages(prev => [...prev, aiMessage]); + } else { + throw new Error(data.error || 'AI response was not successful'); + } } } catch (error) { console.error('Error sending message:', error); - // setIsTyping(false); // Typing indicator disabled - + + // Remove both thinking indicator and streaming message if present + setMessages(prev => prev.filter(m => m.id !== thinkingId && m.id !== streamingId)); + const errorMessage: Message = { id: `page-error-${Date.now() + 1}`, text: 'Sorry, I\'m having trouble connecting to the AI service. Please try again later.', @@ -309,27 +387,27 @@ export default function AIPage() {
- - -
- + {message.sender === 'user' && (
@@ -493,7 +569,7 @@ export default function AIPage() {
)} - +