Precisa de Ajuda? Atendimento
0
Tempo de Garantia

Todas as garantias são dadas pelos fabricantes, e variam de produto para produto. Elas serão informadas nas páginas de cada produto. Caso tenha alguma dúvida, entre em contato conosco.

Para devoluções, os produtos deverão ser remetidos em suas embalagens originais e caso for constatado mal uso, o custo será por parte do comprador.

`; } else { return '👋 Olá! Bem-vindo à Phoenix Hospitalar™! Estou aqui para te ajudar a escolher o equipamento hospitalar perfeito. O que você está procurando?'; } } function playNotificationSound() { try { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.frequency.value = 800; oscillator.type = 'sine'; gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.1); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + 0.1); } catch (error) { console.error('[Phoenix Widget] Erro ao tocar som:', error); } } // FIX 2: linkify — processa markdown [texto](url), wa.me e URLs brutas function linkify(text) { const WA_MARKER = '\x00WA\x00'; const LINK_MARKER = '\x00LK\x00'; const SEP = '\x00'; // 1. Processar markdown [texto](url) ANTES de escapar HTML let result = text.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, function(_, label, url) { var cleanLabel = label.replace(/[\u{1F300}-\u{1FFFF}\u{2600}-\u{27FF}]/gu, '').trim(); if (url.indexOf('wa.me') !== -1) { return WA_MARKER + url + SEP + (cleanLabel || 'Abrir WhatsApp') + SEP; } return LINK_MARKER + url + SEP + (cleanLabel || url) + SEP; }); // 2. Remover emojis soltos antes de URLs brutas result = result.replace(/[\u{1F300}-\u{1FFFF}\u{2600}-\u{27FF}]\s*(?=https?:\/\/)/gu, ''); // 3. Processar URLs brutas restantes result = result.replace(/(https?:\/\/[^\s\x00<>"']+)/g, function(url) { if (url.indexOf('wa.me') !== -1) { return WA_MARKER + url + SEP + 'Abrir WhatsApp' + SEP; } return LINK_MARKER + url + SEP + url + SEP; }); // 4. Escapar HTML no texto puro (entre os marcadores) var parts = result.split(/(\x00(?:WA|LK)\x00[^\x00]+\x00[^\x00]+\x00)/g); var escaped = parts.map(function(part) { if (part.charAt(0) === '\x00') return part; return part.replace(/&/g, '&').replace(//g, '>'); }).join(''); // 5. Substituir marcadores por HTML final return escaped .replace(/\x00WA\x00([^\x00]+)\x00([^\x00]+)\x00/g, function(_, url, label) { return '' + label + ''; }) .replace(/\x00LK\x00([^\x00]+)\x00([^\x00]+)\x00/g, function(_, url, label) { var display = (label === url && url.length > 60) ? url.replace(/https?:\/\/[^/]+/, '').substring(0, 45) + '...' : label; return '' + display + ''; }); } const widgetHTML = `
`; document.body.insertAdjacentHTML('beforeend', widgetHTML); const button = document.getElementById('phoenix-chat-button'); const modal = document.getElementById('phoenix-chat-modal'); const closeBtn = document.getElementById('phoenix-chat-close'); const input = document.getElementById('phoenix-chat-input'); const sendBtn = document.getElementById('phoenix-chat-send'); const messagesContainer = document.getElementById('phoenix-chat-messages'); const flyingBird = document.getElementById('phoenix-flying-bird'); const hoveringBird = document.getElementById('phoenix-hovering'); const typingIndicator = document.getElementById('phoenix-typing-indicator'); state.sessionId = sessionStorage.getItem('phoenix_session_id'); if (!state.sessionId) { state.sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; sessionStorage.setItem('phoenix_session_id', state.sessionId); } // FIX 1: startSession popula renderedMessageIds ANTES do polling iniciar async function startSession() { try { const response = await fetch(`${WIDGET_CONFIG.apiUrl}/start`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: state.sessionId, productUrl: window.location.href, isLocationPage: isLocationPage(), }), }); const data = await response.json(); if (!state.welcomeRendered) { state.welcomeRendered = true; const initialDiv = document.getElementById('phoenix-initial-message'); if (data.welcomeMessage) { // Sessão nova: renderizar boas-vindas e marcar ID sintético initialDiv.innerHTML = linkify(data.welcomeMessage); state.renderedMessageIds.add('welcome'); } else if (initialDiv) { // Sessão existente: esconder card de boas-vindas const card = initialDiv.closest('div[style]'); if (card) card.style.display = 'none'; } } // Marcar todos os IDs do histórico como já renderizados if (data.messages && data.messages.length > 0) { data.messages.forEach(function(m) { state.renderedMessageIds.add(m.id); if (m.id > (state.lastMessageId || 0)) state.lastMessageId = m.id; }); } } catch (error) { console.error('[Phoenix Widget] Error starting session:', error); } } // FIX 1: polling passa id para check-and-mark interno no addMessage function startPolling() { if (state.pollingTimer) clearInterval(state.pollingTimer); state.pollingTimer = setInterval(async () => { try { const url = `${WIDGET_CONFIG.apiUrl}/messages/${state.sessionId}${state.lastMessageId ? `?lastMessageId=${state.lastMessageId}` : ''}`; const response = await fetch(url); const data = await response.json(); if (data.success) { if (data.isTyping && !state.isTypingVisible) { typingIndicator.style.display = 'block'; state.isTypingVisible = true; } else if (!data.isTyping && state.isTypingVisible) { typingIndicator.style.display = 'none'; state.isTypingVisible = false; } if (data.messages && data.messages.length > 0) { data.messages.forEach(function(msg) { if (msg.sender !== 'user') { // FIX 1: passa msg.id para check-and-mark — evita duplicação com send addMessage(msg.content, msg.sender === 'vendor' ? 'vendor' : 'ai', msg.id); if (msg.id > (state.lastMessageId || 0)) state.lastMessageId = msg.id; playNotificationSound(); } }); if (state.isTypingVisible) { typingIndicator.style.display = 'none'; state.isTypingVisible = false; } } } } catch (error) { console.error('[Phoenix Widget] Error polling messages:', error); } }, WIDGET_CONFIG.pollingInterval); } function stopPolling() { if (state.pollingTimer) { clearInterval(state.pollingTimer); state.pollingTimer = null; } } async function openModal(autoOpened = false) { button.style.display = 'none'; flyingBird.style.display = 'block'; flyingBird.style.animation = 'flyUp 1.5s ease-out forwards'; setTimeout(async () => { modal.style.display = 'flex'; flyingBird.style.display = 'none'; hoveringBird.style.display = 'block'; input.focus(); // FIX 1: startSession PRIMEIRO, polling só inicia depois (sem race condition) await startSession(); startPolling(); }, 1000); markAsVisited(); } function closeModal() { modal.style.display = 'none'; hoveringBird.style.display = 'none'; button.style.display = 'flex'; stopPolling(); } if (!hasVisitedBefore()) { setTimeout(() => { openModal(true); }, WIDGET_CONFIG.autoOpenDelay); } button.addEventListener('click', openModal); closeBtn.addEventListener('click', closeModal); async function sendMessage() { const message = input.value.trim(); if (!message) return; addMessage(message, 'user'); input.value = ''; try { const response = await fetch(`${WIDGET_CONFIG.apiUrl}/send-message`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: state.sessionId, message, productUrl: window.location.href, isLocationPage: isLocationPage() }), }); const data = await response.json(); if (data.success && data.response) { // FIX 1: avançar lastMessageId para evitar re-exibição pelo polling if (data.aiMessageId && data.aiMessageId > (state.lastMessageId || 0)) { state.lastMessageId = data.aiMessageId; } if (data.welcomeMessageId && data.welcomeMessageId > (state.lastMessageId || 0)) { state.lastMessageId = data.welcomeMessageId; } // FIX 1: passa aiMessageId para check-and-mark — polling não re-exibe addMessage(data.response, 'ai', data.aiMessageId || null); if (data.welcomeMessageId) { state.renderedMessageIds.add(data.welcomeMessageId); } } else if (!data.success) { addMessage('Erro ao enviar mensagem. Tente novamente.', 'ai'); } } catch (error) { console.error('Erro ao enviar mensagem:', error); addMessage('Erro ao enviar mensagem. Tente novamente.', 'ai'); } } // FIX 1 + FIX 2 + FIX 3: addMessage com check-and-mark e linkify function addMessage(text, sender, id) { id = id || null; // FIX 1: check-and-mark — quem renderizar primeiro marca; segundo cai no return if (id && state.renderedMessageIds.has(id)) return; const isUser = sender === 'user'; const isVendor = sender === 'vendor'; const bgColor = isUser ? '#667eea' : (isVendor ? '#25D366' : '#f3f4f6'); const textColor = isUser || isVendor ? 'white' : '#1f2937'; const marginLeft = isUser ? '40px' : '0'; // FIX 2 + FIX 3: linkify processa markdown, URLs e remove emojis antes de URLs const formattedText = isUser ? text.replace(/&/g, '&').replace(//g, '>') : linkify(text); const messageHTML = `
${!isUser ? `
Phoenix
${isVendor ? 'Consultor Phoenix' : 'Phoenix Hospitalar'}
` : ''}
${formattedText}
`; messagesContainer.insertAdjacentHTML('beforeend', messageHTML); messagesContainer.scrollTop = messagesContainer.scrollHeight; // FIX 1: marcar APÓS render if (id) state.renderedMessageIds.add(id); } sendBtn.addEventListener('click', sendMessage); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); }); })();