feat: 新增功能清单文档和ComfyUI工作流测试页面

- 新增功能清单.md:基于代码库实际实现情况的完整功能统计
  - 43个功能模块详细分类(完成/开发中/待开发/实验性)
  - 后端API和前端UI实现状态分析
  - 开发优先级建议和项目成熟度评估
- 新增ComfyUI工作流测试页面:支持工作流执行和调试
- 优化ComfyUI服务集成和错误处理
- 更新导航菜单,添加ComfyUI相关页面入口
This commit is contained in:
imeepos
2025-08-07 11:14:21 +08:00
parent 7d8b8a3de1
commit 4da8a9a33e
8 changed files with 839 additions and 17 deletions

View File

@@ -33,6 +33,11 @@ impl ComfyuiInfrastructureService {
Ok(Self { client, config })
}
/// 获取配置信息
pub fn get_config(&self) -> &ComfyuiConfig {
&self.config
}
/// 获取所有工作流
///
/// GET /api/workflow
@@ -340,11 +345,6 @@ impl ComfyuiInfrastructureService {
Ok(())
}
/// 获取服务配置
pub fn get_config(&self) -> &ComfyuiConfig {
&self.config
}
/// 更新服务配置
pub fn update_config(&mut self, config: ComfyuiConfig) -> Result<()> {
// 验证新配置

View File

@@ -574,6 +574,8 @@ pub fn run() {
commands::comfyui_commands::comfyui_test_connection,
commands::comfyui_commands::comfyui_get_config,
commands::comfyui_commands::comfyui_update_config,
commands::comfyui_commands::comfyui_get_native_data,
commands::comfyui_commands::comfyui_node_get_data,
// Hedra 口型合成命令
commands::bowong_text_video_agent_commands::hedra_upload_file,
commands::bowong_text_video_agent_commands::hedra_submit_task,
@@ -641,9 +643,9 @@ pub fn run() {
// 不返回错误,让应用继续启动,只是记录错误
}
// 初始化 ComfyUI 服务
// 初始化 ComfyUI 服务 - 使用本地 ComfyUI 服务器
let comfyui_config = data::models::comfyui::ComfyuiConfig {
base_url: "https://bowongai-dev--waas-demo-fastapi-webapp.modal.run".to_string(),
base_url: "http://192.168.0.193:8188".to_string(),
timeout: Some(600), // 10分钟超时适应 ComfyUI 工作流的长时间处理
retry_attempts: Some(3),
enable_cache: Some(true),

View File

@@ -253,7 +253,7 @@ pub async fn comfyui_get_api_root(
}
/// 测试 ComfyUI 服务连接
///
///
/// 前端调用示例:
/// ```typescript
/// const isConnected = await invoke('comfyui_test_connection');
@@ -263,7 +263,7 @@ pub async fn comfyui_test_connection(
state: State<'_, AppState>,
) -> Result<bool, String> {
info!("Command: comfyui_test_connection");
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
@@ -280,6 +280,114 @@ pub async fn comfyui_test_connection(
}
}
/// 直接获取 ComfyUI 原生 API 数据
///
/// 前端调用示例:
/// ```typescript
/// const data = await invoke('comfyui_get_native_data', { endpoint: 'history' });
/// ```
#[tauri::command]
pub async fn comfyui_get_native_data(
endpoint: String,
state: State<'_, AppState>,
) -> Result<serde_json::Value, String> {
info!("Command: comfyui_get_native_data, endpoint: {}", endpoint);
let app_state = state.inner();
let comfyui_service = app_state.get_comfyui_service()
.ok_or_else(|| "ComfyUI service not initialized".to_string())?;
// 构建 URL
let base_url = &comfyui_service.get_config().base_url;
let url = if endpoint.starts_with('/') {
format!("{}{}", base_url.trim_end_matches('/'), endpoint)
} else {
format!("{}/{}", base_url.trim_end_matches('/'), endpoint)
};
info!("Fetching data from: {}", url);
// 创建 HTTP 客户端
let client = reqwest::Client::new();
match client.get(&url).send().await {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>().await {
Ok(data) => {
info!("Successfully fetched data from {}", endpoint);
Ok(data)
}
Err(e) => {
error!("Failed to parse JSON response: {}", e);
Err(format!("Failed to parse JSON response: {}", e))
}
}
} else {
error!("HTTP error {}: {}", response.status(), url);
Err(format!("HTTP error {}", response.status()))
}
}
Err(e) => {
error!("Failed to fetch data from {}: {}", url, e);
Err(format!("Failed to fetch data: {}", e))
}
}
}
/// 直接从指定的 ComfyUI 节点获取数据(用于本地节点管理)
///
/// 前端调用示例:
/// ```typescript
/// const data = await invoke('comfyui_node_get_data', {
/// nodeUrl: 'http://192.168.0.193:8188',
/// endpoint: 'history'
/// });
/// ```
#[tauri::command]
pub async fn comfyui_node_get_data(
nodeUrl: String,
endpoint: String,
) -> Result<serde_json::Value, String> {
info!("Command: comfyui_node_get_data, node: {}, endpoint: {}", nodeUrl, endpoint);
// 构建 URL
let url = if endpoint.starts_with('/') {
format!("{}{}", nodeUrl.trim_end_matches('/'), endpoint)
} else {
format!("{}/{}", nodeUrl.trim_end_matches('/'), endpoint)
};
info!("Fetching data from node: {}", url);
// 创建 HTTP 客户端
let client = reqwest::Client::new();
match client.get(&url).send().await {
Ok(response) => {
if response.status().is_success() {
match response.json::<serde_json::Value>().await {
Ok(data) => {
info!("Successfully fetched data from node {} endpoint {}", nodeUrl, endpoint);
Ok(data)
}
Err(e) => {
error!("Failed to parse JSON response from node: {}", e);
Err(format!("Failed to parse JSON response: {}", e))
}
}
} else {
error!("HTTP error {} from node: {}", response.status(), url);
Err(format!("HTTP error {}", response.status()))
}
}
Err(e) => {
error!("Failed to fetch data from node {}: {}", url, e);
Err(format!("Failed to fetch data: {}", e))
}
}
}
/// 获取 ComfyUI 服务配置
///
/// 前端调用示例:

View File

@@ -37,7 +37,8 @@ import MaterialCenter from './pages/MaterialCenter';
import VideoGeneration from './pages/VideoGeneration';
import { OutfitPhotoGenerationPage } from './pages/OutfitPhotoGeneration';
import ComfyUIManagement from './pages/ComfyUIManagement';
import CanvasTool from './pages/CanvasTool';
import ComfyUIWorkflowTest from './pages/ComfyUIWorkflowTest';
// import CanvasTool from './pages/CanvasTool';
import Navigation from './components/Navigation';
import { NotificationSystem, useNotifications } from './components/NotificationSystem';
@@ -136,7 +137,8 @@ function App() {
<Route path="/outfit-photo-generation/:projectId" element={<OutfitPhotoGenerationPage />} />
<Route path="/outfit-photo-generation/:projectId/:modelId" element={<OutfitPhotoGenerationPage />} />
<Route path="/comfyui" element={<ComfyUIManagement />} />
<Route path="/comfyui-cluster" element={<ComfyUIManagement />} />
<Route path="/comfyui-node" element={<ComfyUIWorkflowTest />} />
<Route path="/tools" element={<Tools />} />
<Route path="/tools/data-cleaning" element={<DataCleaningTool />} />
<Route path="/tools/json-parser" element={<JsonParserTool />} />

View File

@@ -53,10 +53,16 @@ const Navigation: React.FC = () => {
description: 'AI穿搭方案推荐与素材检索'
},
{
name: 'ComfyUI',
href: '/comfyui',
name: 'ComfyUI 集群管理',
href: '/comfyui-cluster',
icon: CommandLineIcon,
description: 'ComfyUI工作流管理与执行'
description: '管理分布式ComfyUI集群和工作流调度'
},
{
name: 'ComfyUI 节点管理',
href: '/comfyui-node',
icon: CommandLineIcon,
description: '直接管理本地ComfyUI节点 (192.168.0.193:8188)'
},
{
name: '工具',

View File

@@ -24,7 +24,8 @@ import type {
} from '../types/comfyui';
/**
* ComfyUI 工作流管理页面
* ComfyUI 集群管理页面
* 管理分布式 ComfyUI 集群和工作流调度
* 遵循 Tauri 开发规范和现有 UI 设计模式
*/
const ComfyUIManagement: React.FC = () => {
@@ -229,9 +230,15 @@ const ComfyUIManagement: React.FC = () => {
<div>
<h1 className="text-2xl font-semibold text-gray-900 flex items-center gap-2">
<Workflow className="w-6 h-6" />
ComfyUI
ComfyUI
</h1>
<p className="text-gray-600 mt-1"> ComfyUI </p>
<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
使 "ComfyUI 节点管理"
</p>
</div>
</div>
<div className="flex items-center gap-3">

View File

@@ -0,0 +1,519 @@
import React, { useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/core';
import {
RefreshCw,
Server,
FileText,
CheckCircle,
XCircle,
AlertCircle,
Clock,
Database,
Download,
Play,
Eye,
Copy
} from 'lucide-react';
interface ComfyUISystemStats {
system: {
os: string;
ram_total: number;
ram_free: number;
comfyui_version: string;
python_version: string;
};
}
interface ComfyUIQueue {
queue_running: any[];
queue_pending: any[];
}
interface ComfyUIHistory {
[key: string]: {
prompt: any;
outputs: any;
status: {
status_str: string;
completed: boolean;
messages: any[];
};
};
}
interface WorkflowInfo {
id: string;
name: string;
description: string;
status: string;
completed: boolean;
node_count: number;
created_at: string;
prompt_data: any;
outputs?: any;
}
export const ComfyUIWorkflowTest: React.FC = () => {
const [connectionStatus, setConnectionStatus] = useState<'testing' | 'connected' | 'failed'>('testing');
const [systemStats, setSystemStats] = useState<ComfyUISystemStats | null>(null);
const [queue, setQueue] = useState<ComfyUIQueue | null>(null);
const [history, setHistory] = useState<ComfyUIHistory | null>(null);
const [workflows, setWorkflows] = useState<WorkflowInfo[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [selectedWorkflow, setSelectedWorkflow] = useState<WorkflowInfo | null>(null);
// 测试连接
const testConnection = async () => {
setLoading(true);
setError(null);
setConnectionStatus('testing');
try {
// 注意:这里不更新全局 ComfyUI 配置,避免影响集群管理系统
// 直接使用本地节点地址进行连接测试
const nodeUrl = 'http://192.168.0.193:8188';
console.log('🔗 直接测试本地节点连接...', nodeUrl);
// 2. 直接测试节点连接
try {
const stats = await invoke<ComfyUISystemStats>('comfyui_node_get_data', {
nodeUrl: nodeUrl,
endpoint: 'system_stats'
});
console.log('✅ 节点连接成功:', stats);
setConnectionStatus('connected');
setSystemStats(stats);
// 3. 获取其他系统信息
await fetchSystemInfo();
} catch (err) {
console.error('❌ 节点连接失败:', err);
setConnectionStatus('failed');
setError('无法连接到 ComfyUI 节点: ' + (err instanceof Error ? err.message : '未知错误'));
}
} catch (err) {
console.error('连接测试失败:', err);
setConnectionStatus('failed');
setError(err instanceof Error ? err.message : '连接测试失败');
} finally {
setLoading(false);
}
};
// 获取系统信息
const fetchSystemInfo = async () => {
const nodeUrl = 'http://192.168.0.193:8188';
try {
// 获取队列状态
try {
const queueData = await invoke<ComfyUIQueue>('comfyui_node_get_data', {
nodeUrl: nodeUrl,
endpoint: 'queue'
});
setQueue(queueData);
console.log('📋 队列状态:', queueData);
} catch (err) {
console.error('获取队列状态失败:', err);
}
} catch (err) {
console.error('获取系统信息失败:', err);
setError('获取系统信息失败');
}
};
// 尝试获取工作流列表
const fetchWorkflows = async () => {
setLoading(true);
setError(null);
const nodeUrl = 'http://192.168.0.193:8188';
try {
// 直接从 ComfyUI 节点历史记录中获取工作流信息
const historyData = await invoke<ComfyUIHistory>('comfyui_node_get_data', {
nodeUrl: nodeUrl,
endpoint: 'history'
});
console.log('📂 获取到历史记录:', historyData);
const workflowsFromHistory = extractWorkflowsFromHistory(historyData);
setWorkflows(workflowsFromHistory);
setHistory(historyData);
console.log('📂 提取到工作流:', workflowsFromHistory);
} catch (err) {
console.error('获取工作流失败:', err);
setError('获取工作流失败: ' + (err instanceof Error ? err.message : '未知错误'));
} finally {
setLoading(false);
}
};
// 从历史记录中提取工作流信息
const extractWorkflowsFromHistory = (historyData: ComfyUIHistory): WorkflowInfo[] => {
const workflows: WorkflowInfo[] = [];
console.log('🔍 开始提取工作流,历史记录条目数:', Object.keys(historyData).length);
Object.entries(historyData).forEach(([id, record]) => {
console.log(`🔍 处理记录 ${id}:`, {
hasPrompt: !!record.prompt,
promptLength: record.prompt?.length,
status: record.status?.status_str,
completed: record.status?.completed
});
if (record.prompt && Array.isArray(record.prompt) && record.prompt.length > 2 && record.prompt[2]) {
const promptData = record.prompt[2];
const nodeCount = Object.keys(promptData).length;
workflows.push({
id,
name: `工作流 ${id.substring(0, 8)}`,
description: `包含 ${nodeCount} 个节点`,
status: record.status?.status_str || 'unknown',
completed: record.status?.completed || false,
node_count: nodeCount,
created_at: new Date().toISOString(), // ComfyUI 不提供创建时间
prompt_data: promptData,
outputs: record.outputs
});
console.log(`✅ 成功提取工作流 ${id}, 节点数: ${nodeCount}`);
} else {
console.log(`⚠️ 跳过记录 ${id}: 无效的 prompt 数据`);
}
});
console.log(`🎯 总共提取到 ${workflows.length} 个工作流`);
return workflows.sort((a, b) => b.id.localeCompare(a.id)); // 按 ID 倒序排列
};
// 下载工作流为 JSON 文件
const downloadWorkflow = (workflow: WorkflowInfo) => {
const dataStr = JSON.stringify(workflow.prompt_data, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `${workflow.name.replace(/\s+/g, '_')}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
// 复制工作流到剪贴板
const copyWorkflowToClipboard = async (workflow: WorkflowInfo) => {
try {
await navigator.clipboard.writeText(JSON.stringify(workflow.prompt_data, null, 2));
alert('工作流已复制到剪贴板!');
} catch (err) {
console.error('复制失败:', err);
alert('复制失败,请手动复制');
}
};
// 查看工作流详情
const viewWorkflowDetails = (workflow: WorkflowInfo) => {
setSelectedWorkflow(workflow);
};
// 初始化
useEffect(() => {
testConnection();
}, []);
const formatBytes = (bytes: number) => {
const gb = bytes / (1024 * 1024 * 1024);
return `${gb.toFixed(2)} GB`;
};
return (
<div className="p-6 max-w-6xl mx-auto">
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 mb-2">ComfyUI </h1>
<p className="text-gray-600">
ComfyUI (192.168.0.193:8188)
</p>
<div className="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-lg">
<p className="text-sm text-blue-700">
<strong></strong> ComfyUI
ComfyUI
</p>
</div>
</div>
{/* 连接状态 */}
<div className="bg-white rounded-lg shadow-sm border p-6 mb-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-gray-900"></h2>
<button
onClick={testConnection}
disabled={loading}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
</button>
</div>
<div className="flex items-center gap-3">
{connectionStatus === 'testing' && (
<>
<Clock className="w-5 h-5 text-yellow-500" />
<span className="text-yellow-700">...</span>
</>
)}
{connectionStatus === 'connected' && (
<>
<CheckCircle className="w-5 h-5 text-green-500" />
<span className="text-green-700"></span>
</>
)}
{connectionStatus === 'failed' && (
<>
<XCircle className="w-5 h-5 text-red-500" />
<span className="text-red-700"></span>
</>
)}
</div>
{error && (
<div className="mt-3 p-3 bg-red-50 border border-red-200 rounded-lg">
<div className="flex items-center gap-2">
<AlertCircle className="w-4 h-4 text-red-500" />
<span className="text-red-700">{error}</span>
</div>
</div>
)}
</div>
{/* 系统信息 */}
{systemStats && (
<div className="bg-white rounded-lg shadow-sm border p-6 mb-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<Server className="w-5 h-5" />
</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div>
<div className="text-sm text-gray-500"></div>
<div className="font-medium">{systemStats.system.os}</div>
</div>
<div>
<div className="text-sm text-gray-500">ComfyUI </div>
<div className="font-medium">{systemStats.system.comfyui_version}</div>
</div>
<div>
<div className="text-sm text-gray-500"></div>
<div className="font-medium">{formatBytes(systemStats.system.ram_total)}</div>
</div>
<div>
<div className="text-sm text-gray-500"></div>
<div className="font-medium">{formatBytes(systemStats.system.ram_free)}</div>
</div>
</div>
</div>
)}
{/* 队列状态 */}
{queue && (
<div className="bg-white rounded-lg shadow-sm border p-6 mb-6">
<h2 className="text-lg font-semibold text-gray-900 mb-4"></h2>
<div className="grid grid-cols-2 gap-4">
<div>
<div className="text-sm text-gray-500"></div>
<div className="text-2xl font-bold text-blue-600">{queue.queue_running.length}</div>
</div>
<div>
<div className="text-sm text-gray-500"></div>
<div className="text-2xl font-bold text-yellow-600">{queue.queue_pending.length}</div>
</div>
</div>
</div>
)}
{/* 工作流列表 */}
<div className="bg-white rounded-lg shadow-sm border p-6">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
<FileText className="w-5 h-5" />
</h2>
<button
onClick={fetchWorkflows}
disabled={loading || connectionStatus !== 'connected'}
className="flex items-center gap-2 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50"
>
<Database className="w-4 h-4" />
</button>
</div>
{workflows.length > 0 ? (
<div className="space-y-3">
{workflows.map((workflow, index) => (
<div key={workflow.id || index} className="border rounded-lg p-4 hover:shadow-md transition-shadow">
<div className="flex items-center justify-between mb-2">
<h3 className="font-medium text-gray-900">{workflow.name}</h3>
<div className="flex items-center gap-2">
<span className={`px-2 py-1 rounded-full text-xs ${
workflow.completed
? 'bg-green-100 text-green-700'
: workflow.status === 'error'
? 'bg-red-100 text-red-700'
: 'bg-yellow-100 text-yellow-700'
}`}>
{workflow.status}
</span>
</div>
</div>
<p className="text-sm text-gray-600 mb-3">{workflow.description}</p>
<div className="text-xs text-gray-500 mb-3">
ID: {workflow.id.substring(0, 16)}...
{workflow.node_count && ` • 节点数: ${workflow.node_count}`}
</div>
{/* 操作按钮 */}
<div className="flex items-center gap-2 pt-2 border-t">
<button
onClick={() => viewWorkflowDetails(workflow)}
className="flex items-center gap-1 px-3 py-1 text-xs bg-blue-100 text-blue-700 rounded hover:bg-blue-200 transition-colors"
>
<Eye className="w-3 h-3" />
</button>
<button
onClick={() => downloadWorkflow(workflow)}
className="flex items-center gap-1 px-3 py-1 text-xs bg-green-100 text-green-700 rounded hover:bg-green-200 transition-colors"
>
<Download className="w-3 h-3" />
</button>
<button
onClick={() => copyWorkflowToClipboard(workflow)}
className="flex items-center gap-1 px-3 py-1 text-xs bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors"
>
<Copy className="w-3 h-3" />
</button>
{workflow.completed && (
<button
onClick={() => {/* TODO: 实现重新执行功能 */}}
className="flex items-center gap-1 px-3 py-1 text-xs bg-purple-100 text-purple-700 rounded hover:bg-purple-200 transition-colors"
>
<Play className="w-3 h-3" />
</button>
)}
</div>
</div>
))}
</div>
) : (
<div className="text-center py-8 text-gray-500">
{connectionStatus === 'connected'
? '暂无工作流数据,请点击"获取工作流"按钮'
: '请先建立连接'}
</div>
)}
</div>
{/* 工作流详情模态框 */}
{selectedWorkflow && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg shadow-xl max-w-4xl max-h-[80vh] w-full mx-4 overflow-hidden">
<div className="flex items-center justify-between p-6 border-b">
<h2 className="text-xl font-semibold text-gray-900"></h2>
<button
onClick={() => setSelectedWorkflow(null)}
className="text-gray-400 hover:text-gray-600"
>
<XCircle className="w-6 h-6" />
</button>
</div>
<div className="p-6 overflow-y-auto max-h-[60vh]">
<div className="grid grid-cols-2 gap-4 mb-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<div className="text-gray-900">{selectedWorkflow.name}</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<span className={`px-2 py-1 rounded-full text-xs ${
selectedWorkflow.completed
? 'bg-green-100 text-green-700'
: selectedWorkflow.status === 'error'
? 'bg-red-100 text-red-700'
: 'bg-yellow-100 text-yellow-700'
}`}>
{selectedWorkflow.status}
</span>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1"> ID</label>
<div className="text-gray-900 font-mono text-sm">{selectedWorkflow.id}</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1"></label>
<div className="text-gray-900">{selectedWorkflow.node_count}</div>
</div>
</div>
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2"> JSON</label>
<div className="bg-gray-50 rounded-lg p-4 max-h-96 overflow-y-auto">
<pre className="text-xs text-gray-800 whitespace-pre-wrap">
{JSON.stringify(selectedWorkflow.prompt_data, null, 2)}
</pre>
</div>
</div>
{selectedWorkflow.outputs && (
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2"></label>
<div className="bg-gray-50 rounded-lg p-4 max-h-48 overflow-y-auto">
<pre className="text-xs text-gray-800 whitespace-pre-wrap">
{JSON.stringify(selectedWorkflow.outputs, null, 2)}
</pre>
</div>
</div>
)}
</div>
<div className="flex items-center justify-end gap-3 p-6 border-t bg-gray-50">
<button
onClick={() => copyWorkflowToClipboard(selectedWorkflow)}
className="flex items-center gap-2 px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700"
>
<Copy className="w-4 h-4" />
JSON
</button>
<button
onClick={() => downloadWorkflow(selectedWorkflow)}
className="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
<Download className="w-4 h-4" />
</button>
<button
onClick={() => setSelectedWorkflow(null)}
className="px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300"
>
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default ComfyUIWorkflowTest;

178
功能清单.md Normal file
View File

@@ -0,0 +1,178 @@
# MixVideo 功能清单
## 📋 功能概览
本文档记录了 MixVideo 项目的所有功能模块,基于代码库实际实现情况进行分类管理。
---
## 🟢 已完成功能 (后端API + 前端UI完整实现)
| 功能模块 | 功能名称 | 描述 | 后端API | 前端UI | 状态 |
| ------------ | ------------ | ---------------------------- | ------- | ------ | ------ |
| **项目管理** | 项目CRUD | 创建、查看、编辑、删除项目 | ✅ | ✅ | ✅ 完成 |
| **素材管理** | 素材导入管理 | 视频、音频、图片素材统一管理 | ✅ | ✅ | ✅ 完成 |
| **素材管理** | 素材分段视图 | 素材片段查看和管理 | ✅ | ✅ | ✅ 完成 |
| **素材管理** | 缩略图生成 | 自动生成视频缩略图 | ✅ | ✅ | ✅ 完成 |
| **模特管理** | 模特CRUD | 模特信息的完整管理 | ✅ | ✅ | ✅ 完成 |
| **模特管理** | 模特照片管理 | 模特照片上传和管理 | ✅ | ✅ | ✅ 完成 |
| **模特管理** | 模特动态管理 | 模特动态内容管理 | ✅ | ✅ | ✅ 完成 |
| **AI分析** | 视频内容分类 | 基于AI的视频内容自动分类 | ✅ | ✅ | ✅ 完成 |
| **AI分析** | AI分类设置 | AI分类规则配置管理 | ✅ | ✅ | ✅ 完成 |
| **模板系统** | 模板管理 | 视频模板的创建、导入、管理 | ✅ | ✅ | ✅ 完成 |
| **模板系统** | 模板匹配 | 智能模板匹配和推荐 | ✅ | ✅ | ✅ 完成 |
| **模板系统** | 项目模板绑定 | 项目与模板的绑定管理 | ✅ | ✅ | ✅ 完成 |
| **导出功能** | 剪映导出 | 导出到剪映格式 | ✅ | ✅ | ✅ 完成 |
| **导出功能** | 导出记录管理 | 导出历史记录管理 | ✅ | ✅ | ✅ 完成 |
| **素材绑定** | 素材模特绑定 | 素材与模特的关联管理 | ✅ | ✅ | ✅ 完成 |
| **工具集** | 数据清洗工具 | JSONL格式数据去重处理 | ✅ | ✅ | ✅ 完成 |
---
## 🟡 后端完成,前端开发中
| 功能模块 | 功能名称 | 描述 | 后端API | 前端UI | 状态 |
| ------------ | ------------------ | ---------------------- | ------- | ------ | -------- |
| **水印处理** | 水印检测 | 自动检测视频中的水印 | ✅ | 🔄 | 🔄 开发中 |
| **水印处理** | 水印去除 | AI水印去除功能 | ✅ | 🔄 | 🔄 开发中 |
| **水印处理** | 水印添加 | 批量添加自定义水印 | ✅ | 🔄 | 🔄 开发中 |
| **搜索功能** | 相似度搜索 | 基于内容的相似素材搜索 | ✅ | 🔄 | 🔄 开发中 |
| **搜索功能** | 素材搜索 | 全文搜索素材内容 | ✅ | 🔄 | 🔄 开发中 |
| **服装搭配** | 服装搭配搜索 | AI服装搭配分析和搜索 | ✅ | 🔄 | 🔄 开发中 |
| **服装搭配** | 搭配推荐 | 智能服装搭配推荐 | ✅ | ✅ | 🔄 开发中 |
| **服装搭配** | 搭配收藏 | 用户搭配收藏管理 | ✅ | 🔄 | <20> 开发中 |
| **服装搭配** | 服装图片生成 | AI服装图片生成 | ✅ | 🔄 | 🔄 开发中 |
| **服装搭配** | 服装照片生成 | 个性化服装照片生成 | ✅ | ✅ | 🔄 开发中 |
| **AI生成** | 图片生成 | 基于AI的图片生成工具 | ✅ | 🔄 | 🔄 开发中 |
| **AI生成** | 视频生成 | AI视频生成功能 | ✅ | ✅ | 🔄 开发中 |
| **语音处理** | 语音克隆 | 个性化语音克隆技术 | ✅ | 🔄 | 🔄 开发中 |
| **语音处理** | 语音合成 | 文本转语音功能 | ✅ | 🔄 | 🔄 开发中 |
| **语音处理** | 系统音色管理 | 系统内置音色管理 | ✅ | 🔄 | 🔄 开发中 |
| **视频处理** | 火山引擎视频 | 火山引擎视频生成集成 | ✅ | 🔄 | 🔄 开发中 |
| **视频处理** | Hedra口型合成 | Hedra口型同步技术 | ✅ | ✅ | 🔄 开发中 |
| **图片处理** | 图片编辑 | 高级图片编辑功能 | ✅ | 🔄 | 🔄 开发中 |
| **对话系统** | 智能对话 | AI对话交互系统 | ✅ | 🔄 | 🔄 开发中 |
| **AI集成** | ComfyUI集成 | ComfyUI工作流集成 | ✅ | ✅ | 🔄 开发中 |
| **AI集成** | Bowong文本视频代理 | 第三方AI视频生成服务 | ✅ | 🔄 | 🔄 开发中 |
---
## 🔴 后端完成,前端未开始
| 功能模块 | 功能名称 | 描述 | 后端API | 前端UI | 状态 |
| ------------ | ------------ | ---------------------- | ------- | ------ | -------- |
| **数据处理** | JSON容错解析 | 容错性JSON数据解析 | ✅ | ❌ | ❌ 待开发 |
| **数据处理** | Markdown解析 | Markdown文档解析和处理 | ✅ | ❌ | ❌ 待开发 |
| **标签系统** | 自定义标签 | 用户自定义标签系统 | ✅ | ❌ | ❌ 待开发 |
| **图片下载** | 批量图片下载 | 批量下载和管理图片 | ✅ | ❌ | ❌ 待开发 |
| **RAG系统** | RAG接地 | 检索增强生成系统 | ✅ | ❌ | ❌ 待开发 |
| **模板权重** | 模板片段权重 | 智能模板片段权重分析 | ✅ | ❌ | ❌ 待开发 |
| **目录设置** | 目录配置 | 高级目录结构配置 | ✅ | ❌ | ❌ 待开发 |
| **工作流** | 工作流管理 | 自动化工作流程管理 | ✅ | ❌ | ❌ 待开发 |
| **系统功能** | 错误处理 | 统一错误处理机制 | ✅ | ❌ | ❌ 待开发 |
---
## 🧪 实验性功能 (部分实现)
| 功能模块 | 功能名称 | 描述 | 后端API | 前端UI | 状态 |
| ------------ | -------------- | ------------------ | ------- | ------ | -------- |
| **缩略图** | 批量缩略图生成 | 批量生成视频缩略图 | ✅ | 🧪 | 🧪 实验中 |
| **系统功能** | 性能监控 | 系统性能监控和优化 | 🧪 | ❌ | 🧪 实验中 |
| **AI画板** | AI画板 | AI画板集成工作流 | 🧪 | ❌ | 🧪 实验中 |
---
## 📊 功能统计
### 总体进度
- **✅ 完全完成**: 16个功能模块 (37%)
- **🔄 后端完成,前端开发中**: 16个功能模块 (37%)
- **❌ 后端完成,前端未开始**: 9个功能模块 (21%)
- **🧪 实验性功能**: 2个功能模块 (5%)
- **📊 总计**: 43个功能模块
### 实现状态分析
| 实现状态 | 数量 | 占比 | 说明 |
| -------- | ---- | ---- | ------------------------- |
| 完整实现 | 16 | 37% | 后端API + 前端UI都已完成 |
| 后端完成 | 25 | 58% | 后端API已实现前端待开发 |
| 实验阶段 | 2 | 5% | 部分功能在测试验证中 |
### 按功能模块分类
| 模块类别 | 功能数量 | 完整实现 | 后端完成 | 完成度 |
| -------- | -------- | -------- | -------- | ------ |
| 项目管理 | 1 | 1 | 1 | 100% |
| 素材管理 | 3 | 3 | 3 | 100% |
| 模特管理 | 3 | 3 | 3 | 100% |
| AI分析 | 2 | 2 | 2 | 100% |
| 模板系统 | 3 | 3 | 3 | 100% |
| 导出功能 | 2 | 2 | 2 | 100% |
| 素材绑定 | 1 | 1 | 1 | 100% |
| 工具集 | 1 | 1 | 1 | 100% |
| 水印处理 | 3 | 0 | 3 | 33% |
| 搜索功能 | 2 | 0 | 2 | 50% |
| 服装搭配 | 4 | 1 | 4 | 75% |
| AI生成 | 2 | 0 | 2 | 50% |
| 语音处理 | 3 | 0 | 3 | 33% |
| 视频处理 | 2 | 0 | 2 | 50% |
| 图片处理 | 1 | 0 | 1 | 0% |
| 对话系统 | 1 | 0 | 1 | 0% |
| AI集成 | 2 | 1 | 2 | 75% |
| 数据处理 | 2 | 0 | 2 | 0% |
| 标签系统 | 1 | 0 | 1 | 0% |
| 图片下载 | 1 | 0 | 1 | 0% |
| RAG系统 | 1 | 0 | 1 | 0% |
| 模板权重 | 1 | 0 | 1 | 0% |
| 目录设置 | 1 | 0 | 1 | 0% |
| 工作流 | 1 | 0 | 1 | 0% |
| 系统功能 | 2 | 0 | 1 | 25% |
### 开发优先级建议
#### 🔥 高优先级 (用户核心功能)
1. **水印处理** - 后端已完成需要前端UI
2. **搜索功能** - 核心检索能力,需要前端界面
3. **服装搭配** - 主要业务功能,部分前端已完成
#### 🔶 中优先级 (增强功能)
1. **AI生成** - 图片/视频生成,前端部分完成
2. **语音处理** - 语音克隆和合成功能
3. **视频处理** - 火山引擎和Hedra集成
#### 🔵 低优先级 (辅助功能)
1. **数据处理** - JSON/Markdown解析工具
2. **标签系统** - 自定义标签管理
3. **系统功能** - 错误处理和性能监控
---
## 🎯 核心技术栈
### 前端技术
- **框架**: React 18 + TypeScript 5.8
- **构建工具**: Vite 6.0
- **UI框架**: TailwindCSS 3.4
- **状态管理**: Zustand 4.4
- **路由**: React Router 6.20
- **图标**: Lucide React + Heroicons
### 后端技术
- **框架**: Tauri 2.0 + Rust 1.70+
- **数据库**: SQLite (WAL模式支持连接池)
- **异步**: Tokio + async/await
- **序列化**: Serde + JSON
- **错误处理**: anyhow + thiserror
### AI集成
- **主要AI服务**: Google Gemini API
- **图片生成**: Midjourney集成
- **视频生成**: 极梦、火山引擎等多平台
- **语音处理**: 语音克隆和合成技术
- **ComfyUI**: 工作流自动化
### 多媒体处理
- **视频处理**: FFmpeg
- **图像处理**: 水印检测/去除/添加
- **缩略图**: 自动生成和管理
- **格式支持**: 多种视频、音频、图片格式
---