(function () { const chatHtml = `
logo
`; function generateUUID() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function (c) { const r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); } ); } function createBubble(contents, sender) { const bubble = document.createElement("div"); const bubbleClasses = `tulu-bubble-base ${ sender === "user" ? "tulu-bubble-user" : "tulu-bubble-bot" }`; bubble.className = bubbleClasses; bubble.innerHTML = contents; return bubble; } function onDomContentLoaded() { console.log("Injecting script"); const rootContainer = document.createElement("div"); rootContainer.innerHTML = chatHtml; document.body.appendChild(rootContainer); let centralChatExpanded = false; const chatId = "5337cc4d-767a-47c1-a745-acf994c0c7fc"; const projectKey = "0aaed52d-7d4d-4578-ac90-3e538c6c0abb"; // Mobile detection const isMobile = window.innerWidth <= 768 || /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent ); // Instances that should default to CTA on mobile (ruban + ortho labs) const instanceKey = "caho"; const isRubanClient = instanceKey === "ruban"; const isOrthoLabsClient = ["ortholabs"].includes(instanceKey); const isDefaultCtaOnMobile = isMobile && (isRubanClient || isOrthoLabsClient); // Enable inactivity nudge only for MOC const isMocClient = "caho" === "moc"; const cahoAssistantSmallButton = document.getElementById( "cahoAssistantSmallButton" ); const chatbotContainer = document.getElementById("chatbotContainer"); const centralChatCloseButton = document.getElementById( "centralChatCloseButton" ); const centralChatEnlargeButton = document.getElementById( "centralChatEnlargeButton" ); const sendMessageButton = document.getElementById("sendMessageButton"); const sendMessageButtonSvg = document.getElementById( "sendMessageButtonSvg" ); const microphoneButton = document.getElementById("microphoneButton"); const textInput = document.getElementById("textInput"); const chatMessagesListContainer = document.getElementById( "chatMessagesListContainer" ); const botDescriptionAndSuggestionsContainer = document.getElementById( "botDescriptionAndSuggestionsContainer" ); const topLogosContainer = document.querySelector( "#chatbotContainer > div:first-child" ); const contractSvgIcon = document.getElementById("contractSvgIcon"); const expandSvgIcon = document.getElementById("expandSvgIcon"); // Inactivity timer setup (MOC only, 2 minutes) let inactivityTimer = null; let inactivityMessageSent = false; const INACTIVITY_MS = 120 * 1000; function appendInactivityMessage() { if (!isMocClient) return; if (inactivityMessageSent) return; // Auto-open expanded chat to show the nudge message // First ensure the main container is visible (not in CTA button state) if (cahoAssistantSmallButton.style.display === "block") { cahoAssistantSmallButton.style.setProperty( "display", "none", "important" ); chatbotContainer.style.setProperty("display", "flex", "important"); } // Then expand if not already expanded if (!centralChatExpanded) { handleChatWidgetEnlargeOrContract(); } const callbackText = "We're here to help. For any assistance, please call +91 9769709229."; const botBubble = createBubble(callbackText, "bot"); chatMessagesListContainer.appendChild(botBubble); inactivityMessageSent = true; scrollToBottom(); } function startOrResetInactivityTimer() { if (!isMocClient) return; if (inactivityTimer) clearTimeout(inactivityTimer); inactivityTimer = setTimeout(appendInactivityMessage, INACTIVITY_MS); } // Initialize in collapsed state on page load centralChatExpanded = false; chatMessagesListContainer.style.setProperty("display", "none", "important"); contractSvgIcon.classList.add("tulu-hidden"); expandSvgIcon.classList.remove("tulu-hidden"); topLogosContainer.style.marginBottom = "0px"; // Inactivity timer now starts only after first user message or bot reply (no initial start) // Special default state for selected clients on mobile - show CTA button instead of center container if (isDefaultCtaOnMobile) { cahoAssistantSmallButton.style.setProperty( "display", "block", "important" ); chatbotContainer.style.setProperty("display", "none", "important"); } else { cahoAssistantSmallButton.style.setProperty( "display", "none", "important" ); chatbotContainer.style.setProperty("display", "flex", "important"); } function removeBotDescriptionAndSuggestionsContainer() { botDescriptionAndSuggestionsContainer.style.display = "none"; } // Removed global click/keystroke resets: timer now only starts/resets on initial load, // user messages, and bot replies (no page-wide activity resets). // Make the collapsed chat clickable to expand chatbotContainer.addEventListener("click", (e) => { // Only expand if clicked on container but not on buttons if ( !centralChatExpanded && !e.target.closest("#centralChatEnlargeButton") && !e.target.closest("#centralChatCloseButton") ) { handleChatWidgetEnlargeOrContract(); } }); cahoAssistantSmallButton.addEventListener("click", () => { cahoAssistantSmallButton.style.setProperty( "display", "none", "important" ); chatbotContainer.style.setProperty("display", "flex", "important"); // Start in collapsed state centralChatExpanded = false; chatMessagesListContainer.style.setProperty( "display", "none", "important" ); contractSvgIcon.classList.add("tulu-hidden"); expandSvgIcon.classList.remove("tulu-hidden"); topLogosContainer.style.marginBottom = "0px"; }); centralChatCloseButton.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event // console.log("Close button clicked"); cahoAssistantSmallButton.style.setProperty( "display", "block", "important" ); chatbotContainer.style.setProperty("display", "none", "important"); // Reset state centralChatExpanded = false; chatMessagesListContainer.style.setProperty( "display", "none", "important" ); contractSvgIcon.classList.add("tulu-hidden"); expandSvgIcon.classList.remove("tulu-hidden"); topLogosContainer.style.marginBottom = "0px"; }); function handleChatWidgetEnlargeOrContract() { if (centralChatExpanded) { // action: contract to small widget // console.log("Contracting to small widget"); // show expand icon, hide contract icon contractSvgIcon.classList.add("tulu-hidden"); expandSvgIcon.classList.remove("tulu-hidden"); topLogosContainer.style.marginBottom = "0px"; // hide the messages list container: do not show messages chatMessagesListContainer.style.setProperty( "display", "none", "important" ); // hide suggestions on contract botDescriptionAndSuggestionsContainer.style.setProperty( "display", "none", "important" ); centralChatExpanded = false; // console.log("Contracted, new state:", centralChatExpanded); } else { // action: expand to bigger window // console.log("Expanding to bigger window"); // Normal expansion behavior // show contract icon, hide expand icon contractSvgIcon.classList.remove("tulu-hidden"); expandSvgIcon.classList.add("tulu-hidden"); topLogosContainer.style.marginBottom = "20px"; // show the messages list container chatMessagesListContainer.style.setProperty( "display", "flex", "important" ); chatMessagesListContainer.style.setProperty( "min-height", "60vh", "important" ); // show suggestions on expand botDescriptionAndSuggestionsContainer.style.setProperty( "display", "flex", "important" ); centralChatExpanded = true; // console.log("Expanded, new state:", centralChatExpanded); } } centralChatEnlargeButton.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event handleChatWidgetEnlargeOrContract(); }); textInput.addEventListener("focus", (e) => { e.stopPropagation(); // Prevent container click event sendMessageButton.style.backgroundColor = "#6b7cfc"; if (!centralChatExpanded) handleChatWidgetEnlargeOrContract(); const paths = sendMessageButtonSvg.getElementsByTagName("path"); for (let i = 0; i < paths.length; i++) { paths[i].setAttribute("stroke", "white"); } }); const onTextInputOutOfFocus = () => { sendMessageButton.style.backgroundColor = "transparent"; const paths = sendMessageButtonSvg.getElementsByTagName("path"); for (let i = 0; i < paths.length; i++) { paths[i].setAttribute("stroke", "#6b7cfc"); } }; textInput.addEventListener("focusout", onTextInputOutOfFocus); textInput.addEventListener("blur", onTextInputOutOfFocus); sendMessageButton.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event sendMessage(textInput.value); startOrResetInactivityTimer(); }); textInput.addEventListener("keypress", (e) => { if (e.key === "Enter") { e.preventDefault(); sendMessage(textInput.value); startOrResetInactivityTimer(); } }); function scrollToBottom() { chatMessagesListContainer.scrollTop = chatMessagesListContainer.scrollHeight; } async function sendMessage(questionText) { const question = questionText.trim(); if (!question || question.length === 0) return; removeBotDescriptionAndSuggestionsContainer(); chatMessagesListContainer.style.paddingTop = "12px"; chatMessagesListContainer.style.paddingBottom = "12px"; const userBubble = createBubble(question, "user"); chatMessagesListContainer.appendChild(userBubble); textInput.value = ""; // clear input // Reset inactivity timer on user message inactivityMessageSent = false; startOrResetInactivityTimer(); const typingBubble = createBubble("...", "bot"); chatMessagesListContainer.appendChild(typingBubble); // Save UUID in session storage let sessionUUID = sessionStorage.getItem("sessionUUID"); if (!sessionUUID) { sessionUUID = generateUUID(); sessionStorage.setItem("sessionUUID", sessionUUID); } const response = await fetch( "https://main.tuluhealth.com/v1/web/chat/?instance=caho", { method: "POST", body: JSON.stringify({ query: question, project_id: projectKey, id: chatId || sessionUUID, }), headers: { "Content-Type": "application/json" }, } ); if (!response.ok) { typingBubble.innerHTML = "

An error occured in serving your query. please try again

"; scrollToBottom(); return; } const reader = response.body.getReader(); const decoder = new TextDecoder("utf-8"); let botAnswer = ""; while (true) { const { done, value } = await reader.read(); if (done) break; botAnswer += decoder.decode(value, { stream: true }); typingBubble.innerHTML = marked.parse(botAnswer); scrollToBottom(); } typingBubble.remove(); const botBubble = createBubble(marked.parse(botAnswer), "bot"); chatMessagesListContainer.appendChild(botBubble); scrollToBottom(); // Reset inactivity timer after bot reply as well startOrResetInactivityTimer(); } // speech to text (voice input) let recognition; let isListening = false; if ("webkitSpeechRecognition" in window) { recognition = new webkitSpeechRecognition(); recognition.continuous = false; recognition.interimResults = false; recognition.lang = "en-US"; recognition.onstart = () => { isListening = true; microphoneButton.classList.add("listening"); }; recognition.onend = () => { isListening = false; microphoneButton.classList.remove("listening"); }; recognition.onresult = (e) => { const transcript = e.results[0][0].transcript; textInput.value = transcript; }; } else { microphoneButton.disabled = true; microphoneButton.title = "Speech recognition not supported in this browser"; } microphoneButton.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event if (!recognition) return; if (!isListening) recognition.start(); else recognition.stop(); }); document.querySelectorAll(".suggestionsButton").forEach((suggestionBtn) => { suggestionBtn.addEventListener("click", (e) => { e.stopPropagation(); // Prevent container click event const question = suggestionBtn.innerHTML.trim(); textInput.value = question; sendMessage(question); startOrResetInactivityTimer(); }); }); } async function loadLibrary(src) { return new Promise((resolve, reject) => { const script = document.createElement("script"); script.src = src; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } loadLibrary("https://cdn.jsdelivr.net/npm/marked/marked.min.js"); // Remove Tailwind CDN - use our exact equivalent styles instead document.addEventListener("DOMContentLoaded", onDomContentLoaded); })();