import { useEffect, useState, useRef } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import { useChatStore } from '../stores/chatStore'; import { useCharacterStore } from '../stores/characterStore'; import { useAuthStore } from '../stores/authStore'; import type { Message } from '../types'; import { io, Socket } from 'socket.io-client'; import { chatControllerSendMessage } from '../api/generated/conversations/conversations'; const WS_URL = (import.meta.env as unknown as ImportMetaEnv).VITE_WS_URL || 'http://localhost:3000'; export function Chat() { const { characterId, conversationId } = useParams<{ characterId?: string; conversationId?: string }>(); const navigate = useNavigate(); const { logout } = useAuthStore(); const { currentCharacter, getCharacter } = useCharacterStore(); const { currentConversation, isStreaming, error, createConversation, getConversation, addMessage, setStreaming, clearError } = useChatStore(); const [message, setMessage] = useState(''); const [streamingContent, setStreamingContent] = useState(''); const [socketConnected, setSocketConnected] = useState(false); const messagesEndRef = useRef(null); const socketRef = useRef(null); // Initialize socket connection useEffect(() => { const token = localStorage.getItem('accessToken'); if (!token) return; const socket = io(`${WS_URL}/chat`, { auth: { token: `Bearer ${token}` }, }); socketRef.current = socket; socket.on('connect', () => { console.log('Connected to chat server'); setSocketConnected(true); }); socket.on('disconnect', () => { console.log('Disconnected from chat server'); setSocketConnected(false); }); socket.on('connect_error', (err) => { console.error('Socket connection error:', err.message); setSocketConnected(false); }); socket.on('message_chunk', (data: { conversationId: string; chunk: { content: string } }) => { if (data.conversationId === conversationId) { setStreamingContent((prev) => prev + data.chunk.content); } }); socket.on('message_complete', () => { setStreaming(false); setStreamingContent(''); if (conversationId) { getConversation(conversationId); } }); socket.on('message', (data: { message: { assistantMessage?: Message } }) => { if (data.message.assistantMessage) { addMessage(data.message.assistantMessage); } }); socket.on('error', (data: { message: string }) => { console.error('Socket error:', data.message); setStreaming(false); }); return () => { socket.disconnect(); }; }, [conversationId, addMessage, getConversation, setStreaming]); // Join conversation room useEffect(() => { if (socketRef.current && conversationId) { socketRef.current.emit('join_conversation', { conversationId }); return () => { socketRef.current?.emit('leave_conversation', { conversationId }); }; } }, [conversationId]); // Load character and conversation useEffect(() => { if (characterId) { getCharacter(characterId); // Create new conversation createConversation({ characterId }).then((conv) => { navigate(`/conversations/${conv.id}`, { replace: true }); }); } else if (conversationId) { getConversation(conversationId); } }, [characterId, conversationId, getCharacter, createConversation, getConversation, navigate]); // Scroll to bottom when messages change useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [currentConversation?.messages, streamingContent]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!message.trim() || !conversationId || isStreaming) return; const content = message.trim(); setMessage(''); setStreamingContent(''); setStreaming(true); // Check if socket is connected, otherwise fallback to HTTP if (socketRef.current?.connected) { socketRef.current.emit('send_message', { conversationId, content, }); } else { // Fallback to HTTP API when socket is not connected try { const result = await chatControllerSendMessage(conversationId, { content }); // Add messages to the conversation if (result.userMessage) { addMessage(result.userMessage as Message); } if (result.assistantMessage) { addMessage(result.assistantMessage as Message); } setStreaming(false); } catch (error) { console.error('Failed to send message:', error); setStreaming(false); } } }; const handleLogout = () => { logout(); navigate('/login'); }; const formatTime = (dateString: string) => { return new Date(dateString).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', }); }; const messages = currentConversation?.messages || []; return (
← Back

{currentConversation?.title || 'Chat'}

{currentCharacter && (

with {currentCharacter.name}

)}
{error && (
{error}
)}
{messages.length === 0 && !isStreaming && (

Start a conversation with {currentCharacter?.name || 'your character'}

)} {messages.map((msg) => (

{msg.content}

{formatTime(msg.createdAt)}
))} {isStreaming && streamingContent && (

{streamingContent}

typing...
)}
setMessage(e.target.value)} placeholder="Type your message..." disabled={isStreaming} className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent disabled:opacity-50" />
); }