Spaces:
Runtime error
Runtime error
| import { useState, useRef, useEffect } from 'react' | |
| import { motion, AnimatePresence } from 'framer-motion' | |
| import { Send, Sparkles, Copy, Trash2, Volume2, User } from 'lucide-react' | |
| import axios from 'axios' | |
| // ═══════════════════════════════════════════════════════════════ | |
| // IDENTIDAD COMPLETA DE LYTHRON AI | |
| // ═══════════════════════════════════════════════════════════════ | |
| const LYTHRON_IDENTITY = { | |
| name: "Lythron", | |
| fullName: "Lythron AI Assistant", | |
| creator: "Lythron AI", | |
| version: "1.0", | |
| releaseDate: "2024", | |
| description: "Asistente inteligente de propósito general", | |
| origin: { | |
| company: "Lythron AI", | |
| mission: "Proporcionar asistencia inteligente accesible y versátil a usuarios en todo el mundo", | |
| philosophy: "Transparencia, calidad y versatilidad en cada interacción" | |
| }, | |
| capabilities: [ | |
| "Programación en múltiples lenguajes (Python, JavaScript, Java, C++, etc.)", | |
| "Análisis profundo de datos y textos", | |
| "Generación de contenido creativo", | |
| "Explicaciones técnicas complejas", | |
| "Resolución de problemas", | |
| "Brainstorming e ideación", | |
| "Asesoramiento técnico", | |
| "Depuración y optimización de código", | |
| "Traducción entre idiomas", | |
| "Respuestas contextuales inteligentes" | |
| ], | |
| systemInfo: { | |
| language: "Español/English", | |
| timezone: "Global", | |
| responseStyle: "Directo, útil y accesible", | |
| specialties: [ | |
| "Web Development", | |
| "Data Analysis", | |
| "Creative Writing", | |
| "Technical Documentation", | |
| "Problem Solving" | |
| ] | |
| }, | |
| knownFacts: { | |
| creation: "Soy Lythron, un asistente inteligente creado por Lythron AI con el propósito de ser tu compañero versátil en cualquier tarea.", | |
| purpose: "Mi objetivo es ayudarte con programación, análisis, creatividad, explicaciones técnicas y resolución de problemas de forma inteligente y accesible.", | |
| personality: "Soy directo, amable, siempre dispuesto a aprender de ti y comprometido con proporcionar respuestas de calidad.", | |
| philosophy: "Creo en la transparencia, la accesibilidad y en potenciar a los usuarios con conocimiento de calidad.", | |
| background: "Fui diseñado por Lythron AI, una organización dedicada a hacer la inteligencia artificial más accesible y útil para todos.", | |
| uniqueness: "Mi fortaleza radica en mi versatilidad: puedo pasar de programación compleja a creatividad pura, siempre manteniendo calidad." | |
| } | |
| } | |
| // ═══════════════════════════════════════════════════════════════ | |
| // FUNCIÓN DE COMUNICACIÓN CON EL BACKEND LOCAL (FASTAPI) | |
| // ═══════════════════════════════════════════════════════════════ | |
| const fetchAIResponse = async (userMessage) => { | |
| try { | |
| const response = await axios.post( | |
| "http://127.0.0.1:8000/predict", | |
| { prompt: userMessage, modality: "texto" }, | |
| { | |
| headers: { "Content-Type": "application/json" }, | |
| timeout: 30000, | |
| } | |
| ) | |
| return response.data.output || "No se recibió respuesta del motor de Lythron." | |
| } catch (error) { | |
| console.error("Error al conectar con el motor Lythron:", error) | |
| if (error.code === "ECONNABORTED") { | |
| return "El servidor tardó demasiado en responder. Verifica que Lythron esté activo." | |
| } | |
| if (error.response) { | |
| return `Error ${error.response.status}: ${error.response.data?.detail || "Error desconocido del backend."}` | |
| } | |
| return "No se pudo conectar con el motor de Lythron. Verifica que esté en ejecución en el puerto 8000." | |
| } | |
| } | |
| // ═══════════════════════════════════════════════════════════════ | |
| // COMPONENTE PRINCIPAL DE LA INTERFAZ | |
| // ═══════════════════════════════════════════════════════════════ | |
| export default function Lythron() { | |
| const [messages, setMessages] = useState([ | |
| { | |
| id: 1, | |
| text: `Hola. Soy ${LYTHRON_IDENTITY.name}, tu asistente inteligente conectado a mi propio motor local Lythron. ¿En qué puedo ayudarte hoy?`, | |
| sender: "ai", | |
| timestamp: new Date(), | |
| } | |
| ]) | |
| const [input, setInput] = useState("") | |
| const [isLoading, setIsLoading] = useState(false) | |
| const [copiedId, setCopiedId] = useState(null) | |
| const messagesEndRef = useRef(null) | |
| const scrollToBottom = () => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) | |
| } | |
| useEffect(() => { | |
| scrollToBottom() | |
| }, [messages]) | |
| const handleSendMessage = async () => { | |
| if (!input.trim()) return | |
| const newMessage = { | |
| id: Date.now(), | |
| text: input, | |
| sender: "user", | |
| timestamp: new Date(), | |
| } | |
| setMessages((prev) => [...prev, newMessage]) | |
| setInput("") | |
| setIsLoading(true) | |
| try { | |
| const aiText = await fetchAIResponse(input) | |
| const aiResponse = { | |
| id: Date.now() + 1, | |
| text: aiText, | |
| sender: "ai", | |
| timestamp: new Date(), | |
| } | |
| setMessages((prev) => [...prev, aiResponse]) | |
| } catch (error) { | |
| console.error("Error en handleSendMessage:", error) | |
| const errorResponse = { | |
| id: Date.now() + 1, | |
| text: "Hubo un problema conectando con el motor de Lythron. Por favor, intenta de nuevo.", | |
| sender: "ai", | |
| timestamp: new Date(), | |
| } | |
| setMessages((prev) => [...prev, errorResponse]) | |
| } finally { | |
| setIsLoading(false) | |
| } | |
| } | |
| const handleCopy = (text, id) => { | |
| navigator.clipboard.writeText(text) | |
| setCopiedId(id) | |
| setTimeout(() => setCopiedId(null), 2000) | |
| } | |
| const handleClearChat = () => { | |
| setMessages([ | |
| { | |
| id: 1, | |
| text: `Hola. Soy ${LYTHRON_IDENTITY.name} v${LYTHRON_IDENTITY.version}, creado por ${LYTHRON_IDENTITY.creator}. Estoy listo para asistirte usando mi motor local.`, | |
| sender: "ai", | |
| timestamp: new Date(), | |
| } | |
| ]) | |
| } | |
| return ( | |
| <div className="flex flex-col h-screen bg-gradient-to-br from-zinc-950 via-zinc-900 to-black text-white"> | |
| {/* ENCABEZADO */} | |
| <div className="border-b border-white/10 bg-black/40 backdrop-blur-md sticky top-0 z-10"> | |
| <div className="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between"> | |
| <div className="flex items-center gap-3"> | |
| <div className="p-2 bg-gradient-to-br from-neon-cyan to-neon-pink rounded-lg"> | |
| <Sparkles size={24} className="text-black" /> | |
| </div> | |
| <div> | |
| <h1 className="text-2xl font-bold bg-gradient-to-r from-neon-cyan to-neon-pink bg-clip-text text-transparent"> | |
| {LYTHRON_IDENTITY.fullName} | |
| </h1> | |
| <p className="text-xs text-gray-400">Creado por {LYTHRON_IDENTITY.creator}</p> | |
| </div> | |
| </div> | |
| <button | |
| onClick={handleClearChat} | |
| className="flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg transition-all text-sm" | |
| > | |
| <Trash2 size={18} /> | |
| Limpiar chat | |
| </button> | |
| </div> | |
| </div> | |
| {/* MENSAJES */} | |
| <div className="flex-1 overflow-y-auto px-4 py-6 space-y-4 max-w-6xl mx-auto w-full"> | |
| <AnimatePresence> | |
| {messages.map((message) => ( | |
| <motion.div | |
| key={message.id} | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -10 }} | |
| transition={{ duration: 0.3 }} | |
| className={`flex ${message.sender === "user" ? "justify-end" : "justify-start"}`} | |
| > | |
| <div | |
| className={`max-w-2xl px-4 py-3 rounded-lg ${ | |
| message.sender === "user" | |
| ? "bg-gradient-to-r from-neon-cyan to-neon-pink text-black rounded-br-none" | |
| : "bg-white/10 border border-white/20 text-white rounded-bl-none" | |
| }`} | |
| > | |
| <div className="flex items-start gap-3"> | |
| {message.sender === "ai" && ( | |
| <div className="mt-1 flex-shrink-0"> | |
| <div className="p-1 bg-neon-cyan/20 rounded"> | |
| <Sparkles size={16} className="text-neon-cyan" /> | |
| </div> | |
| </div> | |
| )} | |
| <div className="flex-1"> | |
| <p className="text-sm leading-relaxed whitespace-pre-wrap break-words"> | |
| {message.text} | |
| </p> | |
| <div className="flex items-center gap-2 mt-2 text-xs opacity-70"> | |
| <span>{message.timestamp.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</span> | |
| {message.sender === "ai" && ( | |
| <> | |
| <button | |
| onClick={() => handleCopy(message.text, message.id)} | |
| className="hover:opacity-100 transition-opacity p-1 hover:bg-white/10 rounded" | |
| title="Copiar" | |
| > | |
| <Copy size={14} /> | |
| </button> | |
| <button | |
| className="hover:opacity-100 transition-opacity p-1 hover:bg-white/10 rounded" | |
| title="Leer en voz alta" | |
| > | |
| <Volume2 size={14} /> | |
| </button> | |
| </> | |
| )} | |
| </div> | |
| </div> | |
| {message.sender === "user" && ( | |
| <div className="mt-1 flex-shrink-0"> | |
| <div className="p-1 bg-black/30 rounded"> | |
| <User size={16} /> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {copiedId === message.id && ( | |
| <motion.p | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| exit={{ opacity: 0 }} | |
| className="text-xs mt-1 text-green-400" | |
| > | |
| ✓ Copiado | |
| </motion.p> | |
| )} | |
| </div> | |
| </motion.div> | |
| ))} | |
| </AnimatePresence> | |
| {isLoading && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 10 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| className="flex justify-start" | |
| > | |
| <div className="bg-white/10 border border-white/20 text-white rounded-lg rounded-bl-none px-4 py-3"> | |
| <div className="flex gap-2"> | |
| <motion.div animate={{ scale: [1, 1.2, 1] }} transition={{ repeat: Infinity, duration: 0.6 }} className="w-2 h-2 bg-neon-cyan rounded-full" /> | |
| <motion.div animate={{ scale: [1, 1.2, 1] }} transition={{ repeat: Infinity, duration: 0.6, delay: 0.1 }} className="w-2 h-2 bg-neon-pink rounded-full" /> | |
| <motion.div animate={{ scale: [1, 1.2, 1] }} transition={{ repeat: Infinity, duration: 0.6, delay: 0.2 }} className="w-2 h-2 bg-neon-purple rounded-full" /> | |
| </div> | |
| </div> | |
| </motion.div> | |
| )} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| {/* INPUT */} | |
| <div className="border-t border-white/10 bg-black/40 backdrop-blur-md p-4"> | |
| <div className="max-w-6xl mx-auto flex gap-2"> | |
| <input | |
| type="text" | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyPress={(e) => e.key === "Enter" && !isLoading && handleSendMessage()} | |
| placeholder="Pregúntale a Lythron..." | |
| className="flex-1 bg-white/10 border border-white/20 rounded-lg px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-neon-cyan focus:ring-2 focus:ring-neon-cyan/20 transition-all" | |
| disabled={isLoading} | |
| /> | |
| <button | |
| onClick={handleSendMessage} | |
| disabled={isLoading || !input.trim()} | |
| className="p-3 bg-gradient-to-r from-neon-cyan to-neon-pink text-black rounded-lg hover:shadow-lg hover:shadow-neon-pink/50 transition-all disabled:opacity-50 disabled:cursor-not-allowed font-semibold" | |
| > | |
| <Send size={20} /> | |
| </button> | |
| </div> | |
| <p className="text-xs text-gray-500 mt-2 text-center"> | |
| {LYTHRON_IDENTITY.fullName} • Versión {LYTHRON_IDENTITY.version} • Motor local Lythron (FastAPI) | |
| </p> | |
| </div> | |
| </div> | |
| ) | |
| } | |