Mor ui work

This commit is contained in:
Jaret Burkett
2025-02-21 12:40:17 -07:00
parent ad87f72384
commit 2b6e66e0cb
7 changed files with 44 additions and 32 deletions

View File

@@ -16,7 +16,7 @@ model Settings {
model Job { model Job {
id String @id @default(uuid()) id String @id @default(uuid())
name String @unique name String @unique
gpu_id Int gpu_ids String
job_config String // JSON string job_config String // JSON string
created_at DateTime @default(now()) created_at DateTime @default(now())
updated_at DateTime @updatedAt updated_at DateTime @updatedAt

View File

@@ -70,7 +70,7 @@ export async function GET(request: NextRequest, { params }: { params: { jobID: s
return NextResponse.json({ error: 'run.py not found' }, { status: 500 }); return NextResponse.json({ error: 'run.py not found' }, { status: 500 });
} }
console.log('Spawning command:', `AITK_JOB_ID=${jobID} CUDA_VISIBLE_DEVICES=${job.gpu_id} ${pythonPath} ${runFilePath} ${configPath}`); console.log('Spawning command:', `AITK_JOB_ID=${jobID} CUDA_VISIBLE_DEVICES=${job.gpu_ids} ${pythonPath} ${runFilePath} ${configPath}`);
// start job // start job
const subprocess = spawn(pythonPath, [runFilePath, configPath], { const subprocess = spawn(pythonPath, [runFilePath, configPath], {
@@ -79,7 +79,7 @@ export async function GET(request: NextRequest, { params }: { params: { jobID: s
env: { env: {
...process.env, ...process.env,
AITK_JOB_ID: jobID, AITK_JOB_ID: jobID,
CUDA_VISIBLE_DEVICES: `${job.gpu_id}`, CUDA_VISIBLE_DEVICES: `${job.gpu_ids}`,
}, },
cwd: TOOLKIT_ROOT, cwd: TOOLKIT_ROOT,
}); });

View File

@@ -9,16 +9,16 @@ export async function GET(request: Request) {
try { try {
if (id) { if (id) {
const training = await prisma.job.findUnique({ const job = await prisma.job.findUnique({
where: { id }, where: { id },
}); });
return NextResponse.json(training); return NextResponse.json(job);
} }
const trainings = await prisma.job.findMany({ const jobs = await prisma.job.findMany({
orderBy: { created_at: 'desc' }, orderBy: { created_at: 'desc' },
}); });
return NextResponse.json(trainings); return NextResponse.json({ jobs: jobs });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return NextResponse.json({ error: 'Failed to fetch training data' }, { status: 500 }); return NextResponse.json({ error: 'Failed to fetch training data' }, { status: 500 });
@@ -28,7 +28,7 @@ export async function GET(request: Request) {
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const body = await request.json(); const body = await request.json();
const { id, name, job_config, gpu_id } = body; const { id, name, job_config, gpu_ids } = body;
if (id) { if (id) {
// Update existing training // Update existing training
@@ -36,7 +36,7 @@ export async function POST(request: Request) {
where: { id }, where: { id },
data: { data: {
name, name,
gpu_id, gpu_ids,
job_config: JSON.stringify(job_config), job_config: JSON.stringify(job_config),
}, },
}); });
@@ -46,7 +46,7 @@ export async function POST(request: Request) {
const training = await prisma.job.create({ const training = await prisma.job.create({
data: { data: {
name, name,
gpu_id, gpu_ids,
job_config: JSON.stringify(job_config), job_config: JSON.stringify(job_config),
}, },
}); });

View File

@@ -58,7 +58,7 @@ export default function JobPage({ params }: { params: { jobID: string } }) {
<h2 className="text-lg font-semibold">Job Details</h2> <h2 className="text-lg font-semibold">Job Details</h2>
<p className="text-gray-400">ID: {job.id}</p> <p className="text-gray-400">ID: {job.id}</p>
<p className="text-gray-400">Name: {job.name}</p> <p className="text-gray-400">Name: {job.name}</p>
<p className="text-gray-400">GPU: {job.gpu_id}</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">Status: {job.status}</p>
<p className="text-gray-400">Info: {job.info}</p> <p className="text-gray-400">Info: {job.info}</p>
<p className="text-gray-400">Step: {job.step}</p> <p className="text-gray-400">Step: {job.step}</p>

View File

@@ -22,7 +22,7 @@ export default function TrainingForm() {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const runId = searchParams.get('id'); const runId = searchParams.get('id');
const [gpuID, setGpuID] = useState<number | null>(null); const [gpuIDs, setGpuIDs] = useState<string | null>(null);
const { settings, isSettingsLoaded } = useSettings(); const { settings, isSettingsLoaded } = useSettings();
const { gpuList, isGPUInfoLoaded } = useGPUInfo(); const { gpuList, isGPUInfoLoaded } = useGPUInfo();
const { datasets, status: datasetFetchStatus } = useDatasetList(); const { datasets, status: datasetFetchStatus } = useDatasetList();
@@ -52,7 +52,7 @@ export default function TrainingForm() {
fetch(`/api/jobs?id=${runId}`) fetch(`/api/jobs?id=${runId}`)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
setGpuID(data.gpu_id); setGpuIDs(data.gpu_ids);
setJobConfig(JSON.parse(data.job_config)); setJobConfig(JSON.parse(data.job_config));
}) })
.catch(error => console.error('Error fetching training:', error)); .catch(error => console.error('Error fetching training:', error));
@@ -61,8 +61,8 @@ export default function TrainingForm() {
useEffect(() => { useEffect(() => {
if (isGPUInfoLoaded) { if (isGPUInfoLoaded) {
if (gpuID === null && gpuList.length > 0) { if (gpuIDs === null && gpuList.length > 0) {
setGpuID(gpuList[0]); setGpuIDs(`${gpuList[0]}`);
} }
} }
}, [gpuList, isGPUInfoLoaded]); }, [gpuList, isGPUInfoLoaded]);
@@ -73,8 +73,8 @@ export default function TrainingForm() {
} }
}, [settings, isSettingsLoaded]); }, [settings, isSettingsLoaded]);
const handleSubmit = async (e: React.FormEvent) => { const saveJob = async () => {
e.preventDefault(); if (status === 'saving') return;
setStatus('saving'); setStatus('saving');
try { try {
@@ -86,7 +86,7 @@ export default function TrainingForm() {
body: JSON.stringify({ body: JSON.stringify({
id: runId, id: runId,
name: jobConfig.config.name, name: jobConfig.config.name,
gpu_id: gpuID, gpu_ids: gpuIDs,
job_config: jobConfig, job_config: jobConfig,
}), }),
}); });
@@ -106,6 +106,11 @@ export default function TrainingForm() {
} }
}; };
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
saveJob();
};
return ( return (
<> <>
<TopBar> <TopBar>
@@ -118,6 +123,15 @@ export default function TrainingForm() {
<h1 className="text-lg">{runId ? 'Edit Training Job' : 'New Training Job'}</h1> <h1 className="text-lg">{runId ? 'Edit Training Job' : 'New Training Job'}</h1>
</div> </div>
<div className="flex-1"></div> <div className="flex-1"></div>
<div>
<Button
className="text-gray-200 bg-green-800 px-3 py-1 rounded-md"
onClick={() => saveJob()}
disabled={status === 'saving'}
>
{status === 'saving' ? 'Saving...' : runId ? 'Update Job' : 'Create Job'}
</Button>
</div>
</TopBar> </TopBar>
<MainContent> <MainContent>
<form onSubmit={handleSubmit} className="space-y-8"> <form onSubmit={handleSubmit} className="space-y-8">
@@ -132,9 +146,9 @@ export default function TrainingForm() {
/> />
<SelectInput <SelectInput
label="GPU ID" label="GPU ID"
value={`${gpuID}`} value={`${gpuIDs}`}
className="pt-2" className="pt-2"
onChange={value => setGpuID(parseInt(value))} onChange={value => setGpuIDs(value)}
options={gpuList.map(gpu => ({ value: `${gpu}`, label: `GPU #${gpu}` }))} options={gpuList.map(gpu => ({ value: `${gpu}`, label: `GPU #${gpu}` }))}
/> />
</Card> </Card>
@@ -553,17 +567,10 @@ export default function TrainingForm() {
</Card> </Card>
</div> </div>
<button
type="submit"
disabled={status === 'saving'}
className="w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{status === 'saving' ? 'Saving...' : runId ? 'Update Training' : 'Create Training'}
</button>
{status === 'success' && <p className="text-green-500 text-center">Training saved successfully!</p>} {status === 'success' && <p className="text-green-500 text-center">Training saved successfully!</p>}
{status === 'error' && <p className="text-red-500 text-center">Error saving training. Please try again.</p>} {status === 'error' && <p className="text-red-500 text-center">Error saving training. Please try again.</p>}
</form> </form>
<div className="pt-20"></div>
</MainContent> </MainContent>
</> </>
); );

View File

@@ -38,7 +38,7 @@ export default function JobsTable(props: JobsTableProps) {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{jobs.map((job, index) => { {jobs?.map((job, index) => {
const jobConfig: JobConfig = JSON.parse(job.job_config); const jobConfig: JobConfig = JSON.parse(job.job_config);
const totalSteps = jobConfig.config.process[0].train.steps; const totalSteps = jobConfig.config.process[0].train.steps;
@@ -68,7 +68,7 @@ export default function JobsTable(props: JobsTableProps) {
</div> </div>
</div> </div>
</td> </td>
<td className="px-3 py-2">{job.gpu_id}</td> <td className="px-3 py-2">{job.gpu_ids}</td>
<td className={`px-3 py-2 ${statusClass}`}>{job.status}</td> <td className={`px-3 py-2 ${statusClass}`}>{job.status}</td>
<td className="px-3 py-2 truncate max-w-xs">{job.info}</td> <td className="px-3 py-2 truncate max-w-xs">{job.info}</td>
</tr> </tr>

View File

@@ -13,8 +13,13 @@ export default function useJobsList() {
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
console.log('Jobs:', data); console.log('Jobs:', data);
setJobs(data); if (data.error) {
setStatus('success'); console.log('Error fetching jobs:', data.error);
setStatus('error');
} else {
setJobs(data.jobs);
setStatus('success');
}
}) })
.catch(error => { .catch(error => {
console.error('Error fetching datasets:', error); console.error('Error fetching datasets:', error);