Compare commits

...
Author SHA1 Message Date
Tanmay Sharma afa7f2d325 add default payment method for change of subscription 2025-11-24 19:38:38 +05:30
Tanmay Sharma 91bfc47f01 add change pricing plan view 2025-11-24 19:18:02 +05:30
Tanmay Sharma 862e33e7b9 Merge remote-tracking branch 'origin/stripe_v2/base' into stripe_v2/update_subscription 2025-11-24 19:16:49 +05:30
Tanmay Sharma 7d2307d343 fix spec handle stripe event service spec 2025-11-24 19:15:01 +05:30
Tanmay Deep Sharma 69b818896a update plan catalog display_name 2025-11-19 20:01:49 +05:30
Tanmay Deep Sharma 01d17674e6 fix comments 2025-11-19 19:39:40 +05:30
Tanmay Deep Sharma 689525ce85 fix credit addition for topup and monthly 2025-11-17 11:57:27 +05:30
Tanmay Sharma 6abecdfd43 update the logic to get the number of credits for a monthly plan 2025-11-17 11:15:21 +05:30
Tanmay Deep SharmaandGitHub 7ac8e857c7 Merge branch 'stripe_v2/base' into stripe_v2/update_subscription 2025-11-17 10:23:15 +05:30
Tanmay Deep Sharma 418d9cce27 update stripe controller rspec 2025-11-10 20:44:10 +05:30
Tanmay Deep Sharma eba12567e9 cursor review commit fixes 2025-11-10 19:15:20 +05:30
Tanmay Deep Sharma d230bb68d0 add updated rspecs 2025-11-10 19:06:24 +05:30
Tanmay Deep Sharma 7babcfe622 add updated rspecs 2025-11-10 17:55:57 +05:30
Tanmay Deep Sharma f31666f877 feat: add update subscription in controller 2025-11-10 17:42:09 +05:30
Tanmay Deep Sharma 4c1a047c94 feat: add changes to support update subscription 2025-11-10 17:14:59 +05:30
Tanmay Deep Sharma c853d07f02 init: base for stripe V2 2025-11-10 14:28:18 +05:30
Lê Nam KhánhandGitHub fb1aa085cf chore(docs): Fix typos in some files (#12817)
This PR fixes typos in the file file using codespell.
2025-11-07 07:57:37 -08:00
Chatwoot BotandGitHub e8a01ace41 chore: Update translations (#12794) 2025-11-07 10:21:33 +05:30
PranavandGitHub 01363042ce fix: Handle login when there are no accounts (#12816) 2025-11-07 10:14:59 +05:30
5bf39d20e5 feat: Update Captain navigation structure (#12761)
# Pull Request Template

## Description

This PR includes an update to the Captain navigation structure.

## Route Structure

```javascript
1. captain_assistants_responses_index    → /captain/:assistantId/faqs
2. captain_assistants_documents_index    → /captain/:assistantId/documents
3. captain_assistants_scenarios_index    → /captain/:assistantId/scenarios
4. captain_assistants_playground_index   → /captain/:assistantId/playground
5. captain_assistants_inboxes_index      → /captain/:assistantId/inboxes
6. captain_tools_index                   → /captain/tools
7. captain_assistants_settings_index     → /captain/:assistantId/settings
8. captain_assistants_guardrails_index   → /captain/:assistantId/settings/guardrails
9. captain_assistants_guidelines_index   → /captain/:assistantId/settings/guidelines
10. captain_assistants_index             → /captain/:navigationPath
```

**How it works:**

1. User clicks sidebar item → Routes to `captain_assistants_index` with
`navigationPath`
2. `AssistantsIndexPage` validates route and gets last active assistant,
if not redirects to assistant create page.
3. Routes to actual page: `/captain/:assistantId/:page`
4. Page loads with correct assistant context

Fixes
https://linear.app/chatwoot/issue/CW-5832/updating-captain-navigation

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?




## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2025-11-06 16:31:23 -08:00
Tanmay Deep SharmaandGitHub 90352b3a20 fix: Remove the same account validation for whatsapp channels (#12811)
## Description

Modified the phone number validation in Whatsapp::ChannelCreationService
to check for duplicate phone numbers across ALL accounts, not just
within the current account.

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

- Added test coverage for cross-account phone number validation
- Using actual UI flow 
<img width="1493" height="532" alt="image"
src="https://github.com/user-attachments/assets/67d2bb99-2eb9-4115-8d56-449e4785e0d8"
/>


## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2025-11-06 21:18:52 +05:30
Sivin VargheseandGitHub ba8df900e3 feat: Enhance button interactions (#12738) 2025-11-06 16:24:05 +05:30
9b75d9bd1b fix: Add empty line before signature in compose conversation editor (#12702)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2025-11-06 14:05:52 +05:30
Shivam MishraandGitHub ec6c3b3571 feat: allow bots to handle campaigns when sender_id is nil (#12805) 2025-11-06 14:00:47 +05:30
Sivin VargheseandGitHub 48ba273730 fix: Invalid image URL issue in Help Center articles (#12806) 2025-11-06 13:53:31 +05:30
161 changed files with 3858 additions and 1834 deletions
+1 -1
View File
@@ -159,7 +159,7 @@ gem 'working_hours'
gem 'pg_search' gem 'pg_search'
# Subscriptions, Billing # Subscriptions, Billing
gem 'stripe' gem 'stripe', '17.2.0.pre.alpha.2'
## - helper gems --## ## - helper gems --##
## to populate db with sample data ## to populate db with sample data
+2 -2
View File
@@ -902,7 +902,7 @@ GEM
squasher (0.7.2) squasher (0.7.2)
stackprof (0.2.25) stackprof (0.2.25)
statsd-ruby (1.5.0) statsd-ruby (1.5.0)
stripe (8.5.0) stripe (17.2.0.pre.alpha.2)
telephone_number (1.4.20) telephone_number (1.4.20)
test-prof (1.2.1) test-prof (1.2.1)
thor (1.4.0) thor (1.4.0)
@@ -1110,7 +1110,7 @@ DEPENDENCIES
spring-watcher-listen spring-watcher-listen
squasher squasher
stackprof stackprof
stripe stripe (= 17.2.0.pre.alpha.2)
telephone_number telephone_number
test-prof test-prof
tidewave tidewave
+4 -1
View File
@@ -8,7 +8,10 @@ module BillingHelper
# Return false if not plans are configured, so that no checks are enforced # Return false if not plans are configured, so that no checks are enforced
return false if default_plan.blank? return false if default_plan.blank?
account.custom_attributes['plan_name'].nil? || account.custom_attributes['plan_name'] == default_plan['name'] # 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
end end
def conversations_this_month(account) def conversations_this_month(account)
-13
View File
@@ -1,6 +1,5 @@
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import AddAccountModal from './components/app/AddAccountModal.vue';
import LoadingState from './components/widgets/LoadingState.vue'; import LoadingState from './components/widgets/LoadingState.vue';
import NetworkNotification from './components/NetworkNotification.vue'; import NetworkNotification from './components/NetworkNotification.vue';
import UpdateBanner from './components/app/UpdateBanner.vue'; import UpdateBanner from './components/app/UpdateBanner.vue';
@@ -25,7 +24,6 @@ export default {
name: 'App', name: 'App',
components: { components: {
AddAccountModal,
LoadingState, LoadingState,
NetworkNotification, NetworkNotification,
UpdateBanner, UpdateBanner,
@@ -51,7 +49,6 @@ export default {
}, },
data() { data() {
return { return {
showAddAccountModal: false,
latestChatwootVersion: null, latestChatwootVersion: null,
reconnectService: null, reconnectService: null,
}; };
@@ -64,21 +61,12 @@ export default {
authUIFlags: 'getAuthUIFlags', authUIFlags: 'getAuthUIFlags',
accountUIFlags: 'accounts/getUIFlags', accountUIFlags: 'accounts/getUIFlags',
}), }),
hasAccounts() {
const { accounts = [] } = this.currentUser || {};
return accounts.length > 0;
},
hideOnOnboardingView() { hideOnOnboardingView() {
return !isOnOnboardingView(this.$route); return !isOnOnboardingView(this.$route);
}, },
}, },
watch: { watch: {
currentUser() {
if (!this.hasAccounts) {
this.showAddAccountModal = true;
}
},
currentAccountId: { currentAccountId: {
immediate: true, immediate: true,
handler() { handler() {
@@ -156,7 +144,6 @@ export default {
<component :is="Component" /> <component :is="Component" />
</transition> </transition>
</router-view> </router-view>
<AddAccountModal :show="showAddAccountModal" :has-accounts="hasAccounts" />
<WootSnackbarBox /> <WootSnackbarBox />
<NetworkNotification /> <NetworkNotification />
</div> </div>
@@ -1,5 +1,6 @@
<script setup> <script setup>
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
import ButtonGroup from 'dashboard/components-next/buttonGroup/ButtonGroup.vue';
import { useUISettings } from 'dashboard/composables/useUISettings'; import { useUISettings } from 'dashboard/composables/useUISettings';
import { computed } from 'vue'; import { computed } from 'vue';
import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import { FEATURE_FLAGS } from 'dashboard/featureFlags';
@@ -55,17 +56,17 @@ useKeyboardEvents(keyboardEvents);
</script> </script>
<template> <template>
<div <ButtonGroup
class="flex flex-col justify-center items-center absolute top-36 xl:top-24 ltr:right-2 rtl:left-2 bg-n-solid-2 border border-n-weak rounded-full gap-2 p-1" class="flex flex-col justify-center items-center absolute top-36 xl:top-24 ltr:right-2 rtl:left-2 bg-n-solid-2/90 backdrop-blur-lg border border-n-weak/50 rounded-full gap-1.5 p-1.5 shadow-sm transition-shadow duration-200 hover:shadow"
> >
<Button <Button
v-tooltip.top="$t('CONVERSATION.SIDEBAR.CONTACT')" v-tooltip.top="$t('CONVERSATION.SIDEBAR.CONTACT')"
ghost ghost
slate slate
sm sm
class="!rounded-full" class="!rounded-full transition-all duration-[250ms] ease-out active:!scale-95 active:!brightness-105 active:duration-75"
:class="{ :class="{
'bg-n-alpha-2': isContactSidebarOpen, 'bg-n-alpha-2 active:shadow-sm': isContactSidebarOpen,
}" }"
icon="i-ph-user-bold" icon="i-ph-user-bold"
@click="handleConversationSidebarToggle" @click="handleConversationSidebarToggle"
@@ -75,13 +76,14 @@ useKeyboardEvents(keyboardEvents);
v-tooltip.bottom="$t('CONVERSATION.SIDEBAR.COPILOT')" v-tooltip.bottom="$t('CONVERSATION.SIDEBAR.COPILOT')"
ghost ghost
slate slate
class="!rounded-full"
:class="{
'bg-n-alpha-2 !text-n-iris-9': isCopilotPanelOpen,
}"
sm sm
class="!rounded-full transition-all duration-[250ms] ease-out active:!scale-95 active:duration-75"
:class="{
'bg-n-alpha-2 !text-n-iris-9 active:!brightness-105 active:shadow-sm':
isCopilotPanelOpen,
}"
icon="i-woot-captain" icon="i-woot-captain"
@click="handleCopilotSidebarToggle" @click="handleCopilotSidebarToggle"
/> />
</div> </ButtonGroup>
</template> </template>
@@ -21,6 +21,10 @@ const props = defineProps({
enableCannedResponses: { type: Boolean, default: true }, enableCannedResponses: { type: Boolean, default: true },
enabledMenuOptions: { type: Array, default: () => [] }, enabledMenuOptions: { type: Array, default: () => [] },
enableCaptainTools: { type: Boolean, default: false }, enableCaptainTools: { type: Boolean, default: false },
signature: { type: String, default: '' },
allowSignature: { type: Boolean, default: false },
sendWithSignature: { type: Boolean, default: false },
channelType: { type: String, default: '' },
}); });
const emit = defineEmits(['update:modelValue']); const emit = defineEmits(['update:modelValue']);
@@ -100,6 +104,10 @@ watch(
:enable-canned-responses="enableCannedResponses" :enable-canned-responses="enableCannedResponses"
:enabled-menu-options="enabledMenuOptions" :enabled-menu-options="enabledMenuOptions"
:enable-captain-tools="enableCaptainTools" :enable-captain-tools="enableCaptainTools"
:signature="signature"
:allow-signature="allowSignature"
:send-with-signature="sendWithSignature"
:channel-type="channelType"
@input="handleInput" @input="handleInput"
@focus="handleFocus" @focus="handleFocus"
@blur="handleBlur" @blur="handleBlur"
@@ -14,6 +14,7 @@ import {
} from 'dashboard/helper/portalHelper'; } from 'dashboard/helper/portalHelper';
import wootConstants from 'dashboard/constants/globals'; import wootConstants from 'dashboard/constants/globals';
import ButtonGroup from 'dashboard/components-next/buttonGroup/ButtonGroup.vue';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue'; import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
@@ -140,11 +141,12 @@ const updateArticleStatus = async ({ value }) => {
:disabled="!articleId" :disabled="!articleId"
@click="previewArticle" @click="previewArticle"
/> />
<div class="flex items-center"> <ButtonGroup class="flex items-center">
<Button <Button
:label="t('HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.PUBLISH')" :label="t('HELP_CENTER.EDIT_ARTICLE_PAGE.HEADER.PUBLISH')"
size="sm" size="sm"
class="ltr:rounded-r-none rtl:rounded-l-none" class="ltr:rounded-r-none rtl:rounded-l-none"
no-animation
:is-loading="isArticlePublishing" :is-loading="isArticlePublishing"
:disabled=" :disabled="
status === ARTICLE_STATUSES.PUBLISHED || status === ARTICLE_STATUSES.PUBLISHED ||
@@ -159,6 +161,7 @@ const updateArticleStatus = async ({ value }) => {
icon="i-lucide-chevron-down" icon="i-lucide-chevron-down"
size="sm" size="sm"
:disabled="!articleId" :disabled="!articleId"
no-animation
class="ltr:rounded-l-none rtl:rounded-r-none" class="ltr:rounded-l-none rtl:rounded-r-none"
@click.stop="showArticleActionMenu = !showArticleActionMenu" @click.stop="showArticleActionMenu = !showArticleActionMenu"
/> />
@@ -170,7 +173,7 @@ const updateArticleStatus = async ({ value }) => {
/> />
</OnClickOutside> </OnClickOutside>
</div> </div>
</div> </ButtonGroup>
</div> </div>
</div> </div>
</div> </div>
@@ -92,7 +92,6 @@ const setSignature = () => {
const toggleMessageSignature = () => { const toggleMessageSignature = () => {
setSignatureFlagForInbox(props.channelType, !sendWithSignature.value); setSignatureFlagForInbox(props.channelType, !sendWithSignature.value);
setSignature();
}; };
// Added this watch to dynamically set signature on target inbox change. // Added this watch to dynamically set signature on target inbox change.
@@ -199,16 +199,20 @@ const handleInboxAction = ({ value, action, ...rest }) => {
state.attachedFiles = []; state.attachedFiles = [];
}; };
const removeTargetInbox = value => { const removeSignatureFromMessage = () => {
v$.value.$reset(); // Always remove the signature from message content when inbox/contact is removed
// Remove the signature from message content // to ensure no leftover signature content remains
// Based on the Advance Editor (used in isEmailOrWebWidget) and Plain editor(all other inboxes except WhatsApp) const signatureToRemove = inboxTypes.value.isEmailOrWebWidget
if (props.sendWithSignature) { ? props.messageSignature
const signatureToRemove = inboxTypes.value.isEmailOrWebWidget : extractTextFromMarkdown(props.messageSignature);
? props.messageSignature if (signatureToRemove) {
: extractTextFromMarkdown(props.messageSignature);
state.message = removeSignature(state.message, signatureToRemove); state.message = removeSignature(state.message, signatureToRemove);
} }
};
const removeTargetInbox = value => {
v$.value.$reset();
removeSignatureFromMessage();
emit('updateTargetInbox', value); emit('updateTargetInbox', value);
state.attachedFiles = []; state.attachedFiles = [];
}; };
@@ -216,6 +220,7 @@ const removeTargetInbox = value => {
const clearSelectedContact = () => { const clearSelectedContact = () => {
emit('clearSelectedContact'); emit('clearSelectedContact');
state.attachedFiles = []; state.attachedFiles = [];
removeSignatureFromMessage();
}; };
const onClickInsertEmoji = emoji => { const onClickInsertEmoji = emoji => {
@@ -354,6 +359,7 @@ const shouldShowMessageEditor = computed(() => {
:is-email-or-web-widget-inbox="inboxTypes.isEmailOrWebWidget" :is-email-or-web-widget-inbox="inboxTypes.isEmailOrWebWidget"
:has-errors="validationStates.isMessageInvalid" :has-errors="validationStates.isMessageInvalid"
:has-attachments="state.attachedFiles.length > 0" :has-attachments="state.attachedFiles.length > 0"
:channel-type="inboxChannelType"
/> />
<AttachmentPreviews <AttachmentPreviews
@@ -17,6 +17,7 @@ const props = defineProps({
hasAttachments: { type: Boolean, default: false }, hasAttachments: { type: Boolean, default: false },
sendWithSignature: { type: Boolean, default: false }, sendWithSignature: { type: Boolean, default: false },
messageSignature: { type: String, default: '' }, messageSignature: { type: String, default: '' },
channelType: { type: String, default: '' },
}); });
const { t } = useI18n(); const { t } = useI18n();
@@ -90,6 +91,10 @@ const replaceText = async message => {
" "
enable-variables enable-variables
:show-character-count="false" :show-character-count="false"
:signature="messageSignature"
allow-signature
:send-with-signature="sendWithSignature"
:channel-type="channelType"
/> />
</template> </template>
<template v-else> <template v-else>
@@ -36,6 +36,7 @@ const props = defineProps({
icon: { type: [String, Object, Function], default: '' }, icon: { type: [String, Object, Function], default: '' },
trailingIcon: { type: Boolean, default: false }, trailingIcon: { type: Boolean, default: false },
isLoading: { type: Boolean, default: false }, isLoading: { type: Boolean, default: false },
noAnimation: { type: Boolean, default: false },
}); });
const slots = useSlots(); const slots = useSlots();
@@ -179,12 +180,18 @@ const STYLE_CONFIG = {
md: 'text-sm font-medium', md: 'text-sm font-medium',
lg: 'text-base', lg: 'text-base',
}, },
clickAnimation: {
xs: 'active:enabled:scale-[0.97]',
sm: 'active:enabled:scale-[0.97]',
md: 'active:enabled:scale-[0.98]',
lg: 'active:enabled:scale-[0.98]',
},
justify: { justify: {
start: 'justify-start', start: 'justify-start',
center: 'justify-center', center: 'justify-center',
end: 'justify-end', end: 'justify-end',
}, },
base: 'inline-flex items-center min-w-0 gap-2 transition-all duration-200 ease-in-out border-0 rounded-lg outline-1 outline disabled:opacity-50', base: 'inline-flex items-center min-w-0 gap-2 transition-all duration-100 ease-out border-0 rounded-lg outline-1 outline disabled:opacity-50',
}; };
const variantClasses = computed(() => { const variantClasses = computed(() => {
@@ -221,6 +228,12 @@ const linkButtonClasses = computed(() => {
return classes.join(' '); return classes.join(' ');
}); });
const animationClasses = computed(() => {
return props.noAnimation
? ''
: STYLE_CONFIG.clickAnimation[computedSize.value];
});
</script> </script>
<template> <template>
@@ -230,6 +243,7 @@ const linkButtonClasses = computed(() => {
[STYLE_CONFIG.base]: true, [STYLE_CONFIG.base]: true,
[isLink ? linkButtonClasses : buttonClasses]: true, [isLink ? linkButtonClasses : buttonClasses]: true,
[STYLE_CONFIG.fontSize[computedSize]]: true, [STYLE_CONFIG.fontSize[computedSize]]: true,
[animationClasses]: true,
[STYLE_CONFIG.justify[computedJustify]]: true, [STYLE_CONFIG.justify[computedJustify]]: true,
'flex-row-reverse': trailingIcon && !isIconOnly, 'flex-row-reverse': trailingIcon && !isIconOnly,
}" }"
@@ -0,0 +1,20 @@
<script setup>
defineProps({
noAnimation: {
type: Boolean,
default: false,
},
});
</script>
<template>
<div
:class="
noAnimation
? ''
: 'has-[button:not(:disabled):active]:scale-[0.98] transition-transform duration-150 ease-out'
"
>
<slot />
</div>
</template>
@@ -1,11 +1,17 @@
<script setup> <script setup>
import { computed } from 'vue'; import { ref, computed } from 'vue';
import { OnClickOutside } from '@vueuse/components';
import { useRoute } from 'vue-router';
import { useMapGetter } from 'dashboard/composables/store.js';
import { usePolicy } from 'dashboard/composables/usePolicy'; 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 Button from 'dashboard/components-next/button/Button.vue';
import BackButton from 'dashboard/components/widgets/BackButton.vue'; import BackButton from 'dashboard/components/widgets/BackButton.vue';
import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue'; import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue'; import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import Policy from 'dashboard/components/policy.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({ const props = defineProps({
currentPage: { currentPage: {
@@ -56,11 +62,32 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
showAssistantSwitcher: {
type: Boolean,
default: true,
},
}); });
const emit = defineEmits(['click', 'close', 'update:currentPage']); const emit = defineEmits(['click', 'close', 'update:currentPage']);
const route = useRoute();
const { shouldShowPaywall } = usePolicy(); 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(() => { const showPaywall = computed(() => {
return shouldShowPaywall(props.featureFlag); return shouldShowPaywall(props.featureFlag);
}); });
@@ -72,6 +99,15 @@ const handleButtonClick = () => {
const handlePageChange = event => { const handlePageChange = event => {
emit('update:currentPage', event); emit('update:currentPage', event);
}; };
const toggleAssistantSwitcher = () => {
showAssistantSwitcherDropdown.value = !showAssistantSwitcherDropdown.value;
};
const handleCreateAssistant = () => {
showAssistantSwitcherDropdown.value = false;
createAssistantDialogRef.value.dialogRef.open();
};
</script> </script>
<template> <template>
@@ -82,9 +118,48 @@ const handlePageChange = event => {
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" 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"> <div class="flex gap-4 items-center">
<BackButton v-if="backUrl" :to="backUrl" /> <BackButton v-if="backUrl" :back-url="backUrl" />
<slot name="headerTitle"> <slot name="headerTitle">
<span class="text-xl font-medium text-n-slate-12"> <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">
{{ headerTitle }} {{ headerTitle }}
</span> </span>
</slot> </slot>
@@ -97,21 +172,24 @@ const handlePageChange = event => {
</div> </div>
</div> </div>
<div <div class="flex gap-2">
v-if="!showPaywall && buttonLabel" <slot name="search" />
v-on-clickaway="() => emit('close')" <div
class="relative group/campaign-button" v-if="!showPaywall && buttonLabel"
> v-on-clickaway="() => emit('close')"
<Policy :permissions="buttonPolicy"> class="relative group/captain-button"
<Button >
:label="buttonLabel" <Policy :permissions="buttonPolicy">
icon="i-lucide-plus" <Button
size="sm" :label="buttonLabel"
class="group-hover/campaign-button:brightness-110" icon="i-lucide-plus"
@click="handleButtonClick" size="sm"
/> class="group-hover/captain-button:brightness-110"
</Policy> @click="handleButtonClick"
<slot name="action" /> />
</Policy>
<slot name="action" />
</div>
</div> </div>
</div> </div>
<slot name="subHeader" /> <slot name="subHeader" />
@@ -144,5 +222,6 @@ const handlePageChange = event => {
@update:current-page="handlePageChange" @update:current-page="handlePageChange"
/> />
</footer> </footer>
<CreateAssistantDialog ref="createAssistantDialogRef" type="create" />
</section> </section>
</template> </template>
@@ -1,91 +0,0 @@
<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,12 +76,11 @@ const handleAction = ({ action, value }) => {
<template> <template>
<CardLayout> <CardLayout>
<div class="flex justify-between w-full gap-1"> <div class="flex justify-between w-full gap-1">
<router-link <h6
:to="{ name: 'captain_assistants_edit', params: { assistantId: id } }" class="text-base font-normal text-n-slate-12 line-clamp-1 hover:underline transition-colors"
class="text-base text-n-slate-12 line-clamp-1 hover:underline transition-colors"
> >
{{ name }} {{ name }}
</router-link> </h6>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div <div
v-on-clickaway="() => toggleDropdown(false)" v-on-clickaway="() => toggleDropdown(false)"
@@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import NextButton from 'dashboard/components-next/button/Button.vue'; import NextButton from 'dashboard/components-next/button/Button.vue';
import MessageList from './MessageList.vue'; import MessageList from './MessageList.vue';
@@ -29,6 +29,16 @@ const resetConversation = () => {
newMessage.value = ''; newMessage.value = '';
}; };
// Watch for assistant ID changes and reset conversation
watch(
() => assistantId,
(newId, oldId) => {
if (oldId && newId !== oldId) {
resetConversation();
}
}
);
const sendMessage = async () => { const sendMessage = async () => {
if (!newMessage.value.trim() || isLoading.value) return; if (!newMessage.value.trim() || isLoading.value) return;
@@ -74,7 +84,7 @@ const sendMessage = async () => {
</h3> </h3>
<NextButton <NextButton
ghost ghost
size="small" sm
icon="i-lucide-rotate-ccw" icon="i-lucide-rotate-ccw"
@click="resetConversation" @click="resetConversation"
/> />
@@ -97,7 +107,7 @@ const sendMessage = async () => {
/> />
<NextButton <NextButton
ghost ghost
size="small" sm
:disabled="!newMessage.trim()" :disabled="!newMessage.trim()"
icon="i-lucide-send" icon="i-lucide-send"
@click="sendMessage" @click="sendMessage"
@@ -1,68 +0,0 @@
<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>
@@ -18,7 +18,7 @@ const props = defineProps({
validator: value => ['create', 'edit'].includes(value), validator: value => ['create', 'edit'].includes(value),
}, },
}); });
const emit = defineEmits(['close']); const emit = defineEmits(['close', 'created']);
const { t } = useI18n(); const { t } = useI18n();
const store = useStore(); const store = useStore();
@@ -35,8 +35,18 @@ const i18nKey = computed(
() => `CAPTAIN.ASSISTANTS.${props.type.toUpperCase()}` () => `CAPTAIN.ASSISTANTS.${props.type.toUpperCase()}`
); );
const createAssistant = assistantDetails => const createAssistant = async assistantDetails => {
store.dispatch('captainAssistants/create', 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 handleSubmit = async updatedAssistant => { const handleSubmit = async updatedAssistant => {
try { try {
@@ -1,333 +0,0 @@
<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>
@@ -3,6 +3,8 @@ import { reactive, computed, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useVuelidate } from '@vuelidate/core'; import { useVuelidate } from '@vuelidate/core';
import { minLength } from '@vuelidate/validators'; 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 Button from 'dashboard/components-next/button/Button.vue';
import Editor from 'dashboard/components-next/Editor/Editor.vue'; import Editor from 'dashboard/components-next/Editor/Editor.vue';
@@ -17,10 +19,16 @@ const props = defineProps({
const emit = defineEmits(['submit']); const emit = defineEmits(['submit']);
const { t } = useI18n(); const { t } = useI18n();
const { isCloudFeatureEnabled } = useAccount();
const isCaptainV2Enabled = computed(() =>
isCloudFeatureEnabled(FEATURE_FLAGS.CAPTAIN_V2)
);
const initialState = { const initialState = {
handoffMessage: '', handoffMessage: '',
resolutionMessage: '', resolutionMessage: '',
instructions: '',
temperature: 1, temperature: 1,
}; };
@@ -29,6 +37,7 @@ const state = reactive({ ...initialState });
const validationRules = { const validationRules = {
handoffMessage: { minLength: minLength(1) }, handoffMessage: { minLength: minLength(1) },
resolutionMessage: { minLength: minLength(1) }, resolutionMessage: { minLength: minLength(1) },
instructions: { minLength: minLength(1) },
}; };
const v$ = useVuelidate(validationRules, state); const v$ = useVuelidate(validationRules, state);
@@ -40,20 +49,30 @@ const getErrorMessage = field => {
const formErrors = computed(() => ({ const formErrors = computed(() => ({
handoffMessage: getErrorMessage('handoffMessage'), handoffMessage: getErrorMessage('handoffMessage'),
resolutionMessage: getErrorMessage('resolutionMessage'), resolutionMessage: getErrorMessage('resolutionMessage'),
instructions: getErrorMessage('instructions'),
})); }));
const updateStateFromAssistant = assistant => { const updateStateFromAssistant = assistant => {
const { config = {} } = assistant; const { config = {} } = assistant;
state.handoffMessage = config.handoff_message; state.handoffMessage = config.handoff_message;
state.resolutionMessage = config.resolution_message; state.resolutionMessage = config.resolution_message;
state.instructions = config.instructions;
state.temperature = config.temperature || 1; state.temperature = config.temperature || 1;
}; };
const handleSystemMessagesUpdate = async () => { const handleSystemMessagesUpdate = async () => {
const result = await Promise.all([ const validations = [
v$.value.handoffMessage.$validate(), v$.value.handoffMessage.$validate(),
v$.value.resolutionMessage.$validate(), v$.value.resolutionMessage.$validate(),
]).then(results => results.every(Boolean)); ];
if (!isCaptainV2Enabled.value) {
validations.push(v$.value.instructions.$validate());
}
const result = await Promise.all(validations).then(results =>
results.every(Boolean)
);
if (!result) return; if (!result) return;
const payload = { const payload = {
@@ -65,6 +84,10 @@ const handleSystemMessagesUpdate = async () => {
}, },
}; };
if (!isCaptainV2Enabled.value) {
payload.config.instructions = state.instructions;
}
emit('submit', payload); emit('submit', payload);
}; };
@@ -95,6 +118,16 @@ watch(
:message-type="formErrors.resolutionMessage ? 'error' : 'info'" :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"> <div class="flex flex-col gap-2">
<label class="text-sm font-medium text-n-slate-12"> <label class="text-sm font-medium text-n-slate-12">
{{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.LABEL') }} {{ t('CAPTAIN.ASSISTANTS.FORM.TEMPERATURE.LABEL') }}
@@ -8,6 +8,13 @@ import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue'; import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
import DocumentForm from './DocumentForm.vue'; import DocumentForm from './DocumentForm.vue';
defineProps({
assistantId: {
type: Number,
required: true,
},
});
const emit = defineEmits(['close']); const emit = defineEmits(['close']);
const { t } = useI18n(); const { t } = useI18n();
const store = useStore(); const store = useStore();
@@ -48,7 +55,11 @@ defineExpose({ dialogRef });
:show-confirm-button="false" :show-confirm-button="false"
@close="handleClose" @close="handleClose"
> >
<DocumentForm @submit="handleSubmit" @cancel="handleCancel" /> <DocumentForm
:assistant-id="assistantId"
@submit="handleSubmit"
@cancel="handleCancel"
/>
<template #footer /> <template #footer />
</Dialog> </Dialog>
</template> </template>
@@ -2,7 +2,7 @@
import { reactive, computed, ref, nextTick } from 'vue'; import { reactive, computed, ref, nextTick } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useVuelidate } from '@vuelidate/core'; import { useVuelidate } from '@vuelidate/core';
import { required, minLength, requiredIf, url } from '@vuelidate/validators'; import { minLength, requiredIf, url } from '@vuelidate/validators';
import { useMapGetter } from 'dashboard/composables/store'; import { useMapGetter } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
@@ -10,6 +10,13 @@ import Input from 'dashboard/components-next/input/Input.vue';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue'; import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
const props = defineProps({
assistantId: {
type: Number,
required: true,
},
});
const emit = defineEmits(['submit', 'cancel']); const emit = defineEmits(['submit', 'cancel']);
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
@@ -18,13 +25,11 @@ const { t } = useI18n();
const formState = { const formState = {
uiFlags: useMapGetter('captainDocuments/getUIFlags'), uiFlags: useMapGetter('captainDocuments/getUIFlags'),
assistants: useMapGetter('captainAssistants/getRecords'),
}; };
const initialState = { const initialState = {
name: '', name: '',
url: '', url: '',
assistantId: null,
documentType: 'url', documentType: 'url',
pdfFile: null, pdfFile: null,
}; };
@@ -38,19 +43,11 @@ const validationRules = {
url: requiredIf(() => state.documentType === 'url' && url), url: requiredIf(() => state.documentType === 'url' && url),
minLength: requiredIf(() => state.documentType === 'url' && minLength(1)), minLength: requiredIf(() => state.documentType === 'url' && minLength(1)),
}, },
assistantId: { required },
pdfFile: { pdfFile: {
required: requiredIf(() => state.documentType === 'pdf'), required: requiredIf(() => state.documentType === 'pdf'),
}, },
}; };
const assistantList = computed(() =>
formState.assistants.value.map(assistant => ({
value: assistant.id,
label: assistant.name,
}))
);
const documentTypeOptions = [ const documentTypeOptions = [
{ value: 'url', label: t('CAPTAIN.DOCUMENTS.FORM.TYPE.URL') }, { value: 'url', label: t('CAPTAIN.DOCUMENTS.FORM.TYPE.URL') },
{ value: 'pdf', label: t('CAPTAIN.DOCUMENTS.FORM.TYPE.PDF') }, { value: 'pdf', label: t('CAPTAIN.DOCUMENTS.FORM.TYPE.PDF') },
@@ -70,7 +67,6 @@ const getErrorMessage = (field, errorKey) => {
const formErrors = computed(() => ({ const formErrors = computed(() => ({
url: getErrorMessage('url', 'URL'), url: getErrorMessage('url', 'URL'),
assistantId: getErrorMessage('assistantId', 'ASSISTANT'),
pdfFile: getErrorMessage('pdfFile', 'PDF_FILE'), pdfFile: getErrorMessage('pdfFile', 'PDF_FILE'),
})); }));
@@ -106,7 +102,7 @@ const openFileDialog = () => {
const prepareDocumentDetails = () => { const prepareDocumentDetails = () => {
const formData = new FormData(); const formData = new FormData();
formData.append('document[assistant_id]', state.assistantId); formData.append('document[assistant_id]', props.assistantId);
if (state.documentType === 'url') { if (state.documentType === 'url') {
formData.append('document[external_link]', state.url); formData.append('document[external_link]', state.url);
@@ -218,21 +214,6 @@ const handleSubmit = async () => {
:placeholder="t('CAPTAIN.DOCUMENTS.FORM.NAME.PLACEHOLDER')" :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"> <div class="flex gap-3 justify-between items-center w-full">
<Button <Button
type="button" type="button"
@@ -3,6 +3,7 @@ import { ref, computed } from 'vue';
import { useStore } from 'dashboard/composables/store'; import { useStore } from 'dashboard/composables/store';
import { useAlert } from 'dashboard/composables'; import { useAlert } from 'dashboard/composables';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue'; import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
import ResponseForm from './ResponseForm.vue'; import ResponseForm from './ResponseForm.vue';
@@ -21,6 +22,7 @@ const props = defineProps({
const emit = defineEmits(['close']); const emit = defineEmits(['close']);
const { t } = useI18n(); const { t } = useI18n();
const store = useStore(); const store = useStore();
const route = useRoute();
const dialogRef = ref(null); const dialogRef = ref(null);
const responseForm = ref(null); const responseForm = ref(null);
@@ -39,9 +41,15 @@ const createResponse = responseDetails =>
const handleSubmit = async updatedResponse => { const handleSubmit = async updatedResponse => {
try { try {
if (props.type === 'edit') { if (props.type === 'edit') {
await updateResponse(updatedResponse); await updateResponse({
...updatedResponse,
assistant_id: route.params.assistantId,
});
} else { } else {
await createResponse(updatedResponse); await createResponse({
...updatedResponse,
assistant_id: route.params.assistantId,
});
} }
useAlert(t(`${i18nKey.value}.SUCCESS_MESSAGE`)); useAlert(t(`${i18nKey.value}.SUCCESS_MESSAGE`));
dialogRef.value.close(); dialogRef.value.close();
@@ -8,7 +8,6 @@ import { useMapGetter } from 'dashboard/composables/store';
import Input from 'dashboard/components-next/input/Input.vue'; import Input from 'dashboard/components-next/input/Input.vue';
import Editor from 'dashboard/components-next/Editor/Editor.vue'; import Editor from 'dashboard/components-next/Editor/Editor.vue';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
const props = defineProps({ const props = defineProps({
mode: { mode: {
@@ -21,18 +20,17 @@ const props = defineProps({
default: () => ({}), default: () => ({}),
}, },
}); });
const emit = defineEmits(['submit', 'cancel']); const emit = defineEmits(['submit', 'cancel']);
const { t } = useI18n(); const { t } = useI18n();
const formState = { const formState = {
uiFlags: useMapGetter('captainResponses/getUIFlags'), uiFlags: useMapGetter('captainResponses/getUIFlags'),
assistants: useMapGetter('captainAssistants/getRecords'),
}; };
const initialState = { const initialState = {
question: '', question: '',
answer: '', answer: '',
assistantId: null,
}; };
const state = reactive({ ...initialState }); const state = reactive({ ...initialState });
@@ -40,16 +38,8 @@ const state = reactive({ ...initialState });
const validationRules = { const validationRules = {
question: { required, minLength: minLength(1) }, question: { required, minLength: minLength(1) },
answer: { 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 v$ = useVuelidate(validationRules, state);
const isLoading = computed(() => formState.uiFlags.value.creatingItem); const isLoading = computed(() => formState.uiFlags.value.creatingItem);
@@ -63,7 +53,6 @@ const getErrorMessage = (field, errorKey) => {
const formErrors = computed(() => ({ const formErrors = computed(() => ({
question: getErrorMessage('question', 'QUESTION'), question: getErrorMessage('question', 'QUESTION'),
answer: getErrorMessage('answer', 'ANSWER'), answer: getErrorMessage('answer', 'ANSWER'),
assistantId: getErrorMessage('assistantId', 'ASSISTANT'),
})); }));
const handleCancel = () => emit('cancel'); const handleCancel = () => emit('cancel');
@@ -71,7 +60,6 @@ const handleCancel = () => emit('cancel');
const prepareDocumentDetails = () => ({ const prepareDocumentDetails = () => ({
question: state.question, question: state.question,
answer: state.answer, answer: state.answer,
assistant_id: state.assistantId,
}); });
const handleSubmit = async () => { const handleSubmit = async () => {
@@ -86,12 +74,11 @@ const handleSubmit = async () => {
const updateStateFromResponse = response => { const updateStateFromResponse = response => {
if (!response) return; if (!response) return;
const { question, answer, assistant } = response; const { question, answer } = response;
Object.assign(state, { Object.assign(state, {
question, question,
answer, answer,
assistantId: assistant.id,
}); });
}; };
@@ -115,7 +102,6 @@ watch(
:message="formErrors.question" :message="formErrors.question"
:message-type="formErrors.question ? 'error' : 'info'" :message-type="formErrors.question ? 'error' : 'info'"
/> />
<Editor <Editor
v-model="state.answer" v-model="state.answer"
:label="t('CAPTAIN.RESPONSES.FORM.ANSWER.LABEL')" :label="t('CAPTAIN.RESPONSES.FORM.ANSWER.LABEL')"
@@ -124,22 +110,6 @@ watch(
:max-length="10000" :max-length="10000"
:message-type="formErrors.answer ? 'error' : 'info'" :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"> <div class="flex items-center justify-between w-full gap-3">
<Button <Button
type="button" type="button"
@@ -0,0 +1,143 @@
<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>
@@ -96,6 +96,7 @@ watch(
:label="selectedLabel" :label="selectedLabel"
trailing-icon trailing-icon
:disabled="disabled" :disabled="disabled"
no-animation
class="justify-between w-full !px-3 !py-2.5 text-n-slate-12 font-normal group-hover/combobox:border-n-slate-6 focus:outline-n-brand" class="justify-between w-full !px-3 !py-2.5 text-n-slate-12 font-normal group-hover/combobox:border-n-slate-6 focus:outline-n-brand"
:class="{ :class="{
focused: open, focused: open,
@@ -78,8 +78,10 @@ const handleSuggestion = opt => {
</p> </p>
<router-link <router-link
:to="{ :to="{
name: 'captain_assistants_index', name: 'captain_assistants_create_index',
params: { accountId: route.params.accountId }, params: {
accountId: route.params.accountId,
},
}" }"
class="text-n-slate-11 underline hover:text-n-slate-12" class="text-n-slate-11 underline hover:text-n-slate-12"
> >
@@ -2,6 +2,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
import ButtonGroup from 'dashboard/components-next/buttonGroup/ButtonGroup.vue';
import { useUISettings } from 'dashboard/composables/useUISettings'; import { useUISettings } from 'dashboard/composables/useUISettings';
import { useMapGetter } from 'dashboard/composables/store'; import { useMapGetter } from 'dashboard/composables/store';
import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import { FEATURE_FLAGS } from 'dashboard/featureFlags';
@@ -53,14 +54,17 @@ const toggleSidebar = () => {
v-if="showCopilotLauncher" v-if="showCopilotLauncher"
class="fixed bottom-4 ltr:right-4 rtl:left-4 z-50" class="fixed bottom-4 ltr:right-4 rtl:left-4 z-50"
> >
<div class="rounded-full bg-n-alpha-2 p-1"> <ButtonGroup
class="rounded-full bg-n-alpha-2 backdrop-blur-lg p-1 shadow hover:shadow-md"
>
<Button <Button
icon="i-woot-captain" icon="i-woot-captain"
class="!rounded-full !bg-n-solid-3 dark:!bg-n-alpha-2 !text-n-slate-12 text-xl" no-animation
class="!rounded-full !bg-n-solid-3 dark:!bg-n-alpha-2 !text-n-slate-12 text-xl transition-all duration-200 ease-out hover:brightness-110"
lg lg
@click="toggleSidebar" @click="toggleSidebar"
/> />
</div> </ButtonGroup>
</div> </div>
<template v-else /> <template v-else />
</template> </template>
@@ -2,6 +2,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
import ButtonGroup from 'dashboard/components-next/buttonGroup/ButtonGroup.vue';
defineProps({ defineProps({
isMobileSidebarOpen: { isMobileSidebarOpen: {
@@ -45,14 +46,17 @@ const toggleSidebar = () => {
}, },
]" ]"
> >
<div class="rounded-full bg-n-alpha-2 p-1"> <ButtonGroup
class="rounded-full bg-n-alpha-2 backdrop-blur-lg p-1 shadow hover:shadow-md"
>
<Button <Button
icon="i-lucide-menu" icon="i-lucide-menu"
class="!rounded-full !bg-n-solid-3 dark:!bg-n-alpha-2 !text-n-slate-12 text-xl" no-animation
class="!rounded-full !bg-n-solid-3 dark:!bg-n-alpha-2 !text-n-slate-12 text-xl transition-all duration-200 ease-out hover:brightness-110"
lg lg
@click="toggleSidebar" @click="toggleSidebar"
/> />
</div> </ButtonGroup>
</div> </div>
<template v-else /> <template v-else />
</template> </template>
@@ -221,26 +221,70 @@ const menuItems = computed(() => {
name: 'Captain', name: 'Captain',
icon: 'i-woot-captain', icon: 'i-woot-captain',
label: t('SIDEBAR.CAPTAIN'), label: t('SIDEBAR.CAPTAIN'),
activeOn: ['captain_assistants_create_index'],
children: [ children: [
{ {
name: 'Assistants', name: 'FAQs',
label: t('SIDEBAR.CAPTAIN_ASSISTANTS'), label: t('SIDEBAR.CAPTAIN_RESPONSES'),
to: accountScopedRoute('captain_assistants_index'), activeOn: [
'captain_assistants_responses_index',
'captain_assistants_responses_pending',
],
to: accountScopedRoute('captain_assistants_index', {
navigationPath: 'captain_assistants_responses_index',
}),
}, },
{ {
name: 'Documents', name: 'Documents',
label: t('SIDEBAR.CAPTAIN_DOCUMENTS'), label: t('SIDEBAR.CAPTAIN_DOCUMENTS'),
to: accountScopedRoute('captain_documents_index'), activeOn: ['captain_assistants_documents_index'],
to: accountScopedRoute('captain_assistants_index', {
navigationPath: 'captain_assistants_documents_index',
}),
}, },
{ {
name: 'Responses', name: 'Scenarios',
label: t('SIDEBAR.CAPTAIN_RESPONSES'), label: t('SIDEBAR.CAPTAIN_SCENARIOS'),
to: accountScopedRoute('captain_responses_index'), 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: 'Tools', name: 'Tools',
label: t('SIDEBAR.CAPTAIN_TOOLS'), label: t('SIDEBAR.CAPTAIN_TOOLS'),
to: accountScopedRoute('captain_tools_index'), 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',
}),
}, },
], ],
}, },
@@ -19,19 +19,46 @@ export function useSidebarContext() {
return '/'; 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 => { const resolvePermissions = to => {
if (to) return router.resolve(to)?.meta?.permissions ?? []; if (!to) return [];
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 ?? [];
}; };
const resolveFeatureFlag = to => { const resolveFeatureFlag = to => {
if (to) return router.resolve(to)?.meta?.featureFlag || ''; if (!to) return '';
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 || '';
}; };
const resolveInstallationType = to => { const resolveInstallationType = to => {
if (to) return router.resolve(to)?.meta?.installationTypes || []; if (!to) return [];
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 || [];
}; };
const isAllowed = to => { const isAllowed = to => {
@@ -1,5 +1,7 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed, ref, onMounted, nextTick } from 'vue';
import { useResizeObserver } from '@vueuse/core';
const props = defineProps({ const props = defineProps({
initialActiveTab: { initialActiveTab: {
type: Number, type: Number,
@@ -22,6 +24,32 @@ const emit = defineEmits(['tabChanged']);
const activeTab = computed(() => props.initialActiveTab); const activeTab = computed(() => props.initialActiveTab);
const tabRefs = ref([]);
const indicatorStyle = ref({});
const enableTransition = ref(false);
const activeElement = computed(() => tabRefs.value[activeTab.value]);
const updateIndicator = () => {
if (!activeElement.value) return;
indicatorStyle.value = {
left: `${activeElement.value.offsetLeft}px`,
width: `${activeElement.value.offsetWidth}px`,
};
};
useResizeObserver(activeElement, () => {
if (enableTransition.value || !activeElement.value) updateIndicator();
});
onMounted(() => {
updateIndicator();
nextTick(() => {
enableTransition.value = true;
});
});
const selectTab = index => { const selectTab = index => {
emit('tabChanged', props.tabs[index]); emit('tabChanged', props.tabs[index]);
}; };
@@ -37,20 +65,30 @@ const showDivider = index => {
</script> </script>
<template> <template>
<div class="flex items-center h-8 rounded-lg bg-n-alpha-1 w-fit"> <div
class="relative flex items-center h-8 rounded-lg bg-n-alpha-1 w-fit transition-all duration-200 ease-out has-[button:active]:scale-[1.01]"
>
<div
class="absolute rounded-lg bg-n-solid-active shadow-sm pointer-events-none h-8 outline-1 outline outline-n-container inset-y-0"
:class="{ 'transition-all duration-300 ease-out': enableTransition }"
:style="indicatorStyle"
/>
<template v-for="(tab, index) in tabs" :key="index"> <template v-for="(tab, index) in tabs" :key="index">
<button <button
class="relative px-4 truncate py-1.5 text-sm border-0 outline-1 outline rounded-lg transition-colors duration-300 ease-in-out hover:text-n-brand" :ref="el => (tabRefs[index] = el)"
class="relative z-10 px-4 truncate py-1.5 text-sm border-0 outline-1 outline-transparent rounded-lg transition-all duration-200 ease-out hover:text-n-brand active:scale-[1.02]"
:class="[ :class="[
activeTab === index activeTab === index
? 'text-n-blue-text bg-n-solid-active outline-n-container dark:outline-transparent' ? 'text-n-blue-text scale-100'
: 'text-n-slate-10 outline-transparent h-8', : 'text-n-slate-10 scale-[0.98]',
]" ]"
@click="selectTab(index)" @click="selectTab(index)"
> >
{{ tab.label }} {{ tab.count ? `(${tab.count})` : '' }} {{ tab.label }} {{ tab.count ? `(${tab.count})` : '' }}
</button> </button>
<div <div
v-if="index < tabs.length - 1"
class="w-px h-3.5 rounded my-auto transition-colors duration-300 ease-in-out" class="w-px h-3.5 rounded my-auto transition-colors duration-300 ease-in-out"
:class=" :class="
showDivider(index) showDivider(index)
@@ -15,6 +15,7 @@ import {
CMD_RESOLVE_CONVERSATION, CMD_RESOLVE_CONVERSATION,
} from 'dashboard/helper/commandbar/events'; } from 'dashboard/helper/commandbar/events';
import ButtonGroup from 'dashboard/components-next/buttonGroup/ButtonGroup.vue';
import Button from 'dashboard/components-next/button/Button.vue'; import Button from 'dashboard/components-next/button/Button.vue';
const store = useStore(); const store = useStore();
@@ -133,7 +134,7 @@ useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);
<template> <template>
<div class="relative flex items-center justify-end resolve-actions"> <div class="relative flex items-center justify-end resolve-actions">
<div <ButtonGroup
class="rounded-lg shadow outline-1 outline flex-shrink-0" class="rounded-lg shadow outline-1 outline flex-shrink-0"
:class="!showOpenButton ? 'outline-n-container' : 'outline-transparent'" :class="!showOpenButton ? 'outline-n-container' : 'outline-transparent'"
> >
@@ -142,6 +143,7 @@ useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);
:label="t('CONVERSATION.HEADER.RESOLVE_ACTION')" :label="t('CONVERSATION.HEADER.RESOLVE_ACTION')"
size="sm" size="sm"
color="slate" color="slate"
no-animation
class="ltr:rounded-r-none rtl:rounded-l-none !outline-0" class="ltr:rounded-r-none rtl:rounded-l-none !outline-0"
:is-loading="isLoading" :is-loading="isLoading"
@click="onCmdResolveConversation" @click="onCmdResolveConversation"
@@ -151,6 +153,7 @@ useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);
:label="t('CONVERSATION.HEADER.REOPEN_ACTION')" :label="t('CONVERSATION.HEADER.REOPEN_ACTION')"
size="sm" size="sm"
color="slate" color="slate"
no-animation
class="ltr:rounded-r-none rtl:rounded-l-none !outline-0" class="ltr:rounded-r-none rtl:rounded-l-none !outline-0"
:is-loading="isLoading" :is-loading="isLoading"
@click="onCmdOpenConversation" @click="onCmdOpenConversation"
@@ -160,6 +163,7 @@ useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);
:label="t('CONVERSATION.HEADER.OPEN_ACTION')" :label="t('CONVERSATION.HEADER.OPEN_ACTION')"
size="sm" size="sm"
color="slate" color="slate"
no-animation
:is-loading="isLoading" :is-loading="isLoading"
@click="onCmdOpenConversation" @click="onCmdOpenConversation"
/> />
@@ -169,12 +173,13 @@ useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);
icon="i-lucide-chevron-down" icon="i-lucide-chevron-down"
:disabled="isLoading" :disabled="isLoading"
size="sm" size="sm"
no-animation
class="ltr:rounded-l-none rtl:rounded-r-none !outline-0" class="ltr:rounded-l-none rtl:rounded-r-none !outline-0"
color="slate" color="slate"
trailing-icon trailing-icon
@click="openDropdown" @click="openDropdown"
/> />
</div> </ButtonGroup>
<div <div
v-if="showActionsDropdown" v-if="showActionsDropdown"
v-on-clickaway="closeDropdown" v-on-clickaway="closeDropdown"
@@ -302,7 +302,16 @@ function isBodyEmpty(content) {
} }
function handleEmptyBodyWithSignature() { function handleEmptyBodyWithSignature() {
const { schema, tr } = state; const { schema, tr, doc } = state;
const isEmptyParagraph = node =>
node && node.type === schema.nodes.paragraph && node.content.size === 0;
// Check if empty paragraph already exists to prevent duplicates when toggling signatures
if (isEmptyParagraph(doc.firstChild)) {
focusEditorInputField('start');
return;
}
// create a paragraph node and // create a paragraph node and
// start a transaction to append it at the end // start a transaction to append it at the end
@@ -59,7 +59,7 @@ const translateValue = computed(() => {
<template> <template>
<button <button
class="flex items-center w-auto h-8 p-1 transition-all border rounded-full bg-n-alpha-2 group relative duration-300 ease-in-out z-0" class="flex items-center w-auto h-8 p-1 transition-all border rounded-full bg-n-alpha-2 group relative duration-300 ease-in-out z-0 active:scale-[0.995] active:duration-75"
:disabled="disabled" :disabled="disabled"
:class="{ :class="{
'cursor-not-allowed': disabled, 'cursor-not-allowed': disabled,
@@ -14,7 +14,7 @@ import {
} from 'dashboard/constants/automation'; } from 'dashboard/constants/automation';
/** /**
* This is a shared composables that holds utilites used to build dropdown and file options * This is a shared composables that holds utilities used to build dropdown and file options
* @returns {Object} An object containing various automation-related functions and computed properties. * @returns {Object} An object containing various automation-related functions and computed properties.
*/ */
export default function useAutomationValues() { export default function useAutomationValues() {
@@ -21,7 +21,7 @@ export const initializeAudioAlerts = user => {
enable_audio_alerts: audioAlertType, enable_audio_alerts: audioAlertType,
alert_if_unread_assigned_conversation_exist: alertIfUnreadConversationExist, alert_if_unread_assigned_conversation_exist: alertIfUnreadConversationExist,
notification_tone: audioAlertTone, notification_tone: audioAlertTone,
// UI Settings can be undefined initally as we don't send the // UI Settings can be undefined initially as we don't send the
// entire payload for the user during the signup process. // entire payload for the user during the signup process.
} = uiSettings || {}; } = uiSettings || {};
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "حذف جهة الاتصال"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "Изтриване на контакта"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "Διαγραφή Επαφής"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -341,6 +341,11 @@
"CAPTAIN": { "CAPTAIN": {
"NAME": "Captain", "NAME": "Captain",
"HEADER_KNOW_MORE": "Know more", "HEADER_KNOW_MORE": "Know more",
"ASSISTANT_SWITCHER": {
"ASSISTANTS": "Assistants",
"SWITCH_ASSISTANT": "Switch between assistants",
"NEW_ASSISTANT": "Create Assistant"
},
"COPILOT": { "COPILOT": {
"TITLE": "Copilot", "TITLE": "Copilot",
"TRY_THESE_PROMPTS": "Try these prompts", "TRY_THESE_PROMPTS": "Try these prompts",
@@ -480,9 +485,7 @@
"NOT_FOUND": "Could not find the assistant. Please try again." "NOT_FOUND": "Could not find the assistant. Please try again."
}, },
"SETTINGS": { "SETTINGS": {
"BREADCRUMB": { "HEADER": "Settings",
"ASSISTANT": "Assistant"
},
"BASIC_SETTINGS": { "BASIC_SETTINGS": {
"TITLE": "Basic settings", "TITLE": "Basic settings",
"DESCRIPTION": "Customize what the assistant says when ending a conversation or transferring to a human." "DESCRIPTION": "Customize what the assistant says when ending a conversation or transferring to a human."
@@ -499,15 +502,16 @@
"TITLE": "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." "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": { "RESPONSE_GUIDELINES": {
"TITLE": "Response guidelines", "TITLE": "Response guidelines",
"DESCRIPTION": "The vibe and structure of your assistants replies—clear and friendly? Short and snappy? Detailed and formal?" "DESCRIPTION": "The vibe and structure of your assistants 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": { "OPTIONS": {
@@ -526,9 +530,6 @@
"GUARDRAILS": { "GUARDRAILS": {
"TITLE": "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.", "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": { "BULK_ACTION": {
"SELECTED": "{count} item selected | {count} items selected", "SELECTED": "{count} item selected | {count} items selected",
"SELECT_ALL": "Select all ({count})", "SELECT_ALL": "Select all ({count})",
@@ -574,9 +575,6 @@
"RESPONSE_GUIDELINES": { "RESPONSE_GUIDELINES": {
"TITLE": "Response Guidelines", "TITLE": "Response Guidelines",
"DESCRIPTION": "The vibe and structure of your assistants replies—clear and friendly? Short and snappy? Detailed and formal?", "DESCRIPTION": "The vibe and structure of your assistants replies—clear and friendly? Short and snappy? Detailed and formal?",
"BREADCRUMB": {
"TITLE": "Response Guidelines"
},
"BULK_ACTION": { "BULK_ACTION": {
"SELECTED": "{count} item selected | {count} items selected", "SELECTED": "{count} item selected | {count} items selected",
"SELECT_ALL": "Select all ({count})", "SELECT_ALL": "Select all ({count})",
@@ -622,9 +620,6 @@
"SCENARIOS": { "SCENARIOS": {
"TITLE": "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.”", "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": { "BULK_ACTION": {
"SELECTED": "{count} item selected | {count} items selected", "SELECTED": "{count} item selected | {count} items selected",
"SELECT_ALL": "Select all ({count})", "SELECT_ALL": "Select all ({count})",
@@ -722,11 +717,6 @@
"NAME": { "NAME": {
"LABEL": "Document Name (Optional)", "LABEL": "Document Name (Optional)",
"PLACEHOLDER": "Enter a name for the document" "PLACEHOLDER": "Enter a name for the document"
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select the assistant",
"ERROR": "The assistant field is required"
} }
}, },
"DELETE": { "DELETE": {
@@ -921,12 +911,6 @@
"LABEL": "Answer", "LABEL": "Answer",
"PLACEHOLDER": "Enter the answer here", "PLACEHOLDER": "Enter the answer here",
"ERROR": "Please provide a valid answer." "ERROR": "Please provide a valid answer."
},
"ASSISTANT": {
"LABEL": "Assistant",
"PLACEHOLDER": "Select an assistant",
"ERROR": "Please select an assistant."
} }
}, },
"EDIT": { "EDIT": {
@@ -251,6 +251,12 @@
"ACCOUNT_SUSPENDED": { "ACCOUNT_SUSPENDED": {
"TITLE": "Account Suspended", "TITLE": "Account Suspended",
"MESSAGE": "Your account is suspended. Please reach out to the support team for more information." "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": { "COMPONENTS": {
@@ -305,6 +311,10 @@
"CAPTAIN_DOCUMENTS": "Documents", "CAPTAIN_DOCUMENTS": "Documents",
"CAPTAIN_RESPONSES": "FAQs", "CAPTAIN_RESPONSES": "FAQs",
"CAPTAIN_TOOLS": "Tools", "CAPTAIN_TOOLS": "Tools",
"CAPTAIN_SCENARIOS": "Scenarios",
"CAPTAIN_PLAYGROUND": "Playground",
"CAPTAIN_INBOXES": "Inboxes",
"CAPTAIN_SETTINGS": "Settings",
"HOME": "Home", "HOME": "Home",
"AGENTS": "Agents", "AGENTS": "Agents",
"AGENT_BOTS": "Bots", "AGENT_BOTS": "Bots",
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "حذف مخاطب"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "מחק איש קשר"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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ð"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -13,7 +13,7 @@
"BROWSER": "Browser", "BROWSER": "Browser",
"OS": "Sistema Operativo", "OS": "Sistema Operativo",
"INITIATED_FROM": "Iniziato da", "INITIATED_FROM": "Iniziato da",
"INITIATED_AT": "Iniziato alle", "INITIATED_AT": "Avviata alle",
"IP_ADDRESS": "Indirizzo IP", "IP_ADDRESS": "Indirizzo IP",
"CREATED_AT_LABEL": "Creato il", "CREATED_AT_LABEL": "Creato il",
"NEW_MESSAGE": "Nuovo messaggio", "NEW_MESSAGE": "Nuovo messaggio",
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "Nessuna etichetta disponibile.", "NO_LABELS_FOUND": "Nessuna etichetta disponibile.",
"SELECTED_COUNT": "{count} selezionate", "SELECTED_COUNT": "{count} selezionate",
"CLEAR_SELECTION": "Annulla selezione", "CLEAR_SELECTION": "Annulla selezione",
"SELECT_ALL": "Seleziona tutto ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -1,6 +1,6 @@
{ {
"CONVERSATION": { "CONVERSATION": {
"SELECT_A_CONVERSATION": "Si prega di selezionare una conversazione dal pannello a sinistra", "SELECT_A_CONVERSATION": "Seleziona una conversazione dal pannello a sinistra",
"CSAT_REPLY_MESSAGE": "Valuta la conversazione", "CSAT_REPLY_MESSAGE": "Valuta la conversazione",
"404": "Siamo spiacenti, non siamo riusciti a trovare la conversazione. Riprova", "404": "Siamo spiacenti, non siamo riusciti a trovare la conversazione. Riprova",
"SWITCH_VIEW_LAYOUT": "Cambia layout", "SWITCH_VIEW_LAYOUT": "Cambia layout",
@@ -6,7 +6,7 @@
"LOADING": "Caricamento notifiche", "LOADING": "Caricamento notifiche",
"404": "Non ci sono notifiche attive in questo gruppo.", "404": "Non ci sono notifiche attive in questo gruppo.",
"NO_NOTIFICATIONS": "Nessuna notifica", "NO_NOTIFICATIONS": "Nessuna notifica",
"NOTE": "Notifiche da tutte le inbox sottoscritte", "NOTE": "Notifiche da tutte le Inbox",
"NO_MESSAGES_AVAILABLE": "Oops! Impossibile recuperare i messaggi", "NO_MESSAGES_AVAILABLE": "Oops! Impossibile recuperare i messaggi",
"SNOOZED_UNTIL": "Posticipata fino a", "SNOOZED_UNTIL": "Posticipata fino a",
"SNOOZED_UNTIL_TOMORROW": "Posticipata fino a domani", "SNOOZED_UNTIL_TOMORROW": "Posticipata fino a domani",
@@ -45,8 +45,8 @@
"MARK_AS_UNREAD": "Segna come da leggere", "MARK_AS_UNREAD": "Segna come da leggere",
"SNOOZE": "Posticipa", "SNOOZE": "Posticipa",
"DELETE": "Elimina", "DELETE": "Elimina",
"MARK_ALL_READ": "Segna tutto come letto", "MARK_ALL_READ": "Segna tutte come lette",
"DELETE_ALL": "Elimina tutto", "DELETE_ALL": "Elimina tutte",
"DELETE_ALL_READ": "Elimina tutte le lette" "DELETE_ALL_READ": "Elimina tutte le lette"
}, },
"DISPLAY_MENU": { "DISPLAY_MENU": {
@@ -252,7 +252,7 @@
}, },
"BUSINESS_ACCOUNT_ID": { "BUSINESS_ACCOUNT_ID": {
"LABEL": "ID Account Business", "LABEL": "ID Account Business",
"PLACEHOLDER": "Si prega di inserire l'ID dell'account business ottenuto dalla dashboard sviluppatore di Facebook.", "PLACEHOLDER": "Inserisci l'ID dell'Account Business ottenuto dalla dashboard sviluppatore di Facebook.",
"ERROR": "Inserisci un valore valido." "ERROR": "Inserisci un valore valido."
}, },
"WEBHOOK_VERIFY_TOKEN": { "WEBHOOK_VERIFY_TOKEN": {
@@ -107,7 +107,7 @@
}, },
"HELP_TEXT": { "HELP_TEXT": {
"TITLE": "Stai utilizzando l'integrazione Slack", "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à dellintegrazione:\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.", "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à dellintegrazione:\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.",
"SELECTED": "selezionato" "SELECTED": "selezionato"
}, },
"SELECT_CHANNEL": { "SELECT_CHANNEL": {
@@ -472,7 +472,7 @@
"FEATURES": { "FEATURES": {
"TITLE": "Funzionalità", "TITLE": "Funzionalità",
"ALLOW_CONVERSATION_FAQS": "Genera FAQ dalle conversazioni risolte", "ALLOW_CONVERSATION_FAQS": "Genera FAQ dalle conversazioni risolte",
"ALLOW_MEMORIES": "Cattura i dettagli chiave come memorie dalle interazioni con i clienti.", "ALLOW_MEMORIES": "Salva memorie e dettagli chiave dalle interazioni con i clienti.",
"ALLOW_CITATIONS": "Includi citazioni alle fonti nelle risposte" "ALLOW_CITATIONS": "Includi citazioni alle fonti nelle risposte"
} }
}, },
@@ -694,8 +694,8 @@
"HEADER": "Documents", "HEADER": "Documents",
"ADD_NEW": "Create a new document", "ADD_NEW": "Create a new document",
"RELATED_RESPONSES": { "RELATED_RESPONSES": {
"TITLE": "Related FAQs", "TITLE": "FAQ Correlate",
"DESCRIPTION": "These FAQs are generated directly from the document." "DESCRIPTION": "Queste FAQ vengono generate direttamente dai Documenti."
}, },
"FORM_DESCRIPTION": "Enter the URL of the document to add it as a knowledge source and choose the assistant to associate it with.", "FORM_DESCRIPTION": "Enter the URL of the document to add it as a knowledge source and choose the assistant to associate it with.",
"CREATE": { "CREATE": {
@@ -740,7 +740,7 @@
"ERROR_MESSAGE": "Si è verificato un errore durante l'eliminazione del documento, riprova." "ERROR_MESSAGE": "Si è verificato un errore durante l'eliminazione del documento, riprova."
}, },
"OPTIONS": { "OPTIONS": {
"VIEW_RELATED_RESPONSES": "Visualizza Risposte Correlate", "VIEW_RELATED_RESPONSES": "Visualizza FAQ Correlate",
"DELETE_DOCUMENT": "Elimina Documento" "DELETE_DOCUMENT": "Elimina Documento"
}, },
"EMPTY_STATE": { "EMPTY_STATE": {
@@ -895,7 +895,7 @@
"FILTER": { "FILTER": {
"ASSISTANT": "Assistente: {selected}", "ASSISTANT": "Assistente: {selected}",
"STATUS": "Stato: {selected}", "STATUS": "Stato: {selected}",
"ALL_ASSISTANTS": "Tutte" "ALL_ASSISTANTS": "Tutti"
}, },
"STATUS": { "STATUS": {
"TITLE": "Stato", "TITLE": "Stato",
@@ -193,7 +193,7 @@
} }
}, },
"LABEL_REPORTS": { "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.", "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...", "LOADING_CHART": "Caricamento dati grafici...",
"NO_ENOUGH_DATA": "Non ci sono abbastanza dati per generare il report, riprova più tardi.", "NO_ENOUGH_DATA": "Non ci sono abbastanza dati per generare il report, riprova più tardi.",
@@ -331,7 +331,7 @@
} }
}, },
"TEAM_REPORTS": { "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.", "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...", "LOADING_CHART": "Caricamento dati grafici...",
"NO_ENOUGH_DATA": "Non ci sono abbastanza dati per generare il report, riprova più tardi.", "NO_ENOUGH_DATA": "Non ci sono abbastanza dati per generare il report, riprova più tardi.",
@@ -399,7 +399,7 @@
} }
}, },
"CSAT_REPORTS": { "CSAT_REPORTS": {
"HEADER": "Rapporti CSAT", "HEADER": "Report CSAT",
"NO_RECORDS": "Non ci sono risposte ai sondaggi CSAT disponibili.", "NO_RECORDS": "Non ci sono risposte ai sondaggi CSAT disponibili.",
"DOWNLOAD": "Scarica report CSAT", "DOWNLOAD": "Scarica report CSAT",
"DOWNLOAD_FAILED": "Download dei report CSAT non riuscito", "DOWNLOAD_FAILED": "Download dei report CSAT non riuscito",
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "連絡先を削除"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "연락처 지우기"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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ą"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "Atlasīti {count}", "SELECTED_COUNT": "Atlasīti {count}",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "കോൺടാക്റ്റ് ഇല്ലാതാക്കുക"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Selecionar todas ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selecionado", "SELECTED_COUNT": "{count} selecionado",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Selecionar todos ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "Выбрано {count}", "SELECTED_COUNT": "Выбрано {count}",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Выбрать все ({count})" "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": "Удалить контакт"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "ลบผู้ติดต่อ"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -578,8 +578,19 @@
"DESCRIPTION": "Select the labels you want to add to the selected contacts.", "DESCRIPTION": "Select the labels you want to add to the selected contacts.",
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Seçimi temizle",
"SELECT_ALL": "Tümünü seç ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -32,7 +32,7 @@
"LOADING_CONVERSATIONS": "Sohbetler Yükleniyor\n", "LOADING_CONVERSATIONS": "Sohbetler Yükleniyor\n",
"CANNOT_REPLY": "Nedeniyle cevap veremezsiniz", "CANNOT_REPLY": "Nedeniyle cevap veremezsiniz",
"24_HOURS_WINDOW": "24 saat mesaj penceresi kısıtlaması", "24_HOURS_WINDOW": "24 saat mesaj penceresi kısıtlaması",
"API_HOURS_WINDOW": "Bu sohbete yalnızca {hours} saat içinde yanıt verebilirsiniz", "API_HOURS_WINDOW": "Bu konuşmaya yalnızca {hours} saat içinde cevap verebilirsiniz",
"NOT_ASSIGNED_TO_YOU": "Bu görüşme size atanmamış. Bu konuşmayı kendinize atamak ister misiniz?", "NOT_ASSIGNED_TO_YOU": "Bu görüşme size atanmamış. Bu konuşmayı kendinize atamak ister misiniz?",
"ASSIGN_TO_ME": "Bana ata", "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.", "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.", "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_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ı", "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ünecek. Bu sohbetten artık mesaj gönderemezsiniz.", "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.",
"REPLYING_TO": "Cevap veriyorsun:", "REPLYING_TO": "Cevap veriyorsun:",
"REMOVE_SELECTION": "Seçimi Kaldır", "REMOVE_SELECTION": "Seçimi Kaldır",
"DOWNLOAD": "İndir", "DOWNLOAD": "İndir",
@@ -139,8 +139,8 @@
} }
}, },
"DELETE_CONVERSATION": { "DELETE_CONVERSATION": {
"TITLE": "#{conversationId} numaralı sohbeti sil", "TITLE": "#{conversationId} numaralı konuşmayı sil",
"DESCRIPTION": "Bu sohbeti silmek istediğinizden emin misiniz?", "DESCRIPTION": "Bu konuşmayı silmek istediğinizden emin misiniz?",
"CONFIRM": "Sil" "CONFIRM": "Sil"
}, },
"CARD_CONTEXT_MENU": { "CARD_CONTEXT_MENU": {
@@ -159,7 +159,7 @@
"ASSIGN_LABEL": "Etiket ata", "ASSIGN_LABEL": "Etiket ata",
"AGENTS_LOADING": "Temsilciler Yükleniyor...", "AGENTS_LOADING": "Temsilciler Yükleniyor...",
"ASSIGN_TEAM": "Takım ata", "ASSIGN_TEAM": "Takım ata",
"DELETE": "Sohbeti sil", "DELETE": "Konuşmayı sil",
"OPEN_IN_NEW_TAB": "Yeni sekmede aç", "OPEN_IN_NEW_TAB": "Yeni sekmede aç",
"COPY_LINK": "Konuşma bağlantısını kopyala", "COPY_LINK": "Konuşma bağlantısını kopyala",
"COPY_LINK_SUCCESS": "Konuşma bağlantısı panoya kopyalandı", "COPY_LINK_SUCCESS": "Konuşma bağlantısı panoya kopyalandı",
@@ -244,8 +244,8 @@
"ASSIGN_LABEL_SUCCESFUL": "Etiket başarıyla atandı", "ASSIGN_LABEL_SUCCESFUL": "Etiket başarıyla atandı",
"ASSIGN_LABEL_FAILED": "Etiket ataması yapılamadı", "ASSIGN_LABEL_FAILED": "Etiket ataması yapılamadı",
"CHANGE_TEAM": "Takım değişti", "CHANGE_TEAM": "Takım değişti",
"SUCCESS_DELETE_CONVERSATION": "Sohbet başarıyla silindi", "SUCCESS_DELETE_CONVERSATION": "Konuşma başarıyla silindi",
"FAIL_DELETE_CONVERSATION": "Sohbet silinemedi! Tekrar deneyin", "FAIL_DELETE_CONVERSATION": "Konuşma silinemedi! Tekrar deneyin",
"FILE_SIZE_LIMIT": "Dosya, {MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE} MB ek dosya sınırını aşıyor", "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", "MESSAGE_ERROR": "Bu mesaj gönderilemiyor, lütfen daha sonra tekrar deneyin",
"SENT_BY": "Tarafından gönderildi:", "SENT_BY": "Tarafından gönderildi:",
@@ -1,8 +1,8 @@
{ {
"GENERAL_SETTINGS": { "GENERAL_SETTINGS": {
"LIMIT_MESSAGES": { "LIMIT_MESSAGES": {
"CONVERSATION": "Sohbet sınırını aştınız. Hacker planı yalnızca 500 sohbete izin verir.", "CONVERSATION": "Konuşma limitini aştınız. Hacker planı yalnızca 500 konuşmaya izin vermektedir.",
"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.", "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.",
"AGENTS": "Temsilci sınırını aştınız. Planınız yalnızca {allowedAgents} temsilciye izin veriyor.", "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." "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", "BUTTON_TEXT": "Hesabınızı Silin",
"CONFIRM": { "CONFIRM": {
"TITLE": "Hesabı Sil", "TITLE": "Hesabı Sil",
"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.", "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.",
"BUTTON_TEXT": "Sil", "BUTTON_TEXT": "Sil",
"DISMISS": "İptal Et", "DISMISS": "İptal Et",
"PLACE_HOLDER": "{accountName} yazarak onaylayın" "PLACE_HOLDER": "Onaylamak için lütfen {accountName} yazın"
}, },
"SUCCESS": "Hesap silinmek üzere işaretlendi", "SUCCESS": "Hesap silinmek üzere işaretlendi",
"FAILURE": "Hesap silinemedi, tekrar deneyin!", "FAILURE": "Hesap silinemedi, tekrar deneyin!",
@@ -45,11 +45,11 @@
"NOTE": "API tabanlı bir entegrasyon oluşturuyorsanız bu kimlik gereklidir" "NOTE": "API tabanlı bir entegrasyon oluşturuyorsanız bu kimlik gereklidir"
}, },
"AUTO_RESOLVE": { "AUTO_RESOLVE": {
"TITLE": "Sohbetleri otomatik çöz", "TITLE": "Konuşmaları Otomatik Çöz",
"NOTE": "Bu yapılandırma, belirli bir re etkinlik olmadığında sohbeti otomatik olarak çözmenizi sağlar.", "NOTE": "Bu yapılandırma, belirli bir hareketsizlik süresinden sonra konuşmayı otomatik olarak çözümlemenize olanak tanır.",
"DURATION": { "DURATION": {
"LABEL": "Etkinliksizlik süresi", "LABEL": "Hareketsizlik süresi",
"HELP": "Sohbetin otomatik olarak çözüleceği etkinliksizlik süresi", "HELP": "Konuşmanın otomatik olarak çözümlenmesinden önceki hareketsizlik süresi",
"PLACEHOLDER": "30", "PLACEHOLDER": "30",
"ERROR": "Otomatik çözme süresi 10 dakika ile 999 gün arasında olmalıdır", "ERROR": "Otomatik çözme süresi 10 dakika ile 999 gün arasında olmalıdır",
"API": { "API": {
@@ -59,8 +59,8 @@
}, },
"MESSAGE": { "MESSAGE": {
"LABEL": "Özel otomatik çözüm mesajı", "LABEL": "Özel otomatik çözüm mesajı",
"PLACEHOLDER": "Sohbet, 15 gün etkinlik olmadığı için sistem tarafından çözüldü olarak işaretlendi", "PLACEHOLDER": "Konuşma, 15 günlük hareketsizlik nedeniyle sistem tarafından çözümlendi olarak işaretlendi",
"HELP": "Sohbet otomatik olarak çözüldükten sonra müşteriye gönderilen mesaj" "HELP": "Konuşma otomatik olarak çözüldükten sonra müşteriye gönderilen mesaj"
}, },
"PREFERENCES": "Tercihler", "PREFERENCES": "Tercihler",
"LABEL": { "LABEL": {
@@ -94,19 +94,19 @@
}, },
"AUTO_RESOLVE_IGNORE_WAITING": { "AUTO_RESOLVE_IGNORE_WAITING": {
"LABEL": "Yanıtsız sohbetleri hariç tut", "LABEL": "Yanıtsız sohbetleri hariç tut",
"HELP": "Etkinleştirildiğinde, sistem hâlâ temsilci yanıtı bekleyen sohbetleri çözmeyi atlayacaktır." "HELP": "Etkinleştirildiğinde, sistem hâlâ bir temsilcinin yanıtını bekleyen konuşmaları çözümlemeyi atlayacaktır."
}, },
"AUDIO_TRANSCRIPTION": { "AUDIO_TRANSCRIPTION": {
"TITLE": "Sesli Mesajları Yazıya Döktür", "TITLE": "Sesli Mesajları Metne Çevir",
"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.", "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.",
"API": { "API": {
"SUCCESS": "Sesli mesaj transkripsiyon ayarı başarıyla güncellendi", "SUCCESS": "Sesli mesaj transkripsiyon ayarı başarıyla güncellendi",
"ERROR": "Sesli yazıya dökme ayarı güncellenemedi" "ERROR": "Ses transkripsiyon ayarı güncellenirken hata oluştu"
} }
}, },
"AUTO_RESOLVE_DURATION": { "AUTO_RESOLVE_DURATION": {
"LABEL": "Çözüm için etkinliksizlik süresi", "LABEL": "Çözümleme için hareketsizlik süresi",
"HELP": "Bir sohbette etkinlik yoksa otomatik çözüm için süre", "HELP": "Bir konuşmanın, hiçbir etkinlik olmazsa otomatik olarak çözümlenmesi gereken süre",
"PLACEHOLDER": "30", "PLACEHOLDER": "30",
"ERROR": "Otomatik çözme süresi 10 dakika ile 999 gün arasında olmalıdır", "ERROR": "Otomatik çözme süresi 10 dakika ile 999 gün arasında olmalıdır",
"API": { "API": {
@@ -115,8 +115,8 @@
}, },
"UPDATE_BUTTON": "Güncelleme", "UPDATE_BUTTON": "Güncelleme",
"MESSAGE_LABEL": "Özel çözüm mesajı", "MESSAGE_LABEL": "Özel çözüm mesajı",
"MESSAGE_PLACEHOLDER": "Sohbet, 15 gün etkinlik olmadığı için sistem tarafından çözüldü olarak işaretlendi", "MESSAGE_PLACEHOLDER": "Konuşma, 15 günlük hareketsizlik nedeniyle sistem tarafından çözümlendi olarak işaretlendi",
"MESSAGE_HELP": "Bu mesaj, bir sohbet sistem tarafından etkinliksizlik nedeniyle otomatik olarak çözüldüğünde müşteriye gönderilir." "MESSAGE_HELP": "Bu mesaj, bir konuşma hareketsizlik nedeniyle sistem tarafından otomatik olarak çözümlendiğinde müşteriye gönderilir."
}, },
"FEATURES": { "FEATURES": {
"INBOUND_EMAIL_ENABLED": "Hesabınız için e-posta ile iletişim devre dışı bırakıldı.", "INBOUND_EMAIL_ENABLED": "Hesabınız için e-posta ile iletişim devre dışı bırakıldı.",
@@ -51,10 +51,10 @@
"INSTAGRAM": { "INSTAGRAM": {
"CONTINUE_WITH_INSTAGRAM": "Instagram ile devam et", "CONTINUE_WITH_INSTAGRAM": "Instagram ile devam et",
"CONNECT_YOUR_INSTAGRAM_PROFILE": "Instagram profilinizi bağlayın", "CONNECT_YOUR_INSTAGRAM_PROFILE": "Instagram profilinizi bağlayın",
"HELP": "Bu Instagram hesabı yeni Instagram kanal gelen kutusuna taşındı. Artık bu gelen kutusundan Instagram mesajı gönderip alamayacaksınız ", "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. ",
"ERROR_MESSAGE": "Instagram'a bağlanırken bir hata oluştu, lütfen tekrar deneyin", "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", "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ünecek. Eski gelen kutusu artık bu hesap için mesaj gönderip alamayacak.", "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.",
"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." "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": { "TWITTER": {
@@ -341,9 +341,9 @@
"TWILIO_STATUS_URL_TITLE": "Twilio Status Callback URL", "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." "TWILIO_STATUS_URL_SUBTITLE": "Configure this URL as the Status Callback URL on your Twilio phone number."
}, },
"SUBMIT_BUTTON": "Create Voice Channel", "SUBMIT_BUTTON": "Ses Kanalı Oluştur",
"API": { "API": {
"ERROR_MESSAGE": "We were not able to create the voice channel" "ERROR_MESSAGE": "Ses kanalı oluşturulamadı"
} }
}, },
"API_CHANNEL": { "API_CHANNEL": {
@@ -437,7 +437,7 @@
}, },
"FACEBOOK": { "FACEBOOK": {
"TITLE": "Facebook\n", "TITLE": "Facebook\n",
"DESCRIPTION": "Connect your Facebook page" "DESCRIPTION": "Facebook sayfanızı bağlayın"
}, },
"WHATSAPP": { "WHATSAPP": {
"TITLE": "WhatsApp", "TITLE": "WhatsApp",
@@ -445,31 +445,31 @@
}, },
"EMAIL": { "EMAIL": {
"TITLE": "E-Posta", "TITLE": "E-Posta",
"DESCRIPTION": "Connect with Gmail, Outlook, or other providers" "DESCRIPTION": "Gmail, Outlook veya diğer sağlayıcılarla bağlantı kurun"
}, },
"SMS": { "SMS": {
"TITLE": "SMS", "TITLE": "SMS",
"DESCRIPTION": "Integrate SMS channel with Twilio or bandwidth" "DESCRIPTION": "SMS kanalını Twilio veya Bandwidth ile entegre edin"
}, },
"API": { "API": {
"TITLE": "API", "TITLE": "API",
"DESCRIPTION": "Make a custom channel using our API" "DESCRIPTION": "API'mizi kullanarak özel bir kanal oluşturun"
}, },
"TELEGRAM": { "TELEGRAM": {
"TITLE": "Telegram", "TITLE": "Telegram",
"DESCRIPTION": "Configure Telegram channel using Bot token" "DESCRIPTION": "Bot token kullanarak Telegram kanalını yapılandırın"
}, },
"LINE": { "LINE": {
"TITLE": "Line", "TITLE": "Line",
"DESCRIPTION": "Integrate your Line channel" "DESCRIPTION": "Line kanalınızı entegre edin"
}, },
"INSTAGRAM": { "INSTAGRAM": {
"TITLE": "Instagram", "TITLE": "Instagram",
"DESCRIPTION": "Connect your instagram account" "DESCRIPTION": "Instagram hesabınızı bağlayın"
}, },
"VOICE": { "VOICE": {
"TITLE": "Ses", "TITLE": "Ses",
"DESCRIPTION": "Integrate with Twilio Voice" "DESCRIPTION": "Twilio Voice ile entegre edin"
} }
} }
}, },
@@ -526,9 +526,9 @@
"BUTTON_TEXT": "Beni oraya götür", "BUTTON_TEXT": "Beni oraya götür",
"MORE_SETTINGS": "Daha fazla ayar", "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.", "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": "Scan the QR code above to quickly test your WhatsApp inbox", "WHATSAPP_QR_INSTRUCTION": "WhatsApp gelen kutunuzu hızlıca test etmek için yukarıdaki QR kodu tarayın",
"MESSENGER_QR_INSTRUCTION": "Scan the QR code above to quickly test your Facebook Messenger inbox", "MESSENGER_QR_INSTRUCTION": "Facebook Messenger gelen kutunuzu hızlıca test etmek için yukarıdaki QR kodu tarayın",
"TELEGRAM_QR_INSTRUCTION": "Scan the QR code above to quickly test your Telegram inbox" "TELEGRAM_QR_INSTRUCTION": "Telegram gelen kutunuzu hızlıca test etmek için yukarıdaki QR kodu tarayın"
}, },
"REAUTH": "Yeniden yetkilendir", "REAUTH": "Yeniden yetkilendir",
"VIEW": "Görünüm", "VIEW": "Görünüm",
@@ -607,37 +607,37 @@
"BUSINESS_HOURS": "İş Saatleri", "BUSINESS_HOURS": "İş Saatleri",
"WIDGET_BUILDER": "Widget Oluşturucu", "WIDGET_BUILDER": "Widget Oluşturucu",
"BOT_CONFIGURATION": "Bot Yapılandırma", "BOT_CONFIGURATION": "Bot Yapılandırma",
"ACCOUNT_HEALTH": "Account Health", "ACCOUNT_HEALTH": "Hesap Sağlığı",
"CSAT": "CSAT" "CSAT": "CSAT"
}, },
"ACCOUNT_HEALTH": { "ACCOUNT_HEALTH": {
"TITLE": "Manage your WhatsApp account", "TITLE": "WhatsApp hesabınızı yönetin",
"DESCRIPTION": "Review your WhatsApp account status, messaging limits, and quality. Update settings or resolve issues if needed", "DESCRIPTION": "WhatsApp hesap durumunuzu, mesajlaşma limitlerinizi ve kalitesini inceleyin. Gerekirse ayarları güncelleyin veya sorunları çözün",
"GO_TO_SETTINGS": "Go to Meta Business Manager", "GO_TO_SETTINGS": "Meta Business Manager'a gidin",
"NO_DATA": "Health data is not available", "NO_DATA": "Sağlık verisi mevcut değil",
"FIELDS": { "FIELDS": {
"DISPLAY_PHONE_NUMBER": { "DISPLAY_PHONE_NUMBER": {
"LABEL": "Display phone number", "LABEL": "Telefon numarasını göster",
"TOOLTIP": "Phone number displayed to customers" "TOOLTIP": "Müşterilere gösterilen telefon numarası"
}, },
"VERIFIED_NAME": { "VERIFIED_NAME": {
"LABEL": "Business name", "LABEL": "İşletme adı",
"TOOLTIP": "Business name verified by WhatsApp" "TOOLTIP": "WhatsApp tarafından onaylanmış işletme adı"
}, },
"DISPLAY_NAME_STATUS": { "DISPLAY_NAME_STATUS": {
"LABEL": "Display name status", "LABEL": "Görünen adın durumu",
"TOOLTIP": "Status of your business name verification" "TOOLTIP": "İşletme adı doğrulamanızın durumu"
}, },
"QUALITY_RATING": { "QUALITY_RATING": {
"LABEL": "Quality rating", "LABEL": "Kalite puanı",
"TOOLTIP": "WhatsApp quality rating for your account" "TOOLTIP": "Hesabınız için WhatsApp kalite puanı"
}, },
"MESSAGING_LIMIT_TIER": { "MESSAGING_LIMIT_TIER": {
"LABEL": "Messaging limit tier", "LABEL": "Mesajlaşma limiti seviyesi",
"TOOLTIP": "Daily messaging limit for your account" "TOOLTIP": "Hesabınız için günlük mesajlaşma limiti"
}, },
"ACCOUNT_MODE": { "ACCOUNT_MODE": {
"LABEL": "Account mode", "LABEL": "Hesap modu",
"TOOLTIP": "Current operating mode of your WhatsApp account" "TOOLTIP": "Current operating mode of your WhatsApp account"
} }
}, },
@@ -784,7 +784,7 @@
}, },
"CSAT": { "CSAT": {
"TITLE": "CSAT'yi etkinleştir", "TITLE": "CSAT'yi etkinleştir",
"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.", "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.",
"DISPLAY_TYPE": { "DISPLAY_TYPE": {
"LABEL": "Görüntüleme türü" "LABEL": "Görüntüleme türü"
}, },
@@ -802,7 +802,7 @@
}, },
"SELECT_PLACEHOLDER": "etiketleri seç" "SELECT_PLACEHOLDER": "etiketleri seç"
}, },
"NOTE": "Not: CSAT anketleri her sohbet için yalnızca bir kez gönderilir", "NOTE": "Not: CSAT anketleri, konuşma başına yalnızca bir kez gönderilir",
"API": { "API": {
"SUCCESS_MESSAGE": "CSAT ayarları başarıyla güncellendi", "SUCCESS_MESSAGE": "CSAT ayarları başarıyla güncellendi",
"ERROR_MESSAGE": "CSAT ayarları güncellenemedi. Lütfen daha sonra tekrar deneyin." "ERROR_MESSAGE": "CSAT ayarları güncellenemedi. Lütfen daha sonra tekrar deneyin."
@@ -34,7 +34,7 @@
"SUBSCRIPTIONS": { "SUBSCRIPTIONS": {
"LABEL": "Olaylar", "LABEL": "Olaylar",
"EVENTS": { "EVENTS": {
"CONVERSATION_CREATED": "Görüşme Oluşturuldu", "CONVERSATION_CREATED": "Konuşma Oluşturuldu",
"CONVERSATION_STATUS_CHANGED": "Görüşme Durumu Değişti", "CONVERSATION_STATUS_CHANGED": "Görüşme Durumu Değişti",
"CONVERSATION_UPDATED": "Görüşme Güncellendi", "CONVERSATION_UPDATED": "Görüşme Güncellendi",
"MESSAGE_CREATED": "Mesaj Oluşturuldu", "MESSAGE_CREATED": "Mesaj Oluşturuldu",
@@ -42,8 +42,8 @@
"WEBWIDGET_TRIGGERED": "Kullanıcı tarafından canlı sohbet widget'ı açıldı", "WEBWIDGET_TRIGGERED": "Kullanıcı tarafından canlı sohbet widget'ı açıldı",
"CONTACT_CREATED": "Kişi Oluşturuldu", "CONTACT_CREATED": "Kişi Oluşturuldu",
"CONTACT_UPDATED": "Kişi Güncellendi", "CONTACT_UPDATED": "Kişi Güncellendi",
"CONVERSATION_TYPING_ON": "Sohbet Yazıyor Açık", "CONVERSATION_TYPING_ON": "Konuşmada Yazıyor Açık",
"CONVERSATION_TYPING_OFF": "Sohbet Yazıyor Kapalı" "CONVERSATION_TYPING_OFF": "Konuşmada Yazıyor Kapalı"
} }
}, },
"END_POINT": { "END_POINT": {
@@ -327,7 +327,7 @@
}, },
"CTA": { "CTA": {
"TITLE": "Linear'a bağlan", "TITLE": "Linear'a bağlan",
"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.", "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.",
"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.", "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" "BUTTON_TEXT": "Linear çalışma alanını bağlayın"
} }
@@ -348,7 +348,7 @@
"TITLE": "Copilot", "TITLE": "Copilot",
"TRY_THESE_PROMPTS": "Try these prompts", "TRY_THESE_PROMPTS": "Try these prompts",
"PANEL_TITLE": "Copilot ile başlayın", "PANEL_TITLE": "Copilot ile başlayın",
"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.", "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.",
"SEND_MESSAGE": "Mesajı Gönder...", "SEND_MESSAGE": "Mesajı Gönder...",
"EMPTY_MESSAGE": "Yanıt oluşturulurken bir hata oluştu. Lütfen tekrar deneyin.", "EMPTY_MESSAGE": "Yanıt oluşturulurken bir hata oluştu. Lütfen tekrar deneyin.",
"LOADER": "Captain is thinking", "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." "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": { "HIGH_PRIORITY": {
"LABEL": "Yüksek öncelikli sohbetler", "LABEL": "Yüksek öncelikli konuşmalar",
"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." "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."
}, },
"LIST_CONTACTS": { "LIST_CONTACTS": {
"LABEL": "Kişileri listele", "LABEL": "Kişileri listele",
@@ -383,7 +383,7 @@
"PLAYGROUND": { "PLAYGROUND": {
"USER": "Sen", "USER": "Sen",
"ASSISTANT": "Assistant", "ASSISTANT": "Assistant",
"MESSAGE_PLACEHOLDER": "Mesajınız...", "MESSAGE_PLACEHOLDER": "Mesajınızı yazın...",
"HEADER": "Oyun Alanı", "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.", "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." "CREDIT_NOTE": "Buradan gönderilen mesajlar Captain kredilerinize sayılacaktır."
@@ -194,7 +194,7 @@
}, },
"LABEL_REPORTS": { "LABEL_REPORTS": {
"HEADER": "Etiketler Genel Bakış", "HEADER": "Etiketler Genel Bakış",
"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.", "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.",
"LOADING_CHART": "Grafik verileri yükleniyor...", "LOADING_CHART": "Grafik verileri yükleniyor...",
"NO_ENOUGH_DATA": "Rapor oluşturmak için yeterli veri yok, Lütfen daha sonra tekrar deneyin.", "NO_ENOUGH_DATA": "Rapor oluşturmak için yeterli veri yok, Lütfen daha sonra tekrar deneyin.",
"DOWNLOAD_LABEL_REPORTS": "Etiket raporlarını indir", "DOWNLOAD_LABEL_REPORTS": "Etiket raporlarını indir",
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "Видалити контакт"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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": "کانٹیکٹ حذف کریں۔"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {
@@ -579,7 +579,18 @@
"NO_LABELS_FOUND": "No labels available yet.", "NO_LABELS_FOUND": "No labels available yet.",
"SELECTED_COUNT": "{count} selected", "SELECTED_COUNT": "{count} selected",
"CLEAR_SELECTION": "Clear selection", "CLEAR_SELECTION": "Clear selection",
"SELECT_ALL": "Select all ({count})" "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"
}
}, },
"COMPOSE_NEW_CONVERSATION": { "COMPOSE_NEW_CONVERSATION": {
"CONTACT_SEARCH": { "CONTACT_SEARCH": {

Some files were not shown because too many files have changed in this diff Show More