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 ce1d595a99f..c9ed47f048c 100644 --- a/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js +++ b/addons/im_livechat/static/src/legacy/public_livechat_chatbot.js @@ -24,8 +24,6 @@ const QWeb = core.qweb; init() { this._super(...arguments); - this._chatbotMessageDelay = 3500; // in milliseconds - // debounced to let the user type several sentences, see '_chatbotAwaitUserInput' for details this._debouncedChatbotAwaitUserInput = _.debounce( this._chatbotAwaitUserInput.bind(this), @@ -53,17 +51,14 @@ const QWeb = core.qweb; async willStart() { const superResult = await this._super(...arguments); - this.chatbotState = null; - - if (this.messaging.livechatButtonView.rule && this.messaging.livechatButtonView.rule.chatbot) { + if (this.messaging.livechatButtonView.rule && !!this.messaging.livechatButtonView.rule.chatbot) { this.messaging.livechatButtonView.update({ isChatbot: true }); - this.chatbotState = 'init'; + this.messaging.livechatButtonView.update({ chatbotState: 'init' }); } else if (this.messaging.livechatButtonView.history !== null && this.messaging.livechatButtonView.history.length === 0) { this.messaging.livechatButtonView.update({ livechatInit: await session.rpc('/im_livechat/init', {channel_id: this.options.channel_id}) }); - if (this.messaging.livechatButtonView.livechatInit.rule.chatbot) { this.messaging.livechatButtonView.update({ isChatbot: true }); - this.chatbotState = 'welcome'; + this.messaging.livechatButtonView.update({ chatbotState: 'welcome' }); } } else if (this.messaging.livechatButtonView.history !== null && this.messaging.livechatButtonView.history.length !== 0) { const sessionCookie = utils.get_cookie('im_livechat_session'); @@ -71,22 +66,25 @@ const QWeb = core.qweb; const sessionKey = 'im_livechat.chatbot.state.uuid_' + JSON.parse(sessionCookie).uuid; if (localStorage.getItem(sessionKey)) { this.messaging.livechatButtonView.update({ isChatbot: true }); - this.chatbotState = 'restore_session'; + this.messaging.livechatButtonView.update({ chatbotState: 'restore_session' }); } } } - if (this.chatbotState === 'init') { + if (this.messaging.livechatButtonView.chatbotState === 'init') { // we landed on a website page where a channel rule is configured to run a chatbot.script // -> initialize necessary state this.messaging.livechatButtonView.update({ chatbot: this.messaging.livechatButtonView.rule.chatbot, }); if (this.messaging.livechatButtonView.rule.chatbot_welcome_steps && this.messaging.livechatButtonView.rule.chatbot_welcome_steps.length !== 0) { - this._chatbotCurrentStep = this.messaging.livechatButtonView.chatbot.chatbot_welcome_steps[ - this.messaging.livechatButtonView.chatbot.chatbot_welcome_steps.length - 1]; + this.messaging.livechatButtonView.update({ + chatbotCurrentStep: this.messaging.livechatButtonView.chatbot.chatbot_welcome_steps[ + this.messaging.livechatButtonView.chatbot.chatbot_welcome_steps.length - 1 + ], + }); } - } else if (this.chatbotState === 'welcome') { + } else if (this.messaging.livechatButtonView.chatbotState === 'welcome') { // we landed on a website page and a chatbot script was initialized on a previous one // however the end-user did not interact with the bot ( :( ) // -> remove cookie to force opening the popup again @@ -98,7 +96,7 @@ const QWeb = core.qweb; this.messaging.livechatButtonView.update({ chatbot: this.messaging.livechatButtonView.livechatInit.rule.chatbot }); this.messaging.livechatButtonView.update({ isChatbot: true }); this.messaging.livechatButtonView.update({ isChatbotBatchWelcomeMessages: true }); - } else if (this.chatbotState === 'restore_session') { + } else if (this.messaging.livechatButtonView.chatbotState === 'restore_session') { // we landed on a website page and a chatbot script is currently running // -> restore the user's session (see '_chatbotRestoreSession') this._chatbotRestoreSession(utils.get_cookie('im_livechat_session')); @@ -150,7 +148,7 @@ const QWeb = core.qweb; } else { this._chatbotSetIsTyping(); this.nextStepTimeout = setTimeout( - this._chatbotTriggerNextStep.bind(this), this._chatbotMessageDelay); + this._chatbotTriggerNextStep.bind(this), this.messaging.livechatButtonView.chatbotMessageDelay); } } }, @@ -227,7 +225,7 @@ const QWeb = core.qweb; $composerTextField.off('keydown', this.chatbotInputKeyDownHandler); } - if (this._chatbotCurrentStep.chatbot_step_type === 'free_input_multi') { + if (this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'free_input_multi') { this.chatbotInputKeyDownHandler = this._onChatbotInputKeyDown.bind(this); $composerTextField.on('keydown', this.chatbotInputKeyDownHandler); } @@ -254,9 +252,9 @@ const QWeb = core.qweb; * @private */ _chatbotDisplayRestartButton() { - return this.messaging.livechatButtonView.isChatbot && (!this._chatbotCurrentStep || - (this._chatbotCurrentStep.chatbot_step_type !== 'forward_operator' || - !this._chatbotCurrentStep.chatbot_operator_found)); + return this.messaging.livechatButtonView.isChatbot && (!this.messaging.livechatButtonView.chatbotCurrentStep || + (this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type !== 'forward_operator' || + !this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_operator_found)); }, /** * Works as a hook since other modules can add their own step types. @@ -269,7 +267,7 @@ const QWeb = core.qweb; 'question_email', 'free_input_single', 'free_input_multi', - ].includes(this._chatbotCurrentStep.chatbot_step_type); + ].includes(this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type); }, /** * Processes the step, depending on the current state of the script and the author of the last @@ -301,21 +299,21 @@ const QWeb = core.qweb; _chatbotProcessStep() { if (this._chatbotShouldEndScript()) { this._chatbotEndScript(); - } else if (this._chatbotCurrentStep.chatbot_step_type === 'forward_operator' - && this._chatbotCurrentStep.chatbot_operator_found) { + } else if (this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'forward_operator' + && this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_operator_found) { this._chatbotEnableInput(); } else if (this._chatbotIsExpectingUserInput()) { if (this._isLastMessageFromCustomer()) { // user has already typed a message in -> trigger next step this._chatbotSetIsTyping(); this.nextStepTimeout = setTimeout( - this._chatbotTriggerNextStep.bind(this), this._chatbotMessageDelay); + this._chatbotTriggerNextStep.bind(this), this.messaging.livechatButtonView.chatbotMessageDelay); } else { this._chatbotEnableInput(); } } else { let triggerNextStep = true; - if (this._chatbotCurrentStep.chatbot_step_type === 'question_selection') { + if (this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'question_selection') { if (!this._isLastMessageFromCustomer()) { // if there is no last message or if the last message is from the bot // -> don't trigger the next step, we are waiting for the user to pick an option @@ -324,7 +322,7 @@ const QWeb = core.qweb; } if (triggerNextStep) { - let nextStepDelay = this._chatbotMessageDelay; + let nextStepDelay = this.messaging.livechatButtonView.chatbotMessageDelay; if (this.messaging.livechatButtonView.chatWindow.$('.o_livechat_chatbot_typing').length !== 0) { // special case where we already have a "is typing" message displayed // can happen when the previous step did not trigger any message posted from the bot @@ -374,7 +372,7 @@ const QWeb = core.qweb; chatbotState = JSON.parse(chatbotState); this.messaging.livechatButtonView.update({ isChatbot: true }); this.messaging.livechatButtonView.update({ chatbot: chatbotState._chatbot }); - this._chatbotCurrentStep = chatbotState._chatbotCurrentStep; + this.messaging.livechatButtonView.update({ chatbotCurrentStep: chatbotState._chatbotCurrentStep }); } }, /** @@ -390,7 +388,7 @@ const QWeb = core.qweb; const chatUuid = this.messaging.livechatButtonView.livechat.toData().uuid; localStorage.setItem('im_livechat.chatbot.state.uuid_' + chatUuid, JSON.stringify({ '_chatbot': this.messaging.livechatButtonView.chatbot, - '_chatbotCurrentStep': this._chatbotCurrentStep, + '_chatbotCurrentStep': this.messaging.livechatButtonView.chatbotCurrentStep, })); }, /** @@ -418,7 +416,7 @@ const QWeb = core.qweb; ); this.messaging.livechatButtonView.chatWindow.scrollToBottom(); - }, this._chatbotMessageDelay / 3), + }, this.messaging.livechatButtonView.chatbotMessageDelay / 3), }); }, /** @@ -434,16 +432,18 @@ const QWeb = core.qweb; * @private */ _chatbotShouldEndScript() { - if (this._chatbotCurrentStep.chatbot_step_is_last && - (this._chatbotCurrentStep.chatbot_step_type !== 'forward_operator' || - !this._chatbotCurrentStep.chatbot_operator_found)) { - if (this._chatbotCurrentStep.chatbot_step_type === 'question_email' - && !this._chatbotCurrentStep.is_email_valid) { + if (this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_is_last && + (this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type !== 'forward_operator' || + !this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_operator_found) + ) { + if (this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'question_email' + && !this.messaging.livechatButtonView.chatbotCurrentStep.is_email_valid + ) { // email is not (yet) valid, let the user answer / try again return false; } else if ( (this._chatbotIsExpectingUserInput() || - this._chatbotCurrentStep.chatbot_step_type === 'question_selection') && + this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'question_selection') && this.messaging.livechatButtonView.messages.length !== 0 ) { const lastMessage = this.messaging.livechatButtonView.messages[this.messaging.livechatButtonView.messages.length - 1]; @@ -479,7 +479,7 @@ const QWeb = core.qweb; }); if (emailValidResult.success) { - this._chatbotCurrentStep.is_email_valid = true; + this.messaging.livechatButtonView.chatbotCurrentStep.is_email_valid = true; this._chatbotSaveSession(); return true; @@ -501,7 +501,7 @@ const QWeb = core.qweb; */ async _chatbotTriggerNextStep() { let triggerNextStep = true; - if (this._chatbotCurrentStep && this._chatbotCurrentStep.chatbot_step_type === 'question_email') { + if (this.messaging.livechatButtonView.chatbotCurrentStep && this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'question_email') { triggerNextStep = await this._chatbotValidateEmail(); } @@ -519,12 +519,12 @@ const QWeb = core.qweb; this._chatbotAddMessage(nextStep.chatbot_posted_message); } - this._chatbotCurrentStep = nextStep.chatbot_step; + this.messaging.livechatButtonView.update({ chatbotCurrentStep: nextStep.chatbot_step }); this._chatbotProcessStep(); } else { // did not find next step -> end the script - this._chatbotCurrentStep['chatbot_step_is_last'] = true; + this.messaging.livechatButtonView.chatbotCurrentStep['chatbot_step_is_last'] = true; this._renderMessages(); this._chatbotEndScript(); } @@ -593,7 +593,7 @@ const QWeb = core.qweb; } }); - if (this._chatbotCurrentStep && this.messaging.livechatButtonView.messages && this.messaging.livechatButtonView.messages.length !== 0) { + if (this.messaging.livechatButtonView.chatbotCurrentStep && this.messaging.livechatButtonView.messages && this.messaging.livechatButtonView.messages.length !== 0) { this._chatbotProcessStep(); } }); @@ -656,7 +656,7 @@ const QWeb = core.qweb; const superArguments = arguments; const superMethod = this._super; - if (this.messaging.livechatButtonView.isChatbot && this._chatbotCurrentStep) { + if (this.messaging.livechatButtonView.isChatbot && this.messaging.livechatButtonView.chatbotCurrentStep) { await this._chatbotPostWelcomeMessages(); } @@ -665,16 +665,18 @@ const QWeb = core.qweb; return; } - if (this.messaging.livechatButtonView.isChatbot && this._chatbotCurrentStep) { - if (this._chatbotCurrentStep.chatbot_step_type === 'forward_operator' && - this._chatbotCurrentStep.chatbot_operator_found) { + if (this.messaging.livechatButtonView.isChatbot && this.messaging.livechatButtonView.chatbotCurrentStep) { + if ( + this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'forward_operator' && + this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_operator_found + ) { return; // operator has taken over the conversation, let them speak - } else if (this._chatbotCurrentStep.chatbot_step_type === 'free_input_multi') { + } else if (this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'free_input_multi') { this._debouncedChatbotAwaitUserInput(); } else if (!this._chatbotShouldEndScript()) { this._chatbotSetIsTyping(); this.nextStepTimeout = setTimeout( - this._chatbotTriggerNextStep.bind(this), this._chatbotMessageDelay); + this._chatbotTriggerNextStep.bind(this), this.messaging.livechatButtonView.chatbotMessageDelay); } else { this._chatbotEndScript(); } @@ -691,7 +693,7 @@ const QWeb = core.qweb; if (this.messaging.livechatButtonView.isChatbot) { this._sendWelcomeChatbotMessage( 0, - this.messaging.livechatButtonView.isChatbotBatchWelcomeMessages ? 0 : this._chatbotMessageDelay, + this.messaging.livechatButtonView.isChatbotBatchWelcomeMessages ? 0 : this.messaging.livechatButtonView.chatbotMessageDelay, ); } else { this._super(...arguments); @@ -720,7 +722,7 @@ const QWeb = core.qweb; */ _sendWelcomeChatbotMessage(stepIndex, welcomeMessageDelay) { const chatbotStep = this.messaging.livechatButtonView.chatbot.chatbot_welcome_steps[stepIndex]; - this._chatbotCurrentStep = chatbotStep; + this.messaging.livechatButtonView.update({ chatbotCurrentStep: chatbotStep }); if (chatbotStep.chatbot_step_message) { this._addMessage({ @@ -748,7 +750,7 @@ const QWeb = core.qweb; this._renderMessages(); }, welcomeMessageDelay); } else { - if (this._chatbotCurrentStep.chatbot_step_type === 'forward_operator') { + if (this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'forward_operator') { // special case when the last welcome message is a forward to an operator // we need to save the welcome messages before continuing the script // indeed, if there are no operator available, the script will continue @@ -793,15 +795,17 @@ const QWeb = core.qweb; this._chatbotAddMessage(postedMessage); } - this._chatbotCurrentStep = null; + this.messaging.livechatButtonView.update({ chatbotCurrentStep: clear() }); this._chatbotSetIsTyping(); this.nextStepTimeout = setTimeout( - this._chatbotTriggerNextStep.bind(this), this._chatbotMessageDelay); + this._chatbotTriggerNextStep.bind(this), this.messaging.livechatButtonView.chatbotMessageDelay); }, _onChatbotInputKeyDown() { - if (this._chatbotCurrentStep && - this._chatbotCurrentStep.chatbot_step_type === 'free_input_multi') { + if ( + this.messaging.livechatButtonView.chatbotCurrentStep && + this.messaging.livechatButtonView.chatbotCurrentStep.chatbot_step_type === 'free_input_multi' + ) { this._debouncedChatbotAwaitUserInput(); } }, @@ -845,7 +849,7 @@ const QWeb = core.qweb; } const messageId = stepMessage.getID(); stepMessage.setChatbotStepAnswerId(selectedAnswer); - this._chatbotCurrentStep['chatbot_selected_answer_id'] = selectedAnswer; + this.messaging.livechatButtonView.chatbotCurrentStep['chatbot_selected_answer_id'] = selectedAnswer; this._renderMessages(); this._chatbotSaveSession(); 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 baab3001281..a50e730eaea 100644 --- a/addons/im_livechat/static/src/models/livechat_button_view.js +++ b/addons/im_livechat/static/src/models/livechat_button_view.js @@ -17,6 +17,16 @@ registerModel({ } return this.env._t("Chat with one of our collaborators"); }, + /** + * @private + * @returns {integer|FieldCommand} + */ + _computeChatbotMessageDelay() { + if (this.isWebsiteLivechatChatbotFlow) { + return 100; + } + return clear(); + }, /** * @private * @returns {string} @@ -69,6 +79,12 @@ registerModel({ compute: '_computeButtonText', }), chatbot: attr(), + chatbotCurrentStep: attr(), + chatbotMessageDelay: attr({ + compute: '_computeChatbotMessageDelay', + default: 3500, // in milliseconds + }), + chatbotState: attr(), // livechat window chatWindow: attr({ default: null, @@ -99,6 +115,9 @@ registerModel({ default: false, }), isTypingTimeout: attr(), + isWebsiteLivechatChatbotFlow: attr({ + default: false, + }), // livechat model livechat: attr({ default: null, 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 9e9fa515150..9c704a972a7 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 @@ -27,8 +27,11 @@ const LivechatButtonTestChatbot = LivechatButton.extend({ }, }); this.messaging.livechatButtonView.update({ chatbot: chatbotData.chatbot }); - this._chatbotCurrentStep = this.messaging.livechatButtonView.chatbot.chatbot_welcome_steps[ - this.messaging.livechatButtonView.chatbot.chatbot_welcome_steps.length - 1]; + this.messaging.livechatButtonView.update({ + chatbotCurrentStep: this.messaging.livechatButtonView.chatbot.chatbot_welcome_steps[ + this.messaging.livechatButtonView.chatbot.chatbot_welcome_steps.length - 1 + ], + }); this._channelData = chatbotData.channel; this.messaging.livechatButtonView.update({ isChatbot: true }); }, diff --git a/addons/website_livechat/static/tests/tours/website_livechat_chatbot_flow.js b/addons/website_livechat/static/tests/tours/website_livechat_chatbot_flow.js index b0cb9652c45..0783f0a7a08 100644 --- a/addons/website_livechat/static/tests/tours/website_livechat_chatbot_flow.js +++ b/addons/website_livechat/static/tests/tours/website_livechat_chatbot_flow.js @@ -11,7 +11,7 @@ LivechatButton.include({ * Let us also debounce waiting for more user inputs for only 500ms. */ start: function () { - this._chatbotMessageDelay = 100; + this.messaging.livechatButtonView.update({ isWebsiteLivechatChatbotFlow: true }); this._debouncedChatbotAwaitUserInput = _.debounce( this._chatbotAwaitUserInput.bind(this),