Implement context archive handling and metadata extraction for render jobs. Add functionality to check for Blender availability, create context archives, and extract metadata from .blend files. Update job creation and retrieval processes to support new metadata structure and context file management. Enhance client-side components to display context files and integrate new API endpoints for context handling.

This commit is contained in:
2025-11-24 10:02:13 -06:00
parent f9ff4d0138
commit a029714e08
13 changed files with 3887 additions and 856 deletions

View File

@@ -1,10 +1,12 @@
import { useState, useEffect, useRef } from 'react';
import { jobs } from '../utils/api';
import VideoPlayer from './VideoPlayer';
import FileExplorer from './FileExplorer';
export default function JobDetails({ job, onClose, onUpdate }) {
const [jobDetails, setJobDetails] = useState(job);
const [files, setFiles] = useState([]);
const [contextFiles, setContextFiles] = useState([]);
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
const [videoUrl, setVideoUrl] = useState(null);
@@ -89,6 +91,15 @@ export default function JobDetails({ job, onClose, onUpdate }) {
setJobDetails(details);
setFiles(fileList);
setTasks(taskList);
// Fetch context archive contents separately (may not exist for old jobs)
try {
const contextList = await jobs.getContextArchive(job.id);
setContextFiles(contextList || []);
} catch (error) {
// Context archive may not exist for old jobs
setContextFiles([]);
}
// Only load task data (logs/steps) for tasks that don't have data yet
// This prevents overwriting logs that are being streamed via WebSocket
@@ -446,7 +457,7 @@ export default function JobDetails({ job, onClose, onUpdate }) {
</div>
</div>
{videoUrl && jobDetails.output_format === 'MP4' && (
{videoUrl && (jobDetails.output_format === 'EXR_264_MP4' || jobDetails.output_format === 'EXR_AV1_MP4') && (
<div>
<h3 className="text-lg font-semibold text-gray-100 mb-3">
Video Preview
@@ -455,68 +466,38 @@ export default function JobDetails({ job, onClose, onUpdate }) {
</div>
)}
{contextFiles.length > 0 && (
<div>
<h3 className="text-lg font-semibold text-gray-100 mb-3">
Context Archive
</h3>
<FileExplorer
files={contextFiles.map(f => ({
id: 0, // Context files don't have IDs
file_name: f.path || f.name || '',
file_size: f.size || 0,
file_type: 'input'
}))}
onDownload={null} // Context files can't be downloaded individually
isImageFile={isImageFile}
/>
</div>
)}
{outputFiles.length > 0 && (
<div>
<h3 className="text-lg font-semibold text-gray-100 mb-3">
Output Files
</h3>
<div className="space-y-2">
{outputFiles.map((file) => {
const isImage = isImageFile(file.file_name);
const imageUrl = isImage ? jobs.downloadFile(job.id, file.id) : null;
return (
<div
key={file.id}
className="flex items-center justify-between p-3 bg-gray-900 rounded-lg border border-gray-700"
>
<div className="flex-1">
<p className="font-medium text-gray-100">{file.file_name}</p>
<p className="text-sm text-gray-400">
{(file.file_size / 1024 / 1024).toFixed(2)} MB
</p>
</div>
<div className="flex gap-2">
{isImage && imageUrl && (
<button
onClick={() => setPreviewImage({ url: imageUrl, fileName: file.file_name })}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-500 transition-colors"
>
Preview
</button>
)}
<button
onClick={() => handleDownload(file.id, file.file_name)}
className="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-500 transition-colors"
>
Download
</button>
</div>
</div>
);
})}
</div>
</div>
)}
{inputFiles.length > 0 && (
<div>
<h3 className="text-lg font-semibold text-gray-100 mb-3">
Input Files
</h3>
<div className="space-y-2">
{inputFiles.map((file) => (
<div
key={file.id}
className="p-3 bg-gray-900 rounded-lg border border-gray-700"
>
<p className="font-medium text-gray-100">{file.file_name}</p>
<p className="text-sm text-gray-400">
{(file.file_size / 1024 / 1024).toFixed(2)} MB
</p>
</div>
))}
</div>
<FileExplorer
files={outputFiles}
onDownload={handleDownload}
onPreview={(file) => {
const imageUrl = jobs.downloadFile(job.id, file.id);
setPreviewImage({ url: imageUrl, fileName: file.file_name });
}}
isImageFile={isImageFile}
/>
</div>
)}