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} />;
}
};
Modal UX Details
- 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.