'use client' import { useState, useCallback } from 'react' import type { Collection, UploadResult } from '../types' const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086' interface UploadTabProps { collections: Collection[] onUploadComplete: () => void } function UploadTab({ collections, onUploadComplete }: UploadTabProps) { const [isDragging, setIsDragging] = useState(false) const [files, setFiles] = useState([]) const [uploading, setUploading] = useState(false) const [uploadResults, setUploadResults] = useState([]) const [currentFile, setCurrentFile] = useState('') const [selectedCollection, setSelectedCollection] = useState('bp_nibis_eh') const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault() setIsDragging(false) const droppedFiles = Array.from(e.dataTransfer.files) const validFiles = droppedFiles.filter( f => f.name.endsWith('.zip') || f.name.endsWith('.pdf') ) setFiles(prev => [...prev, ...validFiles]) }, []) const handleFileSelect = useCallback((e: React.ChangeEvent) => { if (e.target.files) { const selectedFiles = Array.from(e.target.files) setFiles(prev => [...prev, ...selectedFiles]) } }, []) const removeFile = useCallback((index: number) => { setFiles(prev => prev.filter((_, i) => i !== index)) }, []) const handleUpload = async () => { if (files.length === 0) return setUploading(true) setUploadResults([]) const results: UploadResult[] = [] try { for (const file of files) { setCurrentFile(file.name) const formData = new FormData() formData.append('file', file) formData.append('collection', selectedCollection) try { const res = await fetch(`${API_BASE}/api/v1/admin/rag/upload`, { method: 'POST', body: formData, }) if (res.ok) { const data = await res.json() results.push({ filename: file.name, status: data.status, pdfs_extracted: data.pdfs_extracted, pdfs_skipped: data.pdfs_skipped, duplicates: data.duplicates || [], target_year: data.target_year, message: data.message, }) } else { results.push({ filename: file.name, status: 'error', pdfs_extracted: 0, pdfs_skipped: 0, duplicates: [], target_year: 0, message: 'Upload fehlgeschlagen', }) } } catch { results.push({ filename: file.name, status: 'error', pdfs_extracted: 0, pdfs_skipped: 0, duplicates: [], target_year: 0, message: 'Netzwerkfehler', }) } setUploadResults([...results]) } setFiles([]) setCurrentFile('') onUploadComplete() } catch (err) { console.error('Upload failed:', err) } finally { setUploading(false) } } return (

Dokumente hochladen

ZIP-Archive oder einzelne PDFs hochladen. ZIPs werden automatisch entpackt.

{/* Collection Selector */}
{collections.length === 0 && (

Keine Sammlungen vorhanden. Erstellen Sie zuerst eine Sammlung im Tab "Sammlungen".

)}
{/* Drop Zone */}
{ e.preventDefault(); setIsDragging(true) }} onDragLeave={() => setIsDragging(false)} onDrop={handleDrop} className={` border-2 border-dashed rounded-lg p-12 text-center transition-colors ${isDragging ? 'border-primary-500 bg-primary-50' : 'border-slate-300 hover:border-slate-400' } `} >

ZIP-Datei oder Ordner hierher ziehen

oder

Unterstützt: .zip, .pdf

{/* File Queue */} {files.length > 0 && (

Upload-Queue ({files.length})

{files.map((file, index) => (

{file.name}

{(file.size / 1024 / 1024).toFixed(2)} MB

))}
)} {/* Upload Results */} {uploadResults.length > 0 && (

Upload-Ergebnisse

{uploadResults.map((result, index) => (
{result.status === 'success' && ( )} {result.status === 'all_duplicates' && ( )} {result.status === 'partial_duplicates' && ( )} {result.status === 'error' && ( )} {result.filename}
Jahr {result.target_year}

{result.message}

{(result.pdfs_extracted ?? 0) > 0 && (

{result.pdfs_extracted} neue PDFs extrahiert

)} {(result.pdfs_skipped ?? 0) > 0 && (result.duplicates?.length ?? 0) > 0 && (
{result.pdfs_skipped} Duplikate anzeigen
    {result.duplicates?.map((dup, i) => (
  • {dup}
  • ))}
)}
))}
)}
) } // ============================================================================ // Ingestion Tab // ============================================================================ export { UploadTab }