Spaces:
Build error
Build error
| 'use client'; | |
| import { useState, useRef, useEffect } from 'react'; | |
| import { Send, Bot, User, Sparkles, Loader2, X, Maximize2, Minimize2 } from 'lucide-react'; | |
| interface Message { | |
| id: string; | |
| role: 'user' | 'assistant'; | |
| content: string; | |
| timestamp: Date; | |
| } | |
| interface AIChatPanelProps { | |
| onClose?: () => void; | |
| isExpanded?: boolean; | |
| onToggleExpand?: () => void; | |
| } | |
| export default function AIChatPanel({ onClose, isExpanded, onToggleExpand }: AIChatPanelProps) { | |
| const [messages, setMessages] = useState<Message[]>([ | |
| { | |
| id: '1', | |
| role: 'assistant', | |
| content: "Hello! I'm the QuantumShield AI Assistant. I can help you analyze transactions, understand fraud patterns, and explain how our quantum-enhanced detection works. What would you like to know?", | |
| timestamp: new Date() | |
| } | |
| ]); | |
| 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 () => { | |
| if (!input.trim() || isLoading) return; | |
| const userMessage: Message = { | |
| id: Date.now().toString(), | |
| role: 'user', | |
| content: input, | |
| timestamp: new Date() | |
| }; | |
| setMessages(prev => [...prev, userMessage]); | |
| setInput(''); | |
| setIsLoading(true); | |
| try { | |
| const response = await fetch('http://localhost:8000/api/chat', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| message: input, | |
| conversation_history: messages.map(m => ({ | |
| role: m.role, | |
| content: m.content | |
| })) | |
| }) | |
| }); | |
| if (!response.ok) throw new Error('Chat request failed'); | |
| const data = await response.json(); | |
| const assistantMessage: Message = { | |
| id: (Date.now() + 1).toString(), | |
| role: 'assistant', | |
| content: data.response, | |
| timestamp: new Date() | |
| }; | |
| setMessages(prev => [...prev, assistantMessage]); | |
| } catch (error) { | |
| const errorMessage: Message = { | |
| id: (Date.now() + 1).toString(), | |
| role: 'assistant', | |
| content: "I'm having trouble connecting to the server. Please make sure the backend is running on port 8000.", | |
| timestamp: new Date() | |
| }; | |
| setMessages(prev => [...prev, errorMessage]); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| const handleKeyPress = (e: React.KeyboardEvent) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| }; | |
| const suggestedQuestions = [ | |
| "How does quantum detection work?", | |
| "What are VQC, QAOA, and QNN?", | |
| "Explain the fraud scoring system" | |
| ]; | |
| return ( | |
| <div className="card-dark rounded-3xl p-6 card-hover h-full flex flex-col"> | |
| {/* Header */} | |
| <div className="flex items-center justify-between mb-4"> | |
| <div className="flex items-center gap-3"> | |
| <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center"> | |
| <Sparkles className="w-5 h-5 text-white" /> | |
| </div> | |
| <div> | |
| <h3 className="text-xl font-bold text-white">AI ASSISTANT</h3> | |
| <p className="text-xs text-white/60">Quantum Analysis Expert</p> | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-2"> | |
| {onToggleExpand && ( | |
| <button | |
| onClick={onToggleExpand} | |
| className="p-2 text-white/60 hover:text-white hover:bg-white/10 rounded-lg transition-colors" | |
| > | |
| {isExpanded ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />} | |
| </button> | |
| )} | |
| {onClose && ( | |
| <button | |
| onClick={onClose} | |
| className="p-2 text-white/60 hover:text-white hover:bg-white/10 rounded-lg transition-colors" | |
| > | |
| <X className="w-4 h-4" /> | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| {/* Messages */} | |
| <div className="flex-1 overflow-y-auto space-y-4 mb-4 min-h-[200px] max-h-[400px] scrollbar-thin pr-2"> | |
| {messages.map((message) => ( | |
| <div | |
| key={message.id} | |
| className={`flex gap-3 ${message.role === 'user' ? 'flex-row-reverse' : ''}`} | |
| > | |
| <div className={`w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 ${ | |
| message.role === 'user' | |
| ? 'bg-blue-500' | |
| : 'bg-gradient-to-br from-purple-500 to-pink-500' | |
| }`}> | |
| {message.role === 'user' ? ( | |
| <User className="w-4 h-4 text-white" /> | |
| ) : ( | |
| <Bot className="w-4 h-4 text-white" /> | |
| )} | |
| </div> | |
| <div className={`max-w-[80%] rounded-2xl px-4 py-3 ${ | |
| message.role === 'user' | |
| ? 'bg-blue-500 text-white' | |
| : 'bg-white/10 text-white' | |
| }`}> | |
| <p className="text-sm whitespace-pre-wrap">{message.content}</p> | |
| <p className={`text-xs mt-1 ${message.role === 'user' ? 'text-blue-200' : 'text-white/40'}`}> | |
| {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} | |
| </p> | |
| </div> | |
| </div> | |
| ))} | |
| {isLoading && ( | |
| <div className="flex gap-3"> | |
| <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center"> | |
| <Bot className="w-4 h-4 text-white" /> | |
| </div> | |
| <div className="bg-white/10 rounded-2xl px-4 py-3"> | |
| <div className="flex items-center gap-2"> | |
| <Loader2 className="w-4 h-4 text-white animate-spin" /> | |
| <span className="text-sm text-white/60">Analyzing...</span> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| {/* Suggested Questions */} | |
| {messages.length <= 2 && ( | |
| <div className="flex flex-wrap gap-2 mb-4"> | |
| {suggestedQuestions.map((question, index) => ( | |
| <button | |
| key={index} | |
| onClick={() => setInput(question)} | |
| className="text-xs px-3 py-1.5 bg-white/10 hover:bg-white/20 text-white/80 rounded-full transition-colors" | |
| > | |
| {question} | |
| </button> | |
| ))} | |
| </div> | |
| )} | |
| {/* Input */} | |
| <div className="flex gap-2"> | |
| <input | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyPress={handleKeyPress} | |
| placeholder="Ask about fraud detection..." | |
| className="flex-1 bg-white/10 border-0 rounded-xl px-4 py-3 text-white placeholder-white/40 focus:ring-2 focus:ring-blue-500 focus:outline-none" | |
| disabled={isLoading} | |
| /> | |
| <button | |
| onClick={sendMessage} | |
| disabled={!input.trim() || isLoading} | |
| className="px-4 py-3 bg-gradient-to-r from-blue-500 to-purple-600 rounded-xl text-white font-medium hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed" | |
| > | |
| <Send className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| } | |