| import { useState, useRef } from 'react'; | |
| interface FileUploadProps { | |
| onUploadComplete: (path: string) => void; | |
| } | |
| export default function FileUpload({ onUploadComplete }: FileUploadProps) { | |
| const [isUploading, setIsUploading] = useState(false); | |
| const [dragActive, setDragActive] = useState(false); | |
| const inputRef = useRef<HTMLInputElement>(null); | |
| const handleFiles = async (files: FileList | null) => { | |
| if (!files || files.length === 0) return; | |
| const file = files[0]; | |
| setIsUploading(true); | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| try { | |
| const response = await fetch('/api/v1/dataset/upload', { | |
| method: 'POST', | |
| body: formData, | |
| }); | |
| if (!response.ok) { | |
| const text = await response.text(); | |
| let detail = 'Upload failed'; | |
| try { | |
| const json = JSON.parse(text); | |
| detail = json.detail || detail; | |
| } catch { | |
| detail = `Server error (${response.status}): ${text.slice(0, 100)}`; | |
| } | |
| throw new Error(detail); | |
| } | |
| const data = await response.json(); | |
| const uploadPath = `data/uploaded_datasets/${file.name.replace('.zip', '')}`; | |
| onUploadComplete(uploadPath); | |
| } catch (error: any) { | |
| console.error(error); | |
| alert(`Error: ${error.message}`); | |
| } finally { | |
| setIsUploading(false); | |
| } | |
| }; | |
| return ( | |
| <div | |
| className={`relative border-2 border-dashed rounded-xl p-8 text-center transition-all ${dragActive ? 'border-indigo-500 bg-indigo-500/10' : 'border-gray-700 hover:border-indigo-400/50' | |
| }`} | |
| onDragEnter={(e) => { e.preventDefault(); e.stopPropagation(); setDragActive(true); }} | |
| onDragLeave={(e) => { e.preventDefault(); e.stopPropagation(); setDragActive(false); }} | |
| onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }} | |
| onDrop={(e) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| setDragActive(false); | |
| handleFiles(e.dataTransfer.files); | |
| }} | |
| > | |
| <input | |
| ref={inputRef} | |
| type="file" | |
| className="hidden" | |
| accept=".zip,.mp4,.mov" | |
| onChange={(e) => handleFiles(e.target.files)} | |
| /> | |
| {isUploading ? ( | |
| <div className="flex flex-col items-center"> | |
| <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-500 mb-2"></div> | |
| <p className="text-gray-400">Uploading...</p> | |
| </div> | |
| ) : ( | |
| <div onClick={() => inputRef.current?.click()} className="cursor-pointer"> | |
| <svg className="w-10 h-10 text-gray-500 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> | |
| </svg> | |
| <p className="text-gray-300 font-medium">Click to upload ARKit Video / Zip</p> | |
| <p className="text-gray-500 text-sm mt-1">or drag and drop here</p> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |