/ showTypingIndicator: function() { var $indicator = $('
') .attr('id', 'supportWidgetTyping') .addClass('support-widget-message bot'); $indicator.append($('
').addClass('support-widget-message-label').text('Support Assistant')); var $typing = $('
').addClass('support-widget-message-bubble'); $typing.append($('
').addClass('support-widget-typing') .append($('')) .append($('')) .append($('')) ); $indicator.append($typing); this.$messages.append($indicator); this.scrollToMessage($indicator); }, /** * Hide typing indicator */ hideTypingIndicator: function() { $('#supportWidgetTyping').remove(); }, /** * Show escalation prompt */ showEscalationPrompt: function(message) { this.addSystemMessage(message || "Would you like to talk to a human support agent?"); }, /** * Escalate to Helpscout with chat history */ escalateToHuman: function() { console.log('[SupportWidget] Escalating to human support'); // Show transition message this.addSystemMessage('Connecting you to our support team...'); // Format chat history for Helpscout var context = this.formatContextForHelpscout(); // Small delay for visual continuity setTimeout(function() { try { // Check if Beacon is available if (typeof window.Beacon === 'undefined') { console.error('[SupportWidget] Helpscout Beacon not loaded'); this.addSystemMessage('Unable to connect to support. Please email us at support@porkbun.com'); return; } // Reset Beacon to clear any cached data window.Beacon('reset'); // Only prefill with chat context if user has asked questions if (context.trim().length > 0) { // Pre-fill Beacon with chat context (for email form) window.Beacon('prefill', { subject: 'Escalated from AI Assistant', text: context }); // Convert newlines to HTML breaks for session-data display var htmlContext = context.replace(/\n/g, '
'); // Add session data (visible to agents in chat sidebar) window.Beacon('session-data', { 'AI Chat History': htmlContext.substring(0, 5000), // Limit to 5000 chars 'Session ID': this.sessionId || 'N/A', 'Total Queries': this.totalQueries.toString(), 'Escalation Source': 'AI Support Widget' }); } else { // User went straight to human without any AI interaction window.Beacon('session-data', { 'Straight to Human': 'Yes' }); } // Navigate to contact selection (email or chat) window.Beacon('navigate', '/ask/'); // Open Beacon window.Beacon('open'); // Log escalation to database $.post('/tools/support_widget_escalate', { session_id: this.sessionId || '', chat_history: JSON.stringify(this.chatHistory) }) .fail(function(xhr, status, error) { console.error('[SupportWidget] Escalation logging failed:', error); }); // Mark as escalated so chat bubble reopens Beacon instead of AI widget this.isEscalated = true; sessionStorage.setItem('support_widget_escalated', '1'); // Hide our widget to avoid overlap this.minimizeWidget(); } catch (error) { console.error('[SupportWidget] Escalation error:', error); this.addSystemMessage('Unable to open support chat. Please email us at support@porkbun.com'); } }.bind(this), 800); }, /** * Format chat history for Helpscout ticket */ formatContextForHelpscout: function() { // Only include context if user has sent messages in the CURRENT session // (don't prefill for restored sessions or cleared history) if (!this.hasActiveSessionMessages) { return ''; } var context = '=== CHAT ESCALATED FROM AI ASSISTANT ===\n\n'; if (this.sessionId) { context += 'Session ID: ' + this.sessionId + '\n'; } context += 'Timestamp: ' + new Date().toISOString() + '\n'; context += 'Total queries: ' + this.totalQueries + '\n\n'; context += '=== Conversation History ===\n\n'; this.chatHistory.forEach(function(msg, index) { // Skip system messages in context if (msg.role === 'system') { return; } var role = msg.role === 'user' ? 'CUSTOMER' : 'AI ASSISTANT'; var timestamp = ''; if (msg.timestamp) { try { timestamp = ' (' + new Date(msg.timestamp).toLocaleTimeString() + ')'; } catch (e) { // Ignore timestamp formatting errors } } context += '[' + (index + 1) + '] ' + role + timestamp + ':\n'; context += msg.content + '\n\n'; if (msg.sources && msg.sources.length > 0) { context += ' 📚 Sources referenced:\n'; msg.sources.forEach(function(source) { context += ' • ' + source.title + '\n'; context += ' ' + source.url + '\n'; }); context += '\n'; } }); context += '=== END CHAT HISTORY ===\n\n'; context += 'Customer requested assistance from human support agent.\n'; return context; }, /** * Minimize widget after escalation */ minimizeWidget: function() { // Option A: Hide completely this.close(); // Option B: Keep minimized state with indicator // this.$window.addClass('minimized'); // this.$launcher.addClass('active-human-chat'); }, /** * Scroll to a specific message element */ scrollToMessage: function($message) { if (!$message || !$message.length) { return; } var messagesContainer = this.$messages[0]; if (messagesContainer) { setTimeout(function() { // Get the position of the message relative to the container var messageTop = $message.position().top; var containerScrollTop = messagesContainer.scrollTop; // Scroll so the message starts near the top of the visible area // Add a small offset (50px) so there's some breathing room messagesContainer.scrollTop = containerScrollTop + messageTop - 50; }, 100); } }, /** * Scroll messages to bottom (used for initial load) */ scrollToBottom: function() { var messages = this.$messages[0]; if (messages) { setTimeout(function() { messages.scrollTop = messages.scrollHeight; }, 100); } }, /** * Generate a unique session ID */ generateSessionId: function() { var timestamp = Date.now().toString(36); var random = Math.random().toString(36).substring(2, 15); var random2 = Math.random().toString(36).substring(2, 15); return 'sw_' + timestamp + '_' + random + random2; }, /** * Save session data to localStorage */ saveSession: function() { if (this.sessionId) { try { localStorage.setItem('support_widget_session', this.sessionId); localStorage.setItem('support_widget_history', JSON.stringify(this.chatHistory)); } catch (e) { console.warn('[SupportWidget] Unable to save session:', e); } } }, /** * Restore session from localStorage */ restoreSession: function() { try { var sessionId = localStorage.getItem('support_widget_session'); var history = localStorage.getItem('support_widget_history'); var escalated = sessionStorage.getItem('support_widget_escalated'); if (escalated === '1') { this.isEscalated = true; } if (sessionId) { this.sessionId = sessionId; console.log('[SupportWidget] Restored session:', sessionId); } if (history) { var parsedHistory = JSON.parse(history); // Restore messages to UI (only if recently saved - within 24 hours) if (parsedHistory.length > 0) { var lastMessage = parsedHistory[parsedHistory.length - 1]; var lastTime = new Date(lastMessage.timestamp); var now = new Date(); var hoursSince = (now - lastTime) / (1000 * 60 * 60); if (hoursSince < 24) { console.log('[SupportWidget] Restoring chat history (' + parsedHistory.length + ' messages)'); this.$welcome.hide(); // Restore messages to UI WITHOUT adding to chatHistory (to avoid duplication) parsedHistory.forEach(function(msg) { // Don't restore system messages if (msg.role !== 'system') { this.addMessageToUI(msg.role, msg.content, msg.sources); } }.bind(this)); // Now set chatHistory to the parsed history this.chatHistory = parsedHistory; } else { // Clear old session (older than 24 hours) console.log('[SupportWidget] Clearing old session (last activity: ' + hoursSince.toFixed(1) + ' hours ago)'); this.clearSession(); } } } } catch (e) { console.warn('[SupportWidget] Unable to restore session:', e); } }, /** * Clear session data */ clearSession: function() { this.sessionId = null; this.chatHistory = []; this.isEscalated = false; try { localStorage.removeItem('support_widget_session'); localStorage.removeItem('support_widget_history'); sessionStorage.removeItem('support_widget_escalated'); } catch (e) { console.warn('[SupportWidget] Unable to clear session:', e); } }, /** * Clear chat history and start fresh (triggered by user clicking clear button) */ clearHistory: function() { console.log('[SupportWidget] Clearing chat history'); // Clear session data this.clearSession(); // Clear all messages from UI this.$messages.find('.support-widget-message').remove(); // Show welcome message again this.$welcome.fadeIn(); // Reset stats this.totalQueries = 0; this.hasActiveSessionMessages = false; // Clear input field this.$input.val(''); this.$input.css('height', 'auto'); this.$sendBtn.prop('disabled', true); console.log('[SupportWidget] Chat history cleared - starting fresh'); }, /** * Save widget state (open/closed) */ saveState: function() { try { localStorage.setItem('support_widget_open', this.isOpen ? 'true' : 'false'); } catch (e) { console.warn('[SupportWidget] Unable to save state:', e); } } }; // Auto-initialize when DOM is ready $(document).ready(function() { if (typeof SupportWidget !== 'undefined') { SupportWidget.init(); } }); // ============================================ // Global API - Call from anywhere on the page // ============================================ /** * Toggle support widget (similar to Helpscout's beaconEvent) * Usage: Chat Support */ function toggleSupportWidget() { if (typeof SupportWidget !== 'undefined' && SupportWidget.toggle) { SupportWidget.toggle(); } } /** * Open support widget * Usage: Chat Support */ function openSupportWidget() { if (typeof SupportWidget !== 'undefined' && SupportWidget.open) { SupportWidget.open(); } } /** * Close support widget */ function closeSupportWidget() { if (typeof SupportWidget !== 'undefined' && SupportWidget.close) { SupportWidget.close(); } } /** * Support widget event handler (Helpscout-style API) * Usage: Chat Support * * @param {string} action - 'toggle', 'open', 'close', or 'clear' * @param {Event} event - Optional event object */ function supportWidgetEvent(action, event) { if (event) { event.preventDefault(); } if (typeof SupportWidget === 'undefined') { console.warn('[SupportWidget] Widget not initialized'); return; } switch(action) { case 'toggle': SupportWidget.toggle(); break; case 'open': SupportWidget.open(); break; case 'close': SupportWidget.close(); break; case 'clear': SupportWidget.clearHistory(); break; default: console.warn('[SupportWidget] Unknown action:', action); } } Ә