+
diff --git a/addons/im_livechat/static/src/models/chatbot.js b/addons/im_livechat/static/src/models/chatbot.js
index 6148863d999..7d47e6f712a 100644
--- a/addons/im_livechat/static/src/models/chatbot.js
+++ b/addons/im_livechat/static/src/models/chatbot.js
@@ -4,9 +4,208 @@ import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
+import { qweb } from 'web.core';
+
registerModel({
name: 'Chatbot',
recordMethods: {
+ /**
+ * Once the script ends, adds a visual element at the end of the chat window allowing to restart
+ * the whole script.
+ */
+ endScript() {
+ if (
+ this.currentStep &&
+ this.currentStep.data &&
+ this.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.chatWindow.legacyChatWindow.$('.o_composer_text_field').addClass('d-none');
+ this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').show();
+ this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_restart').one('click', this.messaging.publicLivechatGlobal.livechatButtonView.onChatbotRestartScript);
+ },
+ /**
+ * See 'Chatbot/saveSession'.
+ *
+ * We retrieve the livechat uuid from the session cookie since the livechat Widget is not yet
+ * initialized when we restore the chatbot state.
+ *
+ * We also clear any older keys that store a previously saved chatbot session.
+ * (In that case we clear the actual browser's local storage, we don't use the localStorage
+ * object as it does not allow browsing existing keys, see 'local_storage.js'.)
+ */
+ restoreSession() {
+ const browserLocalStorage = window.localStorage;
+ 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.sessionCookieKey) {
+ browserLocalStorage.removeItem(key);
+ }
+ }
+ }
+ const chatbotState = localStorage.getItem(this.sessionCookieKey);
+ if (chatbotState) {
+ this.update({ currentStep: { data: this.localStorageState._chatbotCurrentStep } });
+ }
+ },
+ /**
+ * Register current chatbot step state into localStorage to be able to resume if the visitor
+ * goes to another website page or if he refreshes his page.
+ *
+ * (Will not work if the visitor switches browser but his livechat session will not be restored
+ * anyway in that case, since it's stored into a cookie).
+ */
+ saveSession() {
+ localStorage.setItem('im_livechat.chatbot.state.uuid_' + this.messaging.publicLivechatGlobal.publicLivechat.uuid, JSON.stringify({
+ '_chatbot': this.data,
+ '_chatbotCurrentStep': this.currentStep.data,
+ }));
+ },
+ /**
+ * Adds a small "is typing" animation into the chat window.
+ *
+ * @param {boolean} [isWelcomeMessage=false]
+ */
+ setIsTyping(isWelcomeMessage = false) {
+ if (this.messaging.publicLivechatGlobal.livechatButtonView.isTypingTimeout) {
+ clearTimeout(this.messaging.publicLivechatGlobal.livechatButtonView.isTypingTimeout);
+ }
+ this.messaging.publicLivechatGlobal.livechatButtonView.widget._chatbotDisableInput('');
+ this.messaging.publicLivechatGlobal.livechatButtonView.update({
+ isTypingTimeout: setTimeout(
+ () => {
+ this.messaging.publicLivechatGlobal.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.name,
+ 'isWelcomeMessage': isWelcomeMessage,
+ }))
+ );
+ this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
+ },
+ this.messageDelay / 3,
+ ),
+ });
+ },
+ /**
+ * Triggers the next step of the script by calling the associated route.
+ * This will receive the next step and call step processing.
+ */
+ async triggerNextStep() {
+ let triggerNextStep = true;
+ if (
+ this.currentStep &&
+ this.currentStep.data &&
+ this.currentStep.data.chatbot_step_type === 'question_email'
+ ) {
+ triggerNextStep = await this.messaging.publicLivechatGlobal.livechatButtonView.widget._chatbotValidateEmail();
+ }
+
+ if (!triggerNextStep) {
+ return;
+ }
+
+ const nextStep = await this.messaging.rpc({
+ route: '/chatbot/step/trigger',
+ params: {
+ channel_uuid: this.messaging.publicLivechatGlobal.publicLivechat.uuid,
+ chatbot_script_id: this.scriptId,
+ },
+ });
+
+ if (nextStep) {
+ if (nextStep.chatbot_posted_message) {
+ this.messaging.publicLivechatGlobal.livechatButtonView.widget._chatbotAddMessage(nextStep.chatbot_posted_message);
+ }
+
+ this.update({ currentStep: { data: nextStep.chatbot_step } });
+
+ this.messaging.publicLivechatGlobal.livechatButtonView.widget._chatbotProcessStep();
+ } else {
+ // did not find next step -> end the script
+ this.currentStep.data.chatbot_step_is_last = true;
+ this.messaging.publicLivechatGlobal.livechatButtonView.widget._renderMessages();
+ this.endScript();
+ }
+
+ this.saveSession();
+
+ return nextStep;
+ },
+ /**
+ * This method will be transformed into a 'debounced' version (see init).
+ *
+ * The purpose is to handle steps of type 'free_input_multi', that will let the user type in
+ * multiple lines of text before the bot goes to the next step.
+ *
+ * Every time a 'keydown' is detected into the input, or every time a message is sent, we call
+ * this debounced method, which will give the user about 10 seconds to type more text before
+ * the next step is triggered.
+ *
+ * First we check if the last message was sent by the user, to make sure we always let him type
+ * at least one message before moving on.
+ */
+ awaitUserInput() {
+ if (this.messaging.publicLivechatGlobal.isLastMessageFromCustomer) {
+ if (this.shouldEndScript) {
+ this.endScript();
+ } else {
+ this.setIsTyping();
+ this.update({
+ nextStepTimeout: setTimeout(
+ this.triggerNextStep,
+ this.messageDelay,
+ )
+ });
+ }
+ }
+ },
+ /**
+ * @private
+ * @returns {integer}
+ */
+ _computeAwaitUserInputDebounceTime() {
+ return 10000;
+ },
+ /**
+ * @private
+ * @returns {Object|FieldCommand}
+ */
+ _computeData() {
+ if (this.messaging.publicLivechatGlobal.isTestChatbot) {
+ return this.messaging.publicLivechatGlobal.testChatbotData.chatbot;
+ }
+ if (this.state === 'init') {
+ return this.messaging.publicLivechatGlobal.rule.chatbot;
+ }
+ if (this.state === 'welcome') {
+ return this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot;
+ }
+ if (
+ this.state === 'restore_session' &&
+ this.localStorageState
+ ) {
+ return this.localStorageState._chatbot;
+ }
+ return clear();
+ },
+ /**
+ * @private
+ * @returns {function}
+ */
+ _computeDebouncedAwaitUserInput() {
+ // debounced to let the user type several sentences, see 'Chatbot/awaitUserInput' for details
+ return _.debounce(
+ this.awaitUserInput,
+ this.awaitUserInputDebounceTime,
+ );
+ },
/**
* @private
* @returns {boolean}
@@ -22,11 +221,28 @@ registerModel({
'free_input_multi',
].includes(this.currentStep.data.chatbot_step_type);
},
+ /**
+ * @private
+ * @returns {FieldCommand|Object}
+ */
+ _computeLocalStorageState() {
+ if (!this.messaging.publicLivechatGlobal.sessionCookie) {
+ return clear();
+ }
+ const data = localStorage.getItem(this.sessionCookieKey);
+ if (!data) {
+ return clear();
+ }
+ return JSON.parse(data);
+ },
/**
* @private
* @returns {string}
*/
_computeName() {
+ if (!this.data) {
+ return clear();
+ }
return this.data.name;
},
/**
@@ -62,9 +278,6 @@ registerModel({
* @returns {integer|FieldCommand}
*/
_computeMessageDelay() {
- if (this.messaging.publicLivechatGlobal.isWebsiteLivechatChatbotFlow) {
- return 100;
- }
return clear();
},
/**
@@ -72,8 +285,21 @@ registerModel({
* @returns {integer}
*/
_computeScriptId() {
+ if (!this.data) {
+ return clear();
+ }
return this.data.chatbot_script_id;
},
+ /**
+ * @private
+ * @returns {string|FieldCommand}
+ */
+ _computeSessionCookieKey() {
+ if (!this.messaging.publicLivechatGlobal.sessionCookie) {
+ return clear();
+ }
+ return 'im_livechat.chatbot.state.uuid_' + JSON.parse(this.messaging.publicLivechatGlobal.sessionCookie).uuid;
+ },
/**
* Helper method that checks if the script should be ended or not.
* If the user has closed the conversation -> script has ended.
@@ -123,20 +349,44 @@ registerModel({
}
return false;
},
+ /**
+ * @private
+ * @returns {string|FieldCommand}
+ */
+ _computeState() {
+ if (this.messaging.publicLivechatGlobal.rule && !!this.messaging.publicLivechatGlobal.rule.chatbot) {
+ return 'init';
+ }
+ if (this.messaging.publicLivechatGlobal.livechatInit && this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot) {
+ return 'welcome';
+ }
+ return clear();
+ },
/**
* @private
* @returns {Array|FieldCommand}
*/
_computeWelcomeSteps() {
+ if (!this.data) {
+ return clear();
+ }
return this.data.chatbot_welcome_steps;
},
},
fields: {
- data: attr(),
+ awaitUserInputDebounceTime: attr({
+ compute: '_computeAwaitUserInputDebounceTime',
+ }),
+ data: attr({
+ compute: '_computeData',
+ }),
currentStep: one('ChatbotStep', {
inverse: 'chabotOwner',
isCausal: true,
}),
+ debouncedAwaitUserInput: attr({
+ compute: '_computeDebouncedAwaitUserInput',
+ }),
hasRestartButton: attr({
compute: '_computeHasRestartButton',
default: false,
@@ -145,12 +395,19 @@ registerModel({
compute: '_computeIsExpectingUserInput',
default: false,
}),
+ isRedirecting: attr({
+ default: false,
+ }),
lastWelcomeStep: attr({
compute: '_computeLastWelcomeStep',
}),
+ localStorageState: attr({
+ compute: '_computeLocalStorageState',
+ }),
name: attr({
compute: '_computeName',
}),
+ nextStepTimeout: attr(),
messageDelay: attr({
compute: '_computeMessageDelay',
default: 3500, // in milliseconds
@@ -162,10 +419,18 @@ registerModel({
scriptId: attr({
compute: '_computeScriptId',
}),
+ serverUrl: attr(),
+ sessionCookieKey: attr({
+ compute: '_computeSessionCookieKey',
+ }),
shouldEndScript: attr({
compute: '_computeShouldEndScript',
default: false,
}),
+ state: attr({
+ compute: '_computeState',
+ }),
+ welcomeMessageTimeout: attr(),
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 dce7fc82158..caa4e3be019 100644
--- a/addons/im_livechat/static/src/models/livechat_button_view.js
+++ b/addons/im_livechat/static/src/models/livechat_button_view.js
@@ -6,7 +6,6 @@ import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
-import { qweb } from 'web.core';
import { get_cookie, set_cookie, unaccent } from 'web.utils';
registerModel({
@@ -54,7 +53,7 @@ registerModel({
}
},
askFeedback() {
- this.chatWindow.legacyChatWindow.$('.o_thread_composer input').prop('disabled', true);
+ this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.$('.o_thread_composer input').prop('disabled', true);
this.messaging.publicLivechatGlobal.update({ feedbackView: {} });
/**
* When we enter the "ask feedback" process of the chat, we hide some elements that become
@@ -66,59 +65,28 @@ registerModel({
this.messaging.publicLivechatGlobal.chatbot.currentStep.data
) {
this.messaging.publicLivechatGlobal.chatbot.currentStep.data.conversation_closed = true;
- this.chatbotSaveSession();
+ this.messaging.publicLivechatGlobal.chatbot.saveSession();
}
- 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')
+ this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_main_restart').addClass('d-none');
+ this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').hide();
+ this.messaging.publicLivechatGlobal.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.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
- 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.onChatbotRestartScript);
- },
- /**
- * Register current chatbot step state into localStorage to be able to resume if the visitor
- * goes to another website page or if he refreshes his page.
- *
- * (Will not work if the visitor switches browser but his livechat session will not be restored
- * anyway in that case, since it's stored into a cookie).
- */
- chatbotSaveSession() {
- localStorage.setItem('im_livechat.chatbot.state.uuid_' + this.messaging.publicLivechatGlobal.publicLivechat.uuid, JSON.stringify({
- '_chatbot': this.messaging.publicLivechatGlobal.chatbot.data,
- '_chatbotCurrentStep': this.messaging.publicLivechatGlobal.chatbot.currentStep.data,
- }));
- },
/**
* Restart the script and then trigger the "next step" (which will be the first of the script
* in this case).
*/
async onChatbotRestartScript(ev) {
- this.chatWindow.legacyChatWindow.$('.o_composer_text_field').removeClass('d-none');
- this.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').hide();
+ this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.$('.o_composer_text_field').removeClass('d-none');
+ this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.$('.o_livechat_chatbot_end').hide();
- if (this.chatbotNextStepTimeout) {
- clearTimeout(this.chatbotNextStepTimeout);
+ if (this.messaging.publicLivechatGlobal.chatbot.nextStepTimeout) {
+ clearTimeout(this.messaging.publicLivechatGlobal.chatbot.nextStepTimeout);
}
- if (this.chatbotWelcomeMessageTimeout) {
- clearTimeout(this.chatbotWelcomeMessageTimeout);
+ if (this.messaging.publicLivechatGlobal.chatbot.welcomeMessageTimeout) {
+ clearTimeout(this.messaging.publicLivechatGlobal.chatbot.welcomeMessageTimeout);
}
const postedMessage = await this.messaging.rpc({
@@ -134,41 +102,16 @@ registerModel({
}
this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: clear() });
- this.chatbotSetIsTyping();
- this.update({
- chatbotNextStepTimeout: setTimeout(
- this.widget._chatbotTriggerNextStep.bind(this.widget),
+ this.messaging.publicLivechatGlobal.chatbot.setIsTyping();
+ this.messaging.publicLivechatGlobal.chatbot.update({
+ nextStepTimeout: setTimeout(
+ this.messaging.publicLivechatGlobal.chatbot.triggerNextStep,
this.messaging.publicLivechatGlobal.chatbot.messageDelay,
),
});
},
- /**
- * See 'chatbotSaveSession'.
- *
- * We retrieve the livechat uuid from the session cookie since the livechat Widget is not yet
- * initialized when we restore the chatbot state.
- *
- * We also clear any older keys that store a previously saved chatbot session.
- * (In that case we clear the actual browser's local storage, we don't use the localStorage
- * object as it does not allow browsing existing keys, see 'local_storage.js'.)
- */
- chatbotRestoreSession() {
- const browserLocalStorage = window.localStorage;
- 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.messaging.publicLivechatGlobal.chatbotSessionCookieKey) {
- browserLocalStorage.removeItem(key);
- }
- }
- }
- const chatbotState = localStorage.getItem(this.messaging.publicLivechatGlobal.chatbotSessionCookieKey);
- if (chatbotState) {
- this.messaging.publicLivechatGlobal.chatbot.update({ currentStep: { data: this.messaging.publicLivechatGlobal.localStorageChatbotState._chatbotCurrentStep } });
- }
- },
closeChat() {
- this.update({ chatWindow: clear() });
+ this.messaging.publicLivechatGlobal.update({ chatWindow: clear() });
set_cookie('im_livechat_session', "", -1); // remove cookie
},
/**
@@ -191,42 +134,14 @@ registerModel({
}
},
async openChatWindow() {
- this.update({ chatWindow: {} });
- await this.chatWindow.legacyChatWindow.appendTo($('body'));
+ this.messaging.publicLivechatGlobal.update({ chatWindow: {} });
+ await this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.appendTo($('body'));
const cssProps = { bottom: 0 };
cssProps[this.messaging.locale.textDirection === 'rtl' ? 'left' : 'right'] = 0;
- this.chatWindow.legacyChatWindow.$el.css(cssProps);
+ this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.$el.css(cssProps);
this.widget.$el.hide();
this._openChatWindowChatbot();
},
- /**
- * 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.messaging.publicLivechatGlobal.chatbot.name,
- 'isWelcomeMessage': isWelcomeMessage,
- }))
- );
- this.chatWindow.publicLivechatView.widget.scrollToBottom();
- },
- this.messaging.publicLivechatGlobal.chatbot.messageDelay / 3,
- ),
- });
- },
/**
* @param {Object} message
*/
@@ -235,6 +150,37 @@ registerModel({
await this._sendMessage(message);
this._sendMessageChatbotAfter();
},
+ start() {
+ this.widget.$el.text(this.buttonText);
+ if (this.messaging.publicLivechatGlobal.history) {
+ for (const m of this.messaging.publicLivechatGlobal.history) {
+ this.addMessage(m);
+ }
+ this.openChat();
+ } else if (!this.messaging.device.isSmall && this.messaging.publicLivechatGlobal.rule.action === 'auto_popup') {
+ const autoPopupCookie = get_cookie('im_livechat_auto_popup');
+ if (!autoPopupCookie || JSON.parse(autoPopupCookie)) {
+ this.update({
+ autoOpenChatTimeout: setTimeout(
+ this.openChat,
+ this.messaging.publicLivechatGlobal.rule.auto_popup_timer * 1000,
+ ),
+ });
+ }
+ }
+ if (this.buttonBackgroundColor) {
+ this.widget.$el.css('background-color', this.buttonBackgroundColor);
+ }
+ if (this.buttonTextColor) {
+ this.widget.$el.css('color', this.buttonTextColor);
+ }
+
+ // If website_event_track installed, put the livechat banner above the PWA banner.
+ const pwaBannerHeight = $('.o_pwa_install_banner').outerHeight(true);
+ if (pwaBannerHeight) {
+ this.widget.$el.css('bottom', pwaBannerHeight + 'px');
+ }
+ },
/**
* @private
* @returns {string}
@@ -257,16 +203,6 @@ registerModel({
_computeButtonTextColor() {
return this.messaging.publicLivechatGlobal.options.button_text_color;
},
- /**
- * @private
- * @returns {integer|FieldCommand}
- */
- _computeChatbotMessageDelay() {
- if (this.messaging.publicLivechatGlobal.isWebsiteLivechatChatbotFlow) {
- return 100;
- }
- return clear();
- },
/**
* @private
* @returns {integer}
@@ -333,10 +269,10 @@ registerModel({
if (this.messaging.publicLivechatGlobal.livechatInit && this.messaging.publicLivechatGlobal.livechatInit.rule.chatbot) {
return true;
}
- if (this.messaging.publicLivechatGlobal.chatbotState === 'welcome') {
+ if (this.messaging.publicLivechatGlobal.chatbot.state === 'welcome') {
return true;
}
- if (this.messaging.publicLivechatGlobal.localStorageChatbotState) {
+ if (this.messaging.publicLivechatGlobal.chatbot.localStorageState) {
return true;
}
return clear();
@@ -361,7 +297,7 @@ registerModel({
*/
_computeServerUrl() {
if (this.isChatbot) {
- return this.messaging.publicLivechatGlobal.chatbotServerUrl;
+ return this.messaging.publicLivechatGlobal.chatbot.serverUrl;
}
return this.messaging.publicLivechatGlobal.serverUrl;
},
@@ -449,8 +385,8 @@ registerModel({
*/
_openChatWindowChatbot() {
window.addEventListener('resize', () => {
- if (this.chatWindow) {
- this.chatWindow.publicLivechatView.widget.scrollToBottom();
+ if (this.messaging.publicLivechatGlobal.chatWindow) {
+ this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
}
});
@@ -493,13 +429,13 @@ registerModel({
}
this.closeChat();
}
- this.chatWindow.publicLivechatView.widget.scrollToBottom();
+ this.messaging.publicLivechatGlobal.chatWindow.publicLivechatView.widget.scrollToBottom();
},
/**
* @private
*/
_sendMessageChatbotAfter() {
- if (this.isChatbotRedirecting) {
+ if (this.messaging.publicLivechatGlobal.chatbot.isRedirecting) {
return;
}
if (
@@ -513,19 +449,19 @@ registerModel({
) {
return; // operator has taken over the conversation, let them speak
} else if (this.messaging.publicLivechatGlobal.chatbot.currentStep.data.chatbot_step_type === 'free_input_multi') {
- this.widget._debouncedChatbotAwaitUserInput();
+ this.messaging.publicLivechatGlobal.chatbot.debouncedAwaitUserInput();
} else if (!this.messaging.publicLivechatGlobal.chatbot.shouldEndScript) {
- this.chatbotSetIsTyping();
- this.update({
- chatbotNextStepTimeout: setTimeout(
- this.widget._chatbotTriggerNextStep.bind(this.widget),
+ this.messaging.publicLivechatGlobal.chatbot.setIsTyping();
+ this.messaging.publicLivechatGlobal.chatbot.update({
+ nextStepTimeout: setTimeout(
+ this.messaging.publicLivechatGlobal.chatbot.triggerNextStep,
this.messaging.publicLivechatGlobal.chatbot.messageDelay,
),
});
} else {
- this.chatbotEndScript();
+ this.messaging.publicLivechatGlobal.chatbot.endScript();
}
- this.chatbotSaveSession();
+ this.messaging.publicLivechatGlobal.chatbot.saveSession();
}
},
/**
@@ -533,7 +469,7 @@ registerModel({
* - If the conversation has been forwarded to an operator
* Then there is nothing to do, we let them speak
* - If we are currently on a 'free_input_multi' step
- * Await more user input (see #_chatbotAwaitUserInput() for details)
+ * Await more user input (see #Chatbot/awaitUserInput for details)
* - Otherwise we continue the script or end it if it's the last step
*
* We also save the current session state.
@@ -569,10 +505,6 @@ registerModel({
}),
chatbotNextStepTimeout: attr(),
chatbotWelcomeMessageTimeout: attr(),
- chatWindow: one('PublicLivechatWindow', {
- inverse: 'livechatButtonViewOwner',
- isCausal: true,
- }),
currentPartnerId: attr({
compute: '_computeCurrentPartnerId',
}),
@@ -593,9 +525,6 @@ registerModel({
compute: '_computeIsChatbot',
default: false,
}),
- isChatbotRedirecting: attr({
- default: false,
- }),
isOpenChatDebounced: attr({
compute: '_computeIsOpenChatDebounced',
default: true,
@@ -614,7 +543,6 @@ registerModel({
serverUrl: attr({
compute: '_computeServerUrl',
}),
- testChatbotData: attr(),
titleColor: attr({
compute: '_computeTitleColor',
}),
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 6ae55db488b..7110f9959a4 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
@@ -16,7 +16,7 @@ registerModel({
this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat,
),
});
- this.messaging.publicLivechatGlobal.livechatButtonView.chatWindow.legacyChatWindow.replaceContentWith(this.widget);
+ this.messaging.publicLivechatGlobal.chatWindow.legacyChatWindow.replaceContentWith(this.widget);
this.widget.on('feedback_sent', null, this._onFeedbackSent);
this.widget.on('send_message', null, this._onSendMessage);
},
diff --git a/addons/im_livechat/static/src/models/public_livechat_global.js b/addons/im_livechat/static/src/models/public_livechat_global.js
index 84e652f333c..41760c632a7 100644
--- a/addons/im_livechat/static/src/models/public_livechat_global.js
+++ b/addons/im_livechat/static/src/models/public_livechat_global.js
@@ -48,48 +48,6 @@ registerModel({
_computeChannelId() {
return this.options.channel_id;
},
- /**
- * @private
- * @returns {FieldCommand}
- */
- _computeChatbot() {
- if (this.isTestChatbot) {
- return { data: this.testChatbotData.chatbot };
- }
- if (this.chatbotState === 'init') {
- return { data: this.rule.chatbot };
- }
- if (this.chatbotState === 'welcome') {
- return { data: this.livechatInit.rule.chatbot };
- }
- if (this.chatbotState === 'restore_session' && this.localStorageChatbotState) {
- return { 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();
- },
/**
* Compares the last message of the conversation to this livechat's operator id.
*
@@ -125,20 +83,6 @@ registerModel({
}
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) {
@@ -197,13 +141,13 @@ registerModel({
const sessionCookie = get_cookie('im_livechat_session');
if (sessionCookie) {
this.update({ sessionCookie });
- if (localStorage.getItem(this.chatbotSessionCookieKey)) {
- this.update({ chatbotState: 'restore_session' });
+ if (localStorage.getItem(this.chatbot.sessionCookieKey)) {
+ this.chatbot.update({ state: 'restore_session' });
}
}
}
- if (this.chatbotState === 'init') {
+ if (this.chatbot.state === '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) {
@@ -213,7 +157,7 @@ registerModel({
},
});
}
- } else if (this.chatbotState === 'welcome') {
+ } else if (this.chatbot.state === '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
@@ -222,10 +166,10 @@ registerModel({
set_cookie('im_livechat_auto_popup', '', -1);
this.update({ history: clear() });
this.update({ rule: this.livechatInit.rule });
- } else if (this.chatbotState === 'restore_session') {
+ } else if (this.chatbot.state === 'restore_session') {
// we landed on a website page and a chatbot script is currently running
- // -> restore the user's session (see 'chatbotRestoreSession')
- this.livechatButtonView.chatbotRestoreSession();
+ // -> restore the user's session (see 'Chatbot/restoreSession')
+ this.chatbot.restoreSession();
}
},
},
@@ -247,16 +191,13 @@ registerModel({
compute: '_computeChannelId',
}),
chatbot: one('Chatbot', {
- compute: '_computeChatbot',
+ default: {},
inverse: 'publicLivechatGlobalOwner',
isCausal: true,
}),
- chatbotServerUrl: attr(),
- chatbotSessionCookieKey: attr({
- compute: '_computeChatbotSessionCookieKey',
- }),
- chatbotState: attr({
- compute: '_computeChatbotState',
+ chatWindow: one('PublicLivechatWindow', {
+ inverse: 'publicLivechatGlobalOwner',
+ isCausal: true,
}),
feedbackView: one('PublicLivechatFeedbackView', {
inverse: 'publicLivechatGlobalOwner',
@@ -281,9 +222,6 @@ registerModel({
isTestChatbot: attr({
default: false,
}),
- isWebsiteLivechatChatbotFlow: attr({
- default: false,
- }),
lastMessage: one('PublicLivechatMessage', {
compute: '_computeLastMessage',
}),
@@ -293,9 +231,6 @@ registerModel({
isCausal: true,
}),
livechatInit: attr(),
- localStorageChatbotState: attr({
- compute: '_computeLocalStorageChatbotState',
- }),
messages: many('PublicLivechatMessage'),
notificationHandler: one('PublicLivechatGlobalNotificationHandler', {
inverse: 'publicLivechatGlobalOwner',
@@ -313,5 +248,6 @@ registerModel({
default: '',
}),
sessionCookie: attr(),
+ testChatbotData: attr(),
},
});
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 abd6fad3314..53d1c3ba161 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
@@ -64,7 +64,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.publicLivechatView.widget.isAtBottom()) {
+ if (this.messaging.publicLivechatGlobal.publicLivechat.isFolded || !this.messaging.publicLivechatGlobal.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_window.js b/addons/im_livechat/static/src/models/public_livechat_window.js
index 67387d9dea0..be8fa4b7de4 100644
--- a/addons/im_livechat/static/src/models/public_livechat_window.js
+++ b/addons/im_livechat/static/src/models/public_livechat_window.js
@@ -11,7 +11,7 @@ registerModel({
_created() {
this.update({
legacyChatWindow: new PublicLivechatWindow(
- this.livechatButtonViewOwner.widget,
+ this.messaging.publicLivechatGlobal.livechatButtonView.widget,
this.messaging,
this.messaging.publicLivechatGlobal.publicLivechat.legacyPublicLivechat,
),
@@ -40,7 +40,7 @@ registerModel({
legacyChatWindow: attr({
default: null,
}),
- livechatButtonViewOwner: one('LivechatButtonView', {
+ publicLivechatGlobalOwner: one('PublicLivechatGlobal', {
identifying: true,
inverse: 'chatWindow',
}),
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 296f13351d2..a84bb3a5ccb 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
@@ -26,7 +26,7 @@ const LivechatButtonTestChatbot = LivechatButton.extend({
},
});
this.messaging.publicLivechatGlobal.update({ isTestChatbot: true });
- this.messaging.publicLivechatGlobal.livechatButtonView.update({ testChatbotData: chatbotData.chatbot });
+ this.messaging.publicLivechatGlobal.update({ testChatbotData: chatbotData.chatbot });
this.messaging.publicLivechatGlobal.chatbot.update({
currentStep: {
data: this.messaging.publicLivechatGlobal.chatbot.lastWelcomeStep,
@@ -52,7 +52,7 @@ publicWidget.registry.livechatChatbotTestScript = publicWidget.Widget.extend({
const messaging = await this.env.services.messaging.get();
return this._super(...arguments).then(() => {
messaging.update({
- publicLivechatGlobal: { isAvailable: true, chatbotServerUrl: this.$el.data().serverUrl },
+ publicLivechatGlobal: { isAvailable: true, chatbot: { serverUrl: this.$el.data().serverUrl } },
});
this.livechatButton = new LivechatButtonTestChatbot(this, messaging, this.$el.data());
this.livechatButton.appendTo(document.body);
diff --git a/addons/website_livechat/static/src/models/chatbot.js b/addons/website_livechat/static/src/models/chatbot.js
new file mode 100644
index 00000000000..8cb790cf738
--- /dev/null
+++ b/addons/website_livechat/static/src/models/chatbot.js
@@ -0,0 +1,40 @@
+/** @odoo-module **/
+
+import { addFields, patchRecordMethods } from '@mail/model/model_core';
+import { attr } from '@mail/model/model_field';
+// ensure that the model definition is loaded before the patch
+import '@im_livechat/models/chatbot';
+
+addFields('Chatbot', {
+ isWebsiteLivechatTourFlow: attr({
+ default: false,
+ }),
+});
+
+patchRecordMethods('Chatbot', {
+ /**
+ * @override
+ */
+ _computeAwaitUserInputDebounceTime() {
+ if (this.isWebsiteLivechatTourFlow) {
+ /**
+ * Let us make it a bit faster than the default delay (3500ms).
+ * Let us also debounce waiting for more user inputs for only 500ms.
+ */
+ return 500;
+ }
+ return this._super();
+ },
+ /**
+ * @private
+ * @returns {integer|FieldCommand}
+ */
+ _computeMessageDelay() {
+ if (this.isWebsiteLivechatTourFlow) {
+ return 100;
+ }
+ return this._super();
+ },
+});
+
+
diff --git a/addons/website_livechat/static/src/models/livechat_button_view.js b/addons/website_livechat/static/src/models/livechat_button_view.js
index a4a69300009..9d2df13064b 100644
--- a/addons/website_livechat/static/src/models/livechat_button_view.js
+++ b/addons/website_livechat/static/src/models/livechat_button_view.js
@@ -16,7 +16,7 @@ patchRecordMethods('LivechatButtonView', {
* @override
*/
async onChatbotRestartScript(ev) {
- if (this.messaging.publicLivechatGlobal.isWebsiteLivechatChatbotFlow) {
+ if (this.messaging.publicLivechatGlobal.chatbot.isWebsiteLivechatTourFlow) {
this.messaging.publicLivechatGlobal.update({ messages: clear() });
this.widget._renderMessages();
}
@@ -45,7 +45,7 @@ patchRecordMethods('LivechatButtonView', {
return this._super();
}
this.messaging.publicLivechatGlobal.update({
- publicLivechat: { data: this.testChatbotData.channel },
+ publicLivechat: { data: this.messaging.publicLivechatGlobal.testChatbotData.channel },
});
await this.openChatWindow();
this.widget._sendWelcomeMessage();
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 90766eeff78..a074b29584b 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
@@ -1,21 +1,19 @@
/** @odoo-module */
import tour from "web_tour.tour";
-import LivechatButton from '@im_livechat/legacy/widgets/livechat_button';
-LivechatButton.include({
+import { patchRecordMethods } from '@mail/model/model_core';
+// ensure that the model definition is loaded before the patch
+import '@im_livechat/models/livechat_button_view';
+
+patchRecordMethods('LivechatButtonView', {
/**
* Let us make it a bit faster than the default delay (3500ms).
* Let us also debounce waiting for more user inputs for only 500ms.
*/
- start: function () {
- this.messaging.publicLivechatGlobal.update({ isWebsiteLivechatChatbotFlow: true });
-
- this._debouncedChatbotAwaitUserInput = _.debounce(
- this._chatbotAwaitUserInput.bind(this),
- 500);
-
- return this._super(...arguments);
+ start() {
+ this.messaging.publicLivechatGlobal.chatbot.update({ isWebsiteLivechatTourFlow: true });
+ this._super();
},
});