feat: add Kombi-Modus (PaddleOCR + Tesseract) for OCR Overlay
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 35s
CI / test-go-edu-search (push) Successful in 33s
CI / test-python-klausur (push) Failing after 2m20s
CI / test-python-agent-core (push) Successful in 22s
CI / test-nodejs-website (push) Successful in 41s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 35s
CI / test-go-edu-search (push) Successful in 33s
CI / test-python-klausur (push) Failing after 2m20s
CI / test-python-agent-core (push) Successful in 22s
CI / test-nodejs-website (push) Successful in 41s
Runs both OCR engines on the preprocessed image and merges results: word boxes matched by IoU, coordinates averaged by confidence weight. Unmatched Tesseract words (bullets, symbols) are added for better coverage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,14 +10,38 @@ type Phase = 'idle' | 'running' | 'overlay'
|
||||
interface PaddleDirectStepProps {
|
||||
sessionId: string | null
|
||||
onNext: () => void
|
||||
/** Backend endpoint suffix, default: 'paddle-direct' */
|
||||
endpoint?: string
|
||||
/** Title shown in idle state */
|
||||
title?: string
|
||||
/** Description shown in idle state */
|
||||
description?: string
|
||||
/** Icon shown in idle state */
|
||||
icon?: string
|
||||
/** Button label */
|
||||
buttonLabel?: string
|
||||
/** Running label */
|
||||
runningLabel?: string
|
||||
/** OCR engine key to check for auto-detect */
|
||||
engineKey?: string
|
||||
}
|
||||
|
||||
export function PaddleDirectStep({ sessionId, onNext }: PaddleDirectStepProps) {
|
||||
export function PaddleDirectStep({
|
||||
sessionId,
|
||||
onNext,
|
||||
endpoint = 'paddle-direct',
|
||||
title = 'Paddle Direct',
|
||||
description = 'PaddleOCR erkennt alle Woerter direkt auf dem Originalbild — ohne Begradigung, Entzerrung oder Zuschnitt.',
|
||||
icon = '⚡',
|
||||
buttonLabel = 'PaddleOCR starten',
|
||||
runningLabel = 'PaddleOCR laeuft...',
|
||||
engineKey = 'paddle_direct',
|
||||
}: PaddleDirectStepProps) {
|
||||
const [phase, setPhase] = useState<Phase>('idle')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [stats, setStats] = useState<{ cells: number; rows: number; duration: number } | null>(null)
|
||||
|
||||
// Auto-detect: if session already has paddle_direct word_result → show overlay
|
||||
// Auto-detect: if session already has matching word_result → show overlay
|
||||
useEffect(() => {
|
||||
if (!sessionId) return
|
||||
let cancelled = false
|
||||
@@ -26,7 +50,7 @@ export function PaddleDirectStep({ sessionId, onNext }: PaddleDirectStepProps) {
|
||||
const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}`)
|
||||
if (!res.ok || cancelled) return
|
||||
const data = await res.json()
|
||||
if (data.word_result?.ocr_engine === 'paddle_direct') {
|
||||
if (data.word_result?.ocr_engine === engineKey) {
|
||||
setPhase('overlay')
|
||||
}
|
||||
} catch {
|
||||
@@ -34,14 +58,14 @@ export function PaddleDirectStep({ sessionId, onNext }: PaddleDirectStepProps) {
|
||||
}
|
||||
})()
|
||||
return () => { cancelled = true }
|
||||
}, [sessionId])
|
||||
}, [sessionId, engineKey])
|
||||
|
||||
const runPaddleDirect = useCallback(async () => {
|
||||
const runOcr = useCallback(async () => {
|
||||
if (!sessionId) return
|
||||
setPhase('running')
|
||||
setError(null)
|
||||
try {
|
||||
const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/paddle-direct`, {
|
||||
const res = await fetch(`${KLAUSUR_API}/api/v1/ocr-pipeline/sessions/${sessionId}/${endpoint}`, {
|
||||
method: 'POST',
|
||||
})
|
||||
if (!res.ok) {
|
||||
@@ -59,7 +83,7 @@ export function PaddleDirectStep({ sessionId, onNext }: PaddleDirectStepProps) {
|
||||
setError(e instanceof Error ? e.message : 'Unbekannter Fehler')
|
||||
setPhase('idle')
|
||||
}
|
||||
}, [sessionId])
|
||||
}, [sessionId, endpoint])
|
||||
|
||||
if (!sessionId) {
|
||||
return (
|
||||
@@ -91,7 +115,7 @@ export function PaddleDirectStep({ sessionId, onNext }: PaddleDirectStepProps) {
|
||||
<div className="w-10 h-10 border-4 border-teal-200 dark:border-teal-800 border-t-teal-600 dark:border-t-teal-400 rounded-full animate-spin" />
|
||||
<div className="text-center space-y-1">
|
||||
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
PaddleOCR laeuft...
|
||||
{runningLabel}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
Bild wird analysiert (ca. 5-30s)
|
||||
@@ -101,12 +125,12 @@ export function PaddleDirectStep({ sessionId, onNext }: PaddleDirectStepProps) {
|
||||
) : (
|
||||
<>
|
||||
<div className="text-center space-y-2">
|
||||
<div className="text-4xl">⚡</div>
|
||||
<div className="text-4xl">{icon}</div>
|
||||
<h3 className="text-lg font-medium text-gray-700 dark:text-gray-300">
|
||||
Paddle Direct
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 max-w-md">
|
||||
PaddleOCR erkennt alle Woerter direkt auf dem Originalbild — ohne Begradigung, Entzerrung oder Zuschnitt.
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -117,10 +141,10 @@ export function PaddleDirectStep({ sessionId, onNext }: PaddleDirectStepProps) {
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={runPaddleDirect}
|
||||
onClick={runOcr}
|
||||
className="px-6 py-2.5 bg-teal-600 text-white text-sm font-medium rounded-lg hover:bg-teal-700 transition-colors"
|
||||
>
|
||||
PaddleOCR starten
|
||||
{buttonLabel}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user