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

根据ComfyUI SDK规范,为参数配置添加了折叠表单功能,提供简洁的信息展示和详细的配置表单:

🎯 核心功能:
-  折叠/展开控制:每个参数都有独立的折叠状态管理
-  简洁信息展示:参数名称、类型、默认值、描述的概览
-  详细配置表单:展开后显示完整的参数配置选项
-  类型图标标识:不同参数类型使用不同颜色和图标

🎨 用户界面优化:
- 参数卡片式设计,支持折叠/展开
- 类型标签和必填标识的视觉提示
- 悬停效果和交互反馈
- 简洁的信息概览和详细配置分离

🛠 技术实现:
- 使用Set管理展开状态,支持多个参数同时展开
- 类型图标映射系统,每种类型有专属图标和颜色
- 智能值格式化显示,根据类型优化显示效果
- 事件冒泡控制,确保删除按钮不触发展开/折叠

📱 交互体验:
- 点击参数卡片头部切换展开/折叠状态
- 展开状态下显示完整的参数配置表单
- 折叠状态下显示关键信息概览
- 删除按钮独立操作,不影响折叠状态

这个实现大大提升了参数配置的用户体验,
让用户能够快速浏览参数概览,按需展开详细配置。
This commit is contained in:
root 2025-08-08 22:32:20 +08:00
parent d7e1ce792f
commit 4b016c2702
1 changed files with 140 additions and 16 deletions

View File

@ -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>