diff --git a/addons/im_livechat/static/src/legacy/models/public_livechat.js b/addons/im_livechat/static/src/legacy/models/public_livechat.js index 0c920eb4382..39706013d75 100644 --- a/addons/im_livechat/static/src/legacy/models/public_livechat.js +++ b/addons/im_livechat/static/src/legacy/models/public_livechat.js @@ -107,17 +107,6 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, { this.on('message_added', this, this._onTypingMessageAdded); this.on('message_posted', this, this._onTypingMessagePosted); - // Necessary for thread typing mixin to display is typing notification - // bar text (at least, for the operator in the members). - this._members = ( - this.messaging.publicLivechatGlobal.publicLivechat.operator - ? [{ - id: this.messaging.publicLivechatGlobal.publicLivechat.operator.id, - name: this.messaging.publicLivechatGlobal.publicLivechat.operator.name, - }] - : [] - ); - if (params.data.message_unread_counter !== undefined) { this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: params.data.message_unread_counter @@ -149,7 +138,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, { */ getMessages() { // ignore removed messages - return this._messages.filter(message => !message.isEmpty()); + return this.messaging.publicLivechatGlobal.livechatButtonView.messages.filter(message => !message.legacyPublicLivechatMessage.isEmpty()).map(message => message.legacyPublicLivechatMessage); }, /** * Get the text to display when some partners are typing something on the @@ -178,9 +167,11 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, { */ getTypingMembersToText() { const typingPartnerIDs = this._typingPartnerIDs; - const typingMembers = _.filter(this._members, function (member) { - return _.contains(typingPartnerIDs, member.id); - }); + const typingMembers = ( + this.messaging.publicLivechatGlobal.publicLivechat.operator && this._typingPartnerIDs.includes(this.messaging.publicLivechatGlobal.publicLivechat.operator.id) + ? [this.messaging.publicLivechatGlobal.publicLivechat.operator] + : [] + ); const sortedTypingMembers = _.sortBy(typingMembers, function (member) { return _.indexOf(typingPartnerIDs, member.id); }); diff --git a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js index 483c188f5dc..a19ea68668b 100644 --- a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js +++ b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js @@ -10,7 +10,6 @@ import LivechatButton from '@im_livechat/legacy/widgets/livechat_button'; import { clear, increment, insertAndReplace, replace } from '@mail/model/model_field_command'; const _t = core._t; -const QWeb = core.qweb; /** * Override of the LivechatButton to include chatbot capabilities. @@ -107,7 +106,7 @@ const QWeb = core.qweb; _chatbotAddMessage(message, options) { message.body = utils.Markup(message.body); this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(message, options); - if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.isAtBottom()) { + if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.isAtBottom()) { this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: increment() }); } @@ -131,10 +130,10 @@ const QWeb = core.qweb; */ _chatbotAwaitUserInput() { if (this._isLastMessageFromCustomer()) { - if (this._chatbotShouldEndScript()) { - this._chatbotEndScript(); + if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) { + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } else { - this._chatbotSetIsTyping(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), @@ -224,54 +223,6 @@ const QWeb = core.qweb; $composerTextField.on('keydown', this.chatbotInputKeyDownHandler); } }, - /** - * Once the script ends, adds a visual element at the end of the chat window allowing to restart - * the whole script. - * - * @private - */ - _chatbotEndScript() { - if ( - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.conversation_closed - ) { - // don't touch anything if the user has closed the conversation, let the chat window - // handle the display - return; - } - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_composer_text_field').addClass('d-none'); - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').show(); - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_restart').one('click', - this._onChatbotRestartScript.bind(this)); - - }, - /** - * Will display a "Restart script" button in the conversation toolbar. - * - * Side-case: if the conversation has been forwarded to a human operator, we don't want to - * display that restart button. - * - * @private - */ - _chatbotDisplayRestartButton() { - return this.messaging.publicLivechatGlobal.livechatButtonView.isChatbot && (!this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep || - (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type !== 'forward_operator' || - !this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_operator_found)); - }, - /** - * Works as a hook since other modules can add their own step types. - * - * @private - */ - _chatbotIsExpectingUserInput() { - return [ - 'question_phone', - 'question_email', - 'free_input_single', - 'free_input_multi', - ].includes(this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type); - }, /** * Processes the step, depending on the current state of the script and the author of the last * message that was typed into the conversation. @@ -300,15 +251,15 @@ const QWeb = core.qweb; * @private */ _chatbotProcessStep() { - if (this._chatbotShouldEndScript()) { - this._chatbotEndScript(); + if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) { + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'forward_operator' && this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_operator_found) { this._chatbotEnableInput(); - } else if (this._chatbotIsExpectingUserInput()) { + } else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.isExpectingUserInput) { if (this._isLastMessageFromCustomer()) { // user has already typed a message in -> trigger next step - this._chatbotSetIsTyping(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), @@ -337,7 +288,7 @@ const QWeb = core.qweb; // -> in that case, don't wait and trigger the next step immediately nextStepDelay = 0; } else { - this._chatbotSetIsTyping(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); } this.messaging.publicLivechatGlobal.livechatButtonView.update({ @@ -349,7 +300,7 @@ const QWeb = core.qweb; } } - if (!this._chatbotDisplayRestartButton()) { + if (!this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.hasRestartButton) { this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none'); } }, @@ -368,82 +319,6 @@ const QWeb = core.qweb; '_chatbotCurrentStep': this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data, })); }, - /** - * Adds a small "is typing" animation into the chat window. - * - * @private - */ - _chatbotSetIsTyping(isWelcomeMessage=false) { - if (this.messaging.publicLivechatGlobal.livechatButtonView.isTypingTimeout) { - clearTimeout(this.isTypingTimeout); - } - - this._chatbotDisableInput(''); - - this.messaging.publicLivechatGlobal.livechatButtonView.update({ - isTypingTimeout: setTimeout(() => { - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_mail_thread_content').append( - $(QWeb.render('im_livechat.legacy.chatbot.is_typing_message', { - 'chatbotImageSrc': `/im_livechat/operator/${ - this.messaging.publicLivechatGlobal.publicLivechat.operator.id - }/avatar`, - 'chatbotName': this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.name, - 'isWelcomeMessage': isWelcomeMessage, - })) - ); - - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.scrollToBottom(); - }, this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay / 3), - }); - }, - /** - * Helper method that checks if the script should be ended or not. - * If the user has closed the conversation -> script has ended. - * - * Otherwise, there are 2 use cases where we want to end the script: - * - * If the current step is the last one AND the conversation was not taken over by a human operator - * 1. AND we expect a user input (or we are on a selection) - * AND the user has already answered - * 2. AND we don't expect a user input - * - * @returns {Boolean} - * @private - */ - _chatbotShouldEndScript() { - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.conversation_closed) { - return true; - } - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_is_last && - (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type !== 'forward_operator' || - !this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_operator_found) - ) { - if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'question_email' - && !this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.is_email_valid - ) { - // email is not (yet) valid, let the user answer / try again - return false; - } else if ( - (this._chatbotIsExpectingUserInput() || - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'question_selection') && - this.messaging.publicLivechatGlobal.livechatButtonView.messages.length !== 0 - ) { - const lastMessage = this.messaging.publicLivechatGlobal.livechatButtonView.messages[this.messaging.publicLivechatGlobal.livechatButtonView.messages.length - 1]; - if (lastMessage.legacyPublicLivechatMessage.getAuthorID() !== this.messaging.publicLivechatGlobal.publicLivechat.operator.id) { - // we are on the last step of the script, expect a user input and the user has - // already answered - // -> end the script - return true; - } - } else if (!this._chatbotIsExpectingUserInput()) { - // we are on the last step of the script and we do not expect a user input - // -> end the script - return true; - } - } - - return false; - }, /** * A special case is handled for email steps, where we first validate the email (server side) * and we allow the user to try again in case the format is incorrect. @@ -512,7 +387,7 @@ const QWeb = core.qweb; // did not find next step -> end the script this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_is_last = true; this._renderMessages(); - this._chatbotEndScript(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } this._chatbotSaveSession(); @@ -537,37 +412,13 @@ const QWeb = core.qweb; */ _isLastMessageFromCustomer() { const lastMessage = this.messaging.publicLivechatGlobal.livechatButtonView.messages.length !== 0 ? this.messaging.publicLivechatGlobal.livechatButtonView.messages[this.messaging.publicLivechatGlobal.livechatButtonView.messages.length - 1] : null; - return lastMessage && lastMessage.legacyPublicLivechatMessage.getAuthorID() !== this.messaging.publicLivechatGlobal.publicLivechat.operator.id; + return lastMessage && lastMessage.authorId !== this.messaging.publicLivechatGlobal.publicLivechat.operator.id; }, //-------------------------------------------------------------------------- // Private - LiveChat Overrides //-------------------------------------------------------------------------- - /** - * When we enter the "ask feedback" process of the chat, we hide some elements that become - * unnecessary and irrelevant (restart / end messages, any text field values, ...). - * - * @override - * @private - */ - _askFeedback() { - this._super(...arguments); - - if ( - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep && - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data - ) { - this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.conversation_closed = true; - this._chatbotSaveSession(); - } - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none'); - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').hide(); - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_composer_text_field') - .removeClass('d-none') - .val(''); - }, /** * Resuming the chatbot script if we are currently running one. * @@ -583,7 +434,7 @@ const QWeb = core.qweb; return this._super(...arguments).then(() => { window.addEventListener('resize', () => { if (this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow) { - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); } }); @@ -681,8 +532,8 @@ const QWeb = core.qweb; return; // operator has taken over the conversation, let them speak } else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') { this._debouncedChatbotAwaitUserInput(); - } else if (!this._chatbotShouldEndScript()) { - this._chatbotSetIsTyping(); + } else if (!this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) { + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), @@ -690,7 +541,7 @@ const QWeb = core.qweb; ), }); } else { - this._chatbotEndScript(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript(); } this._chatbotSaveSession(); @@ -761,7 +612,7 @@ const QWeb = core.qweb; if (stepIndex + 1 < this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.welcomeSteps.length) { if (welcomeMessageDelay !== 0) { - this._chatbotSetIsTyping(true); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(true); } this.messaging.publicLivechatGlobal.livechatButtonView.update({ @@ -817,7 +668,7 @@ const QWeb = core.qweb; } this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ currentStep: clear() }); - this._chatbotSetIsTyping(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(); this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatbotNextStepTimeout: setTimeout( this._chatbotTriggerNextStep.bind(this), diff --git a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml index 50d3271a0c5..528f4c34291 100644 --- a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml +++ b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.xml @@ -20,12 +20,12 @@ - - diff --git a/addons/im_livechat/static/src/legacy/widgets/livechat_button.js b/addons/im_livechat/static/src/legacy/widgets/livechat_button.js index 7a2da22e5f6..0ad0276233f 100644 --- a/addons/im_livechat/static/src/legacy/widgets/livechat_button.js +++ b/addons/im_livechat/static/src/legacy/widgets/livechat_button.js @@ -7,7 +7,7 @@ import time from 'web.time'; import utils from 'web.utils'; import Widget from 'web.Widget'; -import { clear, insertAndReplace } from '@mail/model/model_field_command'; +import { insertAndReplace } from '@mail/model/model_field_command'; const _t = core._t; @@ -21,7 +21,7 @@ const LivechatButton = Widget.extend({ 'updated_unread_counter': '_onUpdatedUnreadCounter', }, events: { - 'click': '_openChat' + 'click': '_onClick' }, init(parent, messaging) { this._super(parent); @@ -53,12 +53,15 @@ const LivechatButton = Widget.extend({ for (const m of this.messaging.publicLivechatGlobal.livechatButtonView.history) { this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(m); } - this._openChat(); + this.messaging.publicLivechatGlobal.livechatButtonView.openChat(); } else if (!config.device.isMobile && this.messaging.publicLivechatGlobal.livechatButtonView.rule.action === 'auto_popup') { const autoPopupCookie = utils.get_cookie('im_livechat_auto_popup'); if (!autoPopupCookie || JSON.parse(autoPopupCookie)) { this.messaging.publicLivechatGlobal.livechatButtonView.update({ - autoOpenChatTimeout: setTimeout(this._openChat.bind(this), this.messaging.publicLivechatGlobal.livechatButtonView.rule.auto_popup_timer * 1000), + autoOpenChatTimeout: setTimeout( + this.messaging.publicLivechatGlobal.livechatButtonView.openChat, + this.messaging.publicLivechatGlobal.livechatButtonView.rule.auto_popup_timer * 1000, + ), }); } } @@ -82,85 +85,6 @@ const LivechatButton = Widget.extend({ // Private //-------------------------------------------------------------------------- - /** - * @private - */ - _askFeedback() { - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_thread_composer input').prop('disabled', true); - this.messaging.publicLivechatGlobal.update({ feedbackView: insertAndReplace() }); - }, - /** - * @private - */ - _closeChat() { - this.messaging.publicLivechatGlobal.livechatButtonView.update({ chatWindow: clear() }); - utils.set_cookie('im_livechat_session', "", -1); // remove cookie - }, - /** - * @private - */ - _openChat: _.debounce(function () { - if (this.messaging.publicLivechatGlobal.livechatButtonView.isOpeningChat) { - return; - } - const cookie = utils.get_cookie('im_livechat_session'); - let def; - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isOpeningChat: true }); - clearTimeout(this.messaging.publicLivechatGlobal.livechatButtonView.autoOpenChatTimeout); - if (cookie) { - def = Promise.resolve(JSON.parse(cookie)); - } else { - // re-initialize messages cache - this.messaging.publicLivechatGlobal.livechatButtonView.update({ messages: clear() }); - def = session.rpc( - '/im_livechat/get_session', - this._prepareGetSessionParameters(), - { shadow: true }, - ); - } - def.then((livechatData) => { - if (!livechatData || !livechatData.operator_pid) { - try { - this.displayNotification({ - message: _t("No available collaborator, please try again later."), - sticky: true, - }); - } catch (_err) { - /** - * Failure in displaying notification happens when - * notification service doesn't exist, which is the case in - * external lib. We don't want notifications in external - * lib at the moment because they use bootstrap toast and - * we don't want to include boostrap in external lib. - */ - console.warn(_t("No available collaborator, please try again later.")); - } - } else { - this.messaging.publicLivechatGlobal.update({ - publicLivechat: insertAndReplace({ data: livechatData }), - }); - return this._openChatWindow().then(() => { - if (!this.messaging.publicLivechatGlobal.livechatButtonView.history) { - this._sendWelcomeMessage(); - } - this._renderMessages(); - this.messaging.publicLivechatGlobal.update({ notificationHandler: insertAndReplace() }); - - utils.set_cookie('im_livechat_session', utils.unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.toData()), true), 60 * 60); - utils.set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60 * 60); - if (this.messaging.publicLivechatGlobal.publicLivechat.operator) { - const operatorPidId = this.messaging.publicLivechatGlobal.publicLivechat.operator.id; - const oneWeek = 7 * 24 * 60 * 60; - utils.set_cookie('im_livechat_previous_operator_pid', operatorPidId, oneWeek); - } - }); - } - }).then(() => { - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isOpeningChat: false }); - }).guardedCatch(() => { - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isOpeningChat: false }); - }); - }, 200, true), /** * Will try to get a previous operator for this visitor. * If the visitor already had visitor A, it's better for his user experience @@ -206,11 +130,10 @@ const LivechatButton = Widget.extend({ * @private */ _renderMessages() { - const shouldScroll = !this.messaging.publicLivechatGlobal.publicLivechat.isFolded && this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.isAtBottom(); - this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat._messages = this.messaging.publicLivechatGlobal.livechatButtonView.messages.map(message => message.legacyPublicLivechatMessage); + const shouldScroll = !this.messaging.publicLivechatGlobal.publicLivechat.isFolded && this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.isAtBottom(); this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.render(); if (shouldScroll) { - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); } }, /** @@ -240,9 +163,9 @@ const LivechatButton = Widget.extend({ */ console.warn(_t("Session expired... Please refresh and try again.")); } - this._closeChat(); + this.messaging.publicLivechatGlobal.livechatButtonView.closeChat(); } - this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); }); }, /** @@ -269,6 +192,12 @@ const LivechatButton = Widget.extend({ // Handlers //-------------------------------------------------------------------------- + /** + * @private + */ + _onClick() { + this.messaging.publicLivechatGlobal.livechatButtonView.openChat(); + }, /** * @private * @param {OdooEvent} ev @@ -281,11 +210,11 @@ const LivechatButton = Widget.extend({ }); if (shouldAskFeedback) { this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.toggleFold(false); - this._askFeedback(); + this.messaging.publicLivechatGlobal.livechatButtonView.askFeedback(); } else { - this._closeChat(); + this.messaging.publicLivechatGlobal.livechatButtonView.closeChat(); } - this._visitorLeaveSession(); + this.messaging.publicLivechatGlobal.livechatButtonView.leaveSession(); }, /** * @private @@ -324,19 +253,6 @@ const LivechatButton = Widget.extend({ ev.stopPropagation(); this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.renderHeader(); }, - /** - * @private - * Called when the visitor leaves the livechat chatter the first time (first click on X button) - * this will deactivate the mail_channel, notify operator that visitor has left the channel. - */ - _visitorLeaveSession() { - const cookie = utils.get_cookie('im_livechat_session'); - if (cookie) { - const channel = JSON.parse(cookie); - session.rpc('/im_livechat/visitor_leave_session', {uuid: channel.uuid}); - utils.set_cookie('im_livechat_session', "", -1); // remove cookie - } - }, }); export default LivechatButton; diff --git a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js index 0dd1efe2c1d..8eaa6c03afa 100644 --- a/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js +++ b/addons/im_livechat/static/src/legacy/widgets/public_livechat_window/public_livechat_window.js @@ -1,7 +1,5 @@ /** @odoo-module **/ -import PublicLivechatView from '@im_livechat/legacy/widgets/public_livechat_view/public_livechat_view'; - import config from 'web.config'; import { _t, qweb } from 'web.core'; import Widget from 'web.Widget'; @@ -46,13 +44,6 @@ const PublicLivechatWindow = Widget.extend({ async start() { this.$input = this.$('.o_composer_text_field'); this.$header = this.$('.o_thread_window_header'); - const options = { - displayMarkAsRead: false, - }; - if (this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat._type === 'document_thread') { - options.displayDocumentLinks = false; - } - this._publicLivechatView = new PublicLivechatView(this, this.messaging, options); // animate the (un)folding of thread windows this.$el.css({ transition: 'height ' + this.FOLD_ANIMATION_DURATION + 'ms linear' }); @@ -65,8 +56,8 @@ const PublicLivechatWindow = Widget.extend({ const margin_dir = _t.database.parameters.direction === "rtl" ? "margin-left" : "margin-right"; this.$el.css(margin_dir, $.position.scrollbarWidth()); } - const def = this._publicLivechatView.replace(this.$('.o_thread_window_content')).then(() => { - this._publicLivechatView.$el.on('scroll', this, this._debouncedOnScroll); + const def = this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.replace(this.$('.o_thread_window_content')).then(() => { + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.$el.on('scroll', this, this._debouncedOnScroll); }); await Promise.all([this._super(), def]); if (this.messaging.publicLivechatGlobal.livechatButtonView.headerBackgroundColor) { @@ -102,7 +93,7 @@ const PublicLivechatWindow = Widget.extend({ */ render() { this.renderHeader(); - this._publicLivechatView.render({ displayLoadMore: false }); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.render({ displayLoadMore: false }); }, /** * Render the header of this thread window. @@ -128,7 +119,7 @@ const PublicLivechatWindow = Widget.extend({ * @param {$.Element} $element */ replaceContentWith($element) { - $element.replace(this._publicLivechatView.$el); + $element.replace(this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.$el); }, /** * Toggle the fold state of this thread window. Also update the fold state @@ -155,7 +146,7 @@ const PublicLivechatWindow = Widget.extend({ */ updateVisualFoldState() { if (!this.messaging.publicLivechatGlobal.publicLivechat.isFolded) { - this._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); this._focusInput(); } const height = this.messaging.publicLivechatGlobal.publicLivechat.isFolded ? this.HEIGHT_FOLDED : this.HEIGHT_OPEN; @@ -203,7 +194,7 @@ const PublicLivechatWindow = Widget.extend({ this.trigger_up('post_message_chat_window', { messageData }); this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.postMessage(messageData) .then(() => { - this._publicLivechatView.scrollToBottom(); + this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.scrollToBottom(); }); }, @@ -301,7 +292,7 @@ const PublicLivechatWindow = Widget.extend({ * @private */ _onScroll() { - if (this._publicLivechatView.isAtBottom()) { + if (this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.isAtBottom()) { this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.markAsRead(); } }, diff --git a/addons/im_livechat/static/src/models/chatbot.js b/addons/im_livechat/static/src/models/chatbot.js index 64f8ec3b8cd..644109c8b42 100644 --- a/addons/im_livechat/static/src/models/chatbot.js +++ b/addons/im_livechat/static/src/models/chatbot.js @@ -8,6 +8,21 @@ registerModel({ name: 'Chatbot', identifyingFields: ['livechatButtonViewOwner'], recordMethods: { + /** + * @private + * @returns {boolean} + */ + _computeIsExpectingUserInput() { + if (!this.currentStep) { + return clear(); + } + return [ + 'question_phone', + 'question_email', + 'free_input_single', + 'free_input_multi', + ].includes(this.currentStep.data.chatbot_step_type); + }, /** * @private * @returns {string} @@ -15,6 +30,24 @@ registerModel({ _computeName() { return this.data.name; }, + /** + * Will display a "Restart script" button in the conversation toolbar. + * + * Side-case: if the conversation has been forwarded to a human operator, we don't want to + * display that restart button. + * + * @private + * @returns {boolean} + */ + _computeHasRestartButton() { + return Boolean( + !this.currentStep || + ( + this.currentStep.data.chatbot_step_type !== 'forward_operator' || + !this.currentStep.data.chatbot_operator_found + ) + ); + }, /** * @private * @returns {Array|FieldCommand} @@ -42,6 +75,56 @@ registerModel({ _computeScriptId() { return this.data.chatbot_script_id; }, + /** + * Helper method that checks if the script should be ended or not. + * If the user has closed the conversation -> script has ended. + * + * Otherwise, there are 2 use cases where we want to end the script: + * + * If the current step is the last one AND the conversation was not taken over by a human operator + * 1. AND we expect a user input (or we are on a selection) + * AND the user has already answered + * 2. AND we don't expect a user input + * + * @private + * @returns {boolean} + */ + _computeShouldEndScript() { + if (!this.currentStep) { + return clear(); + } + if (this.currentStep.data.conversation_closed) { + return true; + } + if (this.currentStep.data.chatbot_step_is_last && + (this.currentStep.data.chatbot_step_type !== 'forward_operator' || + !this.currentStep.data.chatbot_operator_found) + ) { + if (this.currentStep.data.chatbot_step_type === 'question_email' + && !this.currentStep.data.is_email_valid + ) { + // email is not (yet) valid, let the user answer / try again + return false; + } else if ( + (this.isExpectingUserInput || + this.currentStep.data.chatbot_step_type === 'question_selection') && + this.messaging.publicLivechatGlobal.livechatButtonView.messages.length !== 0 + ) { + const lastMessage = this.messaging.publicLivechatGlobal.livechatButtonView.messages[this.messaging.publicLivechatGlobal.livechatButtonView.messages.length - 1]; + if (lastMessage.authorId !== this.messaging.publicLivechatGlobal.publicLivechat.operator.id) { + // we are on the last step of the script, expect a user input and the user has + // already answered + // -> end the script + return true; + } + } else if (!this.isExpectingUserInput) { + // we are on the last step of the script and we do not expect a user input + // -> end the script + return true; + } + } + return false; + }, /** * @private * @returns {Array|FieldCommand} @@ -56,6 +139,14 @@ registerModel({ inverse: 'chabotOwner', isCausal: true, }), + hasRestartButton: attr({ + compute: '_computeHasRestartButton', + default: false, + }), + isExpectingUserInput: attr({ + compute: '_computeIsExpectingUserInput', + default: false, + }), lastWelcomeStep: attr({ compute: '_computeLastWelcomeStep', }), @@ -74,6 +165,10 @@ registerModel({ scriptId: attr({ compute: '_computeScriptId', }), + shouldEndScript: attr({ + compute: '_computeShouldEndScript', + default: false, + }), welcomeSteps: attr({ compute: '_computeWelcomeSteps', }), diff --git a/addons/im_livechat/static/src/models/livechat_button_view.js b/addons/im_livechat/static/src/models/livechat_button_view.js index eed3d6bc1dd..290855f429b 100644 --- a/addons/im_livechat/static/src/models/livechat_button_view.js +++ b/addons/im_livechat/static/src/models/livechat_button_view.js @@ -6,6 +6,9 @@ import { registerModel } from '@mail/model/model_core'; import { attr, many, one } from '@mail/model/model_field'; import { clear, insertAndReplace, replace } from '@mail/model/model_field_command'; +import { qweb } from 'web.core'; +import { get_cookie, set_cookie, unaccent } from 'web.utils'; + registerModel({ name: 'LivechatButtonView', identifyingFields: ['publicLivechatGlobalOwner'], @@ -43,6 +46,46 @@ registerModel({ }); } }, + askFeedback() { + this.chatWindow.legacyChatWindow.$('.o_thread_composer input').prop('disabled', true); + this.messaging.publicLivechatGlobal.update({ feedbackView: insertAndReplace() }); + /** + * When we enter the "ask feedback" process of the chat, we hide some elements that become + * unnecessary and irrelevant (restart / end messages, any text field values, ...). + */ + if ( + this.chatbot && + this.chatbot.currentStep && + this.chatbot.currentStep.data + ) { + this.chatbot.currentStep.data.conversation_closed = true; + this.widget._chatbotSaveSession(); + } + this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none'); + this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').hide(); + this.chatWindow.legacyChatWindow.$('.o_composer_text_field') + .removeClass('d-none') + .val(''); + }, + /** + * Once the script ends, adds a visual element at the end of the chat window allowing to restart + * the whole script. + */ + chatbotEndScript() { + if ( + this.chatbot.currentStep && + this.chatbot.currentStep.data && + this.chatbot.currentStep.data.conversation_closed + ) { + // don't touch anything if the user has closed the conversation, let the chat window + // handle the display + return; + } + this.chatWindow.legacyChatWindow.$('.o_composer_text_field').addClass('d-none'); + this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').show(); + this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_restart').one('click', + this.widget._onChatbotRestartScript.bind(this.widget)); + }, /** * See '_chatbotSaveSession'. * @@ -68,6 +111,57 @@ registerModel({ this.chatbot.update({ currentStep: insertAndReplace({ data: this.localStorageChatbotState._chatbotCurrentStep }) }); } }, + closeChat() { + this.update({ chatWindow: clear() }); + set_cookie('im_livechat_session', "", -1); // remove cookie + }, + /** + * Called when the visitor leaves the livechat chatter the first time (first click on X button) + * this will deactivate the mail_channel, notify operator that visitor has left the channel. + */ + leaveSession() { + const cookie = get_cookie('im_livechat_session'); + if (cookie) { + const channel = JSON.parse(cookie); + this.messaging.rpc({ route: '/im_livechat/visitor_leave_session', params: { uuid: channel.uuid } }); + set_cookie('im_livechat_session', "", -1); // remove cookie + } + }, + openChat() { + if (this.isOpenChatDebounced) { + this.openChatDebounced(); + } else { + this._openChat(); + } + }, + /** + * Adds a small "is typing" animation into the chat window. + * + * @param {boolean} [isWelcomeMessage=false] + */ + chatbotSetIsTyping(isWelcomeMessage = false) { + if (this.isTypingTimeout) { + clearTimeout(this.isTypingTimeout); + } + this.widget._chatbotDisableInput(''); + this.update({ + isTypingTimeout: setTimeout( + () => { + this.chatWindow.legacyChatWindow.$('.o_mail_thread_content').append( + $(qweb.render('im_livechat.legacy.chatbot.is_typing_message', { + 'chatbotImageSrc': `/im_livechat/operator/${ + this.messaging.publicLivechatGlobal.publicLivechat.operator.id + }/avatar`, + 'chatbotName': this.chatbot.name, + 'isWelcomeMessage': isWelcomeMessage, + })) + ); + this.chatWindow.publicLivechatView.widget.scrollToBottom(); + }, + this.chatbot.messageDelay / 3, + ), + }); + }, /** * @private * @returns {string} @@ -213,6 +307,13 @@ registerModel({ } return clear(); }, + /** + * @private + * @returns {FieldCommand} + */ + _computeIsOpenChatDebounced() { + return clear(); + }, /** * @private * @returns {FieldCommand} @@ -227,6 +328,13 @@ registerModel({ } return JSON.parse(data); }, + /** + * @private + * @returns {_.debounce} + */ + _computeOpenChatDebounced() { + return _.debounce(this._openChat, 200, true); + }, /** * @private * @returns {string} @@ -253,6 +361,70 @@ registerModel({ _computeTitleColor() { return this.messaging.publicLivechatGlobal.options.title_color; }, + /** + * @private + */ + _openChat() { + if (this.isOpeningChat) { + return; + } + const cookie = get_cookie('im_livechat_session'); + let def; + this.update({ isOpeningChat: true }); + clearTimeout(this.autoOpenChatTimeout); + if (cookie) { + def = Promise.resolve(JSON.parse(cookie)); + } else { + // re-initialize messages cache + this.update({ messages: clear() }); + def = this.messaging.rpc({ + route: '/im_livechat/get_session', + params: this.widget._prepareGetSessionParameters(), + }, { silent: true }); + } + def.then((livechatData) => { + if (!livechatData || !livechatData.operator_pid) { + try { + this.widget.displayNotification({ + message: this.env._t("No available collaborator, please try again later."), + sticky: true, + }); + } catch (_err) { + /** + * Failure in displaying notification happens when + * notification service doesn't exist, which is the case in + * external lib. We don't want notifications in external + * lib at the moment because they use bootstrap toast and + * we don't want to include boostrap in external lib. + */ + console.warn(this.env._t("No available collaborator, please try again later.")); + } + } else { + this.messaging.publicLivechatGlobal.update({ + publicLivechat: insertAndReplace({ data: livechatData }), + }); + return this.widget._openChatWindow().then(() => { + if (!this.history) { + this.widget._sendWelcomeMessage(); + } + this.widget._renderMessages(); + this.messaging.publicLivechatGlobal.update({ notificationHandler: insertAndReplace() }); + + set_cookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.toData()), true), 60 * 60); + set_cookie('im_livechat_auto_popup', JSON.stringify(false), 60 * 60); + if (this.messaging.publicLivechatGlobal.publicLivechat.operator) { + const operatorPidId = this.messaging.publicLivechatGlobal.publicLivechat.operator.id; + const oneWeek = 7 * 24 * 60 * 60; + set_cookie('im_livechat_previous_operator_pid', operatorPidId, oneWeek); + } + }); + } + }).then(() => { + this.update({ isOpeningChat: false }); + }).guardedCatch(() => { + this.update({ isOpeningChat: false }); + }); + }, }, fields: { autoOpenChatTimeout: attr(), @@ -311,6 +483,10 @@ registerModel({ isChatbotRedirecting: attr({ default: false, }), + isOpenChatDebounced: attr({ + compute: '_computeIsOpenChatDebounced', + default: true, + }), isOpeningChat: attr({ default: false, }), @@ -326,6 +502,9 @@ registerModel({ compute: '_computeLocalStorageChatbotState', }), messages: many('PublicLivechatMessage'), + openChatDebounced: attr({ + compute: '_computeOpenChatDebounced', + }), publicLivechatGlobalOwner: one('PublicLivechatGlobal', { inverse: 'livechatButtonView', readonly: true, diff --git a/addons/im_livechat/static/src/models/public_livechat_feedback_view.js b/addons/im_livechat/static/src/models/public_livechat_feedback_view.js index 661486c379d..1db032a4c33 100644 --- a/addons/im_livechat/static/src/models/public_livechat_feedback_view.js +++ b/addons/im_livechat/static/src/models/public_livechat_feedback_view.js @@ -26,8 +26,8 @@ registerModel({ }, }, recordMethods: { - _onFeedbackSent(...args) { - this.messaging.publicLivechatGlobal.livechatButtonView.widget._closeChat(...args); + _onFeedbackSent() { + this.messaging.publicLivechatGlobal.livechatButtonView.closeChat(); }, _onSendMessage(...args) { this.messaging.publicLivechatGlobal.livechatButtonView.widget._sendMessage(...args); diff --git a/addons/im_livechat/static/src/models/public_livechat_global_notification_handler.js b/addons/im_livechat/static/src/models/public_livechat_global_notification_handler.js index e3bdedb03e7..78f8646c2c5 100644 --- a/addons/im_livechat/static/src/models/public_livechat_global_notification_handler.js +++ b/addons/im_livechat/static/src/models/public_livechat_global_notification_handler.js @@ -66,7 +66,7 @@ registerModel({ } notificationData.body = utils.Markup(notificationData.body); this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(notificationData); - if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow._publicLivechatView.isAtBottom()) { + if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.publicLivechatView.widget.isAtBottom()) { this.messaging.publicLivechatGlobal.publicLivechat.update({ unreadCounter: increment() }); } this.messaging.publicLivechatGlobal.livechatButtonView.widget._renderMessages(); diff --git a/addons/im_livechat/static/src/models/public_livechat_message.js b/addons/im_livechat/static/src/models/public_livechat_message.js index 8a92f022229..76f60f51c2c 100644 --- a/addons/im_livechat/static/src/models/public_livechat_message.js +++ b/addons/im_livechat/static/src/models/public_livechat_message.js @@ -2,11 +2,27 @@ import { registerModel } from '@mail/model/model_core'; import { attr } from '@mail/model/model_field'; +import { clear } from '@mail/model/model_field_command'; registerModel({ name: 'PublicLivechatMessage', identifyingFields: ['id'], + recordMethods: { + /** + * @private + * @returns {FieldCommand|integer} + */ + _computeAuthorId() { + if (!(this.data.author_id && this.data.author_id[0])) { + return clear(); + } + return this.data.author_id[0]; + }, + }, fields: { + authorId: attr({ + compute: '_computeAuthorId', + }), data: attr(), id: attr({ readonly: true, diff --git a/addons/im_livechat/static/src/models/public_livechat_view.js b/addons/im_livechat/static/src/models/public_livechat_view.js new file mode 100644 index 00000000000..f191a0e4a1a --- /dev/null +++ b/addons/im_livechat/static/src/models/public_livechat_view.js @@ -0,0 +1,29 @@ +/** @odoo-module **/ + +import PublicLivechatView from '@im_livechat/legacy/widgets/public_livechat_view/public_livechat_view'; + +import { registerModel } from '@mail/model/model_core'; +import { attr, one } from '@mail/model/model_field'; + +registerModel({ + name: 'PublicLivechatView', + identifyingFields: ['publicLivechatWindowOwner'], + lifecycleHooks: { + _created() { + this.update({ + widget: new PublicLivechatView(this, this.messaging, { displayMarkAsRead: false }), + }); + }, + _willDelete() { + this.widget.destroy(); + }, + }, + fields: { + publicLivechatWindowOwner: one('PublicLivechatWindow', { + inverse: 'publicLivechatView', + readonly: true, + required: true, + }), + widget: attr(), + }, +}); diff --git a/addons/im_livechat/static/src/models/public_livechat_window.js b/addons/im_livechat/static/src/models/public_livechat_window.js index 4f61034a4b1..505e19ffb90 100644 --- a/addons/im_livechat/static/src/models/public_livechat_window.js +++ b/addons/im_livechat/static/src/models/public_livechat_window.js @@ -4,6 +4,7 @@ import PublicLivechatWindow from '@im_livechat/legacy/widgets/public_livechat_wi import { registerModel } from '@mail/model/model_core'; import { attr, one } from '@mail/model/model_field'; +import { insertAndReplace } from '@mail/model/model_field_command'; registerModel({ name: 'PublicLivechatWindow', @@ -46,5 +47,10 @@ registerModel({ readonly: true, required: true, }), + publicLivechatView: one('PublicLivechatView', { + default: insertAndReplace(), + inverse: 'publicLivechatWindowOwner', + isCausal: true, + }), }, }); diff --git a/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js b/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js index 0e9a369719f..4dbc58f2a69 100644 --- a/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js +++ b/addons/website_livechat/static/src/legacy/website_livechat_chatbot_test_script.js @@ -34,7 +34,6 @@ const LivechatButtonTestChatbot = LivechatButton.extend({ data: this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.lastWelcomeStep, }), }); - this._channelData = this.messaging.publicLivechatGlobal.livechatButtonView.testChatbotData.channel; }, /** @@ -46,31 +45,6 @@ const LivechatButtonTestChatbot = LivechatButton.extend({ willStart: function () { return this.messaging.publicLivechatGlobal.loadQWebTemplate(); }, - - /** - * Overridden to avoid calling the "get_session" endpoint as it requires a im_livechat.channel - * linked to work properly. - * - * Here, we already have a mail.channel created (see 'website_livechat_chatbot_test_script') so we - * use its configuration to create the 'WebsiteLivechat' Widget. - * - * @private - * @override - */ - _openChat: function () { - this.messaging.publicLivechatGlobal.update({ - publicLivechat: insertAndReplace({ data: this._channelData }), - }); - - return this._openChatWindow().then(() => { - this._sendWelcomeMessage(); - this._renderMessages(); - this.call('bus_service', 'addChannel', this.messaging.publicLivechatGlobal.publicLivechat.uuid); - this.call('bus_service', 'startPolling'); - utils.set_cookie('im_livechat_session', utils.unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.toData()), true), 60 * 60); - this.messaging.publicLivechatGlobal.livechatButtonView.update({ isOpeningChat: false }); - }); - }, }); publicWidget.registry.livechatChatbotTestScript = publicWidget.Widget.extend({ diff --git a/addons/website_livechat/static/src/models/livechat_button_view.js b/addons/website_livechat/static/src/models/livechat_button_view.js new file mode 100644 index 00000000000..fe6e7a99fbb --- /dev/null +++ b/addons/website_livechat/static/src/models/livechat_button_view.js @@ -0,0 +1,44 @@ +/** @odoo-module **/ + +import { patchRecordMethods } from '@mail/model/model_core'; +import { insertAndReplace } from '@mail/model/model_field_command'; +// ensure that the model definition is loaded before the patch +import '@im_livechat/models/livechat_button_view'; + +import { set_cookie, unaccent } from 'web.utils'; + +patchRecordMethods('LivechatButtonView', { + /** + * @override + */ + _computeIsOpenChatDebounced() { + if (this.isTestChatbot) { + return false; + } + return this._super(); + }, + /** + * Overridden to avoid calling the "get_session" endpoint as it requires a im_livechat.channel + * linked to work properly. + * + * Here, we already have a mail.channel created (see 'website_livechat_chatbot_test_script') so we + * use its configuration to create the 'WebsiteLivechat' Widget. + * + * @override + */ + async _openChat() { + if (!this.isTestChatbot) { + return this._super(); + } + this.messaging.publicLivechatGlobal.update({ + publicLivechat: insertAndReplace({ data: this.testChatbotData.channel }), + }); + await this.widget._openChatWindow(); + this.widget._sendWelcomeMessage(); + this.widget._renderMessages(); + this.env.services.bus_service.addChannel(this.messaging.publicLivechatGlobal.publicLivechat.uuid); + this.env.services.bus_service.startPolling(); + set_cookie('im_livechat_session', unaccent(JSON.stringify(this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat.toData()), true), 60 * 60); + this.update({ isOpeningChat: false }); + }, +});