Dynamic Config Form System
Overview
The Dynamic Config Form system is a schema-driven UI component designed to automatically generate configuration forms based on backend Pydantic models. It supports rich metadata for UI customization and a flexible Hook system for complex fields.
Core Components
- DynamicConfigForm: The main component that takes a
ConfigSchemaand renders the entire form. - DynamicField: A lower-level component that renders individual fields based on their type and UI metadata.
- FieldHookRegistry: A registry for custom React components that can replace or wrap default field rendering.
Quick Start
To use the dynamic form in your page:
import { DynamicConfigForm } from '@/components/dynamic-form'
import { fieldHooks } from '@/lib/field-hooks'
// Example usage in a component
export function ConfigPage() {
const [config, setConfig] = useState({})
const schema = useConfigSchema() // Fetch from API
const handleChange = (fieldPath: string, value: unknown) => {
// fieldPath can be nested, e.g., 'section.subfield'
updateConfigAt(fieldPath, value)
}
return (
<DynamicConfigForm
schema={schema}
values={config}
onChange={handleChange}
hooks={fieldHooks}
/>
)
}
Adding UI Metadata (Backend)
You can customize how fields are rendered by adding json_schema_extra to your Pydantic Field definitions.
Supported Metadata
x-widget: Specifies the UI component to use.slider: A range slider (requiresge,le, andstep).switch: A toggle switch (for booleans).textarea: A multi-line text input.select: A dropdown menu (forLiteralor enum types).custom: Indicates that this field requires a Hook for rendering.
x-icon: A Lucide icon name (e.g.,MessageSquare,Settings).step: Incremental step for sliders or number inputs.
Example
class ChatConfig(ConfigBase):
talk_value: float = Field(
default=0.5,
ge=0.0,
le=1.0,
json_schema_extra={
"x-widget": "slider",
"x-icon": "MessageSquare",
"step": 0.1
}
)
Creating Hook Components
Hooks allow you to provide custom UI for complex configuration sections or fields.
FieldHookComponent Interface
A Hook component receives the following props:
fieldPath: The full path to the field.value: The current value of the field/section.onChange: Callback to update the value.children: (Only forwrapperhooks) The default field renderer.
Implementation Example
import type { FieldHookComponent } from '@/lib/field-hooks'
export const CustomSectionHook: FieldHookComponent = ({
fieldPath,
value,
onChange
}) => {
return (
<div className="custom-section">
<h3>Custom UI</h3>
<input
value={value.some_prop}
onChange={(e) => onChange({ ...value, some_prop: e.target.value })}
/>
</div>
)
}
Registering Hooks
Register hooks in your component's lifecycle:
useEffect(() => {
fieldHooks.register('chat', ChatSectionHook, 'replace')
return () => fieldHooks.unregister('chat')
}, [])
API Reference
DynamicConfigForm
| Prop | Type | Description |
|---|---|---|
schema |
ConfigSchema |
The schema generated by the backend. |
values |
Record<string, any> |
Current configuration values. |
onChange |
(field: string, value: any) => void |
Change handler. |
hooks |
FieldHookRegistry |
Optional custom hook registry. |
FieldHookRegistry
register(path, component, type): Register a hook.get(path): Retrieve a registered hook.has(path): Check if a hook exists.unregister(path): Remove a hook.
Troubleshooting
- Hook not rendering: Ensure the registration path matches the schema field name exactly (e.g.,
chatvsChat). - Field missing: Check if the field is present in the
ConfigSchemareturned by the backend. - TypeScript errors: Ensure your Hook implements the
FieldHookComponenttype.