customeragent-api / client /src /components /AgentTakeover.jsx
anasraza526's picture
Clean deploy to Hugging Face
ac90985
import React, { useState, useEffect, useRef } from 'react';
import { X, Send, Loader2 } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import axios from '../api/axiosConfig';
export default function AgentTakeover({ sessionId, visitorName, onClose }) {
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [isSending, setIsSending] = useState(false);
const [socket, setSocket] = useState(null);
const messagesEndRef = useRef(null);
useEffect(() => {
// Load session history
loadSessionHistory();
// Connect WebSocket (simplified - you'll need socket.io-client)
const ws = new WebSocket(`ws://localhost:8000/ws/session/${sessionId}`);
ws.onopen = () => {
// Send takeover event
ws.send(JSON.stringify({
type: 'takeover',
session_id: sessionId,
agent_id: 'current_user_id' // Get from auth context
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'visitor_message') {
setMessages(prev => [...prev, {
id: Date.now(),
message: data.message,
from: 'visitor',
timestamp: new Date()
}]);
}
};
setSocket(ws);
return () => {
if (ws) {
ws.close();
}
};
}, [sessionId]);
useEffect(() => {
scrollToBottom();
}, [messages]);
const loadSessionHistory = async () => {
try {
const response = await axios.get(`/api/chat/sessions/${sessionId}/messages`);
setMessages(response.data.map(msg => ({
id: msg.id,
message: msg.message,
from: msg.is_from_visitor ? 'visitor' : msg.is_from_ai ? 'ai' : 'agent',
timestamp: new Date(msg.created_at)
})));
setIsLoading(false);
} catch (error) {
console.error('Failed to load session history:', error);
setIsLoading(false);
}
};
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
const sendMessage = async () => {
if (!message.trim() || isSending || !socket) return;
setIsSending(true);
const newMessage = {
id: Date.now(),
message: message,
from: 'agent',
timestamp: new Date()
};
// Optimistic update
setMessages(prev => [...prev, newMessage]);
// Send via WebSocket
socket.send(JSON.stringify({
type: 'agent_message',
session_id: sessionId,
message: message,
agent_id: 'current_user_id'
}));
setMessage('');
setIsSending(false);
};
const handleKeyPress = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
};
const saveAsKnowledge = async (messageId) => {
try {
await axios.post(`/api/knowledge/save`, {
message_id: messageId,
session_id: sessionId
});
alert('Saved to knowledge base!');
} catch (error) {
console.error('Failed to save:', error);
}
};
return (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="bg-white rounded-2xl shadow-2xl w-full max-w-4xl h-[80vh] flex flex-col"
>
{/* Header */}
<div className="bg-gradient-to-r from-blue-600 to-blue-700 text-white p-6 rounded-t-2xl flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold">Live Chat Session</h2>
<p className="text-blue-100 text-sm mt-1">
Chatting with {visitorName || 'Anonymous'} • Session #{sessionId}
</p>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-white/10 rounded-lg transition-colors"
>
<X className="w-6 h-6" />
</button>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto p-6 space-y-4 bg-gray-50">
{isLoading ? (
<div className="flex items-center justify-center h-full">
<Loader2 className="w-8 h-8 animate-spin text-blue-600" />
</div>
) : (
<>
<AnimatePresence>
{messages.map((msg) => (
<motion.div
key={msg.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
className={`flex ${msg.from === 'visitor' ? 'justify-start' : 'justify-end'}`}
>
<div
className={`max-w-[70%] rounded-2xl px-4 py-3 ${msg.from === 'visitor'
? 'bg-white border border-gray-200 text-gray-900'
: msg.from === 'ai'
? 'bg-gradient-to-r from-purple-100 to-purple-50 border border-purple-200 text-purple-900'
: 'bg-gradient-to-r from-blue-600 to-blue-700 text-white'
}`}
>
<div className="flex items-center gap-2 mb-1">
<span className="text-xs font-semibold opacity-70">
{msg.from === 'visitor' ? 'Visitor' : msg.from === 'ai' ? '🤖 AI' : 'You'}
</span>
<span className="text-xs opacity-50">
{msg.timestamp.toLocaleTimeString()}
</span>
</div>
<p className="text-sm leading-relaxed">{msg.message}</p>
{/* Save as knowledge button for agent messages */}
{msg.from === 'agent' && (
<button
onClick={() => saveAsKnowledge(msg.id)}
className="mt-2 text-xs opacity-70 hover:opacity-100 underline"
>
Save as Knowledge
</button>
)}
</div>
</motion.div>
))}
</AnimatePresence>
<div ref={messagesEndRef} />
</>
)}
</div>
{/* Input */}
<div className="p-6 bg-white border-t border-gray-200 rounded-b-2xl">
<div className="flex gap-3">
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Type your message..."
rows="2"
className="flex-1 border border-gray-300 rounded-xl px-4 py-3 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
disabled={isSending}
/>
<button
onClick={sendMessage}
disabled={!message.trim() || isSending}
className="bg-gradient-to-r from-blue-600 to-blue-700 text-white px-6 py-3 rounded-xl font-semibold hover:shadow-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
{isSending ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
<>
<Send className="w-5 h-5" />
Send
</>
)}
</button>
</div>
{/* Quick actions */}
<div className="flex gap-2 mt-3">
<button className="text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded-lg transition-colors">
Mark as Resolved
</button>
<button className="text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded-lg transition-colors">
Request More Info
</button>
<button className="text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded-lg transition-colors">
Schedule Follow-up
</button>
</div>
</div>
</motion.div>
</div>
);
}