feat: Rewrite command bar mixin to a composable (#10015)

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
Sivin Varghese
2024-08-26 15:55:59 +05:30
committed by GitHub
parent 3489783cb8
commit 7f8d718da3
24 changed files with 1547 additions and 756 deletions

View File

@@ -13,7 +13,7 @@ import wootConstants from 'dashboard/constants/globals';
import {
CMD_REOPEN_CONVERSATION,
CMD_RESOLVE_CONVERSATION,
} from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
} from 'dashboard/helper/commandbar/events';
const store = useStore();
const getters = useStoreGetters();

View File

@@ -7,7 +7,7 @@ import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import { useAI } from 'dashboard/composables/useAI';
import AICTAModal from './AICTAModal.vue';
import AIAssistanceModal from './AIAssistanceModal.vue';
import { CMD_AI_ASSIST } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import { CMD_AI_ASSIST } from 'dashboard/helper/commandbar/events';
import AIAssistanceCTAButton from './AIAssistanceCTAButton.vue';
export default {

View File

@@ -7,7 +7,7 @@ import {
CMD_MUTE_CONVERSATION,
CMD_SEND_TRANSCRIPT,
CMD_UNMUTE_CONVERSATION,
} from '../../../routes/dashboard/commands/commandBarBusEvents';
} from 'dashboard/helper/commandbar/events';
export default {
components: {

View File

@@ -6,7 +6,7 @@ import {
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
CMD_BULK_ACTION_REOPEN_CONVERSATION,
CMD_BULK_ACTION_RESOLVE_CONVERSATION,
} from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
} from 'dashboard/helper/commandbar/events';
import AgentSelector from './AgentSelector.vue';
import UpdateActions from './UpdateActions.vue';

View File

@@ -0,0 +1,197 @@
export const mockAssignableAgents = [
{
id: 1,
account_id: 1,
availability_status: 'online',
auto_offline: true,
confirmed: true,
email: 'john@doe.com',
available_name: 'John Doe',
name: 'John Doe',
role: 'administrator',
thumbnail: '',
},
];
export const mockCurrentChat = {
meta: {
sender: {
additional_attributes: {},
availability_status: 'offline',
email: null,
id: 212,
name: 'Chatwoot',
phone_number: null,
identifier: null,
thumbnail: '',
custom_attributes: {},
last_activity_at: 1723553344,
created_at: 1722588710,
},
channel: 'Channel::WebWidget',
assignee: {
id: 1,
account_id: 1,
availability_status: 'online',
auto_offline: true,
confirmed: true,
email: 'john@doe.com',
available_name: 'John Doe',
name: 'John Doe',
role: 'administrator',
thumbnail: '',
},
hmac_verified: false,
},
id: 138,
messages: [
{
id: 3348,
content: 'Hello, how can I assist you today?',
account_id: 1,
inbox_id: 1,
conversation_id: 138,
message_type: 1,
created_at: 1724398739,
updated_at: '2024-08-23T07:38:59.763Z',
private: false,
status: 'sent',
source_id: null,
content_type: 'text',
content_attributes: {},
sender_type: 'User',
sender_id: 1,
external_source_ids: {},
additional_attributes: {},
processed_message_content: 'Hello, how can I assist you today?',
sentiment: {},
conversation: {
assignee_id: 1,
unread_count: 0,
last_activity_at: 1724398739,
contact_inbox: {
source_id: '5e57317d-053b-4a72-8292-a25b9f29c401',
},
},
sender: {
id: 1,
name: 'John Doe',
available_name: 'John Doe',
avatar_url: '',
type: 'user',
availability_status: 'online',
thumbnail: '',
},
},
],
account_id: 1,
uuid: '69dd6922-2f0c-4317-8796-bbeb3679cead',
additional_attributes: {
browser: {
device_name: 'Unknown',
browser_name: 'Chrome',
platform_name: 'macOS',
browser_version: '127.0.0.0',
platform_version: '10.15.7',
},
referer: 'http://chatwoot.com/widget_tests?dark_mode=auto',
initiated_at: {
timestamp: 'Fri Aug 02 2024 15:21:18 GMT+0530 (India Standard Time)',
},
browser_language: 'en',
},
agent_last_seen_at: 1724400730,
assignee_last_seen_at: 1724400686,
can_reply: true,
contact_last_seen_at: 1723553351,
custom_attributes: {},
inbox_id: 1,
labels: ['billing'],
muted: false,
snoozed_until: null,
status: 'open',
created_at: 1722592278,
timestamp: 1724398739,
first_reply_created_at: 1722592316,
unread_count: 0,
last_non_activity_message: {},
last_activity_at: 1724398739,
priority: null,
waiting_since: 0,
sla_policy_id: 10,
applied_sla: {
id: 143,
sla_id: 10,
sla_status: 'missed',
created_at: 1722592279,
updated_at: 1722874214,
sla_description: '',
sla_name: 'Hacker SLA',
sla_first_response_time_threshold: 600,
sla_next_response_time_threshold: 240,
sla_only_during_business_hours: false,
sla_resolution_time_threshold: 259200,
},
sla_events: [
{
id: 270,
event_type: 'nrt',
meta: {
message_id: 2743,
},
updated_at: 1722592819,
created_at: 1722592819,
},
{
id: 275,
event_type: 'rt',
meta: {},
updated_at: 1722852322,
created_at: 1722852322,
},
],
allMessagesLoaded: false,
dataFetched: true,
};
export const mockTeamsList = [
{
id: 5,
name: 'design',
description: 'design team',
allow_auto_assign: true,
account_id: 1,
is_member: false,
},
];
export const mockActiveLabels = [
{
id: 16,
title: 'billing',
description: '',
color: '#D8EA19',
show_on_sidebar: true,
},
];
export const mockInactiveLabels = [
{
id: 2,
title: 'Feature Request',
description: '',
color: '#D8EA19',
show_on_sidebar: true,
},
];
export const MOCK_FEATURE_FLAGS = {
CRM: 'crm',
AGENT_MANAGEMENT: 'agent_management',
TEAM_MANAGEMENT: 'team_management',
INBOX_MANAGEMENT: 'inbox_management',
REPORTS: 'reports',
LABELS: 'labels',
CANNED_RESPONSES: 'canned_responses',
INTEGRATIONS: 'integrations',
};

View File

@@ -0,0 +1,83 @@
import { useAppearanceHotKeys } from '../useAppearanceHotKeys';
import { useI18n } from 'dashboard/composables/useI18n';
import { LocalStorage } from 'shared/helpers/localStorage';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { setColorTheme } from 'dashboard/helper/themeHelper.js';
vi.mock('dashboard/composables/useI18n');
vi.mock('shared/helpers/localStorage');
vi.mock('dashboard/helper/themeHelper.js');
describe('useAppearanceHotKeys', () => {
beforeEach(() => {
useI18n.mockReturnValue({
t: vi.fn(key => key),
});
window.matchMedia = vi.fn().mockReturnValue({ matches: false });
});
it('should return goToAppearanceHotKeys computed property', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
expect(goToAppearanceHotKeys.value).toBeDefined();
});
it('should have the correct number of appearance options', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
expect(goToAppearanceHotKeys.value.length).toBe(4); // 1 parent + 3 theme options
});
it('should have the correct parent option', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
const parentOption = goToAppearanceHotKeys.value.find(
option => option.id === 'appearance_settings'
);
expect(parentOption).toBeDefined();
expect(parentOption.children.length).toBe(3);
});
it('should have the correct theme options', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
const themeOptions = goToAppearanceHotKeys.value.filter(
option => option.parent === 'appearance_settings'
);
expect(themeOptions.length).toBe(3);
expect(themeOptions.map(option => option.id)).toEqual([
'light',
'dark',
'auto',
]);
});
it('should call setAppearance when a theme option is selected', () => {
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
const lightThemeOption = goToAppearanceHotKeys.value.find(
option => option.id === 'light'
);
lightThemeOption.handler();
expect(LocalStorage.set).toHaveBeenCalledWith(
LOCAL_STORAGE_KEYS.COLOR_SCHEME,
'light'
);
expect(setColorTheme).toHaveBeenCalledWith(false);
});
it('should handle system dark mode preference', () => {
window.matchMedia = vi.fn().mockReturnValue({ matches: true });
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
const autoThemeOption = goToAppearanceHotKeys.value.find(
option => option.id === 'auto'
);
autoThemeOption.handler();
expect(LocalStorage.set).toHaveBeenCalledWith(
LOCAL_STORAGE_KEYS.COLOR_SCHEME,
'auto'
);
expect(setColorTheme).toHaveBeenCalledWith(true);
});
});

View File

@@ -0,0 +1,102 @@
import { useBulkActionsHotKeys } from '../useBulkActionsHotKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import wootConstants from 'dashboard/constants/globals';
import { emitter } from 'shared/helpers/mitt';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables/useI18n');
vi.mock('shared/helpers/mitt');
describe('useBulkActionsHotKeys', () => {
let store;
beforeEach(() => {
store = {
getters: {
'bulkActions/getSelectedConversationIds': [],
},
};
useStore.mockReturnValue(store);
useMapGetter.mockImplementation(key => ({
value: store.getters[key],
}));
useI18n.mockReturnValue({ t: vi.fn(key => key) });
emitter.emit = vi.fn();
});
it('should return bulk actions when conversations are selected', () => {
store.getters['bulkActions/getSelectedConversationIds'] = [1, 2, 3];
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
expect(bulkActionsHotKeys.value.length).toBeGreaterThan(0);
expect(bulkActionsHotKeys.value).toContainEqual(
expect.objectContaining({
id: 'bulk_action_snooze_conversation',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
})
);
expect(bulkActionsHotKeys.value).toContainEqual(
expect.objectContaining({
id: 'bulk_action_reopen_conversation',
title: 'COMMAND_BAR.COMMANDS.REOPEN_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
})
);
expect(bulkActionsHotKeys.value).toContainEqual(
expect.objectContaining({
id: 'bulk_action_resolve_conversation',
title: 'COMMAND_BAR.COMMANDS.RESOLVE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
})
);
});
it('should include snooze options in bulk actions', () => {
store.getters['bulkActions/getSelectedConversationIds'] = [1, 2, 3];
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
const snoozeAction = bulkActionsHotKeys.value.find(
action => action.id === 'bulk_action_snooze_conversation'
);
expect(snoozeAction).toBeDefined();
expect(snoozeAction.children).toEqual(
Object.values(wootConstants.SNOOZE_OPTIONS)
);
});
it('should create handlers for reopen and resolve actions', () => {
store.getters['bulkActions/getSelectedConversationIds'] = [1, 2, 3];
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
const reopenAction = bulkActionsHotKeys.value.find(
action => action.id === 'bulk_action_reopen_conversation'
);
const resolveAction = bulkActionsHotKeys.value.find(
action => action.id === 'bulk_action_resolve_conversation'
);
expect(reopenAction.handler).toBeDefined();
expect(resolveAction.handler).toBeDefined();
reopenAction.handler();
expect(emitter.emit).toHaveBeenCalledWith(
'CMD_BULK_ACTION_REOPEN_CONVERSATION'
);
resolveAction.handler();
expect(emitter.emit).toHaveBeenCalledWith(
'CMD_BULK_ACTION_RESOLVE_CONVERSATION'
);
});
it('should return an empty array when no conversations are selected', () => {
store.getters['bulkActions/getSelectedConversationIds'] = [];
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
expect(bulkActionsHotKeys.value).toEqual([]);
});
});

View File

@@ -0,0 +1,204 @@
import { useConversationHotKeys } from '../useConversationHotKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute } from 'dashboard/composables/route';
import { useConversationLabels } from 'dashboard/composables/useConversationLabels';
import { useAI } from 'dashboard/composables/useAI';
import { useAgentsList } from 'dashboard/composables/useAgentsList';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import {
mockAssignableAgents,
mockCurrentChat,
mockTeamsList,
mockActiveLabels,
mockInactiveLabels,
} from './fixtures';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables/useI18n');
vi.mock('dashboard/composables/route');
vi.mock('dashboard/composables/useConversationLabels');
vi.mock('dashboard/composables/useAI');
vi.mock('dashboard/composables/useAgentsList');
describe('useConversationHotKeys', () => {
let store;
beforeEach(() => {
store = {
dispatch: vi.fn(),
getters: {
getSelectedChat: mockCurrentChat,
'draftMessages/getReplyEditorMode': REPLY_EDITOR_MODES.REPLY,
getContextMenuChatId: null,
'teams/getTeams': mockTeamsList,
'draftMessages/get': vi.fn(),
},
};
useStore.mockReturnValue(store);
useMapGetter.mockImplementation(key => ({
value: store.getters[key],
}));
useI18n.mockReturnValue({ t: vi.fn(key => key) });
useRoute.mockReturnValue({ name: 'inbox_conversation' });
useConversationLabels.mockReturnValue({
activeLabels: { value: mockActiveLabels },
inactiveLabels: { value: mockInactiveLabels },
addLabelToConversation: vi.fn(),
removeLabelFromConversation: vi.fn(),
});
useAI.mockReturnValue({ isAIIntegrationEnabled: { value: true } });
useAgentsList.mockReturnValue({
agentsList: { value: [] },
assignableAgents: { value: mockAssignableAgents },
});
});
it('should return the correct computed properties', () => {
const { conversationHotKeys } = useConversationHotKeys();
expect(conversationHotKeys.value).toBeDefined();
});
it('should generate conversation hot keys', () => {
const { conversationHotKeys } = useConversationHotKeys();
expect(conversationHotKeys.value.length).toBeGreaterThan(0);
});
it('should include AI assist actions when AI integration is enabled', () => {
const { conversationHotKeys } = useConversationHotKeys();
const aiAssistAction = conversationHotKeys.value.find(
action => action.id === 'ai_assist'
);
expect(aiAssistAction).toBeDefined();
});
it('should not include AI assist actions when AI integration is disabled', () => {
useAI.mockReturnValue({ isAIIntegrationEnabled: { value: false } });
const { conversationHotKeys } = useConversationHotKeys();
const aiAssistAction = conversationHotKeys.value.find(
action => action.id === 'ai_assist'
);
expect(aiAssistAction).toBeUndefined();
});
it('should dispatch actions when handlers are called', () => {
const { conversationHotKeys } = useConversationHotKeys();
const assignAgentAction = conversationHotKeys.value.find(
action => action.id === 'assign_an_agent'
);
expect(assignAgentAction).toBeDefined();
if (assignAgentAction && assignAgentAction.children) {
const childAction = conversationHotKeys.value.find(
action => action.id === assignAgentAction.children[0]
);
if (childAction && childAction.handler) {
childAction.handler({ agentInfo: { id: 2 } });
expect(store.dispatch).toHaveBeenCalledWith('assignAgent', {
conversationId: 1,
agentId: 2,
});
}
}
});
it('should return snooze actions when in snooze context', () => {
store.getters.getContextMenuChatId = 1;
useMapGetter.mockImplementation(key => ({
value: store.getters[key],
}));
useRoute.mockReturnValue({ name: 'inbox_conversation' });
const { conversationHotKeys } = useConversationHotKeys();
const snoozeAction = conversationHotKeys.value.find(action =>
action.id.includes('snooze_conversation')
);
expect(snoozeAction).toBeDefined();
});
it('should return the correct label actions when there are active labels', () => {
const { conversationHotKeys } = useConversationHotKeys();
const addLabelAction = conversationHotKeys.value.find(
action => action.id === 'add_a_label_to_the_conversation'
);
const removeLabelAction = conversationHotKeys.value.find(
action => action.id === 'remove_a_label_to_the_conversation'
);
expect(addLabelAction).toBeDefined();
expect(removeLabelAction).toBeDefined();
});
it('should return only add label actions when there are no active labels', () => {
useConversationLabels.mockReturnValue({
activeLabels: { value: [] },
inactiveLabels: { value: [{ title: 'inactive_label' }] },
addLabelToConversation: vi.fn(),
removeLabelFromConversation: vi.fn(),
});
const { conversationHotKeys } = useConversationHotKeys();
const addLabelAction = conversationHotKeys.value.find(
action => action.id === 'add_a_label_to_the_conversation'
);
const removeLabelAction = conversationHotKeys.value.find(
action => action.id === 'remove_a_label_to_the_conversation'
);
expect(addLabelAction).toBeDefined();
expect(removeLabelAction).toBeUndefined();
});
it('should return the correct team assignment actions', () => {
const { conversationHotKeys } = useConversationHotKeys();
const assignTeamAction = conversationHotKeys.value.find(
action => action.id === 'assign_a_team'
);
expect(assignTeamAction).toBeDefined();
expect(assignTeamAction.children.length).toBe(mockTeamsList.length);
});
it('should return the correct priority assignment actions', () => {
const { conversationHotKeys } = useConversationHotKeys();
const assignPriorityAction = conversationHotKeys.value.find(
action => action.id === 'assign_priority'
);
expect(assignPriorityAction).toBeDefined();
expect(assignPriorityAction.children.length).toBe(4);
});
it('should return the correct conversation additional actions', () => {
const { conversationHotKeys } = useConversationHotKeys();
const muteAction = conversationHotKeys.value.find(
action => action.id === 'mute_conversation'
);
const sendTranscriptAction = conversationHotKeys.value.find(
action => action.id === 'send_transcript'
);
expect(muteAction).toBeDefined();
expect(sendTranscriptAction).toBeDefined();
});
it('should return unmute action when conversation is muted', () => {
store.getters.getSelectedChat = { ...mockCurrentChat, muted: true };
const { conversationHotKeys } = useConversationHotKeys();
const unmuteAction = conversationHotKeys.value.find(
action => action.id === 'unmute_conversation'
);
expect(unmuteAction).toBeDefined();
});
it('should not return conversation hot keys when not in conversation or inbox route', () => {
useRoute.mockReturnValue({ name: 'some_other_route' });
const { conversationHotKeys } = useConversationHotKeys();
expect(conversationHotKeys.value.length).toBe(0);
});
});

View File

@@ -0,0 +1,188 @@
import { useGoToCommandHotKeys } from '../useGoToCommandHotKeys';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRouter } from 'dashboard/composables/route';
import { useAdmin } from 'dashboard/composables/useAdmin';
import { frontendURL } from 'dashboard/helper/URLHelper';
import { MOCK_FEATURE_FLAGS } from './fixtures';
vi.mock('dashboard/composables/store');
vi.mock('dashboard/composables/useI18n');
vi.mock('dashboard/composables/route');
vi.mock('dashboard/composables/useAdmin');
vi.mock('dashboard/helper/URLHelper');
const mockRoutes = [
{ path: 'accounts/:accountId/dashboard', name: 'dashboard' },
{
path: 'accounts/:accountId/contacts',
name: 'contacts',
featureFlag: MOCK_FEATURE_FLAGS.CRM,
},
{
path: 'accounts/:accountId/settings/agents/list',
name: 'agent_settings',
featureFlag: MOCK_FEATURE_FLAGS.AGENT_MANAGEMENT,
},
{
path: 'accounts/:accountId/settings/teams/list',
name: 'team_settings',
featureFlag: MOCK_FEATURE_FLAGS.TEAM_MANAGEMENT,
},
{
path: 'accounts/:accountId/settings/inboxes/list',
name: 'inbox_settings',
featureFlag: MOCK_FEATURE_FLAGS.INBOX_MANAGEMENT,
},
{ path: 'accounts/:accountId/profile/settings', name: 'profile_settings' },
{ path: 'accounts/:accountId/notifications', name: 'notifications' },
{
path: 'accounts/:accountId/reports/overview',
name: 'reports_overview',
featureFlag: MOCK_FEATURE_FLAGS.REPORTS,
},
{
path: 'accounts/:accountId/settings/labels/list',
name: 'label_settings',
featureFlag: MOCK_FEATURE_FLAGS.LABELS,
},
{
path: 'accounts/:accountId/settings/canned-response/list',
name: 'canned_responses',
featureFlag: MOCK_FEATURE_FLAGS.CANNED_RESPONSES,
},
{
path: 'accounts/:accountId/settings/applications',
name: 'applications',
featureFlag: MOCK_FEATURE_FLAGS.INTEGRATIONS,
},
];
describe('useGoToCommandHotKeys', () => {
let store;
beforeEach(() => {
store = {
getters: {
getCurrentAccountId: 1,
'accounts/isFeatureEnabledonAccount': vi.fn().mockReturnValue(true),
},
};
useStore.mockReturnValue(store);
useMapGetter.mockImplementation(key => ({
value: store.getters[key],
}));
useI18n.mockReturnValue({ t: vi.fn(key => key) });
useRouter.mockReturnValue({ push: vi.fn() });
useAdmin.mockReturnValue({ isAdmin: { value: true } });
frontendURL.mockImplementation(url => url);
});
it('should return goToCommandHotKeys computed property', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
expect(goToCommandHotKeys.value).toBeDefined();
expect(goToCommandHotKeys.value.length).toBeGreaterThan(0);
});
it('should filter commands based on feature flags', () => {
store.getters['accounts/isFeatureEnabledonAccount'] = vi.fn(
(accountId, flag) => flag !== MOCK_FEATURE_FLAGS.CRM
);
const { goToCommandHotKeys } = useGoToCommandHotKeys();
mockRoutes.forEach(route => {
const command = goToCommandHotKeys.value.find(cmd =>
cmd.id.includes(route.name)
);
if (route.featureFlag === MOCK_FEATURE_FLAGS.CRM) {
expect(command).toBeUndefined();
} else if (!route.featureFlag) {
expect(command).toBeDefined();
}
});
});
it('should filter commands for non-admin users', () => {
useAdmin.mockReturnValue({ isAdmin: { value: false } });
const { goToCommandHotKeys } = useGoToCommandHotKeys();
const adminOnlyCommands = goToCommandHotKeys.value.filter(
cmd =>
cmd.id.includes('agent_settings') ||
cmd.id.includes('team_settings') ||
cmd.id.includes('inbox_settings')
);
expect(adminOnlyCommands.length).toBe(0);
});
it('should include commands for both admin and agent roles when user is admin', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
const adminCommand = goToCommandHotKeys.value.find(cmd =>
cmd.id.includes('agent_settings')
);
const agentCommand = goToCommandHotKeys.value.find(cmd =>
cmd.id.includes('profile_settings')
);
expect(adminCommand).toBeDefined();
expect(agentCommand).toBeDefined();
});
it('should translate section and title for each command', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
goToCommandHotKeys.value.forEach(command => {
expect(useI18n().t).toHaveBeenCalledWith(
expect.stringContaining('COMMAND_BAR.SECTIONS.')
);
expect(useI18n().t).toHaveBeenCalledWith(
expect.stringContaining('COMMAND_BAR.COMMANDS.')
);
expect(command.section).toBeDefined();
expect(command.title).toBeDefined();
});
});
it('should call router.push with correct URL when handler is called', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
goToCommandHotKeys.value.forEach(command => {
command.handler();
expect(useRouter().push).toHaveBeenCalledWith(expect.any(String));
});
});
it('should use current account ID in the path', () => {
store.getters.getCurrentAccountId = 42;
const { goToCommandHotKeys } = useGoToCommandHotKeys();
goToCommandHotKeys.value.forEach(command => {
command.handler();
expect(useRouter().push).toHaveBeenCalledWith(
expect.stringContaining('42')
);
});
});
it('should include icon for each command', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
goToCommandHotKeys.value.forEach(command => {
expect(command.icon).toBeDefined();
});
});
it('should return commands for all enabled features', () => {
const { goToCommandHotKeys } = useGoToCommandHotKeys();
const enabledFeatureCommands = goToCommandHotKeys.value.filter(cmd =>
mockRoutes.some(route => route.featureFlag && cmd.id.includes(route.name))
);
expect(enabledFeatureCommands.length).toBeGreaterThan(0);
});
it('should not return commands for disabled features', () => {
store.getters['accounts/isFeatureEnabledonAccount'] = vi.fn(() => false);
const { goToCommandHotKeys } = useGoToCommandHotKeys();
const disabledFeatureCommands = goToCommandHotKeys.value.filter(cmd =>
mockRoutes.some(route => route.featureFlag && cmd.id.includes(route.name))
);
expect(disabledFeatureCommands.length).toBe(0);
});
});

View File

@@ -0,0 +1,37 @@
import { useInboxHotKeys } from '../useInboxHotKeys';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute } from 'dashboard/composables/route';
import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers';
vi.mock('dashboard/composables/useI18n');
vi.mock('dashboard/composables/route');
vi.mock('dashboard/helper/routeHelpers');
vi.mock('shared/helpers/mitt');
describe('useInboxHotKeys', () => {
beforeEach(() => {
useI18n.mockReturnValue({ t: vi.fn(key => key) });
useRoute.mockReturnValue({ name: 'inbox_dashboard' });
isAInboxViewRoute.mockReturnValue(true);
});
it('should return inbox hot keys when on an inbox view route', () => {
const { inboxHotKeys } = useInboxHotKeys();
expect(inboxHotKeys.value.length).toBeGreaterThan(0);
expect(inboxHotKeys.value[0].id).toBe('snooze_notification');
});
it('should return an empty array when not on an inbox view route', () => {
isAInboxViewRoute.mockReturnValue(false);
const { inboxHotKeys } = useInboxHotKeys();
expect(inboxHotKeys.value).toEqual([]);
});
it('should have the correct structure for snooze actions', () => {
const { inboxHotKeys } = useInboxHotKeys();
const snoozeNotificationAction = inboxHotKeys.value.find(
action => action.id === 'snooze_notification'
);
expect(snoozeNotificationAction).toBeDefined();
});
});

View File

@@ -0,0 +1,70 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import {
ICON_APPEARANCE,
ICON_LIGHT_MODE,
ICON_DARK_MODE,
ICON_SYSTEM_MODE,
} from 'dashboard/helper/commandbar/icons';
import { LocalStorage } from 'shared/helpers/localStorage';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { setColorTheme } from 'dashboard/helper/themeHelper.js';
const getThemeOptions = t => [
{
key: 'light',
label: t('COMMAND_BAR.COMMANDS.LIGHT_MODE'),
icon: ICON_LIGHT_MODE,
},
{
key: 'dark',
label: t('COMMAND_BAR.COMMANDS.DARK_MODE'),
icon: ICON_DARK_MODE,
},
{
key: 'auto',
label: t('COMMAND_BAR.COMMANDS.SYSTEM_MODE'),
icon: ICON_SYSTEM_MODE,
},
];
const setAppearance = theme => {
LocalStorage.set(LOCAL_STORAGE_KEYS.COLOR_SCHEME, theme);
const isOSOnDarkMode = window.matchMedia(
'(prefers-color-scheme: dark)'
).matches;
setColorTheme(isOSOnDarkMode);
};
export function useAppearanceHotKeys() {
const { t } = useI18n();
const themeOptions = computed(() => getThemeOptions(t));
const goToAppearanceHotKeys = computed(() => {
const options = themeOptions.value.map(theme => ({
id: theme.key,
title: theme.label,
parent: 'appearance_settings',
section: t('COMMAND_BAR.SECTIONS.APPEARANCE'),
icon: theme.icon,
handler: () => {
setAppearance(theme.key);
},
}));
return [
{
id: 'appearance_settings',
title: t('COMMAND_BAR.COMMANDS.CHANGE_APPEARANCE'),
section: t('COMMAND_BAR.SECTIONS.APPEARANCE'),
icon: ICON_APPEARANCE,
children: options.map(option => option.id),
},
...options,
];
});
return {
goToAppearanceHotKeys,
};
}

View File

@@ -0,0 +1,89 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useMapGetter } from 'dashboard/composables/store';
import wootConstants from 'dashboard/constants/globals';
import {
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
CMD_BULK_ACTION_REOPEN_CONVERSATION,
CMD_BULK_ACTION_RESOLVE_CONVERSATION,
} from 'dashboard/helper/commandbar/events';
import {
ICON_SNOOZE_CONVERSATION,
ICON_REOPEN_CONVERSATION,
ICON_RESOLVE_CONVERSATION,
} from 'dashboard/helper/commandbar/icons';
import { emitter } from 'shared/helpers/mitt';
import { createSnoozeHandlers } from 'dashboard/helper/commandbar/actions';
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
const createEmitHandler = event => () => emitter.emit(event);
const SNOOZE_CONVERSATION_BULK_ACTIONS = [
{
id: 'bulk_action_snooze_conversation',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_SNOOZE_CONVERSATION,
children: Object.values(SNOOZE_OPTIONS),
},
...createSnoozeHandlers(
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
'bulk_action_snooze_conversation',
'COMMAND_BAR.SECTIONS.BULK_ACTIONS'
),
];
const RESOLVED_CONVERSATION_BULK_ACTIONS = [
{
id: 'bulk_action_reopen_conversation',
title: 'COMMAND_BAR.COMMANDS.REOPEN_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_REOPEN_CONVERSATION,
handler: createEmitHandler(CMD_BULK_ACTION_REOPEN_CONVERSATION),
},
];
const OPEN_CONVERSATION_BULK_ACTIONS = [
{
id: 'bulk_action_resolve_conversation',
title: 'COMMAND_BAR.COMMANDS.RESOLVE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_RESOLVE_CONVERSATION,
handler: createEmitHandler(CMD_BULK_ACTION_RESOLVE_CONVERSATION),
},
];
export function useBulkActionsHotKeys() {
const { t } = useI18n();
const selectedConversations = useMapGetter(
'bulkActions/getSelectedConversationIds'
);
const prepareActions = actions => {
return actions.map(action => ({
...action,
title: t(action.title),
section: t(action.section),
}));
};
const bulkActionsHotKeys = computed(() => {
let actions = [];
if (selectedConversations.value.length > 0) {
actions = [
...SNOOZE_CONVERSATION_BULK_ACTIONS,
...RESOLVED_CONVERSATION_BULK_ACTIONS,
...OPEN_CONVERSATION_BULK_ACTIONS,
];
}
return prepareActions(actions);
});
return {
bulkActionsHotKeys,
};
}

View File

@@ -0,0 +1,408 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useStore, useMapGetter } from 'dashboard/composables/store';
import { useRoute } from 'dashboard/composables/route';
import { emitter } from 'shared/helpers/mitt';
import { useConversationLabels } from 'dashboard/composables/useConversationLabels';
import { useAI } from 'dashboard/composables/useAI';
import { useAgentsList } from 'dashboard/composables/useAgentsList';
import { CMD_AI_ASSIST } from 'dashboard/helper/commandbar/events';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import wootConstants from 'dashboard/constants/globals';
import {
ICON_ADD_LABEL,
ICON_ASSIGN_AGENT,
ICON_ASSIGN_PRIORITY,
ICON_ASSIGN_TEAM,
ICON_REMOVE_LABEL,
ICON_PRIORITY_URGENT,
ICON_PRIORITY_HIGH,
ICON_PRIORITY_LOW,
ICON_PRIORITY_MEDIUM,
ICON_PRIORITY_NONE,
ICON_AI_ASSIST,
ICON_AI_SUMMARY,
ICON_AI_SHORTEN,
ICON_AI_EXPAND,
ICON_AI_GRAMMAR,
} from 'dashboard/helper/commandbar/icons';
import {
OPEN_CONVERSATION_ACTIONS,
SNOOZE_CONVERSATION_ACTIONS,
RESOLVED_CONVERSATION_ACTIONS,
SEND_TRANSCRIPT_ACTION,
UNMUTE_ACTION,
MUTE_ACTION,
} from 'dashboard/helper/commandbar/actions';
import {
isAConversationRoute,
isAInboxViewRoute,
} from 'dashboard/helper/routeHelpers';
const prepareActions = (actions, t) => {
return actions.map(action => ({
...action,
title: t(action.title),
section: t(action.section),
}));
};
const createPriorityOptions = (t, currentPriority) => {
return [
{
label: t('CONVERSATION.PRIORITY.OPTIONS.NONE'),
key: null,
icon: ICON_PRIORITY_NONE,
},
{
label: t('CONVERSATION.PRIORITY.OPTIONS.URGENT'),
key: 'urgent',
icon: ICON_PRIORITY_URGENT,
},
{
label: t('CONVERSATION.PRIORITY.OPTIONS.HIGH'),
key: 'high',
icon: ICON_PRIORITY_HIGH,
},
{
label: t('CONVERSATION.PRIORITY.OPTIONS.MEDIUM'),
key: 'medium',
icon: ICON_PRIORITY_MEDIUM,
},
{
label: t('CONVERSATION.PRIORITY.OPTIONS.LOW'),
key: 'low',
icon: ICON_PRIORITY_LOW,
},
].filter(item => item.key !== currentPriority);
};
const createNonDraftMessageAIAssistActions = (t, replyMode) => {
if (replyMode === REPLY_EDITOR_MODES.REPLY) {
return [
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.REPLY_SUGGESTION'),
key: 'reply_suggestion',
icon: ICON_AI_ASSIST,
},
];
}
return [
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SUMMARIZE'),
key: 'summarize',
icon: ICON_AI_SUMMARY,
},
];
};
const createDraftMessageAIAssistActions = t => {
return [
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.REPHRASE'),
key: 'rephrase',
icon: ICON_AI_ASSIST,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.FIX_SPELLING_GRAMMAR'),
key: 'fix_spelling_grammar',
icon: ICON_AI_GRAMMAR,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.EXPAND'),
key: 'expand',
icon: ICON_AI_EXPAND,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SHORTEN'),
key: 'shorten',
icon: ICON_AI_SHORTEN,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.MAKE_FRIENDLY'),
key: 'make_friendly',
icon: ICON_AI_ASSIST,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.MAKE_FORMAL'),
key: 'make_formal',
icon: ICON_AI_ASSIST,
},
{
label: t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SIMPLIFY'),
key: 'simplify',
icon: ICON_AI_ASSIST,
},
];
};
export function useConversationHotKeys() {
const { t } = useI18n();
const store = useStore();
const route = useRoute();
const {
activeLabels,
inactiveLabels,
addLabelToConversation,
removeLabelFromConversation,
} = useConversationLabels();
const { isAIIntegrationEnabled } = useAI();
const { agentsList } = useAgentsList();
const currentChat = useMapGetter('getSelectedChat');
const replyMode = useMapGetter('draftMessages/getReplyEditorMode');
const contextMenuChatId = useMapGetter('getContextMenuChatId');
const teams = useMapGetter('teams/getTeams');
const getDraftMessage = useMapGetter('draftMessages/get');
const conversationId = computed(() => currentChat.value?.id);
const draftKey = computed(
() => `draft-${conversationId.value}-${replyMode.value}`
);
const draftMessage = computed(() => getDraftMessage.value(draftKey.value));
const hasAnAssignedTeam = computed(() => !!currentChat.value?.meta?.team);
const teamsList = computed(() => {
if (hasAnAssignedTeam.value) {
return [{ id: 0, name: t('TEAMS_SETTINGS.LIST.NONE') }, ...teams.value];
}
return teams.value;
});
const onChangeAssignee = action => {
store.dispatch('assignAgent', {
conversationId: currentChat.value.id,
agentId: action.agentInfo.id,
});
};
const onChangePriority = action => {
store.dispatch('assignPriority', {
conversationId: currentChat.value.id,
priority: action.priority.key,
});
};
const onChangeTeam = action => {
store.dispatch('assignTeam', {
conversationId: currentChat.value.id,
teamId: action.teamInfo.id,
});
};
const statusActions = computed(() => {
const isOpen = currentChat.value?.status === wootConstants.STATUS_TYPE.OPEN;
const isSnoozed =
currentChat.value?.status === wootConstants.STATUS_TYPE.SNOOZED;
const isResolved =
currentChat.value?.status === wootConstants.STATUS_TYPE.RESOLVED;
let actions = [];
if (isOpen) {
actions = [...OPEN_CONVERSATION_ACTIONS, ...SNOOZE_CONVERSATION_ACTIONS];
} else if (isResolved || isSnoozed) {
actions = RESOLVED_CONVERSATION_ACTIONS;
}
return prepareActions(actions, t);
});
const priorityOptions = computed(() =>
createPriorityOptions(t, currentChat.value?.priority)
);
const assignAgentActions = computed(() => {
const agentOptions = agentsList.value.map(agent => ({
id: `agent-${agent.id}`,
title: agent.name,
parent: 'assign_an_agent',
section: t('COMMAND_BAR.SECTIONS.CHANGE_ASSIGNEE'),
agentInfo: agent,
icon: ICON_ASSIGN_AGENT,
handler: onChangeAssignee,
}));
return [
{
id: 'assign_an_agent',
title: t('COMMAND_BAR.COMMANDS.ASSIGN_AN_AGENT'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ASSIGN_AGENT,
children: agentOptions.map(option => option.id),
},
...agentOptions,
];
});
const assignPriorityActions = computed(() => {
const options = priorityOptions.value.map(priority => ({
id: `priority-${priority.key}`,
title: priority.label,
parent: 'assign_priority',
section: t('COMMAND_BAR.SECTIONS.CHANGE_PRIORITY'),
priority: priority,
icon: priority.icon,
handler: onChangePriority,
}));
return [
{
id: 'assign_priority',
title: t('COMMAND_BAR.COMMANDS.ASSIGN_PRIORITY'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ASSIGN_PRIORITY,
children: options.map(option => option.id),
},
...options,
];
});
const assignTeamActions = computed(() => {
const teamOptions = teamsList.value.map(team => ({
id: `team-${team.id}`,
title: team.name,
parent: 'assign_a_team',
section: t('COMMAND_BAR.SECTIONS.CHANGE_TEAM'),
teamInfo: team,
icon: ICON_ASSIGN_TEAM,
handler: onChangeTeam,
}));
return [
{
id: 'assign_a_team',
title: t('COMMAND_BAR.COMMANDS.ASSIGN_A_TEAM'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ASSIGN_TEAM,
children: teamOptions.map(option => option.id),
},
...teamOptions,
];
});
const addLabelActions = computed(() => {
const availableLabels = inactiveLabels.value.map(label => ({
id: label.title,
title: `#${label.title}`,
parent: 'add_a_label_to_the_conversation',
section: t('COMMAND_BAR.SECTIONS.ADD_LABEL'),
icon: ICON_ADD_LABEL,
handler: action => addLabelToConversation({ title: action.id }),
}));
return [
...availableLabels,
{
id: 'add_a_label_to_the_conversation',
title: t('COMMAND_BAR.COMMANDS.ADD_LABELS_TO_CONVERSATION'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ADD_LABEL,
children: inactiveLabels.value.map(label => label.title),
},
];
});
const removeLabelActions = computed(() => {
const activeLabelsComputed = activeLabels.value.map(label => ({
id: label.title,
title: `#${label.title}`,
parent: 'remove_a_label_to_the_conversation',
section: t('COMMAND_BAR.SECTIONS.REMOVE_LABEL'),
icon: ICON_REMOVE_LABEL,
handler: action => removeLabelFromConversation(action.id),
}));
return [
...activeLabelsComputed,
{
id: 'remove_a_label_to_the_conversation',
title: t('COMMAND_BAR.COMMANDS.REMOVE_LABEL_FROM_CONVERSATION'),
section: t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_REMOVE_LABEL,
children: activeLabels.value.map(label => label.title),
},
];
});
const labelActions = computed(() => {
if (activeLabels.value.length) {
return [...addLabelActions.value, ...removeLabelActions.value];
}
return addLabelActions.value;
});
const conversationAdditionalActions = computed(() => {
return prepareActions(
[
currentChat.value.muted ? UNMUTE_ACTION : MUTE_ACTION,
SEND_TRANSCRIPT_ACTION,
],
t
);
});
const AIAssistActions = computed(() => {
const aiOptions = draftMessage.value
? createDraftMessageAIAssistActions(t)
: createNonDraftMessageAIAssistActions(t, replyMode.value);
const options = aiOptions.map(item => ({
id: `ai-assist-${item.key}`,
title: item.label,
parent: 'ai_assist',
section: t('COMMAND_BAR.SECTIONS.AI_ASSIST'),
priority: item,
icon: item.icon,
handler: () => emitter.emit(CMD_AI_ASSIST, item.key),
}));
return [
{
id: 'ai_assist',
title: t('COMMAND_BAR.COMMANDS.AI_ASSIST'),
section: t('COMMAND_BAR.SECTIONS.AI_ASSIST'),
icon: ICON_AI_ASSIST,
children: options.map(option => option.id),
},
...options,
];
});
const isConversationOrInboxRoute = computed(() => {
return isAConversationRoute(route.name) || isAInboxViewRoute(route.name);
});
const shouldShowSnoozeOption = computed(() => {
return (
isAConversationRoute(route.name, true, false) && contextMenuChatId.value
);
});
const getDefaultConversationHotKeys = computed(() => {
const defaultConversationHotKeys = [
...statusActions.value,
...conversationAdditionalActions.value,
...assignAgentActions.value,
...assignTeamActions.value,
...labelActions.value,
...assignPriorityActions.value,
];
if (isAIIntegrationEnabled.value) {
return [...defaultConversationHotKeys, ...AIAssistActions.value];
}
return defaultConversationHotKeys;
});
const conversationHotKeys = computed(() => {
if (shouldShowSnoozeOption.value) {
return prepareActions(SNOOZE_CONVERSATION_ACTIONS, t);
}
if (isConversationOrInboxRoute.value) {
return getDefaultConversationHotKeys.value;
}
return [];
});
return {
conversationHotKeys,
};
}

View File

@@ -1,3 +1,8 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useMapGetter } from 'dashboard/composables/store';
import { useRouter } from 'dashboard/composables/route';
import { useAdmin } from 'dashboard/composables/useAdmin';
import {
ICON_ACCOUNT_SETTINGS,
ICON_AGENT_REPORTS,
@@ -14,11 +19,9 @@ import {
ICON_TEAM_REPORTS,
ICON_USER_PROFILE,
ICON_CONVERSATION_REPORTS,
} from './CommandBarIcons';
import { frontendURL } from '../../../helper/URLHelper';
import { mapGetters } from 'vuex';
import { useAdmin } from 'dashboard/composables/useAdmin';
import { FEATURE_FLAGS } from '../../../featureFlags';
} from 'dashboard/helper/commandbar/icons';
import { frontendURL } from 'dashboard/helper/URLHelper';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
const GO_TO_COMMANDS = [
{
@@ -172,45 +175,45 @@ const GO_TO_COMMANDS = [
},
];
export default {
setup() {
export function useGoToCommandHotKeys() {
const { t } = useI18n();
const router = useRouter();
const { isAdmin } = useAdmin();
return {
isAdmin,
const currentAccountId = useMapGetter('getCurrentAccountId');
const isFeatureEnabledOnAccount = useMapGetter(
'accounts/isFeatureEnabledonAccount'
);
const openRoute = url => {
router.push(frontendURL(url));
};
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
}),
goToCommandHotKeys() {
const goToCommandHotKeys = computed(() => {
let commands = GO_TO_COMMANDS.filter(cmd => {
if (cmd.featureFlag) {
return this.isFeatureEnabledonAccount(
this.accountId,
return isFeatureEnabledOnAccount.value(
currentAccountId.value,
cmd.featureFlag
);
}
return true;
});
if (!this.isAdmin) {
if (!isAdmin.value) {
commands = commands.filter(command => command.role.includes('agent'));
}
return commands.map(command => ({
id: command.id,
section: this.$t(command.section),
title: this.$t(command.title),
section: t(command.section),
title: t(command.title),
icon: command.icon,
handler: () => this.openRoute(command.path(this.accountId)),
handler: () => openRoute(command.path(currentAccountId.value)),
}));
},
},
methods: {
openRoute(url) {
this.$router.push(frontendURL(url));
},
},
});
return {
goToCommandHotKeys,
};
}

View File

@@ -1,13 +1,19 @@
import { computed } from 'vue';
import { useI18n } from 'dashboard/composables/useI18n';
import { useRoute } from 'dashboard/composables/route';
import wootConstants from 'dashboard/constants/globals';
import { CMD_SNOOZE_NOTIFICATION } from './commandBarBusEvents';
import { ICON_SNOOZE_NOTIFICATION } from './CommandBarIcons';
import { CMD_SNOOZE_NOTIFICATION } from 'dashboard/helper/commandbar/events';
import { ICON_SNOOZE_NOTIFICATION } from 'dashboard/helper/commandbar/icons';
import { emitter } from 'shared/helpers/mitt';
import { isAInboxViewRoute } from 'dashboard/helper/routeHelpers';
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
const createSnoozeHandler = option => () =>
emitter.emit(CMD_SNOOZE_NOTIFICATION, option);
const INBOX_SNOOZE_EVENTS = [
{
id: 'snooze_notification',
@@ -21,8 +27,7 @@ const INBOX_SNOOZE_EVENTS = [
parent: 'snooze_notification',
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.AN_HOUR_FROM_NOW),
handler: createSnoozeHandler(SNOOZE_OPTIONS.AN_HOUR_FROM_NOW),
},
{
id: SNOOZE_OPTIONS.UNTIL_TOMORROW,
@@ -30,8 +35,7 @@ const INBOX_SNOOZE_EVENTS = [
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_TOMORROW),
handler: createSnoozeHandler(SNOOZE_OPTIONS.UNTIL_TOMORROW),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_WEEK,
@@ -39,8 +43,7 @@ const INBOX_SNOOZE_EVENTS = [
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_NEXT_WEEK),
handler: createSnoozeHandler(SNOOZE_OPTIONS.UNTIL_NEXT_WEEK),
},
{
id: SNOOZE_OPTIONS.UNTIL_NEXT_MONTH,
@@ -48,8 +51,7 @@ const INBOX_SNOOZE_EVENTS = [
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_NEXT_MONTH),
handler: createSnoozeHandler(SNOOZE_OPTIONS.UNTIL_NEXT_MONTH),
},
{
id: SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME,
@@ -57,26 +59,30 @@ const INBOX_SNOOZE_EVENTS = [
section: 'COMMAND_BAR.SECTIONS.SNOOZE_NOTIFICATION',
parent: 'snooze_notification',
icon: ICON_SNOOZE_NOTIFICATION,
handler: () =>
emitter.emit(CMD_SNOOZE_NOTIFICATION, SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME),
handler: createSnoozeHandler(SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME),
},
];
export default {
computed: {
inboxHotKeys() {
if (isAInboxViewRoute(this.$route.name)) {
return this.prepareActions(INBOX_SNOOZE_EVENTS);
}
return [];
},
},
methods: {
prepareActions(actions) {
export function useInboxHotKeys() {
const { t } = useI18n();
const route = useRoute();
const prepareActions = actions => {
return actions.map(action => ({
...action,
title: this.$t(action.title),
section: this.$t(action.section),
title: t(action.title),
section: action.section ? t(action.section) : undefined,
}));
},
},
};
const inboxHotKeys = computed(() => {
if (isAInboxViewRoute(route.name)) {
return prepareActions(INBOX_SNOOZE_EVENTS);
}
return [];
});
return {
inboxHotKeys,
};
}

View File

@@ -8,7 +8,7 @@ import {
CMD_SEND_TRANSCRIPT,
CMD_SNOOZE_CONVERSATION,
CMD_UNMUTE_CONVERSATION,
} from './commandBarBusEvents';
} from 'dashboard/helper/commandbar/events';
import {
ICON_MUTE_CONVERSATION,
@@ -17,7 +17,7 @@ import {
ICON_SEND_TRANSCRIPT,
ICON_SNOOZE_CONVERSATION,
ICON_UNMUTE_CONVERSATION,
} from './CommandBarIcons';
} from 'dashboard/helper/commandbar/icons';
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
@@ -46,6 +46,7 @@ export const SNOOZE_CONVERSATION_ACTIONS = [
{
id: 'snooze_conversation',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.CONVERSATION',
icon: ICON_SNOOZE_CONVERSATION,
children: Object.values(SNOOZE_OPTIONS),
},

View File

@@ -6,7 +6,7 @@ import { useI18n } from 'dashboard/composables/useI18n';
import { useEmitter } from 'dashboard/composables/emitter';
import { getUnixTime } from 'date-fns';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import { CMD_SNOOZE_CONVERSATION } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import { CMD_SNOOZE_CONVERSATION } from 'dashboard/helper/commandbar/events';
import wootConstants from 'dashboard/constants/globals';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';

View File

@@ -1,65 +0,0 @@
import {
ICON_APPEARANCE,
ICON_LIGHT_MODE,
ICON_DARK_MODE,
ICON_SYSTEM_MODE,
} from './CommandBarIcons';
import { LocalStorage } from 'shared/helpers/localStorage';
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
import { setColorTheme } from 'dashboard/helper/themeHelper.js';
export default {
computed: {
themeOptions() {
return [
{
key: 'light',
label: this.$t('COMMAND_BAR.COMMANDS.LIGHT_MODE'),
icon: ICON_LIGHT_MODE,
},
{
key: 'dark',
label: this.$t('COMMAND_BAR.COMMANDS.DARK_MODE'),
icon: ICON_DARK_MODE,
},
{
key: 'auto',
label: this.$t('COMMAND_BAR.COMMANDS.SYSTEM_MODE'),
icon: ICON_SYSTEM_MODE,
},
];
},
goToAppearanceHotKeys() {
const options = this.themeOptions.map(theme => ({
id: theme.key,
title: theme.label,
parent: 'appearance_settings',
section: this.$t('COMMAND_BAR.SECTIONS.APPEARANCE'),
icon: theme.icon,
handler: () => {
this.setAppearance(theme.key);
},
}));
return [
{
id: 'appearance_settings',
title: this.$t('COMMAND_BAR.COMMANDS.CHANGE_APPEARANCE'),
section: this.$t('COMMAND_BAR.SECTIONS.APPEARANCE'),
icon: ICON_APPEARANCE,
children: options.map(option => option.id),
},
...options,
];
},
},
methods: {
setAppearance(theme) {
LocalStorage.set(LOCAL_STORAGE_KEYS.COLOR_SCHEME, theme);
const isOSOnDarkMode = window.matchMedia(
'(prefers-color-scheme: dark)'
).matches;
setColorTheme(isOSOnDarkMode);
},
},
};

View File

@@ -1,86 +0,0 @@
import { mapGetters } from 'vuex';
import wootConstants from 'dashboard/constants/globals';
import {
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
CMD_BULK_ACTION_REOPEN_CONVERSATION,
CMD_BULK_ACTION_RESOLVE_CONVERSATION,
} from './commandBarBusEvents';
import {
ICON_SNOOZE_CONVERSATION,
ICON_REOPEN_CONVERSATION,
ICON_RESOLVE_CONVERSATION,
} from './CommandBarIcons';
import { emitter } from 'shared/helpers/mitt';
import { createSnoozeHandlers } from './commandBarActions';
const SNOOZE_OPTIONS = wootConstants.SNOOZE_OPTIONS;
export const SNOOZE_CONVERSATION_BULK_ACTIONS = [
{
id: 'bulk_action_snooze_conversation',
title: 'COMMAND_BAR.COMMANDS.SNOOZE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_SNOOZE_CONVERSATION,
children: Object.values(SNOOZE_OPTIONS),
},
...createSnoozeHandlers(
CMD_BULK_ACTION_SNOOZE_CONVERSATION,
'bulk_action_snooze_conversation',
'COMMAND_BAR.SECTIONS.BULK_ACTIONS'
),
];
export const RESOLVED_CONVERSATION_BULK_ACTIONS = [
{
id: 'bulk_action_reopen_conversation',
title: 'COMMAND_BAR.COMMANDS.REOPEN_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_REOPEN_CONVERSATION,
handler: () => emitter.emit(CMD_BULK_ACTION_REOPEN_CONVERSATION),
},
];
export const OPEN_CONVERSATION_BULK_ACTIONS = [
{
id: 'bulk_action_resolve_conversation',
title: 'COMMAND_BAR.COMMANDS.RESOLVE_CONVERSATION',
section: 'COMMAND_BAR.SECTIONS.BULK_ACTIONS',
icon: ICON_RESOLVE_CONVERSATION,
handler: () => emitter.emit(CMD_BULK_ACTION_RESOLVE_CONVERSATION),
},
];
export default {
computed: {
...mapGetters({
selectedConversations: 'bulkActions/getSelectedConversationIds',
}),
bulkActionsHotKeys() {
let actions = [];
if (this.selectedConversations.length > 0) {
actions = [
...SNOOZE_CONVERSATION_BULK_ACTIONS,
...RESOLVED_CONVERSATION_BULK_ACTIONS,
...OPEN_CONVERSATION_BULK_ACTIONS,
];
}
return this.prepareActions(actions);
},
},
watch: {
selectedConversations() {
this.setCommandbarData();
},
},
methods: {
prepareActions(actions) {
return actions.map(action => ({
...action,
title: this.$t(action.title),
section: this.$t(action.section),
}));
},
},
};

View File

@@ -1,118 +1,85 @@
<script>
<script setup>
import '@chatwoot/ninja-keys';
import { useConversationLabels } from 'dashboard/composables/useConversationLabels';
import { useAI } from 'dashboard/composables/useAI';
import { useAgentsList } from 'dashboard/composables/useAgentsList';
import { ref, computed, watchEffect, onMounted } from 'vue';
import { useStore } from 'dashboard/composables/store';
import { useTrack } from 'dashboard/composables';
import { useI18n } from 'dashboard/composables/useI18n';
import { useAppearanceHotKeys } from 'dashboard/composables/commands/useAppearanceHotKeys';
import { useInboxHotKeys } from 'dashboard/composables/commands/useInboxHotKeys';
import { useGoToCommandHotKeys } from 'dashboard/composables/commands/useGoToCommandHotKeys';
import { useBulkActionsHotKeys } from 'dashboard/composables/commands/useBulkActionsHotKeys';
import { useConversationHotKeys } from 'dashboard/composables/commands/useConversationHotKeys';
import wootConstants from 'dashboard/constants/globals';
import conversationHotKeysMixin from './conversationHotKeys';
import bulkActionsHotKeysMixin from './bulkActionsHotKeys';
import inboxHotKeysMixin from './inboxHotKeys';
import goToCommandHotKeys from './goToCommandHotKeys';
import appearanceHotKeys from './appearanceHotKeys';
import { GENERAL_EVENTS } from '../../../helper/AnalyticsHelper/events';
import { GENERAL_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
export default {
mixins: [
conversationHotKeysMixin,
bulkActionsHotKeysMixin,
inboxHotKeysMixin,
appearanceHotKeys,
goToCommandHotKeys,
],
setup() {
// used in conversationHotKeysMixin
const {
activeLabels,
inactiveLabels,
addLabelToConversation,
removeLabelFromConversation,
} = useConversationLabels();
const store = useStore();
const track = useTrack();
const { t } = useI18n();
const { isAIIntegrationEnabled } = useAI();
const { agentsList, assignableAgents } = useAgentsList();
const ninjakeys = ref(null);
return {
agentsList,
assignableAgents,
activeLabels,
inactiveLabels,
addLabelToConversation,
removeLabelFromConversation,
isAIIntegrationEnabled,
};
},
data() {
return {
// Added selectedSnoozeType to track the selected snooze type
// So if the selected snooze type is "custom snooze" then we set selectedSnoozeType with the CMD action id
// So that we can track the selected snooze type and when we close the command bar
selectedSnoozeType: null,
const selectedSnoozeType = ref(null);
const { goToAppearanceHotKeys } = useAppearanceHotKeys();
const { inboxHotKeys } = useInboxHotKeys();
const { goToCommandHotKeys } = useGoToCommandHotKeys();
const { bulkActionsHotKeys } = useBulkActionsHotKeys();
const { conversationHotKeys } = useConversationHotKeys();
const placeholder = computed(() => t('COMMAND_BAR.SEARCH_PLACEHOLDER'));
const hotKeys = computed(() => [
...inboxHotKeys.value,
...goToCommandHotKeys.value,
...goToAppearanceHotKeys.value,
...bulkActionsHotKeys.value,
...conversationHotKeys.value,
]);
const setCommandBarData = () => {
ninjakeys.value.data = hotKeys.value;
};
},
computed: {
placeholder() {
return this.$t('COMMAND_BAR.SEARCH_PLACEHOLDER');
},
accountId() {
return this.$store.getters.getCurrentAccountId;
},
routeName() {
return this.$route.name;
},
hotKeys() {
return [
...this.inboxHotKeys,
...this.conversationHotKeys,
...this.bulkActionsHotKeys,
...this.goToCommandHotKeys,
...this.goToAppearanceHotKeys,
];
},
},
watch: {
routeName() {
this.setCommandbarData();
},
},
mounted() {
this.setCommandbarData();
},
methods: {
setCommandbarData() {
this.$refs.ninjakeys.data = this.hotKeys;
},
onSelected(item) {
const onSelected = item => {
const {
detail: {
action: { title = null, section = null, id = null } = {},
} = {},
detail: { action: { title = null, section = null, id = null } = {} } = {},
} = item;
// Added this condition to prevent setting the selectedSnoozeType to null
// When we select the "custom snooze" (CMD bar will close and the custom snooze modal will open)
if (id === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.selectedSnoozeType =
wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME;
selectedSnoozeType.value = wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME;
} else {
this.selectedSnoozeType = null;
selectedSnoozeType.value = null;
}
this.$track(GENERAL_EVENTS.COMMAND_BAR, {
track(GENERAL_EVENTS.COMMAND_BAR, {
section,
action: title,
});
this.setCommandbarData();
},
onClosed() {
setCommandBarData();
};
const onClosed = () => {
// If the selectedSnoozeType is not "SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME (custom snooze)" then we set the context menu chat id to null
// Else we do nothing and its handled in the ChatList.vue hideCustomSnoozeModal() method
if (
this.selectedSnoozeType !==
wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME
selectedSnoozeType.value !== wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME
) {
this.$store.dispatch('setContextMenuChatId', null);
store.dispatch('setContextMenuChatId', null);
}
},
},
};
watchEffect(() => {
if (ninjakeys.value) {
ninjakeys.value.data = hotKeys.value;
}
});
onMounted(setCommandBarData);
</script>
<!-- eslint-disable vue/attribute-hyphenation -->

View File

@@ -1,413 +0,0 @@
import { mapGetters } from 'vuex';
import wootConstants from 'dashboard/constants/globals';
import { emitter } from 'shared/helpers/mitt';
import { CMD_AI_ASSIST } from './commandBarBusEvents';
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
import {
ICON_ADD_LABEL,
ICON_ASSIGN_AGENT,
ICON_ASSIGN_PRIORITY,
ICON_ASSIGN_TEAM,
ICON_REMOVE_LABEL,
ICON_PRIORITY_URGENT,
ICON_PRIORITY_HIGH,
ICON_PRIORITY_LOW,
ICON_PRIORITY_MEDIUM,
ICON_PRIORITY_NONE,
ICON_AI_ASSIST,
ICON_AI_SUMMARY,
ICON_AI_SHORTEN,
ICON_AI_EXPAND,
ICON_AI_GRAMMAR,
} from './CommandBarIcons';
import {
OPEN_CONVERSATION_ACTIONS,
SNOOZE_CONVERSATION_ACTIONS,
RESOLVED_CONVERSATION_ACTIONS,
SEND_TRANSCRIPT_ACTION,
UNMUTE_ACTION,
MUTE_ACTION,
} from './commandBarActions';
import {
isAConversationRoute,
isAInboxViewRoute,
} from '../../../helper/routeHelpers';
export default {
watch: {
assignableAgents() {
this.setCommandbarData();
},
currentChat() {
this.setCommandbarData();
},
teamsList() {
this.setCommandbarData();
},
activeLabels() {
this.setCommandbarData();
},
draftMessage() {
this.setCommandbarData();
},
replyMode() {
this.setCommandbarData();
},
contextMenuChatId() {
this.setCommandbarData();
},
},
computed: {
...mapGetters({
currentChat: 'getSelectedChat',
replyMode: 'draftMessages/getReplyEditorMode',
contextMenuChatId: 'getContextMenuChatId',
teams: 'teams/getTeams',
}),
draftMessage() {
return this.$store.getters['draftMessages/get'](this.draftKey);
},
draftKey() {
return `draft-${this.conversationId}-${this.replyMode}`;
},
inboxId() {
return this.currentChat?.inbox_id;
},
conversationId() {
return this.currentChat?.id;
},
hasAnAssignedTeam() {
return !!this.currentChat?.meta?.team;
},
teamsList() {
if (this.hasAnAssignedTeam) {
return [
{ id: 0, name: this.$t('TEAMS_SETTINGS.LIST.NONE') },
...this.teams,
];
}
return this.teams;
},
statusActions() {
const isOpen =
this.currentChat?.status === wootConstants.STATUS_TYPE.OPEN;
const isSnoozed =
this.currentChat?.status === wootConstants.STATUS_TYPE.SNOOZED;
const isResolved =
this.currentChat?.status === wootConstants.STATUS_TYPE.RESOLVED;
let actions = [];
if (isOpen) {
actions = [
...OPEN_CONVERSATION_ACTIONS,
...SNOOZE_CONVERSATION_ACTIONS,
];
} else if (isResolved || isSnoozed) {
actions = RESOLVED_CONVERSATION_ACTIONS;
}
return this.prepareActions(actions);
},
priorityOptions() {
return [
{
label: this.$t('CONVERSATION.PRIORITY.OPTIONS.NONE'),
key: null,
icon: ICON_PRIORITY_NONE,
},
{
label: this.$t('CONVERSATION.PRIORITY.OPTIONS.URGENT'),
key: 'urgent',
icon: ICON_PRIORITY_URGENT,
},
{
label: this.$t('CONVERSATION.PRIORITY.OPTIONS.HIGH'),
key: 'high',
icon: ICON_PRIORITY_HIGH,
},
{
label: this.$t('CONVERSATION.PRIORITY.OPTIONS.MEDIUM'),
key: 'medium',
icon: ICON_PRIORITY_MEDIUM,
},
{
label: this.$t('CONVERSATION.PRIORITY.OPTIONS.LOW'),
key: 'low',
icon: ICON_PRIORITY_LOW,
},
].filter(item => item.key !== this.currentChat?.priority);
},
assignAgentActions() {
const agentOptions = this.agentsList.map(agent => ({
id: `agent-${agent.id}`,
title: agent.name,
parent: 'assign_an_agent',
section: this.$t('COMMAND_BAR.SECTIONS.CHANGE_ASSIGNEE'),
agentInfo: agent,
icon: ICON_ASSIGN_AGENT,
handler: this.onChangeAssignee,
}));
return [
{
id: 'assign_an_agent',
title: this.$t('COMMAND_BAR.COMMANDS.ASSIGN_AN_AGENT'),
section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ASSIGN_AGENT,
children: agentOptions.map(option => option.id),
},
...agentOptions,
];
},
assignPriorityActions() {
const options = this.priorityOptions.map(priority => ({
id: `priority-${priority.key}`,
title: priority.label,
parent: 'assign_priority',
section: this.$t('COMMAND_BAR.SECTIONS.CHANGE_PRIORITY'),
priority: priority,
icon: priority.icon,
handler: this.onChangePriority,
}));
return [
{
id: 'assign_priority',
title: this.$t('COMMAND_BAR.COMMANDS.ASSIGN_PRIORITY'),
section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ASSIGN_PRIORITY,
children: options.map(option => option.id),
},
...options,
];
},
assignTeamActions() {
const teamOptions = this.teamsList.map(team => ({
id: `team-${team.id}`,
title: team.name,
parent: 'assign_a_team',
section: this.$t('COMMAND_BAR.SECTIONS.CHANGE_TEAM'),
teamInfo: team,
icon: ICON_ASSIGN_TEAM,
handler: this.onChangeTeam,
}));
return [
{
id: 'assign_a_team',
title: this.$t('COMMAND_BAR.COMMANDS.ASSIGN_A_TEAM'),
section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ASSIGN_TEAM,
children: teamOptions.map(option => option.id),
},
...teamOptions,
];
},
addLabelActions() {
const availableLabels = this.inactiveLabels.map(label => ({
id: label.title,
title: `#${label.title}`,
parent: 'add_a_label_to_the_conversation',
section: this.$t('COMMAND_BAR.SECTIONS.ADD_LABEL'),
icon: ICON_ADD_LABEL,
handler: action => this.addLabelToConversation({ title: action.id }),
}));
return [
...availableLabels,
{
id: 'add_a_label_to_the_conversation',
title: this.$t('COMMAND_BAR.COMMANDS.ADD_LABELS_TO_CONVERSATION'),
section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_ADD_LABEL,
children: this.inactiveLabels.map(label => label.title),
},
];
},
removeLabelActions() {
const activeLabels = this.activeLabels.map(label => ({
id: label.title,
title: `#${label.title}`,
parent: 'remove_a_label_to_the_conversation',
section: this.$t('COMMAND_BAR.SECTIONS.REMOVE_LABEL'),
icon: ICON_REMOVE_LABEL,
handler: action => this.removeLabelFromConversation(action.id),
}));
return [
...activeLabels,
{
id: 'remove_a_label_to_the_conversation',
title: this.$t('COMMAND_BAR.COMMANDS.REMOVE_LABEL_FROM_CONVERSATION'),
section: this.$t('COMMAND_BAR.SECTIONS.CONVERSATION'),
icon: ICON_REMOVE_LABEL,
children: this.activeLabels.map(label => label.title),
},
];
},
labelActions() {
if (this.activeLabels.length) {
return [...this.addLabelActions, ...this.removeLabelActions];
}
return this.addLabelActions;
},
conversationAdditionalActions() {
return this.prepareActions([
this.currentChat.muted ? UNMUTE_ACTION : MUTE_ACTION,
SEND_TRANSCRIPT_ACTION,
]);
},
nonDraftMessageAIAssistActions() {
if (this.replyMode === REPLY_EDITOR_MODES.REPLY) {
return [
{
label: this.$t(
'INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.REPLY_SUGGESTION'
),
key: 'reply_suggestion',
icon: ICON_AI_ASSIST,
},
];
}
return [
{
label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SUMMARIZE'),
key: 'summarize',
icon: ICON_AI_SUMMARY,
},
];
},
draftMessageAIAssistActions() {
return [
{
label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.REPHRASE'),
key: 'rephrase',
icon: ICON_AI_ASSIST,
},
{
label: this.$t(
'INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.FIX_SPELLING_GRAMMAR'
),
key: 'fix_spelling_grammar',
icon: ICON_AI_GRAMMAR,
},
{
label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.EXPAND'),
key: 'expand',
icon: ICON_AI_EXPAND,
},
{
label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SHORTEN'),
key: 'shorten',
icon: ICON_AI_SHORTEN,
},
{
label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.MAKE_FRIENDLY'),
key: 'make_friendly',
icon: ICON_AI_ASSIST,
},
{
label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.MAKE_FORMAL'),
key: 'make_formal',
icon: ICON_AI_ASSIST,
},
{
label: this.$t('INTEGRATION_SETTINGS.OPEN_AI.OPTIONS.SIMPLIFY'),
key: 'simplify',
icon: ICON_AI_ASSIST,
},
];
},
AIAssistActions() {
const aiOptions = this.draftMessage
? this.draftMessageAIAssistActions
: this.nonDraftMessageAIAssistActions;
const options = aiOptions.map(item => ({
id: `ai-assist-${item.key}`,
title: item.label,
parent: 'ai_assist',
section: this.$t('COMMAND_BAR.SECTIONS.AI_ASSIST'),
priority: item,
icon: item.icon,
handler: () => emitter.emit(CMD_AI_ASSIST, item.key),
}));
return [
{
id: 'ai_assist',
title: this.$t('COMMAND_BAR.COMMANDS.AI_ASSIST'),
section: this.$t('COMMAND_BAR.SECTIONS.AI_ASSIST'),
icon: ICON_AI_ASSIST,
children: options.map(option => option.id),
},
...options,
];
},
isConversationOrInboxRoute() {
return (
isAConversationRoute(this.$route.name) ||
isAInboxViewRoute(this.$route.name)
);
},
shouldShowSnoozeOption() {
return (
isAConversationRoute(this.$route.name, true, false) &&
this.contextMenuChatId
);
},
getDefaultConversationHotKeys() {
const defaultConversationHotKeys = [
...this.statusActions,
...this.conversationAdditionalActions,
...this.assignAgentActions,
...this.assignTeamActions,
...this.labelActions,
...this.assignPriorityActions,
];
if (this.isAIIntegrationEnabled) {
return [...defaultConversationHotKeys, ...this.AIAssistActions];
}
return defaultConversationHotKeys;
},
conversationHotKeys() {
if (this.shouldShowSnoozeOption) {
return this.prepareActions(SNOOZE_CONVERSATION_ACTIONS);
}
if (this.isConversationOrInboxRoute) {
return this.getDefaultConversationHotKeys;
}
return [];
},
},
methods: {
onChangeAssignee(action) {
this.$store.dispatch('assignAgent', {
conversationId: this.currentChat.id,
agentId: action.agentInfo.id,
});
},
onChangePriority(action) {
this.$store.dispatch('assignPriority', {
conversationId: this.currentChat.id,
priority: action.priority.key,
});
},
onChangeTeam(action) {
this.$store.dispatch('assignTeam', {
conversationId: this.currentChat.id,
teamId: action.teamInfo.id,
});
},
prepareActions(actions) {
return actions.map(action => ({
...action,
title: this.$t(action.title),
section: this.$t(action.section),
}));
},
},
};

View File

@@ -2,7 +2,7 @@
import { mapGetters } from 'vuex';
import { useAlert } from 'dashboard/composables';
import { getUnixTime } from 'date-fns';
import { CMD_SNOOZE_NOTIFICATION } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import { CMD_SNOOZE_NOTIFICATION } from 'dashboard/helper/commandbar/events';
import wootConstants from 'dashboard/constants/globals';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import { INBOX_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';