test
This commit is contained in:
148
frontend/src/components/ProcessingTrackSelector.tsx
Normal file
148
frontend/src/components/ProcessingTrackSelector.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Cpu, FileText, Sparkles, Info } from 'lucide-react'
|
||||
import type { ProcessingTrack, DocumentAnalysisResponse } from '@/types/apiV2'
|
||||
|
||||
interface ProcessingTrackSelectorProps {
|
||||
value: ProcessingTrack | null // null means "use system recommendation"
|
||||
onChange: (track: ProcessingTrack | null) => void
|
||||
documentAnalysis?: DocumentAnalysisResponse | null
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default function ProcessingTrackSelector({
|
||||
value,
|
||||
onChange,
|
||||
documentAnalysis,
|
||||
disabled = false,
|
||||
}: ProcessingTrackSelectorProps) {
|
||||
const recommendedTrack = documentAnalysis?.recommended_track
|
||||
|
||||
const tracks = [
|
||||
{
|
||||
id: null as ProcessingTrack | null,
|
||||
name: '自動選擇',
|
||||
description: '根據文件類型自動選擇最佳處理方式',
|
||||
icon: Sparkles,
|
||||
color: 'text-purple-600',
|
||||
bgColor: 'bg-purple-50',
|
||||
borderColor: 'border-purple-200',
|
||||
recommended: false,
|
||||
},
|
||||
{
|
||||
id: 'direct' as ProcessingTrack,
|
||||
name: '直接提取 (DIRECT)',
|
||||
description: '從 PDF 中直接提取文字圖層,適用於可編輯 PDF',
|
||||
icon: FileText,
|
||||
color: 'text-blue-600',
|
||||
bgColor: 'bg-blue-50',
|
||||
borderColor: 'border-blue-200',
|
||||
recommended: recommendedTrack === 'direct',
|
||||
},
|
||||
{
|
||||
id: 'ocr' as ProcessingTrack,
|
||||
name: 'OCR 識別',
|
||||
description: '使用光學字元識別處理圖片或掃描文件',
|
||||
icon: Cpu,
|
||||
color: 'text-green-600',
|
||||
bgColor: 'bg-green-50',
|
||||
borderColor: 'border-green-200',
|
||||
recommended: recommendedTrack === 'ocr',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-primary/10 rounded-lg">
|
||||
<Sparkles className="w-5 h-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle>處理方式選擇</CardTitle>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
選擇文件的處理方式,或讓系統自動判斷
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{/* Info about override */}
|
||||
{value !== null && recommendedTrack && value !== recommendedTrack && (
|
||||
<div className="flex items-start gap-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
|
||||
<Info className="w-4 h-4 text-amber-600 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-amber-800">
|
||||
您已覆蓋系統建議。系統原本建議使用「{recommendedTrack === 'direct' ? '直接提取' : 'OCR 識別'}」方式處理此文件。
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Track options */}
|
||||
<div className="grid gap-3">
|
||||
{tracks.map((track) => {
|
||||
const isSelected = value === track.id
|
||||
const Icon = track.icon
|
||||
|
||||
return (
|
||||
<button
|
||||
key={track.id ?? 'auto'}
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => onChange(track.id)}
|
||||
className={`
|
||||
w-full p-4 rounded-lg border-2 text-left transition-all
|
||||
${isSelected
|
||||
? `${track.borderColor} ${track.bgColor}`
|
||||
: 'border-border hover:border-primary/30 hover:bg-muted/30'
|
||||
}
|
||||
${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
||||
`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`p-2 rounded-lg ${isSelected ? track.bgColor : 'bg-muted'}`}>
|
||||
<Icon className={`w-5 h-5 ${isSelected ? track.color : 'text-muted-foreground'}`} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`font-medium ${isSelected ? track.color : ''}`}>
|
||||
{track.name}
|
||||
</span>
|
||||
{track.recommended && (
|
||||
<Badge variant="outline" className="text-xs bg-white">
|
||||
系統建議
|
||||
</Badge>
|
||||
)}
|
||||
{isSelected && (
|
||||
<Badge variant="default" className="text-xs">
|
||||
已選擇
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{track.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Current analysis info */}
|
||||
{documentAnalysis && (
|
||||
<div className="pt-3 border-t border-border">
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
|
||||
<span>文件分析信心度: {(documentAnalysis.confidence * 100).toFixed(0)}%</span>
|
||||
{documentAnalysis.page_count && (
|
||||
<span>頁數: {documentAnalysis.page_count}</span>
|
||||
)}
|
||||
{documentAnalysis.text_coverage !== null && (
|
||||
<span>文字覆蓋率: {(documentAnalysis.text_coverage * 100).toFixed(1)}%</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -8,14 +8,15 @@ 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, Info } from 'lucide-react'
|
||||
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 TaskNotFound from '@/components/TaskNotFound'
|
||||
import { useTaskValidation } from '@/hooks/useTaskValidation'
|
||||
import type { LayoutModel, ProcessingOptions, PreprocessingMode, PreprocessingConfig, TableDetectionConfig, DocumentAnalysisResponse } from '@/types/apiV2'
|
||||
import type { LayoutModel, ProcessingOptions, PreprocessingMode, PreprocessingConfig, TableDetectionConfig, ProcessingTrack } from '@/types/apiV2'
|
||||
|
||||
export default function ProcessingPage() {
|
||||
const { t } = useTranslation()
|
||||
@@ -56,6 +57,9 @@ export default function ProcessingPage() {
|
||||
enable_region_detection: true,
|
||||
})
|
||||
|
||||
// Processing track override state (null = use system recommendation)
|
||||
const [forceTrack, setForceTrack] = useState<ProcessingTrack | null>(null)
|
||||
|
||||
// Analyze document to determine if OCR is needed (only for pending tasks)
|
||||
const { data: documentAnalysis, isLoading: isAnalyzing } = useQuery({
|
||||
queryKey: ['documentAnalysis', taskId],
|
||||
@@ -65,16 +69,23 @@ export default function ProcessingPage() {
|
||||
})
|
||||
|
||||
// Determine if preprocessing options should be shown
|
||||
// Only show for OCR track files (images and non-editable PDFs)
|
||||
const needsOcrTrack = documentAnalysis?.recommended_track === 'ocr' ||
|
||||
documentAnalysis?.recommended_track === 'hybrid' ||
|
||||
!documentAnalysis // Show by default while analyzing
|
||||
// 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: true,
|
||||
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,
|
||||
@@ -392,53 +403,14 @@ export default function ProcessingPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Document Analysis Info */}
|
||||
{documentAnalysis && (
|
||||
<Card className={documentAnalysis.recommended_track === 'direct' ? 'border-blue-200 bg-blue-50' : 'border-green-200 bg-green-50'}>
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Info className={`w-5 h-5 flex-shrink-0 mt-0.5 ${documentAnalysis.recommended_track === 'direct' ? 'text-blue-600' : 'text-green-600'}`} />
|
||||
<div className="flex-1">
|
||||
{documentAnalysis.recommended_track === 'direct' ? (
|
||||
<>
|
||||
<p className="text-sm font-medium text-blue-800">此文件為可編輯 PDF</p>
|
||||
<p className="text-sm text-blue-700 mt-1">
|
||||
系統偵測到此 PDF 包含文字圖層,將使用直接文字提取方式處理。
|
||||
版面偵測和影像前處理設定不適用於此類文件。
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm font-medium text-green-800">
|
||||
{documentAnalysis.is_editable ? '混合文件' : '掃描文件 / 影像'}
|
||||
</p>
|
||||
<p className="text-sm text-green-700 mt-1">
|
||||
{documentAnalysis.reason}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-4 mt-2 text-xs">
|
||||
<span className={documentAnalysis.recommended_track === 'direct' ? 'text-blue-600' : 'text-green-600'}>
|
||||
處理方式: {documentAnalysis.recommended_track === 'direct' ? '直接提取' : documentAnalysis.recommended_track === 'ocr' ? 'OCR 識別' : '混合處理'}
|
||||
</span>
|
||||
{documentAnalysis.page_count && (
|
||||
<span className={documentAnalysis.recommended_track === 'direct' ? 'text-blue-600' : 'text-green-600'}>
|
||||
頁數: {documentAnalysis.page_count}
|
||||
</span>
|
||||
)}
|
||||
{documentAnalysis.text_coverage !== null && (
|
||||
<span className={documentAnalysis.recommended_track === 'direct' ? 'text-blue-600' : 'text-green-600'}>
|
||||
文字覆蓋率: {(documentAnalysis.text_coverage * 100).toFixed(1)}%
|
||||
</span>
|
||||
)}
|
||||
<span className={documentAnalysis.recommended_track === 'direct' ? 'text-blue-600' : 'text-green-600'}>
|
||||
信心度: {(documentAnalysis.confidence * 100).toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Processing Track Selector - Always show after analysis */}
|
||||
{!isAnalyzing && (
|
||||
<ProcessingTrackSelector
|
||||
value={forceTrack}
|
||||
onChange={setForceTrack}
|
||||
documentAnalysis={documentAnalysis}
|
||||
disabled={processOCRMutation.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* OCR Track Options - Only show when document needs OCR */}
|
||||
|
||||
Reference in New Issue
Block a user