fix: dewarp UI shows detection details, quality gate status, confidence bars
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 26s
CI / test-python-klausur (push) Failing after 1m56s
CI / test-python-agent-core (push) Successful in 15s
CI / test-nodejs-website (push) Successful in 19s
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 26s
CI / test-python-klausur (push) Failing after 1m56s
CI / test-python-agent-core (push) Successful in 15s
CI / test-nodejs-website (push) Successful in 19s
- Add DewarpDetection type with per-method results - Expand method labels for all 4 detectors (A-D) - Show green/amber banner: applied vs quality-gate-rejected - Expandable "Details" panel showing all 4 methods with confidence bars - Visual confidence bars instead of plain percentage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,13 +50,20 @@ export interface DeskewGroundTruth {
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface DewarpDetection {
|
||||
method: string
|
||||
shear_degrees: number
|
||||
confidence: number
|
||||
}
|
||||
|
||||
export interface DewarpResult {
|
||||
session_id: string
|
||||
method_used: 'vertical_edge' | 'manual' | 'none'
|
||||
method_used: string
|
||||
shear_degrees: number
|
||||
confidence: number
|
||||
duration_seconds: number
|
||||
dewarped_image_url: string
|
||||
detections?: DewarpDetection[]
|
||||
}
|
||||
|
||||
export interface DewarpGroundTruth {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { DewarpResult, DewarpGroundTruth } from '@/app/(admin)/ai/ocr-pipeline/types'
|
||||
import type { DewarpResult, DewarpDetection, DewarpGroundTruth } from '@/app/(admin)/ai/ocr-pipeline/types'
|
||||
|
||||
interface DewarpControlsProps {
|
||||
dewarpResult: DewarpResult | null
|
||||
@@ -14,11 +14,35 @@ interface DewarpControlsProps {
|
||||
}
|
||||
|
||||
const METHOD_LABELS: Record<string, string> = {
|
||||
vertical_edge: 'Vertikale Kanten',
|
||||
vertical_edge: 'A: Vertikale Kanten',
|
||||
projection: 'B: Projektions-Varianz',
|
||||
hough_lines: 'C: Hough-Linien',
|
||||
text_lines: 'D: Textzeilenanalyse',
|
||||
manual: 'Manuell',
|
||||
none: 'Keine Korrektur',
|
||||
}
|
||||
|
||||
/** Colour for a confidence value (0-1). */
|
||||
function confColor(conf: number): string {
|
||||
if (conf >= 0.7) return 'text-green-600 dark:text-green-400'
|
||||
if (conf >= 0.5) return 'text-yellow-600 dark:text-yellow-400'
|
||||
return 'text-gray-400'
|
||||
}
|
||||
|
||||
/** Short confidence bar (visual). */
|
||||
function ConfBar({ value }: { value: number }) {
|
||||
const pct = Math.round(value * 100)
|
||||
const bg = value >= 0.7 ? 'bg-green-500' : value >= 0.5 ? 'bg-yellow-500' : 'bg-gray-400'
|
||||
return (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-16 h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
||||
<div className={`h-full rounded-full ${bg}`} style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
<span className={`text-xs font-mono ${confColor(value)}`}>{pct}%</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function DewarpControls({
|
||||
dewarpResult,
|
||||
showGrid,
|
||||
@@ -32,6 +56,7 @@ export function DewarpControls({
|
||||
const [gtFeedback, setGtFeedback] = useState<'correct' | 'incorrect' | null>(null)
|
||||
const [gtNotes, setGtNotes] = useState('')
|
||||
const [gtSaved, setGtSaved] = useState(false)
|
||||
const [showDetails, setShowDetails] = useState(false)
|
||||
|
||||
// Initialize slider to auto-detected value when result arrives
|
||||
useEffect(() => {
|
||||
@@ -57,32 +82,61 @@ export function DewarpControls({
|
||||
setGtSaved(true)
|
||||
}
|
||||
|
||||
const wasRejected = dewarpResult && dewarpResult.method_used === 'none' && (dewarpResult.detections || []).length > 0
|
||||
const wasApplied = dewarpResult && dewarpResult.method_used !== 'none' && dewarpResult.method_used !== 'manual'
|
||||
const detections = dewarpResult?.detections || []
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Results */}
|
||||
{/* Summary banner */}
|
||||
{dewarpResult && (
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div className="flex flex-wrap items-center gap-3 text-sm">
|
||||
<div className={`rounded-lg border p-4 ${
|
||||
wasRejected
|
||||
? 'bg-amber-50 border-amber-200 dark:bg-amber-900/20 dark:border-amber-700'
|
||||
: wasApplied
|
||||
? 'bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-700'
|
||||
: 'bg-white border-gray-200 dark:bg-gray-800 dark:border-gray-700'
|
||||
}`}>
|
||||
{/* Status line */}
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className={`text-lg ${wasRejected ? '' : wasApplied ? '' : ''}`}>
|
||||
{wasRejected ? '\u26A0\uFE0F' : wasApplied ? '\u2705' : '\u2796'}
|
||||
</span>
|
||||
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
{wasRejected
|
||||
? 'Quality Gate: Korrektur verworfen (Projektion nicht verbessert)'
|
||||
: wasApplied
|
||||
? `Korrektur angewendet: ${dewarpResult.shear_degrees.toFixed(2)}°`
|
||||
: dewarpResult.method_used === 'manual'
|
||||
? `Manuelle Korrektur: ${dewarpResult.shear_degrees.toFixed(2)}°`
|
||||
: 'Keine Korrektur noetig'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Key metrics */}
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-500">Scherung:</span>{' '}
|
||||
<span className="font-mono font-medium">{dewarpResult.shear_degrees}°</span>
|
||||
<span className="font-mono font-medium">{dewarpResult.shear_degrees.toFixed(2)}°</span>
|
||||
</div>
|
||||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600" />
|
||||
<div>
|
||||
<span className="text-gray-500">Methode:</span>{' '}
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-teal-100 text-teal-700 dark:bg-teal-900/40 dark:text-teal-300">
|
||||
{METHOD_LABELS[dewarpResult.method_used] || dewarpResult.method_used}
|
||||
{dewarpResult.method_used.includes('+')
|
||||
? `Ensemble (${dewarpResult.method_used.split('+').map(m => METHOD_LABELS[m] || m).join(' + ')})`
|
||||
: METHOD_LABELS[dewarpResult.method_used] || dewarpResult.method_used}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-4 w-px bg-gray-300 dark:bg-gray-600" />
|
||||
<div>
|
||||
<span className="text-gray-500">Konfidenz:</span>{' '}
|
||||
<span className="font-mono">{Math.round(dewarpResult.confidence * 100)}%</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-gray-500">Konfidenz:</span>
|
||||
<ConfBar value={dewarpResult.confidence} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Toggle */}
|
||||
<div className="flex gap-3 mt-3">
|
||||
{/* Toggles row */}
|
||||
<div className="flex gap-2 mt-3">
|
||||
<button
|
||||
onClick={onToggleGrid}
|
||||
className={`text-xs px-3 py-1 rounded-full border transition-colors ${
|
||||
@@ -91,9 +145,63 @@ export function DewarpControls({
|
||||
: 'border-gray-300 text-gray-500 dark:border-gray-600 dark:text-gray-400'
|
||||
}`}
|
||||
>
|
||||
Raster anzeigen
|
||||
Raster
|
||||
</button>
|
||||
{detections.length > 0 && (
|
||||
<button
|
||||
onClick={() => setShowDetails(v => !v)}
|
||||
className={`text-xs px-3 py-1 rounded-full border transition-colors ${
|
||||
showDetails
|
||||
? 'bg-blue-100 border-blue-300 text-blue-700 dark:bg-blue-900/40 dark:border-blue-600 dark:text-blue-300'
|
||||
: 'border-gray-300 text-gray-500 dark:border-gray-600 dark:text-gray-400'
|
||||
}`}
|
||||
>
|
||||
Details ({detections.length} Methoden)
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Detailed detections */}
|
||||
{showDetails && detections.length > 0 && (
|
||||
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="text-xs text-gray-500 mb-2">Einzelne Detektoren:</div>
|
||||
<div className="space-y-1.5">
|
||||
{detections.map((d: DewarpDetection) => {
|
||||
const isUsed = dewarpResult.method_used.includes(d.method)
|
||||
const aboveThreshold = d.confidence >= 0.5
|
||||
return (
|
||||
<div
|
||||
key={d.method}
|
||||
className={`flex items-center gap-3 text-xs px-2 py-1.5 rounded ${
|
||||
isUsed
|
||||
? 'bg-teal-50 dark:bg-teal-900/20'
|
||||
: 'bg-gray-50 dark:bg-gray-800'
|
||||
}`}
|
||||
>
|
||||
<span className="w-4 text-center">
|
||||
{isUsed ? '\u2713' : aboveThreshold ? '\u2012' : '\u2717'}
|
||||
</span>
|
||||
<span className={`w-40 ${isUsed ? 'font-medium text-gray-800 dark:text-gray-200' : 'text-gray-500'}`}>
|
||||
{METHOD_LABELS[d.method] || d.method}
|
||||
</span>
|
||||
<span className="font-mono w-16 text-right">
|
||||
{d.shear_degrees.toFixed(2)}°
|
||||
</span>
|
||||
<ConfBar value={d.confidence} />
|
||||
{!aboveThreshold && (
|
||||
<span className="text-gray-400 ml-1">(unter Schwelle)</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{wasRejected && (
|
||||
<div className="mt-2 text-xs text-amber-600 dark:text-amber-400">
|
||||
Die Korrektur wurde verworfen, weil die horizontale Projektions-Varianz nach Anwendung nicht besser war als vorher.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user