feat: 增强ComfyUI V2工作流模板参数配置,支持专业AI工作流参数类型
根据ComfyUI SDK规范和AI工作流需求,大幅增强参数配置功能: 🎯 新增专业参数类型: - ✅ 图片 (Image) - 支持JPG/PNG/WebP等格式,文件大小限制,尺寸推荐 - ✅ 音频 (Audio) - 支持MP3/WAV/FLAC等格式,时长限制 - ✅ 视频 (Video) - 支持MP4/AVI/MOV等格式,尺寸和时长限制 - ✅ 整数 (Integer) - 默认步长1,范围0-100,默认值10 - ✅ 浮点数 (Float) - 默认步长0.01,范围0.0-1.0,默认值0.3 🛠 智能默认配置: - 根据参数类型自动应用合适的默认值和约束 - 整数类型:步长1,最小0,最大100,默认10 - 浮点数类型:步长0.01,最小0.00,最大1.0,默认0.3 - 媒体类型:预设文件格式、大小限制和质量要求 🎨 增强用户界面: - 分组显示参数类型(基础/数值/媒体) - 根据类型动态显示相应的配置选项 - 智能输入控件(数字步长、布尔选择器、文件格式提示) - 专业的媒体参数配置(格式、大小、尺寸、时长) 🔧 技术特性: - 扩展ParameterSchema接口支持媒体属性 - 类型切换时自动应用默认配置 - 完整的参数验证和约束设置 - 符合ComfyUI SDK的参数规范 这个增强使工作流模板能够处理复杂的AI工作流场景, 特别适合图像生成、音频处理、视频编辑等专业应用。
This commit is contained in:
parent
931285b4f2
commit
d7e1ce792f
|
|
@ -28,16 +28,23 @@ interface TemplateMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ParameterSchema {
|
interface ParameterSchema {
|
||||||
param_type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
param_type: 'string' | 'number' | 'boolean' | 'array' | 'object' | 'image' | 'audio' | 'video' | 'integer' | 'float';
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
default?: any;
|
default?: any;
|
||||||
description?: string;
|
description?: string;
|
||||||
enum?: any[];
|
enum?: any[];
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
|
step?: number;
|
||||||
pattern?: string;
|
pattern?: string;
|
||||||
items?: ParameterSchema;
|
items?: ParameterSchema;
|
||||||
properties?: Record<string, ParameterSchema>;
|
properties?: Record<string, ParameterSchema>;
|
||||||
|
// 媒体文件相关属性
|
||||||
|
accept?: string; // 文件类型限制
|
||||||
|
maxSize?: number; // 最大文件大小(字节)
|
||||||
|
width?: number; // 图片/视频宽度
|
||||||
|
height?: number; // 图片/视频高度
|
||||||
|
duration?: number; // 音频/视频时长限制
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WorkflowTemplateData {
|
interface WorkflowTemplateData {
|
||||||
|
|
@ -183,6 +190,61 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
||||||
setNewParameterName('');
|
setNewParameterName('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 根据参数类型获取默认配置
|
||||||
|
const getDefaultConfigForType = (type: string): Partial<ParameterSchema> => {
|
||||||
|
switch (type) {
|
||||||
|
case 'integer':
|
||||||
|
return {
|
||||||
|
default: 10,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
step: 1,
|
||||||
|
};
|
||||||
|
case 'float':
|
||||||
|
return {
|
||||||
|
default: 0.3,
|
||||||
|
min: 0.0,
|
||||||
|
max: 1.0,
|
||||||
|
step: 0.01,
|
||||||
|
};
|
||||||
|
case 'image':
|
||||||
|
return {
|
||||||
|
default: '',
|
||||||
|
accept: '.jpg,.jpeg,.png,.webp,.bmp,.tiff',
|
||||||
|
maxSize: 10 * 1024 * 1024, // 10MB
|
||||||
|
description: '支持的图片格式:JPG, PNG, WebP, BMP, TIFF',
|
||||||
|
};
|
||||||
|
case 'audio':
|
||||||
|
return {
|
||||||
|
default: '',
|
||||||
|
accept: '.mp3,.wav,.flac,.aac,.ogg',
|
||||||
|
maxSize: 50 * 1024 * 1024, // 50MB
|
||||||
|
description: '支持的音频格式:MP3, WAV, FLAC, AAC, OGG',
|
||||||
|
};
|
||||||
|
case 'video':
|
||||||
|
return {
|
||||||
|
default: '',
|
||||||
|
accept: '.mp4,.avi,.mov,.mkv,.webm',
|
||||||
|
maxSize: 500 * 1024 * 1024, // 500MB
|
||||||
|
description: '支持的视频格式:MP4, AVI, MOV, MKV, WebM',
|
||||||
|
};
|
||||||
|
case 'boolean':
|
||||||
|
return {
|
||||||
|
default: false,
|
||||||
|
};
|
||||||
|
case 'number':
|
||||||
|
return {
|
||||||
|
default: 0,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
default: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 验证表单
|
// 验证表单
|
||||||
const validateForm = (): boolean => {
|
const validateForm = (): boolean => {
|
||||||
const newErrors: Record<string, string> = {};
|
const newErrors: Record<string, string> = {};
|
||||||
|
|
@ -567,17 +629,33 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
value={schema.param_type}
|
value={schema.param_type}
|
||||||
onChange={(e) => updateParameter(paramName, {
|
onChange={(e) => {
|
||||||
|
const newType = e.target.value as any;
|
||||||
|
const defaultConfig = getDefaultConfigForType(newType);
|
||||||
|
updateParameter(paramName, {
|
||||||
...schema,
|
...schema,
|
||||||
param_type: e.target.value as any
|
param_type: newType,
|
||||||
})}
|
...defaultConfig
|
||||||
|
});
|
||||||
|
}}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
|
<optgroup label="基础类型">
|
||||||
<option value="string">字符串 (String)</option>
|
<option value="string">字符串 (String)</option>
|
||||||
<option value="number">数字 (Number)</option>
|
|
||||||
<option value="boolean">布尔值 (Boolean)</option>
|
<option value="boolean">布尔值 (Boolean)</option>
|
||||||
<option value="array">数组 (Array)</option>
|
<option value="array">数组 (Array)</option>
|
||||||
<option value="object">对象 (Object)</option>
|
<option value="object">对象 (Object)</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="数值类型">
|
||||||
|
<option value="integer">整数 (Integer)</option>
|
||||||
|
<option value="float">浮点数 (Float)</option>
|
||||||
|
<option value="number">数字 (Number)</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="媒体类型">
|
||||||
|
<option value="image">图片 (Image)</option>
|
||||||
|
<option value="audio">音频 (Audio)</option>
|
||||||
|
<option value="video">视频 (Video)</option>
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -585,16 +663,60 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
默认值
|
默认值
|
||||||
</label>
|
</label>
|
||||||
|
{schema.param_type === 'boolean' ? (
|
||||||
|
<select
|
||||||
|
value={schema.default ? 'true' : 'false'}
|
||||||
|
onChange={(e) => updateParameter(paramName, {
|
||||||
|
...schema,
|
||||||
|
default: e.target.value === 'true'
|
||||||
|
})}
|
||||||
|
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="false">False</option>
|
||||||
|
<option value="true">True</option>
|
||||||
|
</select>
|
||||||
|
) : schema.param_type === 'integer' || schema.param_type === 'float' || schema.param_type === 'number' ? (
|
||||||
<input
|
<input
|
||||||
type={schema.param_type === 'number' ? 'number' : 'text'}
|
type="number"
|
||||||
|
step={schema.step || (schema.param_type === 'float' ? 0.01 : 1)}
|
||||||
|
min={schema.min}
|
||||||
|
max={schema.max}
|
||||||
value={schema.default || ''}
|
value={schema.default || ''}
|
||||||
onChange={(e) => updateParameter(paramName, {
|
onChange={(e) => updateParameter(paramName, {
|
||||||
...schema,
|
...schema,
|
||||||
default: schema.param_type === 'number' ? Number(e.target.value) : e.target.value
|
default: schema.param_type === 'integer' ? parseInt(e.target.value) || 0 : parseFloat(e.target.value) || 0
|
||||||
})}
|
})}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
placeholder="设置默认值"
|
placeholder="设置默认值"
|
||||||
/>
|
/>
|
||||||
|
) : schema.param_type === 'image' || schema.param_type === 'audio' || schema.param_type === 'video' ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={schema.default || ''}
|
||||||
|
onChange={(e) => updateParameter(paramName, {
|
||||||
|
...schema,
|
||||||
|
default: 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="默认文件名或路径"
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
支持格式: {schema.accept || '所有格式'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={schema.default || ''}
|
||||||
|
onChange={(e) => updateParameter(paramName, {
|
||||||
|
...schema,
|
||||||
|
default: 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>
|
||||||
|
|
||||||
<div className="md:col-span-2">
|
<div className="md:col-span-2">
|
||||||
|
|
@ -628,14 +750,15 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{schema.param_type === 'number' && (
|
{(schema.param_type === 'number' || schema.param_type === 'integer' || schema.param_type === 'float') && (
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-1">
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
最小值
|
最小值
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
|
step={schema.param_type === 'float' ? 0.01 : 1}
|
||||||
value={schema.min || ''}
|
value={schema.min || ''}
|
||||||
onChange={(e) => updateParameter(paramName, {
|
onChange={(e) => updateParameter(paramName, {
|
||||||
...schema,
|
...schema,
|
||||||
|
|
@ -651,6 +774,7 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
|
step={schema.param_type === 'float' ? 0.01 : 1}
|
||||||
value={schema.max || ''}
|
value={schema.max || ''}
|
||||||
onChange={(e) => updateParameter(paramName, {
|
onChange={(e) => updateParameter(paramName, {
|
||||||
...schema,
|
...schema,
|
||||||
|
|
@ -660,6 +784,112 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
||||||
placeholder="最大值"
|
placeholder="最大值"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
步长
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step={schema.param_type === 'float' ? 0.001 : 1}
|
||||||
|
value={schema.step || ''}
|
||||||
|
onChange={(e) => updateParameter(paramName, {
|
||||||
|
...schema,
|
||||||
|
step: 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={schema.param_type === 'float' ? '0.01' : '1'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(schema.param_type === 'image' || schema.param_type === 'audio' || schema.param_type === 'video') && (
|
||||||
|
<div className="md:col-span-2 space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
支持的文件格式
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={schema.accept || ''}
|
||||||
|
onChange={(e) => updateParameter(paramName, {
|
||||||
|
...schema,
|
||||||
|
accept: 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=".jpg,.png,.webp"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
最大文件大小 (MB)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={schema.maxSize ? Math.round(schema.maxSize / (1024 * 1024)) : ''}
|
||||||
|
onChange={(e) => updateParameter(paramName, {
|
||||||
|
...schema,
|
||||||
|
maxSize: e.target.value ? Number(e.target.value) * 1024 * 1024 : 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="10"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(schema.param_type === 'image' || schema.param_type === 'video') && (
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
推荐宽度 (px)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={schema.width || ''}
|
||||||
|
onChange={(e) => updateParameter(paramName, {
|
||||||
|
...schema,
|
||||||
|
width: 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="1920"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
推荐高度 (px)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={schema.height || ''}
|
||||||
|
onChange={(e) => updateParameter(paramName, {
|
||||||
|
...schema,
|
||||||
|
height: 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="1080"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(schema.param_type === 'audio' || schema.param_type === 'video') && (
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
最大时长 (秒)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={schema.duration || ''}
|
||||||
|
onChange={(e) => updateParameter(paramName, {
|
||||||
|
...schema,
|
||||||
|
duration: 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="300"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue