')
.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);
}
}
Ó˜