[IMP] im_livechat, *: Refactor JS Public Livechat (step 3)

*: website_livechat

This commit is a step towards refactoring the JS of public livechat,
so that it reuses the same architecture as the code of Discuss.
This implies code that uses JS models and OWL components.

Task-2890189

closes odoo/odoo#94190

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Alexandre Kühn
2022-06-22 02:15:51 +02:00
parent cb26712c52
commit 69cb3a2562
4 changed files with 82 additions and 56 deletions
@@ -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();
@@ -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,
@@ -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 });
},
@@ -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),