(function($, w) { 'use strict'; const updateMetadata = (chat, key, val) => { return new Promise((resolve, reject) => { VBOChatAjax.do( chat.data.environment.url, { task: 'chat.update_metadata', id_context: chat.data.environment.context.id, context: chat.data.environment.context.alias, key: key, val: val, }, (resp) => { resolve(); }, (error) => { reject(error); } ); }); } const summarizeConversation = (chat) => { return new Promise((resolve, reject) => { // convert messages into an acceptable format const messages = chat.data.environment.messages.map((msg) => { return { role: msg.id_sender == -1 ? 'user' : 'assistant', content: msg.message, name: msg.sender_name, }; }); VBOChatAjax.do( chat.data.environment.url, { task: 'ai.summarize', messages: messages.reverse(), }, (resp) => { resolve(resp.result); }, (error) => { reject(error); } ); }); } const sendMessageTemplate = (chat, configId) => { return new Promise((resolve, reject) => { // make the request VBOCore.doAjax( chat.data.environment.url, { task: 'chat.send_template', session_id: chat.data.environment.context.id, config_id: configId, }, (resp) => { resolve(resp); }, (error) => { reject(error); } ); }); } const showContextToast = (chat, panel) => { // identify the target that should hold the info panel const targetEl = $(chat.data.element.conversation).closest('.chat-messages-panel'); let toast = targetEl.find('.chat-session-toast'); if (!toast.length) { // create toast template toast = $('
\n'+ '
\n'+ '
\n'+ '
\n'+ '
'); // append toast HTML to target only once targetEl.append(toast); } // update toast panel content toast.find('.chat-session-toast-message-content').html(panel); let dismissHandler = toast.find('[data-role="popup.dismiss"]'); // disable click event toast.off('click'); $(document).off('click', '.chat-session-toast [data-role="popup.dismiss"]'); if (!dismissHandler.length) { // dismiss when clicking inside the toast toast.css('cursor', 'pointer').on('click', () => { toast.removeClass('slide-in'); }); } // dispose when clicked observer element $(document).on('click', '.chat-session-toast [data-role="popup.dismiss"]', () => { toast.removeClass('slide-in'); }); setTimeout(() => { toast.addClass('slide-in'); }, 256); return toast; } const calcNights = (checkin, checkout) => { const start = new Date(checkin + 'T00:00:00Z'); const end = new Date(checkout + 'T00:00:00Z'); return Math.floor((end - start) / (1000 * 60 * 60 * 24)); } /********************* * BAN/UNBAN SESSION * *********************/ /** * Ban/unban session button text handler. */ $(w).on('chat.session.ban.text', async (event) => { const [root, parentEvent, button, chat] = event.args; event.displayText = Joomla.JText._(chat.data.environment.context.metadata.banned ? 'VBO_CHAT_UNBAN_SESSION' : 'VBO_CHAT_BAN_SESSION'); }); /** * Ban/unban session button icon handler. */ $(w).on('chat.session.ban.icon', async (event) => { const [root, parentEvent, button, chat] = event.args; event.displayIcon = 'fas fa-' + (chat.data.environment.context.metadata.banned ? 'check-circle' : 'ban'); }); /** * Ban/unban session button action handler. */ $(w).on('chat.session.ban.action', async (event) => { const [root, parentEvent, button, chat] = event.args; const context = chat.data.environment.context; try { await updateMetadata(chat, 'banned', context.metadata.banned ? 0 : 1); context.metadata.banned = !context.metadata.banned; $(chat.data.element.conversation).parent().css('background', context.metadata.banned ? '#d003' : 'inherit'); } catch (error) { console.error(error); alert(error.responseText || error.statusText || 'Connection lost!'); } }); /************************** * STOP/RESUME AI REPLIES * **************************/ /** * Stop/resume AI session button text handler. */ $(w).on('chat.session.ai.autoreply.text', async (event) => { const [root, parentEvent, button, chat] = event.args; event.displayText = Joomla.JText._(chat.data.environment.context.metadata.use_ai ? 'VBO_CHAT_STOP_AI_SESSION' : 'VBO_CHAT_RESUME_AI_SESSION'); }); /** * Stop/resume AI session button icon handler. */ $(w).on('chat.session.ai.autoreply.icon', async (event) => { const [root, parentEvent, button, chat] = event.args; event.displayIcon = 'fas fa-' + (chat.data.environment.context.metadata.use_ai ? 'comment-slash' : 'comment'); }); /** * Stop/resume AI session button action handler. */ $(w).on('chat.session.ai.autoreply.action', async (event) => { const [root, parentEvent, button, chat] = event.args; const context = chat.data.environment.context; try { await updateMetadata(chat, 'use_ai', context.metadata.use_ai ? 0 : 1); context.metadata.use_ai = !context.metadata.use_ai; } catch (error) { console.error(error); alert(error.responseText || error.statusText || 'Connection lost!'); } }); /************************** * SUMMARIZE CONVERSATION * **************************/ /** * AI summarize conversation button disabled status handler. */ $(w).on('chat.session.ai.summarize.disabled', async (event) => { const [root, parentEvent, button, chat] = event.args; // disable in case the popup is already visible event.shouldDisable = $(chat.data.element.conversation) .closest('.chat-messages-panel') .find('.chat-session-toast') .hasClass('slide-in'); }); /** * AI summarize conversation button action handler. */ $(w).on('chat.session.ai.summarize.action', async (event) => { const [root, parentEvent, button, chat] = event.args; const popup = $('
'); const displaySummary = (summary) => { popup.html(''); popup.append( $('
') .append($('').text(Joomla.JText._('VBO_CHAT_SUMMARIZE_TITLE'))) .append('') ); popup.append($('
').html(summary)); } // use cached value (if available) if (chat.data.environment.context.aiSummary) { displaySummary(chat.data.environment.context.aiSummary); showContextToast(chat, popup); return; } popup.html('
'); const toast = showContextToast(chat, popup); // wait until the toast is fully visible setTimeout(async () => { try { if (!button.supported) { // VCM 1.9.19 required to support AI summarize service throw new Error("Update VikChannelManager to the latest version first."); } const summary = await summarizeConversation(chat); // internally cache result to prevent duplicate requests chat.data.environment.context.aiSummary = summary; displaySummary(summary); // disable dismiss on toast click toast.off('click').css('cursor', 'default'); } catch (error) { console.error(error); if (error instanceof Error) { error = {responseText: error}; } popup.addClass('error-response').text(error.responseText || error.statusText || 'Connection lost!'); } }, 600); }); /*********************** * SESSION QUOTE QUERY * ***********************/ /** * Quote session query button visibility handler. */ $(w).on('chat.session.query.quote.visible', async (event) => { const [root, parentEvent, button, chat] = event.args; event.shouldDisplay = chat.data.environment.context.metadata?.query?.checkin && chat.data.environment.context.metadata?.query?.checkout; }); /** * Quote session query button action handler. */ $(w).on('chat.session.query.quote.action', async (event) => { const [root, parentEvent, button, chat] = event.args; // open quotation maker on a blank page window.open(button.url, '_blank'); }); /********************* * SESSION SEE QUERY * *********************/ /** * See session query button visibility handler. */ $(w).on('chat.session.query.see.visible', async (event) => { const [root, parentEvent, button, chat] = event.args; event.shouldDisplay = chat.data.environment.context.metadata?.query?.checkin && chat.data.environment.context.metadata?.query?.checkout; }); /** * See session query button disabled status handler. */ $(w).on('chat.session.query.see.disabled', async (event) => { const [root, parentEvent, button, chat] = event.args; // disable in case the popup is already visible event.shouldDisable = $(chat.data.element.conversation) .closest('.chat-messages-panel') .find('.chat-session-toast') .hasClass('slide-in'); }); /** * See session query button action handler. */ $(w).on('chat.session.query.see.action', async (event) => { const [root, parentEvent, button, chat] = event.args; const query = chat.data.environment.context.metadata.query; const popup = $('
'); popup.append( $('
') .append($('').text(Joomla.JText._('VBO_CHAT_QUERY_SUMMARY_TITLE'))) .append('') ); ////////////////////////////////////////////////// const body = $('
'); ////////////////////////////////////////////////// const datesBox = $('
'); datesBox.append( $('
') .append('') .append($('').text(Joomla.JText._('VBO_CONDTEXT_RULE_STAYDATES'))) ); datesBox.append( $('
') .append($('').text(query.checkin)) .append('') .append($('').text(query.checkout)) ); const nights = calcNights(query.checkin, query.checkout); datesBox.append( $('
').text( Joomla.JText._(nights > 1 ? 'VBOSEASONCALNUMNIGHTS' : 'VBOSEASONCALNUMNIGHT').replace(/%d/, nights).toLowerCase() ) ); body.append(datesBox); ////////////////////////////////////////////////// const guestsBox = $('
'); guestsBox.append( $('
') .append('') .append($('').text(Joomla.JText._('VBO_NOTIFS_GROUP_GUESTS'))) ); guestsBox.append('
'); query.parties.forEach((party, index) => { const guestDetails = $(''); let components = [ Joomla.JText._(party.adults > 1 ? 'VBO_N_ADULTS' : 'VBO_N_ADULTS_1').replace(/%d/, party.adults) ]; if (party?.children) { components.push(Joomla.JText._(party.children > 1 ? 'VBO_N_CHILDREN' : 'VBO_N_CHILDREN_1').replace(/%d/, party.children)); } guestDetails.text(components.join(', ')); let guestBox = $('
'); guestBox.append($('
').text(Joomla.JText._('VBMAILROOMNUM') + (index + 1))); guestBox.append($('
').append(guestDetails)); if (party?.pets) { guestBox.find('.query-quest-info-right').append('
').append( $('').text((party.pets + ' ' + Joomla.JText._(party.pets > 1 ? 'VBO_PETS' : 'VBO_PET').toLowerCase())) ); } guestsBox.find('.query-guests-main').append(guestBox); }); body.append(guestsBox); ////////////////////////////////////////////////// if ((query?.rooms || []).length) { const roomsBox = $('
'); roomsBox.append( $('
') .append('') .append($('').text(Joomla.JText._('VBO_CHAT_QUERY_SUMMARY_PREF_ROOMS'))) ); roomsBox.append($('
')); query.rooms.forEach((room) => { roomsBox.find('.query-rooms-main').append( $('').text(room.name) ); }); body.append(roomsBox); } ////////////////////////////////////////////////// popup.append(body); showContextToast(chat, popup); }); /************************** * SEND WHATSAPP TEMPLATE * **************************/ /** * See session query button action handler. */ $(w).on('chat.session.messaging.sendtmpl.action', async (event) => { const [root, parentEvent, button, chat] = event.args; const popup = $('
'); popup.append( $('
') .append($('').text(Joomla.JText._('VBO_MESSAGE_TEMPLATE'))) .append('') ); ////////////////////////////////////////////////// const body = $('
'); body.append(''); body.append('
'); ////////////////////////////////////////////////// const tmplSelect = $(''); // add placeholder option tmplSelect.append( $('').text(Joomla.JText._('JGLOBAL_SELECT_AN_OPTION')) ); // build template select options (button.templates || []).forEach((tmpl) => { tmplSelect.append( $('').text(`${tmpl.name} (${tmpl.lang})`).val(tmpl.identifier) ); }); // append dropdown to popup body body.find('.messaging-tmpl-select').append(tmplSelect); // build button to send the template const sendButton = $(''); // append send button to popup body body.find('.messaging-tmpl-select').append(sendButton); ////////////////////////////////////////////////// popup.append(body); const toast = showContextToast(chat, popup); // handle template change event tmplSelect.on('change', (event) => { // find selected template const tmpl = button.templates.find(tmpl => tmpl.identifier === tmplSelect.val()); if (tmpl) { // display template preview body.find('.messaging-tmpl-preview').html(tmpl.preview_html).show(); sendButton.show(); } else { // hide template preview body.find('.messaging-tmpl-preview').hide().html(''); sendButton.hide(); } }); // handle template send event sendButton.on('click', async (event) => { sendButton.prop('disabled', true).find('i').attr('class', 'fas fa-spinner fa-spin no-margin'); // obtain selected template details let tplIdentifierParts = tmplSelect.val().split(':'); try { // send template await sendMessageTemplate(chat, tplIdentifierParts[2]); // dismiss toast toast.find('[data-role="popup.dismiss"]').trigger('click'); // download new message chat.synchronizeMessages(); } catch (error) { console.error(error); alert(error.responseText || error.statusText || 'Connection lost!'); // enable button again sendButton.prop('disabled', false).find('i').attr('class', 'fas fa-paper-plane no-margin'); } }); }) /*************** * CHAT EVENTS * ***************/ /** * Fires when a chat is prepared and ready to be used. * Changes the background color depending on the "banned" status. */ window.addEventListener('chat.prepare', (event) => { const {chat} = event.detail; if (chat.data.environment.user.id != 0 || chat.data.environment.context.alias !== 'session') { // ignore if we are not visiting the chat as admin return; } if (chat.data.environment.context.metadata.banned) { // change background for banned sessions $(chat.data.element.conversation).parent().css('background', '#d003'); } }); })(jQuery, window);