Spaces:
Paused
Paused
inferencing-llm
/
ui
/litellm-dashboard
/src
/components
/common_components
/check_openapi_schema.tsx
| import React, { useState, useEffect } from 'react'; | |
| import { Form, Input, InputNumber, Select } from 'antd'; | |
| import { TextInput } from "@tremor/react"; | |
| import { InfoCircleOutlined } from '@ant-design/icons'; | |
| import { Tooltip } from 'antd'; | |
| import { getOpenAPISchema } from '../networking'; | |
| interface SchemaProperty { | |
| type?: string; | |
| title?: string; | |
| description?: string; | |
| anyOf?: Array<{ type: string }>; | |
| enum?: string[]; | |
| format?: string; | |
| } | |
| interface OpenAPISchema { | |
| properties: { | |
| [key: string]: SchemaProperty; | |
| }; | |
| required?: string[]; | |
| } | |
| interface SchemaFormFieldsProps { | |
| schemaComponent: string; | |
| excludedFields?: string[]; | |
| form: any; | |
| overrideLabels?: { [key: string]: string }; | |
| overrideTooltips?: { [key: string]: string }; | |
| customValidation?: { | |
| [key: string]: (rule: any, value: any) => Promise<void> | |
| }; | |
| defaultValues?: { [key: string]: any }; | |
| } | |
| // Helper function to determine if a field should be treated as JSON | |
| const isJSONField = (key: string, property: SchemaProperty): boolean => { | |
| const jsonFields = ['metadata', 'config', 'enforced_params', 'aliases']; | |
| return jsonFields.includes(key) || property.format === 'json'; | |
| }; | |
| // Helper function to validate JSON input | |
| const validateJSON = (value: string): boolean => { | |
| if (!value) return true; | |
| try { | |
| JSON.parse(value); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| }; | |
| const getFieldHelp = (key: string, property: SchemaProperty, type: string): string => { | |
| // Default help text based on type | |
| const defaultHelp = { | |
| string: 'Text input', | |
| number: 'Numeric input', | |
| integer: 'Whole number input', | |
| boolean: 'True/False value', | |
| }[type] || 'Text input'; | |
| // Specific field help text | |
| const specificHelp: { [key: string]: string } = { | |
| max_budget: 'Enter maximum budget in USD (e.g., 100.50)', | |
| budget_duration: 'Select a time period for budget reset', | |
| tpm_limit: 'Enter maximum tokens per minute (whole number)', | |
| rpm_limit: 'Enter maximum requests per minute (whole number)', | |
| duration: 'Enter duration (e.g., 30s, 24h, 7d)', | |
| metadata: 'Enter JSON object with key-value pairs\nExample: {"team": "research", "project": "nlp"}', | |
| config: 'Enter configuration as JSON object\nExample: {"setting": "value"}', | |
| permissions: 'Enter comma-separated permission strings', | |
| enforced_params: 'Enter parameters as JSON object\nExample: {"param": "value"}', | |
| blocked: 'Enter true/false or specific block conditions', | |
| aliases: 'Enter aliases as JSON object\nExample: {"alias1": "value1", "alias2": "value2"}', | |
| models: 'Select one or more model names', | |
| key_alias: 'Enter a unique identifier for this key', | |
| tags: 'Enter comma-separated tag strings', | |
| }; | |
| // Get specific help text or use default based on type | |
| const helpText = specificHelp[key] || defaultHelp; | |
| // Add format requirements for special cases | |
| if (isJSONField(key, property)) { | |
| return `${helpText}\nMust be valid JSON format`; | |
| } | |
| if (property.enum) { | |
| return `Select from available options\nAllowed values: ${property.enum.join(', ')}`; | |
| } | |
| return helpText; | |
| }; | |
| const SchemaFormFields: React.FC<SchemaFormFieldsProps> = ({ | |
| schemaComponent, | |
| excludedFields = [], | |
| form, | |
| overrideLabels = {}, | |
| overrideTooltips = {}, | |
| customValidation = {}, | |
| defaultValues = {} | |
| }) => { | |
| const [schemaProperties, setSchemaProperties] = useState<OpenAPISchema | null>(null); | |
| const [error, setError] = useState<string | null>(null); | |
| useEffect(() => { | |
| const fetchOpenAPISchema = async () => { | |
| try { | |
| const schema = await getOpenAPISchema(); | |
| const componentSchema = schema.components.schemas[schemaComponent]; | |
| if (!componentSchema) { | |
| throw new Error(`Schema component "${schemaComponent}" not found`); | |
| } | |
| setSchemaProperties(componentSchema); | |
| const defaultFormValues: { [key: string]: any } = {}; | |
| Object.keys(componentSchema.properties) | |
| .filter(key => !excludedFields.includes(key) && defaultValues[key] !== undefined) | |
| .forEach(key => { | |
| defaultFormValues[key] = defaultValues[key]; | |
| }); | |
| form.setFieldsValue(defaultFormValues); | |
| } catch (error) { | |
| console.error('Schema fetch error:', error); | |
| setError(error instanceof Error ? error.message : 'Failed to fetch schema'); | |
| } | |
| }; | |
| fetchOpenAPISchema(); | |
| }, [schemaComponent, form, excludedFields]); | |
| const getPropertyType = (property: SchemaProperty): string => { | |
| if (property.type) { | |
| return property.type; | |
| } | |
| if (property.anyOf) { | |
| const types = property.anyOf.map(t => t.type); | |
| if (types.includes('number') || types.includes('integer')) return 'number'; | |
| if (types.includes('string')) return 'string'; | |
| } | |
| return 'string'; | |
| }; | |
| const renderFormItem = (key: string, property: SchemaProperty) => { | |
| const type = getPropertyType(property); | |
| const isRequired = schemaProperties?.required?.includes(key); | |
| const label = overrideLabels[key] || property.title || key; | |
| const tooltip = overrideTooltips[key] || property.description; | |
| const rules = []; | |
| if (isRequired) { | |
| rules.push({ required: true, message: `${label} is required` }); | |
| } | |
| if (customValidation[key]) { | |
| rules.push({ validator: customValidation[key] }); | |
| } | |
| if (isJSONField(key, property)) { | |
| rules.push({ | |
| validator: async (_: any, value: string) => { | |
| if (value && !validateJSON(value)) { | |
| throw new Error('Please enter valid JSON'); | |
| } | |
| } | |
| }); | |
| } | |
| const formLabel = tooltip ? ( | |
| <span> | |
| {label}{' '} | |
| <Tooltip title={tooltip}> | |
| <InfoCircleOutlined style={{ marginLeft: '4px' }} /> | |
| </Tooltip> | |
| </span> | |
| ) : label; | |
| let inputComponent; | |
| if (isJSONField(key, property)) { | |
| inputComponent = ( | |
| <Input.TextArea | |
| rows={4} | |
| placeholder="Enter as JSON" | |
| className="font-mono" | |
| /> | |
| ); | |
| } else if (property.enum) { | |
| inputComponent = ( | |
| <Select> | |
| {property.enum.map(value => ( | |
| <Select.Option key={value} value={value}> | |
| {value} | |
| </Select.Option> | |
| ))} | |
| </Select> | |
| ); | |
| } else if (type === 'number' || type === 'integer') { | |
| inputComponent = ( | |
| <InputNumber | |
| style={{ width: '100%' }} | |
| precision={type === 'integer' ? 0 : undefined} | |
| /> | |
| ); | |
| } else if (key === 'duration') { | |
| inputComponent = ( | |
| <TextInput | |
| placeholder="eg: 30s, 30h, 30d" | |
| /> | |
| ); | |
| } else { | |
| inputComponent = ( | |
| <TextInput | |
| placeholder={tooltip || ''} | |
| /> | |
| ); | |
| } | |
| return ( | |
| <Form.Item | |
| key={key} | |
| label={formLabel} | |
| name={key} | |
| className="mt-8" | |
| rules={rules} | |
| initialValue={defaultValues[key]} | |
| help={ | |
| <div className="text-xs text-gray-500"> | |
| {getFieldHelp(key, property, type)} | |
| </div> | |
| } | |
| > | |
| {inputComponent} | |
| </Form.Item> | |
| ); | |
| }; | |
| if (error) { | |
| return <div className="text-red-500">Error: {error}</div>; | |
| } | |
| if (!schemaProperties?.properties) { | |
| return null; | |
| } | |
| return ( | |
| <div> | |
| {Object.entries(schemaProperties.properties) | |
| .filter(([key]) => !excludedFields.includes(key)) | |
| .map(([key, property]) => renderFormItem(key, property))} | |
| </div> | |
| ); | |
| }; | |
| export default SchemaFormFields; |