|
|
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> |
|
|
) |
|
|
} |
|
|
|