Spaces:
Build error
Build error
| 'use client' | |
| import { useState, useRef, useEffect } from 'react' | |
| import { Send, Bot, User, Loader2, Sparkles } from 'lucide-react' | |
| const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000' | |
| interface Message { | |
| role: 'user' | 'assistant' | |
| content: string | |
| timestamp: Date | |
| } | |
| interface ChatBotProps { | |
| transactions: any[] | |
| metrics: any | |
| } | |
| const suggestedQuestions = [ | |
| "Why is my precision so low?", | |
| "How can I improve recall performance?", | |
| "Explain how the quantum ensemble works", | |
| "What's the optimal fraud threshold?", | |
| "Compare VQC, QAOA, and QNN performance" | |
| ] | |
| export default function ChatBot({ transactions, metrics }: ChatBotProps) { | |
| const [messages, setMessages] = useState<Message[]>([]) | |
| const [input, setInput] = useState('') | |
| const [isLoading, setIsLoading] = useState(false) | |
| const messagesEndRef = useRef<HTMLDivElement>(null) | |
| const scrollToBottom = () => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) | |
| } | |
| useEffect(() => { | |
| scrollToBottom() | |
| }, [messages]) | |
| const sendMessage = async (messageText?: string) => { | |
| const text = messageText || input | |
| if (!text.trim() || isLoading) return | |
| const userMessage: Message = { | |
| role: 'user', | |
| content: text, | |
| timestamp: new Date() | |
| } | |
| setMessages(prev => [...prev, userMessage]) | |
| setInput('') | |
| setIsLoading(true) | |
| try { | |
| const response = await fetch(`${API_URL}/api/chat`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| message: text, | |
| history: transactions | |
| }) | |
| }) | |
| const data = await response.json() | |
| const assistantMessage: Message = { | |
| role: 'assistant', | |
| content: data.response || 'Sorry, I could not generate a response.', | |
| timestamp: new Date() | |
| } | |
| setMessages(prev => [...prev, assistantMessage]) | |
| } catch (error) { | |
| const errorMessage: Message = { | |
| role: 'assistant', | |
| content: '⚠️ Failed to connect to the AI service. Please check if the backend is running.', | |
| timestamp: new Date() | |
| } | |
| setMessages(prev => [...prev, errorMessage]) | |
| } finally { | |
| setIsLoading(false) | |
| } | |
| } | |
| const handleKeyDown = (e: React.KeyboardEvent) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault() | |
| sendMessage() | |
| } | |
| } | |
| return ( | |
| <div className="grid grid-cols-1 lg:grid-cols-4 gap-6"> | |
| {/* Main chat area */} | |
| <div className="lg:col-span-3 glass rounded-xl flex flex-col h-[600px]"> | |
| {/* Header */} | |
| <div className="p-4 border-b border-dark-700"> | |
| <h3 className="text-lg font-semibold flex items-center gap-2"> | |
| <Bot className="w-5 h-5 text-primary-500" /> | |
| AI Fraud Intelligence Assistant | |
| </h3> | |
| <p className="text-sm text-dark-400 mt-1"> | |
| Ask questions about fraud patterns, model optimization, or performance metrics | |
| </p> | |
| </div> | |
| {/* Messages */} | |
| <div className="flex-1 overflow-y-auto p-4 space-y-4"> | |
| {messages.length === 0 && ( | |
| <div className="flex flex-col items-center justify-center h-full text-center"> | |
| <Sparkles className="w-12 h-12 text-primary-500 mb-4" /> | |
| <h4 className="text-lg font-semibold mb-2">Welcome to AI Assistant</h4> | |
| <p className="text-dark-400 mb-6 max-w-md"> | |
| I can help you understand fraud patterns, optimize your model, | |
| and explain the quantum-classical hybrid architecture. | |
| </p> | |
| {/* Suggested questions */} | |
| <div className="flex flex-wrap gap-2 justify-center max-w-lg"> | |
| {suggestedQuestions.slice(0, 3).map((q, i) => ( | |
| <button | |
| key={i} | |
| onClick={() => sendMessage(q)} | |
| className="text-sm bg-dark-700 hover:bg-dark-600 border border-dark-600 rounded-lg px-3 py-2 transition-colors" | |
| > | |
| {q} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {messages.map((message, index) => ( | |
| <div | |
| key={index} | |
| className={`flex gap-3 ${message.role === 'user' ? 'justify-end' : 'justify-start'}`} | |
| > | |
| {message.role === 'assistant' && ( | |
| <div className="w-8 h-8 rounded-full bg-primary-500/20 flex items-center justify-center flex-shrink-0"> | |
| <Bot className="w-4 h-4 text-primary-500" /> | |
| </div> | |
| )} | |
| <div | |
| className={` | |
| max-w-[80%] rounded-2xl px-4 py-3 | |
| ${message.role === 'user' | |
| ? 'bg-primary-600 text-white rounded-br-sm' | |
| : 'bg-dark-700 text-white rounded-bl-sm' | |
| } | |
| `} | |
| > | |
| <p className="whitespace-pre-wrap text-sm leading-relaxed"> | |
| {message.content} | |
| </p> | |
| <p className="text-xs opacity-50 mt-1"> | |
| {message.timestamp.toLocaleTimeString()} | |
| </p> | |
| </div> | |
| {message.role === 'user' && ( | |
| <div className="w-8 h-8 rounded-full bg-blue-500/20 flex items-center justify-center flex-shrink-0"> | |
| <User className="w-4 h-4 text-blue-400" /> | |
| </div> | |
| )} | |
| </div> | |
| ))} | |
| {isLoading && ( | |
| <div className="flex gap-3"> | |
| <div className="w-8 h-8 rounded-full bg-primary-500/20 flex items-center justify-center flex-shrink-0"> | |
| <Bot className="w-4 h-4 text-primary-500" /> | |
| </div> | |
| <div className="bg-dark-700 rounded-2xl rounded-bl-sm px-4 py-3"> | |
| <div className="flex items-center gap-2"> | |
| <Loader2 className="w-4 h-4 animate-spin text-primary-500" /> | |
| <span className="text-sm text-dark-300">Analyzing...</span> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| {/* Input */} | |
| <div className="p-4 border-t border-dark-700"> | |
| <div className="flex gap-2"> | |
| <input | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={handleKeyDown} | |
| placeholder="Ask about fraud patterns, model optimization..." | |
| className="flex-1 bg-dark-700 border border-dark-600 rounded-lg px-4 py-3 text-sm focus:border-primary-500 focus:outline-none" | |
| disabled={isLoading} | |
| /> | |
| <button | |
| onClick={() => sendMessage()} | |
| disabled={!input.trim() || isLoading} | |
| className="btn-primary px-4 disabled:opacity-50" | |
| > | |
| <Send className="w-4 h-4" /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Sidebar */} | |
| <div className="space-y-4"> | |
| {/* Current metrics */} | |
| <div className="glass rounded-xl p-4"> | |
| <h4 className="text-sm font-semibold text-dark-300 mb-3">📊 Current Metrics</h4> | |
| {metrics ? ( | |
| <div className="space-y-2 text-sm"> | |
| <div className="flex justify-between"> | |
| <span className="text-dark-400">Accuracy</span> | |
| <span className="font-mono">{(metrics.accuracy * 100).toFixed(1)}%</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-dark-400">Precision</span> | |
| <span className="font-mono">{(metrics.precision * 100).toFixed(1)}%</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-dark-400">Recall</span> | |
| <span className="font-mono">{(metrics.recall * 100).toFixed(1)}%</span> | |
| </div> | |
| <div className="flex justify-between"> | |
| <span className="text-dark-400">F1 Score</span> | |
| <span className="font-mono">{metrics.f1.toFixed(3)}</span> | |
| </div> | |
| </div> | |
| ) : ( | |
| <p className="text-dark-500 text-sm">No data yet</p> | |
| )} | |
| </div> | |
| {/* Suggested questions */} | |
| <div className="glass rounded-xl p-4"> | |
| <h4 className="text-sm font-semibold text-dark-300 mb-3">💡 Try asking</h4> | |
| <div className="space-y-2"> | |
| {suggestedQuestions.map((q, i) => ( | |
| <button | |
| key={i} | |
| onClick={() => sendMessage(q)} | |
| disabled={isLoading} | |
| className="w-full text-left text-xs bg-dark-700 hover:bg-dark-600 rounded-lg px-3 py-2 transition-colors disabled:opacity-50" | |
| > | |
| {q} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Model info */} | |
| <div className="glass rounded-xl p-4"> | |
| <h4 className="text-sm font-semibold text-dark-300 mb-3">🧠 Model Architecture</h4> | |
| <div className="space-y-2 text-xs"> | |
| <div className="flex items-center gap-2"> | |
| <div className="w-2 h-2 bg-blue-500 rounded-full" /> | |
| <span className="text-dark-400">Classical (80%)</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <div className="w-2 h-2 bg-purple-500 rounded-full" /> | |
| <span className="text-dark-400">VQC (8%)</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <div className="w-2 h-2 bg-pink-500 rounded-full" /> | |
| <span className="text-dark-400">QAOA (6%)</span> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| <div className="w-2 h-2 bg-cyan-500 rounded-full" /> | |
| <span className="text-dark-400">QNN (6%)</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| } | |