mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +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
	 Shivam Mishra
					Shivam Mishra