- Add workflow creation, editing, and preview components - Implement execution monitoring and history tracking - Add batch operations and retry management - Create environment configuration system - Add data export and error analysis features - Update navigation and main app structure - Enhance Tauri backend integration - Add comprehensive workflow type definitions
452 lines
17 KiB
TypeScript
452 lines
17 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import {
|
||
Play,
|
||
Plus,
|
||
Settings,
|
||
Server,
|
||
RefreshCw,
|
||
Workflow,
|
||
AlertCircle,
|
||
CheckCircle,
|
||
XCircle,
|
||
Clock,
|
||
} from 'lucide-react';
|
||
import ComfyuiService from '../services/comfyuiService';
|
||
import ComfyUIConfigModal from '../components/ComfyUIConfigModal';
|
||
import ComfyUIExecuteModal from '../components/ComfyUIExecuteModal';
|
||
import ComfyUIPublishModal from '../components/ComfyUIPublishModal';
|
||
import type {
|
||
Workflow as WorkflowType,
|
||
ComfyuiConfig,
|
||
ComfyuiUIState,
|
||
PublishWorkflowResponse,
|
||
ExecuteWorkflowResponse,
|
||
} from '../types/comfyui';
|
||
|
||
/**
|
||
* ComfyUI 集群管理页面
|
||
* 管理分布式 ComfyUI 集群和工作流调度
|
||
* 遵循 Tauri 开发规范和现有 UI 设计模式
|
||
*/
|
||
const ComfyUIManagement: React.FC = () => {
|
||
// ============================================================================
|
||
// 状态管理
|
||
// ============================================================================
|
||
|
||
const [state, setState] = useState<ComfyuiUIState>({
|
||
workflows: [],
|
||
servers: [],
|
||
config: {
|
||
base_url: '',
|
||
timeout: 30,
|
||
retry_attempts: 3,
|
||
enable_cache: true,
|
||
max_concurrency: 8,
|
||
},
|
||
connectionStatus: 'disconnected',
|
||
executionStatus: 'idle',
|
||
loading: {
|
||
workflows: false,
|
||
servers: false,
|
||
execution: false,
|
||
config: false,
|
||
},
|
||
});
|
||
|
||
const [showConfigModal, setShowConfigModal] = useState(false);
|
||
const [showExecuteModal, setShowExecuteModal] = useState(false);
|
||
const [showPublishModal, setShowPublishModal] = useState(false);
|
||
|
||
// ============================================================================
|
||
// 生命周期和数据加载
|
||
// ============================================================================
|
||
|
||
useEffect(() => {
|
||
initializeData();
|
||
}, []);
|
||
|
||
const initializeData = async () => {
|
||
await Promise.all([
|
||
loadConfig(),
|
||
loadWorkflows(),
|
||
loadServers(),
|
||
testConnection(),
|
||
]);
|
||
};
|
||
|
||
const loadConfig = async () => {
|
||
setState(prev => ({ ...prev, loading: { ...prev.loading, config: true } }));
|
||
try {
|
||
const config = await ComfyuiService.getConfig();
|
||
setState(prev => ({ ...prev, config, loading: { ...prev.loading, config: false } }));
|
||
} catch (error) {
|
||
console.error('Failed to load config:', error);
|
||
setState(prev => ({
|
||
...prev,
|
||
error: `加载配置失败: ${error}`,
|
||
loading: { ...prev.loading, config: false }
|
||
}));
|
||
}
|
||
};
|
||
|
||
const loadWorkflows = async () => {
|
||
setState(prev => ({ ...prev, loading: { ...prev.loading, workflows: true } }));
|
||
try {
|
||
const workflows = await ComfyuiService.getAllWorkflows();
|
||
setState(prev => ({
|
||
...prev,
|
||
workflows,
|
||
loading: { ...prev.loading, workflows: false },
|
||
error: undefined
|
||
}));
|
||
} catch (error) {
|
||
console.error('Failed to load workflows:', error);
|
||
setState(prev => ({
|
||
...prev,
|
||
error: `加载工作流失败: ${error}`,
|
||
loading: { ...prev.loading, workflows: false }
|
||
}));
|
||
}
|
||
};
|
||
|
||
const loadServers = async () => {
|
||
setState(prev => ({ ...prev, loading: { ...prev.loading, servers: true } }));
|
||
try {
|
||
const servers = await ComfyuiService.getServersStatus();
|
||
setState(prev => ({
|
||
...prev,
|
||
servers,
|
||
loading: { ...prev.loading, servers: false }
|
||
}));
|
||
} catch (error) {
|
||
console.error('Failed to load servers:', error);
|
||
setState(prev => ({
|
||
...prev,
|
||
error: `加载服务器状态失败: ${error}`,
|
||
loading: { ...prev.loading, servers: false }
|
||
}));
|
||
}
|
||
};
|
||
|
||
const testConnection = async () => {
|
||
setState(prev => ({ ...prev, connectionStatus: 'connecting' }));
|
||
try {
|
||
const isConnected = await ComfyuiService.testConnection();
|
||
setState(prev => ({
|
||
...prev,
|
||
connectionStatus: isConnected ? 'connected' : 'disconnected'
|
||
}));
|
||
} catch (error) {
|
||
console.error('Connection test failed:', error);
|
||
setState(prev => ({ ...prev, connectionStatus: 'error' }));
|
||
}
|
||
};
|
||
|
||
// ============================================================================
|
||
// 事件处理
|
||
// ============================================================================
|
||
|
||
const handleRefresh = () => {
|
||
initializeData();
|
||
};
|
||
|
||
const handleExecuteWorkflow = (workflow: WorkflowType) => {
|
||
setState(prev => ({ ...prev, selectedWorkflow: workflow }));
|
||
setShowExecuteModal(true);
|
||
};
|
||
|
||
const handleDeleteWorkflow = async (workflow: WorkflowType) => {
|
||
if (!confirm(`确定要删除工作流 "${workflow.name}" 吗?`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await ComfyuiService.deleteWorkflow(workflow.name);
|
||
await loadWorkflows(); // 重新加载工作流列表
|
||
} catch (error) {
|
||
setState(prev => ({ ...prev, error: `删除工作流失败: ${error}` }));
|
||
}
|
||
};
|
||
|
||
const handleConfigUpdate = (newConfig: ComfyuiConfig) => {
|
||
setState(prev => ({ ...prev, config: newConfig }));
|
||
// 配置更新后重新测试连接
|
||
testConnection();
|
||
};
|
||
|
||
const handlePublishComplete = (result: PublishWorkflowResponse) => {
|
||
if (result.success) {
|
||
loadWorkflows(); // 重新加载工作流列表
|
||
setShowPublishModal(false);
|
||
}
|
||
};
|
||
|
||
const handleExecutionComplete = (result: ExecuteWorkflowResponse) => {
|
||
// 执行完成后的处理逻辑
|
||
console.log('Workflow execution completed:', result);
|
||
};
|
||
|
||
// ============================================================================
|
||
// 渲染辅助函数
|
||
// ============================================================================
|
||
|
||
const getConnectionStatusIcon = () => {
|
||
switch (state.connectionStatus) {
|
||
case 'connected':
|
||
return <CheckCircle className="w-5 h-5 text-green-500" />;
|
||
case 'connecting':
|
||
return <Clock className="w-5 h-5 text-yellow-500 animate-spin" />;
|
||
case 'error':
|
||
return <XCircle className="w-5 h-5 text-red-500" />;
|
||
default:
|
||
return <AlertCircle className="w-5 h-5 text-gray-400" />;
|
||
}
|
||
};
|
||
|
||
const getConnectionStatusText = () => {
|
||
switch (state.connectionStatus) {
|
||
case 'connected':
|
||
return '已连接';
|
||
case 'connecting':
|
||
return '连接中...';
|
||
case 'error':
|
||
return '连接错误';
|
||
default:
|
||
return '未连接';
|
||
}
|
||
};
|
||
|
||
// ============================================================================
|
||
// 主渲染
|
||
// ============================================================================
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gray-50">
|
||
<div className="max-w-7xl mx-auto py-8 px-4">
|
||
{/* 页面标题和操作栏 */}
|
||
<div className="bg-white rounded-lg shadow-sm mb-6">
|
||
<div className="px-6 py-4 border-b border-gray-200">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-semibold text-gray-900 flex items-center gap-2">
|
||
<Workflow className="w-6 h-6" />
|
||
ComfyUI 集群管理
|
||
</h1>
|
||
<p className="text-gray-600 mt-1">管理分布式 ComfyUI 集群和工作流调度</p>
|
||
<div className="mt-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
|
||
<p className="text-sm text-amber-700">
|
||
<strong>注意:</strong> 这是集群管理系统,用于管理多个 ComfyUI 节点的负载均衡和工作流调度。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-3">
|
||
{/* 连接状态 */}
|
||
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-gray-100">
|
||
{getConnectionStatusIcon()}
|
||
<span className="text-sm font-medium">{getConnectionStatusText()}</span>
|
||
</div>
|
||
|
||
{/* 操作按钮 */}
|
||
<button
|
||
onClick={handleRefresh}
|
||
disabled={state.loading.workflows || state.loading.servers}
|
||
className="flex items-center gap-2 px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors disabled:opacity-50"
|
||
>
|
||
<RefreshCw className={`w-4 h-4 ${state.loading.workflows || state.loading.servers ? 'animate-spin' : ''}`} />
|
||
刷新
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setShowPublishModal(true)}
|
||
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
|
||
>
|
||
<Plus className="w-4 h-4" />
|
||
发布工作流
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setShowConfigModal(true)}
|
||
className="flex items-center gap-2 px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors"
|
||
>
|
||
<Settings className="w-4 h-4" />
|
||
配置
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 错误提示 */}
|
||
{state.error && (
|
||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
|
||
<div className="flex items-center gap-2">
|
||
<AlertCircle className="w-5 h-5 text-red-500" />
|
||
<span className="text-red-700">{state.error}</span>
|
||
<button
|
||
onClick={() => setState(prev => ({ ...prev, error: undefined }))}
|
||
className="ml-auto text-red-500 hover:text-red-700"
|
||
>
|
||
<XCircle className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 主要内容区域 */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
{/* 工作流列表 */}
|
||
<div className="lg:col-span-2">
|
||
<div className="bg-white rounded-lg shadow-sm">
|
||
<div className="px-6 py-4 border-b border-gray-200">
|
||
<h2 className="text-lg font-semibold text-gray-900">工作流列表</h2>
|
||
</div>
|
||
|
||
<div className="p-6">
|
||
{state.loading.workflows ? (
|
||
<div className="flex items-center justify-center py-8">
|
||
<RefreshCw className="w-6 h-6 animate-spin text-gray-400" />
|
||
<span className="ml-2 text-gray-500">加载中...</span>
|
||
</div>
|
||
) : state.workflows.length === 0 ? (
|
||
<div className="text-center py-8">
|
||
<Workflow className="w-12 h-12 text-gray-300 mx-auto mb-4" />
|
||
<p className="text-gray-500">暂无工作流</p>
|
||
<button
|
||
onClick={() => setShowPublishModal(true)}
|
||
className="mt-4 text-blue-600 hover:text-blue-700"
|
||
>
|
||
发布第一个工作流
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-4">
|
||
{state.workflows.map((workflow) => (
|
||
<div
|
||
key={workflow.name}
|
||
className="border border-gray-200 rounded-lg p-4 hover:border-gray-300 transition-colors"
|
||
>
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex-1">
|
||
<h3 className="font-medium text-gray-900">{workflow.name}</h3>
|
||
{workflow.description && (
|
||
<p className="text-sm text-gray-600 mt-1">{workflow.description}</p>
|
||
)}
|
||
<div className="flex items-center gap-4 mt-2 text-xs text-gray-500">
|
||
{workflow.version && <span>版本: {workflow.version}</span>}
|
||
{workflow.created_at && (
|
||
<span>创建: {ComfyuiService.formatDateTime(workflow.created_at)}</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
onClick={() => handleExecuteWorkflow(workflow)}
|
||
className="flex items-center gap-1 px-3 py-1 bg-green-100 text-green-700 rounded-md hover:bg-green-200 transition-colors"
|
||
>
|
||
<Play className="w-3 h-3" />
|
||
执行
|
||
</button>
|
||
<button
|
||
onClick={() => handleDeleteWorkflow(workflow)}
|
||
className="flex items-center gap-1 px-3 py-1 bg-red-100 text-red-700 rounded-md hover:bg-red-200 transition-colors"
|
||
>
|
||
删除
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 服务器状态 */}
|
||
<div>
|
||
<div className="bg-white rounded-lg shadow-sm">
|
||
<div className="px-6 py-4 border-b border-gray-200">
|
||
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
||
<Server className="w-5 h-5" />
|
||
服务器状态
|
||
</h2>
|
||
</div>
|
||
|
||
<div className="p-6">
|
||
{state.loading.servers ? (
|
||
<div className="flex items-center justify-center py-4">
|
||
<RefreshCw className="w-5 h-5 animate-spin text-gray-400" />
|
||
<span className="ml-2 text-gray-500">加载中...</span>
|
||
</div>
|
||
) : state.servers.length === 0 ? (
|
||
<div className="text-center py-4">
|
||
<Server className="w-8 h-8 text-gray-300 mx-auto mb-2" />
|
||
<p className="text-gray-500 text-sm">无服务器信息</p>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-3">
|
||
{state.servers.map((server) => (
|
||
<div
|
||
key={server.server_index}
|
||
className="border border-gray-200 rounded-lg p-3"
|
||
>
|
||
<div className="flex items-center justify-between mb-2">
|
||
<span className="font-medium text-sm">服务器 {server.server_index}</span>
|
||
<span
|
||
className={`px-2 py-1 rounded-full text-xs font-medium ${
|
||
ComfyuiService.getServerStatusColor(server) === 'success'
|
||
? 'bg-green-100 text-green-700'
|
||
: ComfyuiService.getServerStatusColor(server) === 'warning'
|
||
? 'bg-yellow-100 text-yellow-700'
|
||
: 'bg-red-100 text-red-700'
|
||
}`}
|
||
>
|
||
{ComfyuiService.getServerStatusText(server)}
|
||
</span>
|
||
</div>
|
||
<div className="text-xs text-gray-600 space-y-1">
|
||
<div>URL: {server.http_url}</div>
|
||
{server.is_reachable && (
|
||
<div>
|
||
队列: 运行 {server.queue_details.running_count} / 等待 {server.queue_details.pending_count}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 模态框组件 */}
|
||
<ComfyUIConfigModal
|
||
isOpen={showConfigModal}
|
||
onClose={() => setShowConfigModal(false)}
|
||
currentConfig={state.config}
|
||
onConfigUpdate={handleConfigUpdate}
|
||
/>
|
||
|
||
<ComfyUIExecuteModal
|
||
isOpen={showExecuteModal}
|
||
onClose={() => setShowExecuteModal(false)}
|
||
workflow={state.selectedWorkflow || null}
|
||
onExecutionComplete={handleExecutionComplete}
|
||
/>
|
||
|
||
<ComfyUIPublishModal
|
||
isOpen={showPublishModal}
|
||
onClose={() => setShowPublishModal(false)}
|
||
onPublishComplete={handlePublishComplete}
|
||
/>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ComfyUIManagement;
|