Compare commits
109
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9348883ed | ||
|
|
aeb708a053 | ||
|
|
ce8f40debb | ||
|
|
5ca8aec7c3 | ||
|
|
4b8b33c4fd | ||
|
|
e3df65ef09 | ||
|
|
063e9654bc | ||
|
|
b7035de400 | ||
|
|
156220e0e2 | ||
|
|
cec1a64d6f | ||
|
|
8b0caa6909 | ||
|
|
051eeae8ec | ||
|
|
d51cea9d6b | ||
|
|
56160bfc63 | ||
|
|
abca836f0c | ||
|
|
64640092aa | ||
|
|
d478f6e42e | ||
|
|
2dcd071dec | ||
|
|
ea34071553 | ||
|
|
ce4d52926c | ||
|
|
fb86b1354a | ||
|
|
ab28802d5a | ||
|
|
797714f5aa | ||
|
|
b2cc5d113e | ||
|
|
4eca7382f8 | ||
|
|
0fe60d137e | ||
|
|
f6ec3f3a72 | ||
|
|
d51148951f | ||
|
|
79bb3d0ef3 | ||
|
|
7418882a4f | ||
|
|
d38bd5139e | ||
|
|
fcc6973245 | ||
|
|
2590c57893 | ||
|
|
f225e2fcd8 | ||
|
|
b626e0cbf1 | ||
|
|
eb4f93b821 | ||
|
|
4a0c61ddb7 | ||
|
|
dc4dfe60e4 | ||
|
|
d3b4c7225a | ||
|
|
35f42e71f6 | ||
|
|
eb04882e5a | ||
|
|
3a4aa5f837 | ||
|
|
bfb2d2b2bb | ||
|
|
aa0f36245f | ||
|
|
8f83d03354 | ||
|
|
643f7415c1 | ||
|
|
ebee38d4a5 | ||
|
|
16ac2dba17 | ||
|
|
e511527f32 | ||
|
|
bd19d596b3 | ||
|
|
6a0878f893 | ||
|
|
cdececcc67 | ||
|
|
ffdcee7d86 | ||
|
|
05c4ee099b | ||
|
|
643316e19a | ||
|
|
a0cc8e2f3f | ||
|
|
fb0f5cd696 | ||
|
|
6211406681 | ||
|
|
76ff8bdd35 | ||
|
|
10cd5da09d | ||
|
|
ce5a859a4f | ||
|
|
32e0915a20 | ||
|
|
94985bbc77 | ||
|
|
65559b47fd | ||
|
|
40608f4494 | ||
|
|
f31d9a3e76 | ||
|
|
eb94eac9a2 | ||
|
|
52f673e56c | ||
|
|
4aa567ee3d | ||
|
|
79082bea58 | ||
|
|
b9f68fc839 | ||
|
|
e10ef03490 | ||
|
|
6f9f2c32c9 | ||
|
|
24606ab5ef | ||
|
|
dd54cf5af3 | ||
|
|
b91f8012cd | ||
|
|
f999112671 | ||
|
|
be37c5b80c | ||
|
|
abeb938bfd | ||
|
|
81c1ab73e6 | ||
|
|
1689d5cf5c | ||
|
|
3970149b06 | ||
|
|
9c33ce50b3 | ||
|
|
36a2412f42 | ||
|
|
3ba4340042 | ||
|
|
411048715d | ||
|
|
9a7a2486e4 | ||
|
|
39af360a18 | ||
|
|
ac1743514b | ||
|
|
c1df37b41d | ||
|
|
7c3b07c507 | ||
|
|
62203da528 | ||
|
|
029294e16b | ||
|
|
2c095bed86 | ||
|
|
7c6508dd04 | ||
|
|
cef23891af | ||
|
|
19a851c01d | ||
|
|
a22e11c810 | ||
|
|
efe6e64dcd | ||
|
|
d6f5c627eb | ||
|
|
51b4a8f9fc | ||
|
|
1268b2cf55 | ||
|
|
125ae5523a | ||
|
|
59f60936a7 | ||
|
|
216a2c0e73 | ||
|
|
708296a746 | ||
|
|
b9168664f7 | ||
|
|
01cd7b878d | ||
|
|
acb2816826 |
@@ -8,10 +8,7 @@ module BillingHelper
|
||||
# Return false if not plans are configured, so that no checks are enforced
|
||||
return false if default_plan.blank?
|
||||
|
||||
# Handle both string and hash formats for default_plan
|
||||
default_plan_name = default_plan.is_a?(Hash) ? default_plan['name'] : default_plan
|
||||
|
||||
account.custom_attributes['plan_name'].nil? || account.custom_attributes['plan_name'] == default_plan_name
|
||||
account.custom_attributes['plan_name'].nil? || account.custom_attributes['plan_name'] == default_plan['name']
|
||||
end
|
||||
|
||||
def conversations_this_month(account)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import AddAccountModal from './components/app/AddAccountModal.vue';
|
||||
import LoadingState from './components/widgets/LoadingState.vue';
|
||||
import NetworkNotification from './components/NetworkNotification.vue';
|
||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
||||
@@ -24,6 +25,7 @@ export default {
|
||||
name: 'App',
|
||||
|
||||
components: {
|
||||
AddAccountModal,
|
||||
LoadingState,
|
||||
NetworkNotification,
|
||||
UpdateBanner,
|
||||
@@ -49,6 +51,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAddAccountModal: false,
|
||||
latestChatwootVersion: null,
|
||||
reconnectService: null,
|
||||
};
|
||||
@@ -61,12 +64,21 @@ export default {
|
||||
authUIFlags: 'getAuthUIFlags',
|
||||
accountUIFlags: 'accounts/getUIFlags',
|
||||
}),
|
||||
hasAccounts() {
|
||||
const { accounts = [] } = this.currentUser || {};
|
||||
return accounts.length > 0;
|
||||
},
|
||||
hideOnOnboardingView() {
|
||||
return !isOnOnboardingView(this.$route);
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentUser() {
|
||||
if (!this.hasAccounts) {
|
||||
this.showAddAccountModal = true;
|
||||
}
|
||||
},
|
||||
currentAccountId: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
@@ -144,6 +156,7 @@ export default {
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
<AddAccountModal :show="showAddAccountModal" :has-accounts="hasAccounts" />
|
||||
<WootSnackbarBox />
|
||||
<NetworkNotification />
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,35 @@ class EnterpriseAccountAPI extends ApiClient {
|
||||
action_type: action,
|
||||
});
|
||||
}
|
||||
|
||||
// V2 Billing APIs
|
||||
creditGrants() {
|
||||
return axios.get(`${this.url}credit_grants`);
|
||||
}
|
||||
|
||||
v2PricingPlans() {
|
||||
return axios.get(`${this.url}v2_pricing_plans`);
|
||||
}
|
||||
|
||||
v2TopupOptions() {
|
||||
return axios.get(`${this.url}v2_topup_options`);
|
||||
}
|
||||
|
||||
v2Topup(data) {
|
||||
return axios.post(`${this.url}v2_topup`, data);
|
||||
}
|
||||
|
||||
v2Subscribe(data) {
|
||||
return axios.post(`${this.url}v2_subscribe`, data);
|
||||
}
|
||||
|
||||
cancelSubscription(data = {}) {
|
||||
return axios.post(`${this.url}cancel_subscription`, data);
|
||||
}
|
||||
|
||||
changePricingPlan(data) {
|
||||
return axios.post(`${this.url}change_pricing_plan`, data);
|
||||
}
|
||||
}
|
||||
|
||||
export default new EnterpriseAccountAPI();
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { OnClickOutside } from '@vueuse/components';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useMapGetter } from 'dashboard/composables/store.js';
|
||||
import { computed } from 'vue';
|
||||
import { usePolicy } from 'dashboard/composables/usePolicy';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import BackButton from 'dashboard/components/widgets/BackButton.vue';
|
||||
import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue';
|
||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||
import Policy from 'dashboard/components/policy.vue';
|
||||
import AssistantSwitcher from 'dashboard/components-next/captain/pageComponents/switcher/AssistantSwitcher.vue';
|
||||
import CreateAssistantDialog from 'dashboard/components-next/captain/pageComponents/assistant/CreateAssistantDialog.vue';
|
||||
|
||||
const props = defineProps({
|
||||
currentPage: {
|
||||
@@ -62,32 +56,11 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showAssistantSwitcher: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['click', 'close', 'update:currentPage']);
|
||||
|
||||
const route = useRoute();
|
||||
const { shouldShowPaywall } = usePolicy();
|
||||
|
||||
const showAssistantSwitcherDropdown = ref(false);
|
||||
const createAssistantDialogRef = ref(null);
|
||||
|
||||
const assistants = useMapGetter('captainAssistants/getRecords');
|
||||
|
||||
const currentAssistantId = computed(() => route.params.assistantId);
|
||||
|
||||
const activeAssistantName = computed(() => {
|
||||
return (
|
||||
assistants.value?.find(
|
||||
assistant => assistant.id === Number(currentAssistantId.value)
|
||||
)?.name || ''
|
||||
);
|
||||
});
|
||||
|
||||
const showPaywall = computed(() => {
|
||||
return shouldShowPaywall(props.featureFlag);
|
||||
});
|
||||
@@ -99,15 +72,6 @@ const handleButtonClick = () => {
|
||||
const handlePageChange = event => {
|
||||
emit('update:currentPage', event);
|
||||
};
|
||||
|
||||
const toggleAssistantSwitcher = () => {
|
||||
showAssistantSwitcherDropdown.value = !showAssistantSwitcherDropdown.value;
|
||||
};
|
||||
|
||||
const handleCreateAssistant = () => {
|
||||
showAssistantSwitcherDropdown.value = false;
|
||||
createAssistantDialogRef.value.dialogRef.open();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -118,48 +82,9 @@ const handleCreateAssistant = () => {
|
||||
class="flex items-start lg:items-center justify-between w-full py-6 lg:py-0 lg:h-20 gap-4 lg:gap-2 flex-col lg:flex-row"
|
||||
>
|
||||
<div class="flex gap-4 items-center">
|
||||
<BackButton v-if="backUrl" :back-url="backUrl" />
|
||||
<BackButton v-if="backUrl" :to="backUrl" />
|
||||
<slot name="headerTitle">
|
||||
<div v-if="showAssistantSwitcher" class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-1">
|
||||
<span
|
||||
v-if="activeAssistantName"
|
||||
class="text-xl font-medium truncate text-n-slate-12"
|
||||
>
|
||||
{{ activeAssistantName }}
|
||||
</span>
|
||||
<div v-if="activeAssistantName" class="relative group">
|
||||
<OnClickOutside
|
||||
@trigger="showAssistantSwitcherDropdown = false"
|
||||
>
|
||||
<Button
|
||||
icon="i-lucide-chevron-down"
|
||||
variant="ghost"
|
||||
color="slate"
|
||||
size="xs"
|
||||
class="rounded-md group-hover:bg-n-slate-3 hover:bg-n-slate-3 [&>span]:size-4"
|
||||
@click="toggleAssistantSwitcher"
|
||||
/>
|
||||
|
||||
<AssistantSwitcher
|
||||
v-if="showAssistantSwitcherDropdown"
|
||||
class="absolute ltr:left-0 rtl:right-0 top-9"
|
||||
@close="showAssistantSwitcherDropdown = false"
|
||||
@create-assistant="handleCreateAssistant"
|
||||
/>
|
||||
</OnClickOutside>
|
||||
</div>
|
||||
<Icon
|
||||
v-if="activeAssistantName"
|
||||
icon="i-lucide-chevron-right"
|
||||
class="size-6 text-n-slate-11"
|
||||
/>
|
||||
<span class="text-xl font-medium text-n-slate-11">
|
||||
{{ headerTitle }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="text-xl font-medium text-n-slate-12">
|
||||
<span class="text-xl font-medium text-n-slate-12">
|
||||
{{ headerTitle }}
|
||||
</span>
|
||||
</slot>
|
||||
@@ -172,24 +97,21 @@ const handleCreateAssistant = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<slot name="search" />
|
||||
<div
|
||||
v-if="!showPaywall && buttonLabel"
|
||||
v-on-clickaway="() => emit('close')"
|
||||
class="relative group/captain-button"
|
||||
>
|
||||
<Policy :permissions="buttonPolicy">
|
||||
<Button
|
||||
:label="buttonLabel"
|
||||
icon="i-lucide-plus"
|
||||
size="sm"
|
||||
class="group-hover/captain-button:brightness-110"
|
||||
@click="handleButtonClick"
|
||||
/>
|
||||
</Policy>
|
||||
<slot name="action" />
|
||||
</div>
|
||||
<div
|
||||
v-if="!showPaywall && buttonLabel"
|
||||
v-on-clickaway="() => emit('close')"
|
||||
class="relative group/campaign-button"
|
||||
>
|
||||
<Policy :permissions="buttonPolicy">
|
||||
<Button
|
||||
:label="buttonLabel"
|
||||
icon="i-lucide-plus"
|
||||
size="sm"
|
||||
class="group-hover/campaign-button:brightness-110"
|
||||
@click="handleButtonClick"
|
||||
/>
|
||||
</Policy>
|
||||
<slot name="action" />
|
||||
</div>
|
||||
</div>
|
||||
<slot name="subHeader" />
|
||||
@@ -222,6 +144,5 @@ const handleCreateAssistant = () => {
|
||||
@update:current-page="handlePageChange"
|
||||
/>
|
||||
</footer>
|
||||
<CreateAssistantDialog ref="createAssistantDialogRef" type="create" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<script setup>
|
||||
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';
|
||||
|
||||
defineProps({
|
||||
isFetching: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isEmpty: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
totalCount: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
itemsPerPage: {
|
||||
type: Number,
|
||||
default: 25,
|
||||
},
|
||||
showPaginationFooter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
breadcrumbItems: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:currentPage']);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const handlePageChange = event => {
|
||||
emit('update:currentPage', event);
|
||||
};
|
||||
|
||||
const handleBreadcrumbClick = item => {
|
||||
router.push({
|
||||
name: item.routeName,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
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">
|
||||
<Breadcrumb :items="breadcrumbItems" @click="handleBreadcrumbClick" />
|
||||
</header>
|
||||
<main class="flex gap-16 w-full flex-1 pb-16">
|
||||
<section
|
||||
v-if="$slots.body || $slots.emptyState || isFetching"
|
||||
class="flex flex-col w-full"
|
||||
>
|
||||
<div
|
||||
v-if="isFetching"
|
||||
class="flex items-center justify-center py-10 text-n-slate-11"
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
<div v-else-if="isEmpty">
|
||||
<slot name="emptyState" />
|
||||
</div>
|
||||
<slot v-else name="body" />
|
||||
</section>
|
||||
<section v-if="$slots.controls" class="flex w-full">
|
||||
<slot name="controls" />
|
||||
</section>
|
||||
</main>
|
||||
<footer v-if="showPaginationFooter" class="sticky bottom-0 z-10 pb-4">
|
||||
<PaginationFooter
|
||||
:current-page="currentPage"
|
||||
:total-items="totalCount"
|
||||
:items-per-page="itemsPerPage"
|
||||
@update:current-page="handlePageChange"
|
||||
/>
|
||||
</footer>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -76,11 +76,12 @@ const handleAction = ({ action, value }) => {
|
||||
<template>
|
||||
<CardLayout>
|
||||
<div class="flex justify-between w-full gap-1">
|
||||
<h6
|
||||
class="text-base font-normal text-n-slate-12 line-clamp-1 hover:underline transition-colors"
|
||||
<router-link
|
||||
:to="{ name: 'captain_assistants_edit', params: { assistantId: id } }"
|
||||
class="text-base text-n-slate-12 line-clamp-1 hover:underline transition-colors"
|
||||
>
|
||||
{{ name }}
|
||||
</h6>
|
||||
</router-link>
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
v-on-clickaway="() => toggleDropdown(false)"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import NextButton from 'dashboard/components-next/button/Button.vue';
|
||||
import MessageList from './MessageList.vue';
|
||||
@@ -29,16 +29,6 @@ const resetConversation = () => {
|
||||
newMessage.value = '';
|
||||
};
|
||||
|
||||
// Watch for assistant ID changes and reset conversation
|
||||
watch(
|
||||
() => assistantId,
|
||||
(newId, oldId) => {
|
||||
if (oldId && newId !== oldId) {
|
||||
resetConversation();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const sendMessage = async () => {
|
||||
if (!newMessage.value.trim() || isLoading.value) return;
|
||||
|
||||
@@ -84,7 +74,7 @@ const sendMessage = async () => {
|
||||
</h3>
|
||||
<NextButton
|
||||
ghost
|
||||
sm
|
||||
size="small"
|
||||
icon="i-lucide-rotate-ccw"
|
||||
@click="resetConversation"
|
||||
/>
|
||||
@@ -107,7 +97,7 @@ const sendMessage = async () => {
|
||||
/>
|
||||
<NextButton
|
||||
ghost
|
||||
sm
|
||||
size="small"
|
||||
:disabled="!newMessage.trim()"
|
||||
icon="i-lucide-send"
|
||||
@click="sendMessage"
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { OnClickOutside } from '@vueuse/components';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
|
||||
const props = defineProps({
|
||||
assistantId: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update']);
|
||||
const { t } = useI18n();
|
||||
const isFilterOpen = ref(false);
|
||||
|
||||
const assistants = useMapGetter('captainAssistants/getRecords');
|
||||
const assistantOptions = computed(() => [
|
||||
{
|
||||
label: t(`CAPTAIN.RESPONSES.FILTER.ALL_ASSISTANTS`),
|
||||
value: 'all',
|
||||
action: 'filter',
|
||||
},
|
||||
...assistants.value.map(assistant => ({
|
||||
value: assistant.id,
|
||||
label: assistant.name,
|
||||
action: 'filter',
|
||||
})),
|
||||
]);
|
||||
|
||||
const selectedAssistantLabel = computed(() => {
|
||||
const assistant = assistantOptions.value.find(
|
||||
option => option.value === props.assistantId
|
||||
);
|
||||
return t('CAPTAIN.RESPONSES.FILTER.ASSISTANT', {
|
||||
selected: assistant ? assistant.label : '',
|
||||
});
|
||||
});
|
||||
|
||||
const handleAssistantFilterChange = ({ value }) => {
|
||||
isFilterOpen.value = false;
|
||||
emit('update', value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<OnClickOutside @trigger="isFilterOpen = false">
|
||||
<Button
|
||||
:label="selectedAssistantLabel"
|
||||
icon="i-lucide-chevron-down"
|
||||
size="sm"
|
||||
color="slate"
|
||||
trailing-icon
|
||||
class="max-w-48"
|
||||
@click="isFilterOpen = !isFilterOpen"
|
||||
/>
|
||||
|
||||
<DropdownMenu
|
||||
v-if="isFilterOpen"
|
||||
:menu-items="assistantOptions"
|
||||
class="mt-2"
|
||||
@action="handleAssistantFilterChange"
|
||||
/>
|
||||
</OnClickOutside>
|
||||
</template>
|
||||
+3
-13
@@ -18,7 +18,7 @@ const props = defineProps({
|
||||
validator: value => ['create', 'edit'].includes(value),
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['close', 'created']);
|
||||
const emit = defineEmits(['close']);
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
|
||||
@@ -35,18 +35,8 @@ const i18nKey = computed(
|
||||
() => `CAPTAIN.ASSISTANTS.${props.type.toUpperCase()}`
|
||||
);
|
||||
|
||||
const createAssistant = async assistantDetails => {
|
||||
try {
|
||||
const newAssistant = await store.dispatch(
|
||||
'captainAssistants/create',
|
||||
assistantDetails
|
||||
);
|
||||
emit('created', newAssistant);
|
||||
} catch (error) {
|
||||
const errorMessage = error?.message || t(`${i18nKey.value}.ERROR_MESSAGE`);
|
||||
useAlert(errorMessage);
|
||||
}
|
||||
};
|
||||
const createAssistant = assistantDetails =>
|
||||
store.dispatch('captainAssistants/create', assistantDetails);
|
||||
|
||||
const handleSubmit = async updatedAssistant => {
|
||||
try {
|
||||
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
<script setup>
|
||||
import { reactive, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, minLength } from '@vuelidate/validators';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
||||
import Accordion from 'dashboard/components-next/Accordion/Accordion.vue';
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => ['edit', 'create'].includes(value),
|
||||
},
|
||||
assistant: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const formState = {
|
||||
uiFlags: useMapGetter('captainAssistants/getUIFlags'),
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
name: '',
|
||||
description: '',
|
||||
productName: '',
|
||||
welcomeMessage: '',
|
||||
handoffMessage: '',
|
||||
resolutionMessage: '',
|
||||
instructions: '',
|
||||
features: {
|
||||
conversationFaqs: false,
|
||||
memories: false,
|
||||
citations: false,
|
||||
},
|
||||
temperature: 1,
|
||||
};
|
||||
|
||||
const state = reactive({ ...initialState });
|
||||
|
||||
const validationRules = {
|
||||
name: { required, minLength: minLength(1) },
|
||||
description: { required, minLength: minLength(1) },
|
||||
productName: { required, minLength: minLength(1) },
|
||||
welcomeMessage: { minLength: minLength(1) },
|
||||
handoffMessage: { minLength: minLength(1) },
|
||||
resolutionMessage: { minLength: minLength(1) },
|
||||
instructions: { minLength: minLength(1) },
|
||||
};
|
||||
|
||||
const v$ = useVuelidate(validationRules, state);
|
||||
|
||||
const isLoading = computed(() => formState.uiFlags.value.creatingItem);
|
||||
|
||||
const getErrorMessage = field => {
|
||||
return v$.value[field].$error ? v$.value[field].$errors[0].$message : '';
|
||||
};
|
||||
|
||||
const formErrors = computed(() => ({
|
||||
name: getErrorMessage('name'),
|
||||
description: getErrorMessage('description'),
|
||||
productName: getErrorMessage('productName'),
|
||||
welcomeMessage: getErrorMessage('welcomeMessage'),
|
||||
handoffMessage: getErrorMessage('handoffMessage'),
|
||||
resolutionMessage: getErrorMessage('resolutionMessage'),
|
||||
instructions: getErrorMessage('instructions'),
|
||||
}));
|
||||
|
||||
const updateStateFromAssistant = assistant => {
|
||||
const { config = {} } = assistant;
|
||||
state.name = assistant.name;
|
||||
state.description = assistant.description;
|
||||
state.productName = config.product_name;
|
||||
state.welcomeMessage = config.welcome_message;
|
||||
state.handoffMessage = config.handoff_message;
|
||||
state.resolutionMessage = config.resolution_message;
|
||||
state.instructions = config.instructions;
|
||||
state.features = {
|
||||
conversationFaqs: config.feature_faq || false,
|
||||
memories: config.feature_memory || false,
|
||||
citations: config.feature_citation || false,
|
||||
};
|
||||
state.temperature = config.temperature || 1;
|
||||
};
|
||||
|
||||
const handleBasicInfoUpdate = async () => {
|
||||
const result = await Promise.all([
|
||||
v$.value.name.$validate(),
|
||||
v$.value.description.$validate(),
|
||||
v$.value.productName.$validate(),
|
||||
]).then(results => results.every(Boolean));
|
||||
if (!result) return;
|
||||
|
||||
const payload = {
|
||||
name: state.name,
|
||||
description: state.description,
|
||||
config: {
|
||||
...props.assistant.config,
|
||||
product_name: state.productName,
|
||||
},
|
||||
};
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleSystemMessagesUpdate = async () => {
|
||||
const result = await Promise.all([
|
||||
v$.value.welcomeMessage.$validate(),
|
||||
v$.value.handoffMessage.$validate(),
|
||||
v$.value.resolutionMessage.$validate(),
|
||||
]).then(results => results.every(Boolean));
|
||||
if (!result) return;
|
||||
|
||||
const payload = {
|
||||
config: {
|
||||
...props.assistant.config,
|
||||
welcome_message: state.welcomeMessage,
|
||||
handoff_message: state.handoffMessage,
|
||||
resolution_message: state.resolutionMessage,
|
||||
},
|
||||
};
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleInstructionsUpdate = async () => {
|
||||
const result = await v$.value.instructions.$validate();
|
||||
if (!result) return;
|
||||
|
||||
const payload = {
|
||||
config: {
|
||||
...props.assistant.config,
|
||||
temperature: state.temperature || 1,
|
||||
instructions: state.instructions,
|
||||
},
|
||||
};
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
const handleFeaturesUpdate = () => {
|
||||
const payload = {
|
||||
config: {
|
||||
...props.assistant.config,
|
||||
feature_faq: state.features.conversationFaqs,
|
||||
feature_memory: state.features.memories,
|
||||
feature_citation: state.features.citations,
|
||||
},
|
||||
};
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.assistant,
|
||||
newAssistant => {
|
||||
if (props.mode === 'edit' && newAssistant) {
|
||||
updateStateFromAssistant(newAssistant);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="handleSubmit">
|
||||
<!-- Basic Information Section -->
|
||||
<Accordion
|
||||
:title="t('CAPTAIN.ASSISTANTS.FORM.SECTIONS.BASIC_INFO')"
|
||||
is-open
|
||||
>
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
<Input
|
||||
v-model="state.name"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.NAME.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.NAME.PLACEHOLDER')"
|
||||
:message="formErrors.name"
|
||||
:message-type="formErrors.name ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Editor
|
||||
v-model="state.description"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.DESCRIPTION.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.DESCRIPTION.PLACEHOLDER')"
|
||||
:message="formErrors.description"
|
||||
:message-type="formErrors.description ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Input
|
||||
v-model="state.productName"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.PRODUCT_NAME.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.PRODUCT_NAME.PLACEHOLDER')"
|
||||
:message="formErrors.productName"
|
||||
:message-type="formErrors.productName ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
:loading="isLoading"
|
||||
@click="handleBasicInfoUpdate"
|
||||
>
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.UPDATE') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
<!-- Instructions Section -->
|
||||
<Accordion :title="t('CAPTAIN.ASSISTANTS.FORM.SECTIONS.INSTRUCTIONS')">
|
||||
<div class="flex flex-col gap-4">
|
||||
<Editor
|
||||
v-model="state.instructions"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.INSTRUCTIONS.PLACEHOLDER')"
|
||||
:message="formErrors.instructions"
|
||||
:max-length="20000"
|
||||
:message-type="formErrors.instructions ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2 mt-4">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.LABEL') }}
|
||||
</label>
|
||||
<div class="flex items-center gap-4">
|
||||
<input
|
||||
v-model="state.temperature"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.1"
|
||||
class="w-full"
|
||||
/>
|
||||
<span class="text-sm text-n-slate-12">{{ state.temperature }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-n-slate-11 italic">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.DESCRIPTION') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
:loading="isLoading"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.UPDATE')"
|
||||
@click="handleInstructionsUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
<!-- Greeting Messages Section -->
|
||||
<Accordion :title="t('CAPTAIN.ASSISTANTS.FORM.SECTIONS.SYSTEM_MESSAGES')">
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
<Editor
|
||||
v-model="state.handoffMessage"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.HANDOFF_MESSAGE.LABEL')"
|
||||
:placeholder="
|
||||
t('CAPTAIN.ASSISTANTS.FORM.HANDOFF_MESSAGE.PLACEHOLDER')
|
||||
"
|
||||
:message="formErrors.handoffMessage"
|
||||
:message-type="formErrors.handoffMessage ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Editor
|
||||
v-model="state.resolutionMessage"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.RESOLUTION_MESSAGE.LABEL')"
|
||||
:placeholder="
|
||||
t('CAPTAIN.ASSISTANTS.FORM.RESOLUTION_MESSAGE.PLACEHOLDER')
|
||||
"
|
||||
:message="formErrors.resolutionMessage"
|
||||
:message-type="formErrors.resolutionMessage ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
:loading="isLoading"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.UPDATE')"
|
||||
@click="handleSystemMessagesUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
|
||||
<!-- Features Section -->
|
||||
<Accordion :title="t('CAPTAIN.ASSISTANTS.FORM.SECTIONS.FEATURES')">
|
||||
<div class="flex flex-col gap-4 pt-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.TITLE') }}
|
||||
</label>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="state.features.conversationFaqs"
|
||||
type="checkbox"
|
||||
/>
|
||||
{{
|
||||
t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_CONVERSATION_FAQS')
|
||||
}}
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input v-model="state.features.memories" type="checkbox" />
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_MEMORIES') }}
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
<input v-model="state.features.citations" type="checkbox" />
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.FEATURES.ALLOW_CITATIONS') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
size="small"
|
||||
:loading="isLoading"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.UPDATE')"
|
||||
@click="handleFeaturesUpdate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion>
|
||||
</form>
|
||||
</template>
|
||||
+2
-35
@@ -3,8 +3,6 @@ import { reactive, computed, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { minLength } from '@vuelidate/validators';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
||||
@@ -19,16 +17,10 @@ const props = defineProps({
|
||||
const emit = defineEmits(['submit']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const { isCloudFeatureEnabled } = useAccount();
|
||||
|
||||
const isCaptainV2Enabled = computed(() =>
|
||||
isCloudFeatureEnabled(FEATURE_FLAGS.CAPTAIN_V2)
|
||||
);
|
||||
|
||||
const initialState = {
|
||||
handoffMessage: '',
|
||||
resolutionMessage: '',
|
||||
instructions: '',
|
||||
temperature: 1,
|
||||
};
|
||||
|
||||
@@ -37,7 +29,6 @@ const state = reactive({ ...initialState });
|
||||
const validationRules = {
|
||||
handoffMessage: { minLength: minLength(1) },
|
||||
resolutionMessage: { minLength: minLength(1) },
|
||||
instructions: { minLength: minLength(1) },
|
||||
};
|
||||
|
||||
const v$ = useVuelidate(validationRules, state);
|
||||
@@ -49,30 +40,20 @@ const getErrorMessage = field => {
|
||||
const formErrors = computed(() => ({
|
||||
handoffMessage: getErrorMessage('handoffMessage'),
|
||||
resolutionMessage: getErrorMessage('resolutionMessage'),
|
||||
instructions: getErrorMessage('instructions'),
|
||||
}));
|
||||
|
||||
const updateStateFromAssistant = assistant => {
|
||||
const { config = {} } = assistant;
|
||||
state.handoffMessage = config.handoff_message;
|
||||
state.resolutionMessage = config.resolution_message;
|
||||
state.instructions = config.instructions;
|
||||
state.temperature = config.temperature || 1;
|
||||
};
|
||||
|
||||
const handleSystemMessagesUpdate = async () => {
|
||||
const validations = [
|
||||
const result = await Promise.all([
|
||||
v$.value.handoffMessage.$validate(),
|
||||
v$.value.resolutionMessage.$validate(),
|
||||
];
|
||||
|
||||
if (!isCaptainV2Enabled.value) {
|
||||
validations.push(v$.value.instructions.$validate());
|
||||
}
|
||||
|
||||
const result = await Promise.all(validations).then(results =>
|
||||
results.every(Boolean)
|
||||
);
|
||||
]).then(results => results.every(Boolean));
|
||||
if (!result) return;
|
||||
|
||||
const payload = {
|
||||
@@ -84,10 +65,6 @@ const handleSystemMessagesUpdate = async () => {
|
||||
},
|
||||
};
|
||||
|
||||
if (!isCaptainV2Enabled.value) {
|
||||
payload.config.instructions = state.instructions;
|
||||
}
|
||||
|
||||
emit('submit', payload);
|
||||
};
|
||||
|
||||
@@ -118,16 +95,6 @@ watch(
|
||||
:message-type="formErrors.resolutionMessage ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Editor
|
||||
v-if="!isCaptainV2Enabled"
|
||||
v-model="state.instructions"
|
||||
:label="t('CAPTAIN.ASSISTANTS.FORM.INSTRUCTIONS.LABEL')"
|
||||
:placeholder="t('CAPTAIN.ASSISTANTS.FORM.INSTRUCTIONS.PLACEHOLDER')"
|
||||
:message="formErrors.instructions"
|
||||
:max-length="20000"
|
||||
:message-type="formErrors.instructions ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.LABEL') }}
|
||||
|
||||
+1
-12
@@ -8,13 +8,6 @@ import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
|
||||
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
||||
import DocumentForm from './DocumentForm.vue';
|
||||
|
||||
defineProps({
|
||||
assistantId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
@@ -55,11 +48,7 @@ defineExpose({ dialogRef });
|
||||
:show-confirm-button="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<DocumentForm
|
||||
:assistant-id="assistantId"
|
||||
@submit="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
<DocumentForm @submit="handleSubmit" @cancel="handleCancel" />
|
||||
<template #footer />
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
+28
-9
@@ -2,7 +2,7 @@
|
||||
import { reactive, computed, ref, nextTick } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { minLength, requiredIf, url } from '@vuelidate/validators';
|
||||
import { required, minLength, requiredIf, url } from '@vuelidate/validators';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
|
||||
@@ -10,13 +10,6 @@ import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
|
||||
|
||||
const props = defineProps({
|
||||
assistantId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit', 'cancel']);
|
||||
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
@@ -25,11 +18,13 @@ const { t } = useI18n();
|
||||
|
||||
const formState = {
|
||||
uiFlags: useMapGetter('captainDocuments/getUIFlags'),
|
||||
assistants: useMapGetter('captainAssistants/getRecords'),
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
name: '',
|
||||
url: '',
|
||||
assistantId: null,
|
||||
documentType: 'url',
|
||||
pdfFile: null,
|
||||
};
|
||||
@@ -43,11 +38,19 @@ const validationRules = {
|
||||
url: requiredIf(() => state.documentType === 'url' && url),
|
||||
minLength: requiredIf(() => state.documentType === 'url' && minLength(1)),
|
||||
},
|
||||
assistantId: { required },
|
||||
pdfFile: {
|
||||
required: requiredIf(() => state.documentType === 'pdf'),
|
||||
},
|
||||
};
|
||||
|
||||
const assistantList = computed(() =>
|
||||
formState.assistants.value.map(assistant => ({
|
||||
value: assistant.id,
|
||||
label: assistant.name,
|
||||
}))
|
||||
);
|
||||
|
||||
const documentTypeOptions = [
|
||||
{ value: 'url', label: t('CAPTAIN.DOCUMENTS.FORM.TYPE.URL') },
|
||||
{ value: 'pdf', label: t('CAPTAIN.DOCUMENTS.FORM.TYPE.PDF') },
|
||||
@@ -67,6 +70,7 @@ const getErrorMessage = (field, errorKey) => {
|
||||
|
||||
const formErrors = computed(() => ({
|
||||
url: getErrorMessage('url', 'URL'),
|
||||
assistantId: getErrorMessage('assistantId', 'ASSISTANT'),
|
||||
pdfFile: getErrorMessage('pdfFile', 'PDF_FILE'),
|
||||
}));
|
||||
|
||||
@@ -102,7 +106,7 @@ const openFileDialog = () => {
|
||||
|
||||
const prepareDocumentDetails = () => {
|
||||
const formData = new FormData();
|
||||
formData.append('document[assistant_id]', props.assistantId);
|
||||
formData.append('document[assistant_id]', state.assistantId);
|
||||
|
||||
if (state.documentType === 'url') {
|
||||
formData.append('document[external_link]', state.url);
|
||||
@@ -214,6 +218,21 @@ const handleSubmit = async () => {
|
||||
:placeholder="t('CAPTAIN.DOCUMENTS.FORM.NAME.PLACEHOLDER')"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="assistant" class="mb-0.5 text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.DOCUMENTS.FORM.ASSISTANT.LABEL') }}
|
||||
</label>
|
||||
<ComboBox
|
||||
id="assistant"
|
||||
v-model="state.assistantId"
|
||||
:options="assistantList"
|
||||
:has-error="!!formErrors.assistantId"
|
||||
:placeholder="t('CAPTAIN.DOCUMENTS.FORM.ASSISTANT.PLACEHOLDER')"
|
||||
class="[&>div>button]:bg-n-alpha-black2"
|
||||
:message="formErrors.assistantId"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 justify-between items-center w-full">
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
+2
-10
@@ -3,7 +3,6 @@ import { ref, computed } from 'vue';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
||||
import ResponseForm from './ResponseForm.vue';
|
||||
@@ -22,7 +21,6 @@ const props = defineProps({
|
||||
const emit = defineEmits(['close']);
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
|
||||
const dialogRef = ref(null);
|
||||
const responseForm = ref(null);
|
||||
@@ -41,15 +39,9 @@ const createResponse = responseDetails =>
|
||||
const handleSubmit = async updatedResponse => {
|
||||
try {
|
||||
if (props.type === 'edit') {
|
||||
await updateResponse({
|
||||
...updatedResponse,
|
||||
assistant_id: route.params.assistantId,
|
||||
});
|
||||
await updateResponse(updatedResponse);
|
||||
} else {
|
||||
await createResponse({
|
||||
...updatedResponse,
|
||||
assistant_id: route.params.assistantId,
|
||||
});
|
||||
await createResponse(updatedResponse);
|
||||
}
|
||||
useAlert(t(`${i18nKey.value}.SUCCESS_MESSAGE`));
|
||||
dialogRef.value.close();
|
||||
|
||||
+32
-2
@@ -8,6 +8,7 @@ import { useMapGetter } from 'dashboard/composables/store';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
|
||||
|
||||
const props = defineProps({
|
||||
mode: {
|
||||
@@ -20,17 +21,18 @@ const props = defineProps({
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['submit', 'cancel']);
|
||||
const { t } = useI18n();
|
||||
|
||||
const formState = {
|
||||
uiFlags: useMapGetter('captainResponses/getUIFlags'),
|
||||
assistants: useMapGetter('captainAssistants/getRecords'),
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
question: '',
|
||||
answer: '',
|
||||
assistantId: null,
|
||||
};
|
||||
|
||||
const state = reactive({ ...initialState });
|
||||
@@ -38,8 +40,16 @@ const state = reactive({ ...initialState });
|
||||
const validationRules = {
|
||||
question: { required, minLength: minLength(1) },
|
||||
answer: { required, minLength: minLength(1) },
|
||||
assistantId: { required },
|
||||
};
|
||||
|
||||
const assistantList = computed(() =>
|
||||
formState.assistants.value.map(assistant => ({
|
||||
value: assistant.id,
|
||||
label: assistant.name,
|
||||
}))
|
||||
);
|
||||
|
||||
const v$ = useVuelidate(validationRules, state);
|
||||
|
||||
const isLoading = computed(() => formState.uiFlags.value.creatingItem);
|
||||
@@ -53,6 +63,7 @@ const getErrorMessage = (field, errorKey) => {
|
||||
const formErrors = computed(() => ({
|
||||
question: getErrorMessage('question', 'QUESTION'),
|
||||
answer: getErrorMessage('answer', 'ANSWER'),
|
||||
assistantId: getErrorMessage('assistantId', 'ASSISTANT'),
|
||||
}));
|
||||
|
||||
const handleCancel = () => emit('cancel');
|
||||
@@ -60,6 +71,7 @@ const handleCancel = () => emit('cancel');
|
||||
const prepareDocumentDetails = () => ({
|
||||
question: state.question,
|
||||
answer: state.answer,
|
||||
assistant_id: state.assistantId,
|
||||
});
|
||||
|
||||
const handleSubmit = async () => {
|
||||
@@ -74,11 +86,12 @@ const handleSubmit = async () => {
|
||||
const updateStateFromResponse = response => {
|
||||
if (!response) return;
|
||||
|
||||
const { question, answer } = response;
|
||||
const { question, answer, assistant } = response;
|
||||
|
||||
Object.assign(state, {
|
||||
question,
|
||||
answer,
|
||||
assistantId: assistant.id,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -102,6 +115,7 @@ watch(
|
||||
:message="formErrors.question"
|
||||
:message-type="formErrors.question ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Editor
|
||||
v-model="state.answer"
|
||||
:label="t('CAPTAIN.RESPONSES.FORM.ANSWER.LABEL')"
|
||||
@@ -110,6 +124,22 @@ watch(
|
||||
:max-length="10000"
|
||||
:message-type="formErrors.answer ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label for="assistant" class="mb-0.5 text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.RESPONSES.FORM.ASSISTANT.LABEL') }}
|
||||
</label>
|
||||
<ComboBox
|
||||
id="assistant"
|
||||
v-model="state.assistantId"
|
||||
:options="assistantList"
|
||||
:has-error="!!formErrors.assistantId"
|
||||
:placeholder="t('CAPTAIN.RESPONSES.FORM.ASSISTANT.PLACEHOLDER')"
|
||||
class="[&>div>button]:bg-n-alpha-black2 [&>div>button:not(.focused)]:dark:outline-n-weak [&>div>button:not(.focused)]:hover:!outline-n-slate-6"
|
||||
:message="formErrors.assistantId"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between w-full gap-3">
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store.js';
|
||||
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
|
||||
|
||||
const emit = defineEmits(['close', 'createAssistant']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
|
||||
const assistants = useMapGetter('captainAssistants/getRecords');
|
||||
|
||||
const currentAssistantId = computed(() => route.params.assistantId);
|
||||
|
||||
const isAssistantActive = assistant => {
|
||||
return assistant.id === Number(currentAssistantId.value);
|
||||
};
|
||||
|
||||
const fetchDataForRoute = async (routeName, assistantId) => {
|
||||
const dataFetchMap = {
|
||||
captain_assistants_responses_index: async () => {
|
||||
await store.dispatch('captainResponses/get', { assistantId });
|
||||
await store.dispatch('captainResponses/fetchPendingCount', assistantId);
|
||||
},
|
||||
captain_assistants_responses_pending: async () => {
|
||||
await store.dispatch('captainResponses/get', {
|
||||
assistantId,
|
||||
status: 'pending',
|
||||
});
|
||||
},
|
||||
captain_assistants_documents_index: async () => {
|
||||
await store.dispatch('captainDocuments/get', { assistantId });
|
||||
},
|
||||
captain_assistants_scenarios_index: async () => {
|
||||
await store.dispatch('captainScenarios/get', { assistantId });
|
||||
},
|
||||
captain_assistants_playground_index: () => {
|
||||
// Playground doesn't need pre-fetching, it loads on interaction
|
||||
},
|
||||
captain_assistants_inboxes_index: async () => {
|
||||
await store.dispatch('captainInboxes/get', { assistantId });
|
||||
},
|
||||
captain_tools_index: async () => {
|
||||
await store.dispatch('captainCustomTools/get', { page: 1 });
|
||||
},
|
||||
captain_assistants_settings_index: async () => {
|
||||
await store.dispatch('captainAssistants/show', assistantId);
|
||||
},
|
||||
};
|
||||
|
||||
const fetchFn = dataFetchMap[routeName];
|
||||
if (fetchFn) {
|
||||
await fetchFn();
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssistantChange = async assistant => {
|
||||
if (isAssistantActive(assistant)) return;
|
||||
|
||||
const currentRouteName = route.name;
|
||||
const targetRouteName =
|
||||
currentRouteName || 'captain_assistants_responses_index';
|
||||
|
||||
await fetchDataForRoute(targetRouteName, assistant.id);
|
||||
|
||||
await router.push({
|
||||
name: targetRouteName,
|
||||
params: {
|
||||
accountId: route.params.accountId,
|
||||
assistantId: assistant.id,
|
||||
},
|
||||
});
|
||||
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const openCreateAssistantDialog = () => {
|
||||
emit('createAssistant');
|
||||
emit('close');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="pt-5 pb-3 bg-n-alpha-3 backdrop-blur-[100px] outline outline-n-container outline-1 z-50 absolute w-[27.5rem] rounded-xl shadow-md flex flex-col gap-4"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between gap-4 px-6 pb-3 border-b border-n-alpha-2"
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2
|
||||
class="text-base font-medium cursor-pointer text-n-slate-12 w-fit hover:underline"
|
||||
>
|
||||
{{ t('CAPTAIN.ASSISTANT_SWITCHER.ASSISTANTS') }}
|
||||
</h2>
|
||||
</div>
|
||||
<p class="text-sm text-n-slate-11">
|
||||
{{ t('CAPTAIN.ASSISTANT_SWITCHER.SWITCH_ASSISTANT') }}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
:label="t('CAPTAIN.ASSISTANT_SWITCHER.NEW_ASSISTANT')"
|
||||
color="slate"
|
||||
icon="i-lucide-plus"
|
||||
size="sm"
|
||||
class="!bg-n-alpha-2 hover:!bg-n-alpha-3"
|
||||
@click="openCreateAssistantDialog"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="assistants.length > 0" class="flex flex-col gap-2 px-4">
|
||||
<Button
|
||||
v-for="assistant in assistants"
|
||||
:key="assistant.id"
|
||||
:label="assistant.name"
|
||||
variant="ghost"
|
||||
color="slate"
|
||||
trailing-icon
|
||||
:icon="isAssistantActive(assistant) ? 'i-lucide-check' : ''"
|
||||
class="!justify-end !px-2 !py-2 hover:!bg-n-alpha-2 [&>.i-lucide-check]:text-n-teal-10 h-9"
|
||||
size="sm"
|
||||
@click="handleAssistantChange(assistant)"
|
||||
>
|
||||
<span class="text-sm font-medium truncate text-n-slate-12">
|
||||
{{ assistant.name || '' }}
|
||||
</span>
|
||||
<Avatar
|
||||
v-if="assistant"
|
||||
:name="assistant.name"
|
||||
:size="20"
|
||||
icon-name="i-lucide-bot"
|
||||
rounded-full
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -78,10 +78,8 @@ const handleSuggestion = opt => {
|
||||
</p>
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'captain_assistants_create_index',
|
||||
params: {
|
||||
accountId: route.params.accountId,
|
||||
},
|
||||
name: 'captain_assistants_index',
|
||||
params: { accountId: route.params.accountId },
|
||||
}"
|
||||
class="text-n-slate-11 underline hover:text-n-slate-12"
|
||||
>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
|
||||
defineProps({
|
||||
color: {
|
||||
type: String,
|
||||
default: 'blue',
|
||||
validator: value => ['ruby', 'blue', 'teal', 'amber'].includes(value),
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'i-lucide-info',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const colorClasses = {
|
||||
ruby: 'outline-n-ruby-4 bg-n-ruby-3 text-n-ruby-11',
|
||||
blue: 'outline-n-blue-4 bg-n-blue-3 text-n-blue-11',
|
||||
teal: 'outline-n-teal-4 bg-n-teal-3 text-n-teal-11',
|
||||
amber: 'outline-n-amber-4 bg-n-amber-3 text-n-amber-11',
|
||||
};
|
||||
|
||||
const titleColorClasses = {
|
||||
ruby: 'text-n-ruby-11',
|
||||
blue: 'text-n-blue-11',
|
||||
teal: 'text-n-teal-11',
|
||||
amber: 'text-n-amber-11',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="p-4 rounded-xl outline outline-1 -outline-offset-1"
|
||||
:class="colorClasses[color]"
|
||||
>
|
||||
<div class="flex gap-3 items-start">
|
||||
<Icon :icon="icon" class="flex-shrink-0 mt-0.5" />
|
||||
<div class="flex-1 space-y-1">
|
||||
<h4
|
||||
v-if="title"
|
||||
class="text-sm font-semibold"
|
||||
:class="titleColorClasses[color]"
|
||||
>
|
||||
{{ title }}
|
||||
</h4>
|
||||
<p v-if="message" class="mb-0 text-sm">
|
||||
{{ message }}
|
||||
</p>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -221,70 +221,26 @@ const menuItems = computed(() => {
|
||||
name: 'Captain',
|
||||
icon: 'i-woot-captain',
|
||||
label: t('SIDEBAR.CAPTAIN'),
|
||||
activeOn: ['captain_assistants_create_index'],
|
||||
children: [
|
||||
{
|
||||
name: 'FAQs',
|
||||
label: t('SIDEBAR.CAPTAIN_RESPONSES'),
|
||||
activeOn: [
|
||||
'captain_assistants_responses_index',
|
||||
'captain_assistants_responses_pending',
|
||||
],
|
||||
to: accountScopedRoute('captain_assistants_index', {
|
||||
navigationPath: 'captain_assistants_responses_index',
|
||||
}),
|
||||
name: 'Assistants',
|
||||
label: t('SIDEBAR.CAPTAIN_ASSISTANTS'),
|
||||
to: accountScopedRoute('captain_assistants_index'),
|
||||
},
|
||||
{
|
||||
name: 'Documents',
|
||||
label: t('SIDEBAR.CAPTAIN_DOCUMENTS'),
|
||||
activeOn: ['captain_assistants_documents_index'],
|
||||
to: accountScopedRoute('captain_assistants_index', {
|
||||
navigationPath: 'captain_assistants_documents_index',
|
||||
}),
|
||||
to: accountScopedRoute('captain_documents_index'),
|
||||
},
|
||||
{
|
||||
name: 'Scenarios',
|
||||
label: t('SIDEBAR.CAPTAIN_SCENARIOS'),
|
||||
activeOn: ['captain_assistants_scenarios_index'],
|
||||
to: accountScopedRoute('captain_assistants_index', {
|
||||
navigationPath: 'captain_assistants_scenarios_index',
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'Playground',
|
||||
label: t('SIDEBAR.CAPTAIN_PLAYGROUND'),
|
||||
activeOn: ['captain_assistants_playground_index'],
|
||||
to: accountScopedRoute('captain_assistants_index', {
|
||||
navigationPath: 'captain_assistants_playground_index',
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'Inboxes',
|
||||
label: t('SIDEBAR.CAPTAIN_INBOXES'),
|
||||
activeOn: ['captain_assistants_inboxes_index'],
|
||||
to: accountScopedRoute('captain_assistants_index', {
|
||||
navigationPath: 'captain_assistants_inboxes_index',
|
||||
}),
|
||||
name: 'Responses',
|
||||
label: t('SIDEBAR.CAPTAIN_RESPONSES'),
|
||||
to: accountScopedRoute('captain_responses_index'),
|
||||
},
|
||||
{
|
||||
name: 'Tools',
|
||||
label: t('SIDEBAR.CAPTAIN_TOOLS'),
|
||||
activeOn: ['captain_tools_index'],
|
||||
to: accountScopedRoute('captain_assistants_index', {
|
||||
navigationPath: 'captain_tools_index',
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: 'Settings',
|
||||
label: t('SIDEBAR.CAPTAIN_SETTINGS'),
|
||||
activeOn: [
|
||||
'captain_assistants_settings_index',
|
||||
'captain_assistants_guidelines_index',
|
||||
'captain_assistants_guardrails_index',
|
||||
],
|
||||
to: accountScopedRoute('captain_assistants_index', {
|
||||
navigationPath: 'captain_assistants_settings_index',
|
||||
}),
|
||||
to: accountScopedRoute('captain_tools_index'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -19,46 +19,19 @@ export function useSidebarContext() {
|
||||
return '/';
|
||||
};
|
||||
|
||||
// Helper to find route definition by name without resolving
|
||||
const findRouteByName = name => {
|
||||
const routes = router.getRoutes();
|
||||
return routes.find(route => route.name === name);
|
||||
};
|
||||
|
||||
const resolvePermissions = to => {
|
||||
if (!to) return [];
|
||||
|
||||
// If navigationPath param exists, get the target route definition
|
||||
if (to.params?.navigationPath) {
|
||||
const targetRoute = findRouteByName(to.params.navigationPath);
|
||||
return targetRoute?.meta?.permissions ?? [];
|
||||
}
|
||||
|
||||
return router.resolve(to)?.meta?.permissions ?? [];
|
||||
if (to) return router.resolve(to)?.meta?.permissions ?? [];
|
||||
return [];
|
||||
};
|
||||
|
||||
const resolveFeatureFlag = to => {
|
||||
if (!to) return '';
|
||||
|
||||
// If navigationPath param exists, get the target route definition
|
||||
if (to.params?.navigationPath) {
|
||||
const targetRoute = findRouteByName(to.params.navigationPath);
|
||||
return targetRoute?.meta?.featureFlag || '';
|
||||
}
|
||||
|
||||
return router.resolve(to)?.meta?.featureFlag || '';
|
||||
if (to) return router.resolve(to)?.meta?.featureFlag || '';
|
||||
return '';
|
||||
};
|
||||
|
||||
const resolveInstallationType = to => {
|
||||
if (!to) return [];
|
||||
|
||||
// If navigationPath param exists, get the target route definition
|
||||
if (to.params?.navigationPath) {
|
||||
const targetRoute = findRouteByName(to.params.navigationPath);
|
||||
return targetRoute?.meta?.installationTypes || [];
|
||||
}
|
||||
|
||||
return router.resolve(to)?.meta?.installationTypes || [];
|
||||
if (to) return router.resolve(to)?.meta?.installationTypes || [];
|
||||
return [];
|
||||
};
|
||||
|
||||
const isAllowed = to => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from 'dashboard/constants/automation';
|
||||
|
||||
/**
|
||||
* This is a shared composables that holds utilities used to build dropdown and file options
|
||||
* This is a shared composables that holds utilites used to build dropdown and file options
|
||||
* @returns {Object} An object containing various automation-related functions and computed properties.
|
||||
*/
|
||||
export default function useAutomationValues() {
|
||||
|
||||
@@ -41,6 +41,7 @@ export const FEATURE_FLAGS = {
|
||||
CAPTAIN_V2: 'captain_integration_v2',
|
||||
SAML: 'saml',
|
||||
QUOTED_EMAIL_REPLY: 'quoted_email_reply',
|
||||
STRIPE_V2: 'stripe_v2',
|
||||
};
|
||||
|
||||
export const PREMIUM_FEATURES = [
|
||||
|
||||
@@ -21,7 +21,7 @@ export const initializeAudioAlerts = user => {
|
||||
enable_audio_alerts: audioAlertType,
|
||||
alert_if_unread_assigned_conversation_exist: alertIfUnreadConversationExist,
|
||||
notification_tone: audioAlertTone,
|
||||
// UI Settings can be undefined initially as we don't send the
|
||||
// UI Settings can be undefined initally as we don't send the
|
||||
// entire payload for the user during the signup process.
|
||||
} = uiSettings || {};
|
||||
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "حذف",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "حذف جهة الاتصال"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Изтрий",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Изтриване на контакта"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Esborrar",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Contacte esborrat"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Vymazat",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Slet",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Slet kontakt"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Löschen",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Kontakt löschen"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Διαγραφή",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Διαγραφή Επαφής"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -341,11 +341,6 @@
|
||||
"CAPTAIN": {
|
||||
"NAME": "Captain",
|
||||
"HEADER_KNOW_MORE": "Know more",
|
||||
"ASSISTANT_SWITCHER": {
|
||||
"ASSISTANTS": "Assistants",
|
||||
"SWITCH_ASSISTANT": "Switch between assistants",
|
||||
"NEW_ASSISTANT": "Create Assistant"
|
||||
},
|
||||
"COPILOT": {
|
||||
"TITLE": "Copilot",
|
||||
"TRY_THESE_PROMPTS": "Try these prompts",
|
||||
@@ -485,7 +480,9 @@
|
||||
"NOT_FOUND": "Could not find the assistant. Please try again."
|
||||
},
|
||||
"SETTINGS": {
|
||||
"HEADER": "Settings",
|
||||
"BREADCRUMB": {
|
||||
"ASSISTANT": "Assistant"
|
||||
},
|
||||
"BASIC_SETTINGS": {
|
||||
"TITLE": "Basic settings",
|
||||
"DESCRIPTION": "Customize what the assistant says when ending a conversation or transferring to a human."
|
||||
@@ -502,16 +499,15 @@
|
||||
"TITLE": "Guardrails",
|
||||
"DESCRIPTION": "Keeps things on track—only the kinds of questions you want your assistant to answer, nothing off-limits or off-topic."
|
||||
},
|
||||
"SCENARIOS": {
|
||||
"TITLE": "Scenarios",
|
||||
"DESCRIPTION": "Give your assistant some context—like “what to do when a user is stuck,” or “how to act during a refund request.”"
|
||||
},
|
||||
"RESPONSE_GUIDELINES": {
|
||||
"TITLE": "Response guidelines",
|
||||
"DESCRIPTION": "The vibe and structure of your assistant’s replies—clear and friendly? Short and snappy? Detailed and formal?"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Delete Assistant",
|
||||
"DESCRIPTION": "This action is permanent. Deleting this assistant will remove it from all connected inboxes and permanently erase all generated knowledge.",
|
||||
"BUTTON_TEXT": "Delete {assistantName}"
|
||||
}
|
||||
},
|
||||
"OPTIONS": {
|
||||
@@ -530,6 +526,9 @@
|
||||
"GUARDRAILS": {
|
||||
"TITLE": "Guardrails",
|
||||
"DESCRIPTION": "Keeps things on track—only the kinds of questions you want your assistant to answer, nothing off-limits or off-topic.",
|
||||
"BREADCRUMB": {
|
||||
"TITLE": "Guardrails"
|
||||
},
|
||||
"BULK_ACTION": {
|
||||
"SELECTED": "{count} item selected | {count} items selected",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
@@ -575,6 +574,9 @@
|
||||
"RESPONSE_GUIDELINES": {
|
||||
"TITLE": "Response Guidelines",
|
||||
"DESCRIPTION": "The vibe and structure of your assistant’s replies—clear and friendly? Short and snappy? Detailed and formal?",
|
||||
"BREADCRUMB": {
|
||||
"TITLE": "Response Guidelines"
|
||||
},
|
||||
"BULK_ACTION": {
|
||||
"SELECTED": "{count} item selected | {count} items selected",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
@@ -620,6 +622,9 @@
|
||||
"SCENARIOS": {
|
||||
"TITLE": "Scenarios",
|
||||
"DESCRIPTION": "Give your assistant some context—like “what to do when a user is stuck,” or “how to act during a refund request.”",
|
||||
"BREADCRUMB": {
|
||||
"TITLE": "Scenarios"
|
||||
},
|
||||
"BULK_ACTION": {
|
||||
"SELECTED": "{count} item selected | {count} items selected",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
@@ -717,6 +722,11 @@
|
||||
"NAME": {
|
||||
"LABEL": "Document Name (Optional)",
|
||||
"PLACEHOLDER": "Enter a name for the document"
|
||||
},
|
||||
"ASSISTANT": {
|
||||
"LABEL": "Assistant",
|
||||
"PLACEHOLDER": "Select the assistant",
|
||||
"ERROR": "The assistant field is required"
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
@@ -911,6 +921,12 @@
|
||||
"LABEL": "Answer",
|
||||
"PLACEHOLDER": "Enter the answer here",
|
||||
"ERROR": "Please provide a valid answer."
|
||||
},
|
||||
|
||||
"ASSISTANT": {
|
||||
"LABEL": "Assistant",
|
||||
"PLACEHOLDER": "Select an assistant",
|
||||
"ERROR": "Please select an assistant."
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
|
||||
@@ -251,12 +251,6 @@
|
||||
"ACCOUNT_SUSPENDED": {
|
||||
"TITLE": "Account Suspended",
|
||||
"MESSAGE": "Your account is suspended. Please reach out to the support team for more information."
|
||||
},
|
||||
"NO_ACCOUNTS": {
|
||||
"TITLE": "No account found",
|
||||
"MESSAGE_CLOUD": "You are not part of any accounts right now. If you think this is a mistake, please reach out to our support team.",
|
||||
"MESSAGE_SELF_HOSTED": "You are not part of any accounts right now. Please reach out to your administrator.",
|
||||
"LOGOUT": "Log out"
|
||||
}
|
||||
},
|
||||
"COMPONENTS": {
|
||||
@@ -311,10 +305,6 @@
|
||||
"CAPTAIN_DOCUMENTS": "Documents",
|
||||
"CAPTAIN_RESPONSES": "FAQs",
|
||||
"CAPTAIN_TOOLS": "Tools",
|
||||
"CAPTAIN_SCENARIOS": "Scenarios",
|
||||
"CAPTAIN_PLAYGROUND": "Playground",
|
||||
"CAPTAIN_INBOXES": "Inboxes",
|
||||
"CAPTAIN_SETTINGS": "Settings",
|
||||
"HOME": "Home",
|
||||
"AGENTS": "Agents",
|
||||
"AGENT_BOTS": "Bots",
|
||||
@@ -392,6 +382,11 @@
|
||||
"DESCRIPTION": "View your previous invoices, edit your billing details, or cancel your subscription.",
|
||||
"BUTTON_TXT": "Go to the billing portal"
|
||||
},
|
||||
"V2_BILLING_PORTAL": {
|
||||
"TITLE": "Manage your billing",
|
||||
"DESCRIPTION": "View your credits balance, top up, manage subscriptions, and view transactions.",
|
||||
"BUTTON_TXT": "Go to billing dashboard"
|
||||
},
|
||||
"CAPTAIN": {
|
||||
"TITLE": "Captain",
|
||||
"DESCRIPTION": "Manage usage and credits for Captain AI.",
|
||||
@@ -407,6 +402,120 @@
|
||||
},
|
||||
"NO_BILLING_USER": "Your billing account is being configured. Please refresh the page and try again."
|
||||
},
|
||||
"BILLING_SETTINGS_V2": {
|
||||
"TITLE": "Billing",
|
||||
"DESCRIPTION": "Manage your usage-based subscription with flexible credit-based pricing. Top up anytime or subscribe to a plan.",
|
||||
"VIEW_PRICING": "View Pricing",
|
||||
"OPEN_STRIPE_DASHBOARD": "Open Stripe Dashboard",
|
||||
"CREDITS_BALANCE": {
|
||||
"TITLE": "Captain Credits",
|
||||
"DESCRIPTION": "Your current credit balance and recent transactions.",
|
||||
"CURRENT_BALANCE": "Current Balance",
|
||||
"CREDITS": "credits",
|
||||
"REFRESH": "Refresh",
|
||||
"BUY_CREDITS": "Buy Credits",
|
||||
"VIEW_HISTORY": "View History",
|
||||
"MONTHLY_CREDITS": "Monthly Credits",
|
||||
"TOPUP_CREDITS": "Top-up Credits",
|
||||
"USED_THIS_MONTH": "Used This Month",
|
||||
"TOTAL_USED": "Total Used",
|
||||
"USAGE_BASED_INFO": "Credits are consumed as you use Captain"
|
||||
},
|
||||
"PLAN_SUMMARY": {
|
||||
"NAME": "Plan Name",
|
||||
"PRICE": "Price",
|
||||
"PER_MONTH": "{price}/month",
|
||||
"RENEWAL_DATE": "Renewal Date",
|
||||
"CREDITS_INCLUDED": "Captain AI Credits Included",
|
||||
"NUMBER_OF_SEATS": "Number of Seats",
|
||||
"ADJUST_SEATS_HINT": "Adjust seats to scale your plan",
|
||||
"UPDATE": "Update",
|
||||
"VIEW_ALL_PLANS": "View All Plans",
|
||||
"CANCEL_PLAN": "Cancel Plan"
|
||||
},
|
||||
"PRICING_PLANS": {
|
||||
"TITLE": "Pricing Plans",
|
||||
"DESCRIPTION": "Choose a plan that fits your team. All plans include credits that reset monthly.",
|
||||
"CURRENT_PLAN": "Current Subscription",
|
||||
"RECOMMENDED": "Recommended",
|
||||
"MONTH": "month",
|
||||
"SEATS": "seats",
|
||||
"CREDITS_PER_SEAT": "credits/month",
|
||||
"MIN_SEATS": "Minimum seats",
|
||||
"SUBSCRIBE": "Subscribe",
|
||||
"SWITCH_PLAN": "Switch Plan",
|
||||
"BLOCKED": "Blocked",
|
||||
"PENDING_TITLE": "Pending Changes Scheduled",
|
||||
"PENDING_MESSAGE": "Your subscription changes will take effect on your next billing date.",
|
||||
"NEXT_PLAN": "Next Plan",
|
||||
"NEXT_QUANTITY": "Next Quantity",
|
||||
"EFFECTIVE_DATE": "Effective Date",
|
||||
"CANCELLING_TITLE": "Subscription Ending",
|
||||
"CANCELLING_MESSAGE": "Your subscription will be cancelled at the end of the current billing period. You can continue using your current plan until then.",
|
||||
"CANCELLING_BLOCKED_INFO": "You cannot change plans or subscribe to new plans while your subscription is ending.",
|
||||
"ENDS_ON": "Subscription ends on",
|
||||
"NO_PLANS": "No pricing plans available.",
|
||||
"SUBSCRIBE_SUCCESS": "Successfully subscribed to the plan! Your credits have been updated.",
|
||||
"CANCEL_SUCCESS": "Subscription cancelled successfully.",
|
||||
"UPDATE_QUANTITY_SUCCESS": "Subscription quantity updated successfully!",
|
||||
"CHANGE_PLAN_SUCCESS": "Successfully changed to the new plan! Your credits have been updated."
|
||||
},
|
||||
"TOPUP_OPTIONS": {
|
||||
"TITLE": "Top-up Credits",
|
||||
"DESCRIPTION": "Add more credits to your account, to ensure uninterrupted Captain usage.",
|
||||
"CREDITS": "Credits",
|
||||
"PRICE": "Price",
|
||||
"BUY_NOW": "Buy Now",
|
||||
"NO_OPTIONS": "No top-up options available.",
|
||||
"TOPUP_SUCCESS": "Credits added successfully! Your new balance is available now.",
|
||||
"NO_PAYMENT_METHOD_ERROR": "No payment method found. Please add a payment method in your Stripe dashboard before making a purchase. Click the 'Open Stripe Dashboard' button above to add one.",
|
||||
"UPGRADE_REQUIRED": "Upgrade your plan to purchase top-up credits.",
|
||||
"UPGRADE_MESSAGE": "Top-ups are only available for Startup, Business, and Enterprise plans. Please upgrade your plan to purchase additional credits."
|
||||
},
|
||||
"CREDIT_GRANTS": {
|
||||
"TITLE": "Credit History",
|
||||
"CREDITS": "credits",
|
||||
"EXPIRES": "Expires",
|
||||
"NO_EXPIRY": "Never expires",
|
||||
"ADDED": "Added on {date}",
|
||||
"VOIDED": "Voided",
|
||||
"NO_GRANTS": "No credit grants yet.",
|
||||
"TOPUPS": "Topups",
|
||||
"MONTHLY": "Monthly"
|
||||
},
|
||||
"SUBSCRIBE_MODAL": {
|
||||
"TITLE": "Confirm Subscription",
|
||||
"DESCRIPTION": "You are subscribing to {planName} for {planPrice} per seat with {credits} included credits for Captain AI.",
|
||||
"NUMBER_OF_SEATS": "Number of seats",
|
||||
"MIN": "Min",
|
||||
"MAX": "Max",
|
||||
"BASE_PRICE": "Base price per seat",
|
||||
"INCLUDED_CREDITS": "Included credits",
|
||||
"CREDITS": "credits",
|
||||
"MONTHLY_TOTAL": "Monthly Total",
|
||||
"CANCEL": "Cancel",
|
||||
"CONFIRM": "Confirm Subscription"
|
||||
},
|
||||
"TOPUP_MODAL": {
|
||||
"TITLE": "Confirm Top-up",
|
||||
"DESCRIPTION": "Top up your account with {credits} credits for {price}. Your card will be immediately charged.",
|
||||
"CREDITS": "Credits",
|
||||
"PRICE": "Price",
|
||||
"INFO": "Credits will be added to your account immediately and never expire.",
|
||||
"CANCEL": "Cancel",
|
||||
"CONFIRM": "Confirm Top-up"
|
||||
},
|
||||
"CANCEL_MODAL": {
|
||||
"TITLE": "Cancel Subscription",
|
||||
"WARNING_TITLE": "Are you sure?",
|
||||
"WARNING_MESSAGE": "Cancelling your subscription will stop monthly credit renewals. Any remaining credits will still be available until used.",
|
||||
"REASON_LABEL": "Reason for cancellation (optional)",
|
||||
"REASON_PLACEHOLDER": "Help us improve by sharing why you're cancelling...",
|
||||
"INFO": "You can resubscribe anytime to continue receiving monthly credits.",
|
||||
"KEEP_SUBSCRIPTION": "Keep Subscription",
|
||||
"CONFIRM_CANCEL": "Confirm Cancellation"
|
||||
}
|
||||
},
|
||||
"SECURITY_SETTINGS": {
|
||||
"TITLE": "Security",
|
||||
"DESCRIPTION": "Manage your account security settings.",
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Eliminar",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Eliminar contacto"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "حذف",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "حذف مخاطب"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Poista",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Poista yhteystieto"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Supprimer",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Supprimer le contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "מחק",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "מחק איש קשר"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Izbriši",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Törlés",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Kontakt törlése"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Hapus",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Hapus kontak"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Eyða",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Eyða tengilið"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"BROWSER": "Browser",
|
||||
"OS": "Sistema Operativo",
|
||||
"INITIATED_FROM": "Iniziato da",
|
||||
"INITIATED_AT": "Avviata alle",
|
||||
"INITIATED_AT": "Iniziato alle",
|
||||
"IP_ADDRESS": "Indirizzo IP",
|
||||
"CREATED_AT_LABEL": "Creato il",
|
||||
"NEW_MESSAGE": "Nuovo messaggio",
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "Nessuna etichetta disponibile.",
|
||||
"SELECTED_COUNT": "{count} selezionate",
|
||||
"CLEAR_SELECTION": "Annulla selezione",
|
||||
"SELECT_ALL": "Seleziona tutto ({count})",
|
||||
"DELETE_CONTACTS": "Elimina",
|
||||
"DELETE_SUCCESS": "Contatti eliminati con successo.",
|
||||
"DELETE_FAILED": "Impossibile eliminare i contatti.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Elimina i contatti selezionati",
|
||||
"SINGULAR_TITLE": "Elimina il contatto selezionato",
|
||||
"DESCRIPTION": "Questo eliminerà definitivamente i {count} contatti selezionati. Questa azione non può essere annullata.",
|
||||
"SINGULAR_DESCRIPTION": "Questo eliminerà definitivamente il contatto selezionato. Questa azione non può essere annullata.",
|
||||
"CONFIRM_MULTIPLE": "Elimina contatti",
|
||||
"CONFIRM_SINGLE": "Elimina contatto"
|
||||
}
|
||||
"SELECT_ALL": "Seleziona tutto ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"CONVERSATION": {
|
||||
"SELECT_A_CONVERSATION": "Seleziona una conversazione dal pannello a sinistra",
|
||||
"SELECT_A_CONVERSATION": "Si prega di selezionare una conversazione dal pannello a sinistra",
|
||||
"CSAT_REPLY_MESSAGE": "Valuta la conversazione",
|
||||
"404": "Siamo spiacenti, non siamo riusciti a trovare la conversazione. Riprova",
|
||||
"SWITCH_VIEW_LAYOUT": "Cambia layout",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"LOADING": "Caricamento notifiche",
|
||||
"404": "Non ci sono notifiche attive in questo gruppo.",
|
||||
"NO_NOTIFICATIONS": "Nessuna notifica",
|
||||
"NOTE": "Notifiche da tutte le Inbox",
|
||||
"NOTE": "Notifiche da tutte le inbox sottoscritte",
|
||||
"NO_MESSAGES_AVAILABLE": "Oops! Impossibile recuperare i messaggi",
|
||||
"SNOOZED_UNTIL": "Posticipata fino a",
|
||||
"SNOOZED_UNTIL_TOMORROW": "Posticipata fino a domani",
|
||||
@@ -45,8 +45,8 @@
|
||||
"MARK_AS_UNREAD": "Segna come da leggere",
|
||||
"SNOOZE": "Posticipa",
|
||||
"DELETE": "Elimina",
|
||||
"MARK_ALL_READ": "Segna tutte come lette",
|
||||
"DELETE_ALL": "Elimina tutte",
|
||||
"MARK_ALL_READ": "Segna tutto come letto",
|
||||
"DELETE_ALL": "Elimina tutto",
|
||||
"DELETE_ALL_READ": "Elimina tutte le lette"
|
||||
},
|
||||
"DISPLAY_MENU": {
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
},
|
||||
"BUSINESS_ACCOUNT_ID": {
|
||||
"LABEL": "ID Account Business",
|
||||
"PLACEHOLDER": "Inserisci l'ID dell'Account Business ottenuto dalla dashboard sviluppatore di Facebook.",
|
||||
"PLACEHOLDER": "Si prega di inserire l'ID dell'account business ottenuto dalla dashboard sviluppatore di Facebook.",
|
||||
"ERROR": "Inserisci un valore valido."
|
||||
},
|
||||
"WEBHOOK_VERIFY_TOKEN": {
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
},
|
||||
"HELP_TEXT": {
|
||||
"TITLE": "Stai utilizzando l'integrazione Slack",
|
||||
"BODY": "Con questa integrazione, tutte le conversazioni in arrivo verranno sincronizzate nel canale ***{selectedChannelName}*** del tuo workspace Slack. Potrai gestire tutte le conversazioni con i clienti direttamente dal canale e non perderai mai un messaggio.\n\nEcco le principali funzionalità dell’integrazione:\n\n**Rispondi alle conversazioni direttamente da Slack:** Per rispondere a una conversazione nel canale Slack ***{selectedChannelName}***, ti basta scrivere il tuo messaggio e inviarlo come thread. Questo genererà automaticamente una risposta al cliente tramite Chatwoot. Semplice, no?\n\n**Crea note private:** Se vuoi aggiungere note private invece di risposte, inizia il messaggio con ***`note:`***. In questo modo il messaggio resterà privato e non sarà visibile al cliente.\n\n**Associa un profilo operatore:** Se la persona che risponde su Slack ha un profilo operatore in app con lo stesso indirizzo email, le risposte verranno associate automaticamente a quel profilo. In questo modo potrai sapere facilmente chi ha risposto e quando. Se invece chi risponde non ha un profilo operatore associato, le risposte verranno inviate al cliente dal profilo del bot.",
|
||||
"BODY": "Con questa integrazione, tutte le conversazioni in arrivo verranno sincronizzate nel canale ***{selectedChannelName}*** del tuo workspace Slack. Potrai gestire tutte le conversazioni con i clienti direttamente dal canale e non perderai mai un messaggio.\n\nEcco le principali funzionalità dell’integrazione:\n\n**Rispondi alle conversazioni direttamente da Slack:** Per rispondere a una conversazione nel canale Slack ***{selectedChannelName}***, ti basta scrivere il tuo messaggio e inviarlo come thread. Questo genererà automaticamente una risposta al cliente tramite Chatwoot. Semplice, no?\n\n**Crea note private:** Se vuoi aggiungere note private invece di risposte, inizia il messaggio con ***`note:`***. In questo modo il messaggio resterà privato e non sarà visibile al cliente.\n\n**Associa un profilo operatore:** Se la persona che risponde su Slack ha un profilo operatore in Chatwoot con lo stesso indirizzo email, le risposte verranno associate automaticamente a quel profilo. In questo modo potrai sapere facilmente chi ha risposto e quando. Se invece chi risponde non ha un profilo operatore associato, le risposte verranno inviate al cliente dal profilo del bot.",
|
||||
"SELECTED": "selezionato"
|
||||
},
|
||||
"SELECT_CHANNEL": {
|
||||
@@ -472,7 +472,7 @@
|
||||
"FEATURES": {
|
||||
"TITLE": "Funzionalità",
|
||||
"ALLOW_CONVERSATION_FAQS": "Genera FAQ dalle conversazioni risolte",
|
||||
"ALLOW_MEMORIES": "Salva memorie e dettagli chiave dalle interazioni con i clienti.",
|
||||
"ALLOW_MEMORIES": "Cattura i dettagli chiave come memorie dalle interazioni con i clienti.",
|
||||
"ALLOW_CITATIONS": "Includi citazioni alle fonti nelle risposte"
|
||||
}
|
||||
},
|
||||
@@ -694,8 +694,8 @@
|
||||
"HEADER": "Documents",
|
||||
"ADD_NEW": "Create a new document",
|
||||
"RELATED_RESPONSES": {
|
||||
"TITLE": "FAQ Correlate",
|
||||
"DESCRIPTION": "Queste FAQ vengono generate direttamente dai Documenti."
|
||||
"TITLE": "Related FAQs",
|
||||
"DESCRIPTION": "These FAQs are generated directly from the document."
|
||||
},
|
||||
"FORM_DESCRIPTION": "Enter the URL of the document to add it as a knowledge source and choose the assistant to associate it with.",
|
||||
"CREATE": {
|
||||
@@ -740,7 +740,7 @@
|
||||
"ERROR_MESSAGE": "Si è verificato un errore durante l'eliminazione del documento, riprova."
|
||||
},
|
||||
"OPTIONS": {
|
||||
"VIEW_RELATED_RESPONSES": "Visualizza FAQ Correlate",
|
||||
"VIEW_RELATED_RESPONSES": "Visualizza Risposte Correlate",
|
||||
"DELETE_DOCUMENT": "Elimina Documento"
|
||||
},
|
||||
"EMPTY_STATE": {
|
||||
@@ -895,7 +895,7 @@
|
||||
"FILTER": {
|
||||
"ASSISTANT": "Assistente: {selected}",
|
||||
"STATUS": "Stato: {selected}",
|
||||
"ALL_ASSISTANTS": "Tutti"
|
||||
"ALL_ASSISTANTS": "Tutte"
|
||||
},
|
||||
"STATUS": {
|
||||
"TITLE": "Stato",
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
}
|
||||
},
|
||||
"LABEL_REPORTS": {
|
||||
"HEADER": "Panoramica Etichette",
|
||||
"HEADER": "Panoramica etichette",
|
||||
"DESCRIPTION": "Monitora la performance delle etichette con metriche chiave, tra cui conversazioni, tempi di risposta, tempi di risoluzione e casi risolti. Fai clic sul nome di un'etichetta per approfondimenti dettagliati.",
|
||||
"LOADING_CHART": "Caricamento dati grafici...",
|
||||
"NO_ENOUGH_DATA": "Non ci sono abbastanza dati per generare il report, riprova più tardi.",
|
||||
@@ -331,7 +331,7 @@
|
||||
}
|
||||
},
|
||||
"TEAM_REPORTS": {
|
||||
"HEADER": "Panoramica Team",
|
||||
"HEADER": "Panoramica team",
|
||||
"DESCRIPTION": "Ottieni un'istantanea delle prestazioni del tuo team con metriche essenziali, tra cui conversazioni, tempi di risposta, tempi di risoluzione e casi risolti. Fare clic su un nome del team per maggiori dettagli.",
|
||||
"LOADING_CHART": "Caricamento dati grafici...",
|
||||
"NO_ENOUGH_DATA": "Non ci sono abbastanza dati per generare il report, riprova più tardi.",
|
||||
@@ -399,7 +399,7 @@
|
||||
}
|
||||
},
|
||||
"CSAT_REPORTS": {
|
||||
"HEADER": "Report CSAT",
|
||||
"HEADER": "Rapporti CSAT",
|
||||
"NO_RECORDS": "Non ci sono risposte ai sondaggi CSAT disponibili.",
|
||||
"DOWNLOAD": "Scarica report CSAT",
|
||||
"DOWNLOAD_FAILED": "Download dei report CSAT non riuscito",
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "削除",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "連絡先を削除"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "삭제",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "연락처 지우기"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Ištrinti",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Trinti kontaktą"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "Atlasīti {count}",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Dzēst",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Dzēst kontaktpersonu"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "ഇല്ലാതാക്കുക",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "കോൺടാക്റ്റ് ഇല്ലാതാക്കുക"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Padamkan",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Verwijderen",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Contactpersoon verwijderen"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Slett",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Usuń",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Usuń kontakt"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Selecionar todas ({count})",
|
||||
"DELETE_CONTACTS": "Excluir",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Excluir contacto"
|
||||
}
|
||||
"SELECT_ALL": "Selecionar todas ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selecionado",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Selecionar todos ({count})",
|
||||
"DELETE_CONTACTS": "Excluir",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Excluir contato"
|
||||
}
|
||||
"SELECT_ALL": "Selecionar todos ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Şterge",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Șterge contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "Выбрано {count}",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Выбрать все ({count})",
|
||||
"DELETE_CONTACTS": "Удалить",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Удалить контакт"
|
||||
}
|
||||
"SELECT_ALL": "Выбрать все ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Vymazať",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Vymazať kontakt"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Izbriši",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Obriši kontakt"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Radera",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Ta bort kontakt"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "ลบ",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "ลบผู้ติดต่อ"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -578,19 +578,8 @@
|
||||
"DESCRIPTION": "Select the labels you want to add to the selected contacts.",
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Seçimi temizle",
|
||||
"SELECT_ALL": "Tümünü seç ({count})",
|
||||
"DELETE_CONTACTS": "Sil",
|
||||
"DELETE_SUCCESS": "Kişiler başarıyla silindi.",
|
||||
"DELETE_FAILED": "Kişiler silinemedi.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Seçilen kişileri sil",
|
||||
"SINGULAR_TITLE": "Seçilen kişiyi sil",
|
||||
"DESCRIPTION": "Bu işlem, seçilen {count} kişileri kalıcı olarak silecektir. Bu eylem geri alınamaz.",
|
||||
"SINGULAR_DESCRIPTION": "Bu işlem, seçilen kişiyi kalıcı olarak silecektir. Bu eylem geri alınamaz.",
|
||||
"CONFIRM_MULTIPLE": "Kişileri sil",
|
||||
"CONFIRM_SINGLE": "Kişiyi Sil"
|
||||
}
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Tümünü seç ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"LOADING_CONVERSATIONS": "Sohbetler Yükleniyor\n",
|
||||
"CANNOT_REPLY": "Nedeniyle cevap veremezsiniz",
|
||||
"24_HOURS_WINDOW": "24 saat mesaj penceresi kısıtlaması",
|
||||
"API_HOURS_WINDOW": "Bu konuşmaya yalnızca {hours} saat içinde cevap verebilirsiniz",
|
||||
"API_HOURS_WINDOW": "Bu sohbete yalnızca {hours} saat içinde yanıt verebilirsiniz",
|
||||
"NOT_ASSIGNED_TO_YOU": "Bu görüşme size atanmamış. Bu konuşmayı kendinize atamak ister misiniz?",
|
||||
"ASSIGN_TO_ME": "Bana ata",
|
||||
"BOT_HANDOFF_MESSAGE": "Şu anda bir asistan veya bot tarafından yürütülen bir konuşmaya yanıt veriyorsunuz.",
|
||||
@@ -42,7 +42,7 @@
|
||||
"BOT_HANDOFF_ERROR": "Konuşma devralınamadı. Lütfen tekrar deneyin.",
|
||||
"TWILIO_WHATSAPP_CAN_REPLY": "Bu konuşmaya yalnızca şablon mesaj kullanarak yanıt verebilirsiniz, çünkü",
|
||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 saat mesaj penceresi kısıtlaması",
|
||||
"OLD_INSTAGRAM_INBOX_REPLY_BANNER": "Bu Instagram hesabı, yeni Instagram kanal gelen kutusuna taşındı. Tüm yeni mesajlar orada görünecektir. Artık bu konuşmadan mesaj gönderemeyeceksiniz.",
|
||||
"OLD_INSTAGRAM_INBOX_REPLY_BANNER": "Bu Instagram hesabı yeni Instagram kanal gelen kutusuna taşındı. Tüm yeni mesajlar orada görünecek. Bu sohbetten artık mesaj gönderemezsiniz.",
|
||||
"REPLYING_TO": "Cevap veriyorsun:",
|
||||
"REMOVE_SELECTION": "Seçimi Kaldır",
|
||||
"DOWNLOAD": "İndir",
|
||||
@@ -139,8 +139,8 @@
|
||||
}
|
||||
},
|
||||
"DELETE_CONVERSATION": {
|
||||
"TITLE": "#{conversationId} numaralı konuşmayı sil",
|
||||
"DESCRIPTION": "Bu konuşmayı silmek istediğinizden emin misiniz?",
|
||||
"TITLE": "#{conversationId} numaralı sohbeti sil",
|
||||
"DESCRIPTION": "Bu sohbeti silmek istediğinizden emin misiniz?",
|
||||
"CONFIRM": "Sil"
|
||||
},
|
||||
"CARD_CONTEXT_MENU": {
|
||||
@@ -159,7 +159,7 @@
|
||||
"ASSIGN_LABEL": "Etiket ata",
|
||||
"AGENTS_LOADING": "Temsilciler Yükleniyor...",
|
||||
"ASSIGN_TEAM": "Takım ata",
|
||||
"DELETE": "Konuşmayı sil",
|
||||
"DELETE": "Sohbeti sil",
|
||||
"OPEN_IN_NEW_TAB": "Yeni sekmede aç",
|
||||
"COPY_LINK": "Konuşma bağlantısını kopyala",
|
||||
"COPY_LINK_SUCCESS": "Konuşma bağlantısı panoya kopyalandı",
|
||||
@@ -244,8 +244,8 @@
|
||||
"ASSIGN_LABEL_SUCCESFUL": "Etiket başarıyla atandı",
|
||||
"ASSIGN_LABEL_FAILED": "Etiket ataması yapılamadı",
|
||||
"CHANGE_TEAM": "Takım değişti",
|
||||
"SUCCESS_DELETE_CONVERSATION": "Konuşma başarıyla silindi",
|
||||
"FAIL_DELETE_CONVERSATION": "Konuşma silinemedi! Tekrar deneyin",
|
||||
"SUCCESS_DELETE_CONVERSATION": "Sohbet başarıyla silindi",
|
||||
"FAIL_DELETE_CONVERSATION": "Sohbet silinemedi! Tekrar deneyin",
|
||||
"FILE_SIZE_LIMIT": "Dosya, {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE} MB ek dosya sınırını aşıyor",
|
||||
"MESSAGE_ERROR": "Bu mesaj gönderilemiyor, lütfen daha sonra tekrar deneyin",
|
||||
"SENT_BY": "Tarafından gönderildi:",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"GENERAL_SETTINGS": {
|
||||
"LIMIT_MESSAGES": {
|
||||
"CONVERSATION": "Konuşma limitini aştınız. Hacker planı yalnızca 500 konuşmaya izin vermektedir.",
|
||||
"INBOXES": "Gelen kutusu limitini aştınız. Hacker planı yalnızca web sitesi canlı sohbetini desteklemektedir. E-posta, WhatsApp gibi ek gelen kutuları ücretli bir plan gerektirir.",
|
||||
"CONVERSATION": "Sohbet sınırını aştınız. Hacker planı yalnızca 500 sohbete izin verir.",
|
||||
"INBOXES": "Gelen kutusu sınırını aştınız. Hacker planı yalnızca web sitesi canlı sohbetini destekler. E-posta, WhatsApp gibi ek gelen kutuları ücretli plan gerektirir.",
|
||||
"AGENTS": "Temsilci sınırını aştınız. Planınız yalnızca {allowedAgents} temsilciye izin veriyor.",
|
||||
"NON_ADMIN": "Tüm özellikleri kullanmaya devam etmek için lütfen yöneticinizle iletişime geçin ve planı yükseltin."
|
||||
},
|
||||
@@ -20,10 +20,10 @@
|
||||
"BUTTON_TEXT": "Hesabınızı Silin",
|
||||
"CONFIRM": {
|
||||
"TITLE": "Hesabı Sil",
|
||||
"MESSAGE": "Hesabınızı silmek geri alınamaz bir işlemdir. Kalıcı olarak silmek istediğinizi onaylamak için aşağıya hesap adınızı girin.",
|
||||
"MESSAGE": "Hesabınızı silmek geri alınamaz. Kalıcı olarak silmek istediğinizi onaylamak için aşağıya hesap adınızı girin.",
|
||||
"BUTTON_TEXT": "Sil",
|
||||
"DISMISS": "İptal Et",
|
||||
"PLACE_HOLDER": "Onaylamak için lütfen {accountName} yazın"
|
||||
"PLACE_HOLDER": "{accountName} yazarak onaylayın"
|
||||
},
|
||||
"SUCCESS": "Hesap silinmek üzere işaretlendi",
|
||||
"FAILURE": "Hesap silinemedi, tekrar deneyin!",
|
||||
@@ -45,11 +45,11 @@
|
||||
"NOTE": "API tabanlı bir entegrasyon oluşturuyorsanız bu kimlik gereklidir"
|
||||
},
|
||||
"AUTO_RESOLVE": {
|
||||
"TITLE": "Konuşmaları Otomatik Çöz",
|
||||
"NOTE": "Bu yapılandırma, belirli bir hareketsizlik süresinden sonra konuşmayı otomatik olarak çözümlemenize olanak tanır.",
|
||||
"TITLE": "Sohbetleri otomatik çöz",
|
||||
"NOTE": "Bu yapılandırma, belirli bir süre etkinlik olmadığında sohbeti otomatik olarak çözmenizi sağlar.",
|
||||
"DURATION": {
|
||||
"LABEL": "Hareketsizlik süresi",
|
||||
"HELP": "Konuşmanın otomatik olarak çözümlenmesinden önceki hareketsizlik süresi",
|
||||
"LABEL": "Etkinliksizlik süresi",
|
||||
"HELP": "Sohbetin otomatik olarak çözüleceği etkinliksizlik süresi",
|
||||
"PLACEHOLDER": "30",
|
||||
"ERROR": "Otomatik çözme süresi 10 dakika ile 999 gün arasında olmalıdır",
|
||||
"API": {
|
||||
@@ -59,8 +59,8 @@
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Özel otomatik çözüm mesajı",
|
||||
"PLACEHOLDER": "Konuşma, 15 günlük hareketsizlik nedeniyle sistem tarafından çözümlendi olarak işaretlendi",
|
||||
"HELP": "Konuşma otomatik olarak çözüldükten sonra müşteriye gönderilen mesaj"
|
||||
"PLACEHOLDER": "Sohbet, 15 gün etkinlik olmadığı için sistem tarafından çözüldü olarak işaretlendi",
|
||||
"HELP": "Sohbet otomatik olarak çözüldükten sonra müşteriye gönderilen mesaj"
|
||||
},
|
||||
"PREFERENCES": "Tercihler",
|
||||
"LABEL": {
|
||||
@@ -94,19 +94,19 @@
|
||||
},
|
||||
"AUTO_RESOLVE_IGNORE_WAITING": {
|
||||
"LABEL": "Yanıtsız sohbetleri hariç tut",
|
||||
"HELP": "Etkinleştirildiğinde, sistem hâlâ bir temsilcinin yanıtını bekleyen konuşmaları çözümlemeyi atlayacaktır."
|
||||
"HELP": "Etkinleştirildiğinde, sistem hâlâ temsilci yanıtı bekleyen sohbetleri çözmeyi atlayacaktır."
|
||||
},
|
||||
"AUDIO_TRANSCRIPTION": {
|
||||
"TITLE": "Sesli Mesajları Metne Çevir",
|
||||
"NOTE": "Konuşmalardaki sesli mesajları otomatik olarak metne çevirin. Bir sesli mesaj gönderildiğinde veya alındığında bir metin dökümü (transkript) oluşturun ve mesajın yanında görüntüleyin.",
|
||||
"TITLE": "Sesli Mesajları Yazıya Döktür",
|
||||
"NOTE": "Sohbetlerdeki sesli mesajları otomatik olarak yazıya dökün. Bir sesli mesaj gönderildiğinde veya alındığında bir metin dökümü oluşturun ve mesajın yanında gösterin.",
|
||||
"API": {
|
||||
"SUCCESS": "Sesli mesaj transkripsiyon ayarı başarıyla güncellendi",
|
||||
"ERROR": "Ses transkripsiyon ayarı güncellenirken hata oluştu"
|
||||
"ERROR": "Sesli yazıya dökme ayarı güncellenemedi"
|
||||
}
|
||||
},
|
||||
"AUTO_RESOLVE_DURATION": {
|
||||
"LABEL": "Çözümleme için hareketsizlik süresi",
|
||||
"HELP": "Bir konuşmanın, hiçbir etkinlik olmazsa otomatik olarak çözümlenmesi gereken süre",
|
||||
"LABEL": "Çözüm için etkinliksizlik süresi",
|
||||
"HELP": "Bir sohbette etkinlik yoksa otomatik çözüm için süre",
|
||||
"PLACEHOLDER": "30",
|
||||
"ERROR": "Otomatik çözme süresi 10 dakika ile 999 gün arasında olmalıdır",
|
||||
"API": {
|
||||
@@ -115,8 +115,8 @@
|
||||
},
|
||||
"UPDATE_BUTTON": "Güncelleme",
|
||||
"MESSAGE_LABEL": "Özel çözüm mesajı",
|
||||
"MESSAGE_PLACEHOLDER": "Konuşma, 15 günlük hareketsizlik nedeniyle sistem tarafından çözümlendi olarak işaretlendi",
|
||||
"MESSAGE_HELP": "Bu mesaj, bir konuşma hareketsizlik nedeniyle sistem tarafından otomatik olarak çözümlendiğinde müşteriye gönderilir."
|
||||
"MESSAGE_PLACEHOLDER": "Sohbet, 15 gün etkinlik olmadığı için sistem tarafından çözüldü olarak işaretlendi",
|
||||
"MESSAGE_HELP": "Bu mesaj, bir sohbet sistem tarafından etkinliksizlik nedeniyle otomatik olarak çözüldüğünde müşteriye gönderilir."
|
||||
},
|
||||
"FEATURES": {
|
||||
"INBOUND_EMAIL_ENABLED": "Hesabınız için e-posta ile iletişim devre dışı bırakıldı.",
|
||||
|
||||
@@ -51,10 +51,10 @@
|
||||
"INSTAGRAM": {
|
||||
"CONTINUE_WITH_INSTAGRAM": "Instagram ile devam et",
|
||||
"CONNECT_YOUR_INSTAGRAM_PROFILE": "Instagram profilinizi bağlayın",
|
||||
"HELP": "Instagram profilinizi kanal olarak eklemek için, 'Instagram ile Devam Et' seçeneğine tıklayarak Instagram Profilinizin kimliğini doğrulamanız gerekmektedir. ",
|
||||
"HELP": "Bu Instagram hesabı yeni Instagram kanal gelen kutusuna taşındı. Artık bu gelen kutusundan Instagram mesajı gönderip alamayacaksınız ",
|
||||
"ERROR_MESSAGE": "Instagram'a bağlanırken bir hata oluştu, lütfen tekrar deneyin",
|
||||
"ERROR_AUTH": "Instagram'a bağlanırken bir hata oluştu, lütfen tekrar deneyin",
|
||||
"NEW_INBOX_SUGGESTION": "Bu Instagram hesabı daha önce farklı bir gelen kutusuna bağlıydı ve şimdi buraya taşındı. Tüm yeni mesajlar burada görünecektir. Eski gelen kutusu, bu hesap için artık mesaj gönderemeyecek veya alamayacaktır.",
|
||||
"NEW_INBOX_SUGGESTION": "Bu Instagram hesabı daha önce farklı bir gelen kutusuna bağlıydı ve şimdi buraya taşındı. Tüm yeni mesajlar burada görünecek. Eski gelen kutusu artık bu hesap için mesaj gönderip alamayacak.",
|
||||
"DUPLICATE_INBOX_BANNER": "Bu Instagram hesabı yeni Instagram kanal gelen kutusuna taşındı. Artık bu gelen kutusundan Instagram mesajı gönderip alamayacaksınız."
|
||||
},
|
||||
"TWITTER": {
|
||||
@@ -341,9 +341,9 @@
|
||||
"TWILIO_STATUS_URL_TITLE": "Twilio Status Callback URL",
|
||||
"TWILIO_STATUS_URL_SUBTITLE": "Configure this URL as the Status Callback URL on your Twilio phone number."
|
||||
},
|
||||
"SUBMIT_BUTTON": "Ses Kanalı Oluştur",
|
||||
"SUBMIT_BUTTON": "Create Voice Channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "Ses kanalı oluşturulamadı"
|
||||
"ERROR_MESSAGE": "We were not able to create the voice channel"
|
||||
}
|
||||
},
|
||||
"API_CHANNEL": {
|
||||
@@ -437,7 +437,7 @@
|
||||
},
|
||||
"FACEBOOK": {
|
||||
"TITLE": "Facebook\n",
|
||||
"DESCRIPTION": "Facebook sayfanızı bağlayın"
|
||||
"DESCRIPTION": "Connect your Facebook page"
|
||||
},
|
||||
"WHATSAPP": {
|
||||
"TITLE": "WhatsApp",
|
||||
@@ -445,31 +445,31 @@
|
||||
},
|
||||
"EMAIL": {
|
||||
"TITLE": "E-Posta",
|
||||
"DESCRIPTION": "Gmail, Outlook veya diğer sağlayıcılarla bağlantı kurun"
|
||||
"DESCRIPTION": "Connect with Gmail, Outlook, or other providers"
|
||||
},
|
||||
"SMS": {
|
||||
"TITLE": "SMS",
|
||||
"DESCRIPTION": "SMS kanalını Twilio veya Bandwidth ile entegre edin"
|
||||
"DESCRIPTION": "Integrate SMS channel with Twilio or bandwidth"
|
||||
},
|
||||
"API": {
|
||||
"TITLE": "API",
|
||||
"DESCRIPTION": "API'mizi kullanarak özel bir kanal oluşturun"
|
||||
"DESCRIPTION": "Make a custom channel using our API"
|
||||
},
|
||||
"TELEGRAM": {
|
||||
"TITLE": "Telegram",
|
||||
"DESCRIPTION": "Bot token kullanarak Telegram kanalını yapılandırın"
|
||||
"DESCRIPTION": "Configure Telegram channel using Bot token"
|
||||
},
|
||||
"LINE": {
|
||||
"TITLE": "Line",
|
||||
"DESCRIPTION": "Line kanalınızı entegre edin"
|
||||
"DESCRIPTION": "Integrate your Line channel"
|
||||
},
|
||||
"INSTAGRAM": {
|
||||
"TITLE": "Instagram",
|
||||
"DESCRIPTION": "Instagram hesabınızı bağlayın"
|
||||
"DESCRIPTION": "Connect your instagram account"
|
||||
},
|
||||
"VOICE": {
|
||||
"TITLE": "Ses",
|
||||
"DESCRIPTION": "Twilio Voice ile entegre edin"
|
||||
"DESCRIPTION": "Integrate with Twilio Voice"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -526,9 +526,9 @@
|
||||
"BUTTON_TEXT": "Beni oraya götür",
|
||||
"MORE_SETTINGS": "Daha fazla ayar",
|
||||
"WEBSITE_SUCCESS": "Bir web sitesi kanalı oluşturmayı başarıyla tamamladınız. Aşağıda gösterilen kodu kopyalayın ve web sitenize yapıştırın. Bir müşteri canlı sohbeti bir dahaki sefere kullandığında, konuşma otomatik olarak gelen kutunuzda görünecektir.",
|
||||
"WHATSAPP_QR_INSTRUCTION": "WhatsApp gelen kutunuzu hızlıca test etmek için yukarıdaki QR kodu tarayın",
|
||||
"MESSENGER_QR_INSTRUCTION": "Facebook Messenger gelen kutunuzu hızlıca test etmek için yukarıdaki QR kodu tarayın",
|
||||
"TELEGRAM_QR_INSTRUCTION": "Telegram gelen kutunuzu hızlıca test etmek için yukarıdaki QR kodu tarayın"
|
||||
"WHATSAPP_QR_INSTRUCTION": "Scan the QR code above to quickly test your WhatsApp inbox",
|
||||
"MESSENGER_QR_INSTRUCTION": "Scan the QR code above to quickly test your Facebook Messenger inbox",
|
||||
"TELEGRAM_QR_INSTRUCTION": "Scan the QR code above to quickly test your Telegram inbox"
|
||||
},
|
||||
"REAUTH": "Yeniden yetkilendir",
|
||||
"VIEW": "Görünüm",
|
||||
@@ -607,37 +607,37 @@
|
||||
"BUSINESS_HOURS": "İş Saatleri",
|
||||
"WIDGET_BUILDER": "Widget Oluşturucu",
|
||||
"BOT_CONFIGURATION": "Bot Yapılandırma",
|
||||
"ACCOUNT_HEALTH": "Hesap Sağlığı",
|
||||
"ACCOUNT_HEALTH": "Account Health",
|
||||
"CSAT": "CSAT"
|
||||
},
|
||||
"ACCOUNT_HEALTH": {
|
||||
"TITLE": "WhatsApp hesabınızı yönetin",
|
||||
"DESCRIPTION": "WhatsApp hesap durumunuzu, mesajlaşma limitlerinizi ve kalitesini inceleyin. Gerekirse ayarları güncelleyin veya sorunları çözün",
|
||||
"GO_TO_SETTINGS": "Meta Business Manager'a gidin",
|
||||
"NO_DATA": "Sağlık verisi mevcut değil",
|
||||
"TITLE": "Manage your WhatsApp account",
|
||||
"DESCRIPTION": "Review your WhatsApp account status, messaging limits, and quality. Update settings or resolve issues if needed",
|
||||
"GO_TO_SETTINGS": "Go to Meta Business Manager",
|
||||
"NO_DATA": "Health data is not available",
|
||||
"FIELDS": {
|
||||
"DISPLAY_PHONE_NUMBER": {
|
||||
"LABEL": "Telefon numarasını göster",
|
||||
"TOOLTIP": "Müşterilere gösterilen telefon numarası"
|
||||
"LABEL": "Display phone number",
|
||||
"TOOLTIP": "Phone number displayed to customers"
|
||||
},
|
||||
"VERIFIED_NAME": {
|
||||
"LABEL": "İşletme adı",
|
||||
"TOOLTIP": "WhatsApp tarafından onaylanmış işletme adı"
|
||||
"LABEL": "Business name",
|
||||
"TOOLTIP": "Business name verified by WhatsApp"
|
||||
},
|
||||
"DISPLAY_NAME_STATUS": {
|
||||
"LABEL": "Görünen adın durumu",
|
||||
"TOOLTIP": "İşletme adı doğrulamanızın durumu"
|
||||
"LABEL": "Display name status",
|
||||
"TOOLTIP": "Status of your business name verification"
|
||||
},
|
||||
"QUALITY_RATING": {
|
||||
"LABEL": "Kalite puanı",
|
||||
"TOOLTIP": "Hesabınız için WhatsApp kalite puanı"
|
||||
"LABEL": "Quality rating",
|
||||
"TOOLTIP": "WhatsApp quality rating for your account"
|
||||
},
|
||||
"MESSAGING_LIMIT_TIER": {
|
||||
"LABEL": "Mesajlaşma limiti seviyesi",
|
||||
"TOOLTIP": "Hesabınız için günlük mesajlaşma limiti"
|
||||
"LABEL": "Messaging limit tier",
|
||||
"TOOLTIP": "Daily messaging limit for your account"
|
||||
},
|
||||
"ACCOUNT_MODE": {
|
||||
"LABEL": "Hesap modu",
|
||||
"LABEL": "Account mode",
|
||||
"TOOLTIP": "Current operating mode of your WhatsApp account"
|
||||
}
|
||||
},
|
||||
@@ -784,7 +784,7 @@
|
||||
},
|
||||
"CSAT": {
|
||||
"TITLE": "CSAT'yi etkinleştir",
|
||||
"SUBTITLE": "Müşterilerin destek deneyimleri hakkındaki düşüncelerini anlamak için konuşma sonunda CSAT anketlerini (Müşteri Memnuniyeti Anketleri) otomatik olarak tetikleyin. Memnuniyet eğilimlerini takip edin ve zaman içinde iyileştirme alanlarını belirleyin.",
|
||||
"SUBTITLE": "Müşterilerin destek deneyimleri hakkında ne hissettiklerini anlamak için sohbetlerin sonunda otomatik olarak CSAT anketleri başlatın. Memnuniyet eğilimlerini takip edin ve zaman içinde iyileştirme alanlarını belirleyin.",
|
||||
"DISPLAY_TYPE": {
|
||||
"LABEL": "Görüntüleme türü"
|
||||
},
|
||||
@@ -802,7 +802,7 @@
|
||||
},
|
||||
"SELECT_PLACEHOLDER": "etiketleri seç"
|
||||
},
|
||||
"NOTE": "Not: CSAT anketleri, konuşma başına yalnızca bir kez gönderilir",
|
||||
"NOTE": "Not: CSAT anketleri her sohbet için yalnızca bir kez gönderilir",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "CSAT ayarları başarıyla güncellendi",
|
||||
"ERROR_MESSAGE": "CSAT ayarları güncellenemedi. Lütfen daha sonra tekrar deneyin."
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"SUBSCRIPTIONS": {
|
||||
"LABEL": "Olaylar",
|
||||
"EVENTS": {
|
||||
"CONVERSATION_CREATED": "Konuşma Oluşturuldu",
|
||||
"CONVERSATION_CREATED": "Görüşme Oluşturuldu",
|
||||
"CONVERSATION_STATUS_CHANGED": "Görüşme Durumu Değişti",
|
||||
"CONVERSATION_UPDATED": "Görüşme Güncellendi",
|
||||
"MESSAGE_CREATED": "Mesaj Oluşturuldu",
|
||||
@@ -42,8 +42,8 @@
|
||||
"WEBWIDGET_TRIGGERED": "Kullanıcı tarafından canlı sohbet widget'ı açıldı",
|
||||
"CONTACT_CREATED": "Kişi Oluşturuldu",
|
||||
"CONTACT_UPDATED": "Kişi Güncellendi",
|
||||
"CONVERSATION_TYPING_ON": "Konuşmada Yazıyor Açık",
|
||||
"CONVERSATION_TYPING_OFF": "Konuşmada Yazıyor Kapalı"
|
||||
"CONVERSATION_TYPING_ON": "Sohbet Yazıyor Açık",
|
||||
"CONVERSATION_TYPING_OFF": "Sohbet Yazıyor Kapalı"
|
||||
}
|
||||
},
|
||||
"END_POINT": {
|
||||
@@ -327,7 +327,7 @@
|
||||
},
|
||||
"CTA": {
|
||||
"TITLE": "Linear'a bağlan",
|
||||
"AGENT_DESCRIPTION": "Linear çalışma alanı bağlı değil. Bu entegrasyonu kullanmak için yöneticinizden bir çalışma alanı bağlamasını isteyin.",
|
||||
"AGENT_DESCRIPTION": "Doğrusal çalışma alanı bağlı değil. Bu entegrasyonu kullanmak için yöneticinizden bir çalışma alanı bağlamasını isteyin.",
|
||||
"DESCRIPTION": "Linear çalışma alanı bağlı değil. Bu entegrasyonu kullanmak için çalışma alanınızı bağlamak üzere aşağıdaki düğmeye tıklayın.",
|
||||
"BUTTON_TEXT": "Linear çalışma alanını bağlayın"
|
||||
}
|
||||
@@ -348,7 +348,7 @@
|
||||
"TITLE": "Copilot",
|
||||
"TRY_THESE_PROMPTS": "Try these prompts",
|
||||
"PANEL_TITLE": "Copilot ile başlayın",
|
||||
"KICK_OFF_MESSAGE": "Hızlı bir özet mi gerekiyor, geçmiş konuşmaları mı kontrol etmek istiyorsunuz, yoksa daha iyi bir yanıt mı tasarlamak istiyorsunuz? Copilot işleri hızlandırmak için burada.",
|
||||
"KICK_OFF_MESSAGE": "Hızlı bir özet mi lazım, geçmiş sohbetleri mi kontrol etmek istiyorsunuz ya da daha iyi bir yanıt mı yazmak istiyorsunuz? Copilot işleri hızlandırmak için burada.",
|
||||
"SEND_MESSAGE": "Mesajı Gönder...",
|
||||
"EMPTY_MESSAGE": "Yanıt oluşturulurken bir hata oluştu. Lütfen tekrar deneyin.",
|
||||
"LOADER": "Captain is thinking",
|
||||
@@ -371,8 +371,8 @@
|
||||
"CONTENT": "Sohbetin müşterinin ihtiyaçlarını ne kadar karşıladığını gözden geçirin. Ton, açıklık ve etkililik açısından 5 üzerinden bir puan verin."
|
||||
},
|
||||
"HIGH_PRIORITY": {
|
||||
"LABEL": "Yüksek öncelikli konuşmalar",
|
||||
"CONTENT": "Tüm yüksek öncelikli açık konuşmaların bir özetini verin. Konuşma kimliği, müşteri adı (varsa), son mesaj içeriği ve atanan temsilciyi ekleyin. Gerekirse duruma göre gruplayın."
|
||||
"LABEL": "Yüksek öncelikli sohbetler",
|
||||
"CONTENT": "Tüm yüksek öncelikli açık sohbetlerin bir özetini verin. Sohbet kimliği, müşteri adı (varsa), son mesaj içeriği ve atanan temsilciyi ekleyin. Gerekirse duruma göre gruplayın."
|
||||
},
|
||||
"LIST_CONTACTS": {
|
||||
"LABEL": "Kişileri listele",
|
||||
@@ -383,7 +383,7 @@
|
||||
"PLAYGROUND": {
|
||||
"USER": "Sen",
|
||||
"ASSISTANT": "Assistant",
|
||||
"MESSAGE_PLACEHOLDER": "Mesajınızı yazın...",
|
||||
"MESSAGE_PLACEHOLDER": "Mesajınız...",
|
||||
"HEADER": "Oyun Alanı",
|
||||
"DESCRIPTION": "Bu oyun alanını asistanınıza mesaj göndermek ve yanıtlarının doğru, hızlı ve beklediğiniz tonda olup olmadığını kontrol etmek için kullanın.",
|
||||
"CREDIT_NOTE": "Buradan gönderilen mesajlar Captain kredilerinize sayılacaktır."
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
},
|
||||
"LABEL_REPORTS": {
|
||||
"HEADER": "Etiketler Genel Bakış",
|
||||
"DESCRIPTION": "Etiket performansını; konuşmalar, yanıt süreleri, çözüm süreleri ve çözümlenen vakalar dahil olmak üzere temel metriklerle takip edin. Ayrıntılı analizler için bir etiket adına tıklayın.",
|
||||
"DESCRIPTION": "Konuşmalar, yanıt süreleri, çözüm süreleri ve çözülmüş vakalar gibi temel ölçümlerle etiket performansını takip edin. Ayrıntılı içgörüler için bir etiket adına tıklayın.",
|
||||
"LOADING_CHART": "Grafik verileri yükleniyor...",
|
||||
"NO_ENOUGH_DATA": "Rapor oluşturmak için yeterli veri yok, Lütfen daha sonra tekrar deneyin.",
|
||||
"DOWNLOAD_LABEL_REPORTS": "Etiket raporlarını indir",
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Видалити",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Видалити контакт"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "حذف کریں۔",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "کانٹیکٹ حذف کریں۔"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Delete",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Delete contact"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "Xoá",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "Xoá liên hệ"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} 已选择",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "全选 ({count})",
|
||||
"DELETE_CONTACTS": "删除",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "删除联系人"
|
||||
}
|
||||
"SELECT_ALL": "全选 ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -579,18 +579,7 @@
|
||||
"NO_LABELS_FOUND": "No labels available yet.",
|
||||
"SELECTED_COUNT": "{count} selected",
|
||||
"CLEAR_SELECTION": "Clear selection",
|
||||
"SELECT_ALL": "Select all ({count})",
|
||||
"DELETE_CONTACTS": "刪除",
|
||||
"DELETE_SUCCESS": "Contacts deleted successfully.",
|
||||
"DELETE_FAILED": "Failed to delete contacts.",
|
||||
"DELETE_DIALOG": {
|
||||
"TITLE": "Delete selected contacts",
|
||||
"SINGULAR_TITLE": "Delete selected contact",
|
||||
"DESCRIPTION": "This will permanently delete {count} selected contacts. This action cannot be undone.",
|
||||
"SINGULAR_DESCRIPTION": "This will permanently delete the selected contact. This action cannot be undone.",
|
||||
"CONFIRM_MULTIPLE": "Delete contacts",
|
||||
"CONFIRM_SINGLE": "刪除聯絡人"
|
||||
}
|
||||
"SELECT_ALL": "Select all ({count})"
|
||||
},
|
||||
"COMPOSE_NEW_CONVERSATION": {
|
||||
"CONTACT_SEARCH": {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
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();
|
||||
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 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 => {
|
||||
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 || !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"
|
||||
:show-know-more="false"
|
||||
:back-url="{ name: 'captain_assistants_index' }"
|
||||
>
|
||||
<template #body>
|
||||
<div v-if="!isAssistantAvailable">
|
||||
{{ t('CAPTAIN.ASSISTANTS.EDIT.NOT_FOUND') }}
|
||||
</div>
|
||||
<div v-else class="flex gap-4 h-full">
|
||||
<div class="flex-1 lg:overflow-auto pr-4 h-full md:h-auto">
|
||||
<EditAssistantForm
|
||||
:assistant="assistant"
|
||||
mode="edit"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-[400px] hidden lg:block h-full">
|
||||
<AssistantPlayground :assistant-id="Number(assistantId)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</PageLayout>
|
||||
</template>
|
||||
@@ -1,57 +1,91 @@
|
||||
<script setup>
|
||||
import { computed, ref, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
import { computed, onMounted, ref, nextTick } from 'vue';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
|
||||
import AssistantCard from 'dashboard/components-next/captain/assistant/AssistantCard.vue';
|
||||
import DeleteDialog from 'dashboard/components-next/captain/pageComponents/DeleteDialog.vue';
|
||||
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 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';
|
||||
|
||||
const { isOnChatwootCloud } = useAccount();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const store = useStore();
|
||||
const dialogType = ref('');
|
||||
const uiFlags = useMapGetter('captainAssistants/getUIFlags');
|
||||
const assistants = useMapGetter('captainAssistants/getRecords');
|
||||
const isFetching = computed(() => uiFlags.value.fetchingList);
|
||||
|
||||
const selectedAssistant = ref(null);
|
||||
const deleteAssistantDialog = ref(null);
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteAssistantDialog.value.dialogRef.open();
|
||||
};
|
||||
|
||||
const createAssistantDialog = ref(null);
|
||||
const router = useRouter();
|
||||
|
||||
const handleCreate = () => {
|
||||
dialogType.value = 'create';
|
||||
nextTick(() => createAssistantDialog.value.dialogRef.open());
|
||||
};
|
||||
|
||||
const handleEdit = () => {
|
||||
router.push({
|
||||
name: 'captain_assistants_edit',
|
||||
params: { assistantId: selectedAssistant.value.id },
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewConnectedInboxes = () => {
|
||||
router.push({
|
||||
name: 'captain_assistants_inboxes_index',
|
||||
params: { assistantId: selectedAssistant.value.id },
|
||||
});
|
||||
};
|
||||
|
||||
const handleAction = ({ action, id }) => {
|
||||
selectedAssistant.value = assistants.value.find(
|
||||
assistant => id === assistant.id
|
||||
);
|
||||
nextTick(() => {
|
||||
if (action === 'delete') {
|
||||
handleDelete();
|
||||
}
|
||||
if (action === 'edit') {
|
||||
handleEdit();
|
||||
}
|
||||
if (action === 'viewConnectedInboxes') {
|
||||
handleViewConnectedInboxes();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateClose = () => {
|
||||
dialogType.value = '';
|
||||
selectedAssistant.value = null;
|
||||
};
|
||||
|
||||
const handleAfterCreate = newAssistant => {
|
||||
// Navigate directly to documents page with the new assistant ID
|
||||
if (newAssistant?.id) {
|
||||
router.push({
|
||||
name: 'captain_assistants_responses_index',
|
||||
params: {
|
||||
accountId: router.currentRoute.value.params.accountId,
|
||||
assistantId: newAssistant.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
onMounted(() => store.dispatch('captainAssistants/get'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageLayout
|
||||
:header-title="$t('CAPTAIN.ASSISTANTS.HEADER')"
|
||||
:button-label="$t('CAPTAIN.ASSISTANTS.ADD_NEW')"
|
||||
:button-policy="['administrator']"
|
||||
:show-pagination-footer="false"
|
||||
:is-fetching="isFetching"
|
||||
:is-empty="!assistants.length"
|
||||
:feature-flag="FEATURE_FLAGS.CAPTAIN"
|
||||
is-empty
|
||||
@click="handleCreate"
|
||||
>
|
||||
<template #knowMore>
|
||||
@@ -73,13 +107,36 @@ const handleAfterCreate = newAssistant => {
|
||||
<CaptainPaywall />
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<LimitBanner class="mb-5" />
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<AssistantCard
|
||||
v-for="assistant in assistants"
|
||||
:id="assistant.id"
|
||||
:key="assistant.id"
|
||||
:name="assistant.name"
|
||||
:description="assistant.description"
|
||||
:updated-at="assistant.updated_at || assistant.created_at"
|
||||
:created-at="assistant.created_at"
|
||||
@action="handleAction"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<DeleteDialog
|
||||
v-if="selectedAssistant"
|
||||
ref="deleteAssistantDialog"
|
||||
:entity="selectedAssistant"
|
||||
type="Assistants"
|
||||
/>
|
||||
|
||||
<CreateAssistantDialog
|
||||
v-if="dialogType"
|
||||
ref="createAssistantDialog"
|
||||
:type="dialogType"
|
||||
:selected-assistant="selectedAssistant"
|
||||
@close="handleCreateClose"
|
||||
@created="handleAfterCreate"
|
||||
/>
|
||||
</PageLayout>
|
||||
</template>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import SettingsPageLayout from 'dashboard/components-next/captain/SettingsPageLayout.vue';
|
||||
import SettingsHeader from 'dashboard/components-next/captain/pageComponents/settings/SettingsHeader.vue';
|
||||
import SuggestedRules from 'dashboard/components-next/captain/assistant/SuggestedRules.vue';
|
||||
import AddNewRulesInput from 'dashboard/components-next/captain/assistant/AddNewRulesInput.vue';
|
||||
@@ -23,26 +23,29 @@ const route = useRoute();
|
||||
const store = useStore();
|
||||
const { uiSettings, updateUISettings } = useUISettings();
|
||||
|
||||
const assistantId = route.params.assistantId;
|
||||
const uiFlags = useMapGetter('captainAssistants/getUIFlags');
|
||||
const assistantId = computed(() => Number(route.params.assistantId));
|
||||
const isFetching = computed(() => uiFlags.value.fetchingItem);
|
||||
const assistant = computed(() =>
|
||||
store.getters['captainAssistants/getRecord'](assistantId.value)
|
||||
store.getters['captainAssistants/getRecord'](Number(assistantId))
|
||||
);
|
||||
|
||||
const searchQuery = ref('');
|
||||
const newInlineRule = ref('');
|
||||
const newDialogRule = ref('');
|
||||
|
||||
const guardrailsContent = computed(() => assistant.value?.guardrails || []);
|
||||
const breadcrumbItems = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: t('CAPTAIN.ASSISTANTS.SETTINGS.BREADCRUMB.ASSISTANT'),
|
||||
routeName: 'captain_assistants_index',
|
||||
},
|
||||
{ label: assistant.value?.name, routeName: 'captain_assistants_edit' },
|
||||
{ label: t('CAPTAIN.ASSISTANTS.GUARDRAILS.BREADCRUMB.TITLE') },
|
||||
];
|
||||
});
|
||||
|
||||
const backUrl = computed(() => ({
|
||||
name: 'captain_assistants_settings_index',
|
||||
params: {
|
||||
accountId: route.params.accountId,
|
||||
assistantId: assistantId.value,
|
||||
},
|
||||
}));
|
||||
const guardrailsContent = computed(() => assistant.value?.guardrails || []);
|
||||
|
||||
const displayGuardrails = computed(() =>
|
||||
guardrailsContent.value.map((c, idx) => ({ id: idx, content: c }))
|
||||
@@ -110,7 +113,7 @@ const selectedCountLabel = computed(() => {
|
||||
|
||||
const saveGuardrails = async list => {
|
||||
await store.dispatch('captainAssistants/update', {
|
||||
id: assistantId.value,
|
||||
id: assistantId,
|
||||
assistant: { guardrails: list },
|
||||
});
|
||||
};
|
||||
@@ -173,13 +176,9 @@ const addAllExample = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageLayout
|
||||
:header-title="$t('CAPTAIN.ASSISTANTS.GUARDRAILS.TITLE')"
|
||||
<SettingsPageLayout
|
||||
:breadcrumb-items="breadcrumbItems"
|
||||
:is-fetching="isFetching"
|
||||
:back-url="backUrl"
|
||||
:show-know-more="false"
|
||||
:show-pagination-footer="false"
|
||||
:show-assistant-switcher="false"
|
||||
>
|
||||
<template #body>
|
||||
<SettingsHeader
|
||||
@@ -298,5 +297,5 @@ const addAllExample = () => {
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</PageLayout>
|
||||
</SettingsPageLayout>
|
||||
</template>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import SettingsPageLayout from 'dashboard/components-next/captain/SettingsPageLayout.vue';
|
||||
import SettingsHeader from 'dashboard/components-next/captain/pageComponents/settings/SettingsHeader.vue';
|
||||
import SuggestedRules from 'dashboard/components-next/captain/assistant/SuggestedRules.vue';
|
||||
import AddNewRulesInput from 'dashboard/components-next/captain/assistant/AddNewRulesInput.vue';
|
||||
@@ -23,29 +23,32 @@ const route = useRoute();
|
||||
const store = useStore();
|
||||
const { uiSettings, updateUISettings } = useUISettings();
|
||||
|
||||
const assistantId = route.params.assistantId;
|
||||
const uiFlags = useMapGetter('captainAssistants/getUIFlags');
|
||||
const assistantId = computed(() => Number(route.params.assistantId));
|
||||
const isFetching = computed(() => uiFlags.value.fetchingItem);
|
||||
const assistant = computed(() =>
|
||||
store.getters['captainAssistants/getRecord'](assistantId.value)
|
||||
store.getters['captainAssistants/getRecord'](Number(assistantId))
|
||||
);
|
||||
|
||||
const searchQuery = ref('');
|
||||
const newInlineRule = ref('');
|
||||
const newDialogRule = ref('');
|
||||
|
||||
const breadcrumbItems = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: t('CAPTAIN.ASSISTANTS.SETTINGS.BREADCRUMB.ASSISTANT'),
|
||||
routeName: 'captain_assistants_index',
|
||||
},
|
||||
{ label: assistant.value?.name, routeName: 'captain_assistants_edit' },
|
||||
{ label: t('CAPTAIN.ASSISTANTS.RESPONSE_GUIDELINES.TITLE') },
|
||||
];
|
||||
});
|
||||
|
||||
const guidelinesContent = computed(
|
||||
() => assistant.value?.response_guidelines || []
|
||||
);
|
||||
|
||||
const backUrl = computed(() => ({
|
||||
name: 'captain_assistants_settings_index',
|
||||
params: {
|
||||
accountId: route.params.accountId,
|
||||
assistantId: assistantId.value,
|
||||
},
|
||||
}));
|
||||
|
||||
const displayGuidelines = computed(() =>
|
||||
guidelinesContent.value.map((c, idx) => ({ id: idx, content: c }))
|
||||
);
|
||||
@@ -116,7 +119,7 @@ const selectedCountLabel = computed(() => {
|
||||
|
||||
const saveGuidelines = async list => {
|
||||
await store.dispatch('captainAssistants/update', {
|
||||
id: assistantId.value,
|
||||
id: assistantId,
|
||||
assistant: { response_guidelines: list },
|
||||
});
|
||||
};
|
||||
@@ -180,13 +183,9 @@ const addAllExample = async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageLayout
|
||||
:header-title="$t('CAPTAIN.ASSISTANTS.RESPONSE_GUIDELINES.TITLE')"
|
||||
<SettingsPageLayout
|
||||
:breadcrumb-items="breadcrumbItems"
|
||||
:is-fetching="isFetching"
|
||||
:back-url="backUrl"
|
||||
:show-know-more="false"
|
||||
:show-pagination-footer="false"
|
||||
:show-assistant-switcher="false"
|
||||
>
|
||||
<template #body>
|
||||
<SettingsHeader
|
||||
@@ -322,5 +321,5 @@ const addAllExample = async () => {
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</PageLayout>
|
||||
</SettingsPageLayout>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, nextTick } from 'vue';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store';
|
||||
import { computed, onBeforeMount, onMounted, ref, nextTick } from 'vue';
|
||||
import {
|
||||
useMapGetter,
|
||||
useStore,
|
||||
useStoreGetters,
|
||||
} from 'dashboard/composables/store';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
|
||||
import BackButton from 'dashboard/components/widgets/BackButton.vue';
|
||||
import DeleteDialog from 'dashboard/components-next/captain/pageComponents/DeleteDialog.vue';
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import ConnectInboxDialog from 'dashboard/components-next/captain/pageComponents/inbox/ConnectInboxDialog.vue';
|
||||
@@ -47,7 +52,13 @@ const handleCreateClose = () => {
|
||||
selectedInbox.value = null;
|
||||
};
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const assistantId = Number(route.params.assistantId);
|
||||
const assistant = computed(() =>
|
||||
getters['captainAssistants/getRecord'].value(assistantId)
|
||||
);
|
||||
onBeforeMount(() => store.dispatch('captainAssistants/show', assistantId));
|
||||
|
||||
onMounted(() =>
|
||||
store.dispatch('captainInboxes/get', {
|
||||
assistantId: assistantId,
|
||||
@@ -57,16 +68,27 @@ onMounted(() =>
|
||||
|
||||
<template>
|
||||
<PageLayout
|
||||
:header-title="$t('CAPTAIN.INBOXES.HEADER')"
|
||||
:button-label="$t('CAPTAIN.INBOXES.ADD_NEW')"
|
||||
:button-policy="['administrator']"
|
||||
:is-fetching="isFetchingAssistant || isFetching"
|
||||
:is-empty="!captainInboxes.length"
|
||||
:show-pagination-footer="false"
|
||||
:show-know-more="false"
|
||||
:feature-flag="FEATURE_FLAGS.CAPTAIN"
|
||||
@click="handleCreate"
|
||||
>
|
||||
<template v-if="!isFetchingAssistant" #headerTitle>
|
||||
<div class="flex flex-row items-center gap-4">
|
||||
<BackButton compact />
|
||||
<span
|
||||
class="flex items-center gap-1 text-lg font-medium text-n-slate-12"
|
||||
>
|
||||
{{ assistant.name }}
|
||||
<span class="i-lucide-chevron-right text-xl text-n-slate-10" />
|
||||
{{ $t('CAPTAIN.INBOXES.HEADER') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #emptyState>
|
||||
<InboxPageEmptyState @click="handleCreate" />
|
||||
</template>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import AssistantPlayground from 'dashboard/components-next/captain/assistant/AssistantPlayground.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const assistantId = computed(() => Number(route.params.assistantId));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageLayout
|
||||
:header-title="$t('CAPTAIN.PLAYGROUND.HEADER')"
|
||||
show-assistant-switcher
|
||||
:show-pagination-footer="false"
|
||||
class="h-full"
|
||||
>
|
||||
<template #body>
|
||||
<div class="flex flex-col h-full">
|
||||
<AssistantPlayground :assistant-id="assistantId" />
|
||||
</div>
|
||||
</template>
|
||||
</PageLayout>
|
||||
</template>
|
||||
@@ -10,7 +10,7 @@ import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import SettingsPageLayout from 'dashboard/components-next/captain/SettingsPageLayout.vue';
|
||||
import SettingsHeader from 'dashboard/components-next/captain/pageComponents/settings/SettingsHeader.vue';
|
||||
import SuggestedScenarios from 'dashboard/components-next/captain/assistant/SuggestedRules.vue';
|
||||
import ScenariosCard from 'dashboard/components-next/captain/assistant/ScenariosCard.vue';
|
||||
@@ -22,14 +22,28 @@ const route = useRoute();
|
||||
const store = useStore();
|
||||
const { uiSettings, updateUISettings } = useUISettings();
|
||||
const { formatMessage } = useMessageFormatter();
|
||||
const assistantId = computed(() => Number(route.params.assistantId));
|
||||
const assistantId = route.params.assistantId;
|
||||
|
||||
const uiFlags = useMapGetter('captainScenarios/getUIFlags');
|
||||
const isFetching = computed(() => uiFlags.value.fetchingList);
|
||||
const assistant = computed(() =>
|
||||
store.getters['captainAssistants/getRecord'](Number(assistantId))
|
||||
);
|
||||
const scenarios = useMapGetter('captainScenarios/getRecords');
|
||||
|
||||
const searchQuery = ref('');
|
||||
|
||||
const breadcrumbItems = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: t('CAPTAIN.ASSISTANTS.SETTINGS.BREADCRUMB.ASSISTANT'),
|
||||
routeName: 'captain_assistants_index',
|
||||
},
|
||||
{ label: assistant.value?.name, routeName: 'captain_assistants_edit' },
|
||||
{ label: t('CAPTAIN.ASSISTANTS.SCENARIOS.BREADCRUMB.TITLE') },
|
||||
];
|
||||
});
|
||||
|
||||
const LINK_INSTRUCTION_CLASS =
|
||||
'[&_a[href^="tool://"]]:text-n-iris-11 [&_a:not([href^="tool://"])]:text-n-slate-12 [&_a]:pointer-events-none [&_a]:cursor-default';
|
||||
|
||||
@@ -105,7 +119,7 @@ const updateScenario = async scenario => {
|
||||
try {
|
||||
await store.dispatch('captainScenarios/update', {
|
||||
id: scenario.id,
|
||||
assistantId: assistantId.value,
|
||||
assistantId: route.params.assistantId,
|
||||
...scenario,
|
||||
tools: getToolsFromInstruction(scenario.instruction),
|
||||
});
|
||||
@@ -122,7 +136,7 @@ const deleteScenario = async id => {
|
||||
try {
|
||||
await store.dispatch('captainScenarios/delete', {
|
||||
id,
|
||||
assistantId: assistantId.value,
|
||||
assistantId: route.params.assistantId,
|
||||
});
|
||||
useAlert(t('CAPTAIN.ASSISTANTS.SCENARIOS.API.DELETE.SUCCESS'));
|
||||
} catch (error) {
|
||||
@@ -140,7 +154,7 @@ const bulkDeleteScenarios = async ids => {
|
||||
idsArray.map(id =>
|
||||
store.dispatch('captainScenarios/delete', {
|
||||
id,
|
||||
assistantId: assistantId.value,
|
||||
assistantId: route.params.assistantId,
|
||||
})
|
||||
)
|
||||
);
|
||||
@@ -151,7 +165,7 @@ const bulkDeleteScenarios = async ids => {
|
||||
const addScenario = async scenario => {
|
||||
try {
|
||||
await store.dispatch('captainScenarios/create', {
|
||||
assistantId: assistantId.value,
|
||||
assistantId: route.params.assistantId,
|
||||
...scenario,
|
||||
tools: getToolsFromInstruction(scenario.instruction),
|
||||
});
|
||||
@@ -168,7 +182,7 @@ const addAllExampleScenarios = async () => {
|
||||
try {
|
||||
scenariosExample.forEach(async scenario => {
|
||||
await store.dispatch('captainScenarios/create', {
|
||||
assistantId: assistantId.value,
|
||||
assistantId: route.params.assistantId,
|
||||
...scenario,
|
||||
});
|
||||
});
|
||||
@@ -183,18 +197,16 @@ const addAllExampleScenarios = async () => {
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('captainScenarios/get', {
|
||||
assistantId: assistantId.value,
|
||||
assistantId: assistantId,
|
||||
});
|
||||
store.dispatch('captainTools/getTools');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageLayout
|
||||
:header-title="$t('CAPTAIN.DOCUMENTS.HEADER')"
|
||||
<SettingsPageLayout
|
||||
:breadcrumb-items="breadcrumbItems"
|
||||
:is-fetching="isFetching"
|
||||
:show-know-more="false"
|
||||
:show-pagination-footer="false"
|
||||
>
|
||||
<template #body>
|
||||
<SettingsHeader
|
||||
@@ -298,5 +310,5 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</PageLayout>
|
||||
</SettingsPageLayout>
|
||||
</template>
|
||||
|
||||
+84
-127
@@ -1,40 +1,28 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
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 { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
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';
|
||||
import DeleteDialog from 'dashboard/components-next/captain/pageComponents/DeleteDialog.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { isCloudFeatureEnabled } = useAccount();
|
||||
|
||||
const isCaptainV2Enabled = computed(() =>
|
||||
isCloudFeatureEnabled(FEATURE_FLAGS.CAPTAIN_V2)
|
||||
);
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
|
||||
const deleteAssistantDialog = ref(null);
|
||||
|
||||
const assistantId = route.params.assistantId;
|
||||
const uiFlags = useMapGetter('captainAssistants/getUIFlags');
|
||||
const assistants = useMapGetter('captainAssistants/getRecords');
|
||||
const isFetching = computed(() => uiFlags.value.fetchingItem);
|
||||
const assistantId = computed(() => Number(route.params.assistantId));
|
||||
const assistant = computed(() =>
|
||||
store.getters['captainAssistants/getRecord'](assistantId.value)
|
||||
store.getters['captainAssistants/getRecord'](Number(assistantId))
|
||||
);
|
||||
|
||||
const isAssistantAvailable = computed(() => !!assistant.value?.id);
|
||||
|
||||
const controlItems = computed(() => {
|
||||
return [
|
||||
{
|
||||
@@ -46,6 +34,15 @@ const controlItems = computed(() => {
|
||||
),
|
||||
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'
|
||||
@@ -58,10 +55,32 @@ const controlItems = computed(() => {
|
||||
];
|
||||
});
|
||||
|
||||
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.value,
|
||||
id: assistantId,
|
||||
...updatedAssistant,
|
||||
});
|
||||
useAlert(t('CAPTAIN.ASSISTANTS.EDIT.SUCCESS_MESSAGE'));
|
||||
@@ -72,126 +91,64 @@ const handleSubmit = async updatedAssistant => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
deleteAssistantDialog.value.dialogRef.open();
|
||||
};
|
||||
|
||||
const handleDeleteSuccess = () => {
|
||||
// Get remaining assistants after deletion
|
||||
const remainingAssistants = assistants.value.filter(
|
||||
a => a.id !== assistantId.value
|
||||
);
|
||||
|
||||
if (remainingAssistants.length > 0) {
|
||||
// Navigate to the first available assistant's settings
|
||||
const nextAssistant = remainingAssistants[0];
|
||||
router.push({
|
||||
name: 'captain_assistants_settings_index',
|
||||
params: {
|
||||
accountId: route.params.accountId,
|
||||
assistantId: nextAssistant.id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// No assistants left, redirect to create assistant page
|
||||
router.push({
|
||||
name: 'captain_assistants_create_index',
|
||||
params: { accountId: route.params.accountId },
|
||||
});
|
||||
onMounted(() => {
|
||||
if (!isAssistantAvailable.value) {
|
||||
store.dispatch('captainAssistants/show', assistantId);
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageLayout
|
||||
:header-title="$t('CAPTAIN.ASSISTANTS.SETTINGS.HEADER')"
|
||||
<SettingsPageLayout
|
||||
:breadcrumb-items="breadcrumbItems"
|
||||
:is-fetching="isFetching"
|
||||
:show-pagination-footer="false"
|
||||
:show-know-more="false"
|
||||
:class="{
|
||||
'[&>header>div]:max-w-[80rem] [&>main>div]:max-w-[80rem]':
|
||||
isCaptainV2Enabled,
|
||||
}"
|
||||
class="[&>div]:max-w-[80rem]"
|
||||
>
|
||||
<template #body>
|
||||
<div
|
||||
class="gap-6 lg:gap-16 pb-8"
|
||||
:class="{ 'grid grid-cols-2': isCaptainV2Enabled }"
|
||||
>
|
||||
<div class="flex flex-col gap-6">
|
||||
<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>
|
||||
<span class="h-px w-full bg-n-weak mt-2" />
|
||||
<div class="flex items-end justify-between w-full gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<h6 class="text-n-slate-12 text-base font-medium">
|
||||
{{ t('CAPTAIN.ASSISTANTS.SETTINGS.DELETE.TITLE') }}
|
||||
</h6>
|
||||
<span class="text-n-slate-11 text-sm">
|
||||
{{ t('CAPTAIN.ASSISTANTS.SETTINGS.DELETE.DESCRIPTION') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex-shrink-0">
|
||||
<Button
|
||||
:label="
|
||||
t('CAPTAIN.ASSISTANTS.SETTINGS.DELETE.BUTTON_TEXT', {
|
||||
assistantName: assistant.name,
|
||||
})
|
||||
"
|
||||
color="ruby"
|
||||
class="max-w-56 !w-fit"
|
||||
@click="handleDelete"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isCaptainV2Enabled" class="flex flex-col gap-6">
|
||||
<SettingsHeader
|
||||
:heading="t('CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.TITLE')"
|
||||
:heading="t('CAPTAIN.ASSISTANTS.SETTINGS.BASIC_SETTINGS.TITLE')"
|
||||
:description="
|
||||
t('CAPTAIN.ASSISTANTS.SETTINGS.CONTROL_ITEMS.DESCRIPTION')
|
||||
t('CAPTAIN.ASSISTANTS.SETTINGS.BASIC_SETTINGS.DESCRIPTION')
|
||||
"
|
||||
/>
|
||||
<div class="flex flex-col gap-6">
|
||||
<AssistantControlItems
|
||||
v-for="item in controlItems"
|
||||
:key="item.name"
|
||||
:control-item="item"
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
<DeleteDialog
|
||||
v-if="assistant"
|
||||
ref="deleteAssistantDialog"
|
||||
:entity="assistant"
|
||||
type="Assistants"
|
||||
translation-key="ASSISTANTS"
|
||||
@delete-success="handleDeleteSuccess"
|
||||
/>
|
||||
</PageLayout>
|
||||
<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>
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
|
||||
import { frontendURL } from '../../../helper/URLHelper';
|
||||
|
||||
import CaptainPageRouteView from './pages/CaptainPageRouteView.vue';
|
||||
import AssistantsIndexPage from './pages/AssistantsIndexPage.vue';
|
||||
import AssistantEmptyStateIndex from './assistants/Index.vue';
|
||||
|
||||
import AssistantSettingsIndex from './assistants/settings/Settings.vue';
|
||||
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 AssistantPlaygroundIndex from './assistants/playground/Index.vue';
|
||||
import AssistantGuardrailsIndex from './assistants/guardrails/Index.vue';
|
||||
import AssistantGuidelinesIndex from './assistants/guidelines/Index.vue';
|
||||
import AssistantScenariosIndex from './assistants/scenarios/Index.vue';
|
||||
@@ -17,90 +13,14 @@ import ResponsesIndex from './responses/Index.vue';
|
||||
import ResponsesPendingIndex from './responses/Pending.vue';
|
||||
import CustomToolsIndex from './tools/Index.vue';
|
||||
|
||||
const meta = {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [INSTALLATION_TYPES.CLOUD, INSTALLATION_TYPES.ENTERPRISE],
|
||||
};
|
||||
|
||||
const metaV2 = {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN_V2,
|
||||
installationTypes: [INSTALLATION_TYPES.CLOUD, INSTALLATION_TYPES.ENTERPRISE],
|
||||
};
|
||||
|
||||
const assistantRoutes = [
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/faqs'),
|
||||
component: ResponsesIndex,
|
||||
name: 'captain_assistants_responses_index',
|
||||
meta,
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/documents'),
|
||||
component: DocumentsIndex,
|
||||
name: 'captain_assistants_documents_index',
|
||||
meta,
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/tools'),
|
||||
component: CustomToolsIndex,
|
||||
name: 'captain_tools_index',
|
||||
meta: metaV2,
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/scenarios'),
|
||||
component: AssistantScenariosIndex,
|
||||
name: 'captain_assistants_scenarios_index',
|
||||
meta: metaV2,
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/playground'),
|
||||
component: AssistantPlaygroundIndex,
|
||||
name: 'captain_assistants_playground_index',
|
||||
meta,
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/inboxes'),
|
||||
component: AssistantInboxesIndex,
|
||||
name: 'captain_assistants_inboxes_index',
|
||||
meta,
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/faqs/pending'),
|
||||
component: ResponsesPendingIndex,
|
||||
name: 'captain_assistants_responses_pending',
|
||||
meta,
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/settings'),
|
||||
component: AssistantSettingsIndex,
|
||||
name: 'captain_assistants_settings_index',
|
||||
meta,
|
||||
},
|
||||
// Settings sub-pages (guardrails and guidelines)
|
||||
{
|
||||
path: frontendURL(
|
||||
'accounts/:accountId/captain/:assistantId/settings/guardrails'
|
||||
),
|
||||
component: AssistantGuardrailsIndex,
|
||||
name: 'captain_assistants_guardrails_index',
|
||||
meta: metaV2,
|
||||
},
|
||||
{
|
||||
path: frontendURL(
|
||||
'accounts/:accountId/captain/:assistantId/settings/guidelines'
|
||||
),
|
||||
component: AssistantGuidelinesIndex,
|
||||
name: 'captain_assistants_guidelines_index',
|
||||
meta: metaV2,
|
||||
},
|
||||
export const routes = [
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/assistants'),
|
||||
component: AssistantEmptyStateIndex,
|
||||
name: 'captain_assistants_create_index',
|
||||
component: AssistantIndex,
|
||||
name: 'captain_assistants_index',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
@@ -108,26 +28,128 @@ const assistantRoutes = [
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:navigationPath'),
|
||||
component: AssistantsIndexPage,
|
||||
name: 'captain_assistants_index',
|
||||
meta,
|
||||
},
|
||||
];
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain'),
|
||||
component: CaptainPageRouteView,
|
||||
redirect: to => {
|
||||
return {
|
||||
name: 'captain_assistants_index',
|
||||
params: {
|
||||
navigationPath: 'captain_assistants_responses_index',
|
||||
...to.params,
|
||||
},
|
||||
};
|
||||
path: frontendURL('accounts/:accountId/captain/assistants/:assistantId'),
|
||||
component: AssistantEdit,
|
||||
name: 'captain_assistants_edit',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL(
|
||||
'accounts/:accountId/captain/assistants/:assistantId/inboxes'
|
||||
),
|
||||
component: AssistantInboxesIndex,
|
||||
name: 'captain_assistants_inboxes_index',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL(
|
||||
'accounts/:accountId/captain/assistants/:assistantId/guardrails'
|
||||
),
|
||||
component: AssistantGuardrailsIndex,
|
||||
name: 'captain_assistants_guardrails_index',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL(
|
||||
'accounts/:accountId/captain/assistants/:assistantId/scenarios'
|
||||
),
|
||||
component: AssistantScenariosIndex,
|
||||
name: 'captain_assistants_scenarios_index',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL(
|
||||
'accounts/:accountId/captain/assistants/:assistantId/guidelines'
|
||||
),
|
||||
component: AssistantGuidelinesIndex,
|
||||
name: 'captain_assistants_guidelines_index',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/documents'),
|
||||
component: DocumentsIndex,
|
||||
name: 'captain_documents_index',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/responses'),
|
||||
component: ResponsesIndex,
|
||||
name: 'captain_responses_index',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/responses/pending'),
|
||||
component: ResponsesPendingIndex,
|
||||
name: 'captain_responses_pending',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/tools'),
|
||||
component: CustomToolsIndex,
|
||||
name: 'captain_tools_index',
|
||||
meta: {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN_V2,
|
||||
installationTypes: [
|
||||
INSTALLATION_TYPES.CLOUD,
|
||||
INSTALLATION_TYPES.ENTERPRISE,
|
||||
],
|
||||
},
|
||||
children: [...assistantRoutes],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, nextTick } from 'vue';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
|
||||
@@ -11,20 +10,20 @@ import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import CaptainPaywall from 'dashboard/components-next/captain/pageComponents/Paywall.vue';
|
||||
import RelatedResponses from 'dashboard/components-next/captain/pageComponents/document/RelatedResponses.vue';
|
||||
import CreateDocumentDialog from 'dashboard/components-next/captain/pageComponents/document/CreateDocumentDialog.vue';
|
||||
import AssistantSelector from 'dashboard/components-next/captain/pageComponents/AssistantSelector.vue';
|
||||
import DocumentPageEmptyState from 'dashboard/components-next/captain/pageComponents/emptyStates/DocumentPageEmptyState.vue';
|
||||
import FeatureSpotlightPopover from 'dashboard/components-next/feature-spotlight/FeatureSpotlightPopover.vue';
|
||||
import LimitBanner from 'dashboard/components-next/captain/pageComponents/document/LimitBanner.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
|
||||
const { isOnChatwootCloud } = useAccount();
|
||||
const uiFlags = useMapGetter('captainDocuments/getUIFlags');
|
||||
const documents = useMapGetter('captainDocuments/getRecords');
|
||||
const assistants = useMapGetter('captainAssistants/getRecords');
|
||||
const isFetching = computed(() => uiFlags.value.fetchingList);
|
||||
const documentsMeta = useMapGetter('captainDocuments/getMeta');
|
||||
|
||||
const selectedAssistantId = computed(() => Number(route.params.assistantId));
|
||||
const selectedAssistant = ref('all');
|
||||
|
||||
const selectedDocument = ref(null);
|
||||
const deleteDocumentDialog = ref(null);
|
||||
@@ -38,6 +37,12 @@ const showCreateDialog = ref(false);
|
||||
const createDocumentDialog = ref(null);
|
||||
const relationQuestionDialog = ref(null);
|
||||
|
||||
const shouldShowAssistantSelector = computed(() => {
|
||||
if (assistants.value.length === 0) return false;
|
||||
|
||||
return !isFetching.value;
|
||||
});
|
||||
|
||||
const handleShowRelatedDocument = () => {
|
||||
showRelatedResponses.value = true;
|
||||
nextTick(() => relationQuestionDialog.value.dialogRef.open());
|
||||
@@ -72,12 +77,17 @@ const handleAction = ({ action, id }) => {
|
||||
const fetchDocuments = (page = 1) => {
|
||||
const filterParams = { page };
|
||||
|
||||
if (selectedAssistantId.value) {
|
||||
filterParams.assistantId = selectedAssistantId.value;
|
||||
if (selectedAssistant.value !== 'all') {
|
||||
filterParams.assistantId = selectedAssistant.value;
|
||||
}
|
||||
store.dispatch('captainDocuments/get', filterParams);
|
||||
};
|
||||
|
||||
const handleAssistantFilterChange = assistant => {
|
||||
selectedAssistant.value = assistant;
|
||||
fetchDocuments();
|
||||
};
|
||||
|
||||
const onPageChange = page => fetchDocuments(page);
|
||||
|
||||
const onDeleteSuccess = () => {
|
||||
@@ -87,6 +97,9 @@ const onDeleteSuccess = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (!assistants.value.length) {
|
||||
store.dispatch('captainAssistants/get');
|
||||
}
|
||||
fetchDocuments();
|
||||
});
|
||||
</script>
|
||||
@@ -101,7 +114,6 @@ onMounted(() => {
|
||||
:show-pagination-footer="!isFetching && !!documents.length"
|
||||
:is-fetching="isFetching"
|
||||
:is-empty="!documents.length"
|
||||
:show-know-more="false"
|
||||
:feature-flag="FEATURE_FLAGS.CAPTAIN"
|
||||
@update:current-page="onPageChange"
|
||||
@click="handleCreateDocument"
|
||||
@@ -126,6 +138,15 @@ onMounted(() => {
|
||||
<CaptainPaywall />
|
||||
</template>
|
||||
|
||||
<template #controls>
|
||||
<div v-if="shouldShowAssistantSelector" class="mb-4 -mt-3 flex gap-3">
|
||||
<AssistantSelector
|
||||
:assistant-id="selectedAssistant"
|
||||
@update="handleAssistantFilterChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<LimitBanner class="mb-5" />
|
||||
|
||||
@@ -152,7 +173,6 @@ onMounted(() => {
|
||||
<CreateDocumentDialog
|
||||
v-if="showCreateDialog"
|
||||
ref="createDocumentDialog"
|
||||
:assistant-id="selectedAssistantId"
|
||||
@close="handleCreateDialogClose"
|
||||
/>
|
||||
<DeleteDialog
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, nextTick, onMounted } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
|
||||
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
|
||||
|
||||
const store = useStore();
|
||||
const router = useRouter();
|
||||
const { uiSettings } = useUISettings();
|
||||
const route = useRoute();
|
||||
|
||||
const assistants = computed(
|
||||
() => store.getters['captainAssistants/getRecords']
|
||||
);
|
||||
|
||||
const isAssistantPresent = assistantId => {
|
||||
return !!assistants.value.find(a => a.id === Number(assistantId));
|
||||
};
|
||||
|
||||
const routeToView = (name, params) => {
|
||||
router.replace({ name, params, replace: true });
|
||||
};
|
||||
|
||||
const generateRouterParams = () => {
|
||||
const { last_active_assistant_id: lastActiveAssistantId } =
|
||||
uiSettings.value || {};
|
||||
|
||||
if (isAssistantPresent(lastActiveAssistantId)) {
|
||||
return {
|
||||
assistantId: lastActiveAssistantId,
|
||||
};
|
||||
}
|
||||
|
||||
if (assistants.value.length > 0) {
|
||||
const { id: assistantId } = assistants.value[0];
|
||||
return { assistantId };
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const routeToLastActiveAssistant = () => {
|
||||
const params = generateRouterParams();
|
||||
|
||||
// No assistants found, redirect to create page
|
||||
if (!params) {
|
||||
return routeToView('captain_assistants_create_index', {
|
||||
accountId: route.params.accountId,
|
||||
});
|
||||
}
|
||||
|
||||
const { navigationPath } = route.params;
|
||||
const isAValidRoute = [
|
||||
'captain_assistants_responses_index', // Faq page
|
||||
'captain_assistants_documents_index', // Document page
|
||||
'captain_assistants_scenarios_index', // Scenario page
|
||||
'captain_assistants_playground_index', // Playground page
|
||||
'captain_assistants_inboxes_index', // Inboxes page
|
||||
'captain_tools_index', // Tools page
|
||||
'captain_assistants_settings_index', // Settings page
|
||||
].includes(navigationPath);
|
||||
|
||||
const navigateTo = isAValidRoute
|
||||
? navigationPath
|
||||
: 'captain_assistants_responses_index';
|
||||
|
||||
return routeToView(navigateTo, {
|
||||
accountId: route.params.accountId,
|
||||
...params,
|
||||
});
|
||||
};
|
||||
|
||||
const performRouting = async () => {
|
||||
await store.dispatch('captainAssistants/get');
|
||||
nextTick(() => routeToLastActiveAssistant());
|
||||
};
|
||||
|
||||
onMounted(() => performRouting());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-center w-full bg-n-background text-n-slate-11"
|
||||
>
|
||||
<Spinner />
|
||||
</div>
|
||||
</template>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user