Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
File size: 9,561 Bytes
f555806 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
import useGPUInfo from '@/hooks/useGPUInfo';
import GPUWidget from '@/components/GPUWidget';
import FilesWidget from '@/components/FilesWidget';
import { getTotalSteps } from '@/utils/jobs';
import { Cpu, HardDrive, Info, Gauge, Cloud, ExternalLink } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import useJobLog from '@/hooks/useJobLog';
import { JobConfig, JobRecord } from '@/types';
import HFJobStatus from './HFJobStatus';
interface JobOverviewProps {
job: JobRecord;
}
export default function JobOverview({ job }: JobOverviewProps) {
// Parse job config to check if it's an HF Job
const jobConfig = useMemo(() => {
try {
return JSON.parse(job.job_config) as JobConfig;
} catch (e) {
return null;
}
}, [job.job_config]);
const isHFJob = jobConfig?.is_hf_job || false;
const hfJobSubmitted = !!jobConfig?.hf_job_id;
const gpuIds = useMemo(() => {
// For HF Jobs, don't parse GPU IDs as they're hardware names
if (isHFJob) return [];
return job.gpu_ids.split(',').map(id => parseInt(id));
}, [job.gpu_ids, isHFJob]);
const { log, setLog, status: statusLog, refresh: refreshLog } = useJobLog(job.id, 2000);
const logRef = useRef<HTMLDivElement>(null);
// Track whether we should auto-scroll to bottom
const [isScrolledToBottom, setIsScrolledToBottom] = useState(true);
const { gpuList, isGPUInfoLoaded } = useGPUInfo(gpuIds, 5000);
const totalSteps = getTotalSteps(job);
const progress = (job.step / totalSteps) * 100;
const isStopping = job.stop && job.status === 'running';
const logLines: string[] = useMemo(() => {
// split at line breaks on \n or \r\n but not \r
let splits: string[] = log.split(/\n|\r\n/);
splits = splits.map(line => {
return line.split(/\r/).pop();
}) as string[];
// only return last 100 lines max
const maxLines = 1000;
if (splits.length > maxLines) {
splits = splits.slice(splits.length - maxLines);
}
return splits;
}, [log]);
// Handle scroll events to determine if user has scrolled away from bottom
const handleScroll = () => {
if (logRef.current) {
const { scrollTop, scrollHeight, clientHeight } = logRef.current;
// Consider "at bottom" if within 10 pixels of the bottom
const isAtBottom = scrollHeight - scrollTop - clientHeight < 10;
setIsScrolledToBottom(isAtBottom);
}
};
// Auto-scroll to bottom only if we were already at the bottom
useEffect(() => {
if (logRef.current && isScrolledToBottom) {
logRef.current.scrollTop = logRef.current.scrollHeight;
}
}, [log, isScrolledToBottom]);
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case 'running':
return 'bg-emerald-500/10 text-emerald-500';
case 'stopping':
return 'bg-amber-500/10 text-amber-500';
case 'stopped':
return 'bg-gray-500/10 text-gray-400';
case 'completed':
return 'bg-blue-500/10 text-blue-500';
case 'error':
return 'bg-rose-500/10 text-rose-500';
default:
return 'bg-gray-500/10 text-gray-400';
}
};
let status = job.status;
if (isStopping) {
status = 'stopping';
}
return (
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{/* Job Information Panel */}
<div className="col-span-2 bg-gray-900 rounded-xl shadow-lg overflow-hidden border border-gray-800 flex flex-col">
<div className="bg-gray-800 px-4 py-3 flex items-center justify-between">
<h2 className="text-gray-100">
<Info className="w-5 h-5 mr-2 -mt-1 text-amber-400 inline-block" /> {job.info}
</h2>
{isHFJob && hfJobSubmitted && jobConfig?.hf_job_id ? (
<HFJobStatus
hfJobId={jobConfig.hf_job_id}
hfJobUrl={jobConfig.hf_job_url}
/>
) : isHFJob && !hfJobSubmitted ? (
<span className="px-3 py-1 rounded-full text-sm bg-yellow-500/10 text-yellow-500">
Pending Submission
</span>
) : (
<span className={`px-3 py-1 rounded-full text-sm ${getStatusColor(job.status)}`}>
{job.status}
</span>
)}
</div>
<div className="p-4 space-y-6 flex flex-col flex-grow">
{/* Progress Bar */}
{isHFJob && !hfJobSubmitted ? (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Status</span>
<span className="text-yellow-400">Ready for cloud submission</span>
</div>
</div>
) : isHFJob ? (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Cloud Training</span>
<span className="text-gray-200">
Running on {jobConfig?.hardware || job.gpu_ids}
</span>
</div>
</div>
) : (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Progress</span>
<span className="text-gray-200">
Step {job.step} of {totalSteps}
</span>
</div>
<div className="w-full bg-gray-800 rounded-full h-2">
<div className="h-2 rounded-full bg-blue-500 transition-all" style={{ width: `${progress}%` }} />
</div>
</div>
)}
{/* Job Info Grid */}
<div className="grid gap-4 grid-cols-1 md:grid-cols-3">
<div className="flex items-center space-x-4">
<HardDrive className="w-5 h-5 text-blue-400" />
<div>
<p className="text-xs text-gray-400">Job Name</p>
<p className="text-sm font-medium text-gray-200">{job.name}</p>
</div>
</div>
<div className="flex items-center space-x-4">
{isHFJob ? (
<Cloud className="w-5 h-5 text-blue-400" />
) : (
<Cpu className="w-5 h-5 text-purple-400" />
)}
<div>
<p className="text-xs text-gray-400">
{isHFJob ? 'Hardware' : 'Assigned GPUs'}
</p>
<p className="text-sm font-medium text-gray-200">
{isHFJob ? (jobConfig?.hardware || job.gpu_ids) : `GPUs: ${job.gpu_ids}`}
</p>
</div>
</div>
<div className="flex items-center space-x-4">
<Gauge className="w-5 h-5 text-green-400" />
<div>
<p className="text-xs text-gray-400">Speed</p>
<p className="text-sm font-medium text-gray-200">{job.speed_string == '' ? '?' : job.speed_string}</p>
</div>
</div>
</div>
{/* Log - Now using flex-grow to fill remaining space */}
<div className="bg-gray-950 rounded-lg p-4 relative flex-grow min-h-60">
{isHFJob && hfJobSubmitted && jobConfig?.hf_job_url ? (
<div className="absolute inset-0 p-4 flex flex-col items-center justify-center">
<Cloud className="w-16 h-16 text-blue-400 mb-4" />
<p className="text-sm text-gray-300 mb-4 text-center">
This job is running on HF Jobs. View logs and monitor progress on the HuggingFace platform.
</p>
<a
href={jobConfig.hf_job_url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
<ExternalLink className="w-4 h-4 mr-2" />
View on HF Jobs
</a>
</div>
) : isHFJob && !hfJobSubmitted ? (
<div className="absolute inset-0 p-4 flex flex-col items-center justify-center">
<Cloud className="w-16 h-16 text-yellow-400 mb-4" />
<p className="text-sm text-gray-300 mb-4 text-center">
This HF Job is ready for submission. Edit the job clicking on the pen on the top right.
</p>
</div>
) : (
<div
ref={logRef}
className="text-xs text-gray-300 absolute inset-0 p-4 overflow-y-auto"
onScroll={handleScroll}
>
{statusLog === 'loading' && 'Loading log...'}
{statusLog === 'error' && 'Error loading log'}
{['success', 'refreshing'].includes(statusLog) && (
<div>
{logLines.map((line, index) => {
return <pre key={index}>{line}</pre>;
})}
</div>
)}
</div>
)}
</div>
</div>
</div>
{/* GPU Widget Panel */}
<div className="col-span-1">
{!isHFJob && (
<div>{isGPUInfoLoaded && gpuList.length > 0 && <GPUWidget gpu={gpuList[0]} />}</div>
)}
<div className={isHFJob ? '' : 'mt-4'}>
<FilesWidget jobID={job.id} />
</div>
</div>
</div>
);
}
|