fix: prevent preview infinite loop and add document type filtering
- Remove onAutoConfigReceived callback that caused state update loop - Add document analysis to check if file needs OCR track - Only show preprocessing options for OCR-eligible files (images, scanned PDFs) - Show informative message for editable PDFs that use direct text extraction - Display text coverage percentage for editable documents 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,6 @@ interface PreprocessingPreviewProps {
|
|||||||
config: PreprocessingConfig
|
config: PreprocessingConfig
|
||||||
page?: number
|
page?: number
|
||||||
className?: string
|
className?: string
|
||||||
onAutoConfigReceived?: (config: PreprocessingConfig) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PreprocessingPreview({
|
export default function PreprocessingPreview({
|
||||||
@@ -25,7 +24,6 @@ export default function PreprocessingPreview({
|
|||||||
config,
|
config,
|
||||||
page = 1,
|
page = 1,
|
||||||
className,
|
className,
|
||||||
onAutoConfigReceived,
|
|
||||||
}: PreprocessingPreviewProps) {
|
}: PreprocessingPreviewProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@@ -81,18 +79,13 @@ export default function PreprocessingPreview({
|
|||||||
|
|
||||||
setOriginalImageUrl(URL.createObjectURL(originalBlob))
|
setOriginalImageUrl(URL.createObjectURL(originalBlob))
|
||||||
setPreprocessedImageUrl(URL.createObjectURL(preprocessedBlob))
|
setPreprocessedImageUrl(URL.createObjectURL(preprocessedBlob))
|
||||||
|
|
||||||
// Pass auto config to parent if available
|
|
||||||
if (response.auto_config && onAutoConfigReceived) {
|
|
||||||
onAutoConfigReceived(response.auto_config)
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Preview fetch error:', err)
|
console.error('Preview fetch error:', err)
|
||||||
setError(err.response?.data?.detail || err.message || 'Failed to load preview')
|
setError(err.response?.data?.detail || err.message || 'Failed to load preview')
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}, [taskId, debouncedMode, debouncedConfig, page, onAutoConfigReceived])
|
}, [taskId, debouncedMode, debouncedConfig, page])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPreview()
|
fetchPreview()
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useMutation } from '@tanstack/react-query'
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
import { Progress } from '@/components/ui/progress'
|
import { Progress } from '@/components/ui/progress'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { useToast } from '@/components/ui/toast'
|
import { useToast } from '@/components/ui/toast'
|
||||||
import { apiClientV2 } from '@/services/apiV2'
|
import { apiClientV2 } from '@/services/apiV2'
|
||||||
import { Play, CheckCircle, FileText, AlertCircle, Clock, Activity, Loader2 } from 'lucide-react'
|
import { Play, CheckCircle, FileText, AlertCircle, Clock, Activity, Loader2, Info } from 'lucide-react'
|
||||||
import LayoutModelSelector from '@/components/LayoutModelSelector'
|
import LayoutModelSelector from '@/components/LayoutModelSelector'
|
||||||
import PreprocessingSettings from '@/components/PreprocessingSettings'
|
import PreprocessingSettings from '@/components/PreprocessingSettings'
|
||||||
import PreprocessingPreview from '@/components/PreprocessingPreview'
|
import PreprocessingPreview from '@/components/PreprocessingPreview'
|
||||||
import TaskNotFound from '@/components/TaskNotFound'
|
import TaskNotFound from '@/components/TaskNotFound'
|
||||||
import { useTaskValidation } from '@/hooks/useTaskValidation'
|
import { useTaskValidation } from '@/hooks/useTaskValidation'
|
||||||
import type { LayoutModel, ProcessingOptions, PreprocessingMode, PreprocessingConfig } from '@/types/apiV2'
|
import type { LayoutModel, ProcessingOptions, PreprocessingMode, PreprocessingConfig, DocumentAnalysisResponse } from '@/types/apiV2'
|
||||||
|
|
||||||
export default function ProcessingPage() {
|
export default function ProcessingPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -47,6 +47,20 @@ export default function ProcessingPage() {
|
|||||||
})
|
})
|
||||||
const [showPreview, setShowPreview] = useState(false)
|
const [showPreview, setShowPreview] = useState(false)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// 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
|
||||||
|
|
||||||
// Start OCR processing
|
// Start OCR processing
|
||||||
const processOCRMutation = useMutation({
|
const processOCRMutation = useMutation({
|
||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
@@ -360,36 +374,66 @@ export default function ProcessingPage() {
|
|||||||
{/* Processing Options (only show when task is pending) */}
|
{/* Processing Options (only show when task is pending) */}
|
||||||
{isPending && (
|
{isPending && (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Layout Model Selection */}
|
{/* Document Analysis Loading */}
|
||||||
<LayoutModelSelector
|
{isAnalyzing && (
|
||||||
value={layoutModel}
|
<div className="flex items-center gap-2 p-4 bg-muted/30 rounded-lg border">
|
||||||
onChange={setLayoutModel}
|
<Loader2 className="w-4 h-4 animate-spin text-primary" />
|
||||||
disabled={processOCRMutation.isPending}
|
<span className="text-sm text-muted-foreground">分析文件類型中...</span>
|
||||||
/>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Preprocessing Settings */}
|
{/* Direct Track Notice - Show when document is editable PDF */}
|
||||||
<PreprocessingSettings
|
{documentAnalysis && documentAnalysis.recommended_track === 'direct' && (
|
||||||
mode={preprocessingMode}
|
<Card className="border-blue-200 bg-blue-50">
|
||||||
config={preprocessingConfig}
|
<CardContent className="pt-4">
|
||||||
onModeChange={setPreprocessingMode}
|
<div className="flex items-start gap-3">
|
||||||
onConfigChange={setPreprocessingConfig}
|
<Info className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
|
||||||
onPreview={() => setShowPreview(!showPreview)}
|
<div>
|
||||||
disabled={processOCRMutation.isPending}
|
<p className="text-sm font-medium text-blue-800">此文件為可編輯 PDF</p>
|
||||||
/>
|
<p className="text-sm text-blue-700 mt-1">
|
||||||
|
系統偵測到此 PDF 包含文字圖層,將使用直接文字提取方式處理。
|
||||||
|
版面偵測和影像前處理設定不適用於此類文件。
|
||||||
|
</p>
|
||||||
|
{documentAnalysis.text_coverage && (
|
||||||
|
<p className="text-xs text-blue-600 mt-2">
|
||||||
|
文字覆蓋率: {(documentAnalysis.text_coverage * 100).toFixed(1)}%
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Preprocessing Preview */}
|
{/* OCR Track Options - Only show when document needs OCR */}
|
||||||
{showPreview && taskId && (
|
{needsOcrTrack && !isAnalyzing && (
|
||||||
<PreprocessingPreview
|
<>
|
||||||
taskId={taskId}
|
{/* Layout Model Selection */}
|
||||||
mode={preprocessingMode}
|
<LayoutModelSelector
|
||||||
config={preprocessingConfig}
|
value={layoutModel}
|
||||||
onAutoConfigReceived={(autoConfig) => {
|
onChange={setLayoutModel}
|
||||||
// Only update if user hasn't switched to manual mode
|
disabled={processOCRMutation.isPending}
|
||||||
if (preprocessingMode === 'auto') {
|
/>
|
||||||
setPreprocessingConfig(autoConfig)
|
|
||||||
}
|
{/* Preprocessing Settings */}
|
||||||
}}
|
<PreprocessingSettings
|
||||||
/>
|
mode={preprocessingMode}
|
||||||
|
config={preprocessingConfig}
|
||||||
|
onModeChange={setPreprocessingMode}
|
||||||
|
onConfigChange={setPreprocessingConfig}
|
||||||
|
onPreview={() => setShowPreview(!showPreview)}
|
||||||
|
disabled={processOCRMutation.isPending}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Preprocessing Preview */}
|
||||||
|
{showPreview && taskId && (
|
||||||
|
<PreprocessingPreview
|
||||||
|
taskId={taskId}
|
||||||
|
mode={preprocessingMode}
|
||||||
|
config={preprocessingConfig}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user