[IMP] im_livechat, *: remove Promise.reject in LivechatButton

*: website_livechat

closes odoo/odoo#98160

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Alexandre Kühn
2022-08-16 17:50:03 +02:00
parent 737479b25b
commit f2d3a5bbd4
11 changed files with 332 additions and 304 deletions
@@ -66,14 +66,14 @@ const _t = core._t;
*/
_chatbotAwaitUserInput() {
if (this._isLastMessageFromCustomer()) {
if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) {
if (this.messaging.publicLivechatGlobal.chatbot.shouldEndScript) {
this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript();
} else {
this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping();
this.messaging.publicLivechatGlobal.livechatButtonView.update({
chatbotNextStepTimeout: setTimeout(
this._chatbotTriggerNextStep.bind(this),
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay,
this.messaging.publicLivechatGlobal.chatbot.messageDelay,
)
});
}
@@ -103,7 +103,7 @@ const _t = core._t;
const postedWelcomeMessages = await session.rpc('/chatbot/post_welcome_steps', {
channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
chatbot_script_id: this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.scriptId,
chatbot_script_id: this.messaging.publicLivechatGlobal.chatbot.scriptId,
});
const welcomeMessagesIds = welcomeMessages.map(welcomeMessage => welcomeMessage.id);
@@ -154,7 +154,7 @@ const _t = core._t;
$composerTextField.off('keydown', this.chatbotInputKeyDownHandler);
}
if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') {
if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') {
this.chatbotInputKeyDownHandler = this._onChatbotInputKeyDown.bind(this);
$composerTextField.on('keydown', this.chatbotInputKeyDownHandler);
}
@@ -187,19 +187,19 @@ const _t = core._t;
* @private
*/
_chatbotProcessStep() {
if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.shouldEndScript) {
if (this.messaging.publicLivechatGlobal.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) {
} else if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'forward_operator'
&& this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_operator_found) {
this._chatbotEnableInput();
} else if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.isExpectingUserInput) {
} else if (this.messaging.publicLivechatGlobal.chatbot.isExpectingUserInput) {
if (this._isLastMessageFromCustomer()) {
// user has already typed a message in -> trigger next step
this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping();
this.messaging.publicLivechatGlobal.livechatButtonView.update({
chatbotNextStepTimeout: setTimeout(
this._chatbotTriggerNextStep.bind(this),
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay,
this.messaging.publicLivechatGlobal.chatbot.messageDelay,
),
});
} else {
@@ -207,7 +207,7 @@ const _t = core._t;
}
} else {
let triggerNextStep = true;
if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'question_selection') {
if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.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
@@ -216,7 +216,7 @@ const _t = core._t;
}
if (triggerNextStep) {
let nextStepDelay = this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay;
let nextStepDelay = this.messaging.publicLivechatGlobal.chatbot.messageDelay;
if (this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.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
@@ -236,7 +236,7 @@ const _t = core._t;
}
}
if (!this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.hasRestartButton) {
if (!this.messaging.publicLivechatGlobal.chatbot.hasRestartButton) {
this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none');
}
},
@@ -257,7 +257,7 @@ const _t = core._t;
});
if (emailValidResult.success) {
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.is_email_valid = true;
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.is_email_valid = true;
this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSaveSession();
return true;
@@ -280,9 +280,9 @@ const _t = core._t;
async _chatbotTriggerNextStep() {
let triggerNextStep = true;
if (
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data &&
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'question_email'
this.messaging.publicLivechatGlobal.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'question_email'
) {
triggerNextStep = await this._chatbotValidateEmail();
}
@@ -293,7 +293,7 @@ const _t = core._t;
const nextStep = await session.rpc('/chatbot/step/trigger', {
channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
chatbot_script_id: this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.scriptId,
chatbot_script_id: this.messaging.publicLivechatGlobal.chatbot.scriptId,
});
if (nextStep) {
@@ -301,12 +301,12 @@ const _t = core._t;
this._chatbotAddMessage(nextStep.chatbot_posted_message);
}
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ currentStep: insertAndReplace({ data: nextStep.chatbot_step }) });
this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: insertAndReplace({ data: nextStep.chatbot_step }) });
this._chatbotProcessStep();
} else {
// did not find next step -> end the script
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_is_last = true;
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_is_last = true;
this._renderMessages();
this.messaging.publicLivechatGlobal.livechatButtonView.chatbotEndScript();
}
@@ -348,7 +348,7 @@ const _t = core._t;
const parameters = this._super(...arguments);
if (this.messaging.publicLivechatGlobal.livechatButtonView.isChatbot) {
parameters.chatbot_script_id = this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.scriptId;
parameters.chatbot_script_id = this.messaging.publicLivechatGlobal.chatbot.scriptId;
}
return parameters;
@@ -385,7 +385,7 @@ const _t = core._t;
if (this.messaging.publicLivechatGlobal.livechatButtonView.isChatbot) {
this._sendWelcomeChatbotMessage(
0,
this.messaging.publicLivechatGlobal.livechatButtonView.chatbotState === 'welcome' ? 0 : this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.messageDelay,
this.messaging.publicLivechatGlobal.chatbotState === 'welcome' ? 0 : this.messaging.publicLivechatGlobal.chatbot.messageDelay,
);
} else {
this._super(...arguments);
@@ -413,8 +413,8 @@ const _t = core._t;
* @private
*/
_sendWelcomeChatbotMessage(stepIndex, welcomeMessageDelay) {
const chatbotStep = this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.welcomeSteps[stepIndex];
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({ currentStep: insertAndReplace({ data: chatbotStep }) });
const chatbotStep = this.messaging.publicLivechatGlobal.chatbot.welcomeSteps[stepIndex];
this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: insertAndReplace({ data: chatbotStep }) });
if (chatbotStep.chatbot_step_message) {
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage({
@@ -438,7 +438,7 @@ const _t = core._t;
});
}
if (stepIndex + 1 < this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.welcomeSteps.length) {
if (stepIndex + 1 < this.messaging.publicLivechatGlobal.chatbot.welcomeSteps.length) {
if (welcomeMessageDelay !== 0) {
this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSetIsTyping(true);
}
@@ -450,7 +450,7 @@ const _t = core._t;
}, welcomeMessageDelay),
});
} else {
if (this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'forward_operator') {
if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.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
@@ -470,9 +470,9 @@ const _t = core._t;
_onChatbotInputKeyDown() {
if (
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data &&
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi'
this.messaging.publicLivechatGlobal.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi'
) {
this._debouncedChatbotAwaitUserInput();
}
@@ -517,7 +517,7 @@ const _t = core._t;
}
const messageId = stepMessage.id;
stepMessage.legacyPublicLivechatMessage.setChatbotStepAnswerId(selectedAnswer);
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.currentStep.data.chatbot_selected_answer_id = selectedAnswer;
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_selected_answer_id = selectedAnswer;
this._renderMessages();
this.messaging.publicLivechatGlobal.livechatButtonView.chatbotSaveSession();
@@ -20,12 +20,12 @@
<!-- Extend the base livechat window header to include a button allowing to restart the script -->
<t t-extend="im_livechat.legacy.PublicLivechatWindow.HeaderContent">
<t t-jquery="span.o_thread_window_title" t-operation="after">
<a t-if="widget.isMobile() &amp;&amp; widget.messaging.publicLivechatGlobal.livechatButtonView.chatbot &amp;&amp; widget.messaging.publicLivechatGlobal.livechatButtonView.chatbot.hasRestartButton"
<a t-if="widget.isMobile() &amp;&amp; widget.messaging.publicLivechatGlobal.chatbot &amp;&amp; widget.messaging.publicLivechatGlobal.chatbot.hasRestartButton"
href="#" class="o_livechat_chatbot_main_restart fa fa-1x fa-refresh"
title="Restart Conversation"/>
</t>
<t t-jquery="span.o_thread_window_buttons a.o_thread_window_close" t-operation="before">
<a t-if="widget.messaging.publicLivechatGlobal.livechatButtonView.chatbot &amp;&amp; widget.messaging.publicLivechatGlobal.livechatButtonView.chatbot.hasRestartButton"
<a t-if="widget.messaging.publicLivechatGlobal.chatbot &amp;&amp; widget.messaging.publicLivechatGlobal.chatbot.hasRestartButton"
href="#" class="o_livechat_chatbot_main_restart fa fa-refresh"
title="Restart Conversation"/>
</t>
@@ -23,22 +23,21 @@ const LivechatButton = Widget.extend({
},
async willStart() {
this.messaging.publicLivechatGlobal.livechatButtonView.update({ widget: this });
return this.messaging.publicLivechatGlobal.livechatButtonView.willStart();
},
start() {
this.$el.text(this.messaging.publicLivechatGlobal.livechatButtonView.buttonText);
if (this.messaging.publicLivechatGlobal.livechatButtonView.history) {
for (const m of this.messaging.publicLivechatGlobal.livechatButtonView.history) {
if (this.messaging.publicLivechatGlobal.history) {
for (const m of this.messaging.publicLivechatGlobal.history) {
this.messaging.publicLivechatGlobal.livechatButtonView.addMessage(m);
}
this.messaging.publicLivechatGlobal.livechatButtonView.openChat();
} else if (!config.device.isMobile && this.messaging.publicLivechatGlobal.livechatButtonView.rule.action === 'auto_popup') {
} else if (!config.device.isMobile && this.messaging.publicLivechatGlobal.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.messaging.publicLivechatGlobal.livechatButtonView.openChat,
this.messaging.publicLivechatGlobal.livechatButtonView.rule.auto_popup_timer * 1000,
this.messaging.publicLivechatGlobal.rule.auto_popup_timer * 1000,
),
});
}
@@ -86,7 +85,7 @@ const LivechatButton = Widget.extend({
*/
_prepareGetSessionParameters() {
return {
channel_id: this.messaging.publicLivechatGlobal.livechatButtonView.channelId,
channel_id: this.messaging.publicLivechatGlobal.channelId,
anonymous_name: this.messaging.publicLivechatGlobal.livechatButtonView.defaultUsername,
previous_operator_id: this._get_previous_operator_id(),
};
@@ -6,7 +6,7 @@ import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'Chatbot',
identifyingFields: ['livechatButtonViewOwner'],
identifyingFields: ['publicLivechatGlobalOwner'],
recordMethods: {
/**
* @private
@@ -63,7 +63,7 @@ registerModel({
* @returns {integer|FieldCommand}
*/
_computeMessageDelay() {
if (this.livechatButtonViewOwner.isWebsiteLivechatChatbotFlow) {
if (this.messaging.publicLivechatGlobal.isWebsiteLivechatChatbotFlow) {
return 100;
}
return clear();
@@ -149,11 +149,6 @@ registerModel({
lastWelcomeStep: attr({
compute: '_computeLastWelcomeStep',
}),
livechatButtonViewOwner: one('LivechatButtonView', {
inverse: 'chatbot',
readonly: true,
required: true,
}),
name: attr({
compute: '_computeName',
}),
@@ -161,6 +156,11 @@ registerModel({
compute: '_computeMessageDelay',
default: 3500, // in milliseconds
}),
publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
inverse: 'chatbot',
readonly: true,
required: true,
}),
scriptId: attr({
compute: '_computeScriptId',
}),
@@ -7,11 +7,19 @@ import { attr, one } from '@mail/model/model_field';
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
import { qweb } from 'web.core';
import { get_cookie, Markup, set_cookie, unaccent } from 'web.utils';
import { get_cookie, set_cookie, unaccent } from 'web.utils';
registerModel({
name: 'LivechatButtonView',
identifyingFields: ['publicLivechatGlobalOwner'],
lifecycleHooks: {
_created() {
this.update({ widget: this.env.services.public_livechat_service.mountLivechatButton() });
},
_willDelete() {
this.widget.destroy();
},
},
recordMethods: {
/**
* @param {Object} data
@@ -54,11 +62,11 @@ registerModel({
* unnecessary and irrelevant (restart / end messages, any text field values, ...).
*/
if (
this.chatbot &&
this.chatbot.currentStep &&
this.chatbot.currentStep.data
this.messaging.publicLivechatGlobal.chatbot &&
this.messaging.publicLivechatGlobal.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
) {
this.chatbot.currentStep.data.conversation_closed = true;
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.conversation_closed = true;
this.chatbotSaveSession();
}
this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none');
@@ -73,9 +81,9 @@ registerModel({
*/
chatbotEndScript() {
if (
this.chatbot.currentStep &&
this.chatbot.currentStep.data &&
this.chatbot.currentStep.data.conversation_closed
this.messaging.publicLivechatGlobal.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.conversation_closed
) {
// don't touch anything if the user has closed the conversation, let the chat window
// handle the display
@@ -94,8 +102,8 @@ registerModel({
*/
chatbotSaveSession() {
localStorage.setItem('im_livechat.chatbot.state.uuid_' + this.messaging.publicLivechatGlobal.publicLivechat.uuid, JSON.stringify({
'_chatbot': this.chatbot.data,
'_chatbotCurrentStep': this.chatbot.currentStep.data,
'_chatbot': this.messaging.publicLivechatGlobal.chatbot.data,
'_chatbotCurrentStep': this.messaging.publicLivechatGlobal.chatbot.currentStep.data,
}));
},
/**
@@ -118,7 +126,7 @@ registerModel({
route: '/chatbot/restart',
params: {
channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
chatbot_script_id: this.chatbot.scriptId,
chatbot_script_id: this.messaging.publicLivechatGlobal.chatbot.scriptId,
},
});
@@ -126,12 +134,12 @@ registerModel({
this.widget._chatbotAddMessage(postedMessage);
}
this.chatbot.update({ currentStep: clear() });
this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: clear() });
this.chatbotSetIsTyping();
this.update({
chatbotNextStepTimeout: setTimeout(
this.widget._chatbotTriggerNextStep.bind(this.widget),
this.chatbot.messageDelay,
this.messaging.publicLivechatGlobal.chatbot.messageDelay,
),
});
},
@@ -150,14 +158,14 @@ registerModel({
if (browserLocalStorage && browserLocalStorage.length) {
for (let i = 0; i < browserLocalStorage.length; i++) {
const key = browserLocalStorage.key(i);
if (key.startsWith('im_livechat.chatbot.state.uuid_') && key !== this.chatbotSessionCookieKey) {
if (key.startsWith('im_livechat.chatbot.state.uuid_') && key !== this.messaging.publicLivechatGlobal.chatbotSessionCookieKey) {
browserLocalStorage.removeItem(key);
}
}
}
const chatbotState = localStorage.getItem(this.chatbotSessionCookieKey);
const chatbotState = localStorage.getItem(this.messaging.publicLivechatGlobal.chatbotSessionCookieKey);
if (chatbotState) {
this.chatbot.update({ currentStep: insertAndReplace({ data: this.localStorageChatbotState._chatbotCurrentStep }) });
this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: insertAndReplace({ data: this.messaging.publicLivechatGlobal.localStorageChatbotState._chatbotCurrentStep }) });
}
},
closeChat() {
@@ -210,13 +218,13 @@ registerModel({
'chatbotImageSrc': `/im_livechat/operator/${
this.messaging.publicLivechatGlobal.publicLivechat.operator.id
}/avatar`,
'chatbotName': this.chatbot.name,
'chatbotName': this.messaging.publicLivechatGlobal.chatbot.name,
'isWelcomeMessage': isWelcomeMessage,
}))
);
this.chatWindow.publicLivechatView.widget.scrollToBottom();
},
this.chatbot.messageDelay / 3,
this.messaging.publicLivechatGlobal.chatbot.messageDelay / 3,
),
});
},
@@ -228,13 +236,6 @@ registerModel({
await this._sendMessage(message);
this._sendMessageChatbotAfter();
},
async willStart() {
try {
await this._willStart();
} finally {
await this._willStartChatbot();
}
},
/**
* @private
* @returns {string}
@@ -257,55 +258,16 @@ registerModel({
_computeButtonTextColor() {
return this.messaging.publicLivechatGlobal.options.button_text_color;
},
/**
* @private
* @returns {integer}
*/
_computeChannelId() {
return this.messaging.publicLivechatGlobal.options.channel_id;
},
/**
* @private
* @returns {FieldCommand}
*/
_computeChatbot() {
if (this.isTestChatbot) {
return insertAndReplace({ data: this.testChatbotData.chatbot });
}
if (this.chatbotState === 'init') {
return insertAndReplace({ data: this.rule.chatbot });
}
if (this.chatbotState === 'welcome') {
return insertAndReplace({ data: this.livechatInit.rule.chatbot });
}
if (this.chatbotState === 'restore_session' && this.localStorageChatbotState) {
return insertAndReplace({ data: this.localStorageChatbotState._chatbot });
}
return clear();
},
/**
* @private
* @returns {integer|FieldCommand}
*/
_computeChatbotMessageDelay() {
if (this.isWebsiteLivechatChatbotFlow) {
if (this.messaging.publicLivechatGlobal.isWebsiteLivechatChatbotFlow) {
return 100;
}
return clear();
},
/**
* @private
* @returns {string|FieldCommand}
*/
_computeChatbotState() {
if (this.rule && !!this.rule.chatbot) {
return 'init';
}
if (this.livechatInit && this.livechatInit.rule.chatbot) {
return 'welcome';
}
return clear();
},
/**
* @private
* @returns {integer}
@@ -363,19 +325,19 @@ registerModel({
* @returns {boolean}
*/
_computeIsChatbot() {
if (this.isTestChatbot) {
if (this.messaging.publicLivechatGlobal.isTestChatbot) {
return true;
}
if (this.rule && this.rule.chatbot) {
if (this.messaging.publicLivechatGlobal.rule && this.messaging.publicLivechatGlobal.rule.chatbot) {
return true;
}
if (this.livechatInit && this.livechatInit.rule.chatbot) {
if (this.messaging.publicLivechatGlobal.livechatInit && this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot) {
return true;
}
if (this.chatbotState === 'welcome') {
if (this.messaging.publicLivechatGlobal.chatbotState === 'welcome') {
return true;
}
if (this.localStorageChatbotState) {
if (this.messaging.publicLivechatGlobal.localStorageChatbotState) {
return true;
}
return clear();
@@ -387,20 +349,6 @@ registerModel({
_computeIsOpenChatDebounced() {
return clear();
},
/**
* @private
* @returns {FieldCommand}
*/
_computeLocalStorageChatbotState() {
if (!this.sessionCookie) {
return clear();
}
const data = localStorage.getItem(this.chatbotSessionCookieKey);
if (!data) {
return clear();
}
return JSON.parse(data);
},
/**
* @private
* @returns {_.debounce}
@@ -418,15 +366,6 @@ registerModel({
}
return this.messaging.publicLivechatGlobal.serverUrl;
},
/**
* @returns {string|FieldCommand}
*/
_computeChatbotSessionCookieKey() {
if (!this.sessionCookie) {
return clear();
}
return 'im_livechat.chatbot.state.uuid_' + JSON.parse(this.sessionCookie).uuid;
},
/**
* @private
* @returns {string}
@@ -477,7 +416,7 @@ registerModel({
publicLivechat: insertAndReplace({ data: livechatData }),
});
return this.openChatWindow().then(() => {
if (!this.history) {
if (!this.messaging.publicLivechatGlobal.history) {
this.widget._sendWelcomeMessage();
}
this.widget._renderMessages();
@@ -517,9 +456,9 @@ registerModel({
});
if (
this.chatbot &&
this.chatbot.currentStep &&
this.chatbot.currentStep.data &&
this.messaging.publicLivechatGlobal.chatbot &&
this.messaging.publicLivechatGlobal.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data &&
this.messaging.publicLivechatGlobal.messages &&
this.messaging.publicLivechatGlobal.messages.length !== 0
) {
@@ -566,22 +505,22 @@ registerModel({
}
if (
this.isChatbot &&
this.chatbot.currentStep &&
this.chatbot.currentStep.data
this.messaging.publicLivechatGlobal.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
) {
if (
this.chatbot.currentStep.data.chatbot_step_type === 'forward_operator' &&
this.chatbot.currentStep.data.chatbot_operator_found
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'forward_operator' &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_operator_found
) {
return; // operator has taken over the conversation, let them speak
} else if (this.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') {
} else if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') {
this.widget._debouncedChatbotAwaitUserInput();
} else if (!this.chatbot.shouldEndScript) {
} else if (!this.messaging.publicLivechatGlobal.chatbot.shouldEndScript) {
this.chatbotSetIsTyping();
this.update({
chatbotNextStepTimeout: setTimeout(
this.widget._chatbotTriggerNextStep.bind(this.widget),
this.chatbot.messageDelay,
this.messaging.publicLivechatGlobal.chatbot.messageDelay,
),
});
} else {
@@ -611,100 +550,12 @@ registerModel({
async _sendMessageChatbotBefore() {
if (
this.isChatbot &&
this.chatbot.currentStep &&
this.chatbot.currentStep.data
this.messaging.publicLivechatGlobal.chatbot.currentStep &&
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
) {
await this.widget._chatbotPostWelcomeMessages();
}
},
async _willStart() {
const cookie = get_cookie('im_livechat_session');
if (cookie) {
const channel = JSON.parse(cookie);
const history = await this.messaging.rpc({
route: '/mail/chat_history',
params: { uuid: channel.uuid, limit: 100 },
});
history.reverse();
this.update({ history });
for (const message of this.history) {
message.body = Markup(message.body);
}
} else {
const result = await this.messaging.rpc({
route: '/im_livechat/init',
params: { channel_id: this.channelId },
});
if (!result.available_for_me) {
return Promise.reject();
}
this.update({ rule: result.rule });
}
return this.messaging.publicLivechatGlobal.loadQWebTemplate();
},
/**
* This override handles the following use cases:
*
* - If the chat is started for the first time (first visit of a visitor)
* We register the chatbot configuration and the rest of the behavior is triggered by various
* method overrides ('sendWelcomeMessage', 'sendMessage', ...)
*
* - If the chat has been started before, but the user did not interact with the bot
* The default behavior is to open an empty chat window, without any messages.
* In addition, we fetch the configuration (with a '/init' call), to see if we have a bot
* configured.
* Indeed we want to trigger the bot script on every page where the associated rule is matched.
*
* - If we have a non-empty chat history, resume the chat script where the end-user left it by
* fetching the necessary information from the local storage.
*
* @override
*/
async _willStartChatbot() {
if (this.rule && !!this.chatbot) {
// noop
} else if (this.history !== null && this.history.length === 0) {
this.update({
livechatInit: await this.messaging.rpc({
route: '/im_livechat/init',
params: { channel_id: this.channelId },
}),
});
} else if (this.history !== null && this.history.length !== 0) {
const sessionCookie = get_cookie('im_livechat_session');
if (sessionCookie) {
this.update({ sessionCookie });
if (localStorage.getItem(this.chatbotSessionCookieKey)) {
this.update({ chatbotState: 'restore_session' });
}
}
}
if (this.chatbotState === 'init') {
// we landed on a website page where a channel rule is configured to run a chatbot.script
// -> initialize necessary state
if (this.rule.chatbot_welcome_steps && this.rule.chatbot_welcome_steps.length !== 0) {
this.chatbot.update({
currentStep: insertAndReplace({
data: this.chatbot.lastWelcomeStep,
}),
});
}
} else if (this.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
// -> initialize necessary state
// -> batch welcome message (see '_sendWelcomeChatbotMessage')
set_cookie('im_livechat_auto_popup', '', -1);
this.update({ history: clear() });
this.update({ rule: this.livechatInit.rule });
} else if (this.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();
}
},
},
fields: {
autoOpenChatTimeout: attr(),
@@ -717,21 +568,7 @@ registerModel({
buttonTextColor: attr({
compute: '_computeButtonTextColor',
}),
channelId: attr({
compute: '_computeChannelId',
}),
chatbot: one('Chatbot', {
compute: '_computeChatbot',
inverse: 'livechatButtonViewOwner',
isCausal: true,
}),
chatbotNextStepTimeout: attr(),
chatbotSessionCookieKey: attr({
compute: '_computeChatbotSessionCookieKey',
}),
chatbotState: attr({
compute: '_computeChatbotState',
}),
chatbotWelcomeMessageTimeout: attr(),
chatWindow: one('PublicLivechatWindow', {
inverse: 'livechatButtonViewOwner',
@@ -749,9 +586,6 @@ registerModel({
headerBackgroundColor: attr({
compute: '_computeHeaderBackgroundColor',
}),
history: attr({
default: null,
}),
inputPlaceholder: attr({
compute: '_computeInputPlaceholder',
default: '',
@@ -770,17 +604,7 @@ registerModel({
isOpeningChat: attr({
default: false,
}),
isTestChatbot: attr({
default: false,
}),
isTypingTimeout: attr(),
isWebsiteLivechatChatbotFlow: attr({
default: false,
}),
livechatInit: attr(),
localStorageChatbotState: attr({
compute: '_computeLocalStorageChatbotState',
}),
openChatDebounced: attr({
compute: '_computeOpenChatDebounced',
}),
@@ -789,11 +613,9 @@ registerModel({
readonly: true,
required: true,
}),
rule: attr(),
serverUrl: attr({
compute: '_computeServerUrl',
}),
sessionCookie: attr(),
testChatbotData: attr(),
titleColor: attr({
compute: '_computeTitleColor',
@@ -5,7 +5,8 @@ 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 utils from 'web.utils';
import { get_cookie, Markup, set_cookie } from 'web.utils';
registerModel({
name: 'PublicLivechatGlobal',
@@ -14,7 +15,7 @@ registerModel({
_created() {
// History tracking
const page = window.location.href.replace(/^.*\/\/[^/]+/, '');
const pageHistory = utils.get_cookie(this.LIVECHAT_COOKIE_HISTORY);
const pageHistory = get_cookie(this.LIVECHAT_COOKIE_HISTORY);
let urlHistory = [];
if (pageHistory) {
urlHistory = JSON.parse(pageHistory) || [];
@@ -24,8 +25,9 @@ registerModel({
while (urlHistory.length > this.HISTORY_LIMIT) {
urlHistory.shift();
}
utils.set_cookie(this.LIVECHAT_COOKIE_HISTORY, JSON.stringify(urlHistory), 60 * 60 * 24); // 1 day cookie
set_cookie(this.LIVECHAT_COOKIE_HISTORY, JSON.stringify(urlHistory), 60 * 60 * 24); // 1 day cookie
}
this.willStart();
},
},
recordMethods: {
@@ -34,6 +36,60 @@ registerModel({
for (const template of templates) {
qweb.add_template(template);
}
this.update({ hasLoadedQWebTemplate: true });
},
async willStart() {
await this._willStart();
await this._willStartChatbot();
},
/**
* @private
* @returns {integer}
*/
_computeChannelId() {
return this.options.channel_id;
},
/**
* @private
* @returns {FieldCommand}
*/
_computeChatbot() {
if (this.isTestChatbot) {
return insertAndReplace({ data: this.testChatbotData.chatbot });
}
if (this.chatbotState === 'init') {
return insertAndReplace({ data: this.rule.chatbot });
}
if (this.chatbotState === 'welcome') {
return insertAndReplace({ data: this.livechatInit.rule.chatbot });
}
if (this.chatbotState === 'restore_session' && this.localStorageChatbotState) {
return insertAndReplace({ data: this.localStorageChatbotState._chatbot });
}
return clear();
},
/**
* @private
* @returns {string|FieldCommand}
*/
_computeChatbotSessionCookieKey() {
if (!this.sessionCookie) {
return clear();
}
return 'im_livechat.chatbot.state.uuid_' + JSON.parse(this.sessionCookie).uuid;
},
/**
* @private
* @returns {string|FieldCommand}
*/
_computeChatbotState() {
if (this.rule && !!this.rule.chatbot) {
return 'init';
}
if (this.livechatInit && this.livechatInit.rule.chatbot) {
return 'welcome';
}
return clear();
},
/**
* @private
@@ -50,11 +106,114 @@ registerModel({
* @returns {FieldCommand}
*/
_computeLivechatButtonView() {
if (this.isAvailable) {
if (this.isAvailable && this.isAvailableForMe && this.hasLoadedQWebTemplate && this.env.services.public_livechat_service) {
return insertAndReplace();
}
return clear();
},
/**
* @private
* @returns {FieldCommand}
*/
_computeLocalStorageChatbotState() {
if (!this.sessionCookie) {
return clear();
}
const data = localStorage.getItem(this.chatbotSessionCookieKey);
if (!data) {
return clear();
}
return JSON.parse(data);
},
async _willStart() {
const cookie = get_cookie('im_livechat_session');
if (cookie) {
const channel = JSON.parse(cookie);
const history = await this.messaging.rpc({
route: '/mail/chat_history',
params: { uuid: channel.uuid, limit: 100 },
});
history.reverse();
this.update({ history });
for (const message of this.history) {
message.body = Markup(message.body);
}
this.update({ isAvailableForMe: true });
} else {
const result = await this.messaging.rpc({
route: '/im_livechat/init',
params: { channel_id: this.channelId },
});
if (result.available_for_me) {
this.update({ isAvailableForMe: true });
}
this.update({ rule: result.rule });
}
return this.loadQWebTemplate();
},
/**
* This override handles the following use cases:
*
* - If the chat is started for the first time (first visit of a visitor)
* We register the chatbot configuration and the rest of the behavior is triggered by various
* method overrides ('sendWelcomeMessage', 'sendMessage', ...)
*
* - If the chat has been started before, but the user did not interact with the bot
* The default behavior is to open an empty chat window, without any messages.
* In addition, we fetch the configuration (with a '/init' call), to see if we have a bot
* configured.
* Indeed we want to trigger the bot script on every page where the associated rule is matched.
*
* - If we have a non-empty chat history, resume the chat script where the end-user left it by
* fetching the necessary information from the local storage.
*
* @override
*/
async _willStartChatbot() {
if (this.rule && !!this.chatbot) {
// noop
} else if (this.history !== null && this.history.length === 0) {
this.update({
livechatInit: await this.messaging.rpc({
route: '/im_livechat/init',
params: { channel_id: this.channelId },
}),
});
} else if (this.history !== null && this.history.length !== 0) {
const sessionCookie = get_cookie('im_livechat_session');
if (sessionCookie) {
this.update({ sessionCookie });
if (localStorage.getItem(this.chatbotSessionCookieKey)) {
this.update({ chatbotState: 'restore_session' });
}
}
}
if (this.chatbotState === 'init') {
// we landed on a website page where a channel rule is configured to run a chatbot.script
// -> initialize necessary state
if (this.rule.chatbot_welcome_steps && this.rule.chatbot_welcome_steps.length !== 0) {
this.chatbot.update({
currentStep: insertAndReplace({
data: this.chatbot.lastWelcomeStep,
}),
});
}
} else if (this.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
// -> initialize necessary state
// -> batch welcome message (see '_sendWelcomeChatbotMessage')
set_cookie('im_livechat_auto_popup', '', -1);
this.update({ history: clear() });
this.update({ rule: this.livechatInit.rule });
} else if (this.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();
}
},
},
fields: {
HISTORY_LIMIT: attr({
@@ -70,14 +229,43 @@ registerModel({
1: "😞",
},
}),
channelId: attr({
compute: '_computeChannelId',
}),
chatbot: one('Chatbot', {
compute: '_computeChatbot',
inverse: 'publicLivechatGlobalOwner',
isCausal: true,
}),
chatbotServerUrl: attr(),
chatbotSessionCookieKey: attr({
compute: '_computeChatbotSessionCookieKey',
}),
chatbotState: attr({
compute: '_computeChatbotState',
}),
feedbackView: one('PublicLivechatFeedbackView', {
inverse: 'publicLivechatGlobalOwner',
isCausal: true,
}),
hasLoadedQWebTemplate: attr({
default: false,
}),
history: attr({
default: null,
}),
isAvailable: attr({
default: false,
}),
isAvailableForMe: attr({
default: false,
}),
isTestChatbot: attr({
default: false,
}),
isWebsiteLivechatChatbotFlow: attr({
default: false,
}),
lastMessage: one('PublicLivechatMessage', {
compute: '_computeLastMessage',
}),
@@ -86,6 +274,10 @@ registerModel({
inverse: 'publicLivechatGlobalOwner',
isCausal: true,
}),
livechatInit: attr(),
localStorageChatbotState: attr({
compute: '_computeLocalStorageChatbotState',
}),
messages: many('PublicLivechatMessage'),
notificationHandler: one('PublicLivechatGlobalNotificationHandler', {
inverse: 'publicLivechatGlobalOwner',
@@ -98,8 +290,10 @@ registerModel({
inverse: 'publicLivechatGlobalOwner',
isCausal: true,
}),
rule: attr(),
serverUrl: attr({
default: '',
}),
sessionCookie: attr(),
},
});
@@ -8,9 +8,12 @@ export const publicLivechatService = {
dependencies: ['messaging'],
async start(env, { messaging: messagingService }) {
const messaging = await messagingService.get();
if (messaging.publicLivechatGlobal.livechatButtonView) {
const livechatButton = new LivechatButton(rootWidget, messaging);
livechatButton.appendTo(document.body);
}
return {
mountLivechatButton() {
const livechatButton = new LivechatButton(rootWidget, messaging);
livechatButton.appendTo(document.body);
return livechatButton;
},
};
},
};
@@ -27,11 +27,11 @@ const LivechatButtonTestChatbot = LivechatButton.extend({
'auto_popup_timer': 0,
},
});
this.messaging.publicLivechatGlobal.livechatButtonView.update({ isTestChatbot: true });
this.messaging.publicLivechatGlobal.update({ isTestChatbot: true });
this.messaging.publicLivechatGlobal.livechatButtonView.update({ testChatbotData: chatbotData.chatbot });
this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.update({
this.messaging.publicLivechatGlobal.chatbot.update({
currentStep: insertAndReplace({
data: this.messaging.publicLivechatGlobal.livechatButtonView.chatbot.lastWelcomeStep,
data: this.messaging.publicLivechatGlobal.chatbot.lastWelcomeStep,
}),
});
},
@@ -16,39 +16,17 @@ patchRecordMethods('LivechatButtonView', {
* @override
*/
async onChatbotRestartScript(ev) {
if (this.isWebsiteLivechatChatbotFlow) {
if (this.messaging.publicLivechatGlobal.isWebsiteLivechatChatbotFlow) {
this.messaging.publicLivechatGlobal.update({ messages: clear() });
this.widget._renderMessages();
}
return this._super(ev);
},
/**
* Check if a chat request is opened for this visitor
* if yes, replace the session cookie and start the conversation immediately.
* Do this before calling super to have everything ready before executing existing start logic.
* This is used for chat request mechanism, when an operator send a chat request
* from backend to a website visitor.
*
* @override
*/
willStart() {
if (this.isTestChatbot) {
/**
* Overridden to avoid calling the "init" endpoint as it requires a im_livechat.channel linked
* to work properly.
*/
return this.messaging.publicLivechatGlobal.loadQWebTemplate();
}
if (this.messaging.publicLivechatGlobal.options.chat_request_session) {
set_cookie('im_livechat_session', JSON.stringify(this.messaging.publicLivechatGlobal.options.chat_request_session), 60 * 60);
}
return this._super();
},
/**
* @override
*/
_computeIsOpenChatDebounced() {
if (this.isTestChatbot) {
if (this.messaging.publicLivechatGlobal.isTestChatbot) {
return false;
}
return this._super();
@@ -63,7 +41,7 @@ patchRecordMethods('LivechatButtonView', {
* @override
*/
async _openChat() {
if (!this.isTestChatbot) {
if (!this.messaging.publicLivechatGlobal.isTestChatbot) {
return this._super();
}
this.messaging.publicLivechatGlobal.update({
@@ -0,0 +1,32 @@
/** @odoo-module **/
import { patchRecordMethods } from '@mail/model/model_core';
// ensure that the model definition is loaded before the patch
import '@im_livechat/models/public_livechat_global';
import { set_cookie } from 'web.utils';
patchRecordMethods('PublicLivechatGlobal', {
/**
* Check if a chat request is opened for this visitor
* if yes, replace the session cookie and start the conversation immediately.
* Do this before calling super to have everything ready before executing existing start logic.
* This is used for chat request mechanism, when an operator send a chat request
* from backend to a website visitor.
*
* @override
*/
willStart() {
if (this.isTestChatbot) {
/**
* Overridden to avoid calling the "init" endpoint as it requires a im_livechat.channel linked
* to work properly.
*/
return this.loadQWebTemplate();
}
if (this.options.chat_request_session) {
set_cookie('im_livechat_session', JSON.stringify(this.options.chat_request_session), 60 * 60);
}
return this._super();
},
});
@@ -9,7 +9,7 @@ LivechatButton.include({
* Let us also debounce waiting for more user inputs for only 500ms.
*/
start: function () {
this.messaging.publicLivechatGlobal.livechatButtonView.update({ isWebsiteLivechatChatbotFlow: true });
this.messaging.publicLivechatGlobal.update({ isWebsiteLivechatChatbotFlow: true });
this._debouncedChatbotAwaitUserInput = _.debounce(
this._chatbotAwaitUserInput.bind(this),