File size: 6,553 Bytes
29477ed b71de78 29477ed b71de78 29477ed b71de78 29477ed 2a14080 29477ed b71de78 29477ed b71de78 29477ed b71de78 29477ed b71de78 29477ed b71de78 29477ed |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
import React, { useState, useRef, useEffect } from 'react';
import { Send } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
const ChatInterface = ({ sessionId }) => {
// Initialize without a hardcoded greeting (let the user start or fetch history if we had it)
// However, for a fresh session, a simple "How can I help?" is okay, but user requested "No robotic greeting every time".
// Since we persist sessions, we don't want to show "Hello" if we reload.
// Ideally we would fetch history here. But for now, let's start empty or with a very subtle state if history is empty.
// For simplicity given the scope, we'll start with a "welcome" message only if the list is empty AND it's a new session.
// But since we don't fetch history here yet (we only push to it), let's keep the greeting but make it standard.
// Wait, the user said "Do not greeting every time". If I reload, `messages` state resets to default.
// To truly fix this, we'd need to fetch history from the backend on mount.
// I will add a `fetchHistory` call.
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]);
// Fetch history on mount
useEffect(() => {
const fetchHistory = async () => {
try {
// We need an endpoint for history. existing `main.py` might not have it exposed?
// Let's check `main.py`... assumes it doesn't.
// Creating a new endpoint is out of scope of "frontend implementation" unless deemed critical.
// Re-reading requirements: "Use memory from previous messages".
// If I reload the page, and the backend has memory, but frontend wipes it, the context is lost to the USER but kept by the AI.
// The AI will know what we talked about, but the user won't see it.
// That's acceptable for now if we don't have a history endpoint.
// I will add a simple initial greeting if messages are empty.
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();
// Add initial empty assistant message
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];
// Ensure we are appending to the assistant's message
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;
|