import { useState, useEffect } from 'react'; import { jobs } from '../utils/api'; export default function JobSubmission({ onSuccess }) { const [formData, setFormData] = useState({ name: '', frame_start: 1, frame_end: 10, output_format: 'PNG', allow_parallel_runners: true, }); const [file, setFile] = useState(null); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(''); const [metadataStatus, setMetadataStatus] = useState(null); // 'extracting', 'completed', 'error' const [metadata, setMetadata] = useState(null); const [currentJobId, setCurrentJobId] = useState(null); // Poll for metadata after file upload useEffect(() => { if (!currentJobId || metadataStatus !== 'extracting') return; let pollCount = 0; const maxPolls = 30; // 60 seconds max (30 * 2 seconds) let timeoutId = null; const pollMetadata = async () => { pollCount++; // Stop polling after timeout if (pollCount > maxPolls) { setMetadataStatus('error'); // Cancel temp job on timeout try { await jobs.cancel(currentJobId); } catch (err) { // Ignore errors when canceling } return; } try { const metadata = await jobs.getMetadata(currentJobId); if (metadata) { setMetadata(metadata); setMetadataStatus('completed'); // Auto-populate form fields setFormData(prev => ({ ...prev, frame_start: metadata.frame_start || prev.frame_start, frame_end: metadata.frame_end || prev.frame_end, output_format: metadata.render_settings?.output_format || prev.output_format, })); } } catch (err) { // Metadata not ready yet, continue polling (only if 404/not found) if (err.message.includes('404') || err.message.includes('not found')) { // Continue polling via interval } else { setMetadataStatus('error'); } } }; const interval = setInterval(pollMetadata, 2000); // Set timeout to stop polling after 60 seconds timeoutId = setTimeout(() => { clearInterval(interval); if (metadataStatus === 'extracting') { setMetadataStatus('error'); // Cancel temp job on timeout jobs.cancel(currentJobId).catch(() => {}); } }, 60000); return () => { clearInterval(interval); if (timeoutId) clearTimeout(timeoutId); // Cleanup: cancel temp job if component unmounts during extraction if (currentJobId && metadataStatus === 'extracting') { jobs.cancel(currentJobId).catch(() => {}); } }; }, [currentJobId, metadataStatus]); const handleFileChange = async (e) => { const selectedFile = e.target.files[0]; if (!selectedFile) { setFile(null); return; } setFile(selectedFile); setMetadataStatus(null); setMetadata(null); setCurrentJobId(null); // If it's a blend file, create a temporary job to extract metadata if (selectedFile.name.toLowerCase().endsWith('.blend')) { try { // Create a temporary job for metadata extraction const tempJob = await jobs.create({ name: 'Metadata Extraction', frame_start: 1, frame_end: 10, output_format: 'PNG', allow_parallel_runners: true, }); setCurrentJobId(tempJob.id); setMetadataStatus('extracting'); // Upload file to trigger metadata extraction await jobs.uploadFile(tempJob.id, selectedFile); } catch (err) { console.error('Failed to start metadata extraction:', err); setMetadataStatus('error'); } } }; const handleSubmit = async (e) => { e.preventDefault(); setError(''); setSubmitting(true); try { if (!file) { throw new Error('Please select a Blender file'); } if (formData.frame_start < 0 || formData.frame_end < formData.frame_start) { throw new Error('Invalid frame range'); } // If we have a temporary job for metadata extraction, cancel it if (currentJobId) { try { await jobs.cancel(currentJobId); } catch (err) { // Ignore errors when canceling temp job } } // Create actual job const job = await jobs.create({ name: formData.name, frame_start: parseInt(formData.frame_start), frame_end: parseInt(formData.frame_end), output_format: formData.output_format, allow_parallel_runners: formData.allow_parallel_runners, }); // Upload file await jobs.uploadFile(job.id, file); // Reset form setFormData({ name: '', frame_start: 1, frame_end: 10, output_format: 'PNG', allow_parallel_runners: true, }); setFile(null); setMetadata(null); setMetadataStatus(null); setCurrentJobId(null); e.target.reset(); if (onSuccess) { onSuccess(); } } catch (err) { setError(err.message || 'Failed to submit job'); } finally { setSubmitting(false); } }; return (