diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index 0ec1139a6..710ab2888 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -2,7 +2,7 @@ import { ref, provide } from 'vue'; // composable import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; -import { useCaptain } from 'dashboard/composables/useCaptain'; +import { useLabelSuggestions } from 'dashboard/composables/useLabelSuggestions'; import { useSnakeCase } from 'dashboard/composables/useTransformKeys'; // components @@ -59,7 +59,11 @@ export default { useKeyboardEvents(keyboardEvents); - const { captainTasksEnabled, getLabelSuggestions } = useCaptain(); + const { + captainTasksEnabled, + isLabelSuggestionFeatureEnabled, + getLabelSuggestions, + } = useLabelSuggestions(); provide('contextMenuElementTarget', conversationPanelRef); @@ -67,6 +71,7 @@ export default { isPopOutReplyBox, captainTasksEnabled, getLabelSuggestions, + isLabelSuggestionFeatureEnabled, conversationPanelRef, }; }, @@ -94,7 +99,10 @@ export default { }, shouldShowLabelSuggestions() { return ( - this.isOpen && this.captainTasksEnabled && !this.messageSentSinceOpened + this.isOpen && + this.captainTasksEnabled && + this.isLabelSuggestionFeatureEnabled && + !this.messageSentSinceOpened ); }, inboxId() { @@ -282,7 +290,7 @@ export default { const existingLabels = this.currentChat?.labels || []; if (existingLabels.length > 0) return; - if (!this.captainTasksEnabled) { + if (!this.captainTasksEnabled || !this.isLabelSuggestionFeatureEnabled) { return; } diff --git a/app/javascript/dashboard/composables/spec/useCaptain.spec.js b/app/javascript/dashboard/composables/spec/useCaptain.spec.js index c5fad7a00..33fa0ce08 100644 --- a/app/javascript/dashboard/composables/spec/useCaptain.spec.js +++ b/app/javascript/dashboard/composables/spec/useCaptain.spec.js @@ -76,18 +76,6 @@ describe('useCaptain', () => { }); }); - it('gets label suggestions', async () => { - TasksAPI.labelSuggestion.mockResolvedValue({ - data: { message: 'label1, label2' }, - }); - - const { getLabelSuggestions } = useCaptain(); - const result = await getLabelSuggestions(); - - expect(TasksAPI.labelSuggestion).toHaveBeenCalledWith('123'); - expect(result).toEqual(['label1', 'label2']); - }); - it('rewrites content', async () => { TasksAPI.rewrite.mockResolvedValue({ data: { message: 'Rewritten content', follow_up_context: { id: 'ctx1' } }, @@ -193,21 +181,4 @@ describe('useCaptain', () => { await processEvent('improve', 'content', {}); expect(TasksAPI.rewrite).toHaveBeenCalled(); }); - - it('returns empty array when no conversation ID for label suggestions', async () => { - useMapGetter.mockImplementation(getter => { - const mockValues = { - 'accounts/getUIFlags': { isFetchingLimits: false }, - getSelectedChat: { id: null }, - 'draftMessages/getReplyEditorMode': 'reply', - }; - return { value: mockValues[getter] }; - }); - - const { getLabelSuggestions } = useCaptain(); - const result = await getLabelSuggestions(); - - expect(result).toEqual([]); - expect(TasksAPI.labelSuggestion).not.toHaveBeenCalled(); - }); }); diff --git a/app/javascript/dashboard/composables/useCaptain.js b/app/javascript/dashboard/composables/useCaptain.js index a109cb5c1..507f35864 100644 --- a/app/javascript/dashboard/composables/useCaptain.js +++ b/app/javascript/dashboard/composables/useCaptain.js @@ -13,20 +13,6 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import { OPEN_AI_EVENTS } from 'dashboard/helper/AnalyticsHelper/events'; import TasksAPI from 'dashboard/api/captain/tasks'; -/** - * Cleans and normalizes a list of labels. - * @param {string} labels - A comma-separated string of labels. - * @returns {string[]} An array of cleaned and unique labels. - */ -const cleanLabels = labels => { - return labels - .toLowerCase() - .split(',') - .filter(label => label.trim()) - .map(label => label.trim()) - .filter((label, index, self) => self.indexOf(label) === index); -}; - export function useCaptain() { const store = useStore(); const { t } = useI18n(); @@ -183,24 +169,6 @@ export function useCaptain() { } }; - /** - * Gets label suggestions for the current conversation. - * @returns {Promise} An array of suggested labels. - */ - const getLabelSuggestions = async () => { - if (!conversationId.value) return []; - - try { - const result = await TasksAPI.labelSuggestion(conversationId.value); - const { - data: { message: labels }, - } = result; - return cleanLabels(labels); - } catch { - return []; - } - }; - /** * Sends a follow-up message to refine a previous AI task result. * @param {Object} options - The follow-up options. @@ -264,7 +232,6 @@ export function useCaptain() { rewriteContent, summarizeConversation, getReplySuggestion, - getLabelSuggestions, followUp, processEvent, diff --git a/app/javascript/dashboard/composables/useLabelSuggestions.js b/app/javascript/dashboard/composables/useLabelSuggestions.js new file mode 100644 index 000000000..479d1f2fb --- /dev/null +++ b/app/javascript/dashboard/composables/useLabelSuggestions.js @@ -0,0 +1,80 @@ +import { computed, onMounted } from 'vue'; +import { useMapGetter, useStore } from 'dashboard/composables/store'; +import { useAccount } from 'dashboard/composables/useAccount'; +import { FEATURE_FLAGS } from 'dashboard/featureFlags'; +import TasksAPI from 'dashboard/api/captain/tasks'; + +/** + * Cleans and normalizes a list of labels. + * @param {string} labels - A comma-separated string of labels. + * @returns {string[]} An array of cleaned and unique labels. + */ +const cleanLabels = labels => { + return labels + .toLowerCase() + .split(',') + .filter(label => label.trim()) + .map(label => label.trim()) + .filter((label, index, self) => self.indexOf(label) === index); +}; + +export function useLabelSuggestions() { + const store = useStore(); + const { isCloudFeatureEnabled } = useAccount(); + const appIntegrations = useMapGetter('integrations/getAppIntegrations'); + const currentChat = useMapGetter('getSelectedChat'); + const conversationId = computed(() => currentChat.value?.id); + + const captainTasksEnabled = computed(() => { + return isCloudFeatureEnabled(FEATURE_FLAGS.CAPTAIN_TASKS); + }); + + const aiIntegration = computed( + () => + appIntegrations.value.find( + integration => integration.id === 'openai' && !!integration.hooks.length + )?.hooks[0] + ); + + const isLabelSuggestionFeatureEnabled = computed(() => { + if (aiIntegration.value) { + const { settings = {} } = aiIntegration.value || {}; + return !!settings.label_suggestion; + } + return false; + }); + + const fetchIntegrationsIfRequired = async () => { + if (!appIntegrations.value.length) { + await store.dispatch('integrations/get'); + } + }; + + /** + * Gets label suggestions for the current conversation. + * @returns {Promise} An array of suggested labels. + */ + const getLabelSuggestions = async () => { + if (!conversationId.value) return []; + + try { + const result = await TasksAPI.labelSuggestion(conversationId.value); + const { + data: { message: labels }, + } = result; + return cleanLabels(labels); + } catch { + return []; + } + }; + + onMounted(() => { + fetchIntegrationsIfRequired(); + }); + + return { + captainTasksEnabled, + isLabelSuggestionFeatureEnabled, + getLabelSuggestions, + }; +}