fix: 前端封装 hook
This commit is contained in:
parent
e2a7c6d9e2
commit
5080ac1e8d
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'
|
|||
import { Upload, FolderOpen, Trash2, Eye, Download, Search, Filter, Grid, List } from 'lucide-react'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { TemplateService } from '../services/tauri'
|
||||
import { useTemplateProgress } from '../hooks/useProgressCommand'
|
||||
|
||||
interface TemplateInfo {
|
||||
id: string
|
||||
|
|
@ -19,6 +20,9 @@ interface TemplateInfo {
|
|||
tags: string[]
|
||||
}
|
||||
|
||||
// Import the progress interface from the hook
|
||||
import type { ProgressState } from '../hooks/useProgressCommand'
|
||||
|
||||
interface ImportResult {
|
||||
status: boolean
|
||||
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 [templates, setTemplates] = useState<TemplateInfo[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [importing, setImporting] = useState(false)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid')
|
||||
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)
|
||||
|
||||
// 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
|
||||
useEffect(() => {
|
||||
loadTemplates()
|
||||
|
|
@ -80,39 +88,11 @@ const TemplateManagePage: React.FC = () => {
|
|||
const folderResult = await invoke<string>('select_folder')
|
||||
if (!folderResult) return
|
||||
|
||||
// Reset states
|
||||
setImporting(true)
|
||||
setImportResult(null)
|
||||
setImportProgress(null)
|
||||
setImportLogs([])
|
||||
// Show import modal and start import
|
||||
setShowImportModal(true)
|
||||
|
||||
// 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()
|
||||
}
|
||||
await batchImport(folderResult)
|
||||
} catch (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} 个模板导入失败 (点击查看详情)
|
||||
</summary>
|
||||
<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">
|
||||
{failed.name}: {failed.error}
|
||||
</div>
|
||||
|
|
@ -456,7 +436,7 @@ const TemplateManagePage: React.FC = () => {
|
|||
{importResult.failed_count} 个模板导入失败 (点击查看详情)
|
||||
</summary>
|
||||
<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">
|
||||
{failed.name}: {failed.error}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,94 @@
|
|||
|
||||
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
|
||||
export interface VideoProcessRequest {
|
||||
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
|
||||
export class TemplateService {
|
||||
/**
|
||||
|
|
@ -490,31 +657,13 @@ export class TemplateService {
|
|||
sourceFolder: string,
|
||||
onProgress?: (progress: any) => void
|
||||
): Promise<any> {
|
||||
try {
|
||||
const request = { source_folder: sourceFolder }
|
||||
|
||||
// Set up progress listener if callback provided
|
||||
let unlisten: (() => void) | null = null
|
||||
if (onProgress) {
|
||||
const { listen } = await import('@tauri-apps/api/event')
|
||||
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
|
||||
}
|
||||
const request = { source_folder: sourceFolder }
|
||||
return executeCommandWithProgress(
|
||||
'batch_import_templates_with_progress',
|
||||
{ request },
|
||||
'template-import-progress',
|
||||
onProgress
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue