+
{
:question="replaceInstallationName(response.question)"
:answer="replaceInstallationName(response.answer)"
:status="response.status"
+ :documentable="response.documentable"
:assistant="response.assistant"
:created-at="response.created_at"
:updated-at="response.created_at"
diff --git a/app/javascript/dashboard/components-next/captain/pageComponents/emptyStates/captainEmptyStateContent.js b/app/javascript/dashboard/components-next/captain/pageComponents/emptyStates/captainEmptyStateContent.js
index af0331430..fe6e69b0a 100644
--- a/app/javascript/dashboard/components-next/captain/pageComponents/emptyStates/captainEmptyStateContent.js
+++ b/app/javascript/dashboard/components-next/captain/pageComponents/emptyStates/captainEmptyStateContent.js
@@ -141,6 +141,11 @@ export const responsesList = [
id: 87,
question: 'Why is my Messenger in Chatwoot deactivated?',
status: 'pending',
+ documentable: {
+ id: 1,
+ type: 'Captain::Document',
+ name: 'Document 1',
+ },
assistant: {
account_id: 1,
config: { product_name: 'Chatwoot' },
@@ -157,6 +162,11 @@ export const responsesList = [
created_at: 1736283340,
id: 88,
question: 'How do I integrate WhatsApp with Chatwoot?',
+ documentable: {
+ id: 1,
+ type: 'User',
+ available_name: 'John Doe',
+ },
assistant: {
account_id: 2,
config: { product_name: 'Chatwoot' },
@@ -173,6 +183,11 @@ export const responsesList = [
created_at: 1736283350,
id: 89,
question: 'How can I reset my password in Chatwoot?',
+ documentable: {
+ id: 1,
+ type: 'Conversation',
+ display_id: '12345',
+ },
assistant: {
account_id: 3,
config: { product_name: 'Chatwoot' },
@@ -189,6 +204,11 @@ export const responsesList = [
created_at: 1736283360,
id: 90,
question: 'How do I enable dark mode in Chatwoot?',
+ documentable: {
+ id: 1,
+ type: 'Captain::Document',
+ name: 'Document 1',
+ },
assistant: {
account_id: 4,
config: { product_name: 'Chatwoot' },
@@ -205,6 +225,11 @@ export const responsesList = [
created_at: 1736283370,
id: 91,
question: 'How do I add a new team member in Chatwoot?',
+ documentable: {
+ id: 1,
+ type: 'User',
+ available_name: 'John Doe',
+ },
assistant: {
account_id: 5,
config: { product_name: 'Chatwoot' },
@@ -221,6 +246,11 @@ export const responsesList = [
created_at: 1736283380,
id: 92,
question: 'What are campaigns in Chatwoot?',
+ documentable: {
+ id: 1,
+ type: 'User',
+ available_name: 'John Doe',
+ },
assistant: {
account_id: 6,
config: { product_name: 'Chatwoot' },
diff --git a/app/javascript/dashboard/helper/inbox.js b/app/javascript/dashboard/helper/inbox.js
index 401739706..da5edeb74 100644
--- a/app/javascript/dashboard/helper/inbox.js
+++ b/app/javascript/dashboard/helper/inbox.js
@@ -171,3 +171,30 @@ export const getInboxWarningIconClass = (type, reauthorizationRequired) => {
}
return '';
};
+
+export const getChannelTypeDisplayName = (type, medium) => {
+ // Handle Twilio special case
+ if (type === INBOX_TYPES.TWILIO) {
+ return medium === TWILIO_CHANNEL_MEDIUM.WHATSAPP
+ ? 'Twilio WhatsApp'
+ : 'Twilio SMS';
+ }
+
+ // Map channel types to display names
+ const channelTypeMap = {
+ [INBOX_TYPES.WEB]: 'Website',
+ [INBOX_TYPES.FB]: 'Messenger',
+ [INBOX_TYPES.TWITTER]: 'Twitter',
+ [INBOX_TYPES.WHATSAPP]: 'WhatsApp',
+ [INBOX_TYPES.API]: 'API',
+ [INBOX_TYPES.EMAIL]: 'Email',
+ [INBOX_TYPES.TELEGRAM]: 'Telegram',
+ [INBOX_TYPES.LINE]: 'Line',
+ [INBOX_TYPES.SMS]: 'SMS',
+ [INBOX_TYPES.INSTAGRAM]: 'Instagram',
+ [INBOX_TYPES.TIKTOK]: 'TikTok',
+ [INBOX_TYPES.VOICE]: 'Voice',
+ };
+
+ return channelTypeMap[type] || 'Chat';
+};
diff --git a/app/javascript/dashboard/helper/specs/inbox.spec.js b/app/javascript/dashboard/helper/specs/inbox.spec.js
index 775f05d3a..830dfa1fe 100644
--- a/app/javascript/dashboard/helper/specs/inbox.spec.js
+++ b/app/javascript/dashboard/helper/specs/inbox.spec.js
@@ -3,6 +3,7 @@ import {
getInboxClassByType,
getInboxIconByType,
getInboxWarningIconClass,
+ getChannelTypeDisplayName,
} from '../inbox';
describe('#Inbox Helpers', () => {
@@ -166,4 +167,86 @@ describe('#Inbox Helpers', () => {
);
});
});
+
+ describe('getChannelTypeDisplayName', () => {
+ it('returns "Website" for web widget', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.WEB)).toBe('Website');
+ });
+
+ it('returns "Messenger" for Facebook', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.FB)).toBe('Messenger');
+ });
+
+ it('returns "Twitter" for Twitter', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.TWITTER)).toBe('Twitter');
+ });
+
+ it('returns "WhatsApp" for WhatsApp', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.WHATSAPP)).toBe('WhatsApp');
+ });
+
+ it('returns "API" for API', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.API)).toBe('API');
+ });
+
+ it('returns "Email" for Email', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.EMAIL)).toBe('Email');
+ });
+
+ it('returns "Telegram" for Telegram', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.TELEGRAM)).toBe('Telegram');
+ });
+
+ it('returns "Line" for Line', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.LINE)).toBe('Line');
+ });
+
+ it('returns "SMS" for SMS', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.SMS)).toBe('SMS');
+ });
+
+ it('returns "Instagram" for Instagram', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.INSTAGRAM)).toBe(
+ 'Instagram'
+ );
+ });
+
+ it('returns "TikTok" for TikTok', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.TIKTOK)).toBe('TikTok');
+ });
+
+ it('returns "Voice" for Voice', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.VOICE)).toBe('Voice');
+ });
+
+ it('returns "Chat" for unknown type', () => {
+ expect(getChannelTypeDisplayName('UNKNOWN_TYPE')).toBe('Chat');
+ });
+
+ describe('Twilio cases', () => {
+ it('returns "Twilio WhatsApp" for Twilio with whatsapp medium', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.TWILIO, 'whatsapp')).toBe(
+ 'Twilio WhatsApp'
+ );
+ });
+
+ it('returns "Twilio SMS" for Twilio with sms medium', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.TWILIO, 'sms')).toBe(
+ 'Twilio SMS'
+ );
+ });
+
+ it('returns "Twilio SMS" for Twilio with undefined medium', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.TWILIO, undefined)).toBe(
+ 'Twilio SMS'
+ );
+ });
+
+ it('returns "Twilio SMS" for Twilio with empty medium', () => {
+ expect(getChannelTypeDisplayName(INBOX_TYPES.TWILIO, '')).toBe(
+ 'Twilio SMS'
+ );
+ });
+ });
+ });
});
diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json
index c08cde4d3..fab26e5d4 100644
--- a/app/javascript/dashboard/i18n/locale/en/integrations.json
+++ b/app/javascript/dashboard/i18n/locale/en/integrations.json
@@ -867,6 +867,7 @@
"SEARCH_PLACEHOLDER": "Search FAQs...",
"BULK_APPROVE_BUTTON": "Approve",
"BULK_DELETE_BUTTON": "Delete",
+ "CLEAR_SELECTION": "Clear selection",
"BULK_APPROVE": {
"SUCCESS_MESSAGE": "FAQs approved successfully",
"ERROR_MESSAGE": "There was an error approving the FAQs, please try again."
diff --git a/app/javascript/dashboard/routes/dashboard/captain/assistants/guardrails/Index.vue b/app/javascript/dashboard/routes/dashboard/captain/assistants/guardrails/Index.vue
index f61c2ed53..c2c0270d1 100644
--- a/app/javascript/dashboard/routes/dashboard/captain/assistants/guardrails/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/captain/assistants/guardrails/Index.vue
@@ -212,7 +212,7 @@ const addAllExample = () => {
-
+
{
-
+
-
+
{
{{ t('CAPTAIN.ASSISTANTS.SCENARIOS.SEARCH_EMPTY_MESSAGE') }}
-
+
{
-
+
{
// Bulk action
const bulkSelectedIds = ref(new Set());
-const hoveredCard = ref(null);
const buildSelectedCountLabel = computed(() => {
const count = responses.value?.length || 0;
@@ -129,10 +129,6 @@ const selectedCountLabel = computed(() => {
});
});
-const handleCardHover = (isHovered, id) => {
- hoveredCard.value = isHovered ? id : null;
-};
-
const handleCardSelect = id => {
const selected = new Set(bulkSelectedIds.value);
selected[selected.has(id) ? 'delete' : 'add'](id);
@@ -157,6 +153,10 @@ const fetchResponseAfterBulkAction = () => {
bulkSelectedIds.value = new Set();
};
+const clearSelection = () => {
+ bulkSelectedIds.value = new Set();
+};
+
const onPageChange = page => {
const hadSelection = bulkSelectedIds.value.size > 0;
@@ -252,12 +252,23 @@ onMounted(() => {
:select-all-label="buildSelectedCountLabel"
:selected-count-label="selectedCountLabel"
:delete-label="$t('CAPTAIN.RESPONSES.BULK_DELETE_BUTTON')"
- class="w-fit"
:class="{
- 'mb-2': bulkSelectedIds.size > 0,
+ 'mt-2': bulkSelectedIds.size > 0,
}"
+ class="justify-between"
+ animation-direction="vertical"
@bulk-delete="bulkDeleteDialog.dialogRef.open()"
- />
+ >
+
+
+
+
@@ -271,16 +282,16 @@ onMounted(() => {
{{ $t('CAPTAIN.RESPONSES.PENDING_BANNER.TITLE') }}
-
+
{
:created-at="response.created_at"
:updated-at="response.updated_at"
:is-selected="bulkSelectedIds.has(response.id)"
- :selectable="hoveredCard === response.id || bulkSelectedIds.size > 0"
:show-menu="!bulkSelectedIds.has(response.id)"
:show-actions="false"
@action="handleAction"
@navigate="handleNavigationAction"
@select="handleCardSelect"
- @hover="isHovered => handleCardHover(isHovered, response.id)"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/captain/responses/Pending.vue b/app/javascript/dashboard/routes/dashboard/captain/responses/Pending.vue
index b26658f9c..9f7573886 100644
--- a/app/javascript/dashboard/routes/dashboard/captain/responses/Pending.vue
+++ b/app/javascript/dashboard/routes/dashboard/captain/responses/Pending.vue
@@ -140,7 +140,6 @@ const fetchResponses = (page = 1) => {
// Bulk action
const bulkSelectedIds = ref(new Set());
-const hoveredCard = ref(null);
const buildSelectedCountLabel = computed(() => {
const count = filteredResponses.value?.length || 0;
@@ -156,10 +155,6 @@ const selectedCountLabel = computed(() => {
});
});
-const handleCardHover = (isHovered, id) => {
- hoveredCard.value = isHovered ? id : null;
-};
-
const handleCardSelect = id => {
const selected = new Set(bulkSelectedIds.value);
selected[selected.has(id) ? 'delete' : 'add'](id);
@@ -196,6 +191,10 @@ const handleBulkApprove = async () => {
}
};
+const clearSelection = () => {
+ bulkSelectedIds.value = new Set();
+};
+
const onPageChange = page => {
const hadSelection = bulkSelectedIds.value.size > 0;
@@ -291,10 +290,11 @@ onMounted(() => {
:select-all-label="buildSelectedCountLabel"
:selected-count-label="selectedCountLabel"
:delete-label="$t('CAPTAIN.RESPONSES.BULK_DELETE_BUTTON')"
- class="w-fit"
:class="{
'mb-2': bulkSelectedIds.size > 0,
}"
+ class="justify-between"
+ animation-direction="vertical"
@bulk-delete="bulkDeleteDialog.dialogRef.open()"
>
@@ -306,6 +306,14 @@ onMounted(() => {
class="!px-1.5"
@click="handleBulkApprove"
/>
+
+
@@ -325,7 +333,7 @@ onMounted(() => {
-
+
{
:created-at="response.created_at"
:updated-at="response.updated_at"
:is-selected="bulkSelectedIds.has(response.id)"
- :selectable="hoveredCard === response.id || bulkSelectedIds.size > 0"
:show-menu="false"
:show-actions="!bulkSelectedIds.has(response.id)"
@action="handleAction"
@navigate="handleNavigationAction"
@select="handleCardSelect"
- @hover="isHovered => handleCardHover(isHovered, response.id)"
/>
diff --git a/app/javascript/dashboard/routes/dashboard/captain/tools/Index.vue b/app/javascript/dashboard/routes/dashboard/captain/tools/Index.vue
index c350e76f3..e81b517fe 100644
--- a/app/javascript/dashboard/routes/dashboard/captain/tools/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/captain/tools/Index.vue
@@ -100,7 +100,7 @@ onMounted(() => {
-
+
{
:all-items="allItems"
:select-all-label="selectAllLabel"
:selected-count-label="selectedCountLabel"
- class="py-2 ltr:!pr-2 rtl:!pl-2 justify-between"
+ animation-direction="vertical"
+ class="justify-between"
>