import React, { useState, useEffect } from 'react'; import { Card, Form, Typography, Select, Input, Switch, Tooltip, Modal, message, Divider, Space, Tag, Image, Steps } from 'antd'; import { Button, TextInput } from '@tremor/react'; import type { FormInstance } from 'antd'; import { GuardrailProviders, guardrail_provider_map, shouldRenderPIIConfigSettings, guardrailLogoMap } from './guardrail_info_helpers'; import { createGuardrailCall, getGuardrailUISettings, getGuardrailProviderSpecificParams } from '../networking'; import PiiConfiguration from './pii_configuration'; import GuardrailProviderFields from './guardrail_provider_fields'; const { Title, Text, Link } = Typography; const { Option } = Select; const { Step } = Steps; // Define human-friendly descriptions for each mode const modeDescriptions = { pre_call: "Before LLM Call - Runs before the LLM call and checks the input (Recommended)", during_call: "During LLM Call - Runs in parallel with the LLM call, with response held until check completes", post_call: "After LLM Call - Runs after the LLM call and checks only the output", logging_only: "Logging Only - Only runs on logging callbacks without affecting the LLM call" }; interface AddGuardrailFormProps { visible: boolean; onClose: () => void; accessToken: string | null; onSuccess: () => void; } interface GuardrailSettings { supported_entities: string[]; supported_actions: string[]; supported_modes: string[]; pii_entity_categories: Array<{ category: string; entities: string[]; }>; } interface LiteLLMParams { guardrail: string; mode: string; default_on: boolean; [key: string]: any; // Allow additional properties for specific guardrails } // Mapping of provider -> list of param descriptors interface ProviderParam { param: string; description: string; required: boolean; default_value?: string; options?: string[]; type?: string; } interface ProviderParamsResponse { [provider: string]: ProviderParam[]; } const AddGuardrailForm: React.FC = ({ visible, onClose, accessToken, onSuccess }) => { const [form] = Form.useForm(); const [loading, setLoading] = useState(false); const [selectedProvider, setSelectedProvider] = useState(null); const [guardrailSettings, setGuardrailSettings] = useState(null); const [selectedEntities, setSelectedEntities] = useState([]); const [selectedActions, setSelectedActions] = useState<{[key: string]: string}>({}); const [currentStep, setCurrentStep] = useState(0); const [providerParams, setProviderParams] = useState(null); // Fetch guardrail UI settings + provider params on mount / accessToken change useEffect(() => { if (!accessToken) return; const fetchData = async () => { try { // Parallel requests for speed const [uiSettings, providerParamsResp] = await Promise.all([ getGuardrailUISettings(accessToken), getGuardrailProviderSpecificParams(accessToken), ]); setGuardrailSettings(uiSettings); setProviderParams(providerParamsResp); } catch (error) { console.error('Error fetching guardrail data:', error); message.error('Failed to load guardrail configuration'); } }; fetchData(); }, [accessToken]); const handleProviderChange = (value: string) => { setSelectedProvider(value); // Reset form fields that are provider-specific form.setFieldsValue({ config: undefined, presidio_analyzer_api_base: undefined, presidio_anonymizer_api_base: undefined }); // Reset PII selections when changing provider setSelectedEntities([]); setSelectedActions({}); }; const handleEntitySelect = (entity: string) => { setSelectedEntities(prev => { if (prev.includes(entity)) { return prev.filter(e => e !== entity); } else { return [...prev, entity]; } }); }; const handleActionSelect = (entity: string, action: string) => { setSelectedActions(prev => ({ ...prev, [entity]: action })); }; const nextStep = async () => { try { // Validate current step fields if (currentStep === 0) { await form.validateFields(['guardrail_name', 'provider', 'mode', 'default_on']); // Also validate provider-specific fields if applicable if (selectedProvider) { // This will automatically validate any required fields for the selected provider const fieldsToValidate = ['guardrail_name', 'provider', 'mode', 'default_on']; if (selectedProvider === 'PresidioPII') { fieldsToValidate.push('presidio_analyzer_api_base', 'presidio_anonymizer_api_base'); } await form.validateFields(fieldsToValidate); } } setCurrentStep(currentStep + 1); } catch (error) { console.error("Form validation failed:", error); } }; const prevStep = () => { setCurrentStep(currentStep - 1); }; const resetForm = () => { form.resetFields(); setSelectedProvider(null); setSelectedEntities([]); setSelectedActions({}); setCurrentStep(0); }; const handleClose = () => { resetForm(); onClose(); }; const handleSubmit = async () => { try { setLoading(true); // First validate currently visible fields await form.validateFields(); // After validation, fetch *all* form values (including those from previous steps) const values = form.getFieldsValue(true); // Get the guardrail provider value from the map const guardrailProvider = guardrail_provider_map[values.provider]; // Prepare the guardrail data with proper typings const guardrailData: { guardrail_name: string; litellm_params: { guardrail: string; mode: string; default_on: boolean; [key: string]: any; // Allow dynamic properties }; guardrail_info: any; } = { guardrail_name: values.guardrail_name, litellm_params: { guardrail: guardrailProvider, mode: values.mode, default_on: values.default_on }, guardrail_info: {} }; // For Presidio PII, add the entity and action configurations if (values.provider === 'PresidioPII' && selectedEntities.length > 0) { const piiEntitiesConfig: {[key: string]: string} = {}; selectedEntities.forEach(entity => { piiEntitiesConfig[entity] = selectedActions[entity] || 'MASK'; // Default to MASK if no action selected }); guardrailData.litellm_params.pii_entities_config = piiEntitiesConfig; // Add Presidio API bases if provided if (values.presidio_analyzer_api_base) { guardrailData.litellm_params.presidio_analyzer_api_base = values.presidio_analyzer_api_base; } if (values.presidio_anonymizer_api_base) { guardrailData.litellm_params.presidio_anonymizer_api_base = values.presidio_anonymizer_api_base; } } // Add config values to the guardrail_info if provided else if (values.config) { try { const configObj = JSON.parse(values.config); // For some guardrails, the config values need to be in litellm_params guardrailData.guardrail_info = configObj; } catch (error) { message.error('Invalid JSON in configuration'); setLoading(false); return; } } /****************************** * Add provider-specific params * ---------------------------------- * The backend exposes exactly which extra parameters a provider * accepts via `/guardrails/ui/provider_specific_params`. * Instead of copying every unknown form field, we fetch the list for * the selected provider and ONLY pass those recognised params. ******************************/ // Use pre-fetched provider params to copy recognised params if (providerParams && selectedProvider) { const providerKey = guardrail_provider_map[selectedProvider]?.toLowerCase(); const providerSpecificParams = providerParams[providerKey] || []; const allowedParams = new Set( providerSpecificParams.map((p) => p.param) ); allowedParams.forEach((paramName) => { const paramValue = values[paramName]; if (paramValue !== undefined && paramValue !== null && paramValue !== '') { guardrailData.litellm_params[paramName] = paramValue; } }); } if (!accessToken) { throw new Error("No access token available"); } console.log("Sending guardrail data:", JSON.stringify(guardrailData)); await createGuardrailCall(accessToken, guardrailData); message.success('Guardrail created successfully'); // Reset form and close modal resetForm(); onSuccess(); onClose(); } catch (error) { console.error("Failed to create guardrail:", error); message.error('Failed to create guardrail: ' + (error instanceof Error ? error.message : String(error))); } finally { setLoading(false); } }; const renderBasicInfo = () => { return ( <> {/* Use the GuardrailProviderFields component to render provider-specific fields */} ); }; const renderPiiConfiguration = () => { if (!guardrailSettings || selectedProvider !== 'PresidioPII') return null; return ( ); }; const renderStepContent = () => { switch (currentStep) { case 0: return renderBasicInfo(); case 1: if (shouldRenderPIIConfigSettings(selectedProvider)) { return renderPiiConfiguration(); } default: return null; } }; const renderStepButtons = () => { return (
{currentStep > 0 && ( )} {currentStep < 1 && ( )} {currentStep === 1 && ( )}
); }; return (
{renderStepContent()} {renderStepButtons()}
); }; export default AddGuardrailForm;