Spaces:
Sleeping
Sleeping
| 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; | |