'use client'; import { useState, useEffect, useRef, memo } from 'react'; import { Send, Bot, User, Sparkles, Loader2 } from 'lucide-react'; // Use environment variable or relative URL for production const API_URL = process.env.NEXT_PUBLIC_API_URL || ''; interface Transaction { id: number; amount: number; merchant: string; prediction: string; final_score: number; } interface Message { id: string; role: 'user' | 'assistant'; content: string; timestamp: Date; } interface ChatPanelProps { transactions: Transaction[]; metrics: { total: number; flagged: number; accuracy: number; }; } // Memoized to prevent re-renders from parent const ChatPanel = memo(function ChatPanel({ transactions, metrics }: ChatPanelProps) { const [messages, setMessages] = useState([ { id: '1', role: 'assistant', content: "Hello! I'm the QuantumShield AI Assistant. I can help you analyze transactions and explain our quantum detection. What would you like to know?", timestamp: new Date() } ]); const [input, setInput] = useState(''); const [isLoading, setIsLoading] = useState(false); const messagesEndRef = useRef(null); const abortControllerRef = useRef(null); // Store latest data in refs to avoid stale closures const transactionsRef = useRef(transactions); const metricsRef = useRef(metrics); useEffect(() => { transactionsRef.current = transactions; metricsRef.current = metrics; }, [transactions, metrics]); // Scroll to bottom on new messages useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); const sendMessage = async () => { if (!input.trim() || isLoading) return; const userInput = input.trim(); const userMessage: Message = { id: Date.now().toString(), role: 'user', content: userInput, timestamp: new Date() }; setMessages(prev => [...prev, userMessage]); setInput(''); setIsLoading(true); // Cancel any pending request if (abortControllerRef.current) { abortControllerRef.current.abort(); } abortControllerRef.current = new AbortController(); try { // Include current transaction context in the message const contextMessage = ` Current System Status: - Total Transactions Processed: ${metricsRef.current.total} - Flagged as Fraud: ${metricsRef.current.flagged} - Model Accuracy: ${(metricsRef.current.accuracy * 100).toFixed(1)}% - Recent Transactions: ${transactionsRef.current.slice(0, 5).map(t => `#${t.id}: $${t.amount.toFixed(2)} at ${t.merchant} (${t.prediction}, score: ${(t.final_score * 100).toFixed(0)}%)` ).join('; ')} User Question: ${userInput}`; const response = await fetch(`${API_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: contextMessage, conversation_history: messages.slice(-10).map(m => ({ role: m.role, content: m.content })) }), signal: abortControllerRef.current.signal }); if (response.ok) { const data = await response.json(); setMessages(prev => [...prev, { id: (Date.now() + 1).toString(), role: 'assistant', content: data.response, timestamp: new Date() }]); } else { throw new Error('Request failed'); } } catch (error: unknown) { if (error instanceof Error && error.name === 'AbortError') return; setMessages(prev => [...prev, { id: (Date.now() + 1).toString(), role: 'assistant', content: "Connection error. Please check if the backend is running.", timestamp: new Date() }]); } finally { setIsLoading(false); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }; return (
{/* Header */}

AI Operations Lead

Quantum Analysis Expert

{/* Messages */}
{messages.map((msg) => (
{msg.role === 'user' ? ( ) : ( )}
{msg.content}
))} {isLoading && (
)}
{/* Input */}
setInput(e.target.value)} onKeyPress={handleKeyPress} placeholder="Ask about fraud detection..." className="flex-1 bg-gray-100 rounded-lg px-3 py-2 text-gray-800 text-xs placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 border border-gray-200" disabled={isLoading} />
); }); export default ChatPanel;