const VALUES_TO_RPARSE = ["asd"] const CHATBOT_ID = "670762054e1faf6af92e5569"; const WORKSPACE_ID = "66fbe7217a2b5ebea92eea55"; //const serverUrl = "http://localhost:8080"; const serverUrl = "https://queenparts-chatbot-server-beta.fly.dev"; //const serverUrl = "https://queenparts-chatbot-server.fly.dev"; const scriptSocket = document.createElement("script"); scriptSocket.src = "https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.js"; document.head.appendChild(scriptSocket); const scriptMarked = document.createElement("script"); scriptMarked.src = "https://cdn.jsdelivr.net/npm/marked/marked.min.js"; document.head.appendChild(scriptMarked); let linkFonts = document.createElement("link"); linkFonts.href = "https://fonts.googleapis.com"; linkFonts.rel = "preconnect"; document.head.appendChild(linkFonts); linkFonts = document.createElement("link"); linkFonts.href = "https://fonts.gstatic.com"; linkFonts.rel = "preconnect"; linkFonts.crossorigin = true; document.head.appendChild(linkFonts); linkFonts = document.createElement("link"); linkFonts.href = "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"; linkFonts.rel = "stylesheet"; document.head.appendChild(linkFonts); const getChatLanguage = () => { if (typeof window !== "undefined") { console.log(navigator.language); console.log(navigator.userLanguage); return (navigator.language || navigator.userLanguage).split("-")[0]; } return "en"; }; const chatLanguage = getChatLanguage(); const timezoneOffset = 0 - new Date().getTimezoneOffset(); const getElements = () => { const xhr = new XMLHttpRequest(); const url = `${serverUrl}/elements?language_code=${chatLanguage}`; xhr.open("GET", url, false); xhr.send(); if (xhr.status === 200) { return JSON.parse(xhr.responseText); } else { console.error(`Error fetching elements. Status: ${xhr.status}`); return null; } }; const UIElements = getElements(); const styles = ` :host { --userMessageTextColor: white !important; --userMessageBackgroundColor: rgb(37, 99, 235) !important; --botMessageTextColor: black !important; --botMessageBackgroundColor: rgb(252, 252, 252); !important; --systemMessageBackgroundColor: transparent !important; --systemMessageTextColor: rgb(0, 0, 0) !important; --sendMessageButtonBackgroundColor: black !important; --headerTitleTextColor: black !important; --headerSubTitleTextColor: dimgray !important; --mainButtonBoxShadowColor: rgba(0,34,255,1); --userMessageTextColorDark: black !important; --userMessageBackgroundColorDark: white !important; --botMessageTextColorDark: white; --botMessageBackgroundColorDark: black !important; --systemMessageBackgroundColorDark: transparent !important; --systemMessageTextColorDark: white !important; --sendMessageButtonBackgroundColorDark: white !important; --headerTitleTextColorDark: white !important; --headerSubTitleTextColorDark: black !important; } .tnb-container { all: initial; position: fixed; bottom: 4em; right: 8em; will-change: auto; transform: none; font-size: 16px; z-index: 100000; -webkit-font-smoothing: auto; transition: transform 0.5s ease-in-out, opacity 0.3s ease-in-out, visibility 0.3s; transform: translateY(300%); } .tnb-container * { all: unset; } .tnb-container .tooltip-chatbot { display: block; border-radius: 15px; width: 15em; padding: 0.5em; background-color: #ffffff; box-shadow: rgba(15, 15, 15, 0.14) 0 0 10px 3px; font-family: 'Inter', sans-serif !important; color: #000; } .open-chat-btn { all: initial; position: fixed; box-shadow: 0px 0px 50px -18px var(--mainButtonBoxShadowColor); bottom: 2.5em; right: 2.5em; opacity: 1; width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: linear-gradient(180deg, #2777D5 0%, #074792 98.23%); z-index: 1000 !important; font-size: 16px; } .open-chat-btn .open-chat-btn-shadow { width: 64px; height: 64px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border-radius: 100%; z-index: -1; box-shadow: 2px -6px 8px 0px #2777D5 , 6px -2px 8px 0px #2777D5, -8px -4px 12px 0px #00c9ff , 8px 4px 8px 0px #00c9ff, -2px 6px 8px 0px #2777D5; background: linear-gradient(180deg, #2777D5 0%, #074792 98.23%) !important; animation: shadow-rotate 3.5s linear infinite; transform-origin: center; } .open-chat-btn * { all: unset; } .open-chat-btn img { width: 82%; height: 82%; } .chat-container { all: initial; background-color: white; opacity: 1; will-change: auto; transform: none; box-shadow: rgba(15, 15, 15, 0.1) 0 0 10px 3px; border-radius: 1em; position: fixed; width: 400px; height: min(704px, 100% - 84px); bottom: 44px; right: 44px; min-height: 500px; min-width: 400px; overflow: hidden; z-index: 100000; border-width: 1px; display: block; font-size: 16px; font-family: 'Inter', sans-serif; } .chat-container *:not(path, svg, g) { all: unset; display: block; box-sizing: content-box; } .chat-container strong, .chat-container b { font-weight: bolder; } .chat-container em, .chat-container i { font-style: italic; } .chat-container span, .chat-container strong, .chat-container em { display: inline; white-space: normal; word-break: normal; } .chat-container li { display: list-item; list-style-type: disc; margin-bottom: 1em; } .chat-container h1 { font-weight: bold; font-size: 1.25em; margin-bottom: 0.4em; width: fit-content; margin: 0 auto; text-align: center; margin-top: 0.2em; } .chat-container hr { display: block; margin-top: 0.3em; margin-bottom: 1em; margin-left: auto; margin-right: auto; border-style: solid; border-width: 1px; border-bottom-width: 1px; border-bottom-width: 0; opacity: 0.1; width: 40%; } .chat-container .internal-chat-container { opacity: 1 will-change: auto transform: none; box-shadow: rgba(15, 15, 15, 0.1) 0 0 10px 3px; border-radius: 1em; width: 100%; height: 100%; overflow: hidden; font-size: 16px; -ms-overflow-style: none; } .chat-container .internal-chat-container .card-chatbot-wrapper { border-radius: 0.5em; border: none; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); height: 100%; display: flex; flex-direction: column; font-family: 'Inter', sans-serif; } .chat-container .internal-chat-container .card-chatbot-wrapper div::-webkit-scrollbar { display: none !important; } .chat-container .internal-chat-container .card-chatbot-wrapper h3, .chat-container .internal-chat-container .card-chatbot-wrapper p { margin: 0; padding: 0; } .chat-container .internal-chat-container .card-chatbot-wrapper .chat-header-container { display: flex; flex-direction: column; gap: 0.375em; padding: 1em; } .chat-container .internal-chat-container .card-chatbot-wrapper .chat-header-container .chat-header { display: flex; align-items: center; height: 100%; } .chat-container .internal-chat-container .card-chatbot-wrapper .chat-header-container .chat-header .profile-image { width: 48px; height: 48px; border-radius: 50%; color: transparent; } .chat-container .internal-chat-container .card-chatbot-wrapper .chat-header-container .chat-header .header-title { font-size: 1.1em; font-weight: 600; line-height: 1; letter-spacing: -0.025em; color: var(--headerTitleTextColor) !important; } .chat-container .internal-chat-container .card-chatbot-wrapper .chat-header-container .chat-header .header-subtitle { font-size: 0.875em; color: dimgray; } .chat-container .internal-chat-container .card-chatbot-wrapper .chat-header-container .chat-header .close-button { width: 32px; height: 32px; min-width: 32px; min-height: 32px; margin-left: auto; cursor: pointer; } .chat-container .internal-chat-container .card-chatbot-wrapper .chat-header-container .chat-header .close-button svg{ fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; opacity: 0.75; transform: none; transition: transform 0.15s, opacity 0.15s; } .chat-container .internal-chat-container .card-chatbot-wrapper .chat-header-container .chat-header .close-button:hover svg { transform: rotate(90deg); opacity: 1; } .chat-container .internal-chat-container .card-chatbot-wrapper .chat-header-container .chat-header .close-button.overlay { position: absolute; top:0; right:0; z-index: 1; margin-top: 2em; /* also possible as - calc(1em - 16px), since parent element has 1 em (16px currently) padding and the height of the content-box is 64px while the button is 32px high. So for it to be y-centered it "should" have a 16px margin top*/ margin-right: 1em; /*padding of parent element right*/ color: white; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container { display: flex; flex: 1 !important; flex-direction: column !important; overflow-y: auto !important; margin-top: max(2px, 0.2vh) !important; padding: max(24px, 1.5vh) !important; -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ font-size: 15.75px; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container::-webkit-scrollbar { display: none; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper { opacity: 1; will-change: auto; transform: none; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper.user { display: flex; justify-content: end; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper.system { display: flex; width: 100%; justify-content: center; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message { line-height: 1.5em; border-radius: 1em; display: flex; flex-direction: column; font-size: max(1em, 1.2vh); margin-bottom: max(10px, 1vh); padding: max(10px, 1vh); width: fit-content; max-width: 85%; word-wrap: break-word; word-break: break-word; box-shadow: rgba(15, 15, 15, 0.1) 0 0 5px 2px; padding-bottom: 6px; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message div, .message a{ display: inline; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message ul, .message-wrapper .message ol{ margin-block-start: 0.55em; margin-block-end: 0.75em; padding-inline-start: 34px; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message ol { counter-reset: section; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message ol > li{ list-style: none; /* Remove default numbering */ counter-increment: section; /* Increment the counter for each
  • */ margin-bottom: 1em; /* Add spacing between items */ } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message ol > li::marker{ content: counter(section) ". "; font-weight: bold; /* Optional for styling */ } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message p { display: block; margin: 0.15em 0; padding: 0; line-height: 1.5em; font-size: max(1em, 1.2vh); word-wrap: break-word; word-break: break-word; margin-inline-start: 0px; margin-inline-end: 0px; unicode-bidi: isolate; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message .heading-emoji{ padding: 0 6px; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message.bot { background-color: var(--botMessageBackgroundColor); color: var(--botMessageTextColor) } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message.user { background-color: var(--userMessageBackgroundColor); color: var(--userMessageTextColor); } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .message-wrapper .message.system { background-color: var(--systemMessageBackgroundColor); color: var(--systemMessageTextColor); text-align: center; font-size: 0.9em; padding: max(5px, 0.5vh) 10px; box-shadow: none; max-width: 100%; width: 100%; justify-content: center; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper { display: flex; flex-direction: column; padding: 4px 0 8px; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer { display: flex; align-items: center; box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 4px; border-radius: 24px; margin: 0 1em; background-color: transparent; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer.disabled{ opacity: 0.5; background-color: rgb(230,230,230); } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .user-input-container { border: none; border-radius: 1em; width: 100%; will-change: auto; transform: none; height: 2.5em; padding: 4px; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .user-input-container input { border: 1px transparent solid; display: flex; width: 100%; border-radius: 1em; padding: 8px 12px; font-size: 0.875em; background-color: transparent; color: black; box-sizing: border-box; height: 100%; font-weight: 500; font-family: 'Inter', sans-serif; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container { box-sizing: border-box; padding: 0 8px 0 0; display: flex; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn { display: inline-flex; align-items: center; justify-content: center; white-space: nowrap; font-weight: 500; width: 32px; height: 32px; color: rgb(185, 185, 185); background-color: rgb(228, 228, 228); box-shadow: 0px 0px 1px rgb(230, 230, 230); border: solid 1px rgb(218, 218, 218); transition: transform 0.35s, background-color 0.3s ease-out, color 0.3s ease-out, border 0.325s ease-in; position: relative; border-radius: 50%; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn path { box-shadow: 0px 0px 1px currentColor; transition: color 0.1s ease; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn:hover { transform: scale(1.07); } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn:active { transform: scale(0.93); } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn svg{ opacity:1; transition: opacity 0.4s 0.4s ease-in; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn.loading svg { opacity:0; visibility: hidden; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn.disabled { cursor: not-allowed; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .disabled:hover, .send-message-btn-container .disabled:active { transform: scale(1); } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn.black{ background: #141414; color:white; border: solid 1px transparent; transition: transform 0.35s, background-color 0.2s ease-out, color 0.2s ease-out, border 0.1s ease-in; } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn::before { content: ""; /* Always define the pseudo-element */ position: absolute; width: 0.85em; height: 0.85em; top: 0; left: 0; right: 0; bottom: 0; margin: auto; z-index: 10; border: 3px solid transparent; border-top-color: #ffffff; border-radius: 50%; animation: button-loading-spinner 1s ease infinite; transition: opacity 0.3s ease-in; /* Transition opacity */ opacity: 0; /* Default state */ } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .chat-footer .send-message-btn-container .send-message-btn.loading::before { opacity: 1; /* Fade in */ } @keyframes button-loading-spinner { from { transform: rotate(0turn); } to { transform: rotate(1turn); } } .chat-container .internal-chat-container .card-chatbot-wrapper .footer-wrapper .warning-chatbot { color: rgba(0, 0, 0, 0.5); font-size: 0.7em; align-self: center; padding: 6px 7px 0; text-align: center; } .chat-container .internal-chat-container .card-chatbot-wrapper .messages-container .new-message { animation: messageSlideIn 0.3s ease-out forwards !important; } .chat-container .stars-rating-container { display: flex; flex-direction:column; gap:50px; font-size: 16px; justify-content: start; position: absolute; width: 100%; height: 100%; top: 0px; background: black; transition: opacity 0.4s 0.1s ease; /* Delay visibility change to match fade-out */ opacity: 1; } .chat-container .overlay-container, .chat-container .overlay-container::after { display: flex; flex-direction:column; gap:20px; font-size: 16px; justify-content: start; position: absolute; width: 100%; height: 100%; top: 0px; background: black; transition: opacity 0.4s 0.1s ease; opacity: 1; } .chat-container .overlay-container.hidden { opacity: 0; visibility: hidden; transition: opacity 0.3s 4.5s ease-in, visibility 0s 4.85s; /* Delay visibility change to match fade-out */ } .chat-container .overlay-container.hidden h1 { opacity:0; transition: opacity 0.3s ease-in; } .chat-container .overlay-container.hidden * { opacity:0; transition: opacity 0.4s ease-in; } .chat-container .overlay-container::after { content: '${UIElements.form.on_submit}'; font-size: 40px; opacity: 0; width:0px; height:0px; visibility: visible; color: white; font-weight: 600; text-align: center; padding-top: 50%; transition: visibility 0s 4.25s; } @keyframes fadeInSlowOutHide { 0% { opacity: 0; /* Start fully unvisible */ } 25% { opacity: 1; /* Fully visible at the middle */ } 75% { opacity: 1; /* Fully visible at the middle */ } 100% { opacity: 0; /* Fully transparent at the end */ } } .chat-container .overlay-container.hidden::after { visibility: hidden; width: 70%; height: 100%; margin-left: 15%; opacity: 0; animation: 3s fadeInSlowOutHide 1.25s; transition: visibility 0s 4.2s; } .chat-container .overlay-container p { font-weight: 500; font-size: 12px; margin-bottom: 16px; color: dimgray; } .chat-container .overlay-container h1 { color:white; font-size: 32px; padding-top:14%; max-width:70%; } #userForm * { all: unset; } .overlay-container #userForm { width: calc(100% - 48px); padding: 15px; border: none; border-radius: 10px; color:white; background-color: transparent; display: flex; flex-direction: column; align-items: center; gap: 16px; align-self: center; } #userForm label { font-weight: 500; display: block; margin-bottom: 5px; } #userForm input { width: calc(100% - 24px); height: auto; padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 16px; margin-bottom: 5px; } #userForm button { background-color: white; color: black; padding: 12px 24px; margin-top: 20px; font-size: 16px; font-weight: 600; border: none; border-radius: 10px; cursor: pointer; } #userForm .form-item { width: 85%; } #userForm .description { font-size: 0.9em; color: gray; } .stars-rating-container.hidden { opacity: 0; visibility: hidden; transition: opacity 0.5s ease, visibility 0s 0.5s; /* Delay visibility change to match fade-out */ } .chat-container .stars-rating-container h1 { color: white; font-size: 32px; padding-top: 20%; max-width: 70%; } .chat-container .stars-rating-container h1::before { content: attr(data-content); transition: opacity 0.5s 0.25s ease, transform 0.5s 0.25s ease; } .stars-rating-container h1.fade-out::before { opacity: 0; transform: translateY(-10px); } .stars-rating-container h1.fade-in::before { opacity: 1; transform: translateY(0); } .stars-rating-container .close-button { position: absolute; top: 0; right: 0; color:white; width: 32px; height: 32px; margin-top: 32px; margin-right: 1em; } .stars-rating-container .stars-rating { display: flex; font-size: 0; justify-content: center; transition: opacity .5s ease; } .stars-rating .star { display: inline-block; font-size: 44px; cursor: pointer; } .stars-rating .star::before { content: '★'; color: #ccc; transition: color .7s ease; } .stars-rating .star.hover::before, .stars-rating .star.selected::before { color: #ffd700; transition: color .7s ease; } @media (max-width: 1025px) { .chat-container { top: 0px; left: 0px; width: 100%; height: 100%; max-width: 100%; max-height: 100%; min-width: 280px; min-height: 520px; z-index: 100000; border-radius: 0px; } .chat-container .internal-chat-container { border-radius: 0px; } .open-chat-btn { right: 20px; width: 64px; height: 64px; } .open-chat-btn img { width: 53px !important; height: 53px !important; } .tnb-container { display: none; } .tnb-container .tooltip-chatbot { position: fixed; right: 7em; bottom: 4em; } } } @keyframes shadow-rotate { from { transform: translate(-50%, -50%) rotate(0deg); } to { transform: translate(-50%, -50%) rotate(360deg); } } @keyframes focusAnimation { to { transform: scale(1.02); box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.2); } } @keyframes unfocusAnimation { to { transform: scale(1); box-shadow: none; } } .focused { animation: focusAnimation 0.3s forwards !important; } .unfocused { animation: unfocusAnimation 0.3s forwards !important; } .small-border { border: 1px solid #e5e7eb !important; } @keyframes slideUp { from { opacity: 0; transform: translateY(50px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideDown { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(50px); } } .chat-container.chat-open { animation: slideUp 0.2s ease-out forwards; } .chat-container.chat-close { animation: slideDown 0.2s ease-out forwards; } .chat-container.chat-hidden { visibility: hidden; } .chat-container .cursor-not-allowed { cursor: not-allowed !important; } .chat-container .cursor-wait { cursor: wait !important; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .fade-in { animation: fadeIn 0.3s ease-out forwards; } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .fade-out { animation: fadeOut 0.3s ease-out forwards; } @keyframes messageSlideIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } `; const chatHTML = `
    `; document.body.insertAdjacentHTML("beforeend", chatHTML); const chatOverlay = document.getElementById("chatOverlay"); const shadowRoot = chatOverlay.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ``+`
    ${UIElements.welcome_message}

    ${UIElements.bot_title}

    ${UIElements.bot_description}
    ` const input = shadowRoot.getElementById("cbuserInput"); const chatFooter = shadowRoot.querySelector(".chat-footer"); const chatContainer = shadowRoot.getElementById('chatContainer'); const inputContainer = shadowRoot.getElementById("inputContainer"); const sendButton = shadowRoot.getElementById("cb-sendmessage-button"); const mainButton = shadowRoot.getElementById("mainButton"); const tooltipContainer = shadowRoot.getElementById("tooltipContainer"); const closeButton = shadowRoot.getElementById("closeButton"); const messagesContainer = shadowRoot.querySelector(".messages-container"); const formElement = document.createElement("div"); function showMainButtonAndTooltip() { mainButton.classList.remove("fade-out"); mainButton.classList.add("fade-in"); tooltipContainer.classList.remove("fade-out"); tooltipContainer.classList.add("fade-in"); } function hideMainButtonAndTooltip() { mainButton.classList.remove("fade-in"); mainButton.classList.add("fade-out"); tooltipContainer.classList.remove("fade-in"); tooltipContainer.classList.add("fade-out"); } closeButton.addEventListener("click", () => { chatContainer.classList.remove("chat-open"); chatContainer.classList.add("chat-close"); setTimeout(() => { chatContainer.classList.add("chat-hidden"); chatContainer.classList.remove("chat-close"); mainButton.style.display = "flex"; showMainButtonAndTooltip(); }, 300); }); function getCookie(name) { const cookies = document.cookie.split(";"); for (const cookie of cookies) { const [cookieName, cookieValue] = cookie.trim().split("="); if (cookieName === name) { return cookieValue; } } return undefined; } function setCookie(name, value) { document.cookie = name + "=" + value + "; path=/"; } function fetchChatId() { try { const xhr = new XMLHttpRequest(); xhr.open( "GET", `${serverUrl}/get_chat_id?chatbot_id=${CHATBOT_ID}&workspace_id=${WORKSPACE_ID}`, false, ); xhr.withCredentials = true; xhr.send(); if (xhr.status !== 200) { throw new Error(`HTTP error! status: ${xhr.status}`); } return xhr.responseText; } catch (error) { console.error("Error fetching getting chat id:", error); return null; } } function getChatID() { let chatID = getCookie("chatID"); if (chatID) { input.removeAttribute("disabled"); chatFooter.classList.remove("disabled"); // formElement.classList.add("chat-hidden"); return chatID; } else { return null; } } async function createChat() { await fetch( `${serverUrl}/loadChat?chat_id=${chatID}&userLanguage=${chatLanguage}&chatbot_id=${CHATBOT_ID}&workspace_id=${WORKSPACE_ID}`, ); return Promise.resolve('Created Chat'); } var firstclick = true; var chatID = getChatID(); const LOGGED_IN = chatID; const sendRating = (rating, ticketId) => { const xhr = new XMLHttpRequest(); xhr.open( "POST", `${serverUrl}/rate_support?chatbot_id=${CHATBOT_ID}&workspace_id=${WORKSPACE_ID}`, true, ); xhr.setRequestHeader("Content-Type", "application/json"); xhr.send( JSON.stringify({ chat_id: chatID, ticket_id: ticketId, rating: rating }), ); }; const parseCookies = () => { result = [] for (const key in VALUES_TO_RPARSE) { result.push({key: getCookie(key)}) } return result } const sendCookies = (rating, ticketId) => { const parsedCookies = parseCookies() const xhr = new XMLHttpRequest(); xhr.open( "POST", `${serverUrl}/rate_support?chatbot_id=${CHATBOT_ID}&workspace_id=${WORKSPACE_ID}`, true, ); xhr.setRequestHeader("Content-Type", "application/json"); xhr.send( JSON.stringify({ chat_id: chatID, cookies: parsedCookies }), ); }; const addRatingMessageHandlers = (ticket_id) => { const starRating = document.getElementById(`rating-${ticket_id}`); const starRatingOverlay = document.getElementById(`rating-container-${ticket_id}`); const closeStarRatingOverlay = document.getElementById(`close-rating-container-${ticket_id}`); const text = document.getElementById(`text-${ticket_id}`); const stars = []; for (let i = 0; i < 5; i++) { stars.push(document.getElementById(`star-${i}-${ticket_id}`)); } const handleMouseOver = (e) => { if (e.target.classList.contains("star")) { const value = e.target.getAttribute("data-value"); highlightStars(value); } }; const handleMouseOut = () => { highlightStars(0); }; const handleClick = (e) => { if (e.target.classList.contains("star")) { const value = e.target.getAttribute("data-value"); selectStars(value); sendRating(value, ticket_id); starRating.removeEventListener("click", handleClick); starRating.removeEventListener("mouseover", handleMouseOver); starRating.removeEventListener("mouseout", handleMouseOut); text.classList.add('fade-out'); starRating.style.opacity = '0'; // Change the content after the fade-out animation setTimeout(() => { text.setAttribute('data-content', `${UIElements.review}`); // Start fade-in effect text.classList.remove('fade-out'); text.classList.add('fade-in'); }, 750); // Match the transition duration (0.5s) setTimeout(() => { closeRating(); }, 3000); } }; starRating.addEventListener("mouseover", handleMouseOver); starRating.addEventListener("mouseout", handleMouseOut); starRating.addEventListener("click", handleClick); closeStarRatingOverlay.addEventListener("click", closeRating); function highlightStars(count) { for (let i = 0; i < stars.length; i++) { if (i < count) { stars[i].classList.add("hover"); } else { stars[i].classList.remove("hover"); } } } function selectStars(count) { for (let i = 0; i < stars.length; i++) { if (i < count) { stars[i].classList.add("selected"); } else { stars[i].classList.remove("selected"); } } } function closeRating(){ sendRating("0", ticket_id); starRatingOverlay.classList.add('hidden'); } }; function addTimezoneOffset(time, offset) { // Parse the time (assumes "HH:MM" format) const [hours, minutes] = time.split(":").map(Number); // Create a Date object for today with the given time const now = new Date(); const dateWithTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes); // Add the timezone offset (in minutes) dateWithTime.setMinutes(dateWithTime.getMinutes() + offset); // Format the updated time back to "HH:MM" const updatedHours = dateWithTime.getHours().toString().padStart(2, "0"); const updatedMinutes = dateWithTime.getMinutes().toString().padStart(2, "0"); return `${updatedHours}:${updatedMinutes}`; } function createInfo(sender, time, status) { if (!time){ const now = new Date(); const hours = now.getHours(); const minutes = now.getMinutes(); time = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; } else if (time.includes(" ")){ time = time.split(" ")[1]; // Split by space and take the second part time = addTimezoneOffset(time,timezoneOffset); } // Determine the SVG based on conditions let svg = ""; if (sender === "user") { if (status === "read") { svg = ` `; } else { svg = ` `; } } // Return the full HTML return `

    ${time}

    ${svg}
    `; } function addNewMessage({sender, text, time, status, rating = null, ticket_id = null}) { if (sender === "rating") { console.log(sender, text, time, status, rating, ticket_id); const overlayContainer = document.createElement("div"); overlayContainer.classList.add("stars-rating-container", "hidden"); overlayContainer.id = `rating-container-${ticket_id}`; overlayContainer.innerHTML = `

    `; const ratingContainer = document.createElement("div"); ratingContainer.className = "stars-rating"; ratingContainer.id = `rating-${ticket_id}`; for (let i = 0; i < 5; i++) { const star = document.createElement("span"); star.id = `star-${i}-${ticket_id}`; if (rating !== null && rating > i) { star.className = "star selected"; return; } else { star.className = "star"; star.setAttribute("data-value", i + 1); } ratingContainer.appendChild(star); } chatContainer.appendChild(overlayContainer); overlayContainer.appendChild(ratingContainer); if (rating === null) { addRatingMessageHandlers(ticket_id); setTimeout(() => { overlayContainer.classList.remove("hidden"); }, 0); } return; } const messageWrapper = document.createElement("div"); messageWrapper.className = `message-wrapper new-message ${sender}`; const messageDiv = document.createElement("div"); messageDiv.className = `message ${sender}`; const innerDiv = document.createElement("div"); messageDiv.appendChild(innerDiv); messageWrapper.appendChild(messageDiv); messagesContainer.appendChild(messageWrapper); if (sender === "system"){ innerDiv.style.display = 'flex'; innerDiv.style.alignItems = 'center'; innerDiv.innerHTML = `
    `+ marked.parse(text) + `
    `; messagesContainer.scrollTop = messagesContainer.scrollHeight; return; } else { innerDiv.innerHTML = marked.parse(text) + createInfo(sender, time, status); } const links = innerDiv.getElementsByTagName("a"); for (let link of links) { link.style.color = "darkblue"; link.style.textDecoration = "underline"; link.style.fontStyle = "italic"; link.target="_blank"; link.rel="noopener noreferrer"; } function extractTwoEmojis(text) { // Regex to find emojis (Unicode-aware) const emojiRegex = /[\p{Emoji}]/gu; // Extract emojis from the text const emojis = text.match(emojiRegex) || []; // Check if the number of emojis is exactly 1 or 2 if (emojis.length === 0 || emojis.length > 2) { // Return the original text if there are 0 or more than 2 emojis return { textWithoutEmojis: text, emojis: [] }; } // Ensure exactly 2 emojis (duplicate the first if only 1 found) const resultEmojis = emojis.length === 1 ? [emojis[0], emojis[0]] : emojis; // Remove the emojis from the text const textWithoutEmojis = text.replace(emojiRegex, '').trim(); return { textWithoutEmojis, emojis: resultEmojis }; } function placeIconsNearHeading(heading, emoji1, emoji2) { if (!heading || !emoji1 || !emoji2) { //console.error("Invalid heading or emojis."); return; } //console.log("Original heading text:", heading.textContent); // Generate unique IDs for the two emojis const uniqueId1 = `emoji1-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; const uniqueId2 = `emoji2-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`; const originalText = heading.textContent.trim(); const words = originalText.split(/\s+/); if (words.length < 2) { console.error("Heading must have at least two words for proper emoji placement."); return; } // STEP 1: Insert the first emoji at the start of the first word, wrapped in a // We add both an id AND a common class "heading-emoji" to this span. words[0] = `${emoji1}` + words[0]; // Helper to build the heading HTML at a given insertion index for the second emoji function buildHeadingText(wordsArray, secondEmojiInsertIndex) { const tempWords = [...wordsArray]; // Insert the second emoji (also with uniqueId2 and the same "heading-emoji" class) tempWords[secondEmojiInsertIndex] += `${emoji2}`; return tempWords.join(" "); } // Check if both emojis are on the same line (compare their .top in getBoundingClientRect) function areBothEmojisOnSameLine() { const e1 = heading.querySelector(`#${uniqueId1}`); const e2 = heading.querySelector(`#${uniqueId2}`); if (!e1 || !e2) return false; const e1Top = e1.getBoundingClientRect().top; const e2Top = e2.getBoundingClientRect().top; //console.log(`Emoji1 top: ${e1Top}, Emoji2 top: ${e2Top}`); // If they're on the same line, their .top is nearly identical return Math.abs(e1Top - e2Top) < 1; } // STEP 2: Try placing the second emoji from the last word backward // until we find a position that keeps both emojis on the same first line. let foundValidPosition = false; for (let i = words.length - 1; i >= 0; i--) { heading.innerHTML = buildHeadingText(words, i); //console.log(`Trying to place second emoji after word index ${i}:`, heading.textContent); // Force a reflow (so getBoundingClientRect() is accurate) heading.getBoundingClientRect(); if (areBothEmojisOnSameLine()) { foundValidPosition = true; //console.log("✅ Both emojis fit on the same (first) line at this position."); break; } else { // Optionally clear innerHTML before trying the next index heading.innerHTML = ""; } } // STEP 3: If no valid position was found, restore original text if (!foundValidPosition) { heading.textContent = originalText; console.error("❌ Unable to fit both emojis on the first line. Restoring original text."); } } async function fitHeadingToContainer(heading) { const container = innerDiv; container.style.width = '85%'; const container_width = container.getBoundingClientRect().width; container.style.width = null; // Start from a base font size (e.g., 1.2em) or whatever you prefer let fontSize = 1.25; let minFontSize = 1.135 let letterSpacing = 0; heading.style.fontSize = fontSize + 'em'; const oneline = 24; // Measure text width vs container width function textHeight() { return heading.getBoundingClientRect().height; } function textWidth() { return heading.getBoundingClientRect().width; } // Decrease font size until it fits while (textWidth() >= container_width && textHeight() > oneline) { if (fontSize > minFontSize) { fontSize -= 0.01; // adjust decrement step as needed heading.style.fontSize = fontSize.toFixed(2) + 'em'; } else if (letterSpacing > -0.055) { letterSpacing -= 0.001; heading.style.letterSpacing = letterSpacing.toFixed(3) + 'em'; } if (letterSpacing > -0.025) { letterSpacing -= 0.001; heading.style.letterSpacing = letterSpacing.toFixed(3) + 'em'; } if (fontSize <= minFontSize && letterSpacing <= -0.055) { //console.log('Failed to optimize Font'); heading.style.fontSize = '1.25em'; heading.style.letterSpacing = '0em'; const extracted = extractTwoEmojis(heading.textContent); heading.textContent = extracted.textWithoutEmojis; placeIconsNearHeading(heading, extracted.emojis[0], extracted.emojis[1]); break; } } await new Promise((resolve) => requestAnimationFrame(resolve)); } for (let i = 1; i <= 6; i++) { const headers = innerDiv.getElementsByTagName(`h${i}`); for (let header of headers) { if (i === 1) { header.style.fontWeight = "700"; fitHeadingToContainer(header); } else { header.style.fontWeight = "600"; header.style.fontSize = `${1.25 - (i - 1) * 0.08}em`; } } } const listItems = innerDiv.getElementsByTagName("li"); for (let item of listItems) { // Set styles for the
  • element item.style.listStyleType = item.parentNode.nodeName === "OL" ? "decimal" : "disc"; } const preTags = innerDiv.getElementsByTagName("pre"); for (let pre of preTags) { pre.style.whiteSpace = "break-spaces"; } messagesContainer.scrollTop = messagesContainer.scrollHeight; } function markAllMessagesRead(){ const infos = messagesContainer.querySelectorAll(".message.user .msginfo"); infos.forEach(info => { const time_paragraph = info.getElementsByTagName("p")[0]; if (time_paragraph) { info.innerHTML = createInfo("user", time_paragraph.innerText, "read"); } }); } const formHTML = `

    ${UIElements.form.heading}

    ${UIElements.form.fname.todo}
    ${UIElements.form.lname.todo}
    ${UIElements.form.email.todo}
    ${UIElements.form.phone.todo}
    `; function addFormToChat() { formElement.innerHTML = formHTML; formElement.classList.add('overlay-container'); chatContainer.appendChild(formElement); } // NETWORK // scriptSocket.addEventListener("load", () => { const socket = io(serverUrl , { reconnection: true, // default is true, enables auto-reconnection reconnectionAttempts: 5, // Number of tries before giving up reconnectionDelay: 1000, // Initial delay between attempts (in ms) reconnectionDelayMax: 5000, // Max delay between attempts (in ms) }); socket.on("connect", () => { console.log("Connected to the server:", socket.id); }); // Listen for the disconnect event socket.on("disconnect", (reason) => { console.log("Disconnected from the server:", reason); // Optionally handle custom logic here }); // Listen for the connect_error event socket.on("connect_error", (error) => { console.error("Connection error:", error); }); // Listen for the reconnect event socket.on("reconnect", (attempt) => { console.log(`Reconnected successfully on attempt ${attempt}`); }); // Listen for the reconnect_attempt event socket.on("reconnect_attempt", (attempt) => { console.log(`Attempting to reconnect, attempt number: ${attempt}`); }); // Listen for the reconnect_error event socket.on("reconnect_error", (error) => { console.error("Reconnection attempt failed:", error); }); // Listen for the reconnect_failed event socket.on("reconnect_failed", () => { console.error("Reconnection failed. Giving up."); }); function connectToRoom() { socket.emit("connect_to_room", { user_id: chatID }); socket.emit("message", { data: "SYS_MESSAGE_CONNECTION", user_iD: chatID, name: "user", }); } function sendSocketIOMessage(text) { socket.emit("message", { data: text, user_iD: chatID, name: "user", chatbot_id: CHATBOT_ID, }); } function newMessageHandler(addNewMessage, setManagerConnected) { socket.on("message", (data) => { console.log(data); if (data.name === "system") { if (data.data === "MANAGER_CALLED") { return; } else if (data.data === "MANAGER_CONNECTED") { console.log("Manager connected"); setManagerConnected(true); return; } else if (data.data === "SYS_MESSAGE_DISCONNECT") { console.log("Manager disconnected"); setManagerConnected(false); return; } else if (data.data === "READ_USER_MSGS") { markAllMessagesRead(); return; } } if (data.name !== "user") { addNewMessage({ sender: data.name, text: data.data, ticket_id: data.ticket_id }); markAllMessagesRead(); } }); } async function getChatHistory() { const response = await fetch( `${serverUrl}/loadChat?chat_id=${chatID}&userLanguage=${chatLanguage}&chatbot_id=${CHATBOT_ID}&workspace_id=${WORKSPACE_ID}`, ); const messages = await response.json(); console.log(messages); if (Array.isArray(messages) && messages.length === 0) { const welcomeResponse = await fetch( `${serverUrl}/getWelcomeMessage?chat_id=${chatID}&chatbot_id=${CHATBOT_ID}&workspace_id=${WORKSPACE_ID}`, ); const welcomeMessage = await welcomeResponse.json(); addNewMessage({ sender: "bot", text: welcomeMessage }); } else { messages.forEach((message) => addNewMessage({ sender: message.sender, text: message.text, time: message.time, status: message.status, rating: message.rating, ticket_id: message.ticket_id }), ); } } async function sendMessage(userText) { const response = await fetch( `${serverUrl}/ask?chat_id=${chatID}&chatbot_id=${CHATBOT_ID}&workspace_id=${WORKSPACE_ID}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ messageText: userText }), }, ); const data = await response.json(); if (data.not_final) { return null; } if (response.status == 401){ return "Please fill out the form before trying to send a message!"; } return data.text; } const getManagerConnectedSync = () => { try { const xhr = new XMLHttpRequest(); xhr.open( "GET", `${serverUrl}/contacting_human?chat_id=${getChatID()}&chatbot_id=${CHATBOT_ID}&workspace_id=${WORKSPACE_ID}`, false, ); xhr.withCredentials = true; xhr.send(); // Send the request if (xhr.status !== 200) { throw new Error(`HTTP error! status: ${xhr.status}`); } const response = JSON.parse(xhr.responseText); console.log("getManagerConnectedSync: "); console.log(response); return response.value; } catch (error) { console.error("Error fetching contacting human status:", error); return null; } }; let managerConnected = getManagerConnectedSync(); function setManagerConnected(value) { managerConnected = value; } function handleFormSubmit(event) { var button = shadowRoot.getElementById('formSubmitButton'); button.setAttribute('disabled', ''); // Предотвращаем стандартное поведение формы event.preventDefault(); const formXhr = new XMLHttpRequest(); formXhr.open( "POST", `${serverUrl}/chat_details?chatbot_id=${CHATBOT_ID}&workspace_id=${WORKSPACE_ID}`, true, ); formXhr.setRequestHeader("Content-Type", "application/json"); formXhr.onload = function () { if (formXhr.status === 200) { // On successful form submission input.removeAttribute("disabled"); setTimeout(() => { closeButton.classList.add('fade-out'); setTimeout(() => { closeButton.classList.remove('fade-out'); closeButton.classList.remove('overlay'); }, 500); }, 100); chatFooter.classList.remove("disabled", "cursor-not-allowed"); formElement.classList.add('hidden'); setTimeout(() => { input.focus(); }, 4850); setCookie("chatID", chatID); getChatHistory(); connectToRoom(); newMessageHandler(addNewMessage, setManagerConnected); } else { console.error("Error sending form:", formXhr.status); alert("Error sending form. Please try again."); button.removeAttribute('disabled'); } }; formXhr.onerror = function () { console.error("Network error occurred"); alert("Network error occurred. Please check your connection."); button.removeAttribute('disabled'); }; try { chatID = fetchChatId(); const formData = { chat_id: chatID, details: { firstName: event.target.firstName.value, lastName: event.target.lastName.value, email: event.target.email.value, phoneNumber: event.target.phoneNumber.value, }, }; createChat().then((result) => { formXhr.send(JSON.stringify(formData)); }); } catch (error) { console.error("Error sending form:", error); alert("Error sending form. Please try again."); button.removeAttribute('disabled'); } } function sendMessageButtonOnClick() { if (![... sendButton.classList].includes("disabled")) { const userMessage = input.value.trim(); input.value = ""; if (userMessage) { addNewMessage({ sender: "user", text: userMessage }); if (managerConnected) { sendSocketIOMessage(userMessage); } else { sendButton.classList.add("loading" , "disabled"); sendMessage(userMessage).then((botMessage) => { if (botMessage) { addNewMessage({ sender: "bot", text: botMessage }); markAllMessagesRead(); sendButton.classList.remove("loading" , "disabled"); if (!input.value.length > 0 ) { setTimeout(() => { sendButton.classList.remove('black'); }, 400); } } }); } } } } sendButton.addEventListener("click", sendMessageButtonOnClick); input.addEventListener("keydown", (event) => { if (![... sendButton.classList].includes("disabled")){ if (event.key === "Enter") { sendMessageButtonOnClick(); } else { setTimeout(() => { if (input.value.length > 0 ) { sendButton.classList.add('black'); } else if (![... sendButton.classList].includes("disabled")){ sendButton.classList.remove('black'); } }, 10); } } }); mainButton.addEventListener("click", () => { hideMainButtonAndTooltip(); if (firstclick) { if (!LOGGED_IN) { input.setAttribute("disabled", ""); chatFooter.classList.add("cursor-not-allowed"); chatFooter.classList.add("disabled"); closeButton.classList.add('overlay'); addFormToChat(); shadowRoot .getElementById("userForm") .addEventListener("submit", handleFormSubmit); } else { getChatHistory(); connectToRoom(); newMessageHandler(addNewMessage, setManagerConnected); } firstclick = false; } setTimeout(() => { mainButton.style.display = "none"; console.log('mainButton was hidden'); chatContainer.classList.remove("chat-hidden"); console.log('chatContainer made visible'); chatContainer.classList.add("chat-open"); console.log('chatContainer opening animation was added'); tooltipContainer.style.setProperty("opacity", "0"); }, 300); input.focus(); const chatBox = shadowRoot.querySelector(".messages-container"); chatBox.scrollTop = chatBox.scrollHeight; }); }); mainButton.addEventListener("mouseenter", () => { tooltipContainer.classList.add("fade-in"); tooltipContainer.classList.remove("fade-out"); tooltipContainer.style.setProperty("opacity", "1"); tooltipContainer.style.setProperty("transform", "translateY(0)"); }); mainButton.addEventListener("mouseleave", () => { tooltipContainer.classList.remove("fade-in"); tooltipContainer.classList.add("fade-out"); tooltipContainer.style.setProperty("transform", "translateY(300%)"); tooltipContainer.style.setProperty("opacity", "0"); });