initial commit

This commit is contained in:
2025-11-21 17:31:18 -06:00
commit 87cb54a17d
2451 changed files with 508075 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
import { useState, useEffect } from 'react';
import { jobs } from '../utils/api';
import VideoPlayer from './VideoPlayer';
export default function JobDetails({ job, onClose, onUpdate }) {
const [jobDetails, setJobDetails] = useState(job);
const [files, setFiles] = useState([]);
const [loading, setLoading] = useState(true);
const [videoUrl, setVideoUrl] = useState(null);
useEffect(() => {
loadDetails();
const interval = setInterval(loadDetails, 2000);
return () => clearInterval(interval);
}, [job.id]);
const loadDetails = async () => {
try {
const [details, fileList] = await Promise.all([
jobs.get(job.id),
jobs.getFiles(job.id),
]);
setJobDetails(details);
setFiles(fileList);
// Check if there's an MP4 output file
const mp4File = fileList.find(
(f) => f.file_type === 'output' && f.file_name.endsWith('.mp4')
);
if (mp4File) {
setVideoUrl(jobs.getVideoUrl(job.id));
}
} catch (error) {
console.error('Failed to load job details:', error);
} finally {
setLoading(false);
}
};
const handleDownload = (fileId, fileName) => {
window.open(jobs.downloadFile(job.id, fileId), '_blank');
};
const outputFiles = files.filter((f) => f.file_type === 'output');
const inputFiles = files.filter((f) => f.file_type === 'input');
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div className="sticky top-0 bg-white border-b border-gray-200 px-6 py-4 flex justify-between items-center">
<h2 className="text-2xl font-bold text-gray-900">{jobDetails.name}</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 text-2xl font-bold"
>
×
</button>
</div>
<div className="p-6 space-y-6">
{loading && (
<div className="flex justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600"></div>
</div>
)}
{!loading && (
<>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-600">Status</p>
<p className="font-semibold text-gray-900">{jobDetails.status}</p>
</div>
<div>
<p className="text-sm text-gray-600">Progress</p>
<p className="font-semibold text-gray-900">
{jobDetails.progress.toFixed(1)}%
</p>
</div>
<div>
<p className="text-sm text-gray-600">Frame Range</p>
<p className="font-semibold text-gray-900">
{jobDetails.frame_start} - {jobDetails.frame_end}
</p>
</div>
<div>
<p className="text-sm text-gray-600">Output Format</p>
<p className="font-semibold text-gray-900">
{jobDetails.output_format}
</p>
</div>
</div>
{videoUrl && jobDetails.output_format === 'MP4' && (
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-3">
Video Preview
</h3>
<VideoPlayer videoUrl={videoUrl} />
</div>
)}
{outputFiles.length > 0 && (
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-3">
Output Files
</h3>
<div className="space-y-2">
{outputFiles.map((file) => (
<div
key={file.id}
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
>
<div>
<p className="font-medium text-gray-900">{file.file_name}</p>
<p className="text-sm text-gray-600">
{(file.file_size / 1024 / 1024).toFixed(2)} MB
</p>
</div>
<button
onClick={() => handleDownload(file.id, file.file_name)}
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
>
Download
</button>
</div>
))}
</div>
</div>
)}
{inputFiles.length > 0 && (
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-3">
Input Files
</h3>
<div className="space-y-2">
{inputFiles.map((file) => (
<div
key={file.id}
className="p-3 bg-gray-50 rounded-lg"
>
<p className="font-medium text-gray-900">{file.file_name}</p>
<p className="text-sm text-gray-600">
{(file.file_size / 1024 / 1024).toFixed(2)} MB
</p>
</div>
))}
</div>
</div>
)}
{jobDetails.error_message && (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg text-red-700">
<p className="font-semibold">Error:</p>
<p>{jobDetails.error_message}</p>
</div>
)}
</>
)}
</div>
</div>
</div>
);
}