Merge branch 'develop' into inline-edit
This commit is contained in:
@@ -23,6 +23,12 @@ const meta = {
|
||||
installationTypes: [INSTALLATION_TYPES.CLOUD, INSTALLATION_TYPES.ENTERPRISE],
|
||||
};
|
||||
|
||||
const metaCustomTools = {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN_CUSTOM_TOOLS,
|
||||
installationTypes: [INSTALLATION_TYPES.CLOUD, INSTALLATION_TYPES.ENTERPRISE],
|
||||
};
|
||||
|
||||
const metaV2 = {
|
||||
permissions: ['administrator', 'agent'],
|
||||
featureFlag: FEATURE_FLAGS.CAPTAIN_V2,
|
||||
@@ -46,7 +52,7 @@ const assistantRoutes = [
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/tools'),
|
||||
component: CustomToolsIndex,
|
||||
name: 'captain_tools_index',
|
||||
meta,
|
||||
meta: metaCustomTools,
|
||||
},
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/captain/:assistantId/scenarios'),
|
||||
|
||||
@@ -5,13 +5,14 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { usePolicy } from 'dashboard/composables/usePolicy';
|
||||
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import CaptainPaywall from 'dashboard/components-next/captain/pageComponents/Paywall.vue';
|
||||
import CustomToolsPageEmptyState from 'dashboard/components-next/captain/pageComponents/emptyStates/CustomToolsPageEmptyState.vue';
|
||||
import CreateCustomToolDialog from 'dashboard/components-next/captain/pageComponents/customTool/CreateCustomToolDialog.vue';
|
||||
import CustomToolCard from 'dashboard/components-next/captain/pageComponents/customTool/CustomToolCard.vue';
|
||||
import DeleteDialog from 'dashboard/components-next/captain/pageComponents/DeleteDialog.vue';
|
||||
|
||||
const store = useStore();
|
||||
const { isFeatureFlagEnabled } = usePolicy();
|
||||
const { isFeatureFlagEnabled, shouldShowPaywall } = usePolicy();
|
||||
|
||||
const SOFT_LIMIT = 10;
|
||||
const isV2 = computed(() => isFeatureFlagEnabled(FEATURE_FLAGS.CAPTAIN_V2));
|
||||
@@ -80,7 +81,9 @@ const onDeleteSuccess = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchCustomTools();
|
||||
if (!shouldShowPaywall(FEATURE_FLAGS.CAPTAIN_CUSTOM_TOOLS)) {
|
||||
fetchCustomTools();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -89,6 +92,7 @@ onMounted(() => {
|
||||
:header-title="$t('CAPTAIN.CUSTOM_TOOLS.HEADER')"
|
||||
:button-label="$t('CAPTAIN.CUSTOM_TOOLS.ADD_NEW')"
|
||||
:button-policy="['administrator']"
|
||||
:feature-flag="FEATURE_FLAGS.CAPTAIN_CUSTOM_TOOLS"
|
||||
:total-count="customToolsMeta.totalCount"
|
||||
:current-page="customToolsMeta.page"
|
||||
:show-pagination-footer="!isFetching && !!customTools.length"
|
||||
@@ -98,6 +102,10 @@ onMounted(() => {
|
||||
@update:current-page="onPageChange"
|
||||
@click="openCreateDialog"
|
||||
>
|
||||
<template #paywall>
|
||||
<CaptainPaywall feature-prefix="CAPTAIN.CUSTOM_TOOLS" />
|
||||
</template>
|
||||
|
||||
<template #emptyState>
|
||||
<CustomToolsPageEmptyState @click="openCreateDialog" />
|
||||
</template>
|
||||
|
||||
+63
-4
@@ -47,6 +47,7 @@ const formState = reactive({
|
||||
|
||||
const [showAccessToken, toggleAccessToken] = useToggle();
|
||||
const accessToken = ref('');
|
||||
const botSecret = ref('');
|
||||
|
||||
const v$ = useVuelidate(
|
||||
{
|
||||
@@ -179,15 +180,21 @@ const handleSubmit = async () => {
|
||||
: t('AGENT_BOTS.EDIT.API.SUCCESS_MESSAGE');
|
||||
useAlert(alertKey);
|
||||
|
||||
// Show access token after creation
|
||||
// Show access token and secret after creation
|
||||
if (isCreate) {
|
||||
const { access_token: responseAccessToken, id } = response || {};
|
||||
const {
|
||||
access_token: responseAccessToken,
|
||||
secret: responseSecret,
|
||||
id,
|
||||
} = response || {};
|
||||
|
||||
if (id && responseAccessToken) {
|
||||
accessToken.value = responseAccessToken;
|
||||
botSecret.value = responseSecret || '';
|
||||
toggleAccessToken(true);
|
||||
} else {
|
||||
accessToken.value = '';
|
||||
botSecret.value = '';
|
||||
dialogRef.value.close();
|
||||
}
|
||||
} else {
|
||||
@@ -212,14 +219,16 @@ const initializeForm = () => {
|
||||
thumbnail,
|
||||
bot_config: botConfig,
|
||||
access_token: botAccessToken,
|
||||
secret: botSecretValue,
|
||||
} = props.selectedBot;
|
||||
formState.botName = name || '';
|
||||
formState.botDescription = description || '';
|
||||
formState.botUrl = botUrl || botConfig?.webhook_url || '';
|
||||
formState.botAvatarUrl = thumbnail || '';
|
||||
|
||||
if (botAccessToken && props.type === MODAL_TYPES.EDIT) {
|
||||
accessToken.value = botAccessToken;
|
||||
if (props.type === MODAL_TYPES.EDIT) {
|
||||
if (botAccessToken) accessToken.value = botAccessToken;
|
||||
if (botSecretValue) botSecret.value = botSecretValue;
|
||||
}
|
||||
} else {
|
||||
resetForm();
|
||||
@@ -231,6 +240,24 @@ const onCopyToken = async value => {
|
||||
useAlert(t('AGENT_BOTS.ACCESS_TOKEN.COPY_SUCCESSFUL'));
|
||||
};
|
||||
|
||||
const onCopySecret = async value => {
|
||||
await copyTextToClipboard(value || botSecret.value);
|
||||
useAlert(t('AGENT_BOTS.SECRET.COPY_SUCCESS'));
|
||||
};
|
||||
|
||||
const onResetSecret = async () => {
|
||||
const response = await store.dispatch(
|
||||
'agentBots/resetSecret',
|
||||
props.selectedBot.id
|
||||
);
|
||||
if (response) {
|
||||
botSecret.value = response.secret;
|
||||
useAlert(t('AGENT_BOTS.SECRET.RESET_SUCCESS'));
|
||||
} else {
|
||||
useAlert(t('AGENT_BOTS.SECRET.RESET_ERROR'));
|
||||
}
|
||||
};
|
||||
|
||||
const onResetToken = async () => {
|
||||
const response = await store.dispatch(
|
||||
'agentBots/resetAccessToken',
|
||||
@@ -247,6 +274,7 @@ const onResetToken = async () => {
|
||||
const closeModal = () => {
|
||||
if (!showAccessToken.value) v$.value?.$reset();
|
||||
accessToken.value = '';
|
||||
botSecret.value = '';
|
||||
toggleAccessToken(false);
|
||||
};
|
||||
|
||||
@@ -318,6 +346,20 @@ defineExpose({ dialogRef });
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="botSecret && type === MODAL_TYPES.EDIT"
|
||||
class="flex flex-col gap-1"
|
||||
>
|
||||
<label class="mb-0.5 text-sm font-medium text-n-slate-12">
|
||||
{{ $t('AGENT_BOTS.SECRET.LABEL') }}
|
||||
</label>
|
||||
<AccessToken
|
||||
:value="botSecret"
|
||||
@on-copy="onCopySecret"
|
||||
@on-reset="onResetSecret"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="showAccessTokenInput" class="flex flex-col gap-1">
|
||||
<label
|
||||
v-if="type === MODAL_TYPES.EDIT"
|
||||
@@ -339,6 +381,23 @@ defineExpose({ dialogRef });
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="botSecret && showAccessToken && type === MODAL_TYPES.CREATE"
|
||||
class="flex flex-col gap-1"
|
||||
>
|
||||
<p class="text-sm text-n-slate-11">
|
||||
{{ $t('AGENT_BOTS.SECRET.CREATED_DESC') }}
|
||||
</p>
|
||||
<label class="mb-0.5 text-sm font-medium text-n-slate-12">
|
||||
{{ $t('AGENT_BOTS.SECRET.LABEL') }}
|
||||
</label>
|
||||
<AccessToken
|
||||
:value="botSecret"
|
||||
:show-reset-button="false"
|
||||
@on-copy="onCopySecret"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end w-full gap-2 px-0 py-2">
|
||||
<NextButton
|
||||
faded
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup>
|
||||
import { useAdmin } from 'dashboard/composables/useAdmin';
|
||||
import Icon from 'next/icon/Icon.vue';
|
||||
import ButtonV4 from 'next/button/Button.vue';
|
||||
|
||||
@@ -22,6 +23,11 @@ defineProps({
|
||||
});
|
||||
|
||||
const emit = defineEmits(['upgrade']);
|
||||
|
||||
// Cloud agents land on this modal too, but billing is admin-only — they need
|
||||
// the escalation message instead of a button they cannot use. Mirrors the
|
||||
// pattern in UpgradePage.vue.
|
||||
const { isAdmin } = useAdmin();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -47,11 +53,14 @@ const emit = defineEmits(['upgrade']);
|
||||
/>
|
||||
<p class="text-sm font-normal text-n-slate-11">
|
||||
{{ $t(`${featurePrefix}.${i18nKey}.UPGRADE_PROMPT`) }}
|
||||
<span v-if="!isOnChatwootCloud && !isSuperAdmin">
|
||||
<span v-if="isOnChatwootCloud && !isAdmin">
|
||||
{{ $t('GENERAL_SETTINGS.LIMIT_MESSAGES.NON_ADMIN') }}
|
||||
</span>
|
||||
<span v-else-if="!isOnChatwootCloud && !isSuperAdmin">
|
||||
{{ $t(`${featurePrefix}.ENTERPRISE_PAYWALL.ASK_ADMIN`) }}
|
||||
</span>
|
||||
</p>
|
||||
<template v-if="isOnChatwootCloud">
|
||||
<template v-if="isOnChatwootCloud && isAdmin">
|
||||
<ButtonV4 blue solid md @click="emit('upgrade')">
|
||||
{{ $t(`${featurePrefix}.PAYWALL.UPGRADE_NOW`) }}
|
||||
</ButtonV4>
|
||||
@@ -59,7 +68,7 @@ const emit = defineEmits(['upgrade']);
|
||||
{{ $t(`${featurePrefix}.PAYWALL.CANCEL_ANYTIME`) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="isSuperAdmin">
|
||||
<template v-else-if="!isOnChatwootCloud && isSuperAdmin">
|
||||
<a href="/super_admin" class="block w-full">
|
||||
<ButtonV4 solid blue md class="w-full">
|
||||
{{ $t(`${featurePrefix}.PAYWALL.UPGRADE_NOW`) }}
|
||||
|
||||
@@ -38,6 +38,8 @@ import Editor from 'dashboard/components-next/Editor/Editor.vue';
|
||||
import ColorPicker from 'dashboard/components-next/colorpicker/ColorPicker.vue';
|
||||
import SelectInput from 'dashboard/components-next/select/Select.vue';
|
||||
import Widget from 'dashboard/modules/widget-preview/components/Widget.vue';
|
||||
import AccessToken from 'dashboard/routes/dashboard/settings/profile/AccessToken.vue';
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -69,6 +71,7 @@ export default {
|
||||
SelectInput,
|
||||
AccountHealth,
|
||||
Widget,
|
||||
AccessToken,
|
||||
},
|
||||
mixins: [inboxMixin],
|
||||
setup() {
|
||||
@@ -362,6 +365,33 @@ export default {
|
||||
this.fetchSharedData();
|
||||
},
|
||||
methods: {
|
||||
async copyWebhookSecret(value) {
|
||||
await copyTextToClipboard(value);
|
||||
useAlert(
|
||||
this.$t(
|
||||
'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_SECRET.COPY_SUCCESS'
|
||||
)
|
||||
);
|
||||
},
|
||||
async resetWebhookSecret() {
|
||||
const response = await this.$store.dispatch(
|
||||
'inboxes/resetSecret',
|
||||
this.inbox.id
|
||||
);
|
||||
if (response) {
|
||||
useAlert(
|
||||
this.$t(
|
||||
'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_SECRET.RESET_SUCCESS'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
useAlert(
|
||||
this.$t(
|
||||
'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_SECRET.RESET_ERROR'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
fetchSharedData() {
|
||||
this.$store.dispatch('agents/get');
|
||||
this.$store.dispatch('teams/get');
|
||||
@@ -714,6 +744,21 @@ export default {
|
||||
/>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<SettingsFieldSection
|
||||
v-if="isAPIInbox && inbox.secret"
|
||||
:label="
|
||||
$t(
|
||||
'INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_WEBHOOK_SECRET.LABEL'
|
||||
)
|
||||
"
|
||||
>
|
||||
<AccessToken
|
||||
:value="inbox.secret"
|
||||
@on-copy="copyWebhookSecret"
|
||||
@on-reset="resetWebhookSecret"
|
||||
/>
|
||||
</SettingsFieldSection>
|
||||
|
||||
<SettingsFieldSection
|
||||
v-if="isAWebWidgetInbox"
|
||||
:label="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_DOMAIN.LABEL')"
|
||||
|
||||
Reference in New Issue
Block a user