UNPKG

@moveo-ai/web-widget

Version:

Client side library to load the moveo chat widget and connect it with your agent

183 lines (171 loc) 5.62 kB
import $ from 'jquery'; import { MESSAGE_LIST, DEFAULT_PAUSE, MESSAGE_INPUT } from '../constants'; import { isMobile } from '../utils'; import { sendNewMessage } from './sendNewMessage'; import { moveoFileMessage, moveoImageMessage, moveoVideoMessage, moveoAudioMessage, moveoUrlMessage, moveoTyping, moveoTextMessage, moveoCarouselMessage, } from '../templates/moveoResponses'; import * as store from '../store'; const onOptionClick = (e) => { if (isMobile()) { $(MESSAGE_INPUT).attr('contenteditable', false); } if (e.target.tagName.toLowerCase() === 'li') { $('.moveo__messages li.moveo__options:last').hide(); } sendNewMessage(e.target.getAttribute('data-text')); }; export const addMoveoError = (error, avatar) => { const messagesContainer = $(MESSAGE_LIST); messagesContainer.append(moveoTextMessage(error.message || error, avatar)); }; /** * Handles click event and hover animation for options and carousel buttons * @param {string} background_color Color of theme * @param {string} accent_color Color of text * @param {string} className The class of the button/option */ const handleOptionsClick = ({ background_color, accent_color, className }) => { const userOptions = document.querySelectorAll(className); userOptions.forEach((option) => { if (option.tagName.toLowerCase() !== 'a') { option.addEventListener('click', onOptionClick); } option.addEventListener('mouseover', (e) => { e.target.style.backgroundColor = `${background_color}`; e.target.style.color = `${accent_color}`; }); option.addEventListener('mouseout', (e) => { e.target.style.color = `${background_color}`; e.target.style.backgroundColor = `${accent_color}`; }); }); }; /** * Returns the remaining pause duration and renders the typing indicator. * If we check the duration for the first response, we need to adjust the default * time with the time that has elapsed since the user sent his message. If the time * that has elaped is more that the Default time, then we return 0 seconds of wait time. * @param {bool} is_init_message if the message is the first one * */ export const checkPauseDurationAndRender = (is_init_message) => { const messagesContainer = $(MESSAGE_LIST); if (!$('#typing').length) { messagesContainer.append(moveoTyping()); return DEFAULT_PAUSE; } if (is_init_message) { const time_elapsed = Date.now() - store.getUserMessageTime(); return Math.max(DEFAULT_PAUSE - time_elapsed, 0); } return 0; }; const renderPauseEffect = (time, show_typing, is_init_message, resolve) => { const messagesContainer = $(MESSAGE_LIST); if (!$('#typing').length && show_typing) { messagesContainer.append(moveoTyping()); } let remaining_time = time; if (is_init_message) { const time_elapsed = Date.now() - store.getUserMessageTime(); remaining_time = Math.max(time - time_elapsed, 0); } setTimeout(() => resolve(), remaining_time); }; /** * Renders moveo responses. * @param {array} responses the responses to add * @param {string} avatar Image of Moveo * @param {string} background_color Color of theme * @param {string} accent_color Color of text * * */ const renderResponses = ( resp, avatar, background_color, accent_color, is_init_message, ) => { if (resp.type === 'pause') { return new Promise((resolve) => { renderPauseEffect( resp.duration * 1000, resp.show_typing, is_init_message, resolve, ); }); } return new Promise((resolve) => { const messagesContainer = $(MESSAGE_LIST); const pauseTime = checkPauseDurationAndRender(is_init_message); setTimeout(() => { $('.moveo__messages li:last').hide().fadeIn(250); $('#typing').fadeOut(250).remove(); if (resp.type === 'text' || resp.type === 'disambiguation') { messagesContainer.append( moveoTextMessage(resp, avatar, background_color, accent_color), ); if (resp.options) { handleOptionsClick({ background_color, accent_color, className: '.messageOption', }); } } else if (resp.type === 'image') { messagesContainer.append(moveoImageMessage(resp, avatar)); } else if (resp.type === 'video') { messagesContainer.append(moveoVideoMessage(resp, avatar)); } else if (resp.type === 'audio') { messagesContainer.append(moveoAudioMessage(resp, avatar)); } else if (resp.type === 'url') { messagesContainer.append(moveoUrlMessage(resp, avatar)); } else if (resp.type === 'file') { messagesContainer.append(moveoFileMessage(resp, avatar)); } else if (resp.type === 'carousel') { messagesContainer.append(moveoCarouselMessage(resp, avatar)); handleOptionsClick({ background_color, accent_color, className: '.carousel-card__button', }); } else { console.warn(`Message ${resp.type} not supported`); } resolve(); }, pauseTime); }); }; export async function addMoveoResponses( responses, avatar, background_color, accent_color, ) { const messagesContainer = $(MESSAGE_LIST); for (let i = 0; i < responses.length; i += 1) { const resp = responses[i]; // eslint-disable-next-line no-await-in-loop await renderResponses( resp, avatar, background_color, accent_color, i === 0, ); messagesContainer .finish() .animate({ scrollTop: messagesContainer.prop('scrollHeight') }, 250); } }