(function () {
document.head.insertAdjacentHTML('beforeend', ' ');
document.head.insertAdjacentHTML('beforeend', ' ');
document.head.insertAdjacentHTML('beforeend', ' ');
const widgetStylesheet = document.createElement('link');
widgetStylesheet.rel = 'stylesheet';
widgetStylesheet.href = "https://treedoctors.arbostar.com/assets/css/modules/chat_widget/chat-widget.css?v=1.1";
document.head.appendChild(widgetStylesheet);
const settings = {"crmHost":"https:\/\/treedoctors.arbostar.com","leadHost":"https:\/\/treedoctors.arbostar.com","chatWebHookUrl":"https:\/\/n8n.arbostar.com\/webhook\/chat-widget\/production-beta","greeting":"Hi! I\u0027m RAI, the virtual receptionist for Tree Doctors. How can I help you today?","fromAddress":"yl@treedoctors.ca","company_info":"Website: https:\/\/treedoctors.ca\/\r\n\r\nCONTACT INFORMATION:\r\nPhone: (416) 201-8000\r\n\r\nFacebook: https:\/\/www.facebook.com\/pages\/Tree-Doctors\/229000977160808\r\nTwitter: https:\/\/twitter.com\/treedoctorsca\r\nInstagram: https:\/\/www.instagram.com\/tree_doctors\/\r\nYouTube: https:\/\/www.youtube.com\/channel\/UCnVj_AbPQ5GTtuGZ5iME2Yw\r\n\r\n(416) 201-8000 Our services+18Tree Removal Tree Planting Mosquito \u0026 Tick Treatments Tree Trimming \u0026 Pruning Emergency Storm \u0026 Tree Removal Services Arborist Reporting \u0026 Consulting Stump Grinding \u0026 Removal Commercial Property Tree Services Holiday Lights Installation Winter Tree Services Forest Management Planning MFTIP Tree \u0026 Deep Root Fertilization Mulch \u0026 Wood Chip Delivery General Tree Care Soil Aeration Storm Damage Tree Restoration Tree-Risk-Assessments Tree protection Zone Installation About us+13Tree Preservation...","brand_name":"Tree Doctors"};
let mode = 'ai';//could be ai or support
let sessionId = crypto.randomUUID();
let lastMessage = {};
lastMessage.sms_id = null;
let shortPollStarted = false;
// Create chat widget container
const chatWidgetContainer = document.createElement('div');
chatWidgetContainer.id = 'chat-widget-container';
document.body.appendChild(chatWidgetContainer);
// Inject the HTML
chatWidgetContainer.innerHTML = `
`;
// Add event listeners
const chatInput = document.getElementById('chat-input');
const chatSubmit = document.getElementById('chat-submit');
const chatMessages = document.getElementById('chat-messages');
const chatBubble = document.getElementById('chat-bubble');
const chatPopup = document.getElementById('chat-popup');
const closePopupButton = document.getElementById('close-popup');
// Auto-resize the textarea up to three lines before enabling scrollbars.
const maxInputLines = 3;
const inputStyles = window.getComputedStyle(chatInput);
const parsedLineHeight = parseFloat(inputStyles.lineHeight);
const parsedFontSize = parseFloat(inputStyles.fontSize);
const lineHeight = Number.isFinite(parsedLineHeight) && parsedLineHeight > 0
? parsedLineHeight
: (Number.isFinite(parsedFontSize) && parsedFontSize > 0 ? parsedFontSize * 1.2 : 20);
const paddingTop = parseFloat(inputStyles.paddingTop) || 0;
const paddingBottom = parseFloat(inputStyles.paddingBottom) || 0;
const borderTop = parseFloat(inputStyles.borderTopWidth) || 0;
const borderBottom = parseFloat(inputStyles.borderBottomWidth) || 0;
const verticalPadding = paddingTop + paddingBottom;
const verticalBorder = borderTop + borderBottom;
const minInputHeight = lineHeight + verticalPadding + verticalBorder;
const maxInputHeight = (lineHeight * maxInputLines) + verticalPadding + verticalBorder;
function adjustChatInputHeight() {
chatInput.style.height = 'auto';
const scrollHeight = chatInput.scrollHeight;
const clampedHeight = Math.min(Math.max(scrollHeight, minInputHeight), maxInputHeight);
chatInput.style.height = `${clampedHeight}px`;
chatInput.style.overflowY = scrollHeight > maxInputHeight ? 'auto' : 'hidden';
}
chatInput.style.minHeight = `${minInputHeight}px`;
adjustChatInputHeight();
chatInput.addEventListener('input', adjustChatInputHeight);
chatSubmit.addEventListener('click', function () {
const message = chatInput.value.trim();
if (!message) return;
chatMessages.scrollTop = chatMessages.scrollHeight;
chatInput.value = '';
adjustChatInputHeight();
if (mode === 'ai') {
onUserRequest(message);
}
if (mode === 'support') {
onUserRequestToSupport(message);
}
});
chatInput.addEventListener('keydown', function (event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
chatSubmit.click();
}
});
chatBubble.addEventListener('click', function () {
togglePopup();
});
closePopupButton.addEventListener('click', function () {
togglePopup();
});
function isPopupOpen() {
return chatPopup.classList.contains('chat-popup--open');
}
function openPopup() {
chatPopup.classList.add('chat-popup--open');
requestAnimationFrame(() => {
chatInput.focus();
});
}
function closePopup() {
chatPopup.classList.remove('chat-popup--open');
}
function togglePopup() {
if (isPopupOpen()) {
closePopup();
} else {
openPopup();
}
}
function showTypingIndicator(text = 'Thinking') {
hideTypingIndicator();
const typingDiv = document.createElement('div');
typingDiv.className = 'chat-widget-typing-indicator';
typingDiv.id = 'chatWidgetTypingIndicator';
typingDiv.innerHTML = `
`;
chatMessages.appendChild(typingDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function hideTypingIndicator() {
const typingIndicator = document.getElementById('chatWidgetTypingIndicator');
if (typingIndicator) {
typingIndicator.remove();
}
}
function onUserRequest(message) {
console.log('User request:', message);
const messageElement = document.createElement('div');
messageElement.className = 'chat-widget-message-row chat-widget-message-row--outgoing';
messageElement.innerHTML = `
${message}
`;
chatMessages.appendChild(messageElement);
chatMessages.scrollTop = chatMessages.scrollHeight;
chatInput.value = '';
adjustChatInputHeight();
showTypingIndicator();
if (!shortPollStarted) {
shortPollStarted = true;
shortPoll();
}
fetch(settings.chatWebHookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Lead-Host': settings.leadHost,
'From-Address': settings.fromAddress,
},
body: JSON.stringify({
"chatInput": message,
"sessionId": sessionId,
"companyInfo": settings.company_info,
"brandName": settings.brand_name
})
})
.then(response => response.json())
.then(data => {
hideTypingIndicator();
if (data && data.answer) {
if (data.action === 'human-request') {
mode = 'support';
showTypingIndicator('Waiting for support');
// todo add message like 'Waiting for support' to indicate iin sms chats
}
} else {
reply('No response from server');
}
})
.catch(error => {
console.error('Error:', error);
hideTypingIndicator();
reply('Error communicating with server');
});
}
function onUserRequestToSupport(message) {
console.log('User request to support:', message);
const messageElement = document.createElement('div');
messageElement.className = 'chat-widget-message-row chat-widget-message-row--outgoing';
messageElement.innerHTML = `
${message}
`;
chatMessages.appendChild(messageElement);
chatMessages.scrollTop = chatMessages.scrollHeight;
chatInput.value = '';
adjustChatInputHeight();
showTypingIndicator();
fetch(settings.crmHost + '/chat-widget/incomingMessageToSupport', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
"chatInput": message,
"sessionId": sessionId,
})
})
.then(response => response.json())
.then(data => {
console.log(data)
//set lastMessageId
lastMessage = data;
console.log('lastMessage', lastMessage);
})
.catch(error => {
console.error('Error:', error);
hideTypingIndicator();
reply('Error communicating with server');
});
}
function shortPoll() {
setInterval(() => {
const data = {
"lastMessageId": lastMessage.sms_id,
"sessionId": sessionId,
};
// 1. Convert the data object into a query string
const queryString = new URLSearchParams(data).toString();
// 2. Append the query string to the base URL
const url = settings.crmHost + '/chat-widget/getNewMessages?' + queryString;
fetch(url, {
method: 'get',
headers: {
'Content-Type': 'application/json',
},
})
.then(response => response.json())
.then(data => {
if (Array.isArray(data) && data.length > 0) {
lastMessage = data.at(-1);
data.forEach(function (item) {
hideTypingIndicator();
reply(item.sms_body);
if (item.sms_user_id) {
mode = 'support';
}
});
}
});
}, 5000); // Poll every 5 seconds
}
function reply(message) {
const chatMessages = document.getElementById('chat-messages');
const replyElement = document.createElement('div');
replyElement.className = 'chat-widget-message-row chat-widget-message-row--incoming';
replyElement.innerHTML = `
${message}
`;
chatMessages.appendChild(replyElement);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
reply(settings.greeting);
closePopup();
document.addEventListener('click', function (event) {
if (!chatWidgetContainer.contains(event.target) && isPopupOpen()) {
closePopup();
}
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape' && isPopupOpen()) {
closePopup();
}
});
})();