Merge branch 'develop' into feat/github-integration

This commit is contained in:
Muhsin Keloth
2025-07-14 11:32:05 +05:30
committed by GitHub
23 changed files with 1254 additions and 99 deletions
@@ -5,10 +5,13 @@ import { useStore } from 'dashboard/composables/store';
import { useMapGetter } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
import EditAssistantForm from '../../../../components-next/captain/pageComponents/assistant/EditAssistantForm.vue';
import AssistantPlayground from 'dashboard/components-next/captain/assistant/AssistantPlayground.vue';
import AssistantSettings from 'dashboard/routes/dashboard/captain/assistants/settings/Settings.vue';
const route = useRoute();
const store = useStore();
const { t } = useI18n();
@@ -19,6 +22,16 @@ const assistant = computed(() =>
store.getters['captainAssistants/getRecord'](Number(assistantId))
);
const isFeatureEnabledonAccount = useMapGetter(
'accounts/isFeatureEnabledonAccount'
);
const currentAccountId = useMapGetter('getCurrentAccountId');
const isCaptainV2Enabled = isFeatureEnabledonAccount.value(
currentAccountId.value,
FEATURE_FLAGS.CAPTAIN_V2
);
const isAssistantAvailable = computed(() => !!assistant.value?.id);
const handleSubmit = async updatedAssistant => {
@@ -36,14 +49,16 @@ const handleSubmit = async updatedAssistant => {
};
onMounted(() => {
if (!isAssistantAvailable.value) {
if (!isAssistantAvailable.value || !isCaptainV2Enabled) {
store.dispatch('captainAssistants/show', assistantId);
}
});
</script>
<template>
<AssistantSettings v-if="isCaptainV2Enabled" />
<PageLayout
v-else
:header-title="assistant?.name"
:show-pagination-footer="false"
:is-fetching="isFetching"
@@ -0,0 +1,154 @@
<script setup>
import { computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
import { useStore } from 'dashboard/composables/store';
import { useMapGetter } from 'dashboard/composables/store';
import SettingsPageLayout from 'dashboard/components-next/captain/SettingsPageLayout.vue';
import SettingsHeader from 'dashboard/components-next/captain/pageComponents/settings/SettingsHeader.vue';
import AssistantBasicSettingsForm from 'dashboard/components-next/captain/pageComponents/assistant/settings/AssistantBasicSettingsForm.vue';
import AssistantSystemSettingsForm from 'dashboard/components-next/captain/pageComponents/assistant/settings/AssistantSystemSettingsForm.vue';
import AssistantControlItems from 'dashboard/components-next/captain/pageComponents/assistant/settings/AssistantControlItems.vue';
const { t } = useI18n();
const route = useRoute();
const store = useStore();
const assistantId = route.params.assistantId;
const uiFlags = useMapGetter('captainAssistants/getUIFlags');
const isFetching = computed(() => uiFlags.value.fetchingItem);
const assistant = computed(() =>
store.getters['captainAssistants/getRecord'](Number(assistantId))
);
const isAssistantAvailable = computed(() => !!assistant.value?.id);
const controlItems = computed(() => {
return [
{
name: t(
'CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.OPTIONS.GUARDRAILS.TITLE'
),
description: t(
'CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.OPTIONS.GUARDRAILS.DESCRIPTION'
),
// routeName: 'captain_assistants_guardrails_index',
},
{
name: t(
'CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.OPTIONS.SCENARIOS.TITLE'
),
description: t(
'CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.OPTIONS.SCENARIOS.DESCRIPTION'
),
// routeName: 'captain_assistants_scenarios_index',
},
{
name: t(
'CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.OPTIONS.RESPONSE_GUIDELINES.TITLE'
),
description: t(
'CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.OPTIONS.RESPONSE_GUIDELINES.DESCRIPTION'
),
// routeName: 'captain_assistants_guidelines_index',
},
];
});
const breadcrumbItems = computed(() => {
const activeControlItem = controlItems.value?.find(
item => item.routeName === route.name
);
return [
{
label: t('CAPTAIN.ASSISTANTS.SETTINGS.BREADCRUMB.ASSISTANT'),
routeName: 'captain_assistants_index',
},
{ label: assistant.value?.name, routeName: 'captain_assistants_edit' },
...(activeControlItem
? [
{
label: activeControlItem.name,
routeName: activeControlItem.routeName,
},
]
: []),
];
});
const handleSubmit = async updatedAssistant => {
try {
await store.dispatch('captainAssistants/update', {
id: assistantId,
...updatedAssistant,
});
useAlert(t('CAPTAIN.ASSISTANTS.EDIT.SUCCESS_MESSAGE'));
} catch (error) {
const errorMessage =
error?.message || t('CAPTAIN.ASSISTANTS.EDIT.ERROR_MESSAGE');
useAlert(errorMessage);
}
};
onMounted(() => {
if (!isAssistantAvailable.value) {
store.dispatch('captainAssistants/show', assistantId);
}
});
</script>
<template>
<SettingsPageLayout
:breadcrumb-items="breadcrumbItems"
:is-fetching="isFetching"
class="[&>div]:max-w-[80rem]"
>
<template #body>
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-6">
<SettingsHeader
:heading="t('CAPTAIN.ASSISTANTS.SETTINGS.BASIC_SETTINGS.TITLE')"
:description="
t('CAPTAIN.ASSISTANTS.SETTINGS.BASIC_SETTINGS.DESCRIPTION')
"
/>
<AssistantBasicSettingsForm
:assistant="assistant"
@submit="handleSubmit"
/>
</div>
<span class="h-px w-full bg-n-weak mt-2" />
<div class="flex flex-col gap-6">
<SettingsHeader
:heading="t('CAPTAIN.ASSISTANTS.SETTINGS.SYSTEM_SETTINGS.TITLE')"
:description="
t('CAPTAIN.ASSISTANTS.SETTINGS.SYSTEM_SETTINGS.DESCRIPTION')
"
/>
<AssistantSystemSettingsForm
:assistant="assistant"
@submit="handleSubmit"
/>
</div>
</div>
</template>
<template #controls>
<div class="flex flex-col gap-6">
<SettingsHeader
:heading="t('CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.TITLE')"
:description="
t('CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.DESCRIPTION')
"
/>
<div class="flex flex-col gap-6">
<AssistantControlItems
v-for="item in controlItems"
:key="item.name"
:control-item="item"
/>
</div>
</div>
</template>
</SettingsPageLayout>
</template>
@@ -3,6 +3,7 @@ import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
import { frontendURL } from '../../../helper/URLHelper';
import AssistantIndex from './assistants/Index.vue';
import AssistantEdit from './assistants/Edit.vue';
// import AssistantSettings from './assistants/settings/Settings.vue';
import AssistantInboxesIndex from './assistants/inboxes/Index.vue';
import DocumentsIndex from './documents/Index.vue';
import ResponsesIndex from './responses/Index.vue';
@@ -55,6 +55,12 @@ const statusOptions = computed(() =>
}))
);
const filteredResponses = computed(() => {
return selectedStatus.value === 'pending'
? responses.value.filter(r => r.status === 'pending')
: responses.value;
});
const selectedStatusLabel = computed(() => {
const status = statusOptions.value.find(
option => option.value === selectedStatus.value
@@ -94,7 +100,9 @@ const handleEdit = () => {
};
const handleAction = ({ action, id }) => {
selectedResponse.value = responses.value.find(response => id === response.id);
selectedResponse.value = filteredResponses.value.find(
response => id === response.id
);
nextTick(() => {
if (action === 'delete') {
handleDelete();
@@ -139,7 +147,7 @@ const hoveredCard = ref(null);
const bulkSelectionState = computed(() => {
const selectedCount = bulkSelectedIds.value.size;
const totalCount = responses.value?.length || 0;
const totalCount = filteredResponses.value?.length || 0;
return {
hasSelected: selectedCount > 0,
@@ -152,13 +160,13 @@ const bulkCheckbox = computed({
get: () => bulkSelectionState.value.allSelected,
set: value => {
bulkSelectedIds.value = value
? new Set(responses.value.map(r => r.id))
? new Set(filteredResponses.value.map(r => r.id))
: new Set();
},
});
const buildSelectedCountLabel = computed(() => {
const count = responses.value?.length || 0;
const count = filteredResponses.value?.length || 0;
return bulkSelectionState.value.allSelected
? t('CAPTAIN.RESPONSES.UNSELECT_ALL', { count })
: t('CAPTAIN.RESPONSES.SELECT_ALL', { count });
@@ -174,6 +182,24 @@ const handleCardSelect = id => {
bulkSelectedIds.value = selected;
};
const fetchResponseAfterBulkAction = () => {
const hasNoResponsesLeft = filteredResponses.value?.length === 0;
const currentPage = responseMeta.value?.page;
if (hasNoResponsesLeft) {
// Page is now empty after bulk action.
// Fetch the previous page if not already on the first page.
const pageToFetch = currentPage > 1 ? currentPage - 1 : currentPage;
fetchResponses(pageToFetch);
} else {
// Page still has responses left, re-fetch the same page.
fetchResponses(currentPage);
}
// Clear selection
bulkSelectedIds.value = new Set();
};
const handleBulkApprove = async () => {
try {
await store.dispatch(
@@ -181,8 +207,7 @@ const handleBulkApprove = async () => {
Array.from(bulkSelectedIds.value)
);
// Clear selection
bulkSelectedIds.value = new Set();
fetchResponseAfterBulkAction();
useAlert(t('CAPTAIN.RESPONSES.BULK_APPROVE.SUCCESS_MESSAGE'));
} catch (error) {
useAlert(
@@ -205,23 +230,13 @@ const onPageChange = page => {
};
const onDeleteSuccess = () => {
if (responses.value?.length === 0 && responseMeta.value?.page > 1) {
if (filteredResponses.value?.length === 0 && responseMeta.value?.page > 1) {
onPageChange(responseMeta.value.page - 1);
}
};
const onBulkDeleteSuccess = () => {
// Only fetch if no records left
if (responses.value?.length === 0) {
const page =
responseMeta.value?.page > 1
? responseMeta.value.page - 1
: responseMeta.value.page;
fetchResponses(page);
}
// Clear selection
bulkSelectedIds.value = new Set();
fetchResponseAfterBulkAction();
};
const handleStatusFilterChange = ({ value }) => {
@@ -249,8 +264,8 @@ onMounted(() => {
:header-title="$t('CAPTAIN.RESPONSES.HEADER')"
:button-label="$t('CAPTAIN.RESPONSES.ADD_NEW')"
:is-fetching="isFetching"
:is-empty="!responses.length"
:show-pagination-footer="!isFetching && !!responses.length"
:is-empty="!filteredResponses.length"
:show-pagination-footer="!isFetching && !!filteredResponses.length"
:feature-flag="FEATURE_FLAGS.CAPTAIN"
@update:current-page="onPageChange"
@click="handleCreate"
@@ -368,7 +383,7 @@ onMounted(() => {
<div class="flex flex-col gap-4">
<ResponseCard
v-for="response in responses"
v-for="response in filteredResponses"
:id="response.id"
:key="response.id"
:question="response.question"
@@ -380,6 +395,7 @@ onMounted(() => {
:updated-at="response.updated_at"
:is-selected="bulkSelectedIds.has(response.id)"
:selectable="hoveredCard === response.id || bulkSelectedIds.size > 0"
:show-menu="!bulkSelectedIds.has(response.id)"
@action="handleAction"
@navigate="handleNavigationAction"
@select="handleCardSelect"