Compare commits

...
28 Commits
Author SHA1 Message Date
Shivam Mishra aadd2b1992 chore: remove testing timeout 2023-07-10 15:04:29 +05:30
Shivam Mishra 3b45b96ba7 refactor: manually track programatic scrolling 2023-07-10 15:04:29 +05:30
Shivam Mishra c6bd4f2f72 feat: update message view scroll behaviour 2023-07-10 15:04:29 +05:30
Shivam Mishra 19c3eab9c9 feat: fetch new suggestions when a message arrives 2023-07-10 15:04:29 +05:30
Shivam Mishra 5b1c89eabb feat: fetch suggestions and scroll if required 2023-07-10 15:04:28 +05:30
Shivam Mishra 0ff3680fea refactor: depend on the parent to send suggestions 2023-07-10 15:04:28 +05:30
Shivam Mishra 0dd826a900 feat: minor UI tweak 2023-07-10 15:04:28 +05:30
Shivam Mishra 946d62552f feat: update dismiss layout 2023-07-10 15:04:28 +05:30
Shivam Mishra 8c02669000 refactor: component rendering in parent 2023-07-10 15:04:28 +05:30
Shivam Mishra edf7642740 feat: only enable for enterprise 2023-07-10 15:04:28 +05:30
Shivam Mishra 91227e7120 feat: use immidiate watch 2023-07-10 15:04:27 +05:30
Shivam Mishra 9038ec7af9 fix: import 2023-07-10 15:04:27 +05:30
Shivam Mishra 37cc448077 feat: track usage 2023-07-10 15:04:27 +05:30
Shivam Mishra 5164c64b4b feat: store dismissed 2023-07-10 15:04:27 +05:30
Shivam Mishra e67ff5536b feat: replace svg paths with icon component 2023-07-10 15:04:09 +05:30
Shivam Mishra 675796b5c5 feat: update button copy 2023-07-10 15:04:09 +05:30
Shivam Mishra 2da45340a4 feat: add tooltip and translations 2023-07-10 15:04:09 +05:30
Shivam Mishra f62b0397c8 feat: link update method 2023-07-10 15:04:09 +05:30
Shivam Mishra 150bf1319a feat: re-fetch labels on conv-id change 2023-07-10 15:04:09 +05:30
Shivam Mishra 00d9ce4d47 fix: getter not included 2023-07-10 15:04:09 +05:30
Shivam Mishra 3f44808332 feat: fetch label suggestions from the hook 2023-07-10 15:04:08 +05:30
Shivam Mishra 3d3fd2f2bf refactor: move fetch to a separate method 2023-07-10 15:04:08 +05:30
Shivam Mishra b9a54f8524 feat: add event for OpenAI label apply 2023-07-10 15:04:08 +05:30
Shivam Mishra 43de536475 refactor: move computed methods to aiMixin 2023-07-10 15:04:08 +05:30
Shivam Mishra fe87285a30 feat: verify if labels are in store 2023-07-10 15:01:51 +05:30
Shivam Mishra 2acee36084 feat: kickstart label suggestion component 2023-07-10 15:01:51 +05:30
Shivam Mishra e674a117a8 feat: allow slot in avatar and thumbnail 2023-07-10 15:01:50 +05:30
Shivam Mishra eb09ac6706 feat: allow dashed labels 2023-07-10 15:01:50 +05:30
13 changed files with 510 additions and 55 deletions
@@ -65,6 +65,10 @@ export default {
type: String,
default: '',
},
dashed: {
type: Boolean,
default: false,
},
},
computed: {
textColor() {
@@ -74,7 +78,7 @@ export default {
labelClass() {
return `label ${this.colorScheme} ${this.variant} ${
this.small ? 'small' : ''
}`;
} ${this.dashed ? 'dashed' : ''}`;
},
labelStyle() {
if (this.bgColor) {
@@ -199,9 +203,16 @@ export default {
&.smooth {
background: transparent;
border: 1px solid var(--s-100);
color: var(--s-700);
}
&.smooth:not(.dashed) {
border: 1px solid var(--s-100);
}
&.dashed {
border: 1px dashed var(--s-100);
}
}
.label-close--button {
@@ -70,16 +70,15 @@
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { mixin as clickaway } from 'vue-clickaway';
import OpenAPI from 'dashboard/api/integrations/openapi';
import alertMixin from 'shared/mixins/alertMixin';
import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
import aiMixin from 'dashboard/mixins/aiMixin';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
export default {
mixins: [alertMixin, clickaway, eventListenerMixins],
mixins: [aiMixin, alertMixin, clickaway, eventListenerMixins],
props: {
conversationId: {
type: Number,
@@ -119,29 +118,12 @@ export default {
};
},
computed: {
...mapGetters({ appIntegrations: 'integrations/getAppIntegrations' }),
isAIIntegrationEnabled() {
return this.appIntegrations.find(
integration => integration.id === 'openai' && !!integration.hooks.length
);
},
hookId() {
return this.appIntegrations.find(
integration => integration.id === 'openai' && !!integration.hooks.length
).hooks[0].id;
},
buttonText() {
return this.uiFlags.isRephrasing
? this.$t('INTEGRATION_SETTINGS.OPEN_AI.BUTTONS.GENERATING')
: this.$t('INTEGRATION_SETTINGS.OPEN_AI.BUTTONS.GENERATE');
},
},
mounted() {
if (!this.appIntegrations.length) {
this.$store.dispatch('integrations/get');
}
},
methods: {
onKeyDownHandler(event) {
const keyPattern = buildHotKeys(event);
@@ -159,15 +141,6 @@ export default {
closeDropdown() {
this.showDropdown = false;
},
async recordAnalytics({ type, tone }) {
const event = OPEN_AI_EVENTS[type.toUpperCase()];
if (event) {
this.$track(event, {
type,
tone,
});
}
},
async processEvent(type = 'rephrase') {
this.uiFlags[type] = true;
try {
@@ -184,7 +157,7 @@ export default {
this.initialMessage = this.message;
this.$emit('replace-text', generatedMessage || this.message);
this.closeDropdown();
this.recordAnalytics({ type, tone: this.activeTone });
this.recordAnalytics(type, { tone: this.activeTone });
} catch (error) {
this.showAlert(this.$t('INTEGRATION_SETTINGS.OPEN_AI.GENERATE_ERROR'));
} finally {
@@ -1,6 +1,6 @@
<template>
<div class="avatar-container" :style="style" aria-hidden="true">
{{ userInitial }}
<slot>{{ userInitial }}</slot>
</div>
</template>
@@ -5,19 +5,21 @@
:title="title"
>
<!-- Using v-show instead of v-if to avoid flickering as v-if removes dom elements. -->
<img
v-show="shouldShowImage"
:src="src"
:class="thumbnailClass"
@load="onImgLoad"
@error="onImgError"
/>
<Avatar
v-show="!shouldShowImage"
:username="userNameWithoutEmoji"
:class="thumbnailClass"
:size="avatarSize"
/>
<slot>
<img
v-show="shouldShowImage"
:src="src"
:class="thumbnailClass"
@load="onImgLoad"
@error="onImgError"
/>
<Avatar
v-show="!shouldShowImage"
:username="userNameWithoutEmoji"
:class="thumbnailClass"
:size="avatarSize"
/>
</slot>
<img
v-if="badgeSrc"
class="source-badge"
@@ -64,6 +64,12 @@
:has-instagram-story="hasInstagramStory"
:is-web-widget-inbox="isAWebWidgetInbox"
/>
<conversation-label-suggestion
v-if="isEnterprise && isAIIntegrationEnabled"
:suggested-labels="labelSuggestions"
:chat-labels="currentChat.labels"
:conversation-id="currentChat.id"
/>
</ul>
<div
class="conversation-footer"
@@ -91,29 +97,47 @@
</template>
<script>
import { mapGetters } from 'vuex';
// components
import ReplyBox from './ReplyBox';
import Message from './Message';
import ConversationLabelSuggestion from './conversation/LabelSuggestion';
import Banner from 'dashboard/components/ui/Banner.vue';
// stores and apis
import { mapGetters } from 'vuex';
// mixins
import conversationMixin, {
filterDuplicateSourceMessages,
} from '../../../mixins/conversations';
import Banner from 'dashboard/components/ui/Banner.vue';
import { getTypingUsersText } from '../../../helper/commons';
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { REPLY_POLICY } from 'shared/constants/links';
import inboxMixin from 'shared/mixins/inboxMixin';
import configMixin from 'shared/mixins/configMixin';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import aiMixin from 'dashboard/mixins/aiMixin';
// utils
import { getTypingUsersText } from '../../../helper/commons';
import { calculateScrollTop } from './helpers/scrollTopCalculationHelper';
import { isEscape } from 'shared/helpers/KeyboardHelpers';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
// constants
import { BUS_EVENTS } from 'shared/constants/busEvents';
import { REPLY_POLICY } from 'shared/constants/links';
export default {
components: {
Message,
ReplyBox,
Banner,
ConversationLabelSuggestion,
},
mixins: [conversationMixin, inboxMixin, eventListenerMixins],
mixins: [
conversationMixin,
inboxMixin,
eventListenerMixins,
configMixin,
aiMixin,
],
props: {
isContactPanelOpen: {
type: Boolean,
@@ -127,7 +151,10 @@ export default {
heightBeforeLoad: null,
conversationPanel: null,
selectedTweetId: null,
hasUserScrolled: false,
isProgrammaticScroll: false,
isPopoutReplyBox: false,
labelSuggestions: [],
};
},
@@ -138,6 +165,7 @@ export default {
inboxesList: 'inboxes/getInboxes',
listLoadingStatus: 'getAllMessagesLoaded',
loadingChatList: 'getChatListLoadingStatus',
appIntegrations: 'integrations/getAppIntegrations',
}),
inboxId() {
return this.currentChat.inbox_id;
@@ -280,18 +308,22 @@ export default {
return;
}
this.fetchAllAttachmentsFromCurrentChat();
this.fetchSuggestions();
this.selectedTweetId = null;
},
},
created() {
bus.$on(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
// when a new message comes in, we refetch the label suggestions
bus.$on(BUS_EVENTS.FETCH_LABEL_SUGGESTIONS, this.fetchSuggestions);
bus.$on(BUS_EVENTS.SET_TWEET_REPLY, this.setSelectedTweet);
},
mounted() {
this.addScrollListener();
this.fetchAllAttachmentsFromCurrentChat();
this.fetchSuggestions();
},
beforeDestroy() {
@@ -300,6 +332,37 @@ export default {
},
methods: {
async fetchSuggestions() {
// start empty, this ensures that the label suggestions are not shown
this.labelSuggestions = [];
// method available in mixin, need to ensure that integrations are present
await this.fetchIntegrationsIfRequired();
if (!this.isAIIntegrationEnabled) {
return;
}
this.labelSuggestions = await this.fetchLabelSuggestions({
conversationId: this.currentChat.id,
});
// once the labels are fetched, we need to scroll to bottom
// but we need to wait for the DOM to be updated
// so we use the nextTick method
this.$nextTick(() => {
// this param is added to route, telling the UI to navigate to the message
// it is triggered by the SCROLL_TO_MESSAGE method
// see setActiveChat on ConversationView.vue for more info
const { messageId } = this.$route.query;
// only trigger the scroll to bottom if the user has not scrolled
// and there's no active messageId that is selected in view
if (!messageId && !this.hasUserScrolled) {
this.scrollToBottom();
}
});
},
fetchAllAttachmentsFromCurrentChat() {
this.$store.dispatch('fetchAllAttachments', this.currentChat.id);
},
@@ -314,6 +377,7 @@ export default {
this.$nextTick(() => {
const messageElement = document.getElementById('message' + messageId);
if (messageElement) {
this.isProgrammaticScroll = true;
messageElement.scrollIntoView({ behavior: 'smooth' });
this.fetchPreviousMessages();
} else {
@@ -344,18 +408,34 @@ export default {
this.conversationPanel.removeEventListener('scroll', this.handleScroll);
},
scrollToBottom() {
this.isProgrammaticScroll = true;
let relevantMessages = [];
// label suggestions are not part of the messages list
// so we need to handle them separately
let labelSuggestions = this.conversationPanel.querySelector(
'.label-suggestion'
);
// if there are unread messages, scroll to the first unread message
if (this.unreadMessageCount > 0) {
// capturing only the unread messages
relevantMessages = this.conversationPanel.querySelectorAll(
'.message--unread'
);
} else if (labelSuggestions) {
// when scrolling to the bottom, the label suggestions is below the last message
// so we scroll there if there are no unread messages
// Unread messages always take the highest priority
relevantMessages = [labelSuggestions];
} else {
// if there are no unread messages or label suggestion, scroll to the last message
// capturing last message from the messages list
relevantMessages = Array.from(
this.conversationPanel.querySelectorAll('.message--read')
).slice(-1);
}
this.conversationPanel.scrollTop = calculateScrollTop(
this.conversationPanel.scrollHeight,
this.$el.scrollHeight,
@@ -402,6 +482,13 @@ export default {
},
handleScroll(e) {
if (this.isProgrammaticScroll) {
// Reset the flag
this.isProgrammaticScroll = false;
this.hasUserScrolled = false;
} else {
this.hasUserScrolled = true;
}
bus.$emit(BUS_EVENTS.ON_MESSAGE_LIST_SCROLL);
this.fetchPreviousMessages(e.target.scrollTop);
},
@@ -0,0 +1,295 @@
<template>
<li v-if="shouldShowSuggestions" class="label-suggestion right">
<div class="wrap">
<div class="label-suggestion--container">
<h6 class="label-suggestion--title">Suggested labels</h6>
<div v-if="!fetchingSuggestions" class="label-suggestion--options">
<button
v-for="label in preparedLabels"
:key="label.title"
v-tooltip.top="{
content: selectedLabels.includes(label.title)
? $t('LABEL_MGMT.SUGGESTIONS.TOOLTIP.DESELECT')
: labelTooltip,
delay: { show: 600, hide: 0 },
hideOnClick: true,
}"
class="label-suggestion--option"
@click="pushOrAddLabel(label.title)"
>
<woot-label
variant="smooth"
:dashed="true"
v-bind="label"
:bg-color="
selectedLabels.includes(label.title) ? 'var(--w-100)' : ''
"
/>
</button>
<woot-button
v-if="preparedLabels.length === 1"
v-tooltip.top="{
content: $t('LABEL_MGMT.SUGGESTIONS.TOOLTIP.DISMISS'),
delay: { show: 600, hide: 0 },
hideOnClick: true,
}"
variant="smooth"
class="label--add"
icon="dismiss"
size="tiny"
@click="dismissSuggestions"
/>
</div>
<div v-if="preparedLabels.length > 1">
<woot-button
variant="smooth"
class="label--add"
icon="add"
size="tiny"
@click="addAllLabels"
>
{{ addButtonText }}
</woot-button>
<woot-button
v-tooltip.top="{
content: $t('LABEL_MGMT.SUGGESTIONS.TOOLTIP.DISMISS'),
delay: { show: 600, hide: 0 },
hideOnClick: true,
}"
variant="smooth"
class="label--add"
icon="dismiss"
size="tiny"
@click="dismissSuggestions"
/>
</div>
</div>
<div class="sender--info has-tooltip" data-original-title="null">
<woot-thumbnail
v-tooltip.top="{
content: $t('LABEL_MGMT.SUGGESTIONS.POWERED_BY'),
delay: { show: 600, hide: 0 },
hideOnClick: true,
}"
size="16px"
>
<avatar class="user-thumbnail thumbnail-rounded">
<fluent-icon class="chatwoot-ai-icon" icon="chatwoot-ai" />
</avatar>
</woot-thumbnail>
</div>
</div>
</li>
</template>
<script>
// components
import WootButton from '../../../ui/WootButton.vue';
import Avatar from '../../Avatar.vue';
import aiMixin from 'dashboard/mixins/aiMixin';
// store & api
import { mapGetters } from 'vuex';
// utils & constants
import { LocalStorage } from 'shared/helpers/localStorage';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { OPEN_AI_EVENTS } from '../../../../helper/AnalyticsHelper/events';
export default {
name: 'LabelSuggestion',
components: {
Avatar,
WootButton,
},
mixins: [aiMixin],
props: {
suggestedLabels: {
type: Array,
required: true,
},
chatLabels: {
type: Array,
required: false,
default: () => [],
},
conversationId: {
type: Number,
required: true,
},
},
data() {
return {
isDismissed: false,
fetchingSuggestions: false,
selectedLabels: [],
};
},
computed: {
...mapGetters({
allLabels: 'labels/getLabels',
currentAccountId: 'getCurrentAccountId',
}),
labelTooltip() {
if (this.preparedLabels.length > 1) {
return this.$t('LABEL_MGMT.SUGGESTIONS.TOOLTIP.MULTIPLE_SUGGESTION');
}
return this.$t('LABEL_MGMT.SUGGESTIONS.TOOLTIP.SINGLE_SUGGESTION');
},
addButtonText() {
if (this.selectedLabels.length === 1) {
return this.$t('LABEL_MGMT.SUGGESTIONS.ADD_SELECTED_LABEL');
}
if (this.selectedLabels.length > 1) {
return this.$t('LABEL_MGMT.SUGGESTIONS.ADD_SELECTED_LABELS');
}
return this.$t('LABEL_MGMT.SUGGESTIONS.ADD_ALL_LABELS');
},
preparedLabels() {
return this.allLabels.filter(label =>
this.suggestedLabels.includes(label.title)
);
},
shouldShowSuggestions() {
if (this.isDismissed) return false;
if (!this.isAIIntegrationEnabled) return false;
return (
!this.fetchingSuggestions &&
this.preparedLabels.length &&
this.chatLabels.length === 0
);
},
},
watch: {
conversationId: {
immediate: true,
handler() {
this.selectedLabels = [];
this.isDismissed = this.isConversationDismissed();
},
},
},
methods: {
pushOrAddLabel(label) {
if (this.preparedLabels.length === 1) {
this.addAllLabels();
return;
}
if (!this.selectedLabels.includes(label)) {
this.selectedLabels.push(label);
} else {
this.selectedLabels = this.selectedLabels.filter(l => l !== label);
}
},
dismissSuggestions() {
const dismissed = this.getDismissedConversations();
dismissed[this.currentAccountId].push(this.conversationId);
LocalStorage.set(
LOCAL_STORAGE_KEYS.DISMISSED_LABEL_SUGGESTIONS,
dismissed
);
// dismiss this once the values are set
this.isDismissed = true;
this.trackLabelEvent(OPEN_AI_EVENTS.DISMISS_LABEL_SUGGESTION);
},
isConversationDismissed() {
const dismissed = this.getDismissedConversations();
return dismissed[this.currentAccountId].includes(this.conversationId);
},
getDismissedConversations() {
const suggestionKey = LOCAL_STORAGE_KEYS.DISMISSED_LABEL_SUGGESTIONS;
// fetch the value from Storage
const valueFromStorage = LocalStorage.get(suggestionKey);
// Case 1: the key is not initialized
if (!valueFromStorage) {
LocalStorage.set(suggestionKey, {
[this.currentAccountId]: [],
});
return LocalStorage.get(suggestionKey);
}
// Case 2: the key is initialized, but account ID is not present
if (!valueFromStorage[this.currentAccountId]) {
valueFromStorage[this.currentAccountId] = [];
LocalStorage.set(suggestionKey, valueFromStorage);
return LocalStorage.get(suggestionKey);
}
return valueFromStorage;
},
addAllLabels() {
let labelsToAdd = this.selectedLabels;
if (!labelsToAdd.length) {
labelsToAdd = this.preparedLabels.map(label => label.title);
}
this.$store.dispatch('conversationLabels/update', {
conversationId: this.conversationId,
labels: labelsToAdd,
});
this.trackLabelEvent(OPEN_AI_EVENTS.LABEL_SUGGESTION_APPLIED);
},
trackLabelEvent(event) {
const payload = {
conversationId: this.conversationId,
account: this.currentAccountId,
suggestions: this.suggestedLabels,
labelsApplied: this.selectedLabels.length
? this.selectedLabels
: this.suggestedLabels,
};
this.$track(event, payload);
},
},
};
</script>
<style scoped lang="scss">
.wrap {
display: flex;
}
.label-suggestion {
flex-direction: row;
justify-content: flex-end;
margin-top: var(--space-normal);
.label-suggestion--container {
max-width: 300px;
}
.label-suggestion--options {
text-align: right;
display: flex;
align-items: center;
gap: var(--space-micro);
button.label-suggestion--option {
.label {
cursor: pointer;
margin-bottom: 0;
}
}
}
.chatwoot-ai-icon {
height: var(--font-size-mini);
width: var(--font-size-mini);
}
.label-suggestion--title {
color: var(--b-600);
margin-top: var(--space-micro);
font-size: var(--font-size-micro);
}
}
</style>
@@ -3,4 +3,5 @@ export const LOCAL_STORAGE_KEYS = {
WIDGET_BUILDER: 'widgetBubble_',
DRAFT_MESSAGES: 'draftMessages',
COLOR_SCHEME: 'color_scheme',
DISMISSED_LABEL_SUGGESTIONS: 'dismissedLabelSuggestions',
};
@@ -81,4 +81,6 @@ export const OPEN_AI_EVENTS = Object.freeze({
SUMMARIZE: 'OpenAI: Used summarize',
REPLY_SUGGESTION: 'OpenAI: Used reply suggestion',
REPHRASE: 'OpenAI: Used rephrase',
APPLY_LABEL_SUGGESTION: 'OpenAI: Apply label from suggestion',
DISMISS_LABEL_SUGGESTION: 'OpenAI: Apply label from suggestion',
});
@@ -34,6 +34,19 @@
"DELETE": "Delete",
"CANCEL": "Cancel"
},
"SUGGESTIONS": {
"TOOLTIP": {
"SINGLE_SUGGESTION": "Add label to conversation",
"MULTIPLE_SUGGESTION": "Select this label",
"DESELECT": "Deselect label",
"DISMISS": "Dismiss suggestion"
},
"POWERED_BY": "Chatwoot AI",
"DISMISS": "Dismiss",
"ADD_SELECTED_LABELS": "Add selected labels",
"ADD_SELECTED_LABEL": "Add selected label",
"ADD_ALL_LABELS": "Add all labels"
},
"ADD": {
"TITLE": "Add label",
"DESC": "Labels let you group the conversations together.",
@@ -0,0 +1,63 @@
import { mapGetters } from 'vuex';
import { OPEN_AI_EVENTS } from '../helper/AnalyticsHelper/events';
import OpenAPI from '../api/integrations/openapi';
export default {
mounted() {
this.fetchIntegrationsIfRequired();
},
computed: {
...mapGetters({ appIntegrations: 'integrations/getAppIntegrations' }),
isAIIntegrationEnabled() {
return this.appIntegrations.find(
integration => integration.id === 'openai' && !!integration.hooks.length
);
},
hookId() {
return this.appIntegrations.find(
integration => integration.id === 'openai' && !!integration.hooks.length
).hooks[0].id;
},
},
methods: {
async fetchIntegrationsIfRequired() {
if (!this.appIntegrations.length) {
await this.$store.dispatch('integrations/get');
}
},
async recordAnalytics(type, payload) {
const event = OPEN_AI_EVENTS[type.toUpperCase()];
if (event) {
this.$track(event, {
type,
...payload,
});
}
},
async fetchLabelSuggestions({ conversationId }) {
try {
const result = await OpenAPI.processEvent({
type: 'label_suggestion',
hookId: this.hookId,
conversationId: conversationId,
});
const {
data: { message: labels },
} = result;
return this.cleanLabels(labels);
} catch (error) {
return [];
}
},
cleanLabels(labels) {
return labels
.toLowerCase() // Set it to lowercase
.split(',') // split the string into an array
.filter(label => label.trim()) // remove any empty strings
.filter((label, index, self) => self.indexOf(label) === index) // remove any duplicates
.map(label => label.trim()); // trim the words
},
},
};
@@ -179,6 +179,7 @@ export const mutations = {
const { conversation: { unread_count: unreadCount = 0 } = {} } = message;
chat.unread_count = unreadCount;
if (selectedChatId === conversationId) {
window.bus.$emit(BUS_EVENTS.FETCH_LABEL_SUGGESTIONS);
window.bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
}
}
@@ -201,6 +202,7 @@ export const mutations = {
};
Vue.set(allConversations, currentConversationIndex, currentConversation);
if (_state.selectedChatId === conversation.id) {
window.bus.$emit(BUS_EVENTS.FETCH_LABEL_SUGGESTIONS);
window.bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
}
} else {
@@ -218,5 +218,10 @@
"M6.03866 3.50012L5.92929 8.29274H4.86205L4.75599 3.50012H6.03866ZM5.39567 10.3609C5.1946 10.3609 5.02225 10.2902 4.87863 10.1488C4.73721 10.0074 4.6665 9.83503 4.6665 9.63175C4.6665 9.43289 4.73721 9.26275 4.87863 9.12133C5.02225 8.97992 5.1946 8.90921 5.39567 8.90921C5.59232 8.90921 5.76246 8.97992 5.90609 9.12133C6.05192 9.26275 6.12484 9.43289 6.12484 9.63175C6.12484 9.76654 6.09059 9.88917 6.02209 9.99965C5.9558 10.1101 5.86742 10.1985 5.75694 10.2648C5.64867 10.3289 5.52825 10.3609 5.39567 10.3609Z",
"M8.63887 3.50012L8.5295 8.29274H7.46226L7.3562 3.50012H8.63887ZM7.99588 10.3609C7.79481 10.3609 7.62246 10.2902 7.47883 10.1488C7.33742 10.0074 7.26671 9.83503 7.26671 9.63175C7.26671 9.43289 7.33742 9.26275 7.47883 9.12133C7.62246 8.97992 7.79481 8.90921 7.99588 8.90921C8.19253 8.90921 8.36267 8.97992 8.5063 9.12133C8.65213 9.26275 8.72505 9.43289 8.72505 9.63175C8.72505 9.76654 8.6908 9.88917 8.6223 9.99965C8.55601 10.1101 8.46763 10.1985 8.35715 10.2648C8.24888 10.3289 8.12845 10.3609 7.99588 10.3609Z"
],
"priority-low-outline": "M7.88754 3.5L7.77816 8.29261H6.71093L6.60487 3.5H7.88754ZM7.24455 10.3608C7.04347 10.3608 6.87113 10.2901 6.7275 10.1487C6.58609 10.0073 6.51538 9.83491 6.51538 9.63163C6.51538 9.43277 6.58609 9.26263 6.7275 9.12121C6.87113 8.9798 7.04347 8.90909 7.24455 8.90909C7.4412 8.90909 7.61134 8.9798 7.75496 9.12121C7.9008 9.26263 7.97371 9.43277 7.97371 9.63163C7.97371 9.76641 7.93947 9.88905 7.87097 9.99953C7.80468 10.11 7.7163 10.1984 7.60582 10.2647C7.49755 10.3288 7.37712 10.3608 7.24455 10.3608Z"
"priority-low-outline": "M7.88754 3.5L7.77816 8.29261H6.71093L6.60487 3.5H7.88754ZM7.24455 10.3608C7.04347 10.3608 6.87113 10.2901 6.7275 10.1487C6.58609 10.0073 6.51538 9.83491 6.51538 9.63163C6.51538 9.43277 6.58609 9.26263 6.7275 9.12121C6.87113 8.9798 7.04347 8.90909 7.24455 8.90909C7.4412 8.90909 7.61134 8.9798 7.75496 9.12121C7.9008 9.26263 7.97371 9.43277 7.97371 9.63163C7.97371 9.76641 7.93947 9.88905 7.87097 9.99953C7.80468 10.11 7.7163 10.1984 7.60582 10.2647C7.49755 10.3288 7.37712 10.3608 7.24455 10.3608Z",
"chatwoot-ai-outline": [
"M6 3.99988L5.728 4.01854C5.24919 4.08427 4.81038 4.32115 4.49271 4.68539C4.17505 5.04962 4.00002 5.51656 4 5.99986V7.99984H2L1.728 8.01851C1.24919 8.08423 0.810374 8.32112 0.492709 8.68535C0.175044 9.04959 2.04617e-05 9.51653 0 9.99982L0.0186659 10.2718C0.0843951 10.7506 0.321281 11.1894 0.685517 11.5071C1.04975 11.8248 1.5167 11.9998 2 11.9998H4V13.9998L4.01867 14.2718C4.0844 14.7506 4.32128 15.1894 4.68551 15.5071C5.04975 15.8247 5.5167 15.9997 6 15.9998L6.272 15.9811C7.248 15.8478 8 15.0131 8 13.9998V11.9998H10L10.272 11.9811C11.248 11.8478 12 11.0131 12 9.99982L11.9813 9.72782C11.9156 9.24902 11.6787 8.81021 11.3145 8.49255C10.9502 8.17489 10.4833 7.99986 10 7.99984H8V5.99986L7.98133 5.72786C7.9156 5.24906 7.67872 4.81025 7.31448 4.49258C6.95024 4.17492 6.4833 3.9999 6 3.99988Z",
"M14 0L13.9093 0.00622212C13.7497 0.0281315 13.6035 0.107093 13.4976 0.228504C13.3917 0.349915 13.3333 0.505562 13.3333 0.666661V1.33332H12.6667L12.576 1.33954C12.4164 1.36145 12.2701 1.44041 12.1642 1.56183C12.0584 1.68324 12 1.83888 12 1.99998L12.0062 2.09065C12.0281 2.25025 12.1071 2.39652 12.2285 2.50241C12.3499 2.60829 12.5056 2.66664 12.6667 2.66664H13.3333V3.3333L13.3396 3.42397C13.3615 3.58357 13.4404 3.72984 13.5618 3.83573C13.6833 3.94162 13.8389 3.99996 14 3.99996L14.0907 3.99374C14.416 3.9493 14.6667 3.67108 14.6667 3.3333V2.66664H15.3333L15.424 2.66042C15.7493 2.61598 16 2.33776 16 1.99998L15.9938 1.90932C15.9719 1.74971 15.8929 1.60345 15.7715 1.49756C15.6501 1.39167 15.4944 1.33333 15.3333 1.33332H14.6667V0.666661L14.6605 0.575995C14.6385 0.416393 14.5596 0.270123 14.4382 0.164236C14.3168 0.0583485 14.1611 6.79363e-06 14 0Z",
"M16 12.0001L15.8187 12.0125C15.4995 12.0563 15.2069 12.2143 14.9951 12.4571C14.7834 12.6999 14.6667 13.0112 14.6667 13.3334V14.6667H13.3333L13.152 14.6792C12.8328 14.723 12.5403 14.8809 12.3285 15.1237C12.1167 15.3665 12 15.6778 12 16L12.0124 16.1814C12.0563 16.5006 12.2142 16.7931 12.457 17.0049C12.6998 17.2167 13.0111 17.3333 13.3333 17.3334H14.6667V18.6667L14.6791 18.848C14.7229 19.1672 14.8809 19.4598 15.1237 19.6715C15.3665 19.8833 15.6778 20 16 20L16.1813 19.9876C16.832 19.8987 17.3333 19.3422 17.3333 18.6667V17.3334H18.6667L18.848 17.3209C19.4987 17.232 20 16.6756 20 16L19.9876 15.8187C19.9437 15.4995 19.7858 15.207 19.543 14.9952C19.3002 14.7834 18.9889 14.6667 18.6667 14.6667H17.3333V13.3334L17.3209 13.1521C17.2771 12.8329 17.1191 12.5403 16.8763 12.3285C16.6335 12.1168 16.3222 12.0001 16 12.0001Z"
]
}
@@ -4,6 +4,7 @@ export const BUS_EVENTS = {
START_NEW_CONVERSATION: 'START_NEW_CONVERSATION',
FOCUS_CUSTOM_ATTRIBUTE: 'FOCUS_CUSTOM_ATTRIBUTE',
SCROLL_TO_MESSAGE: 'SCROLL_TO_MESSAGE',
FETCH_LABEL_SUGGESTIONS: 'FETCH_LABEL_SUGGESTIONS',
TOGGLE_SIDEMENU: 'TOGGLE_SIDEMENU',
ON_MESSAGE_LIST_SCROLL: 'ON_MESSAGE_LIST_SCROLL',
WEBSOCKET_DISCONNECT: 'WEBSOCKET_DISCONNECT',