From 1b9eec8050209e881bc043f5c8dc85b0dd59fa60 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 8 Aug 2025 22:12:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9B=E5=BB=BA=E7=AC=A6=E5=90=88Com?= =?UTF-8?q?fyUI=20SDK=E8=A7=84=E8=8C=83=E7=9A=84=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=B5=81=E6=A8=A1=E6=9D=BF=E5=88=9B=E5=BB=BA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建了新的WorkflowTemplateCreator组件,专门用于创建符合ComfyUI SDK规范的WorkflowTemplateData格式模板 - 支持模板元数据配置(ID、名称、描述、版本、作者、分类、标签) - 支持ComfyUI工作流JSON导入和编辑 - 支持参数配置(预留接口) - 实现了完整的表单验证和错误处理 - 自动生成模板ID功能 - 为后续与ComfyUI SDK模板系统集成做准备 主要特性: - ✅ 符合ComfyUI SDK的WorkflowTemplateData格式 - ✅ 完整的模板元数据管理 - ✅ JSON文件导入功能 - ✅ 表单验证和错误提示 - ✅ 自动ID生成 - ✅ 响应式设计 --- apps/desktop/package.json | 4 +- .../components/comfyui/WorkflowManager.tsx | 3 +- .../comfyui/WorkflowTemplateCreator.tsx | 518 ++++++++++++++++++ .../components/comfyui/WorkflowV2Creator.tsx | 179 ++++-- pnpm-lock.yaml | 4 +- 5 files changed, 662 insertions(+), 46 deletions(-) create mode 100644 apps/desktop/src/components/comfyui/WorkflowTemplateCreator.tsx diff --git a/apps/desktop/package.json b/apps/desktop/package.json index bc39ba7..90a1870 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -44,8 +44,8 @@ "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", "@testing-library/user-event": "^14.5.1", - "@types/react": "^18.3.1", - "@types/react-dom": "^18.3.1", + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.7", "@types/react-window": "^1.8.8", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.16", diff --git a/apps/desktop/src/components/comfyui/WorkflowManager.tsx b/apps/desktop/src/components/comfyui/WorkflowManager.tsx index 7fde353..b8654d1 100644 --- a/apps/desktop/src/components/comfyui/WorkflowManager.tsx +++ b/apps/desktop/src/components/comfyui/WorkflowManager.tsx @@ -18,7 +18,8 @@ import { } from '@heroicons/react/24/outline'; import { useComfyUIV2Store, useFilteredWorkflows } from '../../store/comfyuiV2Store'; import type { WorkflowV2 } from '../../services/comfyuiV2Service'; -import { WorkflowV2Creator } from './WorkflowV2Creator'; +import { WorkflowTemplateCreator } from './WorkflowTemplateCreator'; +import { invoke } from '@tauri-apps/api/core'; interface WorkflowManagerProps { className?: string; diff --git a/apps/desktop/src/components/comfyui/WorkflowTemplateCreator.tsx b/apps/desktop/src/components/comfyui/WorkflowTemplateCreator.tsx new file mode 100644 index 0000000..a7f08f9 --- /dev/null +++ b/apps/desktop/src/components/comfyui/WorkflowTemplateCreator.tsx @@ -0,0 +1,518 @@ +/** + * ComfyUI V2 工作流模板创建器组件 + * 专门用于创建符合ComfyUI SDK规范的WorkflowTemplateData格式的模板 + */ + +import React, { useState, useEffect } from 'react'; +import { + X, + Save, + Upload, + FileText, + Settings, + Info, + AlertCircle, + Plus, + Trash2, +} from 'lucide-react'; + +// 定义符合ComfyUI SDK的模板数据结构 +interface TemplateMetadata { + id: string; + name: string; + description?: string; + version?: string; + author?: string; + tags?: string[]; + category?: string; +} + +interface ParameterSchema { + param_type: 'string' | 'number' | 'boolean' | 'array' | 'object'; + required?: boolean; + default?: any; + description?: string; + enum?: any[]; + min?: number; + max?: number; + pattern?: string; +} + +interface WorkflowTemplateData { + metadata: TemplateMetadata; + workflow: any; // ComfyUI workflow JSON + parameters: Record; +} + +interface CreateTemplateRequest { + name: string; + category?: string; + description?: string; + template_data: WorkflowTemplateData; + parameter_schema: Record; + tags: string[]; + author?: string; +} + +interface WorkflowTemplateCreatorProps { + /** 是否显示模态框 */ + isOpen: boolean; + /** 关闭回调 */ + onClose: () => void; + /** 保存回调 */ + onSave: (templateData: CreateTemplateRequest) => Promise; + /** 编辑的模板(为空时表示创建新模板) */ + editingTemplate?: any; +} + +/** + * ComfyUI V2 工作流模板创建器组件 + */ +export const WorkflowTemplateCreator: React.FC = ({ + isOpen, + onClose, + onSave, + editingTemplate +}) => { + const [activeTab, setActiveTab] = useState<'basic' | 'workflow' | 'parameters'>('basic'); + const [templateData, setTemplateData] = useState({ + metadata: { + id: '', + name: '', + description: '', + version: '1.0.0', + author: '', + tags: [], + category: '', + }, + workflow: {}, + parameters: {}, + }); + const [errors, setErrors] = useState>({}); + const [isSaving, setIsSaving] = useState(false); + const [workflowJsonText, setWorkflowJsonText] = useState('{}'); + + // 重置表单数据 + useEffect(() => { + if (isOpen) { + if (editingTemplate) { + setTemplateData({ + metadata: { + id: editingTemplate.id || '', + name: editingTemplate.name || '', + description: editingTemplate.description || '', + version: editingTemplate.version || '1.0.0', + author: editingTemplate.author || '', + tags: editingTemplate.tags || [], + category: editingTemplate.category || '', + }, + workflow: editingTemplate.workflow_data || {}, + parameters: editingTemplate.parameters || {}, + }); + setWorkflowJsonText(JSON.stringify(editingTemplate.workflow_data || {}, null, 2)); + } else { + // 重置为默认值 + setTemplateData({ + metadata: { + id: '', + name: '', + description: '', + version: '1.0.0', + author: '', + tags: [], + category: '', + }, + workflow: {}, + parameters: {}, + }); + setWorkflowJsonText('{}'); + } + setErrors({}); + setActiveTab('basic'); + } + }, [editingTemplate, isOpen]); + + // 更新模板元数据字段 + const updateMetadata = (field: keyof TemplateMetadata, value: any) => { + setTemplateData(prev => ({ + ...prev, + metadata: { ...prev.metadata, [field]: value } + })); + // 清除该字段的错误 + if (errors[field]) { + setErrors(prev => { + const newErrors = { ...prev }; + delete newErrors[field]; + return newErrors; + }); + } + }; + + // 验证表单 + const validateForm = (): boolean => { + const newErrors: Record = {}; + + if (!templateData.metadata.name.trim()) { + newErrors.name = '模板名称不能为空'; + } + + if (!templateData.metadata.id.trim()) { + newErrors.id = '模板ID不能为空'; + } + + if (!workflowJsonText.trim() || workflowJsonText.trim() === '{}') { + newErrors.workflow = '工作流数据不能为空'; + } else { + try { + const parsed = JSON.parse(workflowJsonText); + if (typeof parsed !== 'object' || parsed === null) { + newErrors.workflow = '工作流数据必须是有效的JSON对象'; + } + } catch (error) { + newErrors.workflow = 'JSON格式错误,请检查语法'; + } + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + // 处理保存 + const handleSave = async () => { + if (!validateForm()) { + return; + } + + setIsSaving(true); + try { + const workflowData = JSON.parse(workflowJsonText); + + // 构建符合ComfyUI SDK规范的模板数据 + const finalTemplateData: WorkflowTemplateData = { + ...templateData, + workflow: workflowData, + }; + + const requestData: CreateTemplateRequest = { + name: templateData.metadata.name, + category: templateData.metadata.category, + description: templateData.metadata.description, + template_data: finalTemplateData, + parameter_schema: templateData.parameters, + tags: templateData.metadata.tags || [], + author: templateData.metadata.author, + }; + + await onSave(requestData); + } catch (error) { + console.error('保存模板失败:', error); + setErrors({ general: `保存失败: ${error}` }); + } finally { + setIsSaving(false); + } + }; + + // 导入ComfyUI工作流 + const handleImportWorkflow = () => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + input.onchange = (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + try { + const workflow = JSON.parse(e.target?.result as string); + setWorkflowJsonText(JSON.stringify(workflow, null, 2)); + setTemplateData(prev => ({ ...prev, workflow })); + } catch (error) { + console.error('解析ComfyUI工作流失败:', error); + setErrors({ workflow: '文件格式错误,请选择有效的JSON文件' }); + } + }; + reader.readAsText(file); + } + }; + input.click(); + }; + + // 处理标签输入 + const handleTagsChange = (tagsString: string) => { + const tags = tagsString + .split(',') + .map(tag => tag.trim()) + .filter(tag => tag.length > 0); + updateMetadata('tags', tags); + }; + + // 生成模板ID + const generateTemplateId = (name: string) => { + return name + .toLowerCase() + .replace(/[^a-z0-9\s]/g, '') + .replace(/\s+/g, '-') + .trim(); + }; + + // 处理名称变化,自动生成ID + const handleNameChange = (name: string) => { + updateMetadata('name', name); + if (!editingTemplate) { + updateMetadata('id', generateTemplateId(name)); + } + }; + + if (!isOpen) return null; + + return ( +
+
+ {/* 头部 */} +
+
+ +

+ {editingTemplate ? '编辑工作流模板' : '创建工作流模板'} +

+
+ + +
+ + {/* 错误提示 */} + {errors.general && ( +
+ + {errors.general} +
+ )} + + {/* 标签页导航 */} +
+ {[ + { id: 'basic', label: '基本信息', icon: FileText }, + { id: 'workflow', label: '工作流配置', icon: Settings }, + { id: 'parameters', label: '参数配置', icon: Info }, + ].map(tab => { + const Icon = tab.icon; + return ( + + ); + })} +
+ + {/* 内容区域 */} +
+ {/* 基本信息标签页 */} + {activeTab === 'basic' && ( +
+
+
+ + handleNameChange(e.target.value)} + className={`w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${ + errors.name ? 'border-red-300' : 'border-gray-300' + }`} + placeholder="例如:AI模型面部和头发细节修复" + /> + {errors.name && ( +

{errors.name}

+ )} +
+ +
+ + updateMetadata('id', e.target.value)} + className={`w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${ + errors.id ? 'border-red-300' : 'border-gray-300' + }`} + placeholder="例如:ai-model-face-hair-fix" + /> + {errors.id && ( +

{errors.id}

+ )} +
+
+ +
+ +