From e0fbb1124d2446594bc26bdb6f4cea9cf9f62d38 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 10 Jul 2025 23:06:18 +0800 Subject: [PATCH] fix: add template track --- docs/progress_system_usage.md | 349 +++++++++++++++++++++++++++++ src-tauri/src/commands/template.rs | 14 ++ src-tauri/src/lib.rs | 1 + src/pages/TemplateManagePage.tsx | 153 ++++++++++++- src/services/tauri.ts | 13 ++ 5 files changed, 528 insertions(+), 2 deletions(-) create mode 100644 docs/progress_system_usage.md diff --git a/docs/progress_system_usage.md b/docs/progress_system_usage.md new file mode 100644 index 0000000..62e7054 --- /dev/null +++ b/docs/progress_system_usage.md @@ -0,0 +1,349 @@ +# 🚀 通用进度系统使用指南 + +一个完全重构的通用进度监听系统,从特定的模板导入功能抽离出来,现在可以用于任何需要进度监控的操作。 + +## 🎯 系统架构 + +``` +Frontend (React) +├── useProgressCommand Hook # React Hook for progress management +├── ProgressListener Class # Generic progress listener utility +└── Service Classes # Specialized service classes + +Backend (Rust) +├── execute_python_action_with_progress # Generic Python executor +├── PythonCommandBuilder # Command construction helper +└── Progress Callbacks # Flexible callback system + +Python +└── JSON-RPC Protocol # Standardized progress reporting +``` + +## 📚 使用方式 + +### 1. 使用React Hook(推荐) + +```tsx +import { useTemplateProgress } from '../hooks/useProgressCommand' + +function MyComponent() { + const { + isExecuting, + progress, + result, + error, + logs, + batchImport + } = useTemplateProgress({ + onSuccess: (result) => { + console.log('Import completed:', result) + }, + onError: (error) => { + console.error('Import failed:', error) + } + }) + + const handleImport = async () => { + try { + await batchImport('/path/to/templates') + } catch (error) { + // Error is already handled by the hook + } + } + + return ( +
+ {isExecuting && ( +
+
Progress: {progress?.progress}%
+
Message: {progress?.message}
+
+ )} + + + + {/* Real-time logs */} +
+ {logs.map((log, index) => ( +
{log}
+ ))} +
+
+ ) +} +``` + +### 2. 使用通用Hook + +```tsx +import { useProgressCommand } from '../hooks/useProgressCommand' + +function DataProcessingComponent() { + const { execute, isExecuting, progress, logs } = useProgressCommand() + + const processData = async () => { + await execute( + 'process_data_with_progress', + { + request: { + input_file: '/input.csv', + output_file: '/output.json', + processing_type: 'transform' + } + }, + 'data-processing-progress' + ) + } + + return ( +
+ + + {progress && ( +
+
Step: {progress.step}
+
Progress: {progress.progress}%
+
Message: {progress.message}
+
+ )} +
+ ) +} +``` + +### 3. 使用Service类 + +```tsx +import { AIVideoProgressService, DataProcessingService } from '../services/tauri' + +// AI Video Generation +const generateVideo = async () => { + const onProgress = (progress) => { + console.log(`${progress.step}: ${progress.message}`) + updateProgressBar(progress.progress) + } + + try { + const result = await AIVideoProgressService.generateVideoWithProgress( + '/path/to/image.jpg', + 'A beautiful sunset', + '/path/to/output.mp4', + onProgress + ) + console.log('Video generated:', result) + } catch (error) { + console.error('Generation failed:', error) + } +} + +// Data Processing +const processFiles = async () => { + const result = await DataProcessingService.batchConvertFilesWithProgress( + ['/file1.txt', '/file2.txt'], + 'pdf', + (progress) => console.log(progress.message) + ) +} +``` + +### 4. 使用底层ProgressListener + +```tsx +import { ProgressListener } from '../services/tauri' + +const customOperation = async () => { + const listener = new ProgressListener() + + try { + const result = await listener.execute( + 'my_custom_command', + { param1: 'value1' }, + 'my-progress-event', + (progress) => { + // Custom progress handling + console.log(progress) + } + ) + } finally { + listener.cleanup() + } +} +``` + +## 🔧 添加新的进度命令 + +### 1. Rust端(后端) + +```rust +#[tauri::command] +pub async fn my_new_command_with_progress( + app: AppHandle, + my_param: String +) -> Result { + let params = &[("--my_param", my_param.as_str())]; + + execute_python_action_with_progress( + app, + "python_core.my_module", + "my_action", + params, + "my-command-progress", + None, + ).await +} +``` + +### 2. Python端 + +```python +from python_core.utils.jsonrpc import create_response_handler, create_progress_reporter + +def my_action(): + rpc = create_response_handler("my_action") + progress = create_progress_reporter() + + try: + progress.step("start", "开始处理...") + # ... processing ... + + progress.report("process", 50.0, "处理中...", {"detail": "info"}) + # ... more processing ... + + progress.complete("处理完成!") + + result = {"status": True, "data": "result"} + rpc.success(result) + + except Exception as e: + progress.error(f"处理失败: {str(e)}") + rpc.error(-32603, "处理失败", str(e)) +``` + +### 3. 前端Service + +```tsx +export class MyService { + static async myOperationWithProgress( + param: string, + onProgress?: (progress: any) => void + ): Promise { + return executeCommandWithProgress( + 'my_new_command_with_progress', + { myParam: param }, + 'my-command-progress', + onProgress + ) + } +} +``` + +### 4. 专用Hook + +```tsx +export function useMyOperationProgress(options = {}) { + const hook = useProgressCommand(options) + + const executeOperation = useCallback(async (param: string) => { + return hook.execute( + 'my_new_command_with_progress', + { myParam: param }, + 'my-command-progress' + ) + }, [hook]) + + return { + ...hook, + executeOperation, + } +} +``` + +## 🎨 UI组件示例 + +### 进度模态框 + +```tsx +function ProgressModal({ isOpen, onClose, progress, logs, isExecuting }) { + return ( + +
+

操作进度

+ + {/* Progress Bar */} + {progress && ( +
+
+ {progress.step} + {progress.progress >= 0 ? `${progress.progress}%` : '处理中...'} +
+
+
+
+

{progress.message}

+
+ )} + + {/* Logs */} +
+ {logs.map((log, index) => ( +
+ {log} +
+ ))} +
+ + {/* Actions */} +
+ {!isExecuting && ( + + )} +
+
+ + ) +} +``` + +## 🔄 迁移指南 + +### 从旧的模板导入方式迁移 + +**旧方式**: +```tsx +const [importing, setImporting] = useState(false) +const [progress, setProgress] = useState(null) +const [logs, setLogs] = useState([]) + +const handleImport = async () => { + setImporting(true) + // ... manual progress handling +} +``` + +**新方式**: +```tsx +const { isExecuting, progress, logs, batchImport } = useTemplateProgress() + +const handleImport = async () => { + await batchImport(folderPath) +} +``` + +## 🎯 最佳实践 + +1. **优先使用Hook**:React Hook提供最佳的状态管理和生命周期处理 +2. **合理的进度粒度**:不要过于频繁地报告进度,影响性能 +3. **错误处理**:始终处理可能的错误情况 +4. **用户反馈**:提供清晰的进度信息和状态反馈 +5. **资源清理**:确保在组件卸载时清理监听器 + +这个通用进度系统现在可以轻松扩展到任何需要进度监控的操作!🚀 diff --git a/src-tauri/src/commands/template.rs b/src-tauri/src/commands/template.rs index edc91c6..12493fc 100644 --- a/src-tauri/src/commands/template.rs +++ b/src-tauri/src/commands/template.rs @@ -102,6 +102,20 @@ pub async fn delete_template(app: tauri::AppHandle, template_id: String) -> Resu execute_python_command(app, &args, None).await } +#[tauri::command] +pub async fn get_template_detail(app: tauri::AppHandle, template_id: String) -> Result { + let args = vec![ + "-m".to_string(), + "python_core.services.template_manager".to_string(), + "--action".to_string(), + "get_template_detail".to_string(), + "--template_id".to_string(), + template_id, + ]; + + execute_python_command(app, &args, None).await +} + // Example: AI Video generation with progress #[allow(dead_code)] #[tauri::command] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ac9ec10..56a3e8c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -41,6 +41,7 @@ pub fn run() { commands::batch_import_templates_with_progress, commands::get_templates, commands::get_template, + commands::get_template_detail, commands::delete_template ]) .run(tauri::generate_context!()) diff --git a/src/pages/TemplateManagePage.tsx b/src/pages/TemplateManagePage.tsx index d6ea56b..f7c348f 100644 --- a/src/pages/TemplateManagePage.tsx +++ b/src/pages/TemplateManagePage.tsx @@ -20,6 +20,39 @@ interface TemplateInfo { tags: string[] } +// 轨道和片段的数据结构 +interface TrackSegment { + id: string + type: 'video' | 'audio' | 'image' | 'text' | 'effect' + name: string + start_time: number + end_time: number + duration: number + resource_path?: string + properties?: any + effects?: any[] +} + +interface Track { + id: string + name: string + type: 'video' | 'audio' | 'subtitle' + index: number + segments: TrackSegment[] + properties?: any +} + +interface TemplateDetail { + id: string + name: string + description: string + canvas_config: any + tracks: Track[] + duration: number + fps: number + sample_rate?: number +} + // Import the progress interface from the hook import type { ProgressState } from '../hooks/useProgressCommand' @@ -41,6 +74,8 @@ const TemplateManagePage: React.FC = () => { const [searchTerm, setSearchTerm] = useState('') const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid') const [selectedTemplate, setSelectedTemplate] = useState(null) + const [templateDetail, setTemplateDetail] = useState(null) + const [loadingDetail, setLoadingDetail] = useState(false) const [showImportModal, setShowImportModal] = useState(false) // Use the progress hook for template operations @@ -82,6 +117,24 @@ const TemplateManagePage: React.FC = () => { } } + // 加载模板详情(包含轨道和片段信息) + const loadTemplateDetail = async (template: TemplateInfo) => { + try { + setLoadingDetail(true) + setSelectedTemplate(template) + + // 调用后端API获取模板详情 + const detail = await TemplateService.getTemplateDetail(template.id) + setTemplateDetail(detail) + } catch (error) { + console.error('Failed to load template detail:', error) + // 如果加载详情失败,至少显示基本信息 + setTemplateDetail(null) + } finally { + setLoadingDetail(false) + } + } + const handleBatchImport = async () => { try { // Select folder using Tauri dialog @@ -127,6 +180,13 @@ const TemplateManagePage: React.FC = () => { return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}` } + // Helper function to format time (for segments, in seconds) + const formatTime = (seconds: number): string => { + const minutes = Math.floor(seconds / 60) + const secs = (seconds % 60).toFixed(2) + return `${minutes}:${secs.padStart(5, '0')}` + } + const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('zh-CN') } @@ -300,7 +360,7 @@ const TemplateManagePage: React.FC = () => { {/* Actions */}
+ {/* 轨道和片段信息 */} +
+ + + {loadingDetail ? ( +
+
+ 加载详情中... +
+ ) : templateDetail ? ( +
+ {/* 画布信息 */} +
+

画布配置

+
+
尺寸: {templateDetail.canvas_config?.width || 0} × {templateDetail.canvas_config?.height || 0}
+
帧率: {templateDetail.fps || 30} FPS
+
时长: {formatDuration(templateDetail.duration)}
+ {templateDetail.sample_rate && ( +
采样率: {templateDetail.sample_rate} Hz
+ )} +
+
+ + {/* 轨道列表 */} +
+ {templateDetail.tracks.map((track, trackIndex) => ( +
+
+

+ 轨道 {track.index + 1}: {track.name} +

+ + {track.type === 'video' ? '视频' : track.type === 'audio' ? '音频' : '字幕'} + +
+ + {/* 片段列表 */} +
+ {track.segments.length > 0 ? ( + track.segments.map((segment, segmentIndex) => ( +
+
+ {segment.name} + + {segment.type === 'video' ? '视频' : + segment.type === 'audio' ? '音频' : + segment.type === 'image' ? '图片' : + segment.type === 'text' ? '文本' : '特效'} + +
+
+
开始: {formatTime(segment.start_time)}
+
结束: {formatTime(segment.end_time)}
+
时长: {formatTime(segment.duration)}
+
+ {segment.resource_path && ( +
+ 资源: {segment.resource_path} +
+ )} +
+ )) + ) : ( +
该轨道暂无片段
+ )} +
+
+ ))} +
+
+ ) : ( +
+

无法加载模板详情

+

请检查模板文件是否完整

+
+ )} +
+