import { useState, useCallback } from 'react' import { UploadZone } from './UploadZone' import { FileList } from './FileList' import type { UploadedFile } from '../types' interface UploadPanelProps { sessionId: string | null } const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10 MB const MAX_FILES = 5 export function UploadPanel({ sessionId }: UploadPanelProps) { const [files, setFiles] = useState([]) const uploadFile = useCallback( (file: File) => { if (!sessionId) return // Client-side file size validation if (file.size > MAX_FILE_SIZE) { const sizeMB = (file.size / (1024 * 1024)).toFixed(1) const entry: UploadedFile = { name: file.name, size: file.size, type: file.name.split('.').pop() || 'unknown', documents: 0, status: 'error', progress: 0, error: `File too large (${sizeMB} MB). Maximum is ${MAX_FILE_SIZE / (1024 * 1024)} MB.`, } setFiles((prev) => [...prev, entry]) return } const entry: UploadedFile = { name: file.name, size: file.size, type: file.name.split('.').pop() || 'unknown', documents: 0, status: 'uploading', progress: 0, } setFiles((prev) => [...prev, entry]) const fileIndex = files.length const formData = new FormData() formData.append('file', file) formData.append('session_id', sessionId) const xhr = new XMLHttpRequest() xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const pct = Math.round((e.loaded / e.total) * 100) setFiles((prev) => prev.map((f, i) => i === fileIndex ? { ...f, progress: pct, status: pct >= 100 ? 'processing' : 'uploading' } : f ) ) } }) xhr.addEventListener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { const data = JSON.parse(xhr.responseText) setFiles((prev) => prev.map((f, i) => i === fileIndex ? { ...f, status: 'done', progress: 100, documents: data.documents_loaded } : f ) ) } else { let errorMsg = 'Upload failed' try { const data = JSON.parse(xhr.responseText) errorMsg = data.detail || errorMsg } catch { /* ignore */ } setFiles((prev) => prev.map((f, i) => i === fileIndex ? { ...f, status: 'error', error: errorMsg } : f ) ) } }) xhr.addEventListener('error', () => { setFiles((prev) => prev.map((f, i) => i === fileIndex ? { ...f, status: 'error', error: 'Network error' } : f ) ) }) xhr.open('POST', '/api/upload') xhr.send(formData) }, [sessionId, files.length] ) const handleFilesSelected = useCallback( (fileList: FileList) => { const currentCount = files.filter((f) => f.status !== 'error').length const incoming = Array.from(fileList) if (currentCount + incoming.length > MAX_FILES) { const allowed = MAX_FILES - currentCount if (allowed <= 0) { // Add error entries for all rejected files const errorEntries: UploadedFile[] = incoming.map((file) => ({ name: file.name, size: file.size, type: file.name.split('.').pop() || 'unknown', documents: 0, status: 'error' as const, progress: 0, error: `Maximum ${MAX_FILES} files per session reached.`, })) setFiles((prev) => [...prev, ...errorEntries]) return } // Upload only what's allowed, error the rest incoming.slice(0, allowed).forEach(uploadFile) incoming.slice(allowed).forEach((file) => { const entry: UploadedFile = { name: file.name, size: file.size, type: file.name.split('.').pop() || 'unknown', documents: 0, status: 'error', progress: 0, error: `Maximum ${MAX_FILES} files per session. This file was not uploaded.`, } setFiles((prev) => [...prev, entry]) }) return } incoming.forEach(uploadFile) }, [uploadFile, files] ) const hasFiles = files.length > 0 const readyCount = files.filter((f) => f.status === 'done').length const activeCount = files.filter((f) => f.status !== 'error').length return ( ) }