diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js
index 8b9eacf3f..39546096f 100644
--- a/app/javascript/dashboard/api/inbox/conversation.js
+++ b/app/javascript/dashboard/api/inbox/conversation.js
@@ -137,6 +137,10 @@ class ConversationApi extends ApiClient {
requestCopilot(conversationId, body) {
return axios.post(`${this.url}/${conversationId}/copilot`, body);
}
+
+ getInboxAssistant(conversationId) {
+ return axios.get(`${this.url}/${conversationId}/inbox_assistant`);
+ }
}
export default new ConversationApi();
diff --git a/app/javascript/dashboard/components-next/copilot/Copilot.vue b/app/javascript/dashboard/components-next/copilot/Copilot.vue
index cf867789b..e284bb983 100644
--- a/app/javascript/dashboard/components-next/copilot/Copilot.vue
+++ b/app/javascript/dashboard/components-next/copilot/Copilot.vue
@@ -7,6 +7,7 @@ import CopilotInput from './CopilotInput.vue';
import CopilotLoader from './CopilotLoader.vue';
import CopilotAgentMessage from './CopilotAgentMessage.vue';
import CopilotAssistantMessage from './CopilotAssistantMessage.vue';
+import ToggleCopilotAssistant from './ToggleCopilotAssistant.vue';
import Icon from '../icon/Icon.vue';
const props = defineProps({
@@ -26,9 +27,17 @@ const props = defineProps({
type: String,
required: true,
},
+ assistants: {
+ type: Array,
+ default: () => [],
+ },
+ activeAssistant: {
+ type: Object,
+ default: () => ({}),
+ },
});
-const emit = defineEmits(['sendMessage', 'reset']);
+const emit = defineEmits(['sendMessage', 'reset', 'setAssistant']);
const COPILOT_USER_ROLES = ['assistant', 'system'];
@@ -97,14 +106,18 @@ watch(
-
-
+
+
+
{{ $t('COPILOT.TRY_THESE_PROMPTS') }}
-
+
+
+
+
+
emit('setAssistant', $event)"
+ />
+
{{ $t('CAPTAIN.COPILOT.RESET') }}
-
+
diff --git a/app/javascript/dashboard/components-next/copilot/ToggleCopilotAssistant.vue b/app/javascript/dashboard/components-next/copilot/ToggleCopilotAssistant.vue
new file mode 100644
index 000000000..ec21cec9f
--- /dev/null
+++ b/app/javascript/dashboard/components-next/copilot/ToggleCopilotAssistant.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+ emit('setAssistant', assistant)"
+ >
+
+
+
+
+ {{ assistant.name }}
+
+
+ {{ assistant.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownBody.vue b/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownBody.vue
index 246eba5aa..1a67aaa68 100644
--- a/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownBody.vue
+++ b/app/javascript/dashboard/components-next/dropdown-menu/base/DropdownBody.vue
@@ -19,7 +19,7 @@ const beforeClass = computed(() => {
// Add extra blur layer only when strong prop is true, as a hack for Chrome's stacked backdrop-blur limitation
// https://issues.chromium.org/issues/40835530
- return "before:content-['\x00A0'] before:absolute before:bottom-0 before:left-0 before:w-full before:h-full before:backdrop-contrast-70 before:backdrop-blur-sm before:z-0 [&>*]:relative";
+ return "before:content-['\x00A0'] before:absolute before:bottom-0 before:left-0 before:w-full before:h-full before:rounded-xl before:backdrop-contrast-70 before:backdrop-blur-sm before:z-0 [&>*]:relative";
});
diff --git a/app/javascript/dashboard/components/copilot/CopilotContainer.vue b/app/javascript/dashboard/components/copilot/CopilotContainer.vue
index 17d35d82a..0a4638730 100644
--- a/app/javascript/dashboard/components/copilot/CopilotContainer.vue
+++ b/app/javascript/dashboard/components/copilot/CopilotContainer.vue
@@ -1,8 +1,11 @@
@@ -65,6 +113,9 @@ const sendMessage = async message => {
:support-agent="currentUser"
:is-captain-typing="isCaptainTyping"
:conversation-inbox-type="conversationInboxType"
+ :assistants="assistants"
+ :active-assistant="activeAssistant"
+ @set-assistant="setAssistant"
@send-message="sendMessage"
@reset="handleReset"
/>
diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json
index c95820731..8db6e4428 100644
--- a/app/javascript/dashboard/i18n/locale/en/integrations.json
+++ b/app/javascript/dashboard/i18n/locale/en/integrations.json
@@ -313,7 +313,8 @@
"LOADER": "Captain is thinking",
"YOU": "You",
"USE": "Use this",
- "RESET": "Reset"
+ "RESET": "Reset",
+ "SELECT_ASSISTANT": "Select Assistant"
},
"PAYWALL": {
"TITLE": "Upgrade to use Captain AI",
diff --git a/app/javascript/dashboard/store/modules/conversations/actions.js b/app/javascript/dashboard/store/modules/conversations/actions.js
index de12c0077..8296f25de 100644
--- a/app/javascript/dashboard/store/modules/conversations/actions.js
+++ b/app/javascript/dashboard/store/modules/conversations/actions.js
@@ -489,6 +489,15 @@ const actions = {
commit(types.SET_CONTEXT_MENU_CHAT_ID, chatId);
},
+ getInboxCaptainAssistantById: async ({ commit }, conversationId) => {
+ try {
+ const response = await ConversationApi.getInboxAssistant(conversationId);
+ commit(types.SET_INBOX_CAPTAIN_ASSISTANT, response.data);
+ } catch (error) {
+ // Handle error
+ }
+ },
+
...messageReadActions,
...messageTranslateActions,
};
diff --git a/app/javascript/dashboard/store/modules/conversations/getters.js b/app/javascript/dashboard/store/modules/conversations/getters.js
index 3695de6a1..9329ef5dc 100644
--- a/app/javascript/dashboard/store/modules/conversations/getters.js
+++ b/app/javascript/dashboard/store/modules/conversations/getters.js
@@ -108,6 +108,10 @@ const getters = {
getContextMenuChatId: _state => {
return _state.contextMenuChatId;
},
+
+ getCopilotAssistant: _state => {
+ return _state.copilotAssistant;
+ },
};
export default getters;
diff --git a/app/javascript/dashboard/store/modules/conversations/index.js b/app/javascript/dashboard/store/modules/conversations/index.js
index 22f9256b9..1d7fdbe46 100644
--- a/app/javascript/dashboard/store/modules/conversations/index.js
+++ b/app/javascript/dashboard/store/modules/conversations/index.js
@@ -21,6 +21,7 @@ const state = {
conversationLastSeen: null,
syncConversationsMessages: {},
conversationFilters: {},
+ copilotAssistant: {},
};
// mutations
@@ -309,6 +310,9 @@ export const mutations = {
[types.UPDATE_CHAT_LIST_FILTERS](_state, data) {
_state.conversationFilters = { ..._state.conversationFilters, ...data };
},
+ [types.SET_INBOX_CAPTAIN_ASSISTANT](_state, data) {
+ _state.copilotAssistant = data.assistant;
+ },
};
export default {
diff --git a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js
index b0c9d2480..dc2bf8d3a 100644
--- a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js
@@ -687,4 +687,23 @@ describe('#addMentions', () => {
]);
});
});
+
+ describe('#getInboxCaptainAssistantById', () => {
+ it('fetches inbox assistant by id', async () => {
+ axios.get.mockResolvedValue({
+ data: {
+ id: 1,
+ name: 'Assistant',
+ description: 'Assistant description',
+ },
+ });
+ await actions.getInboxCaptainAssistantById({ commit }, 1);
+ expect(commit.mock.calls).toEqual([
+ [
+ types.SET_INBOX_CAPTAIN_ASSISTANT,
+ { id: 1, name: 'Assistant', description: 'Assistant description' },
+ ],
+ ]);
+ });
+ });
});
diff --git a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js
index cded29329..8ac89f49a 100644
--- a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js
@@ -308,4 +308,21 @@ describe('#getters', () => {
});
});
});
+
+ describe('#getCopilotAssistant', () => {
+ it('get copilot assistant', () => {
+ const state = {
+ copilotAssistant: {
+ id: 1,
+ name: 'Assistant',
+ description: 'Assistant description',
+ },
+ };
+ expect(getters.getCopilotAssistant(state)).toEqual({
+ id: 1,
+ name: 'Assistant',
+ description: 'Assistant description',
+ });
+ });
+ });
});
diff --git a/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js
index 6340e4de9..02d2b0f55 100644
--- a/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js
@@ -1,3 +1,4 @@
+import { describe } from 'vitest';
import types from '../../../mutation-types';
import { mutations } from '../../conversations';
@@ -553,4 +554,19 @@ describe('#mutations', () => {
});
});
});
+
+ describe('#SET_INBOX_CAPTAIN_ASSISTANT', () => {
+ it('set inbox captain assistant', () => {
+ const state = { copilotAssistant: {} };
+ const data = {
+ assistant: {
+ id: 1,
+ name: 'Assistant',
+ description: 'Assistant description',
+ },
+ };
+ mutations[types.SET_INBOX_CAPTAIN_ASSISTANT](state, data);
+ expect(state.copilotAssistant).toEqual(data.assistant);
+ });
+ });
});
diff --git a/app/javascript/dashboard/store/mutation-types.js b/app/javascript/dashboard/store/mutation-types.js
index 7416ca9a3..c5b06f1ad 100644
--- a/app/javascript/dashboard/store/mutation-types.js
+++ b/app/javascript/dashboard/store/mutation-types.js
@@ -64,6 +64,9 @@ export default {
SET_CHAT_LIST_FILTERS: 'SET_CHAT_LIST_FILTERS',
UPDATE_CHAT_LIST_FILTERS: 'UPDATE_CHAT_LIST_FILTERS',
+ // Conversation inbox connected copilot assistant
+ SET_INBOX_CAPTAIN_ASSISTANT: 'SET_INBOX_CAPTAIN_ASSISTANT',
+
// Inboxes
SET_INBOXES_UI_FLAG: 'SET_INBOXES_UI_FLAG',
SET_INBOXES: 'SET_INBOXES',
diff --git a/config/routes.rb b/config/routes.rb
index 3b5435ed9..56704d9aa 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -119,6 +119,7 @@ Rails.application.routes.draw do
post :custom_attributes
get :attachments
post :copilot
+ get :inbox_assistant
end
end
diff --git a/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb b/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb
index e9113f4d0..a9dcb0b67 100644
--- a/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb
+++ b/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb
@@ -5,9 +5,17 @@ module Enterprise::Api::V1::Accounts::ConversationsController
end
def copilot
- assistant = @conversation.inbox.captain_assistant
+ # First try to get the user's preferred assistant from UI settings or from the request
+ assistant_id = copilot_params[:assistant_id] || current_user.ui_settings&.dig('preferred_captain_assistant_id')
+
+ # Find the assistant either by ID or from inbox
+ assistant = if assistant_id.present?
+ Captain::Assistant.find_by(id: assistant_id, account_id: Current.account.id)
+ else
+ @conversation.inbox.captain_assistant
+ end
+
return render json: { message: I18n.t('captain.copilot_error') } unless assistant
- return render json: { message: I18n.t('captain.copilot_limit') } unless @conversation.inbox.captain_active?
response = Captain::Copilot::ChatService.new(
assistant,
@@ -18,6 +26,16 @@ module Enterprise::Api::V1::Accounts::ConversationsController
render json: { message: response['response'] }
end
+ def inbox_assistant
+ assistant = @conversation.inbox.captain_assistant
+
+ if assistant
+ render json: { assistant: { id: assistant.id, name: assistant.name } }
+ else
+ render json: { assistant: nil }
+ end
+ end
+
def permitted_update_params
super.merge(params.permit(:sla_policy_id))
end