import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { useMutation, useQuery } from '@tanstack/react-query' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Progress } from '@/components/ui/progress' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { useToast } from '@/components/ui/toast' import { apiClientV2 } from '@/services/apiV2' import { Play, CheckCircle, FileText, AlertCircle, Clock, Activity, Loader2 } from 'lucide-react' import LayoutModelSelector from '@/components/LayoutModelSelector' import PreprocessingSettings from '@/components/PreprocessingSettings' import PreprocessingPreview from '@/components/PreprocessingPreview' import TableDetectionSelector from '@/components/TableDetectionSelector' import ProcessingTrackSelector from '@/components/ProcessingTrackSelector' import OCRPresetSelector from '@/components/OCRPresetSelector' import TaskNotFound from '@/components/TaskNotFound' import { useTaskValidation } from '@/hooks/useTaskValidation' import { useTaskStore, useProcessingState } from '@/store/taskStore' import type { LayoutModel, ProcessingOptions, PreprocessingMode, PreprocessingConfig, TableDetectionConfig, ProcessingTrack, OCRPreset, OCRConfig } from '@/types/apiV2' export default function ProcessingPage() { const { t } = useTranslation() const navigate = useNavigate() const { toast } = useToast() // Use TaskStore for processing state management const { startProcessing, stopProcessing, updateTaskStatus } = useTaskStore() const processingState = useProcessingState() // Use shared hook for task validation const { taskId, taskDetail, isLoading: isValidating, isNotFound, clearAndReset } = useTaskValidation({ refetchInterval: (query) => { const data = query.state.data if (!data) return 2000 if (data.status === 'completed' || data.status === 'failed') { return false } return 2000 }, }) // Layout model state (default to 'chinese' for best Chinese document support) const [layoutModel, setLayoutModel] = useState('chinese') // Preprocessing state const [preprocessingMode, setPreprocessingMode] = useState('auto') const [preprocessingConfig, setPreprocessingConfig] = useState({ contrast: 'clahe', contrast_strength: 1.0, sharpen: true, sharpen_strength: 1.0, binarize: false, remove_scan_artifacts: true, }) const [showPreview, setShowPreview] = useState(false) // Table detection state const [tableDetectionConfig, setTableDetectionConfig] = useState({ enable_wired_table: true, enable_wireless_table: true, enable_region_detection: true, }) // Processing track override state (null = use system recommendation) const [forceTrack, setForceTrack] = useState(null) // OCR Preset state (default to 'datasheet' for best balance) const [ocrPreset, setOcrPreset] = useState('datasheet') const [ocrConfig, setOcrConfig] = useState({}) // Analyze document to determine if OCR is needed (only for pending tasks) const { data: documentAnalysis, isLoading: isAnalyzing } = useQuery({ queryKey: ['documentAnalysis', taskId], queryFn: () => apiClientV2.analyzeDocument(taskId!), enabled: !!taskId && taskDetail?.status === 'pending', staleTime: Infinity, // Cache indefinitely since document doesn't change }) // Determine if preprocessing options should be shown // Show OCR options when: // 1. User explicitly selected OCR track // 2. OR system recommends OCR/hybrid track (and user hasn't overridden to direct) // 3. OR still analyzing (show by default) const needsOcrTrack = forceTrack === 'ocr' || (forceTrack === null && ( documentAnalysis?.recommended_track === 'ocr' || documentAnalysis?.recommended_track === 'hybrid' || !documentAnalysis )) // Start OCR processing const processOCRMutation = useMutation({ mutationFn: () => { const options: ProcessingOptions = { use_dual_track: forceTrack === null, // Only use dual-track auto-detection if not forcing force_track: forceTrack || undefined, // Pass force_track if user selected one language: 'ch', layout_model: layoutModel, preprocessing_mode: preprocessingMode, preprocessing_config: preprocessingMode === 'manual' ? preprocessingConfig : undefined, table_detection: tableDetectionConfig, ocr_preset: ocrPreset, ocr_config: ocrPreset === 'custom' ? ocrConfig : undefined, } // Update TaskStore processing state startProcessing(forceTrack, options) return apiClientV2.startTask(taskId!, options) }, onSuccess: () => { // Update task status in cache if (taskId) { updateTaskStatus(taskId, 'processing', forceTrack || undefined) } toast({ title: '開始處理', description: 'OCR 處理已開始', variant: 'success', }) }, onError: (error: any) => { // Stop processing state on error stopProcessing() toast({ title: t('errors.processingFailed'), description: error.response?.data?.detail || t('errors.networkError'), variant: 'destructive', }) }, }) // Handle task status changes - update store and redirect when completed useEffect(() => { if (taskDetail?.status === 'completed') { // Stop processing state and update cache stopProcessing() if (taskId) { updateTaskStatus(taskId, 'completed', taskDetail.processing_track) } setTimeout(() => { navigate('/tasks') }, 1000) } else if (taskDetail?.status === 'failed') { // Stop processing state on failure stopProcessing() if (taskId) { updateTaskStatus(taskId, 'failed') } } }, [taskDetail?.status, taskDetail?.processing_track, taskId, navigate, stopProcessing, updateTaskStatus]) const handleStartProcessing = () => { processOCRMutation.mutate() } const handleViewResults = () => { navigate('/tasks') } const getStatusBadge = (status: string) => { switch (status) { case 'completed': return {t('processing.completed')} case 'processing': return {t('processing.processing')} case 'failed': return {t('processing.failed')} default: return {t('processing.pending')} } } const getProgressPercentage = (status: string) => { switch (status) { case 'completed': return 100 case 'processing': return 50 case 'failed': return 100 default: return 0 } } // Show loading while validating task if (isValidating) { return (

載入任務資訊...

) } // Show message when task was deleted if (isNotFound) { return } // Show helpful message when no task is selected if (!taskId) { return (
{t('processing.title')}

{t('processing.noBatchMessage', { defaultValue: '尚未選擇任何任務。請先上傳檔案以建立任務。' })}

) } const isProcessing = taskDetail?.status === 'processing' const isCompleted = taskDetail?.status === 'completed' const isPending = !taskDetail || taskDetail.status === 'pending' return (
{/* Page Header */}

{t('processing.title')}

任務 ID: {taskId} {taskDetail?.filename && ` · ${taskDetail.filename}`}

{isCompleted && (
處理完成
)} {isProcessing && (
處理中
)}
{/* Overall Progress */}
{t('processing.progress')}
{taskDetail && getStatusBadge(taskDetail.status)}
{/* Progress bar */}
{t('processing.status')} {taskDetail ? getProgressPercentage(taskDetail.status) : 0}%
{/* Task Info */} {taskDetail && (

檔案名稱

{taskDetail.filename || '未知檔案'}

{taskDetail.processing_time_ms && (

處理時間

{(taskDetail.processing_time_ms / 1000).toFixed(2)}s

)}
)} {/* Error message */} {taskDetail?.error_message && (

處理失敗

{taskDetail.error_message}

)} {/* Action buttons */} {(isPending || isCompleted) && (
{isPending && ( )} {isCompleted && ( )}
)}
{/* Task Details Card */} {taskDetail && (
任務詳情
任務狀態 {getStatusBadge(taskDetail.status)}
建立時間 {new Date(taskDetail.created_at).toLocaleString('zh-TW')}
{taskDetail.updated_at && (
更新時間 {new Date(taskDetail.updated_at).toLocaleString('zh-TW')}
)} {taskDetail.completed_at && (
完成時間 {new Date(taskDetail.completed_at).toLocaleString('zh-TW')}
)}
)} {/* Processing Options (only show when task is pending) */} {isPending && (
{/* Document Analysis Loading */} {isAnalyzing && (
分析文件類型中...
)} {/* Processing Track Selector - Always show after analysis */} {!isAnalyzing && ( )} {/* OCR Track Options - Only show when document needs OCR */} {needsOcrTrack && !isAnalyzing && ( <> {/* OCR Processing Preset - Primary selection */} {/* Layout Model Selection */} {/* Table Detection Settings */} {/* Preprocessing Settings */} setShowPreview(!showPreview)} disabled={processOCRMutation.isPending} /> {/* Preprocessing Preview */} {showPreview && taskId && ( )} )}
)}
) }