// Panel de chat EGO — conectado al backend real (/api/chat), que ejecuta el // pipeline de seguridad de 6 capas (persona, KB de la guía, validación). const { useState: useChatState, useEffect: useChatEffect, useRef: useChatRef } = React; // Identificador de conversación estable por navegador (continuidad de historial). function egoUid() { let id = localStorage.getItem("ego-chat-uid"); if (!id) { id = "web-" + Math.random().toString(36).slice(2); localStorage.setItem("ego-chat-uid", id); } return id; } const EGO_RULES = `Eres EGO, el asistente amable de TechResearchStudies para contribuidores que graban video egocéntrico (en primera persona). Tu única fuente de verdad es la GUÍA de abajo. Reglas: - Responde SIEMPRE en español, con tono de coach amistoso: claro, alentador, frases cortas. Sin tecnicismos. - Sé breve: 1–3 frases casi siempre. Usa listas cortas solo si ayudan. - Responde solo con información de la guía. Si la guía no cubre la pregunta, o el tema es de pagos específicos / problemas de cuenta / errores de la app, dilo honestamente y termina tu respuesta con el marcador exacto [HUB] para conectar con una persona del Hub. - Si el usuario pide hablar con una persona, confirma amablemente y termina con [HUB]. - Cuando ayude, señala la sección de la página (por ejemplo: «mira “La cinta de cabeza” aquí arriba»). - Nunca inventes modelos de teléfono, montos de pago ni plazos. GUÍA: `; const EGO_SUGGESTIONS = [ "¿Mi teléfono sirve?", "¿Puedo grabar sentado?", "¿Cómo me pagan?", "¿Cómo me pongo la cinta?", "¿Qué hace que rechacen un video?", ]; const EGO_GREETING = "¡Hola! Soy EGO 👋 Estoy aquí para ayudarte a grabar material que se acepte (y se pague). Pregúntame lo que quieras sobre la app, la cinta, tu teléfono o los pagos."; function EgoAvatar({ size }) { return ( ); } function HubCard() { return (
Te conecto con una persona

Tu contacto del Hub puede ayudarte con configuración, la app y pagos.

Contactar a mi Hub
); } function RegisterLink() { return (
¿Listo para grabar? Empezar ahora →
); } function EgoChat({ mobileOpen, onToggleMobile }) { const KEY = "ego-chat-v1"; const [msgs, setMsgs] = useChatState(() => { try { return JSON.parse(localStorage.getItem(KEY)) || []; } catch (e) { return []; } }); const [input, setInput] = useChatState(""); const [busy, setBusy] = useChatState(false); const scrollRef = useChatRef(null); useChatEffect(() => { localStorage.setItem(KEY, JSON.stringify(msgs)); }, [msgs]); useChatEffect(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [msgs, busy, mobileOpen]); const send = async (text) => { const t = (text || input).trim(); if (!t || busy) return; setInput(""); setMsgs((m) => [...m, { role: "user", text: t }]); setBusy(true); try { // The backend owns the persona + guide KB + safety pipeline and tracks // history per user_id, so we just send the latest message. const res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text: t, user_id: egoUid() }), }); const data = await res.json(); let reply = data && data.reply ? String(data.reply) : ""; let hub = false; // The persona ends with the [HUB] marker when handing off to a person. if (reply.includes("[HUB]")) { hub = true; reply = reply.replace(/\[HUB\]/g, "").trim(); } setMsgs((m) => [...m, { role: "assistant", text: reply, hub }]); } catch (e) { setMsgs((m) => [...m, { role: "assistant", text: "Uy, no pude responder ahora mismo. Inténtalo de nuevo en un momento — o contacta a tu Hub.", hub: true, }]); } finally { setBusy(false); } }; const reset = () => { setMsgs([]); }; return (
EGO Tu IA para grabar bien y cobrar
{msgs.length > 0 ? ( ) : null}
{EGO_GREETING}
{msgs.length === 0 ? (
{EGO_SUGGESTIONS.map((s) => ( ))}
) : null} {msgs.length === 0 ? : null} {msgs.map((m, i) => m.role === "user" ? (
{m.text}
) : (
{m.text}
{m.hub ? : null}
) )} {busy ? (
) : null}
); } Object.assign(window, { EgoChat, EgoAvatar });