|
|
import React, { useState, useRef, useEffect } from 'react'; |
|
|
import { Send } from 'lucide-react'; |
|
|
import ReactMarkdown from 'react-markdown'; |
|
|
|
|
|
const ChatInterface = ({ sessionId }) => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const [messages, setMessages] = useState([]); |
|
|
const [input, setInput] = useState(''); |
|
|
const [isLoading, setIsLoading] = useState(false); |
|
|
const messagesEndRef = useRef(null); |
|
|
|
|
|
const scrollToBottom = () => { |
|
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); |
|
|
}; |
|
|
|
|
|
useEffect(() => { |
|
|
scrollToBottom(); |
|
|
}, [messages]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
const fetchHistory = async () => { |
|
|
try { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setMessages([ |
|
|
{ |
|
|
role: 'assistant', |
|
|
content: 'Hello, I\'m Tesco Support. How can I help you today?' |
|
|
} |
|
|
]); |
|
|
} catch (e) { |
|
|
console.error(e); |
|
|
} |
|
|
}; |
|
|
fetchHistory(); |
|
|
}, [sessionId]); |
|
|
|
|
|
const handleSubmit = async (e) => { |
|
|
e.preventDefault(); |
|
|
if (!input.trim() || isLoading) return; |
|
|
|
|
|
const userMessage = input; |
|
|
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({ session_id: sessionId, message: userMessage }), |
|
|
}); |
|
|
|
|
|
if (!response.ok) throw new Error('Network response was not ok'); |
|
|
|
|
|
const reader = response.body.getReader(); |
|
|
const decoder = new TextDecoder(); |
|
|
|
|
|
|
|
|
setMessages(prev => [...prev, { role: 'assistant', content: '' }]); |
|
|
|
|
|
while (true) { |
|
|
const { done, value } = await reader.read(); |
|
|
if (done) break; |
|
|
|
|
|
const chunk = decoder.decode(value, { stream: true }); |
|
|
|
|
|
setMessages(prev => { |
|
|
const newMessages = [...prev]; |
|
|
const lastMsg = newMessages[newMessages.length - 1]; |
|
|
|
|
|
if (lastMsg.role === 'assistant') { |
|
|
newMessages[newMessages.length - 1] = { |
|
|
...lastMsg, |
|
|
content: lastMsg.content + chunk |
|
|
}; |
|
|
} |
|
|
return newMessages; |
|
|
}); |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Error:', error); |
|
|
setMessages(prev => [...prev, { |
|
|
role: 'assistant', |
|
|
content: 'I apologize, but I encountered an error. Please try again later.' |
|
|
}]); |
|
|
} finally { |
|
|
setIsLoading(false); |
|
|
} |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<div className="chat-container"> |
|
|
<header className="chat-header"> |
|
|
<div className="logo-area"> |
|
|
<span className="logo-text">TESCO</span> |
|
|
</div> |
|
|
<div className="header-title"> |
|
|
<h1>Support</h1> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
<div className="messages-area"> |
|
|
{messages.map((msg, index) => ( |
|
|
<div key={index} className={`message ${msg.role}`}> |
|
|
{msg.role === 'assistant' ? ( |
|
|
<div className="markdown-body"> |
|
|
<ReactMarkdown>{msg.content}</ReactMarkdown> |
|
|
</div> |
|
|
) : ( |
|
|
msg.content |
|
|
)} |
|
|
</div> |
|
|
))} |
|
|
{isLoading && messages[messages.length - 1]?.role === 'user' && ( |
|
|
<div className="typing-indicator"> |
|
|
<div className="dot"></div> |
|
|
<div className="dot"></div> |
|
|
<div className="dot"></div> |
|
|
</div> |
|
|
)} |
|
|
<div ref={messagesEndRef} /> |
|
|
</div> |
|
|
|
|
|
<form className="input-area" onSubmit={handleSubmit}> |
|
|
<input |
|
|
type="text" |
|
|
className="input-field" |
|
|
value={input} |
|
|
onChange={(e) => setInput(e.target.value)} |
|
|
placeholder="Type a message..." |
|
|
disabled={isLoading} |
|
|
/> |
|
|
<button type="submit" className="send-button" disabled={isLoading || !input.trim()}> |
|
|
<Send size={24} /> |
|
|
</button> |
|
|
</form> |
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default ChatInterface; |
|
|
|