docs(dashboard): add comprehensive dynamic form system documentation

- Overview of schema-driven UI system
- Quick start guide with examples
- Backend UI metadata guide (json_schema_extra)
- Hook component creation tutorial
- API reference for all components
- Troubleshooting section
pull/1496/head
DrSmoothl 2026-02-17 18:18:51 +08:00
parent fa15fef1f0
commit 2de618464a
No known key found for this signature in database
1 changed files with 126 additions and 0 deletions

View File

@ -0,0 +1,126 @@
# 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 `ConfigSchema` and 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:
```typescript
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 (requires `ge`, `le`, and `step`).
- `switch`: A toggle switch (for booleans).
- `textarea`: A multi-line text input.
- `select`: A dropdown menu (for `Literal` or 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
```python
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 for `wrapper` hooks) The default field renderer.
### Implementation Example
```typescript
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:
```typescript
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., `chat` vs `Chat`).
- **Field missing**: Check if the field is present in the `ConfigSchema` returned by the backend.
- **TypeScript errors**: Ensure your Hook implements the `FieldHookComponent` type.