UNPKG

@claudio.giuliano/fm-chatbot-client

Version:

A chat widget to deploy virtual assistants made with Rasa on any website

1,076 lines (910 loc) 40.3 kB
/*jshint esversion: 6 */ /* global console*/ function FmChatbot() { "use strict"; let _this = this; const UID = new IDGenerator().generate(); const DEFAULT_FONT_FAMILY = "\"Titillium Web\", \"Geneva\", \"Tahoma\", sans-serif, serif";//"Arial, Helvetica, sans-serif"; const DEFAULT_PAY_LOAD = "/start"; let apiUrl = null; let selector = "fm-chatbot"; /** * Gets the browser's name and version * @constructor */ function Browser() { this.get = function getBrowser() { let ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || []; console.debug("user agent", ua); if (/trident/i.test(M[1])) { tem = /\brv[ :]+(\d+)/g.exec(ua) || []; return {name: 'IE', version: (tem[1] || '')}; } if (M[1] === 'Chrome') { tem = ua.match(/\bOPR|Edge\/(\d+)/); if (tem !== null) { return {name: 'Opera', version: tem[1]}; } } M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; if ((tem = ua.match(/version\/(\d+)/i)) !== null) { M.splice(1, 1, tem[1]); } return { name: M[0], version: parseInt(M[1]) }; }; } const BROWSER = new Browser().get(); /** * Creates the chatbot widget * @param args */ this.create = (args) => { console.info("creating the chatbot widget...", args); console.info("browser", BROWSER); //todo polling to check the connection // selector contains the chatbot in the host page if (args.hasOwnProperty("selector")) { selector = args.selector; } if (args.hasOwnProperty("apiUrl")) { try { apiUrl = new URL(args.apiUrl); } catch (e) { console.error(e); return; } } else { console.warn("no apiURL"); } document.addEventListener("DOMContentLoaded", function () { // create and show the chatbot widget init(args); // change the widget size based on media type media(args); //todo load/save the conversations? if no messages saved call the welcome // welcome the user, or send a notification if the service is not available // to in this case che chatbot could be closed welcome(args); }); // change position based on the page scroll document.addEventListener("scroll", function () { console.info("scrolling..."); let chatbotContainer = document.getElementById(selector); //let chatbotOpenBtn = document.getElementById("fm-chatbot-open-btn"); let viewportSize = getViewportSize(); console.debug("scrolling...", document.documentElement.scrollTop); if (document.documentElement.scrollTop > 0) { // if (viewportSize.width < 768) { //chatbotOpenBtn.style.marginBottom = "48px"; //chatbotOpenBtn.style.marginBottom = "0"; chatbotContainer.style.bottom = "64px"; chatbotContainer.style.right = "16px"; } else if (viewportSize.width < 1200) { //chatbotOpenBtn.style.marginBottom = "64px"; //chatbotOpenBtn.style.marginBottom = "0"; chatbotContainer.style.bottom = "88px"; chatbotContainer.style.right = "16px"; } else { //chatbotOpenBtn.style.marginBottom = "64px"; //chatbotOpenBtn.style.marginBottom = "0"; chatbotContainer.style.bottom = "104px"; chatbotContainer.style.right = "32px"; } } else { //chatbotOpenBtn.style.marginBottom = "0"; if (viewportSize.width < 768) { chatbotContainer.style.bottom = "16px"; chatbotContainer.style.right = "16px"; } else if (viewportSize.width < 1200) { chatbotContainer.style.bottom = "16px"; chatbotContainer.style.right = "16px"; } else { chatbotContainer.style.bottom = "32px"; chatbotContainer.style.right = "32px"; } } }); }; const MAX_WIDTH = 600; const MAX_HEIGHT = 400; const MAIN_CONTENT_HEIGHT = 200; /** * @deprecated * @param args */ function setSizeAndPosition(args) { console.info("setting the chatbot size and position...", args); let viewportSize = getViewportSize(); setOpenButtonSizeAndPosition(viewportSize); setGridSizeAndPosition(viewportSize); } /** * @deprecated * @param viewportSize */ function setOpenButtonSizeAndPosition(viewportSize) { console.info("setting open button size and position...", viewportSize); let chatbotContainer = document.getElementById(selector); let chatbotOpenIcon = document.getElementById("fm-chatbot-open-btn-icon"); let chatbotCloseIcon = document.getElementById("fm-chatbot-close-btn-icon"); let chatbotOpenBtn = document.getElementById("fm-chatbot-open-btn"); // let chatbotGrid = document.getElementById("fm-chatbot-grid"); //let viewportSize = getViewportSize(); //console.debug("chatbot size", viewportSize); if (viewportSize.width > 1200) { /* the viewport is MAX_WIDTH pixels wide or less */ console.debug(String.format("the viewport is {0} pixels wider than", 1200), viewportSize); chatbotContainer.style.margin = "32px"; chatbotOpenBtn.style.width = "56px"; chatbotOpenBtn.style.height = "56px"; chatbotOpenIcon.style.fontSize = "32px"; chatbotCloseIcon.style.fontSize = "32px"; } else if (viewportSize.width <= 1200 && viewportSize.width > 768) { /* the viewport is more than than MAX_WIDTH pixels wide */ console.debug(String.format("the viewport is more than {0} and less than {1} pixels wide", 768, 1200), viewportSize); chatbotContainer.style.margin = "16px"; chatbotOpenBtn.style.width = "56px"; chatbotOpenBtn.style.height = "56px"; chatbotOpenIcon.style.fontSize = "32px"; chatbotCloseIcon.style.fontSize = "32px"; //chatbotGrid.style.gridTemplateColumns = MAX_WIDTH + "px"; } else { console.debug(String.format("the viewport is less than {0} pixels wide", 768), viewportSize); chatbotContainer.style.margin = "16px"; chatbotOpenBtn.style.width = "40px"; chatbotOpenBtn.style.height = "40px"; chatbotOpenIcon.style.fontSize = "16px"; chatbotCloseIcon.style.fontSize = "16px"; } } /** * @deprecated * @param viewportSize */ function setGridSizeAndPosition(viewportSize) { console.info("setting chatbot grid size and position...", viewportSize); //let viewportSize = getViewportSize(); //console.debug("chatbot size", viewportSize); let chatbotGrid = document.getElementById("fm-chatbot-grid"); let chatbotHeader = document.getElementById('fm-chatbot-header'); let chatbotFooter = document.getElementById('fm-chatbot-footer'); if (viewportSize.width > (MAX_WIDTH + 32)) { chatbotGrid.style.gridTemplateColumns = MAX_WIDTH + "px"; } else { chatbotGrid.style.gridTemplateColumns = "auto"; } if (viewportSize.height < MAX_HEIGHT) { console.debug(String.format("the viewport is {0} pixels high or less", MAX_HEIGHT), viewportSize); chatbotGrid.style.gridTemplateRows = "auto " + MAIN_CONTENT_HEIGHT + "px auto"; } else { console.debug(String.format("the viewport is higher than {0} pixels", MAX_HEIGHT), viewportSize); let h = MAX_HEIGHT - chatbotHeader.offsetHeight - chatbotFooter.offsetHeight; chatbotGrid.style.gridTemplateRows = "auto " + h + "px auto"; } } /** * Sets the chatbot widget size based on the media type. * @param args */ function media(args) { console.info("changing the chatbot widget size..."); let maxWidthQuery1 = window.matchMedia("(min-width: 1201px)"); let maxWidthQuery2 = window.matchMedia("(max-width: 1200px) and (min-width: 768px)"); let maxWidthQuery3 = window.matchMedia("(max-width: 767px)"); let maxWidthQuery4 = window.matchMedia("(max-width: 600px)"); //let maxHeightQuery = window.matchMedia("screen and (max-height: " + MAX_HEIGHT + "px)"); fitOpenButtonToWidth1(maxWidthQuery1); fitOpenButtonToWidth2(maxWidthQuery2); fitOpenButtonToWidth3(maxWidthQuery3); fitChatbotComponentToWidth(maxWidthQuery4); // addEventListener is not supported on Safari < 14 (See https://stackoverflow.com/questions/35719526/safari-ignore-window-matchmedia) if (BROWSER.version >= 14) { maxWidthQuery1.addEventListener('change', fitOpenButtonToWidth1); maxWidthQuery2.addEventListener('change', fitOpenButtonToWidth2); maxWidthQuery3.addEventListener('change', fitOpenButtonToWidth3); maxWidthQuery4.addEventListener('change', fitChatbotComponentToWidth); //maxHeightQuery.addEventListener('change', fitToHeight); } else { maxWidthQuery1.addListener(fitOpenButtonToWidth1); maxWidthQuery2.addListener(fitOpenButtonToWidth2); maxWidthQuery3.addListener(fitOpenButtonToWidth3); maxWidthQuery4.addListener(fitChatbotComponentToWidth); //maxHeightQuery.addListener(fitToHeight); } } /** * Resizes and moves the open button when the viewport width > 1200 * @param e */ function fitOpenButtonToWidth1(e) { console.debug(String.format("the viewport is {0} pixels wider than", 1200), e); let chatbotContainer = document.getElementById(selector); let chatbotOpenIcon = document.getElementById("fm-chatbot-open-btn-icon"); let chatbotCloseIcon = document.getElementById("fm-chatbot-close-btn-icon"); let chatbotOpenBtn = document.getElementById("fm-chatbot-open-btn"); console.debug("match1:", e.matches); if (e.matches) { //chatbotContainer.style.margin = "32px"; chatbotContainer.style.bottom = "32px"; chatbotContainer.style.right = "32px"; chatbotOpenBtn.style.width = "56px"; chatbotOpenBtn.style.height = "56px"; chatbotOpenIcon.style.fontSize = "24px"; chatbotCloseIcon.style.fontSize = "24px"; } } /** * Resizes and moves the open button when the viewport width < 1200 and width > 768 * @param e */ function fitOpenButtonToWidth2(e) { console.debug(String.format("the viewport is {0} pixels wider than", 1200), e); let chatbotContainer = document.getElementById(selector); let chatbotOpenIcon = document.getElementById("fm-chatbot-open-btn-icon"); let chatbotCloseIcon = document.getElementById("fm-chatbot-close-btn-icon"); let chatbotOpenBtn = document.getElementById("fm-chatbot-open-btn"); console.debug("match2:", e.matches); if (e.matches) { //chatbotContainer.style.margin = "16px"; chatbotContainer.style.bottom = "16px"; chatbotContainer.style.right = "16px"; chatbotOpenBtn.style.width = "56px"; chatbotOpenBtn.style.height = "56px"; chatbotOpenIcon.style.fontSize = "24px"; chatbotCloseIcon.style.fontSize = "24px"; } } /** * Resizes and moves the open button when the viewport width < 768 * @param e */ function fitOpenButtonToWidth3(e) { console.debug(String.format("the viewport is {0} pixels wider than", 1200), e); let chatbotContainer = document.getElementById(selector); let chatbotOpenIcon = document.getElementById("fm-chatbot-open-btn-icon"); let chatbotCloseIcon = document.getElementById("fm-chatbot-close-btn-icon"); let chatbotOpenBtn = document.getElementById("fm-chatbot-open-btn"); console.debug("match3:", e.matches); if (e.matches) { //chatbotContainer.style.margin = "16px"; chatbotContainer.style.bottom = "16px"; chatbotContainer.style.right = "16px"; chatbotOpenBtn.style.width = "40px"; chatbotOpenBtn.style.height = "40px"; chatbotOpenIcon.style.fontSize = "16px"; chatbotCloseIcon.style.fontSize = "16px"; } } /** * Resizes the chatbot when the viewport width < MAX_WIDTH * @param e */ function fitChatbotComponentToWidth(e) { let chatbotGrid = document.getElementById("fm-chatbot-grid"); let chatbotContainer= document.getElementById("fm-chatbot-container"); if (e.matches) { chatbotContainer.style.marginLeft="16px"; chatbotGrid.style.gridTemplateColumns = "auto"; } else { chatbotGrid.style.gridTemplateColumns = MAX_WIDTH - 32 + "px"; } } /** * Issues the intent taken from the initPayload or DEFAULT_PAY_LOAD to get the welcome message. * @param args */ function welcome(args) { console.info("getting the welcome message..."); let initPayload = DEFAULT_PAY_LOAD; if (args.hasOwnProperty("initPayload") && args.initPayload.length) { initPayload = args.initPayload; } call(initPayload, args); } /** * Deletes the specified cookie. * * @param name * @param path * @param domain */ function deleteCookie(name, path, domain) { if (getCookie(name)) { document.cookie = name + "=" + ((path) ? ";path=" + path : "") + ((domain) ? ";domain=" + domain : "") + ";expires=Thu, 01 Jan 1970 00:00:01 GMT"; } console.debug("chatbot cookies", document.cookie); } /** * Sets the specified cookie. * * @param name * @param value * @param days */ function setCookie(name, value, days) { let d = new Date(); d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); let expires = "expires=" + d.toUTCString(); document.cookie = name + "=" + value + ";" + expires + ";path=/"; console.debug("set the chatbot cookies", document.cookie); } /** * Gets the specified cookie. * * @param name * @returns {string} */ function getCookie(name) { console.debug("getting the chatbot cookies", document.cookie); name += "="; let decodedCookie = decodeURIComponent(document.cookie); let ca = decodedCookie.split(';'); for (let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === ' ') { c = c.substring(1); } if (c.indexOf(name) === 0) { return c.substring(name.length, c.length); } } return ""; } /** * Inits the chatbot widget. * @param args */ function init(args) { console.info("initializing the chatbot widget..."); addChatbotStyle(args); let chatbotContainer = document.getElementById(selector); //chatbotContainer.style.margin = "16px"; chatbotContainer.style.zIndex = "3"; chatbotContainer.style.fontFamily = DEFAULT_FONT_FAMILY; //chatbotContainer.style.position = "relative"; //todo remove? chatbotContainer.style.position = "fixed"; chatbotContainer.style.bottom = "0"; chatbotContainer.style.right = "0"; let chatbotButton = addAndGetChatbotButton(args); let chatbotComponent = addAndGetChatbotComponent(args); chatbotContainer.appendChild(chatbotButton); chatbotContainer.appendChild(chatbotComponent); //setSizeAndPosition(args); //window.addEventListener('resize', setSizeAndPosition); // set (not) visible when created let open = args.hasOwnProperty("open") ? args.open : true; console.debug("open the chatbot?", open); let isVisible = getCookie("is_visible"); console.debug("is the chatbot visible?", isVisible); if (isVisible !== null && isVisible.length > 0) { // cookie isVisible found if (isVisible === "true") { console.debug("A", open, isVisible); showChatbotComponent(args); } else { console.debug("B", open, isVisible); hideChatbotComponent(args); } } else { // no cookie isVisible if (open) { console.debug("C", open, isVisible); showChatbotComponent(args); } else { console.debug("D", open, isVisible); hideChatbotComponent(args); } } let botInput = document.getElementById("fm-chatbot-input"); botInput.addEventListener("keyup", function (event) { console.trace(event); if (event.code === "Enter") { // Cancel the default action, if needed event.preventDefault(); submitMessage(args); } updateSubmitMessage(); }); let closeBtn = document.getElementById("fm-chatbot-close-btn"); closeBtn.onclick = function () { hideChatbotComponent(args); setCookie("is_visible", false, 90); }; let openBtn = document.getElementById("fm-chatbot-open-btn"); openBtn.onclick = function () { showChatbotComponent(args); setCookie("is_visible", true, 90); }; let sendBtn = document.getElementById("fm-chatbot-send-btn"); sendBtn.onclick = function () { submitMessage(args); updateSubmitMessage(); }; } /** * Shows/hides the send button. */ function updateSubmitMessage() { let botInput = document.getElementById("fm-chatbot-input"); let sendBtn = document.getElementById("fm-chatbot-send-btn"); //console.debug("input",botInput.value); if (botInput.value.length === 0) { sendBtn.style.visibility = "hidden"; sendBtn.setAttribute("aria-hidden", "true"); } else { sendBtn.style.visibility = "visible"; sendBtn.setAttribute("aria-hidden", "false"); } } /** * Sets dynamically the chatbot style. * @param args */ function addChatbotStyle(args) { console.info("adding chatbot style...", args); //Google fonts let link = document.createElement('link'); link.rel = 'stylesheet'; link.href = "https://fonts.googleapis.com/icon?family=Material+Icons"; let head = document.getElementsByTagName('head')[0]; head.appendChild(link); //.sr-only // see https://a11y-guidelines.orange.com/en/web/components-examples/chatbot/ let srOnlyStyle = document.createElement('style'); //style.type = 'text/css'; srOnlyStyle.innerHTML = '.sr-only { position: absolute;\n' + ' width: 1px;\n' + ' height: 1px;\n' + ' padding: 0;\n' + ' margin: -1px;\n' + ' overflow: hidden;\n' + ' clip: rect(0,0,0,0);\n' + ' border: 0; }'; head.appendChild(srOnlyStyle); } /** * Returns the viewport size. * @returns {{width, height}} */ function getViewportSize() { let e = window; let a = 'inner'; if (!('innerWidth' in window)) { a = 'client'; e = document.documentElement || document.body; } return {width: e[a + 'Width'], height: e[a + 'Height']}; } /** * Creates and returns the chatbot component. * @param args * @returns {HTMLDivElement} */ function addAndGetChatbotComponent(args) { console.info("adding the chatbot component...", args); let chatbotComponent = document.createElement('div'); chatbotComponent.id = "fm-chatbot-container"; //todo check if it works //chatbotComponent.style.zIndex = "3"; chatbotComponent.style.background = "transparent"; chatbotComponent.style.borderRadius = "4px 4px 4px 4px"; chatbotComponent.style.boxShadow = "0 0 5px 2px rgba(0,0,0,.15)"; chatbotComponent.setAttribute("role", "region"); chatbotComponent.setAttribute("aria-labelledby", "fm-chatbot-title"); let chatbotGrid = document.createElement('div'); chatbotGrid.id = "fm-chatbot-grid"; chatbotGrid.style.display = "grid"; chatbotGrid.style.borderStyle = "none"; chatbotGrid.style.gridTemplateColumns = MAX_WIDTH + "px"; chatbotGrid.style.gridTemplateRows = "auto " + MAIN_CONTENT_HEIGHT + "px auto"; chatbotGrid.style.background = "transparent"; let chatbotHeader = document.createElement('div'); chatbotHeader.id = "fm-chatbot-header"; chatbotHeader.style.background = "#3980E8"; chatbotHeader.style.color = "white"; chatbotHeader.style.borderRadius = "4px 4px 0 0"; chatbotHeader.style.padding = "10px"; chatbotHeader.style.display = "grid"; chatbotHeader.style.borderStyle = "none"; chatbotHeader.style.gridTemplateColumns = "90% 10%"; chatbotHeader.style.gridTemplateRows = "auto auto"; let chatbotCloseButton = document.createElement('button'); chatbotCloseButton.id = "fm-chatbot-close-btn"; chatbotCloseButton.type = "button"; // tooltips creates conflicts // if (args.hasOwnProperty("closeTooltip") && args.closeTooltip.length > 0) { // chatbotCloseButton.title = args.closeTooltip; // //Used by Bootstrap // chatbotCloseButton.setAttribute("data-toggle", "tooltip"); // } chatbotCloseButton.style.border = "none"; chatbotCloseButton.style.color = "white"; chatbotCloseButton.style.background = "transparent"; chatbotCloseButton.style.position = "relative"; chatbotCloseButton.style.cursor = "pointer"; //todo make smaller the font for mobile? chatbotCloseButton.innerHTML = "<span id=\"fm-chatbot-close-btn-icon\" class=\"material-icons\" style=\"font-size:24px;\">close</span>"; if (args.hasOwnProperty("closeAriaLabel") && args.closeAriaLabel.length > 0) { chatbotCloseButton.setAttribute("aria-label", args.closeAriaLabel); } else { chatbotCloseButton.setAttribute("aria-label", "Close the chatbot"); } let chatbotTitle = document.createElement('h1'); chatbotTitle.id = "fm-chatbot-title"; chatbotTitle.style.margin = "0"; chatbotTitle.style.padding = "0"; chatbotTitle.style.fontSize = "1.1em"; chatbotTitle.style.fontWeight = "bolder"; if (args.hasOwnProperty("title")) { chatbotTitle.innerHTML = "<span>" + args.title + "</span>"; } let chatbotSubtitle = document.createElement('h2'); chatbotSubtitle.id = "fm-chatbot-subtitle"; chatbotSubtitle.style.margin = "0"; chatbotSubtitle.style.padding = "0"; chatbotSubtitle.style.fontSize = "0.8em"; chatbotSubtitle.style.fontWeight = "lighter"; if (args.hasOwnProperty("subtitle")) { chatbotSubtitle.innerHTML = "<span>" + args.subtitle + "</span>"; } chatbotSubtitle.style.fontWeight = "lighter"; chatbotSubtitle.style.fontSize = "0.8em"; chatbotSubtitle.style.whiteSpace = "nowrap"; chatbotSubtitle.style.overflow = "hidden"; chatbotSubtitle.style.textOverflow = "ellipsis"; chatbotHeader.appendChild(chatbotTitle); chatbotHeader.appendChild(chatbotCloseButton); // chatbotHeader.appendChild(chatbotTitle); chatbotHeader.appendChild(chatbotSubtitle); let chatbotMain = document.createElement('div'); chatbotMain.id = "fm-chatbot-main-container"; chatbotMain.className = "fm-chatbot-main"; chatbotMain.style.background = "white"; chatbotMain.style.overflow = "scroll"; chatbotMain.style.padding = "10px"; chatbotMain.setAttribute("tabindex", "0"); chatbotMain.setAttribute("aria-live", "polite"); let chatbotFooter = document.createElement('div'); chatbotFooter.id = "fm-chatbot-footer"; chatbotFooter.style.background = "#F5F5F5"; chatbotFooter.style.color = "black"; chatbotFooter.style.padding = "10px"; chatbotFooter.style.borderRadius = "0 0 4px 4px"; let chatbotInput = document.createElement('textarea'); chatbotInput.id = "fm-chatbot-input"; chatbotInput.autocomplete = "off"; chatbotInput.style.width = "80%"; chatbotInput.style.height = "100%"; if (args.hasOwnProperty("inputTextFieldHint")) { //chatbotInput.placeholder = "Fai una domanda"; chatbotInput.placeholder = args.inputTextFieldHint; } chatbotInput.style.background = "#F5F5F5"; chatbotInput.style.border = "none"; chatbotInput.style.cursor = "pointer"; chatbotInput.style.color = "black"; chatbotInput.style.fontSize = "0.9em"; chatbotInput.style.fontStyle = "normal"; chatbotInput.style.position = "relative"; chatbotInput.style.float = "left"; chatbotInput.style.textAlign = "left"; chatbotInput.style.verticalAlign = "auto"; chatbotInput.style.resize = "none"; chatbotInput.style.overflow = "hidden"; chatbotInput.style.outline = "none"; chatbotInput.style.fontFamily = DEFAULT_FONT_FAMILY; if (args.hasOwnProperty("inputAriaLabel") && args.inputAriaLabel.length > 0) { chatbotInput.setAttribute("aria-label", args.inputAriaLabel); } else { chatbotInput.setAttribute("aria-label", "Input message"); } chatbotFooter.appendChild(chatbotInput); let chatbotSendButton = document.createElement('button'); chatbotSendButton.id = "fm-chatbot-send-btn"; chatbotSendButton.style.visibility = "hidden"; chatbotSendButton.type = "button"; chatbotSendButton.style.border = "none"; chatbotSendButton.style.color = "#3980E8"; chatbotSendButton.style.background = "transparent"; chatbotSendButton.style.position = "relative"; chatbotSendButton.style.float = "right"; chatbotSendButton.style.textAlign = "center"; chatbotSendButton.style.verticalAlign = "center"; chatbotSendButton.style.cursor = "pointer"; //todo make smaller the font for mobile? chatbotSendButton.innerHTML = "<span id=\"fm-chatbot-send-btn-icon\" class=\"material-icons\" style=\"font-size:24px;\">send</span>"; //chatbotSendButton.setAttribute("aria-label", "Invia il messaggio"); if (args.hasOwnProperty("sendAriaLabel") && args.sendAriaLabel.length > 0) { chatbotSendButton.setAttribute("aria-label", args.sendAriaLabel); } else { chatbotSendButton.setAttribute("aria-label", "Send message"); } chatbotFooter.appendChild(chatbotSendButton); chatbotGrid.appendChild(chatbotHeader); chatbotGrid.appendChild(chatbotMain); chatbotGrid.appendChild(chatbotFooter); chatbotComponent.appendChild(chatbotGrid); return chatbotComponent; } /** * Creates and returns the chatbot button. * @param args * @returns {HTMLDivElement} */ function addAndGetChatbotButton(args) { console.info("adding chatbot open button...", args); let chatbotSwitchContainer = document.createElement('div'); chatbotSwitchContainer.id = "fm-chatbot-button-container"; let chatbotOpenButton = document.createElement('button'); chatbotOpenButton.id = "fm-chatbot-open-btn"; chatbotOpenButton.type = "button"; // tooltip creates conflicts // if (args.hasOwnProperty("tooltip") && args.tooltip.length > 0) { // chatbotOpenButton.title = args.tooltip; // //Used by Bootstrap // chatbotOpenButton.setAttribute("data-toggle", "tooltip"); // } chatbotOpenButton.style.border = "none"; chatbotOpenButton.style.color = "white"; chatbotOpenButton.style.background = "#3980E8"; chatbotOpenButton.style.borderRadius = "50%"; chatbotOpenButton.style.width = "40px"; chatbotOpenButton.style.height = "40px"; chatbotOpenButton.style.fontSize = "16px"; chatbotOpenButton.style.fontSize = "16px"; chatbotOpenButton.style.cursor = "pointer"; chatbotOpenButton.style.boxShadow = "0 0 5px 2px rgba(0,0,0,.15)"; if (args.hasOwnProperty("openAriaLabel") && args.openAriaLabel.length > 0) { chatbotOpenButton.setAttribute("aria-label", args.openAriaLabel); } else { chatbotOpenButton.setAttribute("aria-label", "Open the chatbot"); } //chatbotOpenButton.innerHTML = "<span id=\"fm-chatbot-open-btn-icon\" class=\"material-icons\" style=\"font-size:16px;\">smart_toy</span>"; chatbotOpenButton.innerHTML = "<span id=\"fm-chatbot-open-btn-icon\" class=\"material-icons\" style=\"font-size:16px;\">chat_bubble</span>"; chatbotSwitchContainer.appendChild(chatbotOpenButton); return chatbotSwitchContainer; } if (!String.format) { String.format = function (format) { let args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] !== 'undefined' ? args[number] : match; }); }; } /** * Shows the chatbot component and hides the chatbot button. * @param args */ function showChatbotComponent(args) { console.info("showing the chatbot component..."); let chatbotContainer = document.getElementById("fm-chatbot-container"); let switchContainer = document.getElementById("fm-chatbot-button-container"); chatbotContainer.style.display = "block"; chatbotContainer.setAttribute("aria-hidden", "false"); switchContainer.style.display = "none"; switchContainer.setAttribute("aria-hidden", "true"); if (args.hasOwnProperty("onWidgetEvent")) { // callback onChatOpen() args.onWidgetEvent.onChatOpen(); } } /** * Hides the chatbot component and shows the chatbot button. * @param args */ function hideChatbotComponent(args) { console.info("hiding the chatbot component..."); let chatbotContainer = document.getElementById("fm-chatbot-container"); let switchContainer = document.getElementById("fm-chatbot-button-container"); chatbotContainer.style.display = "none"; chatbotContainer.setAttribute("aria-hidden", "true"); switchContainer.style.display = "block"; switchContainer.setAttribute("aria-hidden", "false"); if (args.hasOwnProperty("onWidgetEvent")) { //callback onChatClose() args.onWidgetEvent.onChatClose(); } } /** * Shows and sends the user message. * @param args */ function submitMessage(args) { let input = document.getElementById("fm-chatbot-input"); console.debug("sending input", input.value); if (input.value) { let message = input.value.trim(); if (message.length > 0) { addUserInput(message); call(message, args); } input.value = ""; } } /** * Adds the user message to the chat thread. * @param value */ function addUserInput(value) { console.debug("showing user input", value); let botMain = document.getElementById("fm-chatbot-main-container"); let div = document.createElement('div'); div.className = "user-input"; div.style.background = "#3980E8"; div.style.color = "white"; div.style.borderRadius = "4px 4px 4px 4px"; div.style.margin = "10px 10px 10px 40px"; div.style.padding = "10px"; div.style.wordWrap = "break-word"; div.style.fontSize = "0.9em"; //div.innerHTML = "<span>" + value + "</span>"; div.innerHTML = "<span class=\"sr-only\">Io dico:</span>" + value; botMain.appendChild(div); botMain.scrollTop = botMain.scrollHeight; } /** * Adds the bot message to the chat thread. * @param value */ function addBotOutput(value, args) { console.debug("showing bot output", value); let botMain = document.getElementById("fm-chatbot-main-container"); let div = document.createElement('div'); div.style.background = "#F5F5F5"; div.style.color = "black"; div.style.borderRadius = "4px 4px 4px 4px"; div.style.margin = "10px 40px 10px 10px"; div.style.padding = "10px"; div.style.wordWrap = "break-word"; div.style.fontSize = "0.9em"; div.className = "bot-output"; if (args.hasOwnProperty("botSaid") && args.botSaid.length > 0) { div.innerHTML = "<span class=\"sr-only\">" + args.botSaid + "</span>" + value; } else { div.innerHTML = "<span class=\"sr-only\">Covibot dice:</span>" + value; } botMain.appendChild(div); //botMain.scrollTop = botMain.scrollHeight; } /** * Generates a uniq session id. */ function IDGenerator() { this.length = 8; this.timestamp = +new Date(); let _getRandomInt = function (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }; this.generate = function () { console.info("getting a new SID..."); let ts = this.timestamp.toString(); let parts = ts.split("").reverse(); let id = "0ES6V126-"; for (let i = 0; i < this.length; ++i) { let index = _getRandomInt(0, parts.length - 1); id += parts[index]; } return id; }; } /** * Calls the Rasa's rest endpoint. * For example, using curl: * time curl -s -w "\n" -XPOST https://rasa5007.pokedem.com/webhooks/rest/webhook -H "Content-Type: application/json" -H "Accept-Charset: UTF-8" -d '{"sender": "user_240", "message": "scarica moduli per la prima dose"}'|jq . * @param value * @param args */ function call(value, args) { //replace the newline in the question let question = value.replace("\n", " "); const data = {sender: UID, message: question}; console.debug("calling the chatbot rest endpoint...", data); let delay = (typeof args.customMessageDelay === 'number') ? args.customMessageDelay : 0; // addWaitAnimation(200); //console.debug("url", apiUrl); fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept-Charset': 'UTF-8' }, body: JSON.stringify(data), }) .then(response => response.json()) .then(data => { console.debug('chatbot uttered', data); //todo setTimeout(() => { if (data && data.length > 0) { removeWaitAnimation(); for (let i = 0; i < data.length; i++) { let output = replaceMarkdown(data[i].text); console.trace("bot said", i, output); addBotOutput(output, args); if (args.hasOwnProperty("onApiEvent")) { //callback bot_uttered() args.onApiEvent.bot_uttered(); } } } }, delay); }) .catch((error) => { console.error('Error:', error); //todo according to the error the bot could be hidden removeWaitAnimation(); if (args.hasOwnProperty("hideWhenServiceNotAvailable") && args.hideWhenServiceNotAvailable) { console.debug("hiding the chatbot components..."); hideChatbotComponent(args); } else { if (args.hasOwnProperty("serviceNotAvailableMessage") && args.serviceNotAvailableMessage.length > 0) { addBotOutput(args.serviceNotAvailableMessage, args); } } //todo try to connect again? or wait for a refresh? }).finally(() => { console.debug('chatbot call ended'); // removeWaitAnimation(); }); } /** * Starts the waiting state. * @param speed */ function addWaitAnimation(speed) { console.debug("adding wait animation...", speed); speed = (typeof speed !== 'undefined') ? speed : 200; let botMain = document.getElementById("fm-chatbot-main-container"); let div = document.createElement('div'); div.className = "fm-chatbot-wait-animation"; div.textContent = "Ooo"; div.style.background = "#F5F5F5"; div.style.color = "black"; div.style.borderRadius = "10px 10px 10px 10px"; div.style.margin = "10px 40px 10px 10px"; div.style.padding = "10px"; div.style.wordWrap = "break-word"; div.style.fontSize = "0.9em"; botMain.appendChild(div); botMain.scrollTop = botMain.scrollHeight; const textNode = div.childNodes[0]; // assuming no other children let text = textNode.data; setInterval(() => { text = text[text.length - 1] + text.substring(0, text.length - 1); textNode.data = text; }, speed); } const italicRegex = /\*([^*]+)\*/g; const strongRegex = /\*\*([^*]+)\*\*/g; const aRegex = /\[([^\]]+)]\(([^)]+)\)/g; //todo add a \n regex? //todo add bullet list? /** * Replaces markdown with html tags. * Currently, link and font style are supported. * @param input */ function replaceMarkdown(input) { return input.replace(strongRegex, '<strong class="fm-chatbot-answer-strong">$1</strong>').replace(aRegex, '<a href="$2" style="color: #0066cc !important;" class="fm-chatbot-answer-link">$1</a>').replace(italicRegex, '<em class="fm-chatbot-answer-em">$1</em>'); } /** * Stops the waiting status */ function removeWaitAnimation() { console.debug("removing wait animation..."); let waitAnimations = document.getElementsByClassName("fm-chatbot-wait-animation"); for (let i = 0; i < waitAnimations.length; i++) { waitAnimations[i].remove(); } } }