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