mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 18:22:53 +00:00
feat: add custom tool dialog
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
<script setup>
|
||||
import { defineModel, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
|
||||
const props = defineProps({
|
||||
authType: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: value => ['none', 'bearer', 'basic', 'api_key'].includes(value),
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const authConfig = defineModel('authConfig', {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.authType,
|
||||
() => {
|
||||
authConfig.value = {};
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Input
|
||||
v-if="authType === 'bearer'"
|
||||
v-model="authConfig.token"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.BEARER_TOKEN')"
|
||||
:placeholder="
|
||||
t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.BEARER_TOKEN_PLACEHOLDER')
|
||||
"
|
||||
/>
|
||||
<template v-else-if="authType === 'basic'">
|
||||
<Input
|
||||
v-model="authConfig.username"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.USERNAME')"
|
||||
:placeholder="
|
||||
t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.USERNAME_PLACEHOLDER')
|
||||
"
|
||||
/>
|
||||
<Input
|
||||
v-model="authConfig.password"
|
||||
type="password"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.PASSWORD')"
|
||||
:placeholder="
|
||||
t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.PASSWORD_PLACEHOLDER')
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="authType === 'api_key'">
|
||||
<Input
|
||||
v-model="authConfig.key"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.API_KEY')"
|
||||
:placeholder="
|
||||
t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.API_KEY_PLACEHOLDER')
|
||||
"
|
||||
/>
|
||||
<Input
|
||||
v-model="authConfig.value"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.API_VALUE')"
|
||||
:placeholder="
|
||||
t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_CONFIG.API_VALUE_PLACEHOLDER')
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
|
||||
|
||||
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
|
||||
import CustomToolForm from './CustomToolForm.vue';
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
|
||||
const dialogRef = ref(null);
|
||||
|
||||
const i18nKey = 'CAPTAIN.CUSTOM_TOOLS.CREATE';
|
||||
|
||||
const handleSubmit = async newTool => {
|
||||
try {
|
||||
await store.dispatch('captainCustomTools/create', newTool);
|
||||
useAlert(t(`${i18nKey}.SUCCESS_MESSAGE`));
|
||||
dialogRef.value.close();
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
parseAPIErrorResponse(error) || t(`${i18nKey}.ERROR_MESSAGE`);
|
||||
useAlert(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
dialogRef.value.close();
|
||||
};
|
||||
|
||||
defineExpose({ dialogRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog
|
||||
ref="dialogRef"
|
||||
width="2xl"
|
||||
:title="$t(`${i18nKey}.TITLE`)"
|
||||
:description="$t('CAPTAIN.CUSTOM_TOOLS.FORM_DESCRIPTION')"
|
||||
:show-cancel-button="false"
|
||||
:show-confirm-button="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<CustomToolForm @submit="handleSubmit" @cancel="handleCancel" />
|
||||
<template #footer />
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -0,0 +1,228 @@
|
||||
<script setup>
|
||||
import { reactive, computed, useTemplateRef } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, url } from '@vuelidate/validators';
|
||||
import { useMapGetter } from 'dashboard/composables/store';
|
||||
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Textarea from 'dashboard/components-next/textarea/Textarea.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
|
||||
import ParamRow from './ParamRow.vue';
|
||||
import AuthConfig from './AuthConfig.vue';
|
||||
|
||||
const emit = defineEmits(['submit', 'cancel']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const formState = {
|
||||
uiFlags: useMapGetter('captainCustomTools/getUIFlags'),
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
title: '',
|
||||
description: '',
|
||||
endpoint_url: '',
|
||||
http_method: 'GET',
|
||||
request_template: '',
|
||||
response_template: '',
|
||||
auth_type: 'none',
|
||||
auth_config: {},
|
||||
param_schema: [],
|
||||
};
|
||||
|
||||
const state = reactive({ ...initialState });
|
||||
|
||||
const DEFAULT_PARAM = {
|
||||
name: '',
|
||||
type: 'string',
|
||||
description: '',
|
||||
required: false,
|
||||
};
|
||||
|
||||
const validationRules = {
|
||||
title: { required },
|
||||
endpoint_url: { required, url },
|
||||
http_method: { required },
|
||||
auth_type: { required },
|
||||
};
|
||||
|
||||
const httpMethodOptions = computed(() => [
|
||||
{ value: 'GET', label: 'GET' },
|
||||
{ value: 'POST', label: 'POST' },
|
||||
]);
|
||||
|
||||
const authTypeOptions = computed(() => [
|
||||
{ value: 'none', label: t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_TYPES.NONE') },
|
||||
{ value: 'bearer', label: t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_TYPES.BEARER') },
|
||||
{ value: 'basic', label: t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_TYPES.BASIC') },
|
||||
{
|
||||
value: 'api_key',
|
||||
label: t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_TYPES.API_KEY'),
|
||||
},
|
||||
]);
|
||||
|
||||
const v$ = useVuelidate(validationRules, state);
|
||||
|
||||
const isLoading = computed(() => formState.uiFlags.value.creatingItem);
|
||||
|
||||
const getErrorMessage = (field, errorKey) => {
|
||||
return v$.value[field].$error
|
||||
? t(`CAPTAIN.CUSTOM_TOOLS.FORM.${errorKey}.ERROR`)
|
||||
: '';
|
||||
};
|
||||
|
||||
const formErrors = computed(() => ({
|
||||
title: getErrorMessage('title', 'TITLE'),
|
||||
endpoint_url: getErrorMessage('endpoint_url', 'ENDPOINT_URL'),
|
||||
}));
|
||||
|
||||
const paramsRef = useTemplateRef('paramsRef');
|
||||
|
||||
const isParamsValid = () => {
|
||||
if (!paramsRef.value || paramsRef.value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return paramsRef.value.every(param => param.validate());
|
||||
};
|
||||
|
||||
const removeParam = index => {
|
||||
state.param_schema.splice(index, 1);
|
||||
};
|
||||
|
||||
const addParam = () => {
|
||||
state.param_schema.push({ ...DEFAULT_PARAM });
|
||||
};
|
||||
|
||||
const handleCancel = () => emit('cancel');
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const isFormValid = await v$.value.$validate();
|
||||
if (!isFormValid || !isParamsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('submit', state);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form class="flex flex-col gap-4" @submit.prevent="handleSubmit">
|
||||
<Input
|
||||
v-model="state.title"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.TITLE.LABEL')"
|
||||
:placeholder="t('CAPTAIN.CUSTOM_TOOLS.FORM.TITLE.PLACEHOLDER')"
|
||||
:message="formErrors.title"
|
||||
:message-type="formErrors.title ? 'error' : 'info'"
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
v-model="state.description"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.DESCRIPTION.LABEL')"
|
||||
:placeholder="t('CAPTAIN.CUSTOM_TOOLS.FORM.DESCRIPTION.PLACEHOLDER')"
|
||||
:rows="2"
|
||||
/>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col gap-1 w-28">
|
||||
<label class="mb-0.5 text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.CUSTOM_TOOLS.FORM.HTTP_METHOD.LABEL') }}
|
||||
</label>
|
||||
<ComboBox
|
||||
v-model="state.http_method"
|
||||
:options="httpMethodOptions"
|
||||
class="[&>div>button]:bg-n-alpha-black2"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
v-model="state.endpoint_url"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.ENDPOINT_URL.LABEL')"
|
||||
:placeholder="t('CAPTAIN.CUSTOM_TOOLS.FORM.ENDPOINT_URL.PLACEHOLDER')"
|
||||
:message="formErrors.endpoint_url"
|
||||
:message-type="formErrors.endpoint_url ? 'error' : 'info'"
|
||||
class="flex-1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="mb-0.5 text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.CUSTOM_TOOLS.FORM.AUTH_TYPE.LABEL') }}
|
||||
</label>
|
||||
<ComboBox
|
||||
v-model="state.auth_type"
|
||||
:options="authTypeOptions"
|
||||
class="[&>div>button]:bg-n-alpha-black2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AuthConfig
|
||||
v-model:auth-config="state.auth_config"
|
||||
:auth-type="state.auth_type"
|
||||
/>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAMETERS.LABEL') }}
|
||||
</label>
|
||||
<p class="text-xs text-n-slate-11 -mt-1">
|
||||
{{ t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAMETERS.HELP_TEXT') }}
|
||||
</p>
|
||||
<ul v-if="state.param_schema.length > 0" class="grid gap-2 list-none">
|
||||
<ParamRow
|
||||
v-for="(param, index) in state.param_schema"
|
||||
:key="index"
|
||||
ref="paramsRef"
|
||||
v-model:name="param.name"
|
||||
v-model:type="param.type"
|
||||
v-model:description="param.description"
|
||||
v-model:required="param.required"
|
||||
@remove="removeParam(index)"
|
||||
/>
|
||||
</ul>
|
||||
<Button
|
||||
type="button"
|
||||
sm
|
||||
ghost
|
||||
blue
|
||||
icon="i-lucide-plus"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.ADD_PARAMETER')"
|
||||
@click="addParam"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
v-model="state.request_template"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.REQUEST_TEMPLATE.LABEL')"
|
||||
:placeholder="t('CAPTAIN.CUSTOM_TOOLS.FORM.REQUEST_TEMPLATE.PLACEHOLDER')"
|
||||
:rows="4"
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
v-model="state.response_template"
|
||||
:label="t('CAPTAIN.CUSTOM_TOOLS.FORM.RESPONSE_TEMPLATE.LABEL')"
|
||||
:placeholder="
|
||||
t('CAPTAIN.CUSTOM_TOOLS.FORM.RESPONSE_TEMPLATE.PLACEHOLDER')
|
||||
"
|
||||
:rows="4"
|
||||
/>
|
||||
|
||||
<div class="flex gap-3 justify-between items-center w-full">
|
||||
<Button
|
||||
type="button"
|
||||
variant="faded"
|
||||
color="slate"
|
||||
:label="t('CAPTAIN.FORM.CANCEL')"
|
||||
class="w-full bg-n-alpha-2 text-n-blue-text hover:bg-n-alpha-3"
|
||||
@click="handleCancel"
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
:label="t('CAPTAIN.FORM.CREATE')"
|
||||
class="w-full"
|
||||
:is-loading="isLoading"
|
||||
:disabled="isLoading"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup>
|
||||
import { computed, defineModel, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
|
||||
import Checkbox from 'dashboard/components-next/checkbox/Checkbox.vue';
|
||||
|
||||
const emit = defineEmits(['remove']);
|
||||
const { t } = useI18n();
|
||||
const showErrors = ref(false);
|
||||
|
||||
const name = defineModel('name', {
|
||||
type: String,
|
||||
required: true,
|
||||
});
|
||||
|
||||
const type = defineModel('type', {
|
||||
type: String,
|
||||
required: true,
|
||||
});
|
||||
|
||||
const description = defineModel('description', {
|
||||
type: String,
|
||||
default: '',
|
||||
});
|
||||
|
||||
const required = defineModel('required', {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
});
|
||||
|
||||
const paramTypeOptions = computed(() => [
|
||||
{ value: 'string', label: t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_TYPES.STRING') },
|
||||
{ value: 'number', label: t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_TYPES.NUMBER') },
|
||||
{
|
||||
value: 'boolean',
|
||||
label: t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_TYPES.BOOLEAN'),
|
||||
},
|
||||
{ value: 'array', label: t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_TYPES.ARRAY') },
|
||||
{ value: 'object', label: t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_TYPES.OBJECT') },
|
||||
]);
|
||||
|
||||
const validationError = computed(() => {
|
||||
if (!name.value || name.value.trim() === '') {
|
||||
return 'PARAM_NAME_REQUIRED';
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
watch([name, type, description, required], () => {
|
||||
showErrors.value = false;
|
||||
});
|
||||
|
||||
const validate = () => {
|
||||
showErrors.value = true;
|
||||
return !validationError.value;
|
||||
};
|
||||
|
||||
defineExpose({ validate });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li class="list-none">
|
||||
<div
|
||||
class="flex items-start gap-2 p-3 rounded-lg border border-n-weak bg-n-alpha-2"
|
||||
:class="{
|
||||
'animate-wiggle border-n-ruby-9': showErrors && validationError,
|
||||
}"
|
||||
>
|
||||
<div class="flex flex-col flex-1 gap-3">
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
v-model="name"
|
||||
:placeholder="t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_NAME.PLACEHOLDER')"
|
||||
class="flex-1 [&>input]:h-8 [&>input]:py-1.5 [&>input]:outline-offset-0"
|
||||
/>
|
||||
<ComboBox
|
||||
v-model="type"
|
||||
:options="paramTypeOptions"
|
||||
:placeholder="t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_TYPE.PLACEHOLDER')"
|
||||
class="w-36 [&>div>button]:h-8 [&>div>button]:py-1.5"
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
v-model="description"
|
||||
:placeholder="
|
||||
t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_DESCRIPTION.PLACEHOLDER')
|
||||
"
|
||||
class="[&>input]:h-8 [&>input]:py-1.5 [&>input]:outline-offset-0"
|
||||
/>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox v-model="required" />
|
||||
<span class="text-sm text-n-slate-11">
|
||||
{{ t('CAPTAIN.CUSTOM_TOOLS.FORM.PARAM_REQUIRED.LABEL') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<Button
|
||||
sm
|
||||
solid
|
||||
slate
|
||||
icon="i-lucide-trash"
|
||||
class="flex-shrink-0 mt-0.5"
|
||||
@click.stop="emit('remove')"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
v-if="showErrors && validationError"
|
||||
class="block mt-1 text-sm text-n-ruby-11"
|
||||
>
|
||||
{{ t(`CAPTAIN.CUSTOM_TOOLS.FORM.ERRORS.${validationError}`) }}
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
@@ -760,6 +760,87 @@
|
||||
"TITLE": "Custom Tools",
|
||||
"NOTE": "Custom tools allow your assistant to interact with external APIs and services. Create tools to fetch data, perform actions, or integrate with your existing systems to enhance your assistant's capabilities."
|
||||
}
|
||||
},
|
||||
"FORM_DESCRIPTION": "Configure your custom tool to connect with external APIs",
|
||||
"CREATE": {
|
||||
"TITLE": "Create Custom Tool",
|
||||
"SUCCESS_MESSAGE": "Custom tool created successfully",
|
||||
"ERROR_MESSAGE": "Failed to create custom tool"
|
||||
},
|
||||
"FORM": {
|
||||
"TITLE": {
|
||||
"LABEL": "Tool Name",
|
||||
"PLACEHOLDER": "Order Lookup",
|
||||
"ERROR": "Tool name is required"
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Description",
|
||||
"PLACEHOLDER": "Looks up order details by order ID"
|
||||
},
|
||||
"HTTP_METHOD": {
|
||||
"LABEL": "Method"
|
||||
},
|
||||
"ENDPOINT_URL": {
|
||||
"LABEL": "Endpoint URL",
|
||||
"PLACEHOLDER": "https://api.example.com/orders/{{ order_id }}",
|
||||
"ERROR": "Valid URL is required"
|
||||
},
|
||||
"AUTH_TYPE": {
|
||||
"LABEL": "Authentication Type"
|
||||
},
|
||||
"AUTH_TYPES": {
|
||||
"NONE": "None",
|
||||
"BEARER": "Bearer Token",
|
||||
"BASIC": "Basic Auth",
|
||||
"API_KEY": "API Key"
|
||||
},
|
||||
"AUTH_CONFIG": {
|
||||
"BEARER_TOKEN": "Bearer Token",
|
||||
"BEARER_TOKEN_PLACEHOLDER": "Enter your bearer token",
|
||||
"USERNAME": "Username",
|
||||
"USERNAME_PLACEHOLDER": "Enter username",
|
||||
"PASSWORD": "Password",
|
||||
"PASSWORD_PLACEHOLDER": "Enter password",
|
||||
"API_KEY": "Header Name",
|
||||
"API_KEY_PLACEHOLDER": "X-API-Key",
|
||||
"API_VALUE": "Header Value",
|
||||
"API_VALUE_PLACEHOLDER": "Enter API key value"
|
||||
},
|
||||
"PARAMETERS": {
|
||||
"LABEL": "Parameters",
|
||||
"HELP_TEXT": "Define the parameters that will be extracted from user queries"
|
||||
},
|
||||
"ADD_PARAMETER": "Add Parameter",
|
||||
"PARAM_NAME": {
|
||||
"PLACEHOLDER": "Parameter name (e.g., order_id)"
|
||||
},
|
||||
"PARAM_TYPE": {
|
||||
"PLACEHOLDER": "Type"
|
||||
},
|
||||
"PARAM_TYPES": {
|
||||
"STRING": "String",
|
||||
"NUMBER": "Number",
|
||||
"BOOLEAN": "Boolean",
|
||||
"ARRAY": "Array",
|
||||
"OBJECT": "Object"
|
||||
},
|
||||
"PARAM_DESCRIPTION": {
|
||||
"PLACEHOLDER": "Description of the parameter"
|
||||
},
|
||||
"PARAM_REQUIRED": {
|
||||
"LABEL": "Required"
|
||||
},
|
||||
"REQUEST_TEMPLATE": {
|
||||
"LABEL": "Request Body Template (Optional)",
|
||||
"PLACEHOLDER": "{\n \"order_id\": \"{{ order_id }}\"\n}"
|
||||
},
|
||||
"RESPONSE_TEMPLATE": {
|
||||
"LABEL": "Response Template (Optional)",
|
||||
"PLACEHOLDER": "Order {{ order_id }} status: {{ status }}"
|
||||
},
|
||||
"ERRORS": {
|
||||
"PARAM_NAME_REQUIRED": "Parameter name is required"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RESPONSES": {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
|
||||
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';
|
||||
|
||||
const store = useStore();
|
||||
|
||||
@@ -14,12 +15,18 @@ const customTools = useMapGetter('captainCustomTools/getRecords');
|
||||
const isFetching = computed(() => uiFlags.value.fetchingList);
|
||||
const customToolsMeta = useMapGetter('captainCustomTools/getMeta');
|
||||
|
||||
const createDialogRef = ref(null);
|
||||
|
||||
const fetchCustomTools = (page = 1) => {
|
||||
store.dispatch('captainCustomTools/get', { page });
|
||||
};
|
||||
|
||||
const onPageChange = page => fetchCustomTools(page);
|
||||
|
||||
const openCreateDialog = () => {
|
||||
createDialogRef.value.dialogRef.open();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchCustomTools();
|
||||
});
|
||||
@@ -37,13 +44,14 @@ onMounted(() => {
|
||||
:is-empty="!customTools.length"
|
||||
:feature-flag="FEATURE_FLAGS.CAPTAIN_CUSTOM_TOOLS"
|
||||
@update:current-page="onPageChange"
|
||||
@button-click="openCreateDialog"
|
||||
>
|
||||
<template #paywall>
|
||||
<CaptainPaywall />
|
||||
</template>
|
||||
|
||||
<template #emptyState>
|
||||
<CustomToolsPageEmptyState />
|
||||
<CustomToolsPageEmptyState @click="openCreateDialog" />
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
@@ -76,4 +84,6 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
</PageLayout>
|
||||
|
||||
<CreateCustomToolDialog ref="createDialogRef" />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user