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:
egg
2025-11-27 17:31:05 +08:00
parent 894d18b432
commit 2861f54838
2 changed files with 76 additions and 39 deletions

View File

@@ -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()

View File

@@ -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,6 +374,40 @@ 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">
{/* Document Analysis Loading */}
{isAnalyzing && (
<div className="flex items-center gap-2 p-4 bg-muted/30 rounded-lg border">
<Loader2 className="w-4 h-4 animate-spin text-primary" />
<span className="text-sm text-muted-foreground">...</span>
</div>
)}
{/* Direct Track Notice - Show when document is editable PDF */}
{documentAnalysis && documentAnalysis.recommended_track === 'direct' && (
<Card className="border-blue-200 bg-blue-50">
<CardContent className="pt-4">
<div className="flex items-start gap-3">
<Info className="w-5 h-5 text-blue-600 flex-shrink-0 mt-0.5" />
<div>
<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>
)}
{/* OCR Track Options - Only show when document needs OCR */}
{needsOcrTrack && !isAnalyzing && (
<>
{/* Layout Model Selection */} {/* Layout Model Selection */}
<LayoutModelSelector <LayoutModelSelector
value={layoutModel} value={layoutModel}
@@ -383,14 +431,10 @@ export default function ProcessingPage() {
taskId={taskId} taskId={taskId}
mode={preprocessingMode} mode={preprocessingMode}
config={preprocessingConfig} config={preprocessingConfig}
onAutoConfigReceived={(autoConfig) => {
// Only update if user hasn't switched to manual mode
if (preprocessingMode === 'auto') {
setPreprocessingConfig(autoConfig)
}
}}
/> />
)} )}
</>
)}
</div> </div>
)} )}
</div> </div>