diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 81a5fad..5453b09 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -35,6 +35,7 @@ import { EnrichedAnalysisDemo } from './pages/tools/EnrichedAnalysisDemo'; import MaterialCenter from './pages/MaterialCenter'; import VideoGeneration from './pages/VideoGeneration'; import { OutfitPhotoGenerationPage } from './pages/OutfitPhotoGeneration'; +import ComfyUIManagement from './pages/ComfyUIManagement'; import Navigation from './components/Navigation'; import { NotificationSystem, useNotifications } from './components/NotificationSystem'; @@ -130,6 +131,7 @@ function App() { } /> } /> + } /> } /> } /> } /> diff --git a/apps/desktop/src/components/ComfyUIExecuteModal.tsx b/apps/desktop/src/components/ComfyUIExecuteModal.tsx new file mode 100644 index 0000000..09a18f5 --- /dev/null +++ b/apps/desktop/src/components/ComfyUIExecuteModal.tsx @@ -0,0 +1,358 @@ +import React, { useState, useEffect } from 'react'; +import { X, Play, FileText, AlertCircle, CheckCircle, Clock } from 'lucide-react'; +import ComfyuiService from '../services/comfyuiService'; +import type { + Workflow, + ExecuteWorkflowRequest, + ExecuteWorkflowResponse, + ExecuteWorkflowFormData +} from '../types/comfyui'; + +interface ComfyUIExecuteModalProps { + isOpen: boolean; + onClose: () => void; + workflow: Workflow | null; + onExecutionComplete: (result: ExecuteWorkflowResponse) => void; +} + +/** + * ComfyUI 执行工作流模态框 + */ +const ComfyUIExecuteModal: React.FC = ({ + isOpen, + onClose, + workflow, + onExecutionComplete, +}) => { + const [formData, setFormData] = useState({ + base_name: '', + version: '', + request_data: '{}', + }); + const [executing, setExecuting] = useState(false); + const [result, setResult] = useState(null); + const [errors, setErrors] = useState>({}); + const [showSpec, setShowSpec] = useState(false); + const [spec, setSpec] = useState(null); + const [loadingSpec, setLoadingSpec] = useState(false); + + useEffect(() => { + if (isOpen && workflow) { + const parsed = ComfyuiService.parseWorkflowName(workflow.name); + setFormData({ + base_name: parsed.baseName, + version: parsed.version || '', + request_data: JSON.stringify({}, null, 2), + }); + setResult(null); + setErrors({}); + setSpec(null); + setShowSpec(false); + } + }, [isOpen, workflow]); + + // ============================================================================ + // 表单验证 + // ============================================================================ + + const validateForm = (): Record => { + const newErrors: Record = {}; + + if (!formData.base_name.trim()) { + newErrors.base_name = '工作流名称不能为空'; + } + + if (!formData.request_data.trim()) { + newErrors.request_data = '请求数据不能为空'; + } else { + const validation = ComfyuiService.validateJson(formData.request_data); + if (!validation.valid) { + newErrors.request_data = `JSON 格式错误: ${validation.error}`; + } + } + + return newErrors; + }; + + // ============================================================================ + // 事件处理 + // ============================================================================ + + const handleInputChange = (field: keyof ExecuteWorkflowFormData, value: string) => { + setFormData(prev => ({ ...prev, [field]: value })); + + // 清除对应字段的错误 + if (errors[field]) { + setErrors(prev => { + const newErrors = { ...prev }; + delete newErrors[field]; + return newErrors; + }); + } + }; + + const handleLoadSpec = async () => { + if (!formData.base_name.trim()) { + setErrors({ base_name: '请先输入工作流名称' }); + return; + } + + setLoadingSpec(true); + try { + const specResponse = await ComfyuiService.getWorkflowSpec({ + base_name: formData.base_name, + version: formData.version || undefined, + }); + setSpec(specResponse.spec); + setShowSpec(true); + } catch (error) { + setErrors({ spec: `获取工作流规范失败: ${error}` }); + } finally { + setLoadingSpec(false); + } + }; + + const handleExecute = async () => { + const validationErrors = validateForm(); + if (Object.keys(validationErrors).length > 0) { + setErrors(validationErrors); + return; + } + + setExecuting(true); + setResult(null); + + try { + const request: ExecuteWorkflowRequest = { + base_name: formData.base_name, + version: formData.version || undefined, + request_data: JSON.parse(formData.request_data), + }; + + const response = await ComfyuiService.executeWorkflow(request); + setResult(response); + onExecutionComplete(response); + } catch (error) { + setResult({ + error: `执行失败: ${error}`, + }); + } finally { + setExecuting(false); + } + }; + + const formatJsonData = () => { + try { + const parsed = JSON.parse(formData.request_data); + const formatted = JSON.stringify(parsed, null, 2); + handleInputChange('request_data', formatted); + } catch (error) { + // JSON 格式错误,不做处理 + } + }; + + if (!isOpen || !workflow) return null; + + return ( +
+
+ {/* 标题栏 */} +
+
+

执行工作流

+

工作流: {workflow.name}

+
+ +
+ + {/* 表单内容 */} +
+
+ {/* 左侧:执行参数 */} +
+

执行参数

+ + {/* 工作流名称 */} +
+ + handleInputChange('base_name', e.target.value)} + className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ + errors.base_name ? 'border-red-300' : 'border-gray-300' + }`} + placeholder="my_workflow" + /> + {errors.base_name && ( +

{errors.base_name}

+ )} +
+ + {/* 版本 */} +
+ + handleInputChange('version', e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="1.0" + /> +
+ + {/* 获取规范按钮 */} +
+ + {errors.spec && ( +

{errors.spec}

+ )} +
+ + {/* 请求数据 */} +
+
+ + +
+