feat: refactor dual-track architecture (Phase 1-5)
## Backend Changes - **Service Layer Refactoring**: - Add ProcessingOrchestrator for unified document processing - Add PDFTableRenderer for table rendering extraction - Add PDFFontManager for font management with CJK support - Add MemoryPolicyEngine (73% code reduction from MemoryGuard) - **Bug Fixes**: - Fix Direct Track table row span calculation - Fix OCR Track image path handling - Add cell_boxes coordinate validation - Filter out small decorative images - Add covering image detection ## Frontend Changes - **State Management**: - Add TaskStore for centralized task state management - Add localStorage persistence for recent tasks - Add processing state tracking - **Type Consolidation**: - Merge shared types from api.ts to apiV2.ts - Update imports in authStore, uploadStore, ResultsTable, SettingsPage - **Page Integration**: - Integrate TaskStore in ProcessingPage and TaskDetailPage - Update useTaskValidation hook with cache sync ## Testing - Direct Track: edit.pdf (3 pages, 1.281s), edit3.pdf (2 pages, 0.203s) - Cell boxes validation: 43 valid, 0 invalid - Table merging: 12 merged cells verified 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import type { User } from '@/types/api'
|
||||
import type { User } from '@/types/apiV2'
|
||||
|
||||
interface AuthState {
|
||||
user: User | null
|
||||
|
||||
234
frontend/src/store/taskStore.ts
Normal file
234
frontend/src/store/taskStore.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import type { Task, TaskStatus, ProcessingTrack, ProcessingOptions } from '@/types/apiV2'
|
||||
|
||||
/**
|
||||
* Processing state for tracking ongoing operations
|
||||
*/
|
||||
export interface ProcessingState {
|
||||
isProcessing: boolean
|
||||
startedAt: string | null
|
||||
track: ProcessingTrack | null
|
||||
options: ProcessingOptions | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached task info for quick display without API calls
|
||||
*/
|
||||
export interface CachedTask {
|
||||
taskId: string
|
||||
filename: string | null
|
||||
status: TaskStatus
|
||||
updatedAt: string
|
||||
processingTrack?: ProcessingTrack
|
||||
}
|
||||
|
||||
/**
|
||||
* Task Store State
|
||||
* Centralized state management for task operations
|
||||
*/
|
||||
interface TaskState {
|
||||
// Current active task
|
||||
currentTaskId: string | null
|
||||
|
||||
// Processing state for current task
|
||||
processingState: ProcessingState
|
||||
|
||||
// Recently accessed tasks cache (max 20)
|
||||
recentTasks: CachedTask[]
|
||||
|
||||
// Actions
|
||||
setCurrentTask: (taskId: string | null, filename?: string | null) => void
|
||||
clearCurrentTask: () => void
|
||||
|
||||
// Processing state actions
|
||||
startProcessing: (track: ProcessingTrack | null, options?: ProcessingOptions) => void
|
||||
stopProcessing: () => void
|
||||
|
||||
// Cache management
|
||||
updateTaskCache: (task: Task | CachedTask) => void
|
||||
updateTaskStatus: (taskId: string, status: TaskStatus, track?: ProcessingTrack) => void
|
||||
removeFromCache: (taskId: string) => void
|
||||
clearCache: () => void
|
||||
|
||||
// Get cached task
|
||||
getCachedTask: (taskId: string) => CachedTask | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum number of recent tasks to cache
|
||||
*/
|
||||
const MAX_RECENT_TASKS = 20
|
||||
|
||||
/**
|
||||
* Task Store
|
||||
* Manages task state with localStorage persistence
|
||||
*/
|
||||
export const useTaskStore = create<TaskState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
// Initial state
|
||||
currentTaskId: null,
|
||||
processingState: {
|
||||
isProcessing: false,
|
||||
startedAt: null,
|
||||
track: null,
|
||||
options: null,
|
||||
},
|
||||
recentTasks: [],
|
||||
|
||||
// Set current task
|
||||
setCurrentTask: (taskId, filename) => {
|
||||
set({ currentTaskId: taskId })
|
||||
|
||||
// Add to cache if we have task info
|
||||
if (taskId && filename !== undefined) {
|
||||
const existing = get().recentTasks.find(t => t.taskId === taskId)
|
||||
if (!existing) {
|
||||
get().updateTaskCache({
|
||||
taskId,
|
||||
filename,
|
||||
status: 'pending',
|
||||
updatedAt: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Clear current task
|
||||
clearCurrentTask: () => {
|
||||
set({
|
||||
currentTaskId: null,
|
||||
processingState: {
|
||||
isProcessing: false,
|
||||
startedAt: null,
|
||||
track: null,
|
||||
options: null,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
// Start processing
|
||||
startProcessing: (track, options) => {
|
||||
set({
|
||||
processingState: {
|
||||
isProcessing: true,
|
||||
startedAt: new Date().toISOString(),
|
||||
track,
|
||||
options: options || null,
|
||||
},
|
||||
})
|
||||
|
||||
// Update cache status
|
||||
const currentTaskId = get().currentTaskId
|
||||
if (currentTaskId) {
|
||||
get().updateTaskStatus(currentTaskId, 'processing', track || undefined)
|
||||
}
|
||||
},
|
||||
|
||||
// Stop processing
|
||||
stopProcessing: () => {
|
||||
set((state) => ({
|
||||
processingState: {
|
||||
...state.processingState,
|
||||
isProcessing: false,
|
||||
},
|
||||
}))
|
||||
},
|
||||
|
||||
// Update task in cache
|
||||
updateTaskCache: (task) => {
|
||||
set((state) => {
|
||||
const taskId = 'task_id' in task ? task.task_id : task.taskId
|
||||
const cached: CachedTask = {
|
||||
taskId,
|
||||
filename: task.filename || null,
|
||||
status: task.status,
|
||||
updatedAt: new Date().toISOString(),
|
||||
processingTrack: 'processing_track' in task ? task.processing_track : task.processingTrack,
|
||||
}
|
||||
|
||||
// Remove existing entry if present
|
||||
const filtered = state.recentTasks.filter(t => t.taskId !== taskId)
|
||||
|
||||
// Add to front and limit size
|
||||
const updated = [cached, ...filtered].slice(0, MAX_RECENT_TASKS)
|
||||
|
||||
return { recentTasks: updated }
|
||||
})
|
||||
},
|
||||
|
||||
// Update task status in cache
|
||||
updateTaskStatus: (taskId, status, track) => {
|
||||
set((state) => {
|
||||
const updated = state.recentTasks.map(t => {
|
||||
if (t.taskId === taskId) {
|
||||
return {
|
||||
...t,
|
||||
status,
|
||||
processingTrack: track || t.processingTrack,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
}
|
||||
return t
|
||||
})
|
||||
return { recentTasks: updated }
|
||||
})
|
||||
},
|
||||
|
||||
// Remove task from cache
|
||||
removeFromCache: (taskId) => {
|
||||
set((state) => ({
|
||||
recentTasks: state.recentTasks.filter(t => t.taskId !== taskId),
|
||||
// Also clear current task if it matches
|
||||
currentTaskId: state.currentTaskId === taskId ? null : state.currentTaskId,
|
||||
}))
|
||||
},
|
||||
|
||||
// Clear all cached tasks
|
||||
clearCache: () => {
|
||||
set({
|
||||
recentTasks: [],
|
||||
currentTaskId: null,
|
||||
processingState: {
|
||||
isProcessing: false,
|
||||
startedAt: null,
|
||||
track: null,
|
||||
options: null,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
// Get cached task by ID
|
||||
getCachedTask: (taskId) => {
|
||||
return get().recentTasks.find(t => t.taskId === taskId)
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'tool-ocr-task-store',
|
||||
// Only persist essential state, not processing state
|
||||
partialize: (state) => ({
|
||||
currentTaskId: state.currentTaskId,
|
||||
recentTasks: state.recentTasks,
|
||||
}),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* Helper hook to get current task from cache
|
||||
*/
|
||||
export function useCurrentTask() {
|
||||
const currentTaskId = useTaskStore((state) => state.currentTaskId)
|
||||
const recentTasks = useTaskStore((state) => state.recentTasks)
|
||||
|
||||
if (!currentTaskId) return null
|
||||
return recentTasks.find(t => t.taskId === currentTaskId) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper hook for processing state
|
||||
*/
|
||||
export function useProcessingState() {
|
||||
return useTaskStore((state) => state.processingState)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import type { FileInfo } from '@/types/api'
|
||||
import type { FileInfo } from '@/types/apiV2'
|
||||
|
||||
interface UploadState {
|
||||
batchId: number | null
|
||||
|
||||
Reference in New Issue
Block a user