feat: 实现ComfyUI V2工作流模板参数配置功能

根据ComfyUI SDK规范,完整实现了工作流模板的参数配置功能:

核心功能:
-  参数添加/删除:支持动态添加和删除模板参数
-  参数类型支持:string、number、boolean、array、object
-  参数属性配置:默认值、描述、必填状态
-  类型特定配置:数字类型的最小/最大值、字符串的正则验证
-  实时预览:参数配置实时更新到模板数据中

技术实现:
- 符合ComfyUI SDK的ParameterSchema接口规范
- 支持{{参数名}}语法在工作流中引用参数
- 完整的表单验证和用户体验优化
- 响应式设计,支持不同屏幕尺寸

用户界面:
- 直观的参数管理界面
- 参数类型选择器
- 条件显示的高级配置选项
- 空状态提示和使用说明
- 删除确认和错误处理

这个实现为ComfyUI工作流模板提供了强大的参数化能力,
使模板更加灵活和可重用。
This commit is contained in:
root 2025-08-08 22:19:27 +08:00
parent 45f31c7aaa
commit 931285b4f2
1 changed files with 210 additions and 5 deletions

View File

@ -36,6 +36,8 @@ interface ParameterSchema {
min?: number;
max?: number;
pattern?: string;
items?: ParameterSchema;
properties?: Record<string, ParameterSchema>;
}
interface WorkflowTemplateData {
@ -91,6 +93,7 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
const [errors, setErrors] = useState<Record<string, string>>({});
const [isSaving, setIsSaving] = useState(false);
const [workflowJsonText, setWorkflowJsonText] = useState('{}');
const [newParameterName, setNewParameterName] = useState('');
// 重置表单数据
useEffect(() => {
@ -148,6 +151,38 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
}
};
// 更新参数
const updateParameter = (paramName: string, schema: ParameterSchema) => {
setTemplateData(prev => ({
...prev,
parameters: { ...prev.parameters, [paramName]: schema }
}));
};
// 删除参数
const removeParameter = (paramName: string) => {
setTemplateData(prev => {
const newParameters = { ...prev.parameters };
delete newParameters[paramName];
return { ...prev, parameters: newParameters };
});
};
// 添加新参数
const addParameter = () => {
if (!newParameterName.trim()) return;
const newSchema: ParameterSchema = {
param_type: 'string',
required: false,
description: '',
default: '',
};
updateParameter(newParameterName.trim(), newSchema);
setNewParameterName('');
};
// 验证表单
const validateForm = (): boolean => {
const newErrors: Record<string, string> = {};
@ -471,15 +506,185 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
{/* 参数配置标签页 */}
{activeTab === 'parameters' && (
<div className="space-y-6">
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-gray-900"></h3>
<div className="flex items-center space-x-2">
<Info className="w-5 h-5 text-blue-600" />
<h3 className="text-sm font-medium text-blue-900"></h3>
<input
type="text"
value={newParameterName}
onChange={(e) => setNewParameterName(e.target.value)}
placeholder="参数名称"
className="px-3 py-1 border border-gray-300 rounded text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent"
onKeyDown={(e) => e.key === 'Enter' && addParameter()}
/>
<button
onClick={addParameter}
disabled={!newParameterName.trim()}
className="px-3 py-1 bg-blue-600 text-white rounded text-sm hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-1"
>
<Plus className="w-4 h-4" />
<span></span>
</button>
</div>
<p className="mt-2 text-sm text-blue-700">
...
</div>
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4">
<div className="flex items-center space-x-2 mb-2">
<Info className="w-4 h-4 text-blue-600" />
<span className="text-sm font-medium text-gray-700"></span>
</div>
<p className="text-sm text-gray-600">
JSON中使用 <code className="bg-gray-200 px-1 rounded">{"{{参数名}}"}</code>
<code className="bg-gray-200 px-1 rounded">{"{{input_image}}"}</code>
</p>
</div>
{Object.keys(templateData.parameters).length === 0 ? (
<div className="text-center py-8 text-gray-500">
<Settings className="w-12 h-12 mx-auto mb-3 text-gray-400" />
<p></p>
<p className="text-sm"></p>
</div>
) : (
<div className="space-y-4">
{Object.entries(templateData.parameters).map(([paramName, schema]) => (
<div key={paramName} className="bg-white border border-gray-200 rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<h4 className="font-medium text-gray-900">{paramName}</h4>
<button
onClick={() => removeParameter(paramName)}
className="p-1 text-red-600 hover:bg-red-50 rounded transition-colors"
title="删除参数"
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<select
value={schema.param_type}
onChange={(e) => updateParameter(paramName, {
...schema,
param_type: e.target.value as any
})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="string"> (String)</option>
<option value="number"> (Number)</option>
<option value="boolean"> (Boolean)</option>
<option value="array"> (Array)</option>
<option value="object"> (Object)</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<input
type={schema.param_type === 'number' ? 'number' : 'text'}
value={schema.default || ''}
onChange={(e) => updateParameter(paramName, {
...schema,
default: schema.param_type === 'number' ? Number(e.target.value) : e.target.value
})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="设置默认值"
/>
</div>
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<input
type="text"
value={schema.description || ''}
onChange={(e) => updateParameter(paramName, {
...schema,
description: e.target.value
})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="参数描述"
/>
</div>
<div className="flex items-center space-x-4">
<label className="flex items-center space-x-2">
<input
type="checkbox"
checked={schema.required || false}
onChange={(e) => updateParameter(paramName, {
...schema,
required: e.target.checked
})}
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span className="text-sm text-gray-700"></span>
</label>
</div>
{schema.param_type === 'number' && (
<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<input
type="number"
value={schema.min || ''}
onChange={(e) => updateParameter(paramName, {
...schema,
min: e.target.value ? Number(e.target.value) : undefined
})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="最小值"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<input
type="number"
value={schema.max || ''}
onChange={(e) => updateParameter(paramName, {
...schema,
max: e.target.value ? Number(e.target.value) : undefined
})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="最大值"
/>
</div>
</div>
)}
{schema.param_type === 'string' && (
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-1">
</label>
<input
type="text"
value={schema.pattern || ''}
onChange={(e) => updateParameter(paramName, {
...schema,
pattern: e.target.value || undefined
})}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="例如:^[a-zA-Z0-9]+$"
/>
</div>
)}
</div>
</div>
))}
</div>
)}
</div>
)}
</div>