pieces-props.ts•16.4 kB
import { Property, InputPropertyMap } from "@activepieces/pieces-framework";
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { ImageModel } from 'ai';
import { isNil, SeekPage } from '@activepieces/shared';
import { SUPPORTED_AI_PROVIDERS, SupportedAIProvider } from './supported-ai-providers';
import { AIProviderWithoutSensitiveData } from './types';
export const aiProps = <T extends 'language' | 'image' | 'video'>({ modelType, functionCalling }: AIPropsParams<T>): AIPropsReturn => ({
    provider: Property.Dropdown<string, true>({
        displayName: 'Provider',
        required: true,
        refreshers: [],
        options: async (_, ctx) => {
            const { body: { data: supportedProviders } } = await httpClient.sendRequest<
                SeekPage<AIProviderWithoutSensitiveData>
            >({
                method: HttpMethod.GET,
                url: `${ctx.server.apiUrl}v1/ai-providers`,
                headers: {
                    Authorization: `Bearer ${ctx.server.token}`,
                },
            });
            if (supportedProviders.length === 0) {
                return {
                    disabled: true,
                    options: [],
                    placeholder: 'No AI providers configured by the admin.',
                };
            }
            const providers = supportedProviders.map(supportedProvider => {
                const provider = SUPPORTED_AI_PROVIDERS.find(p => p.provider === supportedProvider.provider);
                if (!provider) return null;
                if (modelType === 'language') {
                    if (provider.languageModels.length === 0) return null;
                    if (functionCalling && !provider.languageModels.some(model => model.functionCalling)) {
                        return null;
                    }
                } else if (modelType === 'image') {
                    if (provider.imageModels.length === 0) return null;
                } else if (modelType === 'video') {
                    if (provider.videoModels.length === 0) return null;
                }
                return {
                    value: provider.provider,
                    label: provider.displayName
                };
            });
            const filteredProviders = providers.filter(p => p !== null);
            return {
                placeholder: filteredProviders.length > 0 ? 'Select AI Provider' : `No providers available for ${modelType} models${functionCalling ? ' with function calling' : ''}`,
                disabled: filteredProviders.length === 0,
                options: filteredProviders,
            };
        },
    }),
    model: Property.Dropdown({
        displayName: 'Model',
        required: true,
        defaultValue: 'gpt-4o',
        refreshers: ['provider'],
        options: async (propsValue) => {
            const provider = propsValue['provider'] as string;
            if (isNil(provider)) {
                return {
                    disabled: true,
                    options: [],
                    placeholder: 'Select AI Provider',
                };
            }
            const supportedProvider = SUPPORTED_AI_PROVIDERS.find(p => p.provider === provider);
            if (isNil(supportedProvider)) {
                return {
                    disabled: true,
                    options: [],
                };
            }
            const allModels = modelType === 'language' ? supportedProvider.languageModels : modelType === 'image' ? supportedProvider.imageModels : supportedProvider.videoModels;
            const models = (modelType === 'language' && functionCalling)
                ? allModels.filter(model => (model as SupportedAIProvider['languageModels'][number]).functionCalling)
                : allModels;
            return {
                placeholder: 'Select AI Model',
                disabled: false,
                options: models.map(model => ({
                    label: model.displayName,
                    value: model.instance,
                })),
            };
        },
    }),
    advancedOptions: Property.DynamicProperties({
        displayName: 'Advanced Options',
        required: false,
        refreshers: ['provider', 'model'],
        props: async (propsValue): Promise<InputPropertyMap> => {
            const provider = propsValue['provider'] as unknown as string;
            const model = propsValue['model'] as unknown as ImageModel;
            const providerMetadata = SUPPORTED_AI_PROVIDERS.find(p => p.provider === provider);
            if (isNil(providerMetadata)) {
                return {};
            }
            let options: InputPropertyMap = {};
            if (modelType === 'image') {
                if (provider === 'openai') {
                    options = {
                        quality: Property.StaticDropdown({
                            options: {
                                options: model.modelId === 'dall-e-3' ? [
                                    { label: 'Standard', value: 'standard' },
                                    { label: 'HD', value: 'hd' },
                                ] : model.modelId === 'gpt-image-1' ? [
                                    { label: 'High', value: 'high' },
                                    { label: 'Medium', value: 'medium' },
                                    { label: 'Low', value: 'low' },
                                ] : [],
                                disabled: model.modelId === 'dall-e-2',
                            },
                            defaultValue: model.modelId === 'dall-e-3' ? 'standard' : 'high',
                            displayName: 'Image Quality',
                            required: false,
                        }),
                        size: Property.StaticDropdown({
                            options: {
                                options: model.modelId === 'dall-e-3' ? [
                                    { label: '1024x1024', value: '1024x1024' },
                                    { label: '1792x1024', value: '1792x1024' },
                                    { label: '1024x1792', value: '1024x1792' },
                                ] : model.modelId === 'gpt-image-1' ? [
                                    { label: '1024x1024', value: '1024x1024' },
                                    { label: '1536x1024', value: '1536x1024' },
                                    { label: '1024x1536', value: '1024x1536' },
                                ] : [
                                    { label: '256x256', value: '256x256' },
                                    { label: '512x512', value: '512x512' },
                                    { label: '1024x1024', value: '1024x1024' },
                                ],
                            },
                            displayName: 'Image Size',
                            required: false,
                        }),
                    }
                    if (model.modelId === 'gpt-image-1') {
                        options = {
                            ...options,
                            background: Property.StaticDropdown({
                                options: {
                                    options: [
                                        { label: 'Auto', value: 'auto' },
                                        { label: 'Transparent', value: 'transparent' },
                                        { label: 'Opaque', value: 'opaque' },
                                    ],
                                },
                                defaultValue: 'auto',
                                description: 'The background of the image.',
                                displayName: 'Background',
                                required: true,
                            }),
                        }
                    }
                    return options;
                }
                if (provider === 'replicate') {
                    options = {
                        negativePrompt: Property.ShortText({
                            displayName: 'Negative Prompt',
                            required: true,
                            description: 'A prompt to avoid in the generated image.',
                        }),
                    }
                }
                if(provider === 'google' && model.modelId === 'gemini-2.5-flash-image-preview') {
                    options = {
                        image: Property.Array({
                            displayName: 'Images',
                            required:false,
                            properties: {
                                file: Property.File({
                                    displayName: 'Image File',
                                    required: true,
                                }),
                            },
                            description: 'The image(s) you want to edit/merge',
                        })
                }
            }
            } else if (modelType === 'video') {
                if (provider === 'google') {
                    if (model.modelId === 'veo-2.0-generate-001') {
                        options = {
                            aspectRatio: Property.StaticDropdown({
                                displayName: 'Aspect Ratio',
                                required: false,
                                defaultValue: '16:9',
                                options: {
                                    options: [
                                        { label: '16:9', value: '16:9' },
                                        { label: '9:16', value: '9:16' },
                                    ],
                                },
                            }),
                            personGeneration: Property.StaticDropdown({
                                displayName: 'Person Generation',
                                required: false,
                                options: {
                                    options: [
                                        { label: 'Allow Adult', value: 'allow_adult' },
                                        { label: 'Don\'t Allow', value: 'dont_allow' },
                                        { label: 'Allow All', value: 'allow_all' },
                                    ],
                                },
                            }),
                        }
                    }
                    
                }
            }
            return options;
        },
    }),
    webSearch: Property.Checkbox({
        displayName: 'Web Search',
        required: false,
        defaultValue: false,
        description: 'Whether to use web search to find information for the AI to use in its response.',
    }),
    webSearchOptions: Property.DynamicProperties({
        displayName: 'Web Search Options',
        required: false,
        refreshers: ['webSearch', 'provider', 'model'],
        props: async (propsValue) => {
            const webSearchEnabled = propsValue['webSearch'] as unknown as boolean;
            const provider = propsValue['provider'] as unknown as string;
            if (!webSearchEnabled) {
                return {};
            }
            const providerMetadata = SUPPORTED_AI_PROVIDERS.find(p => p.provider === provider);
            if (isNil(providerMetadata)) {
                return {};
            }
            let options: InputPropertyMap = {
                maxUses: Property.Number({
                    displayName: 'Max Web Search Uses',
                    required: false,
                    defaultValue: 5,
                    description: 'Maximum number of searches to use. Default is 5.',
                }),
                includeSources: Property.Checkbox({
                    displayName: 'Include Sources',
                    description: 'Whether to include the sources in the response. Useful for getting web search details (e.g. search queries, searched URLs, etc).',
                    required: false,
                    defaultValue: false,
                }),
            };
            const userLocationOptions = {
                userLocationCity: Property.ShortText({
                    displayName: 'User Location - City',
                    required: false,
                    description: 'The city name for localizing search results (e.g., San Francisco).',
                }),
                userLocationRegion: Property.ShortText({
                    displayName: 'User Location - Region',
                    required: false,
                    description: 'The region or state for localizing search results (e.g., California).',
                }),
                userLocationCountry: Property.ShortText({
                    displayName: 'User Location - Country',
                    required: false,
                    description: 'The country code for localizing search results (e.g., US).',
                }),
                userLocationTimezone: Property.ShortText({
                    displayName: 'User Location - Timezone',
                    required: false,
                    description: 'The IANA timezone ID for localizing search results (e.g., America/Los_Angeles).',
                }),
            };
            if (provider === 'anthropic') {
                options = {
                    ...options,
                    allowedDomains: Property.Array({
                        displayName: 'Allowed Domains',
                        required: false,
                        description: 'List of domains to search (e.g., example.com, docs.example.com/blog). Domains should not include HTTP/HTTPS scheme. Subdomains are automatically included unless more specific subpaths are provided. Overrides Blocked Domains if both are provided.',
                        properties: {
                            domain: Property.ShortText({
                                displayName: 'Domain',
                                required: true,
                            }),
                        },
                    }),
                    blockedDomains: Property.Array({
                        displayName: 'Blocked Domains',
                        required: false,
                        description: 'List of domains to exclude from search (e.g., example.com, docs.example.com/blog). Domains should not include HTTP/HTTPS scheme. Subdomains are automatically included unless more specific subpaths are provided. Overrided by Allowed Domains if both are provided.',
                        properties: {
                            domain: Property.ShortText({
                                displayName: 'Domain',
                                required: true,
                            }),
                        },
                    }),
                    ...userLocationOptions,
                };
            }
            if (provider === 'openai') {
                options = {
                    ...options,
                    searchContextSize: Property.StaticDropdown({
                        displayName: 'Search Context Size',
                        required: false,
                        defaultValue: 'medium',
                        options: {
                            options: [
                                { label: 'Low', value: 'low' },
                                { label: 'Medium', value: 'medium' },
                                { label: 'High', value: 'high' },
                            ],
                        },
                        description: 'High level guidance for the amount of context window space to use for the search.',
                    }),
                    ...userLocationOptions,
                };
            }
            return options;
        },
    }),
})
type AIPropsParams<T extends 'language' | 'image' | 'video'> = {
    modelType: T,
    functionCalling?: T extends 'language' ? boolean : never
}
type AIPropsReturn = {
    provider: ReturnType<typeof Property.Dropdown<string, true>>;
    model: ReturnType<typeof Property.Dropdown>;
    advancedOptions: ReturnType<typeof Property.DynamicProperties>;
    webSearch: ReturnType<typeof Property.Checkbox>;
    webSearchOptions: ReturnType<typeof Property.DynamicProperties>;
}