Samples work in ui now

This commit is contained in:
Jaret Burkett
2025-02-21 20:28:52 -07:00
parent 2b6e66e0cb
commit 710c6de1c9
24 changed files with 408 additions and 68 deletions

View File

@@ -0,0 +1,23 @@
import { Job } from '@prisma/client';
interface JobOverviewProps {
job: Job;
}
export default function JobOverview({ job }: JobOverviewProps) {
return (
<>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="">
<h2 className="text-lg font-semibold">Job Details</h2>
<p className="text-gray-400">ID: {job.id}</p>
<p className="text-gray-400">Name: {job.name}</p>
<p className="text-gray-400">GPUs: {job.gpu_ids}</p>
<p className="text-gray-400">Status: {job.status}</p>
<p className="text-gray-400">Info: {job.info}</p>
<p className="text-gray-400">Step: {job.step}</p>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,67 @@
import React, { useRef, useEffect, useState, ReactNode } from 'react';
interface SampleImageCardProps {
imageUrl: string;
alt: string;
children?: ReactNode;
className?: string;
onDelete?: () => void;
}
const SampleImageCard: React.FC<SampleImageCardProps> = ({ imageUrl, alt, children, className = '' }) => {
const cardRef = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState<boolean>(false);
const [loaded, setLoaded] = useState<boolean>(false);
useEffect(() => {
// Create intersection observer to check visibility
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ threshold: 0.1 },
);
if (cardRef.current) {
observer.observe(cardRef.current);
}
return () => {
observer.disconnect();
};
}, []);
const handleLoad = (): void => {
setLoaded(true);
};
return (
<div className={`flex flex-col ${className}`}>
{/* Square image container */}
<div
ref={cardRef}
className="relative w-full"
style={{ paddingBottom: '100%' }} // Make it square
>
<div className="absolute inset-0 rounded-t-lg shadow-md">
{isVisible && (
<img
src={`/api/img/${encodeURIComponent(imageUrl)}`}
alt={alt}
onLoad={handleLoad}
className={`w-full h-full object-contain transition-opacity duration-300 ${
loaded ? 'opacity-100' : 'opacity-0'
}`}
/>
)}
{children && <div className="absolute inset-0 flex items-center justify-center">{children}</div>}
</div>
</div>
</div>
);
};
export default SampleImageCard;

View File

@@ -0,0 +1,89 @@
import { useMemo } from 'react';
import useSampleImages from '@/hooks/useSampleImages';
import SampleImageCard from './SampleImageCard';
import { Job } from '@prisma/client';
import { JobConfig } from '@/types';
interface SampleImagesProps {
job: Job;
}
export default function SampleImages({ job }: SampleImagesProps) {
const { sampleImages, status, refreshSampleImages } = useSampleImages(job.id, 5000);
const numSamples = useMemo(() => {
if (job?.job_config) {
const jobConfig = JSON.parse(job.job_config) as JobConfig;
const sampleConfig = jobConfig.config.process[0].sample;
return sampleConfig.prompts.length;
}
return 10;
}, [job]);
// Use direct Tailwind class without string interpolation
// This way Tailwind can properly generate the class
// I hate this, but it's the only way to make it work
const gridColsClass = useMemo(() => {
const cols = Math.min(numSamples, 20);
switch (cols) {
case 1:
return 'grid-cols-1';
case 2:
return 'grid-cols-2';
case 3:
return 'grid-cols-3';
case 4:
return 'grid-cols-4';
case 5:
return 'grid-cols-5';
case 6:
return 'grid-cols-6';
case 7:
return 'grid-cols-7';
case 8:
return 'grid-cols-8';
case 9:
return 'grid-cols-9';
case 10:
return 'grid-cols-10';
case 11:
return 'grid-cols-11';
case 12:
return 'grid-cols-12';
case 13:
return 'grid-cols-13';
case 14:
return 'grid-cols-14';
case 15:
return 'grid-cols-15';
case 16:
return 'grid-cols-16';
case 17:
return 'grid-cols-17';
case 18:
return 'grid-cols-18';
case 19:
return 'grid-cols-19';
case 20:
return 'grid-cols-20';
default:
return 'grid-cols-1';
}
}, [numSamples]);
return (
<div>
<div className='pb-4'>
{status === 'loading' && sampleImages.length === 0 && <p>Loading...</p>}
{status === 'error' && <p>Error fetching sample images</p>}
{sampleImages && (
<div className={`grid ${gridColsClass} gap-1`}>
{sampleImages.map((sample: string) => (
<SampleImageCard key={sample} imageUrl={sample} alt="Sample Image" />
))}
</div>
)}
</div>
</div>
);
}

View File

@@ -20,7 +20,7 @@ export const TopBar: React.FC<Props> = ({ children, className }) => {
export const MainContent: React.FC<Props> = ({ children, className }) => {
return (
<div className={classNames('pt-16 px-4 absolute top-0 left-0 w-full h-full overflow-auto', className)}>
<div className={classNames('pt-14 px-4 absolute top-0 left-0 w-full h-full overflow-auto', className)}>
{children ? children : null}
</div>
);