Spaces:
Running
Running
File size: 5,975 Bytes
2ecc4a7 bf7338b 2ecc4a7 bf7338b 2ecc4a7 bf7338b 2ecc4a7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | import React, { useState } from 'react';
import { Upload, FileText, CheckCircle, Loader2, Settings } from 'lucide-react';
import api from '../api/client';
import { usePipelineStore } from '../store/pipelineStore';
const STRATEGIES = [
{ value: 'fixed', label: 'Fixed Size' },
{ value: 'token', label: 'Token Based' },
{ value: 'sentence', label: 'Sentence Split' },
{ value: 'paragraph', label: 'Paragraph' },
{ value: 'recursive', label: 'Recursive' },
{ value: 'sliding_window', label: 'Sliding Window' },
];
export default function FileUpload() {
const [file, setFile] = useState(null);
const { setIngesting, isIngesting, setActiveJob, clearSteps } = usePipelineStore();
const [status, setStatus] = useState('idle'); // idle | loading | success | error
const [errorMsg, setErrorMsg] = useState('');
// Chunking controls
const [strategy, setStrategy] = useState('fixed');
const [chunkSize, setChunkSize] = useState(512);
const [overlap, setOverlap] = useState(64);
const handleUpload = async () => {
if (!file) return;
setIngesting(true);
setStatus('loading');
setErrorMsg('');
const formData = new FormData();
formData.append('file', file);
formData.append('chunk_size', chunkSize);
formData.append('overlap', overlap);
formData.append('strategy', strategy);
try {
clearSteps();
const { data } = await api.post('/ingest/upload', formData);
setActiveJob(data.job_id);
setStatus('success');
setTimeout(() => {
setStatus('idle');
setFile(null);
}, 3000);
} catch (error) {
console.error('Upload failed', error);
setErrorMsg(error?.response?.data?.detail || 'Upload failed');
setStatus('error');
setTimeout(() => setStatus('idle'), 5000);
} finally {
setIngesting(false);
}
};
return (
<div className="card space-y-4">
<h2 className="text-xl font-bold flex items-center gap-2">
<Upload className="w-5 h-5 text-primary-500" />
Document Ingestion
</h2>
{/* File Drop Zone */}
<div className="border-2 border-dashed border-surface-700 rounded-xl p-6 flex flex-col items-center justify-center space-y-3 hover:border-primary-500 transition-colors cursor-pointer group">
<input
type="file"
id="file-upload"
className="hidden"
accept=".txt,.pdf,.md,.docx,.csv,.json,.png,.jpg,.jpeg"
onChange={(e) => setFile(e.target.files[0])}
/>
<label htmlFor="file-upload" className="flex flex-col items-center cursor-pointer">
{file ? (
<FileText className="w-10 h-10 text-primary-500" />
) : (
<Upload className="w-10 h-10 text-gray-500 group-hover:text-primary-400 transition-colors" />
)}
<span className="mt-2 text-sm text-gray-400">
{file ? file.name : 'Click to select a file'}
</span>
<span className="text-[10px] text-gray-600 mt-1">
PDF, TXT, MD, DOCX, CSV, JSON, PNG, JPG
</span>
</label>
</div>
{/* Chunking Controls */}
<div className="space-y-3 pt-2 border-t border-surface-700">
<div className="flex items-center gap-2 text-xs font-bold text-gray-500 uppercase tracking-widest">
<Settings className="w-3 h-3" />
Chunking Configuration
</div>
{/* Strategy Selector */}
<div>
<label className="text-xs text-gray-500 mb-1 block">Strategy</label>
<select
value={strategy}
onChange={(e) => setStrategy(e.target.value)}
className="w-full input-field text-sm"
>
{STRATEGIES.map((s) => (
<option key={s.value} value={s.value}>{s.label}</option>
))}
</select>
</div>
{/* Size & Overlap */}
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-xs text-gray-500 mb-1 block">Chunk Size</label>
<input
type="number"
value={chunkSize}
onChange={(e) => setChunkSize(parseInt(e.target.value) || 256)}
min={64}
max={4096}
className="w-full input-field text-sm"
/>
</div>
<div>
<label className="text-xs text-gray-500 mb-1 block">Overlap</label>
<input
type="number"
value={overlap}
onChange={(e) => setOverlap(parseInt(e.target.value) || 0)}
min={0}
max={512}
className="w-full input-field text-sm"
/>
</div>
</div>
{/* Live Preview */}
<div className="bg-surface-900 rounded-lg p-3 text-[11px] font-mono text-gray-500 space-y-1">
<div>Strategy: <span className="text-primary-400">{strategy}</span></div>
<div>Size: <span className="text-accent-400">{chunkSize}</span> chars | Overlap: <span className="text-accent-400">{overlap}</span> chars</div>
</div>
</div>
{/* Error Message */}
{status === 'error' && (
<div className="text-red-400 text-xs bg-red-500/10 border border-red-500/20 rounded-lg p-2">
{errorMsg}
</div>
)}
{/* Upload Button */}
<button
onClick={handleUpload}
disabled={!file || isIngesting}
className="w-full btn-primary flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
{status === 'loading' ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : status === 'success' ? (
<CheckCircle className="w-5 h-5" />
) : (
<Upload className="w-5 h-5" />
)}
{status === 'loading' ? 'Processing...' : status === 'success' ? 'Uploaded!' : 'Upload & Start Pipeline'}
</button>
</div>
);
}
|