fix: 前端封装 hook

This commit is contained in:
root 2025-07-10 22:56:24 +08:00
parent e2a7c6d9e2
commit 5080ac1e8d
3 changed files with 431 additions and 69 deletions

View File

@ -0,0 +1,233 @@
import { useState, useCallback, useRef } from 'react'
import { executeCommandWithProgress, ProgressListener } from '../services/tauri'
export interface ProgressState {
step: string
progress: number // -1 for indeterminate, 0-100 for percentage
message: string
details?: any
timestamp: number
}
export interface UseProgressCommandOptions {
onProgress?: (progress: ProgressState) => void
onSuccess?: (result: any) => void
onError?: (error: Error) => void
autoReset?: boolean // Auto reset state after completion
}
export interface UseProgressCommandReturn {
// State
isExecuting: boolean
progress: ProgressState | null
result: any
error: Error | null
logs: string[]
// Actions
execute: (command: string, params: any, eventName: string) => Promise<any>
reset: () => void
addLog: (message: string) => void
clearLogs: () => void
}
/**
* React Hook for executing Tauri commands with progress monitoring
*
* @param options - Configuration options
* @returns Hook state and actions
*/
export function useProgressCommand(options: UseProgressCommandOptions = {}): UseProgressCommandReturn {
const [isExecuting, setIsExecuting] = useState(false)
const [progress, setProgress] = useState<ProgressState | null>(null)
const [result, setResult] = useState<any>(null)
const [error, setError] = useState<Error | null>(null)
const [logs, setLogs] = useState<string[]>([])
const listenerRef = useRef<ProgressListener | null>(null)
const addLog = useCallback((message: string) => {
const timestamp = new Date().toLocaleTimeString()
setLogs(prev => [...prev, `[${timestamp}] ${message}`])
}, [])
const clearLogs = useCallback(() => {
setLogs([])
}, [])
const reset = useCallback(() => {
setIsExecuting(false)
setProgress(null)
setResult(null)
setError(null)
if (options.autoReset !== false) {
setLogs([])
}
}, [options.autoReset])
const execute = useCallback(async (
command: string,
params: any,
eventName: string
): Promise<any> => {
try {
setIsExecuting(true)
setError(null)
setResult(null)
if (options.autoReset !== false) {
setProgress(null)
setLogs([])
}
addLog(`开始执行命令: ${command}`)
const onProgress = (progressData: ProgressState) => {
setProgress(progressData)
addLog(progressData.message)
options.onProgress?.(progressData)
}
const commandResult = await executeCommandWithProgress(
command,
params,
eventName,
onProgress
)
setResult(commandResult)
addLog('命令执行成功')
options.onSuccess?.(commandResult)
return commandResult
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err))
setError(error)
addLog(`命令执行失败: ${error.message}`)
options.onError?.(error)
throw error
} finally {
setIsExecuting(false)
}
}, [options, addLog])
return {
// State
isExecuting,
progress,
result,
error,
logs,
// Actions
execute,
reset,
addLog,
clearLogs,
}
}
/**
* Specialized hook for template operations
*/
export function useTemplateProgress(options: UseProgressCommandOptions = {}) {
const hook = useProgressCommand(options)
const batchImport = useCallback(async (sourceFolder: string) => {
return hook.execute(
'batch_import_templates_with_progress',
{ request: { source_folder: sourceFolder } },
'template-import-progress'
)
}, [hook])
return {
...hook,
batchImport,
}
}
/**
* Specialized hook for AI video operations
*/
export function useVideoProgress(options: UseProgressCommandOptions = {}) {
const hook = useProgressCommand(options)
const generateVideo = useCallback(async (
imagePath: string,
prompt: string,
outputPath: string
) => {
return hook.execute(
'generate_video_with_progress',
{ imagePath, prompt, outputPath },
'video-generation-progress'
)
}, [hook])
return {
...hook,
generateVideo,
}
}
/**
* Specialized hook for data processing operations
*/
export function useDataProcessingProgress(options: UseProgressCommandOptions = {}) {
const hook = useProgressCommand(options)
const processData = useCallback(async (
inputFile: string,
outputFile: string,
processingType: string
) => {
return hook.execute(
'process_data_with_progress',
{ request: { input_file: inputFile, output_file: outputFile, processing_type: processingType } },
'data-processing-progress'
)
}, [hook])
const batchConvertFiles = useCallback(async (
filePaths: string[],
outputFormat: string
) => {
return hook.execute(
'batch_convert_files_with_progress',
{ filePaths, outputFormat },
'file-conversion-progress'
)
}, [hook])
return {
...hook,
processData,
batchConvertFiles,
}
}
/**
* Specialized hook for machine learning operations
*/
export function useMLProgress(options: UseProgressCommandOptions = {}) {
const hook = useProgressCommand(options)
const trainModel = useCallback(async (
datasetPath: string,
modelType: string,
epochs: number,
learningRate: number
) => {
return hook.execute(
'train_model_with_progress',
{ request: { dataset_path: datasetPath, model_type: modelType, epochs, learning_rate: learningRate } },
'model-training-progress'
)
}, [hook])
return {
...hook,
trainModel,
}
}

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
import { Upload, FolderOpen, Trash2, Eye, Download, Search, Filter, Grid, List } from 'lucide-react' import { Upload, FolderOpen, Trash2, Eye, Download, Search, Filter, Grid, List } from 'lucide-react'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { TemplateService } from '../services/tauri' import { TemplateService } from '../services/tauri'
import { useTemplateProgress } from '../hooks/useProgressCommand'
interface TemplateInfo { interface TemplateInfo {
id: string id: string
@ -19,6 +20,9 @@ interface TemplateInfo {
tags: string[] tags: string[]
} }
// Import the progress interface from the hook
import type { ProgressState } from '../hooks/useProgressCommand'
interface ImportResult { interface ImportResult {
status: boolean status: boolean
msg: string msg: string
@ -31,26 +35,30 @@ interface ImportResult {
}> }>
} }
interface ImportProgress {
step: string
progress: number // -1 for indeterminate, 0-100 for percentage
message: string
details?: any // Additional details from Python
timestamp: number
}
const TemplateManagePage: React.FC = () => { const TemplateManagePage: React.FC = () => {
const [templates, setTemplates] = useState<TemplateInfo[]>([]) const [templates, setTemplates] = useState<TemplateInfo[]>([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [importing, setImporting] = useState(false)
const [searchTerm, setSearchTerm] = useState('') const [searchTerm, setSearchTerm] = useState('')
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid') const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
const [selectedTemplate, setSelectedTemplate] = useState<TemplateInfo | null>(null) const [selectedTemplate, setSelectedTemplate] = useState<TemplateInfo | null>(null)
const [importResult, setImportResult] = useState<ImportResult | null>(null)
const [importProgress, setImportProgress] = useState<ImportProgress | null>(null)
const [importLogs, setImportLogs] = useState<string[]>([])
const [showImportModal, setShowImportModal] = useState(false) const [showImportModal, setShowImportModal] = useState(false)
// Use the progress hook for template operations
const {
isExecuting: importing,
progress: importProgress,
result: importResult,
logs: importLogs,
batchImport,
reset: resetImport
} = useTemplateProgress({
onSuccess: async (result) => {
if (result.status && result.imported_count > 0) {
await loadTemplates() // Reload templates after successful import
}
}
})
// Load templates on component mount // Load templates on component mount
useEffect(() => { useEffect(() => {
loadTemplates() loadTemplates()
@ -80,39 +88,11 @@ const TemplateManagePage: React.FC = () => {
const folderResult = await invoke<string>('select_folder') const folderResult = await invoke<string>('select_folder')
if (!folderResult) return if (!folderResult) return
// Reset states // Show import modal and start import
setImporting(true)
setImportResult(null)
setImportProgress(null)
setImportLogs([])
setShowImportModal(true) setShowImportModal(true)
await batchImport(folderResult)
// Progress callback
const onProgress = (progress: ImportProgress) => {
setImportProgress(progress)
setImportLogs(prev => [...prev, `[${new Date().toLocaleTimeString()}] ${progress.message}`])
}
// Use the new progress-enabled import
const result = await TemplateService.batchImportTemplatesWithProgress(folderResult, onProgress)
setImportResult(result)
if (result.status && result.imported_count > 0) {
// Reload templates
await loadTemplates()
}
} catch (error) { } catch (error) {
console.error('Import failed:', error) console.error('Import failed:', error)
setImportResult({
status: false,
msg: error instanceof Error ? error.message : 'Unknown error',
imported_count: 0,
failed_count: 0,
imported_templates: [],
failed_templates: []
})
} finally {
setImporting(false)
} }
} }
@ -233,7 +213,7 @@ const TemplateManagePage: React.FC = () => {
{importResult.failed_count} () {importResult.failed_count} ()
</summary> </summary>
<div className="mt-2 space-y-1"> <div className="mt-2 space-y-1">
{importResult.failed_templates.map((failed, index) => ( {importResult.failed_templates.map((failed: any, index: number) => (
<div key={index} className="text-xs text-red-500"> <div key={index} className="text-xs text-red-500">
{failed.name}: {failed.error} {failed.name}: {failed.error}
</div> </div>
@ -456,7 +436,7 @@ const TemplateManagePage: React.FC = () => {
{importResult.failed_count} () {importResult.failed_count} ()
</summary> </summary>
<div className="mt-2 space-y-1"> <div className="mt-2 space-y-1">
{importResult.failed_templates.map((failed, index) => ( {importResult.failed_templates.map((failed: any, index: number) => (
<div key={index} className="text-xs text-red-500"> <div key={index} className="text-xs text-red-500">
{failed.name}: {failed.error} {failed.name}: {failed.error}
</div> </div>

View File

@ -5,6 +5,94 @@
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
// Generic progress listener utility
export class ProgressListener {
private unlisten: (() => void) | null = null
/**
* Execute a command with progress monitoring
* @param command - Tauri command name
* @param params - Command parameters
* @param eventName - Progress event name to listen to
* @param onProgress - Progress callback function
* @returns Promise with command result
*/
static async executeWithProgress<T = any>(
command: string,
params: any,
eventName: string,
onProgress?: (progress: any) => void
): Promise<T> {
const listener = new ProgressListener()
return listener.execute(command, params, eventName, onProgress)
}
/**
* Execute a command with progress monitoring (instance method)
*/
async execute<T = any>(
command: string,
params: any,
eventName: string,
onProgress?: (progress: any) => void
): Promise<T> {
try {
// Set up progress listener if callback provided
if (onProgress) {
await this.startListening(eventName, onProgress)
}
try {
const result = await invoke(command, params)
return JSON.parse(result as string)
} finally {
// Clean up listener
this.stopListening()
}
} catch (error) {
console.error(`Failed to execute command ${command} with progress:`, error)
throw error
}
}
/**
* Start listening to progress events
*/
async startListening(eventName: string, onProgress: (progress: any) => void): Promise<void> {
const { listen } = await import('@tauri-apps/api/event')
this.unlisten = await listen(eventName, (event) => {
onProgress(event.payload)
})
}
/**
* Stop listening to progress events
*/
stopListening(): void {
if (this.unlisten) {
this.unlisten()
this.unlisten = null
}
}
/**
* Cleanup method (can be called manually if needed)
*/
cleanup(): void {
this.stopListening()
}
}
// Utility function for simple one-off progress commands
export async function executeCommandWithProgress<T = any>(
command: string,
params: any,
eventName: string,
onProgress?: (progress: any) => void
): Promise<T> {
return ProgressListener.executeWithProgress<T>(command, params, eventName, onProgress)
}
// Types for video processing // Types for video processing
export interface VideoProcessRequest { export interface VideoProcessRequest {
input_path: string input_path: string
@ -467,6 +555,85 @@ export class AIVideoService {
} }
} }
// AI Video generation operations with progress
export class AIVideoProgressService {
/**
* Generate video with progress monitoring
*/
static async generateVideoWithProgress(
imagePath: string,
prompt: string,
outputPath: string,
onProgress?: (progress: any) => void
): Promise<any> {
return executeCommandWithProgress(
'generate_video_with_progress',
{ imagePath, prompt, outputPath },
'video-generation-progress',
onProgress
)
}
}
// Data processing operations
export class DataProcessingService {
/**
* Process data with progress monitoring
*/
static async processDataWithProgress(
inputFile: string,
outputFile: string,
processingType: string,
onProgress?: (progress: any) => void
): Promise<any> {
const request = { input_file: inputFile, output_file: outputFile, processing_type: processingType }
return executeCommandWithProgress(
'process_data_with_progress',
{ request },
'data-processing-progress',
onProgress
)
}
/**
* Batch convert files with progress monitoring
*/
static async batchConvertFilesWithProgress(
filePaths: string[],
outputFormat: string,
onProgress?: (progress: any) => void
): Promise<any> {
return executeCommandWithProgress(
'batch_convert_files_with_progress',
{ filePaths, outputFormat },
'file-conversion-progress',
onProgress
)
}
}
// Machine Learning operations
export class MLService {
/**
* Train model with progress monitoring
*/
static async trainModelWithProgress(
datasetPath: string,
modelType: string,
epochs: number,
learningRate: number,
onProgress?: (progress: any) => void
): Promise<any> {
const request = { dataset_path: datasetPath, model_type: modelType, epochs, learning_rate: learningRate }
return executeCommandWithProgress(
'train_model_with_progress',
{ request },
'model-training-progress',
onProgress
)
}
}
// Template management operations // Template management operations
export class TemplateService { export class TemplateService {
/** /**
@ -490,31 +657,13 @@ export class TemplateService {
sourceFolder: string, sourceFolder: string,
onProgress?: (progress: any) => void onProgress?: (progress: any) => void
): Promise<any> { ): Promise<any> {
try {
const request = { source_folder: sourceFolder } const request = { source_folder: sourceFolder }
return executeCommandWithProgress(
// Set up progress listener if callback provided 'batch_import_templates_with_progress',
let unlisten: (() => void) | null = null { request },
if (onProgress) { 'template-import-progress',
const { listen } = await import('@tauri-apps/api/event') onProgress
unlisten = await listen('template-import-progress', (event) => { )
onProgress(event.payload)
})
}
try {
const result = await invoke('batch_import_templates_with_progress', { request })
return JSON.parse(result as string)
} finally {
// Clean up listener
if (unlisten) {
unlisten()
}
}
} catch (error) {
console.error('Failed to batch import templates with progress:', error)
throw error
}
} }
/** /**