Skip to main content
Glama

Activepieces MCP Server

by eldoonreval
ap-form.tsxβ€’11.7 kB
import { typeboxResolver } from '@hookform/resolvers/typebox'; import { Separator } from '@radix-ui/react-dropdown-menu'; import { TSchema, Type } from '@sinclair/typebox'; import { useMutation } from '@tanstack/react-query'; import { t } from 'i18next'; import { useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useLocation } from 'react-router-dom'; import { ApMarkdown } from '@/components/custom/markdown'; import { ShowPoweredBy } from '@/components/show-powered-by'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Form, FormControl, FormField, FormLabel, FormItem, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { ReadMoreDescription } from '@/components/ui/read-more-description'; import { Textarea } from '@/components/ui/textarea'; import { toast } from '@/components/ui/use-toast'; import { flagsHooks } from '@/hooks/flags-hooks'; import { api } from '@/lib/api'; import { ApFlagId, FileResponseInterface, FormInput, FormInputType, FormResponse, HumanInputFormResultTypes, HumanInputFormResult, createKeyForFormInput, } from '@activepieces/shared'; import { Checkbox } from '../../../components/ui/checkbox'; import { humanInputApi } from '../lib/human-input-api'; type ApFormProps = { form: FormResponse; useDraft: boolean; }; type FormInputWithName = FormInput & { name: string; }; /**We do this because it was the behaviour in previous versions of Activepieces.*/ const putBackQuotesForInputNames = ( value: Record<string, unknown>, inputs: FormInputWithName[], ) => { return inputs.reduce((acc, input) => { const key = createKeyForFormInput(input.displayName); acc[key] = value[key]; return acc; }, {} as Record<string, unknown>); }; const requiredPropertySettings = { minLength: 1, errorMessage: t('This field is required'), }; const createPropertySchema = (input: FormInputWithName) => { const schemaSettings = input.required ? requiredPropertySettings : {}; switch (input.type) { case FormInputType.TOGGLE: return Type.Boolean(schemaSettings); case FormInputType.TEXT: case FormInputType.TEXT_AREA: return Type.String(schemaSettings); case FormInputType.FILE: return Type.Unknown(schemaSettings); } }; function buildSchema(inputs: FormInputWithName[]) { return { properties: Type.Object( inputs.reduce<Record<string, TSchema>>((acc, input) => { acc[input.name] = createPropertySchema(input); return acc; }, {}), ), defaultValues: inputs.reduce<Record<string, string | boolean>>( (acc, input) => { acc[input.name] = input.type === FormInputType.TOGGLE ? false : ''; return acc; }, {}, ), }; } const handleDownloadFile = (fileBase: FileResponseInterface) => { const link = document.createElement('a'); if ('url' in fileBase) { link.href = fileBase.url; } else { link.download = fileBase.fileName; link.href = fileBase.base64Url; URL.revokeObjectURL(fileBase.base64Url); } link.target = '_blank'; link.rel = 'noreferrer noopener'; link.click(); }; const ApForm = ({ form, useDraft }: ApFormProps) => { const location = useLocation(); const queryParams = new URLSearchParams(location.search); const queryParamsLowerCase = Array.from(queryParams.entries()).reduce( (acc, [key, value]) => { acc[key.toLowerCase()] = value; return acc; }, {} as Record<string, string>, ); const inputs = useRef<FormInputWithName[]>( form.props.inputs.map((input) => { return { ...input, name: createKeyForFormInput(input.displayName), }; }), ); const schema = buildSchema(inputs.current); const defaultValues = { ...schema.defaultValues }; inputs.current.forEach((input) => { const queryValue = queryParamsLowerCase[input.name.toLowerCase()]; if (queryValue !== undefined) { defaultValues[input.name] = queryValue; } }); const [markdownResponse, setMarkdownResponse] = useState<string | null>(null); const { data: showPoweredBy } = flagsHooks.useFlag<boolean>( ApFlagId.SHOW_POWERED_BY_IN_FORM, ); const reactForm = useForm({ defaultValues, resolver: typeboxResolver(schema.properties), }); const { mutate, isPending } = useMutation<HumanInputFormResult | null, Error>( { mutationFn: async () => humanInputApi.submitForm( form, useDraft, putBackQuotesForInputNames(reactForm.getValues(), inputs.current), ), onSuccess: (formResult) => { switch (formResult?.type) { case HumanInputFormResultTypes.MARKDOWN: { setMarkdownResponse(formResult.value as string); if (formResult.files) { formResult.files.forEach((file) => { handleDownloadFile(file as FileResponseInterface); }); } break; } case HumanInputFormResultTypes.FILE: handleDownloadFile(formResult.value as FileResponseInterface); break; default: toast({ title: t('Success'), description: t('Your submission was successfully received.'), duration: 3000, }); break; } }, onError: (error) => { if (api.isError(error)) { const status = error.response?.status; if (status === 404) { toast({ title: t('Flow not found'), description: t( 'The flow you are trying to submit to does not exist.', ), duration: 3000, }); } else { toast({ title: t('Error'), description: t('The flow failed to execute.'), duration: 3000, }); } } console.error(error); }, }, ); return ( <div className="w-full h-full flex"> <div className="container py-20"> <Form {...reactForm}> <form onSubmit={(e) => reactForm.handleSubmit(() => mutate())(e)}> <Card className="w-[500px] mx-auto"> <CardHeader> <CardTitle className="text-center">{form?.title}</CardTitle> </CardHeader> <CardContent> <div className="grid w-full items-center gap-3"> {inputs.current.map((input) => { return ( <FormField key={input.name} control={reactForm.control} name={input.name} render={({ field }) => ( <> {input.type === FormInputType.TOGGLE && ( <> <FormItem className="flex items-center gap-2 h-full"> <FormControl> <Checkbox onCheckedChange={(e) => field.onChange(e)} checked={field.value as boolean} ></Checkbox> </FormControl> <FormLabel htmlFor={input.name} className="flex items-center" > {input.displayName} </FormLabel> </FormItem> <ReadMoreDescription text={input.description ?? ''} /> </> )} {input.type !== FormInputType.TOGGLE && ( <FormItem className="flex flex-col gap-1"> <FormLabel htmlFor={input.name} className="flex items-center justify-between" > {input.displayName} {input.required && '*'} </FormLabel> <FormControl className="flex flex-col gap-1"> <> {input.type === FormInputType.TEXT_AREA && ( <Textarea {...field} name={input.name} id={input.name} onChange={field.onChange} value={ field.value as string | undefined } /> )} {input.type === FormInputType.TEXT && ( <Input {...field} onChange={field.onChange} id={input.name} name={input.name} value={ field.value as string | undefined } /> )} {input.type === FormInputType.FILE && ( <Input name={input.name} id={input.name} onChange={(e) => { const file = e.target.files?.[0]; if (file) { field.onChange(file); } }} placeholder={input.displayName} type="file" /> )} <ReadMoreDescription text={input.description ?? ''} /> </> </FormControl> </FormItem> )} </> )} /> ); })} </div> <Button type="submit" className="w-full mt-4" loading={isPending} > {t('Submit')} </Button> {markdownResponse && ( <> <Separator className="my-4" /> <ApMarkdown markdown={markdownResponse} /> </> )} </CardContent> </Card> <div className="mt-2"> <ShowPoweredBy position="static" show={showPoweredBy ?? false} /> </div> </form> </Form> </div> </div> ); }; ApForm.displayName = 'ApForm'; export { ApForm };

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/eldoonreval/activepieces'

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