'use client'; import * as React from 'react'; import UploadedFileCard from './UploadedFileCard'; import FileTypeIcon from './FileTypeIcon'; import { KBFile } from '@/lib/kb-data'; interface FileUploadPanelProps { files: KBFile[]; onUpload: (file: File) => Promise; onDelete: (id: string) => void; deletingIds: Set; } type UploadStatus = 'uploading' | 'done' | 'error'; interface PendingUpload { id: string; name: string; file: File; progress: number; status: UploadStatus; } let uploadSeq = 0; export default function FileUploadPanel({ files, onUpload, onDelete, deletingIds }: FileUploadPanelProps) { const [isDragOver, setIsDragOver] = React.useState(false); const [uploads, setUploads] = React.useState([]); const fileInputRef = React.useRef(null); const dismissUpload = (id: string) => setUploads((prev) => prev.filter((u) => u.id !== id)); // Upload a real file: parsing + embedding happens server-side, so we show an // indeterminate progress bar that creeps toward 90% until the POST resolves. const startUpload = (file: File) => { const fileId = `up-${uploadSeq++}`; setUploads((prev) => [ ...prev, { id: fileId, name: file.name, file, progress: 8, status: 'uploading' }, ]); let prog = 8; const interval = setInterval(() => { prog = Math.min(90, prog + Math.floor(Math.random() * 8) + 4); setUploads((prev) => prev.map((u) => (u.id === fileId && u.status === 'uploading' ? { ...u, progress: prog } : u)) ); }, 300); const succeed = () => { clearInterval(interval); // Don't show a separate "file uploaded" card. The file is already added // to the knowledge-base list below, so just remove the progress toast and // let the file card itself signal completion. dismissUpload(fileId); }; const fail = () => { clearInterval(interval); setUploads((prev) => prev.map((u) => (u.id === fileId ? { ...u, status: 'error' } : u)) ); }; Promise.resolve(onUpload(file)).then(succeed, fail); }; const retryUpload = (u: PendingUpload) => { dismissUpload(u.id); startUpload(u.file); }; const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragOver(true); }; const handleDragLeave = (e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { const droppedFiles = Array.from(e.dataTransfer.files); const eligibleFiles = droppedFiles.filter(f => { const ext = f.name.split('.').pop()?.toLowerCase() || ''; return ['pdf', 'docx', 'doc', 'xlsx', 'xls', 'csv'].includes(ext); }); if (eligibleFiles.length > 0) { eligibleFiles.forEach((f) => startUpload(f)); } else { alert('Please drop accepted file types (PDF, DOCX, XLSX, XLS, CSV).'); } } }; const handleFileChange = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const selected = Array.from(e.target.files); selected.forEach((f) => startUpload(f)); // Reset input value to allow uploading same file again if deleted if (fileInputRef.current) fileInputRef.current.value = ''; } }; const triggerBrowse = () => { fileInputRef.current?.click(); }; return (
{/* Panel Headers */}

Upload documents

Drag and drop PDFs, Word documents, and spreadsheets into your knowledge base.

{/* Hidden File Input */} {/* Drop Zone Box */}
{/* Document Icons – fanned-out brand logos */}

Drop your files here

PDF, DOCX, XLSX, XLS, or CSV up to 25MB

{/* Files List Heading */}

Knowledge Base (Files)

{/* Upload status toasts (uploading / done / error) */} {uploads.length > 0 && (
{uploads.map((u) => ( dismissUpload(u.id)} onRetry={() => retryUpload(u)} /> ))}
)} {/* Stable List of Files */} {files.length === 0 && uploads.length === 0 ? (
No documents uploaded yet. Place your documents here to augment AI knowledge.
) : (
{files.map((file) => ( ))}
)}
{/* Bottom informational footline */}

Documents are indexed for AI search after upload.

); } const TOAST_CONFIG: Record< UploadStatus, { glow: string; ring: string; bar: string; title: string; desc: string; icon: React.ReactNode } > = { uploading: { glow: 'radial-gradient(circle, rgba(99,102,241,0.6) 0%, rgba(59,130,246,0.25) 45%, transparent 72%)', ring: 'border-indigo-400/60 text-indigo-300', bar: 'from-indigo-400 to-violet-400', title: 'Just a minute…', desc: 'Your file is uploading right now. Hang tight while we finish.', icon: ( ), }, done: { glow: 'radial-gradient(circle, rgba(16,185,129,0.55) 0%, rgba(20,184,166,0.2) 45%, transparent 72%)', ring: 'border-emerald-400/60 text-emerald-300', bar: 'from-emerald-400 to-teal-400', title: 'Your file was uploaded!', desc: 'Added to your knowledge base and queued for AI indexing.', icon: ( ), }, error: { glow: 'radial-gradient(circle, rgba(244,63,94,0.55) 0%, rgba(225,29,72,0.2) 45%, transparent 72%)', ring: 'border-rose-400/60 text-rose-300', bar: 'from-rose-400 to-pink-400', title: 'We are so sorry!', desc: "There was an error and your file couldn't be uploaded. Try again?", icon: ( ), }, }; interface UploadToastProps { upload: PendingUpload; onCancel: () => void; onRetry: () => void; } function UploadToast({ upload, onCancel, onRetry }: UploadToastProps) { const { name, progress, status } = upload; const cfg = TOAST_CONFIG[status]; const btn = 'inline-flex items-center justify-center h-8 px-4 rounded-lg text-xs font-semibold bg-white/[0.06] border border-white/10 text-white hover:bg-white/10 transition-colors duration-150 cursor-pointer'; return (
{/* Soft corner glow tinted by status */}
{/* Status icon ring */}
{cfg.icon}

{cfg.title}

{cfg.desc}

{name}

{status === 'uploading' && (
{progress}%
)} {status === 'error' && (
)} {status === 'done' && (
)}
); }