Compare commits

...
Author SHA1 Message Date
Sivin VargheseandGitHub 83a2ddd670 Merge branch 'develop' into feat/CW-5624 2025-11-06 14:21:26 +05:30
Sivin VargheseandGitHub 810c9eb151 Merge branch 'develop' into feat/CW-5624 2025-11-03 19:20:15 +05:30
Sivin VargheseandGitHub 465c7416bb Merge branch 'develop' into feat/CW-5624 2025-10-22 14:15:19 +05:30
Sivin VargheseandGitHub 2b34417ade Merge branch 'develop' into feat/CW-5624 2025-10-21 10:27:20 +05:30
Sivin VargheseandGitHub 476b0bee2f Merge branch 'develop' into feat/CW-5624 2025-10-15 15:38:36 +05:30
Sivin VargheseandGitHub ae0231c378 Merge branch 'develop' into feat/CW-5624 2025-10-13 11:50:25 +05:30
Sivin VargheseandGitHub 4030e4a140 Merge branch 'develop' into feat/CW-5624 2025-10-08 18:47:12 +05:30
Muhsin KelothandGitHub e9078d2544 Merge branch 'develop' into feat/CW-5624 2025-10-08 18:04:39 +05:30
Sivin VargheseandGitHub bfaa450e2a Merge branch 'develop' into feat/CW-5624 2025-10-08 08:48:41 +05:30
Sivin VargheseandGitHub 0b207f43bb Merge branch 'develop' into feat/CW-5624 2025-10-07 19:18:28 +05:30
Sivin VargheseandGitHub 07d268c1ff Merge branch 'develop' into feat/CW-5624 2025-10-06 16:13:16 +05:30
Sivin VargheseandGitHub 4db51103a0 Merge branch 'develop' into feat/CW-5624 2025-10-06 10:01:03 +05:30
Sivin VargheseandGitHub 7a972b2a8a Merge branch 'develop' into feat/CW-5624 2025-10-05 20:09:37 +05:30
Sivin VargheseandGitHub 454b7081c2 Merge branch 'develop' into feat/CW-5624 2025-10-01 20:46:46 +05:30
Sivin VargheseandGitHub 1e3c6c8d30 Merge branch 'develop' into feat/CW-5624 2025-09-24 20:56:05 +05:30
Sivin VargheseandGitHub 2dcef85c41 Merge branch 'develop' into feat/CW-5624 2025-09-24 18:58:41 +05:30
Sivin VargheseandGitHub 6aac6662ff Merge branch 'develop' into feat/CW-5624 2025-09-24 12:26:46 +05:30
Sivin VargheseandGitHub 116d512ca1 Merge branch 'develop' into feat/CW-5624 2025-09-23 20:20:50 +05:30
Sivin VargheseandGitHub 2ed74ea783 Merge branch 'develop' into feat/CW-5624 2025-09-23 12:05:55 +05:30
Muhsin KelothandGitHub 9aabd08273 Merge branch 'develop' into feat/CW-5624 2025-09-23 09:23:34 +05:30
Sivin VargheseandGitHub ab6aa0f4b6 Merge branch 'develop' into feat/CW-5624 2025-09-22 20:28:21 +05:30
Muhsin KelothandGitHub ea1bc3fde1 Merge branch 'develop' into feat/CW-5624 2025-09-22 17:07:27 +05:30
Sivin VargheseandGitHub 48d70acc51 Merge branch 'develop' into feat/CW-5624 2025-09-22 11:21:18 +05:30
Sivin VargheseandGitHub 5ca706e332 Merge branch 'feat/captain-animating-svg' into feat/CW-5624 2025-09-19 12:52:34 +05:30
Sivin VargheseandGitHub d05ab8b5c6 Merge branch 'develop' into feat/captain-animating-svg 2025-09-19 12:52:29 +05:30
Sivin VargheseandGitHub 1f3e6070b8 Merge branch 'feat/captain-animating-svg' into feat/CW-5624 2025-09-19 12:44:24 +05:30
Sivin VargheseandGitHub 40dcaf12b1 Merge branch 'develop' into feat/captain-animating-svg 2025-09-19 12:44:16 +05:30
Sivin VargheseandGitHub 3ddfbe9e65 Merge branch 'feat/captain-animating-svg' into feat/CW-5624 2025-09-18 20:28:02 +05:30
Sivin VargheseandGitHub 744cf90f7c Merge branch 'develop' into feat/captain-animating-svg 2025-09-18 20:27:55 +05:30
Sivin VargheseandGitHub ad34eed78a Merge branch 'feat/captain-animating-svg' into feat/CW-5624 2025-09-18 13:33:49 +05:30
Sivin VargheseandGitHub 56e3a479a0 Merge branch 'develop' into feat/captain-animating-svg 2025-09-18 13:33:44 +05:30
iamsivin 0df627d5f5 feat: Assistants creation flow 2025-09-17 17:34:30 +05:30
iamsivin 6858a6f867 chore: Minor fix 2025-09-17 15:24:01 +05:30
iamsivin e1785a3a35 chore: Minor fix 2025-09-17 15:14:45 +05:30
iamsivin eeda6a9acf chore: Clean Up 2025-09-17 14:50:45 +05:30
iamsivin 88ef2aca45 chore: Add setting captain Icon 2025-09-17 14:49:51 +05:30
Sivin VargheseandGitHub 2cbf11c913 Merge branch 'develop' into feat/captain-animating-svg 2025-09-17 11:13:16 +05:30
Sivin VargheseandGitHub 9a94d49cff Merge branch 'develop' into feat/captain-animating-svg 2025-09-16 22:00:45 +05:30
iamsivin 0549d4b9ec feat: Captain animating SVGs 2025-09-16 16:36:54 +05:30
12 changed files with 1182 additions and 9 deletions
@@ -100,14 +100,14 @@ const handlePageChange = event => {
<div
v-if="!showPaywall && buttonLabel"
v-on-clickaway="() => emit('close')"
class="relative group/campaign-button"
class="relative group/captain-button"
>
<Policy :permissions="buttonPolicy">
<Button
:label="buttonLabel"
icon="i-lucide-plus"
size="sm"
class="group-hover/campaign-button:brightness-110"
class="group-hover/captain-button:brightness-110"
@click="handleButtonClick"
/>
</Policy>
@@ -3,6 +3,7 @@ import { useRouter } from 'vue-router';
import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import Breadcrumb from 'dashboard/components-next/breadcrumb/Breadcrumb.vue';
import Button from 'dashboard/components-next/button/Button.vue';
defineProps({
isFetching: {
@@ -33,9 +34,13 @@ defineProps({
type: Array,
default: () => [],
},
headerButtonLabel: {
type: String,
default: '',
},
});
const emit = defineEmits(['update:currentPage']);
const emit = defineEmits(['update:currentPage', 'click']);
const router = useRouter();
@@ -48,6 +53,10 @@ const handleBreadcrumbClick = item => {
name: item.routeName,
});
};
const onHeaderButtonClick = () => {
emit('click');
};
</script>
<template>
@@ -55,8 +64,20 @@ const handleBreadcrumbClick = item => {
class="px-6 flex flex-col w-full h-screen overflow-y-auto bg-n-background"
>
<div class="max-w-[60rem] mx-auto flex flex-col w-full h-full mb-4">
<header class="mb-7 sticky top-0 bg-n-background pt-4 z-20">
<header
class="mb-7 sticky top-0 bg-n-background pt-4 z-20 w-full flex justify-between items-center"
>
<Breadcrumb :items="breadcrumbItems" @click="handleBreadcrumbClick" />
<div class="flex gap-4 items-center">
<slot name="headerControls" />
<Button
v-if="headerButtonLabel"
class="ml-auto"
@click="onHeaderButtonClick"
>
{{ headerButtonLabel }}
</Button>
</div>
</header>
<main class="flex gap-16 w-full flex-1 pb-16">
<section
@@ -0,0 +1,40 @@
<script setup>
import Icon from 'next/icon/Icon.vue';
defineProps({
title: {
type: String,
default: '',
},
icon: {
type: String,
default: '',
},
subtitle: {
type: String,
default: '',
},
description: {
type: String,
default: '',
},
});
</script>
<template>
<div class="flex items-center justify-between gap-14 mt-3">
<div class="flex flex-col items-start gap-4">
<div
class="p-2 flex items-center gap-1.5 rounded-[10px] border border-n-weak"
>
<Icon :icon="icon" class="size-4 text-n-slate-11" />
<h3 class="text-n-slate-12 text-sm font-medium">{{ title }}</h3>
</div>
<p class="text-n-slate-12 text-lg leading-6 font-semibold">
{{ subtitle }}
</p>
<p class="text-n-slate-11 text-sm">{{ description }}</p>
</div>
<slot />
</div>
</template>
@@ -0,0 +1,58 @@
<script setup>
import { ref, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
import InlineInput from 'dashboard/components-next/inline-input/InlineInput.vue';
const emit = defineEmits(['confirm', 'cancel']);
const { t } = useI18n();
const dialogRef = ref(null);
const state = reactive({
name: '',
url: '',
});
const handleConfirm = () => {
emit('confirm', state);
};
const handleCancel = () => {
emit('cancel');
};
defineExpose({ dialogRef });
</script>
<template>
<Dialog
ref="dialogRef"
type="edit"
:confirm-button-label="
t('CAPTAIN.ASSISTANTS.ONBOARDING.CREATE.CREATE_BUTTON')
"
@close="handleCancel"
@confirm="handleConfirm"
>
<fieldset class="flex flex-col gap-10 py-8">
<InlineInput
v-model="state.name"
:label="t('CAPTAIN.ASSISTANTS.ONBOARDING.CREATE.NAME')"
:placeholder="
t('CAPTAIN.ASSISTANTS.ONBOARDING.CREATE.NAME_PLACEHOLDER')
"
class="flex flex-col !items-start !gap-2 [&>input]:text-base [&>label]:text-xl"
/>
<InlineInput
v-model="state.url"
:label="t('CAPTAIN.ASSISTANTS.ONBOARDING.CREATE.URL')"
:placeholder="t('CAPTAIN.ASSISTANTS.ONBOARDING.CREATE.URL_PLACEHOLDER')"
class="flex flex-col !items-start !gap-2 [&>input]:text-base [&>label]:text-xl"
/>
</fieldset>
<template #footer />
</Dialog>
</template>
@@ -479,6 +479,73 @@
"ERROR_MESSAGE": "There was an error updating the assistant, please try again.",
"NOT_FOUND": "Could not find the assistant. Please try again."
},
"ONBOARDING": {
"HEADER": {
"TITLE": "Create an assistant",
"BUTTON_LABEL": "Save and proceed",
"GO_SETTINGS": "Go to settings",
"BUTTON_SAVE": "Save"
},
"CREATE": {
"NAME": "What would you call your assistant?",
"NAME_PLACEHOLDER": "Type your assistant name...",
"URL": "What is your brand website?",
"URL_PLACEHOLDER": "Type your brand website...",
"CREATE_BUTTON": "Lets go!"
},
"CREATING": "Creating your assistant...",
"BASE_SETTINGS": {
"TITLE": "Basic settings",
"SUBTITLE": "Meet {assistantName}",
"DESCRIPTION": "{assistantName} works for your product {productName}, and helps you reliably answer customer queries. Lets go through your assistant kit.",
"RESPONSE_TEMPERATURE": {
"TITLE": "Response temperature",
"DESCRIPTION": "Your assistant is quite creative with the responses keeping in mind the friendly voice of your brand.",
"HELP": "You can adjust this; lower = focused and predictable, higher = creative and varied."
},
"FEATURES": {
"TITLE": "Features",
"DESCRIPTION": "The work does not always end when the conversation does.",
"OPTIONS": {
"ALLOW_CONVERSATION_FAQS": "Assistant should generate FAQs from resolved conversations",
"ALLOW_MEMORIES": "Assistant should capture key details as memories from customer interactions"
}
}
},
"GUARDRAILS": {
"TITLE": "Guardrails",
"SUBTITLE": "Stay focused on what is relevant",
"DESCRIPTION": "Guardrails help you to keep only the questions you want your assistant to answer, and filter out anything that is off-limits or off-topic.",
"BULK_ACTION": {
"SELECTED": "{count} item selected | {count} items selected",
"SELECT_ALL": "Select all ({count})",
"UNSELECT_ALL": "Unselect all ({count})",
"BULK_DELETE_BUTTON": "Delete"
}
},
"SCENARIOS": {
"TITLE": "Scenarios",
"SUBTITLE": "Have a playbook ready for common situations",
"DESCRIPTION": "Scenarios help your assistant respond smartly based on the situation. You can set tone, tools, or even actions to take. Its like giving it a playbook to follow.",
"BULK_ACTION": {
"SELECTED": "{count} item selected | {count} items selected",
"SELECT_ALL": "Select all ({count})",
"UNSELECT_ALL": "Unselect all ({count})",
"BULK_DELETE_BUTTON": "Delete"
}
},
"GUIDELINES": {
"TITLE": "Response guidelines",
"SUBTITLE": "Serve the right answers",
"DESCRIPTION": "Response guidelines help you to decide how the assistant presents the final response. This makes sure answers feel right for your brand, your team, or your users.",
"BULK_ACTION": {
"SELECTED": "{count} item selected | {count} items selected",
"SELECT_ALL": "Select all ({count})",
"UNSELECT_ALL": "Unselect all ({count})",
"BULK_DELETE_BUTTON": "Delete"
}
}
},
"SETTINGS": {
"BREADCRUMB": {
"ASSISTANT": "Assistant"
@@ -10,6 +10,7 @@ import DeleteDialog from 'dashboard/components-next/captain/pageComponents/Delet
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
import CaptainPaywall from 'dashboard/components-next/captain/pageComponents/Paywall.vue';
import CreateAssistantDialog from 'dashboard/components-next/captain/pageComponents/assistant/CreateAssistantDialog.vue';
import CreateNewAssistantDialog from 'dashboard/components-next/captain/pageComponents/assistant/CreateNewAssistantDialog.vue';
import AssistantPageEmptyState from 'dashboard/components-next/captain/pageComponents/emptyStates/AssistantPageEmptyState.vue';
import FeatureSpotlightPopover from 'dashboard/components-next/feature-spotlight/FeatureSpotlightPopover.vue';
import LimitBanner from 'dashboard/components-next/captain/pageComponents/response/LimitBanner.vue';
@@ -27,15 +28,29 @@ const isFetching = computed(() => uiFlags.value.fetchingList);
const selectedAssistant = ref(null);
const deleteAssistantDialog = ref(null);
const isFeatureEnabledonAccount = useMapGetter(
'accounts/isFeatureEnabledonAccount'
);
const currentAccountId = useMapGetter('getCurrentAccountId');
const isCaptainV2Enabled = isFeatureEnabledonAccount.value(
currentAccountId.value,
FEATURE_FLAGS.CAPTAIN_V2
);
const handleDelete = () => {
deleteAssistantDialog.value.dialogRef.open();
};
const createAssistantDialog = ref(null);
const createNewAssistantDialog = ref(null);
const handleCreate = () => {
dialogType.value = 'create';
nextTick(() => createAssistantDialog.value.dialogRef.open());
if (isCaptainV2Enabled) {
createNewAssistantDialog.value?.dialogRef.open();
} else {
dialogType.value = 'create';
nextTick(() => createAssistantDialog.value.dialogRef.open());
}
};
const handleEdit = () => {
@@ -70,8 +85,16 @@ const handleAction = ({ action, id }) => {
};
const handleCreateClose = () => {
dialogType.value = '';
selectedAssistant.value = null;
if (!isCaptainV2Enabled) {
dialogType.value = '';
selectedAssistant.value = null;
}
};
const handleCreateAssistant = () => {
router.push({
name: 'captain_assistants_create_loader',
});
};
onMounted(() => store.dispatch('captainAssistants/get'));
@@ -131,8 +154,13 @@ onMounted(() => store.dispatch('captainAssistants/get'));
type="Assistants"
/>
<CreateNewAssistantDialog
v-if="isCaptainV2Enabled"
ref="createNewAssistantDialog"
@confirm="handleCreateAssistant"
/>
<CreateAssistantDialog
v-if="dialogType"
v-else-if="dialogType"
ref="createAssistantDialog"
:type="dialogType"
:selected-assistant="selectedAssistant"
@@ -0,0 +1,175 @@
<script setup>
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import RuleCard from 'dashboard/components-next/captain/assistant/RuleCard.vue';
import GuardrailsSVG from 'dashboard/components-next/captain/AnimatingImg/Guardrails.vue';
import BulkSelectBar from 'dashboard/components-next/captain/assistant/BulkSelectBar.vue';
import OnboardingHeader from 'dashboard/components-next/captain/assistant/OnboardingHeader.vue';
import SettingsPageLayout from 'dashboard/components-next/captain/SettingsPageLayout.vue';
const router = useRouter();
const { t } = useI18n();
const breadcrumbItems = computed(() => [
{
label: t('CAPTAIN.ASSISTANTS.SETTINGS.BREADCRUMB.ASSISTANT'),
routeName: 'captain_assistants_index',
},
{
label: t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.TITLE'),
routeName: 'captain_assistants_create',
},
]);
const guardrailsExample = ref([
{
id: 1,
content:
'Block queries that share or request sensitive personal information (e.g. phone numbers, passwords).',
},
{
id: 2,
content:
'Reject queries that include offensive, discriminatory, or threatening language.',
},
{
id: 3,
content:
'Deflect when the assistant is asked for legal or medical diagnosis or treatment.',
},
]);
// Bulk selection & hover state
const bulkSelectedIds = ref(new Set());
const hoveredCard = ref(null);
const handleRuleSelect = id => {
const selected = new Set(bulkSelectedIds.value);
selected[selected.has(id) ? 'delete' : 'add'](id);
bulkSelectedIds.value = selected;
};
const handleRuleHover = (isHovered, id) => {
hoveredCard.value = isHovered ? id : null;
};
const buildSelectedCountLabel = computed(() => {
const count = guardrailsExample.value.length || 0;
const isAllSelected = bulkSelectedIds.value.size === count && count > 0;
return isAllSelected
? t('CAPTAIN.ASSISTANTS.ONBOARDING.GUARDRAILS.BULK_ACTION.UNSELECT_ALL', {
count,
})
: t('CAPTAIN.ASSISTANTS.ONBOARDING.GUARDRAILS.BULK_ACTION.SELECT_ALL', {
count,
});
});
const selectedCountLabel = computed(() => {
return t('CAPTAIN.ASSISTANTS.ONBOARDING.GUARDRAILS.BULK_ACTION.SELECTED', {
count: bulkSelectedIds.value.size,
});
});
const bulkDeleteGuardrails = async () => {
try {
if (bulkSelectedIds.value.size === 0) return;
const updated = guardrailsExample.value.filter(
item => !bulkSelectedIds.value.has(item.id)
);
guardrailsExample.value = updated;
bulkSelectedIds.value.clear();
} catch {
// error
}
};
const editGuardrail = async ({ id, content }) => {
try {
guardrailsExample.value = guardrailsExample.value.map(item => {
if (item.id === id) {
return { id, content };
}
return item;
});
} catch {
// error
}
};
const deleteGuardrail = async id => {
try {
guardrailsExample.value = guardrailsExample.value.filter(
item => item.id !== id
);
} catch {
// error
}
};
const handleSaveAndNext = () => {
router.push({
name: 'captain_assistants_create_scenarios',
});
};
</script>
<template>
<SettingsPageLayout
:breadcrumb-items="breadcrumbItems"
:header-button-label="
t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.BUTTON_LABEL')
"
@click="handleSaveAndNext"
>
<template #body>
<OnboardingHeader
:title="t('CAPTAIN.ASSISTANTS.ONBOARDING.GUARDRAILS.TITLE')"
icon="i-lucide-traffic-cone"
:subtitle="t('CAPTAIN.ASSISTANTS.ONBOARDING.GUARDRAILS.SUBTITLE')"
:description="t('CAPTAIN.ASSISTANTS.ONBOARDING.GUARDRAILS.DESCRIPTION')"
>
<div class="w-[12.5rem] h-[9.75rem] flex-shrink-0">
<GuardrailsSVG class="w-full h-full" />
</div>
</OnboardingHeader>
<div class="pt-8 flex flex-col items-start gap-3 w-full">
<span class="text-base text-n-slate-11 font-medium mb-3">
{{ t('CAPTAIN.ASSISTANTS.ONBOARDING.GUARDRAILS.TITLE') }}
</span>
<div class="w-full gap-2 flex flex-col">
<BulkSelectBar
v-model="bulkSelectedIds"
:all-items="guardrailsExample"
:select-all-label="buildSelectedCountLabel"
:selected-count-label="selectedCountLabel"
:delete-label="
$t(
'CAPTAIN.ASSISTANTS.ONBOARDING.GUARDRAILS.BULK_ACTION.BULK_DELETE_BUTTON'
)
"
class="w-fit"
@bulk-delete="bulkDeleteGuardrails"
/>
<RuleCard
v-for="guardrail in guardrailsExample"
:id="guardrail.id"
:key="guardrail.id"
:content="guardrail.content"
:is-selected="bulkSelectedIds.has(guardrail.id)"
:selectable="
hoveredCard === guardrail.id || bulkSelectedIds.size > 0
"
@select="handleRuleSelect"
@edit="editGuardrail"
@delete="deleteGuardrail"
@hover="isHovered => handleRuleHover(isHovered, guardrail.id)"
/>
</div>
</div>
</template>
</SettingsPageLayout>
</template>
@@ -0,0 +1,185 @@
<script setup>
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import Button from 'dashboard/components-next/button/Button.vue';
import RuleCard from 'dashboard/components-next/captain/assistant/RuleCard.vue';
import ResponseGuidelinesSVG from 'dashboard/components-next/captain/AnimatingImg/ResponseGuidelines.vue';
import BulkSelectBar from 'dashboard/components-next/captain/assistant/BulkSelectBar.vue';
import OnboardingHeader from 'dashboard/components-next/captain/assistant/OnboardingHeader.vue';
import SettingsPageLayout from 'dashboard/components-next/captain/SettingsPageLayout.vue';
const router = useRouter();
const { t } = useI18n();
const breadcrumbItems = computed(() => [
{
label: t('CAPTAIN.ASSISTANTS.SETTINGS.BREADCRUMB.ASSISTANT'),
routeName: 'captain_assistants_index',
},
{
label: t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.TITLE'),
routeName: 'captain_assistants_create',
},
]);
const guidelinesExample = ref([
{
id: 1,
content:
'Block queries that share or request sensitive personal information (e.g. phone numbers, passwords).',
},
{
id: 2,
content:
'Reject queries that include offensive, discriminatory, or threatening language.',
},
{
id: 3,
content:
'Deflect when the assistant is asked for legal or medical diagnosis or treatment.',
},
]);
// Bulk selection & hover state
const bulkSelectedIds = ref(new Set());
const hoveredCard = ref(null);
const handleRuleSelect = id => {
const selected = new Set(bulkSelectedIds.value);
selected[selected.has(id) ? 'delete' : 'add'](id);
bulkSelectedIds.value = selected;
};
const handleRuleHover = (isHovered, id) => {
hoveredCard.value = isHovered ? id : null;
};
const buildSelectedCountLabel = computed(() => {
const count = guidelinesExample.value.length || 0;
const isAllSelected = bulkSelectedIds.value.size === count && count > 0;
return isAllSelected
? t('CAPTAIN.ASSISTANTS.ONBOARDING.GUIDELINES.BULK_ACTION.UNSELECT_ALL', {
count,
})
: t('CAPTAIN.ASSISTANTS.ONBOARDING.GUIDELINES.BULK_ACTION.SELECT_ALL', {
count,
});
});
const selectedCountLabel = computed(() => {
return t('CAPTAIN.ASSISTANTS.ONBOARDING.GUIDELINES.BULK_ACTION.SELECTED', {
count: bulkSelectedIds.value.size,
});
});
const bulkDeleteGuidelines = async () => {
try {
if (bulkSelectedIds.value.size === 0) return;
const updated = guidelinesExample.value.filter(
item => !bulkSelectedIds.value.has(item.id)
);
guidelinesExample.value = updated;
bulkSelectedIds.value.clear();
} catch {
// error
}
};
const editGuideline = async ({ id, content }) => {
try {
guidelinesExample.value = guidelinesExample.value.map(item => {
if (item.id === id) {
return { id, content };
}
return item;
});
} catch {
// error
}
};
const deleteGuideline = async id => {
try {
guidelinesExample.value = guidelinesExample.value.filter(
item => item.id !== id
);
} catch {
// error
}
};
const handleSave = () => {
router.push({
name: 'captain_assistants_index',
});
};
const handleGoBackSettings = () => {
router.push({
name: 'captain_assistants_edit',
});
};
</script>
<template>
<SettingsPageLayout
:breadcrumb-items="breadcrumbItems"
:header-button-label="t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.BUTTON_SAVE')"
@click="handleSave"
>
<template #headerControls>
<Button slate @click="handleGoBackSettings">
{{ t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.GO_SETTINGS') }}
</Button>
</template>
<template #body>
<OnboardingHeader
:title="t('CAPTAIN.ASSISTANTS.ONBOARDING.GUIDELINES.TITLE')"
icon="i-lucide-pencil-ruler"
:subtitle="t('CAPTAIN.ASSISTANTS.ONBOARDING.GUIDELINES.SUBTITLE')"
:description="t('CAPTAIN.ASSISTANTS.ONBOARDING.GUIDELINES.DESCRIPTION')"
>
<div class="w-[12.5rem] h-[9.75rem] flex-shrink-0">
<ResponseGuidelinesSVG class="w-full h-full" />
</div>
</OnboardingHeader>
<div class="pt-8 flex flex-col items-start gap-3 w-full">
<span class="text-base text-n-slate-11 font-medium mb-3">
{{ t('CAPTAIN.ASSISTANTS.ONBOARDING.GUIDELINES.TITLE') }}
</span>
<div class="w-full gap-2 flex flex-col">
<BulkSelectBar
v-model="bulkSelectedIds"
:all-items="guidelinesExample"
:select-all-label="buildSelectedCountLabel"
:selected-count-label="selectedCountLabel"
:delete-label="
$t(
'CAPTAIN.ASSISTANTS.ONBOARDING.GUIDELINES.BULK_ACTION.BULK_DELETE_BUTTON'
)
"
class="w-fit"
@bulk-delete="bulkDeleteGuidelines"
/>
<RuleCard
v-for="guideline in guidelinesExample"
:id="guideline.id"
:key="guideline.id"
:content="guideline.content"
:is-selected="bulkSelectedIds.has(guideline.id)"
:selectable="
hoveredCard === guideline.id || bulkSelectedIds.size > 0
"
@select="handleRuleSelect"
@edit="editGuideline"
@delete="deleteGuideline"
@hover="isHovered => handleRuleHover(isHovered, guideline.id)"
/>
</div>
</div>
</template>
</SettingsPageLayout>
</template>
@@ -0,0 +1,188 @@
<script setup>
import { ref, computed, watch } from 'vue';
import { useRafFn, useElementBounding, useMouse } from '@vueuse/core';
import { useMapGetter } from 'dashboard/composables/store';
import SettingsPageLayout from 'dashboard/components-next/captain/SettingsPageLayout.vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
const router = useRouter();
const { t } = useI18n();
const uiFlags = useMapGetter('captainAssistants/getUIFlags');
const isCreating = computed(() => uiFlags.value.creatingItem);
const breadcrumbItems = computed(() => [
{
label: t('CAPTAIN.ASSISTANTS.SETTINGS.BREADCRUMB.ASSISTANT'),
routeName: 'captain_assistants_index',
},
{
label: t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.TITLE'),
routeName: 'captain_assistants_create',
},
]);
const pageContainer = ref(null);
const svgRef = ref(null);
const eye1Group = ref(null);
const eye2Group = ref(null);
const { x: mouseX, y: mouseY } = useMouse();
const { left, top, width, height } = useElementBounding(svgRef);
const {
left: containerLeft,
top: containerTop,
right: containerRight,
bottom: containerBottom,
} = useElementBounding(pageContainer);
const pos = ref({
eye1: { x: 0, y: 0 },
eye2: { x: 0, y: 0 },
});
const BASE_MAX_TRAVEL = 6;
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
const lerp = (a, b, c) => a + (b - a) * c;
const setTransform = (el, x, y) => {
if (el)
el.setAttribute('transform', `translate(${x.toFixed(3)}, ${y.toFixed(3)})`);
};
const isMouseInContainer = computed(() => {
return (
mouseX.value >= containerLeft.value &&
mouseX.value <= containerRight.value &&
mouseY.value >= containerTop.value &&
mouseY.value <= containerBottom.value
);
});
useRafFn(() => {
if (!svgRef.value || !width.value || !height.value) return;
const cx = left.value + width.value / 2;
const cy = top.value + height.value / 2;
let dx = 0;
let dy = 0;
if (isMouseInContainer.value) {
dx = mouseX.value - cx;
dy = mouseY.value - cy;
}
const len = Math.hypot(dx, dy) || 1;
const scale = Math.min(width.value, height.value) / 100;
const maxTravel = BASE_MAX_TRAVEL * Math.max(1, scale);
const travelX = (dx / len) * clamp(len * 0.08, 0, maxTravel);
const travelY = (dy / len) * clamp(len * 0.06, 0, maxTravel * 0.8);
const targetEye1X = travelX * 0.9;
const targetEye1Y = travelY * 0.9;
const targetEye2X = travelX * 1.1;
const targetEye2Y = travelY * 1.1;
pos.value.eye1.x = lerp(pos.value.eye1.x, targetEye1X, 0.18);
pos.value.eye1.y = lerp(pos.value.eye1.y, targetEye1Y, 0.18);
pos.value.eye2.x = lerp(pos.value.eye2.x, targetEye2X, 0.18);
pos.value.eye2.y = lerp(pos.value.eye2.y, targetEye2Y, 0.18);
setTransform(eye1Group.value, pos.value.eye1.x, pos.value.eye1.y);
setTransform(eye2Group.value, pos.value.eye2.x, pos.value.eye2.y);
});
watch(isCreating, (newVal, oldVal) => {
if (oldVal && !newVal) {
setTimeout(() => {
router.push({ name: 'captain_assistants_create_settings' });
}, 5000);
}
});
</script>
<template>
<SettingsPageLayout ref="pageContainer" :breadcrumb-items="breadcrumbItems">
<template #body>
<div class="w-full h-full flex flex-col items-center justify-center">
<div
class="relative w-full max-w-[450px] aspect-square flex items-center justify-center"
>
<div
class="absolute inset-0 flex items-center justify-center pointer-events-none"
>
<div
v-for="(delay, i) in [0, 200, 400]"
:key="i"
class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 scale-1 aspect-square rounded-full border border-n-slate-3 animate-ripple"
:class="`w-[${60 + i * 20}%]`"
:style="{ animationDelay: `${delay}ms` }"
aria-hidden="true"
/>
</div>
<div class="relative flex items-center justify-center w-full h-full">
<svg
ref="svgRef"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 59 48"
width="59"
height="48"
class="block touch-none"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M44.1624 5.66847C33.8484 4.21327 25.4409 4.15585 14.8951 5.66206C12.2294 6.0428 10.4657 6.30009 9.12091 6.69312C7.87394 7.05755 7.16477 7.49209 6.57821 8.14764C5.36358 9.50511 5.20348 11.0404 5.03792 16.395C4.87521 21.6574 5.19749 26.3737 5.80791 31.6803C6.13318 34.508 6.35702 36.4164 6.71439 37.8673C7.05338 39.2435 7.46339 39.9835 8.03322 40.55C8.6076 41.121 9.34372 41.5236 10.6947 41.8504C12.1237 42.1961 13.9988 42.4051 16.7854 42.7091C25.7979 43.6924 32.5443 43.6875 41.5903 42.7143C44.4107 42.4109 46.3164 42.2017 47.7668 41.8584C49.1447 41.5322 49.8856 41.1324 50.452 40.5777C51.0065 40.0347 51.4303 39.2856 51.7977 37.8408C52.1812 36.3334 52.4421 34.3388 52.8196 31.4086C53.4932 26.1807 53.9782 21.5894 53.9971 16.5598C54.0172 11.1822 53.9072 9.65207 52.6789 8.23286C52.0856 7.5474 51.3643 7.09668 50.0889 6.72132C48.7116 6.31599 46.9002 6.05475 44.1624 5.66847ZM14.2512 1.15321C25.2368 -0.415832 34.0757 -0.354388 44.7987 1.15853L44.9556 1.18066C47.4945 1.53877 49.6395 1.84131 51.3748 2.35201C53.2476 2.90317 54.8137 3.73986 56.1227 5.25214C58.5918 8.10489 58.5757 11.4896 58.5543 15.9864C58.5533 16.1811 58.5524 16.3779 58.5517 16.5768C58.5317 21.8948 58.0172 26.7103 57.3369 31.9906L57.3189 32.1303C56.9639 34.8859 56.6714 37.1566 56.2118 38.9636C55.7285 40.8637 55.0061 42.4928 53.6387 43.8319C52.2832 45.1592 50.6815 45.849 48.8158 46.2906C47.054 46.7076 44.8618 46.9434 42.2194 47.2275L42.0775 47.2428C32.7099 48.2506 25.6345 48.2562 16.2914 47.2369L16.1473 47.2211C13.5418 46.9369 11.3724 46.7003 9.62384 46.2773C7.76507 45.8277 6.17397 45.124 4.82206 43.78C3.4656 42.4314 2.75316 40.8289 2.29197 38.9566C1.85699 37.1907 1.60412 34.9919 1.29963 32.3441L1.28314 32.2008C0.657117 26.7585 0.313761 21.8082 0.485489 16.2542C0.49172 16.0527 0.49768 15.8534 0.503576 15.6563C0.637065 11.1931 0.737198 7.84512 3.18398 5.11059C4.48215 3.65975 6.01657 2.85525 7.84323 2.32139C9.53736 1.82627 11.6262 1.52801 14.0987 1.17499C14.1493 1.16775 14.2002 1.16049 14.2512 1.15321Z"
class="fill-n-slate-10"
/>
<g ref="eye1Group">
<path
d="M13.7949 16.0392C13.7949 14.5096 15.035 13.2695 16.5646 13.2695C18.0943 13.2695 19.3343 14.5096 19.3343 16.0392V22.4403C19.3343 23.97 18.0943 25.21 16.5646 25.21C15.035 25.21 13.7949 23.97 13.7949 22.4403V16.0392Z"
class="fill-n-slate-10"
/>
</g>
<g ref="eye2Group">
<path
d="M22.6572 16.0392C22.6572 14.5096 23.8973 13.2695 25.4269 13.2695C26.9566 13.2695 28.1966 14.5096 28.1966 16.0392V22.4403C28.1966 23.97 26.9566 25.21 25.4269 25.21C23.8973 25.21 22.6572 23.97 22.6572 22.4403V16.0392Z"
class="fill-n-slate-10"
/>
</g>
</svg>
</div>
</div>
<div class="h-32 flex items-center justify-center">
<h5 class="text-n-slate-11 text-sm">
{{ t('CAPTAIN.ASSISTANTS.ONBOARDING.CREATING') }}
</h5>
</div>
</div>
</template>
</SettingsPageLayout>
</template>
<style scoped>
@keyframes rippleScale {
0% {
transform: translate(-50%, -50%) scale(0);
opacity: 1;
}
50% {
transform: translate(-50%, -50%) scale(0.9);
opacity: 0.9;
}
100% {
transform: translate(-50%, -50%) scale(1.2);
opacity: 0;
}
}
.animate-ripple {
animation: rippleScale 2200ms cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
}
</style>
@@ -0,0 +1,172 @@
<script setup>
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import ScenariosCard from 'dashboard/components-next/captain/assistant/ScenariosCard.vue';
import ScenariosSVG from 'dashboard/components-next/captain/AnimatingImg/Scenarios.vue';
import BulkSelectBar from 'dashboard/components-next/captain/assistant/BulkSelectBar.vue';
import OnboardingHeader from 'dashboard/components-next/captain/assistant/OnboardingHeader.vue';
import SettingsPageLayout from 'dashboard/components-next/captain/SettingsPageLayout.vue';
const router = useRouter();
const { t } = useI18n();
const breadcrumbItems = computed(() => [
{
label: t('CAPTAIN.ASSISTANTS.SETTINGS.BREADCRUMB.ASSISTANT'),
routeName: 'captain_assistants_index',
},
{
label: t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.TITLE'),
routeName: 'captain_assistants_create',
},
]);
const scenariosExample = ref([
{
id: 1,
title: 'Prospective Buyer',
description:
'Handle customers who are showing interest in purchasing a license',
instruction:
'If someone is interested in purchasing a license, ask them for following:\n\n1. How many licenses are they willing to purchase?\n2. Are they migrating from another platform?\n. Once these details are collected, do the following steps\n1. add a private note to with the information you collected using [Add Private Note](tool://add_private_note)\n2. Add label "sales" to the contact using [Add Label to Conversation](tool://add_label_to_conversation)\n3. Reply saying "one of us will reach out soon" and provide an estimated timeline for the response and [Handoff to Human](tool://handoff)',
tools: ['add_private_note', 'add_label_to_conversation', 'handoff'],
},
]);
// Bulk selection & hover state
const bulkSelectedIds = ref(new Set());
const hoveredCard = ref(null);
const handleRuleSelect = id => {
const selected = new Set(bulkSelectedIds.value);
selected[selected.has(id) ? 'delete' : 'add'](id);
bulkSelectedIds.value = selected;
};
const handleRuleHover = (isHovered, id) => {
hoveredCard.value = isHovered ? id : null;
};
const buildSelectedCountLabel = computed(() => {
const count = scenariosExample.value.length || 0;
const isAllSelected = bulkSelectedIds.value.size === count && count > 0;
return isAllSelected
? t('CAPTAIN.ASSISTANTS.ONBOARDING.SCENARIOS.BULK_ACTION.UNSELECT_ALL', {
count,
})
: t('CAPTAIN.ASSISTANTS.ONBOARDING.SCENARIOS.BULK_ACTION.SELECT_ALL', {
count,
});
});
const selectedCountLabel = computed(() => {
return t('CAPTAIN.ASSISTANTS.ONBOARDING.SCENARIOS.BULK_ACTION.SELECTED', {
count: bulkSelectedIds.value.size,
});
});
const bulkDeleteScenarios = async () => {
try {
if (bulkSelectedIds.value.size === 0) return;
const updated = scenariosExample.value.filter(
item => !bulkSelectedIds.value.has(item.id)
);
scenariosExample.value = updated;
bulkSelectedIds.value.clear();
} catch {
// error
}
};
const updateScenario = async scenario => {
try {
scenariosExample.value = scenariosExample.value.map(item => {
if (item.id === scenario.id) {
return scenario;
}
return item;
});
} catch (error) {
// error
}
};
const deleteScenario = async id => {
try {
scenariosExample.value = scenariosExample.value.filter(
item => item.id !== id
);
} catch (error) {
// error
}
};
const handleSaveAndNext = () => {
router.push({
name: 'captain_assistants_create_guidelines',
});
};
</script>
<template>
<SettingsPageLayout
:breadcrumb-items="breadcrumbItems"
:header-button-label="
t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.BUTTON_LABEL')
"
@click="handleSaveAndNext"
>
<template #body>
<OnboardingHeader
:title="t('CAPTAIN.ASSISTANTS.ONBOARDING.SCENARIOS.TITLE')"
icon="i-lucide-shapes"
:subtitle="t('CAPTAIN.ASSISTANTS.ONBOARDING.SCENARIOS.SUBTITLE')"
:description="t('CAPTAIN.ASSISTANTS.ONBOARDING.SCENARIOS.DESCRIPTION')"
>
<div class="w-[12.5rem] h-[9.75rem] flex-shrink-0">
<ScenariosSVG class="w-full h-full" />
</div>
</OnboardingHeader>
<div class="pt-8 flex flex-col items-start gap-3 w-full">
<span class="text-base text-n-slate-11 font-medium mb-3">
{{ t('CAPTAIN.ASSISTANTS.ONBOARDING.SCENARIOS.TITLE') }}
</span>
<div class="w-full gap-2 flex flex-col">
<BulkSelectBar
v-model="bulkSelectedIds"
:all-items="scenariosExample"
:select-all-label="buildSelectedCountLabel"
:selected-count-label="selectedCountLabel"
:delete-label="
$t(
'CAPTAIN.ASSISTANTS.ONBOARDING.SCENARIOS.BULK_ACTION.BULK_DELETE_BUTTON'
)
"
class="w-fit"
@bulk-delete="bulkDeleteScenarios"
/>
<ScenariosCard
v-for="scenario in scenariosExample"
:id="scenario.id"
:key="scenario.id"
:title="scenario.title"
:description="scenario.description"
:instruction="scenario.instruction"
:tools="scenario.tools"
:is-selected="bulkSelectedIds.has(scenario.id)"
:selectable="
hoveredCard === scenario.id || bulkSelectedIds.size > 0
"
@select="handleRuleSelect"
@delete="deleteScenario(scenario.id)"
@update="updateScenario"
@hover="isHovered => handleRuleHover(isHovered, scenario.id)"
/>
</div>
</div>
</template>
</SettingsPageLayout>
</template>
@@ -0,0 +1,159 @@
<script setup>
import { reactive, computed } from 'vue';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import Switch from 'dashboard/components-next/switch/Switch.vue';
import SettingsSVG from 'dashboard/components-next/captain/AnimatingImg/Settings.vue';
import OnboardingHeader from 'dashboard/components-next/captain/assistant/OnboardingHeader.vue';
import SettingsPageLayout from 'dashboard/components-next/captain/SettingsPageLayout.vue';
const router = useRouter();
const { t } = useI18n();
const state = reactive({
temperature: 0.7,
features: {
conversationFaqs: false,
memories: false,
},
});
const breadcrumbItems = computed(() => [
{
label: t('CAPTAIN.ASSISTANTS.SETTINGS.BREADCRUMB.ASSISTANT'),
routeName: 'captain_assistants_index',
},
{
label: t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.TITLE'),
routeName: 'captain_assistants_create',
},
]);
const handleSaveAndNext = () => {
router.push({
name: 'captain_assistants_create_guardrails',
});
};
</script>
<template>
<SettingsPageLayout
:breadcrumb-items="breadcrumbItems"
:header-button-label="
t('CAPTAIN.ASSISTANTS.ONBOARDING.HEADER.BUTTON_LABEL')
"
@click="handleSaveAndNext"
>
<template #body>
<OnboardingHeader
:title="t('CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.TITLE')"
icon="i-lucide-settings-2"
:subtitle="
t('CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.SUBTITLE', {
assistantName: 'Support Genie',
})
"
:description="
t('CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.DESCRIPTION', {
assistantName: 'Support Genie',
productName: 'The Cat Cafe',
})
"
>
<div class="w-[12.5rem] h-[9.75rem] flex-shrink-0">
<SettingsSVG class="w-full h-full" />
</div>
</OnboardingHeader>
<div class="pt-8 flex flex-col items-start gap-8 w-full">
<div class="flex flex-col gap-6 mt-4 w-full">
<div class="flex flex-col items-start gap-2">
<label class="text-base font-medium text-n-slate-12 py-1">
{{
t(
'CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.RESPONSE_TEMPERATURE.TITLE'
)
}}
</label>
<p class="text-sm text-n-slate-11 mb-0">
{{
t(
'CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.RESPONSE_TEMPERATURE.DESCRIPTION'
)
}}
</p>
</div>
<div class="flex flex-col items-start gap-3 w-full">
<div
class="flex items-center bg-n-solid-1 py-3 ltr:pl-4 w-full rtl:pr-4 ltr:pr-3 rtl:pl-3 rounded-xl gap-3 outline outline-1 outline-n-weak"
>
<input
v-model="state.temperature"
type="range"
min="0"
max="1"
step="0.1"
class="w-full"
/>
<div
class="bg-n-alpha-3 rounded-lg w-9 h-8 flex items-center justify-center outline outline-1 outline-n-weak"
>
<span class="text-xs font-semibold text-n-slate-12">
{{ state.temperature }}
</span>
</div>
</div>
<p class="text-sm text-n-slate-11 mb-0">
{{
t(
'CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.RESPONSE_TEMPERATURE.HELP'
)
}}
</p>
</div>
</div>
<div class="flex flex-col gap-6 mt-4 w-full">
<div class="flex flex-col items-start gap-2">
<label class="text-base font-medium text-n-slate-12 py-1">
{{
t('CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.FEATURES.TITLE')
}}
</label>
<p class="text-sm text-n-slate-11 mb-0">
{{
t(
'CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.FEATURES.DESCRIPTION'
)
}}
</p>
</div>
<div
class="flex flex-col items-start bg-n-solid-1 w-full rounded-xl outline outline-1 outline-n-weak divide-y divide-n-weak"
>
<div class="p-4 flex items-center gap-2 w-full justify-between">
<label>
{{
t(
'CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.FEATURES.OPTIONS.ALLOW_CONVERSATION_FAQS'
)
}}
</label>
<Switch v-model="state.features.conversationFaqs" />
</div>
<div class="p-4 flex items-center gap-2 w-full justify-between">
<label>
{{
t(
'CAPTAIN.ASSISTANTS.ONBOARDING.BASE_SETTINGS.FEATURES.OPTIONS.ALLOW_MEMORIES'
)
}}
</label>
<Switch v-model="state.features.memories" />
</div>
</div>
</div>
</div>
</template>
</SettingsPageLayout>
</template>
@@ -13,6 +13,13 @@ import ResponsesIndex from './responses/Index.vue';
import ResponsesPendingIndex from './responses/Pending.vue';
import CustomToolsIndex from './tools/Index.vue';
// Onboarding flow components
import AssistantCreateLoader from './assistants/create/Loader.vue';
import AssistantCreateSettings from './assistants/create/Settings.vue';
import AssistantCreateGuardrails from './assistants/create/Guardrails.vue';
import AssistantCreateScenarios from './assistants/create/Scenarios.vue';
import AssistantCreateGuidelines from './assistants/create/Guidelines.vue';
export const routes = [
{
path: frontendURL('accounts/:accountId/captain/assistants'),
@@ -27,6 +34,79 @@ export const routes = [
],
},
},
// Assistant create onboarding flow routes
{
path: frontendURL('accounts/:accountId/captain/assistants/create'),
component: AssistantCreateLoader,
name: 'captain_assistants_create_loader',
meta: {
permissions: ['administrator', 'agent'],
featureFlag: FEATURE_FLAGS.CAPTAIN,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
},
},
{
path: frontendURL('accounts/:accountId/captain/assistants/create/settings'),
component: AssistantCreateSettings,
name: 'captain_assistants_create_settings',
meta: {
permissions: ['administrator', 'agent'],
featureFlag: FEATURE_FLAGS.CAPTAIN,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
},
},
{
path: frontendURL(
'accounts/:accountId/captain/assistants/create/guardrails'
),
component: AssistantCreateGuardrails,
name: 'captain_assistants_create_guardrails',
meta: {
permissions: ['administrator', 'agent'],
featureFlag: FEATURE_FLAGS.CAPTAIN,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
},
},
{
path: frontendURL(
'accounts/:accountId/captain/assistants/create/scenarios'
),
component: AssistantCreateScenarios,
name: 'captain_assistants_create_scenarios',
meta: {
permissions: ['administrator', 'agent'],
featureFlag: FEATURE_FLAGS.CAPTAIN,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
},
},
{
path: frontendURL(
'accounts/:accountId/captain/assistants/create/guidelines'
),
component: AssistantCreateGuidelines,
name: 'captain_assistants_create_guidelines',
meta: {
permissions: ['administrator', 'agent'],
featureFlag: FEATURE_FLAGS.CAPTAIN,
installationTypes: [
INSTALLATION_TYPES.CLOUD,
INSTALLATION_TYPES.ENTERPRISE,
],
},
},
// End
{
path: frontendURL('accounts/:accountId/captain/assistants/:assistantId'),
component: AssistantEdit,