Shyamnath's picture
Push UI dashboard and deployment files
c40c75a
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;