trustgraph/ai-context/workbench-ui/docs/tech-specs/flow-configurable-parameters-client.md

1048 lines
No EOL
32 KiB
Markdown

# Flow Configurable Parameters - Client-Side Technical Specification
## Overview
This specification describes the client-side implementation of configurable parameters for flow classes in TrustGraph UI. This complements the server-side implementation by providing dynamic form generation, parameter validation, and user-friendly parameter input interfaces for flow creation.
The client-side implementation enables users to:
- View available parameters when selecting a flow class
- Input parameter values through dynamically generated forms
- Validate parameters according to their schema definitions
- Launch flows with custom parameter configurations
- Save and reuse parameter presets for common configurations
## Goals
- **Dynamic Form Generation**: Automatically create parameter input forms based on flow class parameter schemas
- **Type-Safe Validation**: Validate parameter inputs according to schema definitions (string, number, boolean, enum)
- **Intuitive User Experience**: Provide clear parameter descriptions, validation feedback, and sensible defaults
- **Integration with Existing UI**: Seamlessly integrate with the current CreateDialog and flow management system
- **Parameter Presets**: Allow users to save and reuse common parameter configurations
- **Real-time Feedback**: Show validation errors and hints as users input parameters
- **Accessibility**: Ensure parameter forms are accessible and follow project UI patterns
## Architecture
### Component Structure
```
src/components/flows/
├── CreateDialog.tsx # Enhanced with parameter support
├── ParameterInputs.tsx # Dynamic parameter form component
├── ParameterPresets.tsx # Parameter preset management
├── ParameterValidation.tsx # Validation logic and error display
└── __tests__/
├── ParameterInputs.test.tsx
└── ParameterPresets.test.tsx
src/state/
├── flows.ts # Enhanced with parameter support
└── flow-parameters.ts # Parameter definition fetching and caching
src/model/
└── flow-parameters.ts # Parameter type definitions and utilities
```
### Data Flow
1. **Parameter Schema Fetching**:
```
User selects flow class → Fetch parameter definitions → Parse schema → Generate form
```
2. **Parameter Input**:
```
User inputs values → Validate against schema → Update form state → Enable/disable submit
```
3. **Flow Creation**:
```
User submits → Validate all parameters → Send to API → Create flow with parameters
```
## Technical Design
### Core Types
```typescript
// Parameter schema definition (from server)
interface ParameterSchema {
type: 'string' | 'number' | 'integer' | 'boolean';
description?: string;
default?: any;
enum?: EnumOption[] | string[]; // Can be rich objects or simple strings
minimum?: number;
maximum?: number;
pattern?: string;
required?: boolean;
helper?: string; // Custom helper text
placeholder?: string; // Custom placeholder text
}
// Rich enum option structure
interface EnumOption {
id: string; // The actual value
description: string; // Display text
}
// Flow class structure (from getFlowClass API)
interface FlowClass {
class: { [processorName: string]: any }; // Processor definitions
description: string;
flow: { [stepName: string]: any }; // Flow step definitions
interfaces: { [interfaceName: string]: any }; // Interface definitions
parameters?: { [flowParamName: string]: FlowParameterMetadata }; // Maps flow param names to parameter metadata
tags?: string[];
}
// Flow parameter metadata structure
interface FlowParameterMetadata {
type: string; // Reference to parameter-type definition name
description: string; // Human-readable description for UI display
order: number; // Display order for parameter forms (lower numbers appear first)
}
// Parameter definitions fetched from config
interface ParameterDefinitions {
[definitionName: string]: ParameterSchema;
}
// User parameter values
interface ParameterValues {
[flowParamName: string]: any;
}
// Parameter validation result
interface ValidationResult {
isValid: boolean;
errors: { [paramName: string]: string };
}
```
### Component Implementation
#### 1. Enhanced CreateDialog
```typescript
// Key enhancements to existing CreateDialog
const CreateDialog = ({ open, onOpenChange }) => {
const [flowClass, setFlowClass] = useState<string>();
const [id, setId] = useState("");
const [description, setDescription] = useState("");
const [parameterValues, setParameterValues] = useState<ParameterValues>({});
// Fetch parameter definitions when flow class is selected
const {
parameterDefinitions,
isLoadingParameters
} = useFlowParameters(flowClass);
// Validate form including parameters
const { isValid, errors } = useParameterValidation(
flowClass,
parameterDefinitions,
parameterValues
);
const onSubmit = () => {
if (!isValid) return;
flowState.startFlow({
id,
flowClass,
description,
parameters: parameterValues, // Include parameters
onSuccess: () => {
setParameterValues({}); // Clear parameters on success
// ... rest of success logic
},
});
};
return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
{/* Existing dialog structure */}
{/* Enhanced with parameter inputs */}
<ParameterInputs
flowClass={flowClass}
parameterDefinitions={parameterDefinitions}
parameterValues={parameterValues}
onParameterChange={setParameterValues}
validationErrors={errors}
/>
{/* Parameter presets */}
<ParameterPresets
flowClass={flowClass}
onPresetLoad={setParameterValues}
currentValues={parameterValues}
/>
</Dialog.Root>
);
};
```
#### 2. ParameterInputs Component
**Following Project Patterns**:
- Uses common components (TextField, SelectField) instead of raw Chakra
- Implements Chakra v3 patterns (Field.Root, etc.)
- Provides proper SelectField descriptions with SelectOptionText
```typescript
interface ParameterInputsProps {
flowClass?: string;
parameterDefinitions: ParameterDefinitions;
parameterValues: ParameterValues;
onParameterChange: (values: ParameterValues) => void;
validationErrors: { [key: string]: string };
}
const ParameterInputs: React.FC<ParameterInputsProps> = ({
parameterDefinitions,
parameterValues,
onParameterChange,
validationErrors,
}) => {
const contentRef = useRef<HTMLDivElement>(null);
const handleParameterChange = (paramName: string, value: any) => {
onParameterChange({
...parameterValues,
[paramName]: value,
});
};
const renderParameterInput = (paramName: string, schema: ParameterSchema) => {
const defaultValue = schema.default;
const value = parameterValues[paramName] ?? defaultValue ?? "";
const error = validationErrors[paramName];
const label = (schema.description || paramName) + (schema.required ? " *" : "");
// Helper text priority: schema.helper -> type-based fallback
const getHelperText = () => {
if (schema.helper) return schema.helper;
switch (schema.type) {
case 'integer': return 'Enter a whole number';
case 'number': return 'Enter a number (decimals allowed)';
case 'boolean': return 'Select true or false';
case 'string': return schema.enum ? undefined : 'Enter text';
default: return undefined;
}
};
const helperText = getHelperText();
const placeholder = schema.placeholder || "";
// Enum parameters - handle both rich {id, description} and simple string arrays
if (schema.enum && schema.enum.length > 0) {
const options = schema.enum.map(option => {
// Handle both rich {id, description} and simple string enums
const optionId = typeof option === 'object' ? option.id : option;
const optionDesc = typeof option === 'object' ? option.description : option;
return {
value: optionId,
label: optionDesc,
description: (
<SelectOptionText title={optionDesc}>
{optionId}
</SelectOptionText>
),
};
});
return (
<Box key={paramName} mt={5}>
<SelectField
label={label}
items={options}
value={value ? [value.toString()] : []}
onValueChange={(values) => {
const selectedValue = values.length > 0 ? values[0] : "";
handleParameterChange(paramName, selectedValue);
}}
contentRef={contentRef}
/>
{error && <Text color="red.500" fontSize="sm" mt={1}>{error}</Text>}
{helperText && (
<Text fontSize="sm" color="fg.muted" mt={1}>
{helperText}
</Text>
)}
</Box>
);
}
// Boolean parameters - use Checkbox
if (schema.type === 'boolean') {
return (
<Box key={paramName} mt={5}>
<Field.Root>
<Checkbox
checked={value}
onChange={(e) => handleParameterChange(paramName, e.target.checked)}
>
{label}
</Checkbox>
{helperText && <Field.HelperText>{helperText}</Field.HelperText>}
{error && <Text color="red.500" fontSize="sm" mt={1}>{error}</Text>}
</Field.Root>
</Box>
);
}
// Number/Integer parameters - use TextField with type="number"
if (schema.type === 'number' || schema.type === 'integer') {
let enhancedHelperText = helperText;
if (schema.minimum !== undefined || schema.maximum !== undefined) {
const rangeText = [];
if (schema.minimum !== undefined) rangeText.push(`min: ${schema.minimum}`);
if (schema.maximum !== undefined) rangeText.push(`max: ${schema.maximum}`);
const rangeInfo = rangeText.join(", ");
enhancedHelperText = enhancedHelperText
? `${enhancedHelperText} (${rangeInfo})`
: rangeInfo;
}
return (
<Box key={paramName} mt={5}>
<TextField
label={label}
helperText={enhancedHelperText}
placeholder={placeholder}
value={value.toString()}
onValueChange={(val) => {
const numValue = schema.type === 'integer'
? parseInt(val, 10)
: parseFloat(val);
if (!isNaN(numValue)) {
handleParameterChange(paramName, numValue);
} else if (val === "") {
handleParameterChange(paramName, "");
}
}}
type="number"
required={schema.required}
/>
{error && <Text color="red.500" fontSize="sm" mt={1}>{error}</Text>}
</Box>
);
}
// String parameters - use TextField
return (
<Box key={paramName} mt={5}>
<TextField
label={label}
helperText={helperText}
placeholder={placeholder}
value={value.toString()}
onValueChange={(val) => handleParameterChange(paramName, val)}
required={schema.required}
/>
{error && <Text color="red.500" fontSize="sm" mt={1}>{error}</Text>}
</Box>
);
};
return (
<Box>
{Object.entries(parameterDefinitions).map(([paramName, schema]) =>
renderParameterInput(paramName, schema)
)}
</Box>
);
};
```
#### 3. State Management
**Following Project Patterns**:
- Uses TanStack Query for API calls and caching
- Uses useActivity hook for loading states
- Uses useNotification hook for error/success messages
```typescript
// Enhanced flows.ts state
export const useFlows = () => {
// ... existing code
/**
* Enhanced mutation for starting flows with parameters
*/
const startFlowMutation = useMutation({
mutationFn: ({ id, flowClass, description, parameters, onSuccess }) => {
return socket
.flows()
.startFlow(id, flowClass, description, parameters)
.then(() => {
if (onSuccess) onSuccess();
});
},
onError: (err) => {
notify.error(err.message);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["flows"] });
notify.success("Flow started successfully");
},
});
// ... rest of existing code
};
// New flow-parameters.ts state
export const useFlowParameters = (flowClassName?: string) => {
const socket = useSocket();
const connectionState = useConnectionState();
const notify = useNotification();
const isSocketReady =
connectionState?.status === "authenticated" ||
connectionState?.status === "unauthenticated";
/**
* Query for fetching parameter definitions for a flow class
*/
const parametersQuery = useQuery({
queryKey: ["flow-parameters", flowClassName],
enabled: isSocketReady && !!flowClassName,
queryFn: async () => {
if (!flowClassName) return null;
// Get flow class definition first
const flowClass = await socket.flows().getFlowClass(flowClassName);
// Extract parameter metadata
const parameterMetadata = flowClass.parameters || {};
if (Object.keys(parameterMetadata).length === 0) {
return { parameterDefinitions: {}, parameterMapping: {}, parameterMetadata: {} };
}
// Extract unique parameter types for fetching definitions
const parameterTypes = [...new Set(Object.values(parameterMetadata).map(meta => meta.type))];
const configKeys = parameterTypes.map(type => ({ type: "parameter-types", key: type }));
const configResponse = await socket.config().getConfig(configKeys);
const parameterDefinitions = {};
// Parse config response to get parameter definitions
configResponse.values?.forEach(item => {
if (item.type === "parameter-types") {
parameterDefinitions[item.key] = JSON.parse(item.value);
}
});
// Create mapping for backwards compatibility
const parameterMapping = {};
Object.entries(parameterMetadata).forEach(([paramName, meta]) => {
parameterMapping[paramName] = meta.type;
});
return {
parameterDefinitions,
parameterMapping, // Maps flow param names to definition names (backwards compatibility)
parameterMetadata, // Full metadata with description, order, and type
};
},
});
useActivity(parametersQuery.isLoading, "Loading flow parameters");
return {
parameterDefinitions: parametersQuery.data?.parameterDefinitions || {},
parameterMapping: parametersQuery.data?.parameterMapping || {},
parameterMetadata: parametersQuery.data?.parameterMetadata || {},
isLoading: parametersQuery.isLoading,
isError: parametersQuery.isError,
error: parametersQuery.error,
};
};
```
#### 4. Parameter Validation
```typescript
// Custom hook for parameter validation
export const useParameterValidation = (
flowClass: string,
parameterDefinitions: ParameterDefinitions,
parameterValues: ParameterValues
) => {
return useMemo(() => {
const errors: { [key: string]: string } = {};
let isValid = true;
Object.entries(parameterDefinitions).forEach(([paramName, schema]) => {
const value = parameterValues[paramName];
// Check required fields
if (schema.required && (value === undefined || value === "")) {
errors[paramName] = `${paramName} is required`;
isValid = false;
return;
}
// Skip validation for empty optional fields
if (value === undefined || value === "") {
return;
}
// Type validation
if (schema.type === 'number' || schema.type === 'integer') {
const numValue = typeof value === 'string' ? parseFloat(value) : value;
if (isNaN(numValue)) {
errors[paramName] = `${paramName} must be a valid number`;
isValid = false;
return;
}
if (schema.type === 'integer' && !Number.isInteger(numValue)) {
errors[paramName] = `${paramName} must be an integer`;
isValid = false;
return;
}
// Range validation
if (schema.minimum !== undefined && numValue < schema.minimum) {
errors[paramName] = `${paramName} must be at least ${schema.minimum}`;
isValid = false;
}
if (schema.maximum !== undefined && numValue > schema.maximum) {
errors[paramName] = `${paramName} must be at most ${schema.maximum}`;
isValid = false;
}
}
// Enum validation
if (schema.enum && schema.enum.length > 0) {
if (!schema.enum.includes(value)) {
errors[paramName] = `${paramName} must be one of: ${schema.enum.join(', ')}`;
isValid = false;
}
}
// Pattern validation for strings
if (schema.pattern && schema.type === 'string') {
const regex = new RegExp(schema.pattern);
if (!regex.test(value.toString())) {
errors[paramName] = `${paramName} format is invalid`;
isValid = false;
}
}
});
return { isValid, errors };
}, [parameterDefinitions, parameterValues]);
};
```
### API Integration
#### Enhanced Socket API
The socket API has been enhanced to support parameters (already implemented):
```typescript
// In trustgraph-socket.ts - already updated
startFlow(id: string, class_name: string, description: string, parameters?: { [key: string]: any }) {
return this.api.makeRequest<FlowRequest, FlowResponse>(
"flow",
{
operation: "start-flow",
"flow-id": id,
"class-name": class_name,
description: description,
parameters: parameters,
},
30000,
).then((response) => {
if (response.error) {
const errorMessage = typeof response.error === 'object' && response.error.message
? response.error.message
: typeof response.error === 'string'
? response.error
: "Flow start failed";
throw new Error(errorMessage);
}
return response;
});
}
```
#### Config API Integration
```typescript
// Fetching parameter definitions from config system
const fetchParameterDefinitions = async (definitionNames: string[]) => {
const configKeys = definitionNames.map(name => ({
type: "parameter-types",
key: name
}));
const response = await socket.config().getConfig(configKeys);
const definitions = {};
response.values?.forEach(item => {
if (item.type === "parameter-types") {
definitions[item.key] = JSON.parse(item.value);
}
});
return definitions;
};
```
## Real-World Examples
### Flow Class Example
```json
{
"class": {
"text-completion:{id}": {
"model": "{llm-model}",
"request": "non-persistent://tg/request/text-completion:{id}",
"response": "non-persistent://tg/response/text-completion:{id}"
}
},
"description": "GraphRAG, DocumentRAG, structured data + knowledge cores",
"flow": {
"text-completion:{id}": {
"model": "{llm-model}",
"temperature": "{llm-temperature}",
"request": "non-persistent://tg/request/text-completion:{id}",
"response": "non-persistent://tg/response/text-completion:{id}"
}
},
"interfaces": { /* ... */ },
"parameters": {
"llm-model": {
"description": "LLM model",
"order": 1,
"type": "llm-model"
},
"llm-rag-model": {
"description": "LLM model for RAG",
"order": 2,
"type": "llm-model"
},
"llm-rag-temperature": {
"description": "LLM temperature",
"order": 3,
"type": "llm-temperature"
},
"llm-temperature": {
"description": "LLM temperature",
"order": 3,
"type": "llm-temperature"
}
},
"tags": ["document-rag", "graph-rag"]
}
```
### Parameter Definition Examples
#### Rich Enum Parameter (LLM Model)
```json
{
"default": "gemini-2.5-flash-lite",
"description": "LLM model to use",
"enum": [
{
"description": "Gemini 2.5 Pro",
"id": "gemini-2.5-pro"
},
{
"description": "Claude 3.5 Sonnet (via VertexAI)",
"id": "claude-3-5-sonnet@20241022"
}
],
"required": true,
"type": "string"
}
```
#### String Parameter (Free-form)
```json
{
"default": "gemini-2.5-flash-lite",
"description": "LLM model to use",
"required": true,
"type": "string",
"helper": "Enter the model identifier",
"placeholder": "e.g. gpt-4, claude-3"
}
```
#### Number Parameter Example
```json
{
"default": 0.7,
"description": "Temperature for model generation",
"type": "number",
"minimum": 0.0,
"maximum": 2.0,
"required": false,
"helper": "Controls randomness of model output"
}
```
#### Boolean Parameter Example
```json
{
"default": false,
"description": "Enable streaming responses",
"type": "boolean",
"required": false,
"helper": "Stream responses as they are generated"
}
```
### Config API Response Example
```json
{
"values": [
{
"type": "parameter-types",
"key": "llm-model",
"value": "{\"default\": \"gemini-2.5-flash-lite\", \"description\": \"LLM model to use\", \"enum\": [{\"description\": \"Gemini 2.5 Pro\", \"id\": \"gemini-2.5-pro\"}], \"required\": true, \"type\": \"string\"}"
}
]
}
```
## User Experience Design
### Form Behavior
1. **Initial State**: When CreateDialog opens, no parameters are shown
2. **Flow Class Selection**: When user selects a flow class:
- Show loading indicator while fetching parameters
- Display parameter form sections if parameters exist
- Show "No additional parameters required" if none exist
3. **Parameter Input**:
- Show validation errors in real-time
- Disable submit button until all required parameters are valid
- Provide clear descriptions and hints for each parameter
4. **Form Submission**:
- Validate all parameters before submission
- Show progress indicator during submission
- Clear form on successful submission
- Retain values on error for correction
### Parameter Input Types
1. **Enum Parameters (with rich options)**:
- SelectField dropdown with `{id, description}` structure
- Display user-friendly descriptions, store technical IDs
- Example: "Gemini 2.5 Pro" displays, "gemini-2.5-pro" is the value
- Supports both rich objects and simple string arrays
2. **String Parameters (no enum)**:
- TextField for free-form text input
- Uses `description` field as label
- Uses `helper` field or falls back to "Enter text"
- Uses `placeholder` field if provided
3. **Integer Parameters**:
- TextField with `type="number"`
- Helper text: "Enter a whole number"
- Validates and converts to integer
4. **Number/Float Parameters**:
- TextField with `type="number"`
- Helper text: "Enter a number (decimals allowed)"
- Validates and converts to float
5. **Boolean Parameters**:
- Checkbox component
- Helper text: "Select true or false"
- Direct true/false values
6. **Required Parameters**:
- Marked with asterisk (*) in label
- Red error styling for validation failures
- Cannot submit form without valid values
7. **Default Values**:
- Pre-populated when form loads
- Shows current value or default from schema
### Parameter Presets (Future Enhancement)
```typescript
// Parameter preset management component
const ParameterPresets: React.FC<{
flowClass: string;
onPresetLoad: (values: ParameterValues) => void;
currentValues: ParameterValues;
}> = ({ flowClass, onPresetLoad, currentValues }) => {
const [presets, setPresets] = useState<ParameterPreset[]>([]);
const [presetName, setPresetName] = useState("");
const savePreset = () => {
const preset: ParameterPreset = {
id: generateId(),
name: presetName,
flowClass,
values: currentValues,
createdAt: new Date(),
};
// Save to local storage or backend
saveParameterPreset(preset);
setPresets([...presets, preset]);
};
return (
<Box mt={5}>
<Text fontWeight="bold" mb={2}>Parameter Presets</Text>
{/* Preset selection */}
{presets.length > 0 && (
<SelectField
label="Load Preset"
items={presets.map(preset => ({
value: preset.id,
label: preset.name,
description: (
<SelectOptionText title={preset.name}>
{preset.name} - {formatDate(preset.createdAt)}
</SelectOptionText>
),
}))}
value={[]}
onValueChange={(values) => {
const presetId = values[0];
const preset = presets.find(p => p.id === presetId);
if (preset) {
onPresetLoad(preset.values);
}
}}
/>
)}
{/* Save current values as preset */}
<HStack mt={3}>
<TextField
label="Preset Name"
value={presetName}
onValueChange={setPresetName}
placeholder="Enter preset name..."
/>
<Button
onClick={savePreset}
disabled={!presetName.trim() || Object.keys(currentValues).length === 0}
>
Save Preset
</Button>
</HStack>
</Box>
);
};
```
## Testing Strategy
### Unit Tests
1. **ParameterInputs Component Tests**:
```typescript
describe('ParameterInputs', () => {
it('renders string parameters with TextField', () => {
const schema = { type: 'string', description: 'Test string param' };
render(<ParameterInputs parameterDefinitions={{ param1: schema }} ... />);
expect(screen.getByRole('textbox')).toBeInTheDocument();
});
it('renders enum parameters with SelectField', () => {
const schema = { type: 'string', enum: ['option1', 'option2'] };
render(<ParameterInputs parameterDefinitions={{ param1: schema }} ... />);
expect(screen.getByRole('combobox')).toBeInTheDocument();
});
it('validates required parameters', () => {
const schema = { type: 'string', required: true };
const validationErrors = { param1: 'param1 is required' };
render(<ParameterInputs validationErrors={validationErrors} ... />);
expect(screen.getByText('param1 is required')).toBeInTheDocument();
});
it('calls onParameterChange when value changes', () => {
const mockOnChange = jest.fn();
const schema = { type: 'string' };
render(<ParameterInputs onParameterChange={mockOnChange} ... />);
const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: 'test value' } });
expect(mockOnChange).toHaveBeenCalledWith({ param1: 'test value' });
});
});
```
2. **Parameter Validation Tests**:
```typescript
describe('useParameterValidation', () => {
it('validates required parameters', () => {
const { result } = renderHook(() => useParameterValidation(
'test-flow',
{ param1: { type: 'string', required: true } },
{}
));
expect(result.current.isValid).toBe(false);
expect(result.current.errors.param1).toBe('param1 is required');
});
it('validates number ranges', () => {
const { result } = renderHook(() => useParameterValidation(
'test-flow',
{ param1: { type: 'number', minimum: 5, maximum: 10 } },
{ param1: 15 }
));
expect(result.current.isValid).toBe(false);
expect(result.current.errors.param1).toContain('must be at most 10');
});
it('validates enum values', () => {
const { result } = renderHook(() => useParameterValidation(
'test-flow',
{ param1: { type: 'string', enum: ['opt1', 'opt2'] } },
{ param1: 'invalid' }
));
expect(result.current.isValid).toBe(false);
expect(result.current.errors.param1).toContain('must be one of: opt1, opt2');
});
});
```
### Integration Tests
1. **CreateDialog with Parameters**:
```typescript
describe('CreateDialog with Parameters', () => {
it('fetches and displays parameters when flow class is selected', async () => {
const mockFlowClass = {
parameters: { 'model': 'llm-model', 'temp': 'temperature' }
};
const mockParameterDefs = {
'llm-model': { type: 'string', enum: ['gpt-4', 'claude-3'] },
'temperature': { type: 'number', minimum: 0, maximum: 2 }
};
mockSocket.flows().getFlowClass.mockResolvedValue(mockFlowClass);
mockSocket.config().getConfig.mockResolvedValue({
values: [
{ type: 'parameters', key: 'llm-model', value: JSON.stringify(mockParameterDefs['llm-model']) },
{ type: 'parameters', key: 'temperature', value: JSON.stringify(mockParameterDefs.temperature) }
]
});
render(<CreateDialog open={true} />);
// Select flow class
const flowClassSelect = screen.getByLabelText('Flow class');
fireEvent.change(flowClassSelect, { target: { value: 'test-flow' } });
// Wait for parameters to load and display
await waitFor(() => {
expect(screen.getByLabelText('model')).toBeInTheDocument();
expect(screen.getByLabelText('temp')).toBeInTheDocument();
});
});
it('submits flow with parameter values', async () => {
// Set up mocks and render
// Fill in form including parameters
// Submit form
// Verify startFlow called with correct parameters
});
it('prevents submission with invalid parameters', () => {
// Set up form with required parameters
// Leave parameters empty
// Verify submit button is disabled
// Verify validation errors are shown
});
});
```
### End-to-End Tests
1. **Complete Flow Creation with Parameters**:
- Navigate to Flows page
- Click Create button
- Select flow class with parameters
- Fill in all required fields and parameters
- Submit form
- Verify flow appears in list
- Verify flow has correct parameter values
2. **Parameter Validation Scenarios**:
- Test all parameter types (string, number, boolean, enum)
- Test required field validation
- Test range validation for numbers
- Test enum value validation
- Test form reset after successful submission
## Migration Plan
### Phase 1: Core Parameter Support
1. ✅ Update FlowRequest/FlowResponse types (completed)
2. ✅ Update startFlow API method (completed)
3. ✅ Create ParameterInputs component (completed)
4. Integrate ParameterInputs into CreateDialog
5. Implement parameter fetching from config API
6. Add parameter validation logic
### Phase 2: Enhanced User Experience
1. Add loading states during parameter fetching
2. Improve validation error display
3. Add parameter descriptions and help text
4. Implement form reset on successful submission
### Phase 3: Advanced Features (Future)
1. Parameter presets and templates
2. Parameter value suggestions based on history
3. Bulk parameter import/export
4. Parameter dependency validation
5. Real-time parameter preview
## Security Considerations
1. **Parameter Validation**: All parameter validation occurs on both client and server
2. **Type Safety**: TypeScript ensures type safety for parameter values
3. **Sanitization**: Parameter values are properly sanitized before API calls
4. **Schema Validation**: Parameter schemas are validated against expected formats
5. **Error Handling**: Sensitive information is not exposed in validation errors
## Performance Considerations
1. **Lazy Loading**: Parameter definitions are only fetched when needed
2. **Caching**: Parameter definitions are cached using TanStack Query
3. **Validation Debouncing**: Real-time validation is debounced to avoid excessive computation
4. **Component Optimization**: ParameterInputs uses React.memo for performance
5. **Bundle Size**: Components are tree-shakeable and imported dynamically where possible
## Backwards Compatibility
1. **Flow Classes Without Parameters**: Existing flow classes continue to work without changes
2. **API Compatibility**: Parameter field is optional in API calls
3. **UI Graceful Degradation**: UI gracefully handles flows with no parameters
4. **Existing Flows**: Existing flows without parameters continue to function normally
## Future Enhancements
1. **Parameter Templates**: Pre-defined parameter sets for common use cases
2. **Conditional Parameters**: Parameters that appear based on other parameter values
3. **Parameter Groups**: Organize related parameters into collapsible sections
4. **Import/Export**: Bulk parameter configuration via JSON/YAML files
5. **Parameter History**: Track and suggest parameter values based on usage patterns
6. **Advanced Validation**: Custom validation rules and cross-parameter validation
7. **Parameter Documentation**: Rich documentation with examples and best practices