(function () { document.head.insertAdjacentHTML('beforeend', ''); document.head.insertAdjacentHTML('beforeend', ''); document.head.insertAdjacentHTML('beforeend', ''); const widgetStylesheet = document.createElement('link'); widgetStylesheet.rel = 'stylesheet'; widgetStylesheet.href = "https://parallel.arbostar.com/assets/css/modules/chat_widget/chat-widget.css?v=1.1"; document.head.appendChild(widgetStylesheet); const settings = {"crmHost":"https:\/\/parallel.arbostar.com","leadHost":"https:\/\/parallel.arbostar.com","chatWebHookUrl":"https:\/\/n8n.arbostar.com\/webhook\/6ab2879e-74b5-4c7d-b7bc-1c2fd2c13f14","greeting":"Hi! I\u0027m RAI, the virtual receptionist for Arbostar. How can I help you today?","fromAddress":"olegnekraso87@gmail.com","company_info":"Website: https:\/\/tcia.arbostar.com\/\r\n\r\nCONTACT INFORMATION:\r\n\r\nBUSINESS HOURS:\r\nMon\u0026ndash;Fri: 8:00 AM \u0026ndash; 6:00 PM\r\n\r\nSkip to content Skip to footer Tree care,you can trust Committed to keeping your trees strong, safe, and beautiful. Expert pruning, deep care, and sustainable practices \u2014 all rooted in experience What we doWe help your trees thrive all year round From precise pruning to complete tree care and maintenance, we make sure your landscape stays healthy, balanced, and safe all year round Noah ClarkeCertified Arborist Every tree has its own story \u2014 we help it grow stronger. We combine knowledge, safety, and care in everything we do. Working with nature \u2014 ensuring every tree stands strong for years to come From storm recovery to seasonal maintenance, we\u2019re here year-round. Each branch we shape and every root we protect re...","brand_name":"Arbostar"}; 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 = `
Your logo
`; // 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 = `
${text}
`; 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(); } }); })();