Files
ai-toolkit/ui/src/app/jobs/[jobID]/page.tsx
T
2026-03-31 16:54:55 -06:00

126 lines
3.6 KiB
TypeScript

'use client';
import { useState, use } from 'react';
import { FaChevronLeft } from 'react-icons/fa';
import { MdDashboard, MdImage, MdShowChart, MdCode } from 'react-icons/md';
import { Button } from '@headlessui/react';
import { TopBar, MainContent } from '@/components/layout';
import useJob from '@/hooks/useJob';
import SampleImages, { SampleImagesMenu } from '@/components/SampleImages';
import JobOverview from '@/components/JobOverview';
import { redirect } from 'next/navigation';
import JobActionBar from '@/components/JobActionBar';
import JobConfigViewer from '@/components/JobConfigViewer';
import JobLossGraph from '@/components/JobLossGraph';
import { Job } from '@prisma/client';
type PageKey = 'overview' | 'samples' | 'config' | 'loss_log';
interface Page {
name: string;
value: PageKey;
icon: React.ComponentType<{ className?: string }>;
component: React.ComponentType<{ job: Job }>;
menuItem?: React.ComponentType<{ job?: Job | null }> | null;
mainCss?: string;
}
const pages: Page[] = [
{
name: 'Overview',
value: 'overview',
icon: MdDashboard,
component: JobOverview,
mainCss: 'pt-24',
},
{
name: 'Samples',
value: 'samples',
icon: MdImage,
component: SampleImages,
menuItem: SampleImagesMenu,
mainCss: 'pt-24',
},
{
name: 'Loss Graph',
value: 'loss_log',
icon: MdShowChart,
component: JobLossGraph,
mainCss: 'pt-24',
},
{
name: 'Config File',
value: 'config',
icon: MdCode,
component: JobConfigViewer,
mainCss: 'pt-[80px] px-0 pb-0',
},
];
export default function JobPage({ params }: { params: { jobID: string } }) {
const usableParams = use(params as any) as { jobID: string };
const jobID = usableParams.jobID;
const { job, status, refreshJob } = useJob(jobID, 5000);
const [pageKey, setPageKey] = useState<PageKey>('overview');
const page = pages.find(p => p.value === pageKey);
return (
<>
{/* Fixed top bar */}
<TopBar>
<div>
<Button className="text-gray-500 dark:text-gray-300 px-3 mt-1" onClick={() => redirect('/jobs')}>
<FaChevronLeft />
</Button>
</div>
<div>
<h1 className="text-lg">Job: {job?.name}</h1>
</div>
<div className="flex-1"></div>
{job && (
<JobActionBar
job={job}
onRefresh={refreshJob}
hideView
afterDelete={() => {
redirect('/jobs');
}}
autoStartQueue={true}
/>
)}
</TopBar>
<MainContent className={pages.find(page => page.value === pageKey)?.mainCss}>
{status === 'loading' && job == null && <p>Loading...</p>}
{status === 'error' && job == null && <p>Error fetching job</p>}
{job && (
<>
{pages.map(page => {
const Component = page.component;
return page.value === pageKey ? <Component key={page.value} job={job} /> : null;
})}
</>
)}
</MainContent>
<div className="bg-gray-800 absolute top-12 left-0 w-full h-8 flex items-center px-2 text-sm">
{pages.map(page => (
<Button
key={page.value}
onClick={() => setPageKey(page.value)}
className={`px-4 py-1 h-8 flex items-center gap-1.5 ${page.value === pageKey ? 'bg-gray-300 dark:bg-gray-700 text-white' : ''}`}
>
<page.icon className="text-sm" />
{page.name}
</Button>
))}
{page?.menuItem && (
<>
<div className="flex-grow"></div>
<page.menuItem job={job} />
</>
)}
</div>
</>
);
}