Skip to content
Open
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
359 changes: 199 additions & 160 deletions src/renderer/components/Experiment/Tasks/QueueTaskModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ import {
Chip,
} from '@mui/joy';
import { Editor } from '@monaco-editor/react';
import { PlayIcon, AlertTriangleIcon, CheckCircleIcon } from 'lucide-react';
import {
PlayIcon,
AlertTriangleIcon,
CheckCircleIcon,
ChevronDownIcon,
} from 'lucide-react';
import { setTheme } from 'renderer/lib/monacoConfig';
import { useSWRWithAuth as useSWR, useAPI } from 'renderer/lib/authContext';
import * as chatAPI from 'renderer/lib/transformerlab-api-sdk';
Expand Down Expand Up @@ -112,6 +117,8 @@ export default function QueueTaskModal({
const [acceleratorsInput, setAcceleratorsInput] = React.useState('');
const [numNodesInput, setNumNodesInput] = React.useState('');
const [minutesRequestedInput, setMinutesRequestedInput] = React.useState('');
const [showResourceOverrides, setShowResourceOverrides] =
React.useState(false);
const loadingMessages = React.useMemo(
() => [
'Contacting compute provider…',
Expand Down Expand Up @@ -1261,165 +1268,6 @@ export default function QueueTaskModal({
</FormHelperText>
</FormControl>
)}

{/* Resource Requirements Section */}
<Divider />
<Stack spacing={2}>
<Typography level="title-sm">Resource Requirements</Typography>
<Stack direction="row" spacing={2}>
<FormControl sx={{ flex: 1 }}>
<FormLabel>CPUs</FormLabel>
<Input
placeholder="e.g. 4"
value={cpusInput}
onChange={(e) => setCpusInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Memory</FormLabel>
<Input
placeholder="e.g. 16GB"
value={memoryInput}
onChange={(e) => setMemoryInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
</Stack>
<Stack direction="row" spacing={2}>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Disk space</FormLabel>
<Input
placeholder="e.g. 100GB"
value={diskSpaceInput}
onChange={(e) => setDiskSpaceInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Accelerators</FormLabel>
<Input
placeholder="e.g. A100:1, RTX3090:2, 1"
value={acceleratorsInput}
onChange={(e) => setAcceleratorsInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
</Stack>
<Stack direction="row" spacing={2}>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Num nodes</FormLabel>
<Input
placeholder="e.g. 1"
value={numNodesInput}
onChange={(e) => setNumNodesInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Minutes requested</FormLabel>
<Input
type="number"
placeholder="e.g. 60"
value={minutesRequestedInput}
onChange={(e) => setMinutesRequestedInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
</Stack>
<FormHelperText>
These values override the template&apos;s resource
requirements for this run only. Leave a field empty to use the
template default.
</FormHelperText>
</Stack>

{/* Incompatibility Warning */}
{selectedProvider &&
effectiveResources?.accelerators &&
!isProviderCompatible(selectedProvider) && (
<Alert
variant="soft"
color="warning"
startDecorator={<AlertTriangleIcon size={18} />}
sx={{ mt: 1 }}
>
<Typography level="body-sm">
This provider may not support the requested accelerators (
<strong>{effectiveResources.accelerators}</strong>).
</Typography>
</Alert>
)}

{/* Local Provider Resource Validation */}
{isLocalProvider &&
resourceValidation &&
!resourceValidation.isCompatible && (
<Alert
variant="soft"
color={resourceValidation.hasErrors ? 'danger' : 'warning'}
startDecorator={<AlertTriangleIcon size={18} />}
sx={{ mt: 1 }}
>
<Stack spacing={1}>
<Typography
level="title-sm"
color={
resourceValidation.hasErrors ? 'danger' : 'warning'
}
>
{resourceValidation.hasErrors
? 'Local provider cannot meet task requirements'
: 'Local provider may not meet task requirements'}
</Typography>
<Stack spacing={0.5}>
{resourceValidation.issues.map((issue, idx) => (
<Stack
key={idx}
direction="row"
spacing={1}
alignItems="center"
>
<Chip
size="sm"
variant="solid"
color={
issue.type === 'error' ? 'danger' : 'warning'
}
>
{issue.label}
</Chip>
<Typography level="body-xs">
Required: <strong>{issue.required}</strong> —
Available: <strong>{issue.available}</strong>
</Typography>
</Stack>
))}
</Stack>
{resourceValidation.hasErrors && (
<Typography level="body-xs" color="danger">
Consider selecting a different provider with the
required resources.
</Typography>
)}
</Stack>
</Alert>
)}

{isLocalProvider &&
resourceValidation?.isCompatible &&
effectiveResources && (
<Alert
variant="soft"
color="success"
startDecorator={<CheckCircleIcon size={18} />}
sx={{ mt: 1 }}
>
<Typography level="body-sm" color="success">
Local provider meets the task resource requirements.
</Typography>
</Alert>
)}
</Stack>

<Divider />
Expand Down Expand Up @@ -1508,6 +1356,197 @@ export default function QueueTaskModal({
onLowerIsBetterChange={setLowerIsBetter}
parameters={parameters}
/>

{/* Optional Resource Overrides Section */}
<Divider />
<Stack spacing={1}>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
sx={{ cursor: 'pointer' }}
onClick={() => setShowResourceOverrides((prev) => !prev)}
>
<Typography level="title-sm">
Optional resource overrides
</Typography>
<ChevronDownIcon
size={18}
style={{
transform: showResourceOverrides
? 'rotate(180deg)'
: 'rotate(0deg)',
transition: 'transform 0.2s ease',
}}
/>
</Stack>
{showResourceOverrides && (
<Stack spacing={2}>
<Stack direction="row" spacing={2}>
<FormControl sx={{ flex: 1 }}>
<FormLabel>CPUs</FormLabel>
<Input
placeholder="e.g. 4"
value={cpusInput}
onChange={(e) => setCpusInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Memory</FormLabel>
<Input
placeholder="e.g. 16GB"
value={memoryInput}
onChange={(e) => setMemoryInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
</Stack>
<Stack direction="row" spacing={2}>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Disk space</FormLabel>
<Input
placeholder="e.g. 100GB"
value={diskSpaceInput}
onChange={(e) => setDiskSpaceInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Accelerators</FormLabel>
<Input
placeholder="e.g. A100:1, RTX3090:2, 1"
value={acceleratorsInput}
onChange={(e) => setAcceleratorsInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
</Stack>
<Stack direction="row" spacing={2}>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Num nodes</FormLabel>
<Input
placeholder="e.g. 1"
value={numNodesInput}
onChange={(e) => setNumNodesInput(e.target.value)}
disabled={isSubmitting}
/>
</FormControl>
<FormControl sx={{ flex: 1 }}>
<FormLabel>Minutes requested</FormLabel>
<Input
type="number"
placeholder="e.g. 60"
value={minutesRequestedInput}
onChange={(e) =>
setMinutesRequestedInput(e.target.value)
}
disabled={isSubmitting}
/>
</FormControl>
</Stack>
<FormHelperText>
These values override the template&apos;s resource
requirements for this run only. Leave a field empty to use
the template default.
</FormHelperText>

{/* Incompatibility Warning */}
{selectedProvider &&
effectiveResources?.accelerators &&
!isProviderCompatible(selectedProvider) && (
<Alert
variant="soft"
color="warning"
startDecorator={<AlertTriangleIcon size={18} />}
sx={{ mt: 1 }}
>
<Typography level="body-sm">
This provider may not support the requested
accelerators (
<strong>{effectiveResources.accelerators}</strong>).
</Typography>
</Alert>
)}

{/* Local Provider Resource Validation */}
{isLocalProvider &&
resourceValidation &&
!resourceValidation.isCompatible && (
<Alert
variant="soft"
color={
resourceValidation.hasErrors ? 'danger' : 'warning'
}
startDecorator={<AlertTriangleIcon size={18} />}
sx={{ mt: 1 }}
>
<Stack spacing={1}>
<Typography
level="title-sm"
color={
resourceValidation.hasErrors
? 'danger'
: 'warning'
}
>
{resourceValidation.hasErrors
? 'Local provider cannot meet task requirements'
: 'Local provider may not meet task requirements'}
</Typography>
<Stack spacing={0.5}>
{resourceValidation.issues.map((issue, idx) => (
<Stack
key={idx}
direction="row"
spacing={1}
alignItems="center"
>
<Chip
size="sm"
variant="solid"
color={
issue.type === 'error'
? 'danger'
: 'warning'
}
>
{issue.label}
</Chip>
<Typography level="body-xs">
Required: <strong>{issue.required}</strong> —
Available: <strong>{issue.available}</strong>
</Typography>
</Stack>
))}
</Stack>
{resourceValidation.hasErrors && (
<Typography level="body-xs" color="danger">
Consider selecting a different provider with the
required resources.
</Typography>
)}
</Stack>
</Alert>
)}

{isLocalProvider &&
resourceValidation?.isCompatible &&
effectiveResources && (
<Alert
variant="soft"
color="success"
startDecorator={<CheckCircleIcon size={18} />}
sx={{ mt: 1 }}
>
<Typography level="body-sm" color="success">
Local provider meets the task resource requirements.
</Typography>
</Alert>
)}
</Stack>
)}
</Stack>
</Stack>
</DialogContent>
<DialogActions>
Expand Down
Loading