Skip to main content
Glama
ChatInterface.tsx11.8 kB
import React, { useState, useRef, useEffect } from 'react'; import { Send, Bot, User, Loader2, Sparkles, Zap } from 'lucide-react'; import { useAuth } from '@/context/AuthContext'; interface Message { id: string; content: string; sender: 'user' | 'bot'; timestamp: Date; } interface ChatInterfaceProps { className?: string; welcomeMessage?: string; placeholder?: string; height?: string; } const ChatInterface: React.FC<ChatInterfaceProps> = ({ className = '', welcomeMessage = "Hello! How can I help you today? 👋", placeholder = "Type your message...", height = "h-[600px]" }) => { const { user } = useAuth(); const [messages, setMessages] = useState<Message[]>([ { id: '1', content: welcomeMessage, sender: 'bot', timestamp: new Date() } ]); const [inputMessage, setInputMessage] = useState(''); const [isLoading, setIsLoading] = useState(false); const [sessionToken, setSessionToken] = useState<string | null>(null); const [tenantId, setTenantId] = useState<string | null>(null); const messagesEndRef = useRef<HTMLDivElement>(null); const messagesContainerRef = useRef<HTMLDivElement>(null); const scrollToBottom = () => { if (messagesEndRef.current && messagesContainerRef.current) { messagesEndRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' }); } }; useEffect(() => { const timer = setTimeout(() => { scrollToBottom(); }, 100); return () => clearTimeout(timer); }, [messages]); // Initialize chat session useEffect(() => { const initializeChat = async () => { const token = localStorage.getItem('auth-token'); if (!token) { console.error('No auth token found'); return; } try { const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001/api'; const response = await fetch(`${API_BASE_URL}/chat/sessions`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ domain: window.location.hostname, userAgent: navigator.userAgent }), }); if (response.ok) { const data = await response.json(); setSessionToken(data.sessionToken); setTenantId(data.tenantId); } else { console.error('Failed to create chat session'); } } catch (error) { console.error('Error initializing chat session:', error); } }; initializeChat(); }, [user]); const handleSendMessage = async (e: React.FormEvent) => { e.preventDefault(); if (!inputMessage.trim() || isLoading || !sessionToken || !tenantId) { if (!sessionToken || !tenantId) { console.error('Chat session not initialized'); } return; } const userMessage: Message = { id: Date.now().toString(), content: inputMessage.trim(), sender: 'user', timestamp: new Date() }; setMessages(prev => [...prev, userMessage]); setInputMessage(''); setIsLoading(true); try { const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001/api'; const response = await fetch(`${API_BASE_URL}/chat/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ sessionToken, message: userMessage.content, tenantId }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); const botMessage: Message = { id: (Date.now() + 1).toString(), content: data.response, sender: 'bot', timestamp: new Date(data.timestamp) }; setMessages(prev => [...prev, botMessage]); } catch (error) { console.error('Failed to send message:', error); const errorMessage: Message = { id: (Date.now() + 1).toString(), content: "I'm sorry, I'm experiencing technical difficulties. Please try again in a moment. 🔧", sender: 'bot', timestamp: new Date() }; setMessages(prev => [...prev, errorMessage]); } finally { setIsLoading(false); } }; const formatTime = (date: Date) => { return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); }; return ( <div className={`flex flex-col card-modern ${height} ${className}`}> {/* Header */} <div className="flex-shrink-0 p-6 gradient-primary rounded-t-2xl text-white"> <div className="flex items-center justify-between"> <div className="flex items-center"> <div className="w-12 h-12 rounded-2xl bg-white/20 backdrop-blur-sm flex items-center justify-center shadow-lg"> <Bot className="h-6 w-6 text-white" /> </div> <div className="ml-4"> <p className="font-semibold text-lg">AI Support Assistant</p> <div className="flex items-center mt-1"> <div className="w-2 h-2 bg-green-400 rounded-full animate-pulse mr-2"></div> <span className="text-sm text-white/90">Online & Ready to Help</span> </div> </div> </div> <div className="flex items-center space-x-2"> <div className="flex items-center bg-white/20 rounded-full px-3 py-1"> <Sparkles className="h-4 w-4 mr-1" /> <span className="text-sm font-medium">AI Powered</span> </div> </div> </div> </div> {/* Messages Container */} <div ref={messagesContainerRef} className="flex-1 overflow-y-auto p-6 space-y-6 min-h-0 max-h-full bg-gradient-to-b from-gray-50/50 to-white" style={{ scrollBehavior: 'smooth', overflowAnchor: 'none' }} > {messages.map((message, index) => ( <div key={message.id} className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'} animate-fade-in-up`} style={{ animationDelay: `${index * 0.1}s` }} > <div className={`max-w-[85%] rounded-2xl px-5 py-4 shadow-soft transition-all duration-300 hover:shadow-medium ${ message.sender === 'user' ? 'gradient-primary text-white ml-12' : 'bg-white border border-gray-100 text-gray-900 mr-12' }`} > <div className="flex items-start space-x-3"> <div className={`flex-shrink-0 mt-1 ${ message.sender === 'user' ? 'order-2' : 'order-1' }`}> {message.sender === 'user' ? ( <div className="w-8 h-8 bg-white/20 rounded-xl flex items-center justify-center"> <User className="h-4 w-4 text-white" /> </div> ) : ( <div className="w-8 h-8 bg-gradient-to-r from-primary-500 to-primary-600 rounded-xl flex items-center justify-center shadow-md"> <Bot className="h-4 w-4 text-white" /> </div> )} </div> <div className={`flex-1 ${ message.sender === 'user' ? 'order-1' : 'order-2' }`}> <p className="text-sm leading-relaxed whitespace-pre-wrap break-words"> {message.content} </p> <p className={`text-xs mt-2 ${ message.sender === 'user' ? 'text-white/70' : 'text-gray-500' }`}> {formatTime(message.timestamp)} </p> </div> </div> </div> </div> ))} {isLoading && ( <div className="flex justify-start animate-fade-in-up"> <div className="bg-white border border-gray-100 rounded-2xl px-5 py-4 shadow-soft mr-12"> <div className="flex items-center space-x-3"> <div className="w-8 h-8 bg-gradient-to-r from-primary-500 to-primary-600 rounded-xl flex items-center justify-center shadow-md"> <Bot className="h-4 w-4 text-white" /> </div> <div className="flex items-center space-x-2"> <Loader2 className="h-4 w-4 animate-spin text-primary-500" /> <span className="text-sm text-gray-600">AI is thinking...</span> <div className="flex space-x-1"> <div className="w-2 h-2 bg-primary-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></div> <div className="w-2 h-2 bg-primary-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></div> <div className="w-2 h-2 bg-primary-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></div> </div> </div> </div> </div> </div> )} <div ref={messagesEndRef} className="h-0" /> </div> {/* Input Form */} <div className="flex-shrink-0 p-6 bg-white border-t border-gray-100 rounded-b-2xl"> <form onSubmit={handleSendMessage} className="flex space-x-4"> <div className="flex-1 relative"> <input type="text" value={inputMessage} onChange={(e) => setInputMessage(e.target.value)} placeholder={placeholder} disabled={isLoading} className="input-modern pr-12" autoComplete="off" /> {inputMessage.trim() && ( <div className="absolute right-3 top-1/2 transform -translate-y-1/2"> <Zap className="h-4 w-4 text-primary-500" /> </div> )} </div> <button type="submit" disabled={!inputMessage.trim() || isLoading} className="btn-modern flex-shrink-0 group relative overflow-hidden" > <div className="flex items-center"> {isLoading ? ( <Loader2 className="h-5 w-5 animate-spin" /> ) : ( <> <Send className="h-5 w-5 transition-transform group-hover:translate-x-0.5" /> <span className="ml-2 hidden sm:inline">Send</span> </> )} </div> </button> </form> {/* Quick suggestions for first message */} {messages.length === 1 && !isLoading && ( <div className="mt-4 flex flex-wrap gap-2"> <p className="text-xs text-gray-500 w-full mb-2">Quick suggestions:</p> {[ "How does this work?", "What can you help me with?", "Tell me about your features" ].map((suggestion) => ( <button key={suggestion} onClick={() => setInputMessage(suggestion)} className="text-xs px-3 py-2 bg-gray-100 hover:bg-primary-50 hover:text-primary-600 rounded-full text-gray-600 transition-all duration-200 hover:shadow-sm" > {suggestion} </button> ))} </div> )} </div> </div> ); }; export default ChatInterface;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ChiragPatankar/MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server