'use client' import { useState, useEffect } from 'react' import type { IngestionStatus, LiveProgress, IndexedStats, PendingFilesData, IngestionHistoryEntry } from '../types' import { IngestionHistory } from './IngestionHistory' const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086' interface IngestionTabProps { status: IngestionStatus | null onRefresh: () => void } function IngestionTab({ status, onRefresh }: IngestionTabProps) { const [starting, setStarting] = useState(false) const [liveProgress, setLiveProgress] = useState(null) const [indexedStats, setIndexedStats] = useState(null) const [pendingFiles, setPendingFiles] = useState(null) const [ingestionHistory, setIngestionHistory] = useState([]) const [showPending, setShowPending] = useState(false) useEffect(() => { const fetchStats = async () => { try { const res = await fetch(`${API_BASE}/api/v1/admin/nibis/stats`); if (res.ok) setIndexedStats(await res.json()) } catch (err) { console.error('Failed to fetch stats:', err) } } const fetchPending = async () => { try { const res = await fetch(`${API_BASE}/api/v1/admin/rag/files/pending`); if (res.ok) setPendingFiles(await res.json()) } catch (err) { console.error('Failed to fetch pending files:', err) } } const fetchHistory = async () => { try { const res = await fetch(`${API_BASE}/api/v1/admin/rag/ingestion/history?limit=20`); if (res.ok) { const data = await res.json(); setIngestionHistory(data.history || []) } } catch (err) { console.error('Failed to fetch history:', err) } } fetchStats(); fetchPending(); fetchHistory() }, []) useEffect(() => { let interval: NodeJS.Timeout | null = null const fetchProgress = async () => { try { const res = await fetch(`${API_BASE}/api/v1/admin/nibis/progress`) if (res.ok) { const data = await res.json() setLiveProgress(data) if (!data.running && data.phase === 'complete') { onRefresh() const statsRes = await fetch(`${API_BASE}/api/v1/admin/nibis/stats`) if (statsRes.ok) setIndexedStats(await statsRes.json()) } } } catch (err) { console.error('Failed to fetch progress:', err) } } fetchProgress() interval = setInterval(fetchProgress, 1500) return () => { if (interval) clearInterval(interval) } }, [onRefresh]) const startIngestion = async () => { setStarting(true) try { await fetch(`${API_BASE}/api/v1/admin/nibis/ingest`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ewh_only: true, incremental: true }), }) onRefresh() } catch (err) { console.error('Failed to start ingestion:', err) } finally { setStarting(false) } } const phaseLabels: Record = { idle: 'Bereit', extracting: 'Entpacke ZIP-Dateien...', discovering: 'Suche Dokumente...', indexing: 'Indexiere Dokumente...', complete: 'Abgeschlossen', } return (

Ingestion Status

Uebersicht ueber laufende und vergangene Indexierungsvorgaenge

{/* Live Progress */} {liveProgress?.running && (
{liveProgress.phase ? (phaseLabels[liveProgress.phase] || liveProgress.phase) : 'Laeuft...'} {(liveProgress.percent ?? 0).toFixed(1)}%
{liveProgress.current_filename && (

[{liveProgress.current_doc}/{liveProgress.total_docs}] {liveProgress.current_filename}

)}

Indexiert

{liveProgress.documents_indexed}

Uebersprungen

{liveProgress.documents_skipped}

Chunks

{liveProgress.chunks_created}

Fehler

0 ? 'text-red-600' : 'text-primary-900'}`}>{liveProgress.errors_count ?? 0}

)} {/* Indexed Data Overview */} {indexedStats?.indexed && (

Indexierte Daten (Gesamt)

Chunks gesamt

{(indexedStats.total_chunks ?? 0).toLocaleString()}

Jahre

{indexedStats.years?.length ?? 0}

{(indexedStats.years?.length ?? 0) > 0 ? `${Math.min(...indexedStats.years!)} - ${Math.max(...indexedStats.years!)}` : '-'}

Faecher

{indexedStats.subjects?.length || 0}

Status

Bereit

{indexedStats.years && indexedStats.years.length > 0 && (
{indexedStats.years.sort((a, b) => a - b).map((year) => ( {year} ))}
)}
)} {/* Last Run Status */} {!liveProgress?.running && (

Letzter Indexierungslauf

{liveProgress?.phase === 'complete' ? 'Abgeschlossen' : 'Bereit'}
{status && (

Zeitpunkt

{status.lastRun ? new Date(status.lastRun).toLocaleString('de-DE') : 'Noch nie'}

Neu indexiert

{status.documentsIndexed ?? '-'}

Neue Chunks

{status.chunksCreated ?? '-'}

Fehler

0 ? 'text-red-600' : 'text-slate-900'}`}>{status.errors.length}

)} {liveProgress && (liveProgress.documents_skipped ?? 0) > 0 && (

{liveProgress.documents_skipped} Dokumente uebersprungen (bereits indexiert)

)} {status?.errors && status.errors.length > 0 && (

Fehler

    {status.errors.slice(0, 5).map((error, i) =>
  • {error}
  • )} {status.errors.length > 5 &&
  • ... und {status.errors.length - 5} weitere
  • }
)}
)} {/* Pending Files Section */} {pendingFiles && (pendingFiles.pending_count ?? 0) > 0 && (

{pendingFiles.pending_count} Dateien warten auf Indexierung

{pendingFiles.indexed_count} von {pendingFiles.total_files} Dateien sind indexiert

{pendingFiles.by_year && Object.keys(pendingFiles.by_year).length > 0 && (
{Object.entries(pendingFiles.by_year).sort(([a], [b]) => Number(b) - Number(a)).map(([year, count]) => ( {year}: {count} Dateien ))}
)} {showPending && pendingFiles.pending_files && (
{pendingFiles.pending_files.map((file) => ( ))}
DateinameJahrFachNiveau
{file.filename}{file.year}{file.subject}{file.niveau}
{(pendingFiles.pending_count ?? 0) > 100 &&

Zeige 100 von {pendingFiles.pending_count} Dateien

}
)}
)}
) } export { IngestionTab }