import { useRef, useEffect, useState, useCallback, useMemo } from 'react'; import { useAuth } from '@persona-community-2/auth'; import { useChat } from '@persona-community-2/realtime'; import { Card, CardHeader, CardTitle, CardDescription, CardContent, ChatBubble, ChatInput, Badge, ProviderBadge, } from '@persona-community-2/ui'; interface TimelineMessage { id: string; content: string; role: 'user' | 'assistant' | 'system'; timestamp: Date; provider?: string; isStreaming?: boolean; } export function ChatPage() { const { user, getToken } = useAuth(); const messagesEndRef = useRef(null); // API base URL from environment const apiBaseUrl = import.meta.env.VITE_API_URL || ''; const authHeaders = useMemo(() => { const token = getToken(); return token ? { Authorization: `Bearer ${token}` } : undefined; }, [getToken]); const { messages, aiMessages, streamingMessages, sendMessage, connectionState, onlineUsers, } = useChat({ endpoint: `${apiBaseUrl}/api/{{SERVICE_NAME}}/chat/messages`, sseEndpoint: `${apiBaseUrl}/api/{{SERVICE_NAME}}/events`, channel: 'channel:general', userId: user?.id || 'anonymous', userName: user?.name || user?.email || 'Anonymous', headers: authHeaders, }); // Track send errors for user feedback const [sendError, setSendError] = useState(null); // Merge user messages + AI messages into a single sorted timeline const timeline = useMemo(() => { const combined: TimelineMessage[] = []; for (const msg of messages) { combined.push({ id: msg.id, content: msg.content, role: msg.userId === user?.id ? 'user' : 'assistant', timestamp: new Date(msg.timestamp), }); } for (const msg of aiMessages) { combined.push({ id: msg.id, content: msg.content, role: 'assistant', timestamp: new Date(msg.timestamp), provider: msg.provider, }); } // Add in-progress streaming messages for (const [, stream] of streamingMessages) { combined.push({ id: stream.streamId, content: stream.content, role: 'assistant', timestamp: new Date(stream.timestamp), isStreaming: true, }); } combined.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); return combined; }, [messages, aiMessages, streamingMessages, user?.id]); // Handle sending a message (wraps async sendMessage for ChatInput) const handleSendMessage = useCallback((content: string) => { sendMessage(content).catch(() => { setSendError('Failed to send message. Please try again.'); setTimeout(() => setSendError(null), 3000); }); }, [sendMessage]); // Auto-scroll to bottom when new messages arrive useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [timeline]); const connectionBadge = () => { switch (connectionState) { case 'connected': return Connected; case 'connecting': return Connecting...; case 'disconnected': return Disconnected; case 'error': return Error; default: return null; } }; return (
AI Chat Chat with AI in real-time
{onlineUsers.length} online {connectionBadge()}
{/* Messages area */}
{timeline.length === 0 ? (

No messages yet. Start the conversation!

) : ( timeline.map((msg) => (
{msg.provider && (
)}
)) )}
{/* Input area */}
{sendError && (
{sendError}
)}
); }