Skip to main content
Glama
AssetFuncAttachModal.vue15.9 kB
<template> <Modal ref="modalRef" :size=" attachExisting && selectedExistingFunc.value !== nilId() ? '4xl' : 'md' " :title="title" @close="onClose" > <div class="flex flex-row max-h-[75vh]"> <div :class=" clsx( 'flex flex-col gap-y-4 min-w-[250px]', attachExisting && selectedExistingFunc.value !== nilId() ? 'mr-sm' : 'flex-grow', ) " > <SelectMenu v-model="funcKind" :options="funcKindOptions" label="Kind" type="dropdown" /> <VormInput v-if="!attachExisting" v-model="name" label="Name" placeholder="The name of the function" required /> <SelectMenu v-if="attachExisting" v-model="selectedExistingFunc" :options="existingFuncOptions" label="Existing function" required type="dropdown" canFilter /> <template v-if="funcKind.value === FuncKind.Action"> <div class="text-neutral-700 type-bold-sm dark:text-neutral-50"> <SiCheckBox id="create" v-model="isCreate" title="This action creates a resource" @update:model-value="setCreate" /> </div> <div class="text-neutral-700 type-bold-sm dark:text-neutral-50"> <SiCheckBox id="refresh" v-model="isRefresh" title="This action refreshes a resource" @update:model-value="setRefresh" /> </div> <div class="text-neutral-700 type-bold-sm dark:text-neutral-50"> <SiCheckBox id="delete" v-model="isDelete" title="This action deletes a resource" @update:model-value="setDelete" /> </div> <div class="text-neutral-700 type-bold-sm dark:text-neutral-50"> <SiCheckBox id="update" v-model="isUpdate" title="This action updates a resource" @update:model-value="setUpdate" /> </div> </template> <SelectMenu v-if="funcKind.value === FuncKind.Attribute" v-model="attributeOutputLocation" :options="attributeOutputLocationOptions" label="Output Location" required type="dropdown" canFilter /> <ErrorMessage v-if="createFuncReqStatus.isError && createFuncStarted" :requestStatus="createFuncReqStatus" /> <template v-if="attachExisting && funcKind.value === FuncKind.Attribute" > <h1 class="pt-2 text-neutral-700 type-bold-sm dark:text-neutral-50"> Expected Function Arguments: </h1> <h2 class="text-sm"> Below is the source of the data for each function argument listed. </h2> <ul> <li v-for="binding in editableBindings" :key="binding.funcArgumentId" > <h1 class="pt-2 text-neutral-700 type-bold-sm dark:text-neutral-50" > {{ funcArgumentName(binding.funcArgumentId) ?? "none" }} </h1> <SelectMenu v-model="binding.binding" :options="inputSourceOptions" canFilter /> </li> </ul> </template> <VButton :disabled="!attachEnabled" :label="`Attach ${existingOrNew} function`" :loading="showLoading" :loadingText="`Attaching ${existingOrNew} function...`" class="w-full" icon="plus" size="sm" tone="action" @click="onAttach" /> </div> <div v-if="attachExisting && selectedExistingFunc.value !== nilId()" class="overflow-y-scroll" > <div v-if="loadFuncDetailsReq?.value.isPending"> <RequestStatusMessage :requestStatus="loadFuncDetailsReq.value" /> </div> <CodeEditor v-if=" loadFuncDetailsReq && loadFuncDetailsReq?.value.isSuccess && selectedFuncCode " :id="codeEditorId" v-model="selectedFuncCode" :recordId="selectedExistingFunc.value as string" disabled noLint noVim typescript="yes" /> </div> </div> </Modal> </template> <script lang="ts" setup> import { computed, ref, watch } from "vue"; import { ErrorMessage, Modal, RequestStatusMessage, useModal, VButton, VormInput, } from "@si/vue-lib/design-system"; import clsx from "clsx"; import * as _ from "lodash-es"; import { ActionKind } from "@/api/sdf/dal/action"; import SiCheckBox from "@/components/SiCheckBox.vue"; import { CUSTOMIZABLE_FUNC_TYPES, CustomizableFuncKind, FuncKind, FuncId, FuncArgumentId, FuncBindingKind, FuncBinding, Authentication, CodeGeneration, Action, Attribute, Qualification, AttributeArgumentBinding, Management, } from "@/api/sdf/dal/func"; import { outputSocketsAndPropsFor, inputSocketsAndPropsFor, SchemaVariantId, } from "@/api/sdf/dal/schema"; import SelectMenu, { Option, GroupedOptions, } from "@/components/SelectMenu.vue"; import { useFuncStore } from "@/store/func/funcs.store"; import { useAssetStore } from "@/store/asset.store"; import { nilId } from "@/utils/nilId"; import CodeEditor from "./CodeEditor.vue"; const props = defineProps<{ schemaVariantId?: SchemaVariantId; }>(); const funcStore = useFuncStore(); const assetStore = useAssetStore(); const createFuncStarted = ref(false); const createFuncReqStatus = funcStore.getRequestStatus("CREATE_FUNC"); const schemaVariantId = computed(() => props.schemaVariantId ? assetStore.variantFromListById[props.schemaVariantId]?.schemaVariantId : undefined, ); const showLoading = computed(() => createFuncReqStatus.value.isPending); const funcKindOptions = Object.keys(CUSTOMIZABLE_FUNC_TYPES).map((kind) => ({ label: CUSTOMIZABLE_FUNC_TYPES[kind as CustomizableFuncKind]?.singularLabel, value: kind as FuncKind, })); const attachExisting = ref(false); const name = ref(""); const funcKind = ref({ label: "Actions", value: FuncKind.Action }); const noneFunction = { label: "select function", value: nilId(), }; const selectedExistingFunc = ref<Option>(noneFunction); const codeEditorId = computed(() => `func-${selectedExistingFunc.value.value}`); const selectedExistingFuncSummary = computed( () => funcStore.funcsById[selectedExistingFunc.value.value as string], ); const selectedFuncCode = ref<string>(""); const loadFuncDetailsReq = computed(() => { const id = selectedExistingFunc.value.value as string; if (id !== nilId()) return funcStore.getRequestStatus("FETCH_CODE", id); return undefined; }); watch( selectedExistingFunc, async ({ value }) => { const funcId = value as string; if (funcId !== nilId()) { await funcStore.FETCH_CODE(funcId); selectedFuncCode.value = funcStore.funcCodeById[funcId]?.code || ""; editableBindings.value = []; if (selectedExistingFuncSummary.value?.kind === FuncKind.Attribute) { editableBindings.value = selectedExistingFuncSummary.value.arguments?.map((a) => ({ funcArgumentId: a.id, binding: noneSource, })) ?? []; } } }, { immediate: true }, ); const existingFuncOptions = computed(() => Object.values(funcStore.funcsById) .filter((func) => func.kind === funcKind.value.value) .map((func) => ({ label: func.name, value: func.funcId, })), ); const noneOutput = { label: "select output location", value: nilId(), }; const attributeOutputLocation = ref<Option>(noneOutput); const attributeOutputLocationOptions = ref<GroupedOptions>({}); const attrToValidate = ref<string | undefined>(); const assetName = computed( () => assetStore.selectedSchemaVariant?.displayName ?? assetStore.selectedSchemaVariant?.schemaName ?? " none", ); const existingOrNew = computed(() => attachExisting.value ? "existing" : "new", ); const title = computed(() => assetName.value ? `Attach ${existingOrNew.value} function to "${assetName.value}"` : `Attach ${existingOrNew.value} function`, ); const modalRef = ref<InstanceType<typeof Modal>>(); const { open: openModal, close } = useModal(modalRef); const attachEnabled = computed(() => { const nameIsSet = attachExisting.value || !!(name.value && name.value.length > 0); const hasOutput = funcKind.value.value !== FuncKind.Attribute || !!attributeOutputLocation.value; const existingSelected = !attachExisting.value || selectedExistingFunc.value.value !== nilId(); const argsConfigured = !attachExisting.value || funcKind.value.value !== FuncKind.Attribute || editableBindings.value.every((b) => b.binding.value !== nilId()); return nameIsSet && hasOutput && existingSelected && argsConfigured; }); const open = async ( existing?: boolean, variant?: FuncKind, funcId?: FuncId, ) => { attachExisting.value = existing ?? false; attributeOutputLocation.value = noneOutput; name.value = ""; funcKind.value = funcKindOptions.find((o) => o.value === variant) ?? { label: "Actions", value: FuncKind.Action, }; isCreate.value = false; isDelete.value = false; isRefresh.value = false; isUpdate.value = false; selectedFuncCode.value = ""; selectedExistingFunc.value = existingFuncOptions.value.find((o) => o.value === funcId) || noneFunction; attrToValidate.value = undefined; attributeOutputLocationOptions.value = {}; if (props.schemaVariantId) { const schemaVariant = assetStore.variantFromListById[props.schemaVariantId]; if (schemaVariant) { attributeOutputLocationOptions.value = outputSocketsAndPropsFor(schemaVariant); } } openModal(); }; interface EditingBinding { funcArgumentId: string; binding: Option; } const editableBindings = ref<EditingBinding[]>([]); const inputSourceOptions = computed<GroupedOptions>(() => { if (schemaVariantId.value) { const variant = assetStore.variantFromListById[schemaVariantId.value]; if (variant) { return inputSocketsAndPropsFor(variant); } } return {}; }); const funcArgumentName = ( funcArgumentId: FuncArgumentId, ): string | undefined => { return selectedExistingFuncSummary.value?.arguments .filter((a) => a.id === funcArgumentId) .pop()?.name; }; const noneSource = { label: "select source", value: nilId() }; const commonBindingConstruction = () => { const binding = { funcId: selectedExistingFunc.value.value as string, schemaVariantId: schemaVariantId.value, } as unknown; switch (funcKind.value.value) { case FuncKind.Authentication: // eslint-disable-next-line no-case-declarations const auth = binding as Authentication; auth.bindingKind = FuncBindingKind.Authentication; return auth; case FuncKind.Action: // eslint-disable-next-line no-case-declarations const action = binding as Action; action.bindingKind = FuncBindingKind.Action; if (isCreate.value) action.kind = ActionKind.Create; if (isUpdate.value) action.kind = ActionKind.Update; if (isDelete.value) action.kind = ActionKind.Destroy; if (isRefresh.value) action.kind = ActionKind.Refresh; if ( !isRefresh.value && !isDelete.value && !isCreate.value && !isUpdate.value ) action.kind = ActionKind.Manual; return action; case FuncKind.CodeGeneration: // eslint-disable-next-line no-case-declarations const bind = binding as CodeGeneration; bind.inputs = []; bind.bindingKind = FuncBindingKind.CodeGeneration; return bind; case FuncKind.Qualification: // eslint-disable-next-line no-case-declarations const qual = binding as Qualification; qual.inputs = []; qual.bindingKind = FuncBindingKind.Qualification; return qual; case FuncKind.Management: // eslint-disable-next-line no-case-declarations const mgmt = binding as Management; mgmt.bindingKind = FuncBindingKind.Management; return mgmt; default: return null; } }; const attachExistingFunc = async () => { const bindings: FuncBinding[] = []; const binding = commonBindingConstruction(); if (binding) bindings.push(binding); if (funcKind.value.value === FuncKind.Attribute) { const attr = { funcId: selectedExistingFunc.value.value as string, schemaVariantId: schemaVariantId.value, bindingKind: FuncBindingKind.Attribute, } as Attribute; const argBindings: AttributeArgumentBinding[] = []; editableBindings.value.forEach(async (b) => { const arg = { funcArgumentId: b.funcArgumentId, } as AttributeArgumentBinding; const bString = b.binding.value as string; if (bString.startsWith("s_")) arg.inputSocketId = bString.replace("s_", ""); else if (bString.startsWith("p_")) arg.propId = bString.replace("p_", ""); argBindings.push(arg); }); attr.argumentBindings = argBindings; const loc = attributeOutputLocation.value.value as string; if (loc.startsWith("s_")) attr.outputSocketId = loc.replace("s_", ""); else if (loc.startsWith("p_")) attr.propId = loc.replace("p_", ""); bindings.push(attr); } if (bindings.length > 0 && selectedExistingFunc.value.value !== nilId()) { const resp = await funcStore.CREATE_BINDING( selectedExistingFunc.value.value as string, bindings, ); if (resp.result.success) close(); } }; const attachNewFunc = async () => { if (schemaVariantId.value) { createFuncStarted.value = true; let binding = commonBindingConstruction() as FuncBinding; if (!binding) { let outputSocketId; let propId; const loc = attributeOutputLocation.value.value as string; if (loc.startsWith("s_")) outputSocketId = loc.replace("s_", ""); else if (loc.startsWith("p_")) propId = loc.replace("p_", ""); binding = { funcId: selectedExistingFunc.value.value as string, schemaVariantId: schemaVariantId.value, bindingKind: FuncBindingKind.Attribute, argumentBindings: [], propId, outputSocketId, componentId: null, attributePrototypeId: null, } as Attribute; } const resp = await funcStore.CREATE_FUNC({ name: name.value, displayName: name.value, description: "", binding, kind: funcKind.value.value, }); if (resp.result.success) close(); } }; const onAttach = async () => { if (attachExisting.value) { await attachExistingFunc(); } else { await attachNewFunc(); } }; const onClose = () => { createFuncStarted.value = false; }; defineExpose({ open, close }); const isCreate = ref(false); const isDelete = ref(false); const isRefresh = ref(false); const isUpdate = ref(false); const setCreate = () => { if (!isCreate.value) return; isDelete.value = false; isRefresh.value = false; isUpdate.value = false; }; const setRefresh = () => { if (!isRefresh.value) return; isCreate.value = false; isDelete.value = false; isUpdate.value = false; }; const setDelete = () => { if (!isDelete.value) return; isCreate.value = false; isRefresh.value = false; isUpdate.value = false; }; const setUpdate = () => { if (!isUpdate.value) return; isCreate.value = false; isDelete.value = false; isRefresh.value = false; }; </script>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server