Files
mai-bot/dashboard/src/components/dynamic-form

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:

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

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

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., 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.