[split-required] Split 500-1000 LOC files across all services
backend-lehrer (5 files): - alerts_agent/db/repository.py (992 → 5), abitur_docs_api.py (956 → 3) - teacher_dashboard_api.py (951 → 3), services/pdf_service.py (916 → 3) - mail/mail_db.py (987 → 6) klausur-service (5 files): - legal_templates_ingestion.py (942 → 3), ocr_pipeline_postprocess.py (929 → 4) - ocr_pipeline_words.py (876 → 3), ocr_pipeline_ocr_merge.py (616 → 2) - KorrekturPage.tsx (956 → 6) website (5 pages): - mail (985 → 9), edu-search (958 → 8), mac-mini (950 → 7) - ocr-labeling (946 → 7), audit-workspace (871 → 4) studio-v2 (5 files + 1 deleted): - page.tsx (946 → 5), MessagesContext.tsx (925 → 4) - korrektur (914 → 6), worksheet-cleanup (899 → 6) - useVocabWorksheet.ts (888 → 3) - Deleted dead page-original.tsx (934 LOC) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
113
website/app/admin/ocr-labeling/_components/ExportTab.tsx
Normal file
113
website/app/admin/ocr-labeling/_components/ExportTab.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import type { OCRSession, OCRStats } from '../types'
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086'
|
||||
|
||||
export default function ExportTab({
|
||||
sessions,
|
||||
selectedSession,
|
||||
setSelectedSession,
|
||||
stats,
|
||||
onError,
|
||||
}: {
|
||||
sessions: OCRSession[]
|
||||
selectedSession: string | null
|
||||
setSelectedSession: (id: string | null) => void
|
||||
stats: OCRStats | null
|
||||
onError: (msg: string) => void
|
||||
}) {
|
||||
const [exportFormat, setExportFormat] = useState<'generic' | 'trocr' | 'llama_vision'>('generic')
|
||||
const [exporting, setExporting] = useState(false)
|
||||
const [exportResult, setExportResult] = useState<any>(null)
|
||||
|
||||
const handleExport = async () => {
|
||||
setExporting(true)
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/v1/ocr-label/export`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
export_format: exportFormat,
|
||||
session_id: selectedSession,
|
||||
}),
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setExportResult(data)
|
||||
} else {
|
||||
onError('Export fehlgeschlagen')
|
||||
}
|
||||
} catch (err) {
|
||||
onError('Netzwerkfehler')
|
||||
} finally {
|
||||
setExporting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Training-Daten exportieren</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Export-Format</label>
|
||||
<select
|
||||
value={exportFormat}
|
||||
onChange={(e) => setExportFormat(e.target.value as typeof exportFormat)}
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||
>
|
||||
<option value="generic">Generic JSON</option>
|
||||
<option value="trocr">TrOCR Fine-Tuning</option>
|
||||
<option value="llama_vision">Llama Vision Fine-Tuning</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">Session (optional)</label>
|
||||
<select
|
||||
value={selectedSession || ''}
|
||||
onChange={(e) => setSelectedSession(e.target.value || null)}
|
||||
className="w-full px-3 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||
>
|
||||
<option value="">Alle Sessions</option>
|
||||
{sessions.map((session) => (
|
||||
<option key={session.id} value={session.id}>{session.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleExport}
|
||||
disabled={exporting || (stats?.exportable_items || 0) === 0}
|
||||
className="w-full px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 disabled:opacity-50"
|
||||
>
|
||||
{exporting ? 'Exportiere...' : `${stats?.exportable_items || 0} Samples exportieren`}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{exportResult && (
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<h3 className="text-lg font-semibold mb-4">Export-Ergebnis</h3>
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
|
||||
<p className="text-green-800">
|
||||
{exportResult.exported_count} Samples erfolgreich exportiert
|
||||
</p>
|
||||
<p className="text-sm text-green-600">
|
||||
Batch: {exportResult.batch_id}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-slate-50 p-4 rounded-lg overflow-auto max-h-64">
|
||||
<pre className="text-xs">{JSON.stringify(exportResult.samples?.slice(0, 3), null, 2)}</pre>
|
||||
{(exportResult.samples?.length || 0) > 3 && (
|
||||
<p className="text-slate-500 mt-2">... und {exportResult.samples.length - 3} weitere</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user