/* Hexis AI — Omnipresent Intelligence Layer v4 Speed: instant keyword response + async LLM upgrade Persistence: session msgs survive page navigation BLS: behavioral tracking feeds context */ const { useState: useStateH, useEffect: useEffectH, useRef: useRefH, useCallback: useCallbackH } = React; /* ═══════════════════════════════════════════════════════════ SESSION STORE — persists conversation across page nav Cleared only on explicit close or site reload ═══════════════════════════════════════════════════════════ */ /* Load persisted msgs from localStorage so they survive browser close + close button */ const _STORAGE_KEY = 'hexis_msgs_v2'; const _loadPersisted = () => { try { return JSON.parse(localStorage.getItem(_STORAGE_KEY) || '[]'); } catch { return []; } }; let _sessionMsgs = _loadPersisted(); let _sessionGreeted = _sessionMsgs.length > 0; // skip greeting if msgs already exist /* ═══════════════════════════════════════════════════════════ HEXIS STORE — alert/open state ═══════════════════════════════════════════════════════════ */ const HexisStore = (() => { const subs = new Set(); let state = { open: false, seenAt: Date.now() - 1, alerts: [ { id: 'a1', kind: 'success', title: 'Match Premium identificado', body: 'Miguel Tavares · Veridian VC · 98% — janela ótima 14:32–14:50.', at: Date.now() - 60000 * 6, action: 'Ver Match' }, { id: 'a2', kind: 'warning', title: 'Decisão pendente', body: 'RFP Banco Invicta expira em 3h. Resposta sugerida pronta.', at: Date.now() - 60000 * 12, action: 'Responder' }, { id: 'a3', kind: 'info', title: 'Pipeline atualizado', body: '€420K em novas oportunidades atribuídas ao Web Summit.', at: Date.now() - 60000 * 22 }, ], }; const notify = () => subs.forEach(fn => fn({ ...state })); return { get() { return { ...state }; }, subscribe(fn) { subs.add(fn); fn({ ...state }); return () => subs.delete(fn); }, open() { state = { ...state, open: true, seenAt: Date.now() }; notify(); }, close() { state = { ...state, open: false }; notify(); }, clearHistory(){ _sessionMsgs = []; _sessionGreeted = false; }, addAlert(a) { const id = 'a_' + Math.random().toString(36).slice(2, 8); state = { ...state, alerts: [{ id, at: Date.now(), ...a }, ...state.alerts.slice(0, 19)] }; notify(); }, unreadCount() { return state.alerts.filter(a => a.at > state.seenAt).length; }, highestKind() { const order = ['danger', 'warning', 'success', 'info']; const unseen = state.alerts.filter(a => a.at > state.seenAt); for (const k of order) if (unseen.find(a => a.kind === k)) return k; return null; }, }; })(); window.HexisStore = HexisStore; /* ═══════════════════════════════════════════════════════════ BLS — Behavioral Learning Stream (Edge capture) Tracks scroll, dwell, CTAs → feeds HexisEngine context ═══════════════════════════════════════════════════════════ */ const HexisBLS = (() => { const _buffer = []; const _sessionHash = 'bls_' + Math.random().toString(36).slice(2, 10); let _lastFlush = Date.now(); const track = (type, target, value) => { _buffer.push({ type, target, value, ts: new Date().toISOString() }); // Feed HexisEngine immediately for local context updates if (window.HexisEngine) { if (type === 'cta_click') HexisEngine.trackClick(target); if (type === 'page_view') HexisEngine.trackPage(target); } // Batch flush every 8 seconds if (Date.now() - _lastFlush > 8000 && _buffer.length > 0) flush(); }; const flush = () => { if (_buffer.length === 0) return; const batch = _buffer.splice(0, _buffer.length); _lastFlush = Date.now(); const token = window.getAuthToken ? window.getAuthToken() : null; const userId = window.getAuthUser ? window.getAuthUser()?.id : null; const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) }; const base = (typeof API_BASE !== 'undefined') ? API_BASE : 'https://api.marcianusexperience.com/api/v1'; // Fire-and-forget each event, matching analytics schema: { action, category, label, value, user_id } batch.forEach(ev => { fetch(`${base}/analytics/track`, { method: 'POST', headers, body: JSON.stringify({ user_id: userId, session_id: _sessionHash, action: ev.type, category: ev.type.includes('scroll') ? 'engagement' : ev.type.includes('chat') ? 'assistant' : 'navigation', label: ev.target, value: typeof ev.value === 'number' ? ev.value : 1, metadata: { page: window.location.pathname }, }), signal: AbortSignal.timeout(3000), }).catch(() => {}); // silent fail — BLS is non-blocking }); }; // Scroll depth tracker const initScroll = () => { let _maxScroll = 0; document.addEventListener('scroll', () => { const pct = Math.round((window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100); if (pct > _maxScroll + 10) { _maxScroll = pct; track('scroll_depth', window.location.pathname, pct); } }, { passive: true }); }; setTimeout(initScroll, 1000); setInterval(flush, 8000); return { track }; })(); window.HexisBLS = HexisBLS; /* ═══════════════════════════════════════════════════════════ INSIGHT BAR — inline contextual recommendations ═══════════════════════════════════════════════════════════ */ const HexisInsightBar = ({ page }) => { const [recs, setRecs] = useStateH([]); const [idx, setIdx] = useStateH(0); const [dismissed, setDismissed] = useStateH(false); useEffectH(() => { if (!window.HexisEngine) return; setRecs(HexisEngine.getPageRecs(page)); setIdx(0); setDismissed(false); }, [page]); useEffectH(() => { if (recs.length <= 1) return; const t = setInterval(() => setIdx(i => (i + 1) % recs.length), 8000); return () => clearInterval(t); }, [recs]); if (dismissed || recs.length === 0) return null; const r = recs[idx]; const kindColors = { success: '#00C48C', warning: '#FFD700', danger: '#FF3B5C', info: '#00D1FF', gold: '#FFD700' }; const col = kindColors[r.kind] || '#7B61FF'; return (
Hexis AI{r.title} — {r.body}
{r.action && ( )} {recs.length > 1 && (
{recs.map((_, i) =>
)}
)}
); }; window.HexisInsightBar = HexisInsightBar; /* ═══════════════════════════════════════════════════════════ FAB — floating action button ═══════════════════════════════════════════════════════════ */ const HexisFab = () => { const [s, setS] = useStateH(HexisStore.get()); useEffectH(() => HexisStore.subscribe(setS), []); const unread = s.alerts.filter(a => a.at > s.seenAt).length; const kind = HexisStore.highestKind ? HexisStore.highestKind() : null; const showLight = unread > 0 && !s.open; return ( ); }; /* ═══════════════════════════════════════════════════════════ MAIN POPUP ═══════════════════════════════════════════════════════════ */ const HexisPopup = () => { const [s, setS] = useStateH(HexisStore.get()); const [profile, setProfile] = useStateH(window.HexisEngine ? HexisEngine.getProfile() : {}); const [tab, setTab] = useStateH('chat'); const [msgs, setMsgsState] = useStateH(_sessionMsgs); const [input, setInput] = useStateH(''); const [busy, setBusy] = useStateH(false); // true while instant response shown + LLM upgrading const scrollRef = useRefH(null); const inputRef = useRefH(null); const msgsRef = useRefH(_sessionMsgs); const popupRef = useRefH(null); // Persist msgs to module variable + localStorage whenever they change const setMsgs = (updater) => { setMsgsState(prev => { const next = typeof updater === 'function' ? updater(prev) : updater; _sessionMsgs = next; msgsRef.current = next; try { localStorage.setItem(_STORAGE_KEY, JSON.stringify(next.slice(-60))); } catch {} return next; }); }; useEffectH(() => HexisStore.subscribe(setS), []); useEffectH(() => { if (!window.HexisEngine) return; return HexisEngine.subscribe(p => setProfile({ ...p })); }, []); /* ── Click-outside closes popup ──────────────────────────── */ useEffectH(() => { if (!s.open) return; const onDown = (e) => { if (popupRef.current && !popupRef.current.contains(e.target)) { // Don't close if clicking the FAB itself if (e.target.closest('.hexis-fab')) return; HexisStore.close(); } }; document.addEventListener('mousedown', onDown); return () => document.removeEventListener('mousedown', onDown); }, [s.open]); /* ── Show greeting only if no session history ─────────────── */ useEffectH(() => { if (!s.open) return; if (_sessionGreeted && _sessionMsgs.length > 0) { // Restore existing conversation setMsgsState(_sessionMsgs); return; } _sessionGreeted = true; const authUser = window.getAuthUser ? getAuthUser() : null; const isGuest = !authUser; const p = window.HexisEngine ? HexisEngine.getProfile() : {}; const page = p.currentPage || 'home'; const mis = (p.misScore || 0.30).toFixed(2); const firstName = authUser?.name?.split(' ')[0] || null; let greeting; let suggestions; if (isGuest) { const guestGreetings = { home: `Olá! Sou o Hexis AI — o motor de inteligência da Marcianus. Analiso comportamento B2B e identifico conexões de alto valor. Faz login para matching personalizado.`, entrar: `Bem-vindo! Cria uma conta gratuita ou faz login para aceder ao matching B2B, dashboard e Hexis AI completo.`, eventos: `Temos 6 eventos confirmados para 2026 — de FinTech Lisboa ao Marcianus Summit (600 pax, 25 Out). Login para recomendações personalizadas.`, pricing: `Planos desde €0 (Starter) até Enterprise. Para recomendação personalizada ao teu perfil, faz login ou fala connosco.`, sobre: `A Marcianus nasceu para acabar com eventos sem resultado mensurável. Posso agendar uma call com o fundador Israel Nzambi.`, conteudos: `10 peças disponíveis — 4 gratuitas. Gratuito em destaque: "A morte do networking espontâneo" (6 min). O que preferes?`, marketplace: `Kiosk NFC, Hexis AI Live, Pack Leads — produtos para eventos B2B de alto valor. Login para ver preços personalizados.`, 'criar-experiencia':`Para criar uma experiência Marcianus, preciso de conhecer o teu perfil. Login para iniciar o Briefing Inteligente.`, }; greeting = guestGreetings[page] || `Olá! Sou o Hexis AI da Marcianus. Faz login para matching B2B personalizado e dashboard completo.`; suggestions = ['Criar Conta Grátis', 'Ver Eventos', 'Como funciona?']; } else { const activeMissions = window.HexisEngine ? HexisEngine.getActiveMissions().length : 0; const authGreetings = { home: `Olá ${firstName}! MIS atual: ${mis}. ${HexisStore.get().alerts.length} alertas. O que queres optimizar hoje?`, eventos: `FinTech Lisboa 2026 está LIVE agora. Próximo: Aether Real Estate (20 Mai). Qual te interessa?`, dashboard: `Hub operacional: ${activeMissions} missões ativas. Por onde queres começar?`, marketplace: `Marketplace: saldo €${(p.walletBalance || 0).toLocaleString('pt-PT')}. Produto recomendado: Hexis AI Live.`, 'criar-experiencia':`Briefing ligado para o teu perfil (${p.sector || 'Tech'} · ${p.role || 'Executivo'}). Pronto para guiar o setup.`, pricing: `Posso calcular o ROI esperado para o teu perfil. Ou ver plano recomendado?`, sobre: `Posso agendar uma call com o founder Israel Nzambi. Tens disponibilidade esta semana?`, conteudos: `10 peças disponíveis. Gratuitos: "A morte do networking espontâneo" e Podcast #08. O que preferes?`, entrar: `Olá ${firstName}! Já estás autenticado. Vai ao Dashboard para ver o teu perfil completo.`, }; greeting = authGreetings[page] || `Olá ${firstName}. MIS ${mis}. O que precisas?`; const recs = window.HexisEngine ? HexisEngine.getPageRecs(page) : []; suggestions = recs.length > 0 ? recs.slice(0, 3).map(r => r.action) : ['Ver matches', 'Próximo evento', 'Resumo do dia']; } const greetMsg = { id: 1, from: 'hexis', text: greeting, suggestions }; _sessionMsgs = [greetMsg]; msgsRef.current = [greetMsg]; setMsgsState([greetMsg]); }, [s.open]); /* ── Auto-scroll to bottom ─────────────────────────────────── */ useEffectH(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [msgs]); /* ── Scroll to bottom when popup opens ─────────────────────── */ useEffectH(() => { if (!s.open) return; const id = setTimeout(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, 80); return () => clearTimeout(id); }, [s.open]); /* ── Respond: instant local → async LLM upgrade ────────────── */ /* Strip LLM markdown so it renders as clean text */ const stripMd = (t) => (t || '') .replace(/\*\*([^*]+)\*\*/g, '$1') .replace(/\*([^*]+)\*/g, '$1') .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') .replace(/#{1,6}\s+/gm, '') .replace(/^[\s]*[-*]\s+/gm, '• ') .trim(); const respond = useCallbackH(async (userText) => { if (!userText.trim() || busy) return; SoundFX.tap(); HexisBLS.track('hexis_chat', userText.slice(0, 60), 1); if (window.HexisEngine) HexisEngine.trackClick('hexis_chat'); const page = window.HexisEngine ? HexisEngine.getProfile().currentPage : 'home'; const msgId = Date.now(); const history = msgsRef.current.filter(m => m.id !== 1); const local = window.HexisLocal ? HexisLocal.respond(userText, page) : { text: 'Hexis AI a iniciar…', suggestions: ['Ver Eventos', 'Ver Preços', 'Como funciona?'], fromLocal: true }; setBusy(true); setMsgs(prev => [...prev, { id: msgId, from: 'hexis', text: local.text, suggestions: local.suggestions, fromLLM: false, fromLocal: true }]); SoundFX.notify(); const onToken = (partial) => { if (!partial || typeof partial !== 'string') return; setMsgs(prev => prev.map(m => m.id === msgId ? { ...m, text: stripMd(partial), thinking: false, fromLLM: true, fromLocal: false } : m )); }; try { const result = window.HexisBackend ? await window.HexisBackend.chat(userText, history, page, onToken) : window.HexisOllama ? await window.HexisOllama.streamResponse(userText, history, page, onToken) : null; if (result?.fromLLM === true && result?.text && result.text.length > 15) { setMsgs(prev => prev.map(m => m.id === msgId ? { ...m, text: stripMd(result.text), thinking: false, fromLLM: true, fromLocal: false, suggestions: result.suggestions?.length ? result.suggestions : local.suggestions } : m )); } } catch (_) { /* local response stays */ } finally { setBusy(false); } }, [busy]); const send = (text) => { const t = (text || input).trim(); if (!t || busy) return; const userMsg = { id: Date.now() - 1, from: 'me', text: t }; setMsgs(prev => [...prev, userMsg]); setInput(''); respond(t); }; /* Close — do NOT clear history; conversation persists (localStorage) */ const handleClose = () => { SoundFX.tap(); HexisStore.close(); }; /* Nova Conversa — explicit user action to reset */ const newConversation = () => { try { localStorage.removeItem(_STORAGE_KEY); } catch {} _sessionMsgs = []; _sessionGreeted = false; msgsRef.current = []; setMsgsState([]); HexisStore.close(); setTimeout(() => HexisStore.open(), 80); }; const lp = window.HexisEngine ? HexisEngine.getLevelProgress() : { level: 1, tier: 'bronze', xp: 0, pct: 0, xpToNext: 200 }; const activeMissions = window.HexisEngine ? HexisEngine.getActiveMissions() : []; const authUser = window.getAuthUser ? getAuthUser() : null; const isGuest = !authUser; // User avatar initials const userInitials = authUser?.name ? authUser.name.split(' ').slice(0, 2).map(w => w[0]).join('').toUpperCase() : 'Tu'; if (!s.open) return null; return (
{/* Header */}
Hexis AI · {(profile.currentPage || 'home').toUpperCase()}
{isGuest ? ( modo visitante · login para MIS completo ) : ( MIS {(profile.misScore || 0.30).toFixed(2)} · L{lp.level} {lp.tier} )} {busy && ( a melhorar… )}
{/* XP progress — only for authenticated users */} {!isGuest && (
{(profile.xp || 0).toLocaleString()} XP · {(lp.tier || 'bronze').toUpperCase()} {lp.xpToNext} XP para L{lp.level + 1}
)} {/* Tabs */}
{[ { id: 'chat', label: 'Chat' }, { id: 'alerts', label: `Alertas${s.alerts.filter(a => a.at > s.seenAt).length > 0 ? ' · ' + s.alerts.filter(a => a.at > s.seenAt).length : ''}` }, { id: 'missions', label: `Missões${activeMissions.length > 0 ? ' · ' + activeMissions.length : ''}` }, ].map(t => ( ))}
{/* ── CHAT TAB ──────────────────────────────────────────── */} {tab === 'chat' && ( <>
{msgs.map(m => (
{m.from === 'hexis' ? '⟡' : userInitials}
{(m.fromLLM ? stripMd(m.text) : m.text)} {m.fromLLM === true && ( ⟡llm )} {m.fromLocal === true && !m.fromLLM && ( ⟡local )}
{m.suggestions?.length > 0 && (
{m.suggestions.map((sg, i) => ( ))}
)}
))}
setInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && !busy) send(); }} placeholder={busy ? 'Hexis a pensar…' : 'Pergunta à Hexis…'} autoComplete="off" />
)} {/* ── ALERTS TAB ────────────────────────────────────────── */} {tab === 'alerts' && (
{isGuest && (
⟡ Alertas personalizados
Faz login para receber alertas de match, pipeline e janelas de oportunidade em tempo real.
)} {s.alerts.filter(a => isGuest ? a.forGuest : true).slice(0, 12).map(a => { const cols = { success: '#00C48C', warning: '#FFD700', danger: '#FF3B5C', info: '#00D1FF' }; const col = cols[a.kind] || '#7B61FF'; return (
{a.title}
{a.body}
{a.action && ( )}
); })} {s.alerts.filter(a => isGuest ? a.forGuest : true).length === 0 && !isGuest && (
Sem alertas de momento.
)}
)} {/* ── MISSIONS TAB ──────────────────────────────────────── */} {tab === 'missions' && (
{isGuest && ( <>
Missão Disponível · +50 XP
Cria a tua conta gratuita
Regista-te para desbloquear matching B2B, dashboard e missões com recompensas reais.
Missão Disponível · +10 XP
Explora os Eventos 2026
Vê os 10 eventos B2B confirmados para 2026 e identifica oportunidades.
)} {!isGuest && activeMissions.length === 0 && (
Sem missões ativas agora. Navega para o Dashboard ou Eventos para desbloquear.
)} {!isGuest && activeMissions.map(m => { const pct = Math.round((m.elapsed / m.timeLimit) * 100); return (
{m.game} · +{m.reward.xp} XP
{m.title}
); })}
)}
); }; window.HexisFab = HexisFab; window.HexisPopup = HexisPopup; window.HexisInsightBar = HexisInsightBar;