import { useState, useEffect, useRef, useCallback } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { useAuth } from '@clerk/clerk-react' import { API_BASE } from '../config.js' function DocumentRow({ doc, index, onDelete }) { const [deleting, setDeleting] = useState(false) const handleDelete = async (e) => { e.stopPropagation() if (!confirm(`Delete "${doc.name}" and all its indexed chunks?`)) return setDeleting(true) await onDelete(doc.name) setDeleting(false) } return (
{doc.name}
{doc.chunks} chunk{doc.chunks !== 1 ? 's' : ''} {deleting ? ( ) : ( )}
) } function UploadResultBadge({ result }) { const styles = { ok: 'bg-emerald-500/15 border-emerald-500/30 text-emerald-400', queued: 'bg-accent/10 border-accent/25 text-accent/80', warning: 'bg-yellow-500/15 border-yellow-500/30 text-yellow-400', error: 'bg-red-500/15 border-red-500/30 text-red-400', skipped: 'bg-white/5 border-white/10 text-muted', } const icons = { ok: , queued: , warning: <>, error: <>, skipped: <>, } const style = styles[result.status] || styles.skipped return ( {icons[result.status]}
{result.name} {result.status === 'ok' && {result.chunks} chunk{result.chunks !== 1 ? 's' : ''} from {result.pages} page{result.pages !== 1 ? 's' : ''}} {result.message && {result.message}}
) } export default function UploadPanel({ onUploaded }) { const { getToken } = useAuth() const [isOpen, setIsOpen] = useState(false) const [isDragOver, setIsDragOver] = useState(false) const [pendingFiles, setPendingFiles] = useState([]) const [uploadStatus, setUploadStatus] = useState('idle') const [uploadResults, setUploadResults] = useState([]) const [documents, setDocuments] = useState([]) const [totalChunks, setTotalChunks] = useState(0) const [loadingDocs, setLoadingDocs] = useState(false) const fileInputRef = useRef(null) const authHeaders = useCallback(async () => { const token = await getToken() return token ? { Authorization: `Bearer ${token}` } : {} }, [getToken]) const fetchDocuments = useCallback(async () => { setLoadingDocs(true) try { const res = await fetch(`${API_BASE}/documents`, { headers: await authHeaders() }) const data = await res.json() setDocuments(data.documents || []) setTotalChunks(data.total_chunks || 0) } catch { // backend not running — silently ignore } finally { setLoadingDocs(false) } }, [authHeaders]) useEffect(() => { if (isOpen) fetchDocuments() }, [isOpen, fetchDocuments]) const addFiles = useCallback((fileList) => { const pdfs = Array.from(fileList).filter(f => f.name.toLowerCase().endsWith('.pdf')) if (!pdfs.length) return setPendingFiles(prev => { const existing = new Set(prev.map(f => f.name)) return [...prev, ...pdfs.filter(f => !existing.has(f.name))] }) }, []) const handleDragOver = (e) => { e.preventDefault(); setIsDragOver(true) } const handleDragLeave = (e) => { if (!e.currentTarget.contains(e.relatedTarget)) setIsDragOver(false) } const handleDrop = (e) => { e.preventDefault(); setIsDragOver(false); addFiles(e.dataTransfer.files) } const handleFileInput = (e) => { addFiles(e.target.files); e.target.value = '' } const removeFile = (name) => setPendingFiles(prev => prev.filter(f => f.name !== name)) const handleUpload = async () => { if (!pendingFiles.length || uploadStatus === 'uploading') return setUploadStatus('uploading') setUploadResults([]) const formData = new FormData() pendingFiles.forEach(f => formData.append('files', f)) try { const res = await fetch(`${API_BASE}/upload`, { method: 'POST', headers: await authHeaders(), body: formData, }) if (!res.ok) { let msg = `Server error ${res.status}` try { const e = await res.json(); if (e.detail) msg = `${res.status}: ${e.detail}` } catch {} throw new Error(msg) } const data = await res.json() const results = data.results || [] setUploadResults(results) setTotalChunks(data.total_chunks || totalChunks) setPendingFiles([]) setUploadStatus('done') fetchDocuments() onUploaded?.() // If any files are queued for background indexing, poll until they appear if (results.some(r => r.status === 'queued')) { let attempts = 0 const poll = setInterval(async () => { attempts++ await fetchDocuments() if (attempts >= 12) clearInterval(poll) // stop after 2 min }, 10000) // every 10 s } } catch (err) { setUploadResults([{ name: 'Upload failed', status: 'error', message: err.message }]) setUploadStatus('error') } } const handleDelete = async (name) => { try { const res = await fetch(`${API_BASE}/document/${encodeURIComponent(name)}`, { method: 'DELETE', headers: await authHeaders(), }) if (!res.ok) throw new Error(`Server error ${res.status}`) await fetchDocuments() onUploaded?.() } catch (err) { alert(`Delete failed: ${err.message}`) } } const successCount = uploadResults.filter(r => r.status === 'ok').length return (
{isOpen && (
fileInputRef.current?.click()} className="relative rounded-xl p-6 flex flex-col items-center gap-3 cursor-pointer select-none" style={{ border: '1.5px dashed rgba(99,102,241,0.3)' }} >

{isDragOver ? 'Drop PDFs here' : 'Drag & drop PDFs here'}

or click to browse · PDF only

{pendingFiles.length > 0 && (

Ready to upload ({pendingFiles.length})

{pendingFiles.map(file => (
{file.name} {(file.size / 1024).toFixed(0)} KB
))} {uploadStatus === 'uploading' ? (<>Uploading & indexing…) : (<>Upload {pendingFiles.length} file{pendingFiles.length !== 1 ? 's' : ''})}
)}
{uploadResults.length > 0 && ( {successCount > 0 &&

{successCount} file{successCount !== 1 ? 's' : ''} indexed successfully

} {uploadResults.map((r, i) => )}
)}

Indexed Library

{documents.length === 0 && !loadingDocs &&

No documents indexed yet

}
{documents.map((doc, i) => )}
{documents.length > 0 &&

{documents.length} documents · {totalChunks} total chunks

}
)}
) }