'use client'; import { useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Loader2, TrendingUp, ChevronRight } from 'lucide-react'; import { PageHeader } from '@/components/page-header'; import { PoweredBy } from '@/components/powered-by'; import { sessionService, videoService } from '@/services/api'; import { SessionContext } from '@/types'; import { storeVideoFile } from '@/lib/video-storage'; import { ProjectSelectionSection } from '@/components/project-selection-section'; import { Reveal } from '@/components/ui/reveal'; import { cn } from '@/lib/utils'; const DETECTION_METHODS = [ { value: 'yolo', label: 'Road Defect Detection' }, // { value: 'yolo_vl', label: 'YOLO with Vision-Language Model' }, // { value: 'sam3', label: 'OpenAI SAM 3 Segmentation Model' }, { value: 'yoloe', label: 'YOLOE Open-Vocabulary Detection' }, { value: 'yoloe_trained_vl', label: 'YOLOE With Vision Language Model' }, { value: 'culvert_detection', label: 'Culvert Detection' }, { value: 'combined', label: 'Road Defect Detection with vl' }, // { value: 'gemini_video', label: 'Gemini AI Analysis' }, ] as const; export default function UploadPage() { const router = useRouter(); const [session, setSession] = useState(null); const [isLoading, setIsLoading] = useState(true); // Form states const [file, setFile] = useState(null); const [jsonFile, setJsonFile] = useState(null); const [selectMethod, setSelectMethod] = useState('combined'); // Upload states const [uploading, setUploading] = useState(false); const [error, setError] = useState(null); // Load session on mount useEffect(() => { // We don't load session on mount for the upload page to ensure // project details are filled manually as requested. setIsLoading(false); }, []); const handleSelectionChange = useCallback((selectedSession: SessionContext | null) => { setSession(selectedSession); if (selectedSession) { sessionService.saveSession(selectedSession); } }, []); const handleUpload = async () => { if (!file) { setError('Please select a video file'); return; } if (!session) { setError('Please complete the project selection first'); return; } const formData = new FormData(); formData.append('file', file); formData.append('detection_mode', selectMethod); formData.append('chainage_id', session.chainageId as string); if (jsonFile) { formData.append('json_file', jsonFile); } setUploading(true); setError(null); try { const result = await videoService.uploadVideo(formData); // Store file locally if (file) { try { await storeVideoFile(result.video_id, file); } catch (err) { console.error('Failed to store video file:', err); } } sessionService.saveVideoData({ videoId: result.video_id }); router.push(`/upload/${result.video_id}`); } catch (err) { let errorMessage = 'Upload failed'; if (err instanceof TypeError && err.message === 'Failed to fetch') { errorMessage = 'Cannot connect to server. Please check if backend is running.'; } else if (err instanceof Error) { errorMessage = err.message; } setError(errorMessage); setUploading(false); } }; if (isLoading) { return (
); } const isSessionComplete = !!(session && sessionService.isSessionValid(session)); const isFormValid = isSessionComplete && file; return (
{/* Header */}
{/* Module 1: Project Selection */} 1. Project Details Select the target project, package, and segment. {/* Module 2: Upload Data */} 2. Upload Video Data Provide the video file and optional GPS telemetry for processing.
{/* Video File Input */}
{ setFile(e.target.files?.[0] || null); setError(null); }} disabled={uploading || !isSessionComplete} className="h-12 bg-muted/30 border-dashed border-2 group-hover:border-primary/50 transition-colors cursor-pointer file:mr-4 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:bg-primary file:text-primary-foreground file:font-semibold file:text-xs" />
{/* JSON File Input */}
{ setJsonFile(e.target.files?.[0] || null); setError(null); }} disabled={uploading || !isSessionComplete} className="h-11 bg-muted/20 file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:bg-primary/10 file:text-primary file:font-medium file:text-xs" />
{/* Select Method */}
{/* Error Display */} {error && (
{error}
)} {/* Primary Action Button */}
); }