Spaces:
Runtime error
Runtime error
| import { useState, useRef, useEffect } from 'react'; | |
| import { Send, Bot, User, Music, Sparkles } from 'lucide-react'; | |
| export default function ChatInterface({ | |
| messages, | |
| onSendMessage, | |
| isLoading, | |
| spotifyData | |
| }) { | |
| const [input, setInput] = useState(''); | |
| const messagesEndRef = useRef(null); | |
| const inputRef = useRef(null); | |
| const scrollToBottom = () => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); | |
| }; | |
| useEffect(() => { | |
| scrollToBottom(); | |
| }, [messages]); | |
| const handleSubmit = (e) => { | |
| e.preventDefault(); | |
| if (!input.trim() || isLoading) return; | |
| onSendMessage(input); | |
| setInput(''); | |
| }; | |
| return ( | |
| <div className="flex flex-col h-full bg-[#121212] rounded-xl border border-white/10 overflow-hidden"> | |
| {/* Chat Header */} | |
| <div className="p-4 bg-gradient-to-r from-[#1a1a1a] to-[#121212] border-b border-white/10 flex items-center justify-between"> | |
| <div className="flex items-center gap-2"> | |
| <div className="p-1.5 bg-purple-500/20 rounded-lg text-purple-400"> | |
| <Sparkles className="w-5 h-5" /> | |
| </div> | |
| <div> | |
| <h2 className="font-bold text-sm">AI Music Assistant</h2> | |
| <p className="text-xs text-gray-400"> | |
| {spotifyData ? 'Connected to Spotify' : 'Waiting for Spotify data...'} | |
| </p> | |
| </div> | |
| </div> | |
| {isLoading && ( | |
| <div className="flex items-center gap-2 text-xs text-spotify-green animate-pulse"> | |
| <div className="w-2 h-2 bg-spotify-green rounded-full"></div> | |
| Thinking... | |
| </div> | |
| )} | |
| </div> | |
| {/* Messages Area */} | |
| <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 space-y-4 opacity-50"> | |
| <Music className="w-12 h-12 text-gray-600" /> | |
| <div> | |
| <p className="text-lg font-medium">Start the conversation</p> | |
| <p className="text-sm text-gray-400">Ask for recommendations based on your taste</p> | |
| </div> | |
| </div> | |
| ) : ( | |
| messages.map((msg, idx) => ( | |
| <div | |
| key={idx} | |
| className={`flex gap-3 ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`} | |
| > | |
| {msg.role === 'assistant' && ( | |
| <div className="flex-shrink-0 mt-1"> | |
| <div className="w-8 h-8 rounded-full bg-gradient-to-br from-purple-500 to-indigo-600 flex items-center justify-center"> | |
| <Bot className="w-4 h-4 text-white" /> | |
| </div> | |
| </div> | |
| )} | |
| <div | |
| className={`max-w-[85%] rounded-2xl px-4 py-3 text-sm leading-relaxed ${ | |
| msg.role === 'user' | |
| ? 'bg-spotify-green text-black font-medium rounded-tr-sm' | |
| : 'bg-[#282828] text-gray-100 rounded-tl-sm border border-white/5' | |
| }`} | |
| > | |
| {msg.role === 'assistant' ? ( | |
| <div className="prose prose-invert prose-sm max-w-none"> | |
| {/* Simple markdown-like rendering for newlines */} | |
| {msg.content.split('\n').map((line, i) => ( | |
| <p key={i} className="mb-2 last:mb-0">{line}</p> | |
| ))} | |
| </div> | |
| ) : ( | |
| msg.content | |
| )} | |
| </div> | |
| {msg.role === 'user' && ( | |
| <div className="flex-shrink-0 mt-1"> | |
| <div className="w-8 h-8 rounded-full bg-gray-600 flex items-center justify-center"> | |
| <User className="w-4 h-4 text-white" /> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| )) | |
| )} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| {/* Input Area */} | |
| <div className="p-4 bg-[#121212] border-t border-white/10"> | |
| <form onSubmit={handleSubmit} className="relative"> | |
| <input | |
| ref={inputRef} | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| placeholder="Ask for music recommendations..." | |
| disabled={isLoading} | |
| className="w-full bg-[#282828] text-white pl-4 pr-12 py-3 rounded-full border border-transparent focus:border-spotify-green focus:bg-[#3e3e3e] focus:outline-none transition-all disabled:opacity-50 disabled:cursor-not-allowed" | |
| /> | |
| <button | |
| type="submit" | |
| disabled={!input.trim() || isLoading} | |
| className="absolute right-2 top-1/2 -translate-y-1/2 p-2 bg-white text-black rounded-full hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" | |
| > | |
| <Send className="w-4 h-4" /> | |
| </button> | |
| </form> | |
| <p className="text-[10px] text-gray-500 mt-2 text-center"> | |
| AI can make mistakes. Verify important info. | |
| </p> | |
| </div> | |
| </div> | |
| ); | |
| } |