Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83a2ddd670 | ||
|
|
810c9eb151 | ||
|
|
465c7416bb | ||
|
|
2b34417ade | ||
|
|
476b0bee2f | ||
|
|
ae0231c378 | ||
|
|
4030e4a140 | ||
|
|
e9078d2544 | ||
|
|
bfaa450e2a | ||
|
|
0b207f43bb | ||
|
|
07d268c1ff | ||
|
|
4db51103a0 | ||
|
|
7a972b2a8a | ||
|
|
454b7081c2 | ||
|
|
1e3c6c8d30 | ||
|
|
2dcef85c41 | ||
|
|
6aac6662ff | ||
|
|
116d512ca1 | ||
|
|
2ed74ea783 | ||
|
|
9aabd08273 | ||
|
|
ab6aa0f4b6 | ||
|
|
ea1bc3fde1 | ||
|
|
48d70acc51 | ||
|
|
5ca706e332 | ||
|
|
d05ab8b5c6 | ||
|
|
1f3e6070b8 | ||
|
|
40dcaf12b1 | ||
|
|
3ddfbe9e65 | ||
|
|
744cf90f7c | ||
|
|
ad34eed78a | ||
|
|
56e3a479a0 | ||
|
|
0df627d5f5 | ||
|
|
6858a6f867 | ||
|
|
e1785a3a35 | ||
|
|
eeda6a9acf | ||
|
|
88ef2aca45 | ||
|
|
2cbf11c913 | ||
|
|
9a94d49cff | ||
|
|
0549d4b9ec |
@@ -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>
|
||||
+58
@@ -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": "Let’s 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. Let’s 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. It’s 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,
|
||||
|
||||
Reference in New Issue
Block a user