feat: add batch processing for multiple file uploads
- Add BatchState management in taskStore with progress tracking - Implement batch processing service with concurrency control - Direct Track: max 5 parallel tasks - OCR Track: sequential processing (GPU VRAM limit) - Refactor ProcessingPage to support batch mode with BatchProcessingPanel - Update UploadPage to initialize batch state for multi-file uploads - Add i18n translations for batch processing (zh-TW, en-US) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import type { Task, TaskStatus, ProcessingTrack, ProcessingOptions } from '@/types/apiV2'
|
||||
import type { Task, TaskStatus, ProcessingTrack, ProcessingOptions, DocumentAnalysisResponse } from '@/types/apiV2'
|
||||
|
||||
/**
|
||||
* Processing state for tracking ongoing operations
|
||||
@@ -12,6 +12,76 @@ export interface ProcessingState {
|
||||
options: ProcessingOptions | null
|
||||
}
|
||||
|
||||
// ==================== Batch Processing Types ====================
|
||||
|
||||
/**
|
||||
* Processing strategy for batch operations
|
||||
*/
|
||||
export type BatchStrategy = 'auto' | 'force_ocr' | 'force_direct'
|
||||
|
||||
/**
|
||||
* Individual task state within a batch
|
||||
*/
|
||||
export interface BatchTaskState {
|
||||
taskId: string
|
||||
filename: string | null
|
||||
status: TaskStatus
|
||||
track: ProcessingTrack | null
|
||||
recommendedTrack: ProcessingTrack | null
|
||||
analysisResult: DocumentAnalysisResponse | null
|
||||
error: string | null
|
||||
startedAt: string | null
|
||||
completedAt: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch progress tracking
|
||||
*/
|
||||
export interface BatchProgress {
|
||||
total: number
|
||||
pending: number
|
||||
analyzing: number
|
||||
processing: number
|
||||
completed: number
|
||||
failed: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch processing options (unified settings for all tasks)
|
||||
*/
|
||||
export interface BatchProcessingOptions {
|
||||
strategy: BatchStrategy
|
||||
layoutModel: 'chinese' | 'default' | 'cdla'
|
||||
preprocessingMode: 'auto' | 'manual' | 'disabled'
|
||||
language: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch state for managing multiple tasks
|
||||
*/
|
||||
export interface BatchState {
|
||||
// Whether batch mode is active
|
||||
isActive: boolean
|
||||
|
||||
// All task IDs in this batch
|
||||
taskIds: string[]
|
||||
|
||||
// State for each task
|
||||
taskStates: Record<string, BatchTaskState>
|
||||
|
||||
// Progress summary
|
||||
progress: BatchProgress
|
||||
|
||||
// Unified processing options
|
||||
processingOptions: BatchProcessingOptions
|
||||
|
||||
// Batch processing status
|
||||
isProcessing: boolean
|
||||
isAnalyzing: boolean
|
||||
startedAt: string | null
|
||||
completedAt: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached task info for quick display without API calls
|
||||
*/
|
||||
@@ -23,6 +93,43 @@ export interface CachedTask {
|
||||
processingTrack?: ProcessingTrack
|
||||
}
|
||||
|
||||
/**
|
||||
* Default batch processing options
|
||||
*/
|
||||
const defaultBatchProcessingOptions: BatchProcessingOptions = {
|
||||
strategy: 'auto',
|
||||
layoutModel: 'chinese',
|
||||
preprocessingMode: 'auto',
|
||||
language: 'ch',
|
||||
}
|
||||
|
||||
/**
|
||||
* Default batch progress
|
||||
*/
|
||||
const defaultBatchProgress: BatchProgress = {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
analyzing: 0,
|
||||
processing: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
}
|
||||
|
||||
/**
|
||||
* Default batch state
|
||||
*/
|
||||
const defaultBatchState: BatchState = {
|
||||
isActive: false,
|
||||
taskIds: [],
|
||||
taskStates: {},
|
||||
progress: defaultBatchProgress,
|
||||
processingOptions: defaultBatchProcessingOptions,
|
||||
isProcessing: false,
|
||||
isAnalyzing: false,
|
||||
startedAt: null,
|
||||
completedAt: null,
|
||||
}
|
||||
|
||||
/**
|
||||
* Task Store State
|
||||
* Centralized state management for task operations
|
||||
@@ -37,6 +144,9 @@ interface TaskState {
|
||||
// Recently accessed tasks cache (max 20)
|
||||
recentTasks: CachedTask[]
|
||||
|
||||
// Batch processing state
|
||||
batchState: BatchState
|
||||
|
||||
// Actions
|
||||
setCurrentTask: (taskId: string | null, filename?: string | null) => void
|
||||
clearCurrentTask: () => void
|
||||
@@ -53,6 +163,16 @@ interface TaskState {
|
||||
|
||||
// Get cached task
|
||||
getCachedTask: (taskId: string) => CachedTask | undefined
|
||||
|
||||
// Batch processing actions
|
||||
initBatch: (tasks: Array<{ taskId: string; filename: string | null }>) => void
|
||||
clearBatch: () => void
|
||||
setBatchOptions: (options: Partial<BatchProcessingOptions>) => void
|
||||
updateBatchTaskState: (taskId: string, updates: Partial<BatchTaskState>) => void
|
||||
setBatchAnalyzing: (isAnalyzing: boolean) => void
|
||||
startBatchProcessing: () => void
|
||||
stopBatchProcessing: () => void
|
||||
recalculateBatchProgress: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,6 +196,7 @@ export const useTaskStore = create<TaskState>()(
|
||||
options: null,
|
||||
},
|
||||
recentTasks: [],
|
||||
batchState: { ...defaultBatchState },
|
||||
|
||||
// Set current task
|
||||
setCurrentTask: (taskId, filename) => {
|
||||
@@ -207,6 +328,179 @@ export const useTaskStore = create<TaskState>()(
|
||||
getCachedTask: (taskId) => {
|
||||
return get().recentTasks.find(t => t.taskId === taskId)
|
||||
},
|
||||
|
||||
// ==================== Batch Processing Actions ====================
|
||||
|
||||
// Initialize batch with uploaded tasks
|
||||
initBatch: (tasks) => {
|
||||
const taskIds = tasks.map(t => t.taskId)
|
||||
const taskStates: Record<string, BatchTaskState> = {}
|
||||
|
||||
for (const task of tasks) {
|
||||
taskStates[task.taskId] = {
|
||||
taskId: task.taskId,
|
||||
filename: task.filename,
|
||||
status: 'pending',
|
||||
track: null,
|
||||
recommendedTrack: null,
|
||||
analysisResult: null,
|
||||
error: null,
|
||||
startedAt: null,
|
||||
completedAt: null,
|
||||
}
|
||||
}
|
||||
|
||||
set({
|
||||
batchState: {
|
||||
isActive: true,
|
||||
taskIds,
|
||||
taskStates,
|
||||
progress: {
|
||||
total: tasks.length,
|
||||
pending: tasks.length,
|
||||
analyzing: 0,
|
||||
processing: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
},
|
||||
processingOptions: { ...defaultBatchProcessingOptions },
|
||||
isProcessing: false,
|
||||
isAnalyzing: false,
|
||||
startedAt: null,
|
||||
completedAt: null,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
// Clear batch state
|
||||
clearBatch: () => {
|
||||
set({ batchState: { ...defaultBatchState } })
|
||||
},
|
||||
|
||||
// Update batch processing options
|
||||
setBatchOptions: (options) => {
|
||||
set((state) => ({
|
||||
batchState: {
|
||||
...state.batchState,
|
||||
processingOptions: {
|
||||
...state.batchState.processingOptions,
|
||||
...options,
|
||||
},
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
// Update individual task state within batch
|
||||
updateBatchTaskState: (taskId, updates) => {
|
||||
set((state) => {
|
||||
const currentTask = state.batchState.taskStates[taskId]
|
||||
if (!currentTask) return state
|
||||
|
||||
const updatedTaskStates = {
|
||||
...state.batchState.taskStates,
|
||||
[taskId]: {
|
||||
...currentTask,
|
||||
...updates,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
batchState: {
|
||||
...state.batchState,
|
||||
taskStates: updatedTaskStates,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
// Recalculate progress after update
|
||||
get().recalculateBatchProgress()
|
||||
},
|
||||
|
||||
// Set batch analyzing state
|
||||
setBatchAnalyzing: (isAnalyzing) => {
|
||||
set((state) => ({
|
||||
batchState: {
|
||||
...state.batchState,
|
||||
isAnalyzing,
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
// Start batch processing
|
||||
startBatchProcessing: () => {
|
||||
set((state) => ({
|
||||
batchState: {
|
||||
...state.batchState,
|
||||
isProcessing: true,
|
||||
startedAt: new Date().toISOString(),
|
||||
completedAt: null,
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
// Stop batch processing
|
||||
stopBatchProcessing: () => {
|
||||
set((state) => ({
|
||||
batchState: {
|
||||
...state.batchState,
|
||||
isProcessing: false,
|
||||
completedAt: new Date().toISOString(),
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
// Recalculate batch progress from task states
|
||||
recalculateBatchProgress: () => {
|
||||
set((state) => {
|
||||
const { taskStates, taskIds } = state.batchState
|
||||
let pending = 0
|
||||
let analyzing = 0
|
||||
let processing = 0
|
||||
let completed = 0
|
||||
let failed = 0
|
||||
|
||||
for (const taskId of taskIds) {
|
||||
const taskState = taskStates[taskId]
|
||||
if (!taskState) continue
|
||||
|
||||
switch (taskState.status) {
|
||||
case 'pending':
|
||||
// Check if we're analyzing (has recommendedTrack means analyzed)
|
||||
if (taskState.recommendedTrack !== null) {
|
||||
pending++
|
||||
} else if (state.batchState.isAnalyzing) {
|
||||
analyzing++
|
||||
} else {
|
||||
pending++
|
||||
}
|
||||
break
|
||||
case 'processing':
|
||||
processing++
|
||||
break
|
||||
case 'completed':
|
||||
completed++
|
||||
break
|
||||
case 'failed':
|
||||
failed++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
batchState: {
|
||||
...state.batchState,
|
||||
progress: {
|
||||
total: taskIds.length,
|
||||
pending,
|
||||
analyzing,
|
||||
processing,
|
||||
completed,
|
||||
failed,
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'tool-ocr-task-store',
|
||||
@@ -236,3 +530,31 @@ export function useCurrentTask() {
|
||||
export function useProcessingState() {
|
||||
return useTaskStore((state) => state.processingState)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper hook for batch state
|
||||
*/
|
||||
export function useBatchState() {
|
||||
return useTaskStore((state) => state.batchState)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper hook for batch progress
|
||||
*/
|
||||
export function useBatchProgress() {
|
||||
return useTaskStore((state) => state.batchState.progress)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper hook for batch processing options
|
||||
*/
|
||||
export function useBatchOptions() {
|
||||
return useTaskStore((state) => state.batchState.processingOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper hook to check if batch mode is active
|
||||
*/
|
||||
export function useIsBatchMode() {
|
||||
return useTaskStore((state) => state.batchState.isActive && state.batchState.taskIds.length > 1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user