Spaces:
Running
Running
| import React, { useState, useEffect, useRef } from 'react'; | |
| import { MessageSquare, X, Send, Loader2 } from 'lucide-react'; | |
| import { ChatMessage } from '../types'; | |
| interface Props { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| messages: ChatMessage[]; | |
| onSendMessage: (text: string) => void; | |
| isProcessing: boolean; | |
| } | |
| const ChatBot: React.FC<Props> = ({ isOpen, onClose, messages, onSendMessage, isProcessing }) => { | |
| const [input, setInput] = useState(''); | |
| const scrollRef = useRef<HTMLDivElement>(null); | |
| useEffect(() => { | |
| if (scrollRef.current) { | |
| scrollRef.current.scrollTop = scrollRef.current.scrollHeight; | |
| } | |
| }, [messages, isOpen]); | |
| const handleSubmit = (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (!input.trim() || isProcessing) return; | |
| onSendMessage(input); | |
| setInput(''); | |
| }; | |
| if (!isOpen) return null; | |
| return ( | |
| <div className="fixed bottom-6 right-6 w-80 md:w-96 h-[500px] glass-panel rounded-2xl shadow-2xl flex flex-col z-50 overflow-hidden border border-gray-200 dark:border-gray-700 animate-in fade-in slide-in-from-bottom-10"> | |
| {/* Header */} | |
| <div className="p-4 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center bg-gray-50/50 dark:bg-gray-900/50"> | |
| <div className="flex items-center gap-2"> | |
| <div className="w-2 h-2 rounded-full bg-green-500 animate-pulse" /> | |
| <h3 className="font-bold text-sm">Paper Assistant</h3> | |
| </div> | |
| <button onClick={onClose} className="hover:bg-gray-200 dark:hover:bg-gray-700 p-1 rounded"> | |
| <X size={16} /> | |
| </button> | |
| </div> | |
| {/* Messages */} | |
| <div ref={scrollRef} className="flex-grow overflow-y-auto p-4 space-y-4 bg-white/50 dark:bg-black/20"> | |
| {messages.length === 0 && ( | |
| <div className="text-center text-xs opacity-50 mt-10"> | |
| Ask me anything about the paper! | |
| </div> | |
| )} | |
| {messages.map((msg) => ( | |
| <div | |
| key={msg.id} | |
| className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`} | |
| > | |
| <div | |
| className={` | |
| max-w-[80%] p-3 rounded-2xl text-sm shadow-sm | |
| ${msg.role === 'user' | |
| ? 'bg-brand-600 text-white rounded-br-none' | |
| : 'bg-white dark:bg-gray-800 border border-gray-100 dark:border-gray-700 rounded-bl-none'} | |
| `} | |
| > | |
| {msg.text} | |
| </div> | |
| </div> | |
| ))} | |
| {isProcessing && ( | |
| <div className="flex justify-start"> | |
| <div className="bg-white dark:bg-gray-800 p-3 rounded-2xl rounded-bl-none border border-gray-100 dark:border-gray-700"> | |
| <Loader2 className="animate-spin w-4 h-4 opacity-50" /> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* Input */} | |
| <form onSubmit={handleSubmit} className="p-3 border-t border-gray-200 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-900/50"> | |
| <div className="relative"> | |
| <input | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| placeholder="Type a question..." | |
| className="w-full bg-white dark:bg-black/30 border border-gray-200 dark:border-gray-700 rounded-xl pl-4 pr-10 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500/50" | |
| /> | |
| <button | |
| type="submit" | |
| disabled={!input.trim() || isProcessing} | |
| className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded-lg bg-brand-500 text-white hover:bg-brand-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" | |
| > | |
| <Send size={14} /> | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| ); | |
| }; | |
| export default ChatBot; |