@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
JavaScript
/*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();
}
}
}