alaselababatunde's picture
Updated
4128af1
import React, { useState, useEffect, useRef } from 'react';
import { Send, User, Bot, Loader2 } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
import axios from 'axios';
interface Message {
role: 'user' | 'assistant';
content: string;
}
const App: React.FC = () => {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [sessionId, setSessionId] = useState<string | null>(null);
const chatEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
// Initialize session
const initSession = async () => {
try {
const response = await axios.get('/session');
setSessionId(response.data.session_id);
} catch (error) {
console.error('Failed to initialize session:', error);
}
};
initSession();
}, []);
useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const handleSend = async () => {
if (!input.trim() || isLoading) return;
const userMessage = input.trim();
setInput('');
setMessages(prev => [...prev, { role: 'user', content: userMessage }]);
setIsLoading(true);
try {
const response = await fetch('/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: userMessage,
session_id: sessionId,
}),
});
if (!response.ok) throw new Error('Failed to send message');
if (!response.body) throw new Error('No response body');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let assistantContent = '';
setMessages(prev => [...prev, { role: 'assistant', content: '' }]);
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
assistantContent += chunk;
setMessages(prev => {
const newMessages = [...prev];
newMessages[newMessages.length - 1].content = assistantContent;
return newMessages;
});
}
} catch (error) {
console.error('Chat error:', error);
setMessages(prev => [...prev, { role: 'assistant', content: 'I apologize, but I encountered an error. Please try again or contact support if the issue persists.' }]);
} finally {
setIsLoading(false);
}
};
return (
<div className="app-container">
<header className="header">
<a href="https://www.dstv.com" target="_blank" rel="noopener noreferrer">
<img src="/logo.jpeg" alt="DStv Logo" className="logo" />
</a>
<h1>DStv AI Support</h1>
</header>
<div className="chat-window">
{messages.length === 0 && (
<div className="message assistant">
<p>Hello! I'm your DStv AI Support assistant. How can I help you today?</p>
</div>
)}
{messages.map((msg, index) => (
<div key={index} className={`message ${msg.role}`}>
<div className="flex items-center gap-2 mb-2 font-bold text-xs opacity-70">
{msg.role === 'user' ? <User size={14} /> : <Bot size={14} />}
{msg.role === 'user' ? 'You' : 'DStv Support'}
</div>
<div className="markdown-content">
<ReactMarkdown>{msg.content}</ReactMarkdown>
</div>
</div>
))}
{isLoading && messages[messages.length - 1]?.role !== 'assistant' && (
<div className="message assistant">
<div className="typing-animation">
<div className="dot"></div>
<div className="dot"></div>
<div className="dot"></div>
</div>
</div>
)}
<div ref={chatEndRef} />
</div>
<div className="input-area">
<div className="input-wrapper">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
placeholder="Type your message here..."
disabled={isLoading}
/>
</div>
<button onClick={handleSend} disabled={isLoading || !input.trim()}>
{isLoading ? <Loader2 className="animate-spin" /> : <Send size={20} />}
</button>
</div>
</div>
);
};
export default App;