Spaces:
Running
Running
| import React, { useState, useRef, useEffect } from 'react'; | |
| import { Send, Circle, Bot, User, Sparkles, Brain } from 'lucide-react'; | |
| import { CodetteResponseCard, CodetteResponse } from './CodetteComponents'; | |
| interface Message { | |
| role: string; | |
| content: string; | |
| timestamp: Date; | |
| metadata?: CodetteResponse; | |
| } | |
| interface ChatInterfaceProps { | |
| messages: Message[]; | |
| sendMessage: (content: string) => void; | |
| isProcessing: boolean; | |
| darkMode: boolean; | |
| } | |
| const ChatInterface: React.FC<ChatInterfaceProps> = ({ | |
| messages, | |
| sendMessage, | |
| isProcessing, | |
| darkMode | |
| }) => { | |
| const [input, setInput] = useState(''); | |
| const [isDreamMode, setIsDreamMode] = useState(false); | |
| const messagesEndRef = useRef<HTMLDivElement>(null); | |
| const inputRef = useRef<HTMLTextAreaElement>(null); | |
| useEffect(() => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); | |
| }, [messages]); | |
| useEffect(() => { | |
| inputRef.current?.focus(); | |
| }, []); | |
| const handleSubmit = (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (input.trim() && !isProcessing) { | |
| const finalInput = isDreamMode ? `dream about ${input.trim()}` : input.trim(); | |
| sendMessage(finalInput); | |
| setInput(''); | |
| } | |
| }; | |
| const handleKeyDown = (e: React.KeyboardEvent) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| handleSubmit(e); | |
| } | |
| }; | |
| const toggleDreamMode = () => { | |
| setIsDreamMode(!isDreamMode); | |
| if (!isDreamMode) { | |
| inputRef.current?.focus(); | |
| } | |
| }; | |
| return ( | |
| <div className={`flex-1 flex flex-col ${darkMode ? 'bg-gray-800' : 'bg-white'} shadow-lg rounded-lg overflow-hidden transition-colors duration-300`}> | |
| <div className={`p-4 border-b ${darkMode ? 'border-gray-700' : 'border-gray-200'}`}> | |
| <h2 className="text-lg font-semibold flex items-center"> | |
| <Bot className="mr-2" size={18} /> | |
| Conversation with Codette | |
| </h2> | |
| </div> | |
| <div className="flex-1 overflow-y-auto p-4 space-y-4"> | |
| {messages.map((message, index) => ( | |
| <div | |
| key={index} | |
| className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`} | |
| > | |
| {message.role === 'assistant' && message.metadata ? ( | |
| <CodetteResponseCard response={message.metadata} /> | |
| ) : ( | |
| <div | |
| className={`max-w-[80%] rounded-lg p-3 ${ | |
| message.role === 'user' | |
| ? darkMode | |
| ? 'bg-blue-600 text-white' | |
| : 'bg-blue-100 text-blue-900' | |
| : message.role === 'system' | |
| ? darkMode | |
| ? 'bg-red-900 text-white' | |
| : 'bg-red-100 text-red-900' | |
| : darkMode | |
| ? 'bg-gray-700 text-white' | |
| : 'bg-gray-100 text-gray-900' | |
| }`} | |
| > | |
| <div className="flex items-start mb-1"> | |
| {message.role === 'user' ? ( | |
| <User className="mr-2 mt-1" size={14} /> | |
| ) : message.role === 'system' ? ( | |
| <Circle className="mr-2 mt-1" size={14} /> | |
| ) : ( | |
| <Bot className="mr-2 mt-1" size={14} /> | |
| )} | |
| <div className="text-sm font-semibold"> | |
| {message.role === 'user' ? 'You' : message.role === 'system' ? 'System' : 'Codette'} | |
| </div> | |
| </div> | |
| <div className="whitespace-pre-wrap"> | |
| {message.content} | |
| </div> | |
| <div className="text-xs opacity-70 mt-1 text-right"> | |
| {message.timestamp.toLocaleTimeString()} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ))} | |
| {isProcessing && ( | |
| <div className="flex justify-start"> | |
| <div className={`rounded-lg p-3 ${ | |
| darkMode ? 'bg-gray-700 text-white' : 'bg-gray-100 text-gray-900' | |
| }`}> | |
| <div className="flex items-center"> | |
| <Bot className="mr-2" size={14} /> | |
| <div className="text-sm font-semibold">Codette</div> | |
| </div> | |
| <div className="flex items-center mt-2"> | |
| <div className="flex space-x-1"> | |
| <div className="typing-dot h-2 w-2 bg-blue-500 rounded-full animate-pulse" style={{ animationDelay: '0ms' }}></div> | |
| <div className="typing-dot h-2 w-2 bg-blue-500 rounded-full animate-pulse" style={{ animationDelay: '300ms' }}></div> | |
| <div className="typing-dot h-2 w-2 bg-blue-500 rounded-full animate-pulse" style={{ animationDelay: '600ms' }}></div> | |
| </div> | |
| <div className="ml-3 text-sm italic opacity-70"> | |
| {isDreamMode ? 'Weaving dreams through quantum threads...' : 'Processing through recursive thought loops...'} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| <form | |
| onSubmit={handleSubmit} | |
| className={`p-4 border-t ${darkMode ? 'border-gray-700' : 'border-gray-200'}`} | |
| > | |
| <div className="flex items-center mb-2"> | |
| <button | |
| type="button" | |
| onClick={toggleDreamMode} | |
| className={`flex items-center px-3 py-1 rounded-full text-sm transition-colors ${ | |
| isDreamMode | |
| ? darkMode | |
| ? 'bg-purple-600 text-white' | |
| : 'bg-purple-100 text-purple-900' | |
| : darkMode | |
| ? 'bg-gray-700 text-gray-300 hover:bg-gray-600' | |
| : 'bg-gray-100 text-gray-700 hover:bg-gray-200' | |
| }`} | |
| > | |
| {isDreamMode ? ( | |
| <> | |
| <Sparkles size={14} className="mr-1" /> | |
| Dreamweaver Active | |
| </> | |
| ) : ( | |
| <> | |
| <Brain size={14} className="mr-1" /> | |
| Enable Dreamweaver | |
| </> | |
| )} | |
| </button> | |
| </div> | |
| <div className="flex"> | |
| <textarea | |
| ref={inputRef} | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={handleKeyDown} | |
| placeholder={isDreamMode ? "Enter a concept for Codette to dream about..." : "Ask Codette anything..."} | |
| className={`flex-1 resize-none border rounded-lg p-2 focus:outline-none focus:ring-2 ${ | |
| isDreamMode | |
| ? 'focus:ring-purple-500' | |
| : 'focus:ring-blue-500' | |
| } ${ | |
| darkMode | |
| ? 'bg-gray-700 border-gray-600 text-white placeholder-gray-400' | |
| : 'bg-white border-gray-300 text-gray-900 placeholder-gray-500' | |
| }`} | |
| rows={2} | |
| disabled={isProcessing} | |
| /> | |
| <button | |
| type="submit" | |
| disabled={!input.trim() || isProcessing} | |
| className={`ml-2 p-2 rounded-lg transition-colors ${ | |
| !input.trim() || isProcessing | |
| ? darkMode | |
| ? 'bg-gray-700 text-gray-500' | |
| : 'bg-gray-200 text-gray-400' | |
| : isDreamMode | |
| ? darkMode | |
| ? 'bg-purple-600 text-white hover:bg-purple-700' | |
| : 'bg-purple-500 text-white hover:bg-purple-600' | |
| : darkMode | |
| ? 'bg-blue-600 text-white hover:bg-blue-700' | |
| : 'bg-blue-500 text-white hover:bg-blue-600' | |
| }`} | |
| > | |
| <Send size={20} /> | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| ); | |
| }; | |
| export default ChatInterface; |