feat: Add CTAs for AI features (#7538)

This commit is contained in:
Muhsin Keloth
2023-08-16 08:39:41 +05:30
committed by GitHub
parent 7b8a3fcae0
commit b89c917198
7 changed files with 294 additions and 23 deletions

View File

@@ -1,6 +1,12 @@
<template>
<div v-if="isAIIntegrationEnabled" class="position-relative">
<div v-if="!isFetchingAppIntegrations">
<div v-if="isAIIntegrationEnabled" class="relative">
<AIAssistanceCTAButton
v-if="shouldShowAIAssistCTAButton"
@click="openAIAssist"
/>
<woot-button
v-else
v-tooltip.top-end="$t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST')"
icon="wand"
color-scheme="secondary"
@@ -19,23 +25,36 @@
/>
</woot-modal>
</div>
<div v-else-if="shouldShowAIAssistCTAButtonForAdmin" class="relative">
<AIAssistanceCTAButton @click="openAICta" />
<woot-modal :show.sync="showAICtaModal" :on-close="hideAICtaModal">
<AICTAModal @close="hideAICtaModal" />
</woot-modal>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import AICTAModal from './AICTAModal.vue';
import AIAssistanceModal from './AIAssistanceModal.vue';
import aiMixin from 'dashboard/mixins/aiMixin';
import adminMixin from 'dashboard/mixins/aiMixin';
import aiMixin from 'dashboard/mixins/isAdmin';
import { CMD_AI_ASSIST } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import AIAssistanceCTAButton from './AIAssistanceCTAButton.vue';
export default {
components: {
AIAssistanceModal,
AICTAModal,
AIAssistanceCTAButton,
},
mixins: [aiMixin, eventListenerMixins],
mixins: [aiMixin, eventListenerMixins, adminMixin, uiSettingsMixin],
data: () => ({
showAIAssistanceModal: false,
showAICtaModal: false,
aiOption: '',
initialMessage: '',
}),
@@ -43,6 +62,21 @@ export default {
...mapGetters({
currentChat: 'getSelectedChat',
}),
isAICTAModalDismissed() {
return this.uiSettings.is_open_ai_cta_modal_dismissed;
},
// Display a AI CTA button for admins if the AI integration has not been added yet and the AI assistance modal has not been dismissed.
shouldShowAIAssistCTAButtonForAdmin() {
return (
this.isAdmin &&
!this.isAIIntegrationEnabled &&
!this.isAICTAModalDismissed
);
},
// Display a AI CTA button for agents and other admins who have not yet opened the AI assistance modal.
shouldShowAIAssistCTAButton() {
return this.isAIIntegrationEnabled && !this.isAICTAModalDismissed;
},
},
mounted() {
@@ -67,10 +101,22 @@ export default {
this.showAIAssistanceModal = false;
},
openAIAssist() {
// Dismiss the CTA modal if it is not dismissed
if (!this.isAICTAModalDismissed) {
this.updateUISettings({
is_open_ai_cta_modal_dismissed: true,
});
}
this.initialMessage = this.draftMessage;
const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'ai_assist' });
},
hideAICtaModal() {
this.showAICtaModal = false;
},
openAICta() {
this.showAICtaModal = true;
},
onAIAssist(option) {
this.aiOption = option;
this.showAIAssistanceModal = true;

View File

@@ -0,0 +1,99 @@
<template>
<div class="relative">
<woot-button
icon="wand"
color-scheme="secondary"
variant="smooth"
size="small"
class-names="cta-btn cta-btn-light dark:cta-btn-dark hover:cta-btn-light-hover dark:hover:cta-btn-dark-hover"
@click="onClick"
>
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.AI_ASSIST') }}
</woot-button>
<div
class="radar-ping-animation absolute top-0 right-0 -mt-1 -mr-1 rounded-full w-3 h-3 bg-woot-500 dark:bg-woot-500"
/>
<div
class="absolute top-0 right-0 -mt-1 -mr-1 rounded-full w-3 h-3 bg-woot-500 dark:bg-woot-500 opacity-50"
/>
</div>
</template>
<script>
export default {
methods: {
onClick() {
this.$emit('click');
},
},
};
</script>
<style scoped>
@tailwind components;
@layer components {
/* Gradient animation */
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.cta-btn {
animation: gradient 5s ease infinite;
border: 0;
}
.cta-btn-light {
background: linear-gradient(
255.98deg,
rgba(161, 87, 246, 0.2) 15.83%,
rgba(71, 145, 247, 0.2) 81.39%
),
linear-gradient(0deg, #f2f5f8, #f2f5f8);
}
.cta-btn-dark {
background: linear-gradient(
255.98deg,
rgba(161, 87, 246, 0.2) 15.83%,
rgba(71, 145, 247, 0.2) 81.39%
),
linear-gradient(0deg, #313538, #313538);
}
.cta-btn-light-hover {
background: linear-gradient(
255.98deg,
rgba(161, 87, 246, 0.2) 15.83%,
rgba(71, 145, 247, 0.2) 81.39%
),
linear-gradient(0deg, #e3e5e7, #e3e5e7);
}
.cta-btn-dark-hover {
background: linear-gradient(
255.98deg,
rgba(161, 87, 246, 0.2) 15.83%,
rgba(71, 145, 247, 0.2) 81.39%
),
linear-gradient(0deg, #202425, #202425);
}
/* Radar ping animation */
@keyframes ping {
75%,
100% {
transform: scale(2);
opacity: 0;
}
}
.radar-ping-animation {
animation: ping 1s ease infinite;
}
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<div class="px-0 min-w-0 flex-1">
<woot-modal-header
:header-title="$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.TITLE')"
:header-content="$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.DESC')"
/>
<form
class="flex flex-wrap flex-col modal-content"
@submit.prevent="finishOpenAI"
>
<div class="mt-2 w-full">
<woot-input
v-model="value"
type="text"
:class="{ error: $v.value.$error }"
:placeholder="
$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.KEY_PLACEHOLDER')
"
@blur="$v.value.$touch"
/>
</div>
<div class="flex flex-row justify-between gap-2 py-2 px-0 w-full">
<woot-button variant="link" @click.prevent="openOpenAIDoc">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.NEED_HELP') }}
</woot-button>
<div class="flex items-center gap-1">
<woot-button variant="clear" @click.prevent="onDismiss">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.DISMISS') }}
</woot-button>
<woot-button :is-disabled="$v.value.$invalid">
{{ $t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.BUTTONS.FINISH') }}
</woot-button>
</div>
</div>
</form>
</div>
</template>
<script>
import { required } from 'vuelidate/lib/validators';
import { mapGetters } from 'vuex';
import aiMixin from 'dashboard/mixins/aiMixin';
import alertMixin from 'shared/mixins/alertMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
export default {
mixins: [aiMixin, alertMixin, uiSettingsMixin],
data() {
return {
value: '',
};
},
validations: {
value: {
required,
},
},
computed: {
...mapGetters({
appIntegrations: 'integrations/getAppIntegrations',
}),
},
methods: {
onClose() {
this.$emit('close');
},
onDismiss() {
this.showAlert(
this.$t('INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.DISMISS_MESSAGE')
);
this.updateUISettings({
is_open_ai_cta_modal_dismissed: true,
});
this.onClose();
},
async finishOpenAI() {
const payload = {
app_id: 'openai',
settings: {
api_key: this.value,
},
};
try {
await this.$store.dispatch('integrations/createHook', payload);
this.alertMessage = this.$t(
'INTEGRATION_SETTINGS.OPEN_AI.CTA_MODAL.SUCCESS_MESSAGE'
);
this.recordAnalytics(
OPEN_AI_EVENTS.ADDED_AI_INTEGRATION_VIA_CTA_BUTTON
);
this.onClose();
} catch (error) {
const errorMessage = error?.response?.data?.message;
this.alertMessage =
errorMessage || this.$t('INTEGRATION_APPS.ADD.API.ERROR_MESSAGE');
} finally {
this.showAlert(this.alertMessage);
}
},
openOpenAIDoc() {
window.open('https://www.chatwoot.com/blog/v2-17', '_blank');
},
},
};
</script>

View File

@@ -89,6 +89,8 @@ export const OPEN_AI_EVENTS = Object.freeze({
SIMPLIFY: 'OpenAI: Used simplify',
APPLY_LABEL_SUGGESTION: 'OpenAI: Apply label from suggestion',
DISMISS_LABEL_SUGGESTION: 'OpenAI: Dismiss label suggestions',
ADDED_AI_INTEGRATION_VIA_CTA_BUTTON:
'OpenAI: Added AI integration via CTA button',
DISMISS_AI_SUGGESTION: 'OpenAI: Dismiss AI suggestions',
});

View File

@@ -121,6 +121,18 @@
"CANCEL": "Cancel"
}
},
"CTA_MODAL": {
"TITLE": "Integrate with OpenAI",
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
"BUTTONS": {
"NEED_HELP": "Need help?",
"DISMISS": "Dismiss",
"FINISH": "Finish Setup"
},
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
},
"TITLE": "Improve With AI",
"SUMMARY_TITLE": "Summary with AI",
"REPLY_TITLE": "Reply suggestion with AI",

View File

@@ -10,15 +10,19 @@ export default {
},
computed: {
...mapGetters({
uiFlags: 'integrations/getUIFlags',
appIntegrations: 'integrations/getAppIntegrations',
currentChat: 'getSelectedChat',
replyMode: 'draftMessages/getReplyEditorMode',
}),
isAIIntegrationEnabled() {
return this.appIntegrations.find(
return !!this.appIntegrations.find(
integration => integration.id === 'openai' && !!integration.hooks.length
);
},
isFetchingAppIntegrations() {
return this.uiFlags.isFetching;
},
hookId() {
return this.appIntegrations.find(
integration => integration.id === 'openai' && !!integration.hooks.length

View File

@@ -225,7 +225,7 @@ export default {
if (!items.length) return '---';
return (
<div class="cell--social-profiles">
<div class="cell--social-profiles flex gap-0.5 items-center">
{items.map(
profile =>
profiles[profile] && (