| 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' |
|
|
| |
| |
| |
|
|
| 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." |
| } |
| } |
|
|
| |
| const fetchAIResponse = async (userMessage) => { |
| try { |
| const HF_TOKEN = import.meta.env.VITE_HF_TOKEN |
| |
| if (!HF_TOKEN) { |
| return "Error: Token de Hugging Face no configurado. Verifica tu archivo .env con VITE_HF_TOKEN" |
| } |
|
|
| const response = await axios.post( |
| "https://api-inference.huggingface.co/models/meta-llama/Llama-2-7b-chat-hf", |
| { inputs: userMessage }, |
| { |
| headers: { Authorization: `Bearer ${ HF_TOKEN }` }, |
| timeout: 30000 |
| } |
| ) |
|
|
| let text = response.data?.[0]?.generated_text || response.data?.generated_text || "Lo siento, no pude generar respuesta." |
| |
| if (text.includes(userMessage)) { |
| text = text.replace(userMessage, "").trim() |
| } |
| |
| return text || "Lo siento, no pude generar respuesta." |
| } catch (error) { |
| console.error("Error al llamar a Hugging Face:", error) |
| |
| if (error.response?.status === 429) { |
| return "El modelo está sobrecargado. Por favor, intenta en unos segundos." |
| } |
| |
| if (error.response?.status === 503) { |
| return "El modelo se está cargando. Por favor, espera unos momentos e intenta de nuevo." |
| } |
|
|
| return `Error: ${ error.response?.data?.error || error.message || "Problema generando respuesta de IA. Intenta de nuevo." }` |
| } |
| } |
|
|
| export default function Lythron() { |
| const [messages, setMessages] = useState([ |
| { |
| id: 1, |
| text: `Hola. Soy ${ LYTHRON_IDENTITY.name }, tu asistente inteligente creado por ${ LYTHRON_IDENTITY.creator }. Estoy conectado a Hugging Face para darte respuestas reales. 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 la IA. 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 conectado a Hugging Face para darte respuestas reales. En qué puedo ayudarte?`, |
| 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"> |
| <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> |
| |
| <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> |
|
|
| <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} • Creado por {LYTHRON_IDENTITY.creator} • Powered by Hugging Face |
| </p> |
| </div> |
| </div> |
| ) |
| } |
|
|