const { useState, useEffect, useRef } = React; // Dynamically detect server origin to support both localhost and Cloud Run production deployments const API_BASE = "/api"; function App() { const [token, setToken] = useState(localStorage.getItem("token")); const [user, setUser] = useState(null); useEffect(() => { if (token) { axios.get(`${API_BASE}/users/me`, { headers: { Authorization: `Bearer ${token}` } }) .then(res => setUser(res.data)) .catch(() => { setToken(null); localStorage.removeItem("token"); }); } }, [token]); const handleLogin = (newToken) => { setToken(newToken); localStorage.setItem("token", newToken); }; const handleLogout = () => { setToken(null); setUser(null); localStorage.removeItem("token"); }; return (
{isLogin ? "¿No tienes cuenta? " : "¿Ya tienes cuenta? "}
{formattedParts}
; }); }; function ChatInterface({ token }) { const [messages, setMessages] = useState([ { role: "assistant", content: "¡Hola! Soy el asistente de la biblioteca ReLAC. ¿En qué puedo ayudarte hoy? Por ejemplo, puedes pedirme: 'Encuentra evaluaciones sobre género en Ecuador'." } ]); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [loadingTime, setLoadingTime] = useState(0); const messagesEndRef = useRef(null); const inputRef = useRef(null); const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; useEffect(() => { const lastMessage = messages[messages.length - 1]; if (lastMessage && lastMessage.role === "user") { scrollToBottom(); } }, [messages]); useEffect(() => { if (loading) { scrollToBottom(); } }, [loading]); useEffect(() => { if (inputRef.current) { inputRef.current.focus(); } }, []); useEffect(() => { let interval; if (loading) { setLoadingTime(0); interval = setInterval(() => { setLoadingTime((prev) => prev + 1); }, 1000); } else { setLoadingTime(0); } return () => clearInterval(interval); }, [loading]); const handleSend = async (e) => { e.preventDefault(); if (!input.trim() || loading) return; const userMsg = input.trim(); setInput(""); setMessages(prev => [...prev, { role: "user", content: userMsg }]); setLoading(true); try { const res = await axios.post(`${API_BASE}/chat`, { prompt: userMsg }, { headers: { Authorization: `Bearer ${token}` } } ); const botResp = res.data; setMessages(prev => [...prev, { role: "assistant", content: botResp.answer, citations: botResp.citations, prompt: userMsg }]); } catch (err) { let errorMsg = "Lo siento, ocurrió un error."; if (err.response?.status === 429) { errorMsg = "Has alcanzado el límite de 20 consultas en 24 horas."; } else if (err.response?.status === 503) { errorMsg = "El sistema está ocupado. Por favor intenta de nuevo en unos minutos."; } setMessages(prev => [...prev, { role: "assistant", content: errorMsg, isError: true }]); } finally { setLoading(false); // Re-focus the input after submission completes setTimeout(() => { inputRef.current?.focus(); }, 50); } }; const handleFeedback = async (msgIndex, isPositive) => { const msg = messages[msgIndex]; if (msg.feedbackSubmitted) return; try { await axios.post(`${API_BASE}/feedback`, { query_prompt: msg.prompt || "Desconocido", bot_response: msg.content, is_positive: isPositive }, { headers: { Authorization: `Bearer ${token}` } }); setMessages(prev => prev.map((m, i) => i === msgIndex ? { ...m, feedbackSubmitted: true } : m)); } catch (err) { console.error("Feedback error", err); } }; return (Respuestas basadas en el catálogo de 1000+ recursos
Fuentes y Documentos Citados:
{cit.title}
{cit.author}
ReLAC AI MVP • Límite de 20 consultas por día.