import { useRef, useState } from 'react'; const API_URL = import.meta.env.VITE_API_URL || ''; const FIVE_GB = 5 * 1024 * 1024 * 1024; const TEN_GB = 10 * 1024 * 1024 * 1024; function formatBytes(bytes) { if (!Number.isFinite(bytes) || bytes === 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1); return `${(bytes / 1024 ** index).toFixed(index === 0 ? 0 : 2)} ${units[index]}`; } function DownloadIcon() { return ( ); } function FileIcon() { return ( ); } export default function App() { const [selectedFile, setSelectedFile] = useState(null); const [isDragging, setIsDragging] = useState(false); const [isConverting, setIsConverting] = useState(false); const [status, setStatus] = useState('Choose a Parquet file to begin.'); const [error, setError] = useState(''); const [downloadUrl, setDownloadUrl] = useState(''); const [downloadName, setDownloadName] = useState('output.csv'); const fileInputRef = useRef(null); const downloadLinkRef = useRef(null); const chooseFile = (file) => { setError(''); setDownloadUrl(''); if (!file) return; if (!file.name.toLowerCase().endsWith('.parquet')) { setSelectedFile(null); setStatus('Choose a Parquet file to begin.'); setError('That file type is not supported. Please choose a .parquet file.'); return; } if (file.size > TEN_GB) { setSelectedFile(null); setStatus('Choose a Parquet file to begin.'); setError('This file is larger than 10GB. Please choose a smaller Parquet file.'); return; } setSelectedFile(file); setStatus('Ready to convert.'); if (file.size > FIVE_GB) { setStatus('Ready to convert. Large files over 5GB may take several minutes.'); } }; const handleInputChange = (event) => { chooseFile(event.target.files?.[0]); }; const handleDrop = (event) => { event.preventDefault(); setIsDragging(false); chooseFile(event.dataTransfer.files?.[0]); }; const handleDragOver = (event) => { event.preventDefault(); setIsDragging(true); }; const handleDragLeave = (event) => { event.preventDefault(); setIsDragging(false); }; const reset = () => { if (downloadUrl) { URL.revokeObjectURL(downloadUrl); } setSelectedFile(null); setIsDragging(false); setIsConverting(false); setStatus('Choose a Parquet file to begin.'); setError(''); setDownloadUrl(''); setDownloadName('output.csv'); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const convertFile = async () => { if (!selectedFile) return; setIsConverting(true); setError(''); setDownloadUrl(''); setStatus('Uploading and converting your file...'); const formData = new FormData(); formData.append('file', selectedFile); try { const response = await fetch(`${API_URL}/convert`, { method: 'POST', body: formData, }); if (!response.ok) { let message = 'Conversion failed. Please try again.'; try { const data = await response.json(); message = data.detail || message; } catch { message = response.statusText || message; } throw new Error(message); } setStatus('Preparing your CSV download...'); const blob = await response.blob(); const url = URL.createObjectURL(blob); const csvName = selectedFile.name.replace(/\.parquet$/i, '.csv') || 'output.csv'; setDownloadUrl(url); setDownloadName(csvName); setStatus('Conversion complete. Your download should start automatically.'); window.setTimeout(() => { if (downloadLinkRef.current) { downloadLinkRef.current.click(); } }, 0); } catch (caughtError) { const message = caughtError instanceof Error ? caughtError.message : 'Network error. Please check the backend URL and try again.'; setError(message); setStatus('Conversion stopped.'); } finally { setIsConverting(false); } }; return (

DuckDB streaming conversion

Betty Mae Parquet → CSV Converter

Upload a Parquet file and convert it to CSV from your browser. Built for very large datasets without loading the full file into memory.

); }