feat: 实现ComfyUI V2工作流模板参数配置折叠表单功能
根据ComfyUI SDK规范,为参数配置添加了折叠表单功能,提供简洁的信息展示和详细的配置表单: 🎯 核心功能: - ✅ 折叠/展开控制:每个参数都有独立的折叠状态管理 - ✅ 简洁信息展示:参数名称、类型、默认值、描述的概览 - ✅ 详细配置表单:展开后显示完整的参数配置选项 - ✅ 类型图标标识:不同参数类型使用不同颜色和图标 🎨 用户界面优化: - 参数卡片式设计,支持折叠/展开 - 类型标签和必填标识的视觉提示 - 悬停效果和交互反馈 - 简洁的信息概览和详细配置分离 🛠 技术实现: - 使用Set管理展开状态,支持多个参数同时展开 - 类型图标映射系统,每种类型有专属图标和颜色 - 智能值格式化显示,根据类型优化显示效果 - 事件冒泡控制,确保删除按钮不触发展开/折叠 📱 交互体验: - 点击参数卡片头部切换展开/折叠状态 - 展开状态下显示完整的参数配置表单 - 折叠状态下显示关键信息概览 - 删除按钮独立操作,不影响折叠状态 这个实现大大提升了参数配置的用户体验, 让用户能够快速浏览参数概览,按需展开详细配置。
This commit is contained in:
parent
d7e1ce792f
commit
4b016c2702
|
|
@ -14,6 +14,16 @@ import {
|
|||
AlertCircle,
|
||||
Plus,
|
||||
Trash2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Type,
|
||||
Hash,
|
||||
ToggleLeft,
|
||||
Image,
|
||||
Music,
|
||||
Video,
|
||||
List,
|
||||
Braces,
|
||||
} from 'lucide-react';
|
||||
|
||||
// 定义符合ComfyUI SDK的模板数据结构
|
||||
|
|
@ -101,6 +111,7 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
|||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [workflowJsonText, setWorkflowJsonText] = useState('{}');
|
||||
const [newParameterName, setNewParameterName] = useState('');
|
||||
const [expandedParameters, setExpandedParameters] = useState<Set<string>>(new Set());
|
||||
|
||||
// 重置表单数据
|
||||
useEffect(() => {
|
||||
|
|
@ -190,6 +201,19 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
|||
setNewParameterName('');
|
||||
};
|
||||
|
||||
// 切换参数展开/折叠状态
|
||||
const toggleParameterExpanded = (paramName: string) => {
|
||||
setExpandedParameters(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(paramName)) {
|
||||
newSet.delete(paramName);
|
||||
} else {
|
||||
newSet.add(paramName);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
// 根据参数类型获取默认配置
|
||||
const getDefaultConfigForType = (type: string): Partial<ParameterSchema> => {
|
||||
switch (type) {
|
||||
|
|
@ -274,6 +298,51 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
|||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
// 获取参数类型的显示信息
|
||||
const getParameterTypeInfo = (type: string) => {
|
||||
const typeMap = {
|
||||
string: { label: '字符串', icon: Type, color: 'text-blue-600', bgColor: 'bg-blue-50' },
|
||||
integer: { label: '整数', icon: Hash, color: 'text-green-600', bgColor: 'bg-green-50' },
|
||||
float: { label: '浮点数', icon: Hash, color: 'text-green-600', bgColor: 'bg-green-50' },
|
||||
number: { label: '数字', icon: Hash, color: 'text-green-600', bgColor: 'bg-green-50' },
|
||||
boolean: { label: '布尔值', icon: ToggleLeft, color: 'text-purple-600', bgColor: 'bg-purple-50' },
|
||||
image: { label: '图片', icon: Image, color: 'text-pink-600', bgColor: 'bg-pink-50' },
|
||||
audio: { label: '音频', icon: Music, color: 'text-orange-600', bgColor: 'bg-orange-50' },
|
||||
video: { label: '视频', icon: Video, color: 'text-red-600', bgColor: 'bg-red-50' },
|
||||
array: { label: '数组', icon: List, color: 'text-indigo-600', bgColor: 'bg-indigo-50' },
|
||||
object: { label: '对象', icon: Braces, color: 'text-gray-600', bgColor: 'bg-gray-50' },
|
||||
};
|
||||
return typeMap[type as keyof typeof typeMap] || typeMap.string;
|
||||
};
|
||||
|
||||
// 格式化参数值显示
|
||||
const formatParameterValue = (schema: ParameterSchema) => {
|
||||
if (schema.default === undefined || schema.default === null || schema.default === '') {
|
||||
return '未设置';
|
||||
}
|
||||
|
||||
if (schema.param_type === 'boolean') {
|
||||
return schema.default ? 'True' : 'False';
|
||||
}
|
||||
|
||||
if (schema.param_type === 'integer' || schema.param_type === 'float' || schema.param_type === 'number') {
|
||||
let value = String(schema.default);
|
||||
if (schema.min !== undefined || schema.max !== undefined) {
|
||||
const range = [];
|
||||
if (schema.min !== undefined) range.push(`最小: ${schema.min}`);
|
||||
if (schema.max !== undefined) range.push(`最大: ${schema.max}`);
|
||||
if (range.length > 0) value += ` (${range.join(', ')})`;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
if (schema.param_type === 'image' || schema.param_type === 'audio' || schema.param_type === 'video') {
|
||||
return schema.default || '未设置文件';
|
||||
}
|
||||
|
||||
return String(schema.default);
|
||||
};
|
||||
|
||||
// 处理保存
|
||||
const handleSave = async () => {
|
||||
if (!validateForm()) {
|
||||
|
|
@ -608,21 +677,73 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
|||
<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="space-y-3">
|
||||
{Object.entries(templateData.parameters).map(([paramName, schema]) => {
|
||||
const typeInfo = getParameterTypeInfo(schema.param_type);
|
||||
const isExpanded = expandedParameters.has(paramName);
|
||||
const Icon = typeInfo.icon;
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
return (
|
||||
<div key={paramName} className="bg-white border border-gray-200 rounded-lg overflow-hidden">
|
||||
{/* 折叠头部 - 简洁信息展示 */}
|
||||
<div
|
||||
className="p-4 cursor-pointer hover:bg-gray-50 transition-colors"
|
||||
onClick={() => toggleParameterExpanded(paramName)}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
{isExpanded ? (
|
||||
<ChevronDown className="w-4 h-4 text-gray-500" />
|
||||
) : (
|
||||
<ChevronRight className="w-4 h-4 text-gray-500" />
|
||||
)}
|
||||
<div className={`p-1.5 rounded ${typeInfo.bgColor}`}>
|
||||
<Icon className={`w-4 h-4 ${typeInfo.color}`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h4 className="font-medium text-gray-900">{paramName}</h4>
|
||||
<span className={`px-2 py-0.5 text-xs rounded-full ${typeInfo.bgColor} ${typeInfo.color}`}>
|
||||
{typeInfo.label}
|
||||
</span>
|
||||
{schema.required && (
|
||||
<span className="px-2 py-0.5 text-xs rounded-full bg-red-50 text-red-600">
|
||||
必填
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4 mt-1 text-sm text-gray-500">
|
||||
<span>默认值: {formatParameterValue(schema)}</span>
|
||||
{schema.description && (
|
||||
<span className="truncate max-w-xs">描述: {schema.description}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
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>
|
||||
</div>
|
||||
|
||||
{/* 展开的详细配置表单 */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-gray-200 p-4 bg-gray-50">
|
||||
<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">
|
||||
参数类型
|
||||
|
|
@ -908,11 +1029,14 @@ export const WorkflowTemplateCreator: React.FC<WorkflowTemplateCreatorProps> = (
|
|||
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>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue