Documents
Home>Documents>AI>Agent>Xgen

Building a Dynamic Form Modal for Node Parameter Editing

7 min readAug 8, 2025Feb 22, 2026

Editing Node Settings Comfortably

Canvas nodes are small cards, which makes displaying complex configuration difficult. We introduced a modal that opens when you double-click a node, letting you edit all its parameters in one place.

Why a Dynamic Form

XGen's node types keep growing. Hard-coding an edit form for every new node type would make maintenance impossible. Instead, we built a dynamic form system that auto-generates forms from each node's schema.

Parameter Schema Definition

interface ParameterSchema {
  key: string;
  label: string;
  type: 'string' | 'number' | 'boolean' | 'select' | 'textarea' | 'slider' | 'json' | 'code';
  defaultValue: unknown;
  required: boolean;
  description?: string;
  validation?: {
    min?: number;
    max?: number;
    pattern?: string;
    options?: { label: string; value: string }[];  // select 타입용
  };
  group?: string;      // 파라미터 그룹핑
  advanced?: boolean;  // 고급 설정 여부
}

// ChatOpenAI 노드의 파라미터 스키마 예시
const chatOpenAISchema: ParameterSchema[] = [
  { key: 'model', label: '모델', type: 'select', defaultValue: 'gpt-4o',
    required: true, validation: {
      options: [
        { label: 'GPT-4o', value: 'gpt-4o' },
        { label: 'GPT-4o Mini', value: 'gpt-4o-mini' },
      ]
    }, group: '기본 설정' },
  { key: 'temperature', label: 'Temperature', type: 'slider', defaultValue: 0.7,
    required: false, validation: { min: 0, max: 2 }, group: '기본 설정' },
  { key: 'systemPrompt', label: '시스템 프롬프트', type: 'textarea', defaultValue: '',
    required: false, group: '프롬프트' },
  { key: 'maxTokens', label: '최대 토큰', type: 'number', defaultValue: 4096,
    required: false, validation: { min: 1, max: 128000 }, group: '고급', advanced: true },
];

Dynamic Form Renderer

We implemented a renderer that takes a schema and generates the form automatically.

const DynamicForm: React.FC<{ schema: ParameterSchema[]; values: Record<string, unknown>; onChange: (key: string, value: unknown) => void }> = ({
  schema, values, onChange
}) => {
  const groups = groupBy(schema, 'group');
  const [showAdvanced, setShowAdvanced] = useState(false);
  
  return (
    <div className="space-y-6">
      {Object.entries(groups).map(([group, params]) => (
        <fieldset key={group} className="space-y-4">
          <legend className="text-sm font-semibold text-gray-700">{group}</legend>
          {params
            .filter((p) => !p.advanced || showAdvanced)
            .map((param) => (
              <FormField
                key={param.key}
                schema={param}
                value={values[param.key]}
                onChange={(v) => onChange(param.key, v)}
              />
          ))}
        </fieldset>
      ))}
      <button onClick={() => setShowAdvanced(!showAdvanced)} className="text-sm text-blue-500">
        {showAdvanced ? '기본 설정만 보기' : '고급 설정 표시'}
      </button>
    </div>
  );
};

FormField Component Branching

Each type renders the appropriate input component.

const FormField: React.FC<FormFieldProps> = ({ schema, value, onChange }) => {
  switch (schema.type) {
    case 'string':  return <TextInput {...props} />;
    case 'number':  return <NumberInput {...props} />;
    case 'boolean': return <Toggle {...props} />;
    case 'select':  return <Select options={schema.validation?.options} {...props} />;
    case 'textarea':return <Textarea {...props} />;
    case 'slider':  return <Slider min={schema.validation?.min} max={schema.validation?.max} {...props} />;
    case 'json':    return <JsonEditor {...props} />;
    case 'code':    return <CodeEditor {...props} />;
    default:        return <TextInput {...props} />;
  }
};
  • Close with Escape: shows a confirmation dialog if there are unsaved changes
  • Dirty state indicator: a blue dot marks any modified parameter
  • Reset to default: a reset button next to each parameter
  • Real-time validation: validates on every keystroke and displays error messages inline

With this dynamic form system, adding a new node type only requires defining its schema — the edit UI is generated automatically. This has significantly improved our development velocity.

Tags
dynamic formnode editormodalReactschema-based