feat: new Captain Editor (#13235)
Co-authored-by: Aakash Bakhle <48802744+aakashb95@users.noreply.github.com> Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: iamsivin <iamsivin@gmail.com> Co-authored-by: aakashb95 <aakashbakhle@gmail.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import CopilotEditor from 'dashboard/components/widgets/WootWriter/CopilotEditor.vue';
|
||||
import CaptainLoader from 'dashboard/components/widgets/conversation/copilot/CaptainLoader.vue';
|
||||
|
||||
defineProps({
|
||||
showCopilotEditor: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isGeneratingContent: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
generatedContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isPopout: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'focus',
|
||||
'blur',
|
||||
'clearSelection',
|
||||
'contentReady',
|
||||
'send',
|
||||
]);
|
||||
|
||||
const copilotEditorContent = ref('');
|
||||
|
||||
const onFocus = () => {
|
||||
emit('focus');
|
||||
};
|
||||
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
|
||||
const clearEditorSelection = () => {
|
||||
emit('clearSelection');
|
||||
};
|
||||
|
||||
const onSend = () => {
|
||||
emit('send', copilotEditorContent.value);
|
||||
copilotEditorContent.value = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition
|
||||
mode="out-in"
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 translate-y-2 scale-[0.98]"
|
||||
enter-to-class="opacity-100 translate-y-0 scale-100"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0 scale-100"
|
||||
leave-to-class="opacity-0 translate-y-2 scale-[0.98]"
|
||||
@after-enter="emit('contentReady')"
|
||||
>
|
||||
<CopilotEditor
|
||||
v-if="showCopilotEditor && !isGeneratingContent"
|
||||
key="copilot-editor"
|
||||
v-model="copilotEditorContent"
|
||||
class="copilot-editor"
|
||||
:generated-content="generatedContent"
|
||||
:min-height="4"
|
||||
:enabled-menu-options="[]"
|
||||
:is-popout="isPopout"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@clear-selection="clearEditorSelection"
|
||||
@send="onSend"
|
||||
/>
|
||||
<div
|
||||
v-else-if="isGeneratingContent"
|
||||
key="loading-state"
|
||||
class="bg-n-iris-5 rounded min-h-16 w-full mb-4 p-4 flex items-start"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<CaptainLoader class="text-n-iris-10 size-4" />
|
||||
<span class="text-sm text-n-iris-10">
|
||||
{{ $t('CONVERSATION.REPLYBOX.COPILOT_THINKING') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.copilot-editor {
|
||||
.ProseMirror-menubar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -11,7 +11,7 @@ const openProfileSettings = () => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="my-0 mx-4 px-1 flex max-h-[8vh] items-baseline justify-between hover:bg-n-slate-1 border border-dashed border-n-weak rounded-sm overflow-auto"
|
||||
class="my-0 px-1 flex max-h-[8vh] items-baseline justify-between hover:bg-n-slate-1 border border-dashed border-n-weak rounded-sm overflow-auto"
|
||||
>
|
||||
<p class="w-fit !m-0">
|
||||
{{ $t('CONVERSATION.FOOTER.MESSAGE_SIGNATURE_NOT_CONFIGURED') }}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script>
|
||||
import { ref, provide } from 'vue';
|
||||
// composable
|
||||
import { useConfig } from 'dashboard/composables/useConfig';
|
||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||
import { useAI } from 'dashboard/composables/useAI';
|
||||
import { useCaptain } from 'dashboard/composables/useCaptain';
|
||||
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
|
||||
|
||||
// components
|
||||
@@ -49,7 +48,6 @@ export default {
|
||||
setup() {
|
||||
const isPopOutReplyBox = ref(false);
|
||||
const conversationPanelRef = ref(null);
|
||||
const { isEnterprise } = useConfig();
|
||||
|
||||
const keyboardEvents = {
|
||||
Escape: {
|
||||
@@ -61,22 +59,14 @@ export default {
|
||||
|
||||
useKeyboardEvents(keyboardEvents);
|
||||
|
||||
const {
|
||||
isAIIntegrationEnabled,
|
||||
isLabelSuggestionFeatureEnabled,
|
||||
fetchIntegrationsIfRequired,
|
||||
fetchLabelSuggestions,
|
||||
} = useAI();
|
||||
const { captainTasksEnabled, getLabelSuggestions } = useCaptain();
|
||||
|
||||
provide('contextMenuElementTarget', conversationPanelRef);
|
||||
|
||||
return {
|
||||
isEnterprise,
|
||||
isPopOutReplyBox,
|
||||
isAIIntegrationEnabled,
|
||||
isLabelSuggestionFeatureEnabled,
|
||||
fetchIntegrationsIfRequired,
|
||||
fetchLabelSuggestions,
|
||||
captainTasksEnabled,
|
||||
getLabelSuggestions,
|
||||
conversationPanelRef,
|
||||
};
|
||||
},
|
||||
@@ -104,10 +94,7 @@ export default {
|
||||
},
|
||||
shouldShowLabelSuggestions() {
|
||||
return (
|
||||
this.isOpen &&
|
||||
this.isEnterprise &&
|
||||
this.isAIIntegrationEnabled &&
|
||||
!this.messageSentSinceOpened
|
||||
this.isOpen && this.captainTasksEnabled && !this.messageSentSinceOpened
|
||||
);
|
||||
},
|
||||
inboxId() {
|
||||
@@ -291,24 +278,15 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isEnterprise) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Early exit if conversation already has labels - no need to suggest more
|
||||
const existingLabels = this.currentChat?.labels || [];
|
||||
if (existingLabels.length > 0) return;
|
||||
|
||||
// method available in mixin, need to ensure that integrations are present
|
||||
await this.fetchIntegrationsIfRequired();
|
||||
|
||||
if (!this.isLabelSuggestionFeatureEnabled) {
|
||||
if (!this.captainTasksEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.labelSuggestions = await this.fetchLabelSuggestions({
|
||||
conversationId: this.currentChat.id,
|
||||
});
|
||||
this.labelSuggestions = await this.getLabelSuggestions();
|
||||
|
||||
// once the labels are fetched, we need to scroll to bottom
|
||||
// but we need to wait for the DOM to be updated
|
||||
|
||||
@@ -12,7 +12,9 @@ import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.v
|
||||
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel.vue';
|
||||
import ReplyEmailHead from './ReplyEmailHead.vue';
|
||||
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue';
|
||||
import CopilotReplyBottomPanel from 'dashboard/components/widgets/WootWriter/CopilotReplyBottomPanel.vue';
|
||||
import ArticleSearchPopover from 'dashboard/routes/dashboard/helpcenter/components/ArticleSearch/SearchPopover.vue';
|
||||
import CopilotEditorSection from './CopilotEditorSection.vue';
|
||||
import MessageSignatureMissingAlert from './MessageSignatureMissingAlert.vue';
|
||||
import ReplyBoxBanner from './ReplyBoxBanner.vue';
|
||||
import QuotedEmailPreview from './QuotedEmailPreview.vue';
|
||||
@@ -21,6 +23,7 @@ import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vu
|
||||
import AudioRecorder from 'dashboard/components/widgets/WootWriter/AudioRecorder.vue';
|
||||
import { AUDIO_FORMATS } from 'shared/constants/messages';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import { CMD_AI_ASSIST } from 'dashboard/helper/commandbar/events';
|
||||
import {
|
||||
getMessageVariables,
|
||||
getUndefinedVariablesInMessage,
|
||||
@@ -45,6 +48,8 @@ import {
|
||||
removeSignature,
|
||||
getEffectiveChannelType,
|
||||
} from 'dashboard/helper/editorHelper';
|
||||
import { useCopilotReply } from 'dashboard/composables/useCopilotReply';
|
||||
import { useKbd } from 'dashboard/composables/utils/useKbd';
|
||||
import { isFileTypeAllowedForChannel } from 'shared/helpers/FileHelper';
|
||||
|
||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
||||
@@ -70,6 +75,8 @@ export default {
|
||||
WhatsappTemplates,
|
||||
WootMessageEditor,
|
||||
QuotedEmailPreview,
|
||||
CopilotEditorSection,
|
||||
CopilotReplyBottomPanel,
|
||||
},
|
||||
mixins: [inboxMixin, fileUploadMixin, keyboardEventListenerMixins],
|
||||
props: {
|
||||
@@ -89,6 +96,8 @@ export default {
|
||||
} = useUISettings();
|
||||
|
||||
const replyEditor = useTemplateRef('replyEditor');
|
||||
const copilot = useCopilotReply();
|
||||
const shortcutKey = useKbd(['$mod', '+', 'enter']);
|
||||
|
||||
return {
|
||||
uiSettings,
|
||||
@@ -97,6 +106,8 @@ export default {
|
||||
setQuotedReplyFlagForInbox,
|
||||
fetchQuotedReplyFlagFromUISettings,
|
||||
replyEditor,
|
||||
copilot,
|
||||
shortcutKey,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@@ -267,7 +278,7 @@ export default {
|
||||
sendMessageText = this.$t('CONVERSATION.REPLYBOX.CREATE');
|
||||
}
|
||||
const keyLabel = this.isEditorHotKeyEnabled('cmd_enter')
|
||||
? '(⌘ + ↵)'
|
||||
? `(${this.shortcutKey})`
|
||||
: '(↵)';
|
||||
return `${sendMessageText} ${keyLabel}`;
|
||||
},
|
||||
@@ -400,6 +411,9 @@ export default {
|
||||
!!this.quotedEmailText
|
||||
);
|
||||
},
|
||||
isDefaultEditorMode() {
|
||||
return !this.showAudioRecorderEditor && !this.copilot.isActive.value;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentChat(conversation, oldConversation) {
|
||||
@@ -409,6 +423,8 @@ export default {
|
||||
// This prevents overwriting user input (e.g., CC/BCC fields) when performing actions
|
||||
// like self-assign or other updates that do not actually change the conversation context
|
||||
this.setCCAndToEmailsFromLastChat();
|
||||
// Reset Copilot editor state (includes cancelling ongoing generation)
|
||||
this.copilot.reset();
|
||||
}
|
||||
|
||||
if (this.isOnPrivateNote) {
|
||||
@@ -478,6 +494,7 @@ export default {
|
||||
this.onNewConversationModalActive
|
||||
);
|
||||
emitter.on(BUS_EVENTS.INSERT_INTO_NORMAL_EDITOR, this.addIntoEditor);
|
||||
emitter.on(CMD_AI_ASSIST, this.executeCopilotAction);
|
||||
},
|
||||
unmounted() {
|
||||
document.removeEventListener('paste', this.onPaste);
|
||||
@@ -488,6 +505,7 @@ export default {
|
||||
BUS_EVENTS.NEW_CONVERSATION_MODAL,
|
||||
this.onNewConversationModalActive
|
||||
);
|
||||
emitter.off(CMD_AI_ASSIST, this.executeCopilotAction);
|
||||
},
|
||||
methods: {
|
||||
handleInsert(article) {
|
||||
@@ -613,7 +631,9 @@ export default {
|
||||
},
|
||||
'$mod+Enter': {
|
||||
action: () => {
|
||||
if (this.isAValidEvent('cmd_enter')) {
|
||||
if (this.copilot.isActive.value && this.isFocused) {
|
||||
this.onSubmitCopilotReply();
|
||||
} else if (this.isAValidEvent('cmd_enter')) {
|
||||
this.onSendReply();
|
||||
}
|
||||
},
|
||||
@@ -830,6 +850,9 @@ export default {
|
||||
this.updateEditorSelectionWith = content;
|
||||
this.onFocus();
|
||||
},
|
||||
executeCopilotAction(action, data) {
|
||||
this.copilot.execute(action, data);
|
||||
},
|
||||
clearMessage() {
|
||||
this.message = '';
|
||||
if (this.sendWithSignature && !this.isPrivate) {
|
||||
@@ -1095,6 +1118,9 @@ export default {
|
||||
togglePopout() {
|
||||
this.$emit('update:popOutReplyBox', !this.popOutReplyBox);
|
||||
},
|
||||
onSubmitCopilotReply() {
|
||||
this.message = this.copilot.accept();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1105,11 +1131,17 @@ export default {
|
||||
<ReplyTopPanel
|
||||
:mode="replyType"
|
||||
:is-reply-restricted="isReplyRestricted"
|
||||
:disabled="
|
||||
(copilot.isActive.value && copilot.isButtonDisabled.value) ||
|
||||
showAudioRecorderEditor
|
||||
"
|
||||
:is-message-length-reaching-threshold="isMessageLengthReachingThreshold"
|
||||
:characters-remaining="charactersRemaining"
|
||||
:popout-reply-box="popOutReplyBox"
|
||||
@set-reply-mode="setReplyMode"
|
||||
@toggle-popout="togglePopout"
|
||||
@toggle-copilot="copilot.toggleEditor"
|
||||
@execute-copilot-action="executeCopilotAction"
|
||||
/>
|
||||
<ArticleSearchPopover
|
||||
v-if="showArticleSearchPopover && connectedPortalSlug"
|
||||
@@ -1117,112 +1149,167 @@ export default {
|
||||
@insert="handleInsert"
|
||||
@close="onSearchPopoverClose"
|
||||
/>
|
||||
<div class="reply-box__top">
|
||||
<ReplyToMessage
|
||||
v-if="shouldShowReplyToMessage"
|
||||
:message="inReplyTo"
|
||||
@dismiss="resetReplyToMessage"
|
||||
/>
|
||||
<EmojiInput
|
||||
v-if="showEmojiPicker"
|
||||
v-on-clickaway="hideEmojiPicker"
|
||||
:class="{
|
||||
'emoji-dialog--expanded': isOnExpandedLayout || popOutReplyBox,
|
||||
}"
|
||||
:on-click="addIntoEditor"
|
||||
/>
|
||||
<ReplyEmailHead
|
||||
v-if="showReplyHead"
|
||||
v-model:cc-emails="ccEmails"
|
||||
v-model:bcc-emails="bccEmails"
|
||||
v-model:to-emails="toEmails"
|
||||
/>
|
||||
<AudioRecorder
|
||||
v-if="showAudioRecorderEditor"
|
||||
ref="audioRecorderInput"
|
||||
:audio-record-format="audioRecordFormat"
|
||||
@recorder-progress-changed="onRecordProgressChanged"
|
||||
@finish-record="onFinishRecorder"
|
||||
@play="recordingAudioState = 'playing'"
|
||||
@pause="recordingAudioState = 'paused'"
|
||||
/>
|
||||
<WootMessageEditor
|
||||
v-model="message"
|
||||
:editor-id="editorStateId"
|
||||
class="input popover-prosemirror-menu"
|
||||
:is-private="isOnPrivateNote"
|
||||
:placeholder="messagePlaceHolder"
|
||||
:update-selection-with="updateEditorSelectionWith"
|
||||
:min-height="4"
|
||||
enable-variables
|
||||
:variables="messageVariables"
|
||||
:signature="messageSignature"
|
||||
allow-signature
|
||||
:channel-type="channelType"
|
||||
:medium="inbox.medium"
|
||||
@typing-off="onTypingOff"
|
||||
@typing-on="onTypingOn"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@toggle-user-mention="toggleUserMention"
|
||||
@toggle-canned-menu="toggleCannedMenu"
|
||||
@toggle-variables-menu="toggleVariablesMenu"
|
||||
@clear-selection="clearEditorSelection"
|
||||
/>
|
||||
<QuotedEmailPreview
|
||||
v-if="shouldShowQuotedPreview"
|
||||
:quoted-email-text="quotedEmailText"
|
||||
:preview-text="quotedEmailPreviewText"
|
||||
@toggle="toggleQuotedReply"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="hasAttachments && !showAudioRecorderEditor"
|
||||
class="attachment-preview-box"
|
||||
@paste="onPaste"
|
||||
<Transition
|
||||
mode="out-in"
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 translate-y-2 scale-[0.98]"
|
||||
enter-to-class="opacity-100 translate-y-0 scale-100"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0 scale-100"
|
||||
leave-to-class="opacity-0 translate-y-2 scale-[0.98]"
|
||||
>
|
||||
<AttachmentPreview
|
||||
class="flex-col mt-4"
|
||||
:attachments="attachedFiles"
|
||||
@remove-attachment="removeAttachment"
|
||||
<div :key="copilot.editorTransitionKey.value" class="reply-box__top">
|
||||
<ReplyToMessage
|
||||
v-if="shouldShowReplyToMessage"
|
||||
:message="inReplyTo"
|
||||
@dismiss="resetReplyToMessage"
|
||||
/>
|
||||
<EmojiInput
|
||||
v-if="showEmojiPicker"
|
||||
v-on-clickaway="hideEmojiPicker"
|
||||
:class="{
|
||||
'emoji-dialog--expanded': isOnExpandedLayout || popOutReplyBox,
|
||||
}"
|
||||
:on-click="addIntoEditor"
|
||||
/>
|
||||
<ReplyEmailHead
|
||||
v-if="showReplyHead && isDefaultEditorMode"
|
||||
v-model:cc-emails="ccEmails"
|
||||
v-model:bcc-emails="bccEmails"
|
||||
v-model:to-emails="toEmails"
|
||||
/>
|
||||
<AudioRecorder
|
||||
v-if="showAudioRecorderEditor"
|
||||
ref="audioRecorderInput"
|
||||
:audio-record-format="audioRecordFormat"
|
||||
@recorder-progress-changed="onRecordProgressChanged"
|
||||
@finish-record="onFinishRecorder"
|
||||
@play="recordingAudioState = 'playing'"
|
||||
@pause="recordingAudioState = 'paused'"
|
||||
/>
|
||||
<CopilotEditorSection
|
||||
v-if="copilot.isActive.value && !showAudioRecorderEditor"
|
||||
:show-copilot-editor="copilot.showEditor.value"
|
||||
:is-generating-content="copilot.isGenerating.value"
|
||||
:generated-content="copilot.generatedContent.value"
|
||||
:is-popout="popOutReplyBox"
|
||||
:placeholder="$t('CONVERSATION.FOOTER.COPILOT_MSG_INPUT')"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@clear-selection="clearEditorSelection"
|
||||
@close="copilot.showEditor.value = false"
|
||||
@content-ready="copilot.setContentReady"
|
||||
@send="copilot.sendFollowUp"
|
||||
/>
|
||||
<WootMessageEditor
|
||||
v-else-if="!showAudioRecorderEditor"
|
||||
v-model="message"
|
||||
:editor-id="editorStateId"
|
||||
class="input popover-prosemirror-menu"
|
||||
:is-private="isOnPrivateNote"
|
||||
:placeholder="messagePlaceHolder"
|
||||
:update-selection-with="updateEditorSelectionWith"
|
||||
:min-height="4"
|
||||
enable-variables
|
||||
:variables="messageVariables"
|
||||
:signature="messageSignature"
|
||||
allow-signature
|
||||
:channel-type="channelType"
|
||||
:medium="inbox.medium"
|
||||
@typing-off="onTypingOff"
|
||||
@typing-on="onTypingOn"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@toggle-user-mention="toggleUserMention"
|
||||
@toggle-canned-menu="toggleCannedMenu"
|
||||
@toggle-variables-menu="toggleVariablesMenu"
|
||||
@clear-selection="clearEditorSelection"
|
||||
@execute-copilot-action="executeCopilotAction"
|
||||
/>
|
||||
|
||||
<QuotedEmailPreview
|
||||
v-if="shouldShowQuotedPreview && isDefaultEditorMode"
|
||||
:quoted-email-text="quotedEmailText"
|
||||
:preview-text="quotedEmailPreviewText"
|
||||
class="mb-2"
|
||||
@toggle="toggleQuotedReply"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="hasAttachments && isDefaultEditorMode"
|
||||
class="bg-transparent py-0 mb-2"
|
||||
@paste="onPaste"
|
||||
>
|
||||
<AttachmentPreview
|
||||
class="mt-2"
|
||||
:attachments="attachedFiles"
|
||||
@remove-attachment="removeAttachment"
|
||||
/>
|
||||
</div>
|
||||
<MessageSignatureMissingAlert
|
||||
v-if="
|
||||
isSignatureEnabledForInbox &&
|
||||
!isSignatureAvailable &&
|
||||
isDefaultEditorMode
|
||||
"
|
||||
class="mb-2"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
mode="out-in"
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 translate-y-2 scale-[0.98]"
|
||||
enter-to-class="opacity-100 translate-y-0 scale-100"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0 scale-100"
|
||||
leave-to-class="opacity-0 translate-y-2 scale-[0.98]"
|
||||
>
|
||||
<CopilotReplyBottomPanel
|
||||
v-if="copilot.isActive.value"
|
||||
key="copilot-bottom-panel"
|
||||
:is-generating-content="copilot.isButtonDisabled.value"
|
||||
@submit="onSubmitCopilotReply"
|
||||
@cancel="copilot.toggleEditor"
|
||||
/>
|
||||
</div>
|
||||
<MessageSignatureMissingAlert
|
||||
v-if="isSignatureEnabledForInbox && !isSignatureAvailable"
|
||||
/>
|
||||
<ReplyBottomPanel
|
||||
:conversation-id="conversationId"
|
||||
:enable-multiple-file-upload="enableMultipleFileUpload"
|
||||
:enable-whats-app-templates="showWhatsappTemplates"
|
||||
:enable-content-templates="showContentTemplates"
|
||||
:inbox="inbox"
|
||||
:is-on-private-note="isOnPrivateNote"
|
||||
:is-recording-audio="isRecordingAudio"
|
||||
:is-send-disabled="isReplyButtonDisabled"
|
||||
:is-note="isPrivate"
|
||||
:on-file-upload="onFileUpload"
|
||||
:on-send="onSendReply"
|
||||
:conversation-type="conversationType"
|
||||
:recording-audio-duration-text="recordingAudioDurationText"
|
||||
:recording-audio-state="recordingAudioState"
|
||||
:send-button-text="replyButtonLabel"
|
||||
:show-audio-recorder="showAudioRecorder"
|
||||
:show-emoji-picker="showEmojiPicker"
|
||||
:show-file-upload="showFileUpload"
|
||||
:show-quoted-reply-toggle="shouldShowQuotedReplyToggle"
|
||||
:quoted-reply-enabled="quotedReplyPreference"
|
||||
:toggle-audio-recorder-play-pause="toggleAudioRecorderPlayPause"
|
||||
:toggle-audio-recorder="toggleAudioRecorder"
|
||||
:toggle-emoji-picker="toggleEmojiPicker"
|
||||
:message="message"
|
||||
:portal-slug="connectedPortalSlug"
|
||||
:new-conversation-modal-active="newConversationModalActive"
|
||||
@select-whatsapp-template="openWhatsappTemplateModal"
|
||||
@select-content-template="openContentTemplateModal"
|
||||
@replace-text="replaceText"
|
||||
@toggle-insert-article="toggleInsertArticle"
|
||||
@toggle-quoted-reply="toggleQuotedReply"
|
||||
/>
|
||||
<ReplyBottomPanel
|
||||
v-else
|
||||
key="reply-bottom-panel"
|
||||
:conversation-id="conversationId"
|
||||
:enable-multiple-file-upload="enableMultipleFileUpload"
|
||||
:enable-whats-app-templates="showWhatsappTemplates"
|
||||
:enable-content-templates="showContentTemplates"
|
||||
:inbox="inbox"
|
||||
:is-on-private-note="isOnPrivateNote"
|
||||
:is-recording-audio="isRecordingAudio"
|
||||
:is-send-disabled="isReplyButtonDisabled"
|
||||
:is-note="isPrivate"
|
||||
:on-file-upload="onFileUpload"
|
||||
:on-send="onSendReply"
|
||||
:conversation-type="conversationType"
|
||||
:recording-audio-duration-text="recordingAudioDurationText"
|
||||
:recording-audio-state="recordingAudioState"
|
||||
:send-button-text="replyButtonLabel"
|
||||
:show-audio-recorder="showAudioRecorder"
|
||||
:show-emoji-picker="showEmojiPicker"
|
||||
:show-file-upload="showFileUpload"
|
||||
:show-quoted-reply-toggle="shouldShowQuotedReplyToggle"
|
||||
:quoted-reply-enabled="quotedReplyPreference"
|
||||
:toggle-audio-recorder-play-pause="toggleAudioRecorderPlayPause"
|
||||
:toggle-audio-recorder="toggleAudioRecorder"
|
||||
:toggle-emoji-picker="toggleEmojiPicker"
|
||||
:message="message"
|
||||
:portal-slug="connectedPortalSlug"
|
||||
:new-conversation-modal-active="newConversationModalActive"
|
||||
@select-whatsapp-template="openWhatsappTemplateModal"
|
||||
@select-content-template="openContentTemplateModal"
|
||||
@replace-text="replaceText"
|
||||
@toggle-insert-article="toggleInsertArticle"
|
||||
@toggle-quoted-reply="toggleQuotedReply"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<WhatsappTemplates
|
||||
:inbox-id="inbox.id"
|
||||
:show="showWhatsAppTemplatesModal"
|
||||
@@ -1252,13 +1339,7 @@ export default {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
.attachment-preview-box {
|
||||
@apply bg-transparent py-0 px-4;
|
||||
}
|
||||
|
||||
.reply-box {
|
||||
transition: height 2s cubic-bezier(0.37, 0, 0.63, 1);
|
||||
|
||||
@apply relative mb-2 mx-2 border border-n-weak rounded-xl bg-n-solid-1;
|
||||
|
||||
&.is-private {
|
||||
|
||||
@@ -4,7 +4,7 @@ import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
|
||||
|
||||
// composables
|
||||
import { useAI } from 'dashboard/composables/useAI';
|
||||
import { useCaptain } from 'dashboard/composables/useCaptain';
|
||||
import { useTrack } from 'dashboard/composables';
|
||||
|
||||
// store & api
|
||||
@@ -33,9 +33,9 @@ export default {
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { isAIIntegrationEnabled } = useAI();
|
||||
const { captainTasksEnabled } = useCaptain();
|
||||
|
||||
return { isAIIntegrationEnabled };
|
||||
return { captainTasksEnabled };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
},
|
||||
shouldShowSuggestions() {
|
||||
if (this.isDismissed) return false;
|
||||
if (!this.isAIIntegrationEnabled) return false;
|
||||
if (!this.captainTasksEnabled) return false;
|
||||
|
||||
return this.preparedLabels.length && this.chatLabels.length === 0;
|
||||
},
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script setup>
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon v-once icon="i-woot-captain" class="jumping-logo" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.jumping-logo {
|
||||
transform-origin: center bottom;
|
||||
animation: jump 1s cubic-bezier(0.28, 0.84, 0.42, 1) infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
@keyframes jump {
|
||||
0% {
|
||||
transform: translateY(0) scale(1, 1);
|
||||
}
|
||||
20% {
|
||||
transform: translateY(0) scale(1.05, 0.95);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-5px) scale(0.95, 1.05);
|
||||
}
|
||||
80% {
|
||||
transform: translateY(0) scale(1.02, 0.98);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0) scale(1, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user