import React, { useState } from 'react'; import { useCVStore } from '@/store/cvStore'; import { GitBranch, Target, Clock, ArrowRight, Activity, FlaskConical, Trophy, BarChart3, Eye, Download, CheckCircle2 } from 'lucide-react'; import { useUserStore } from '@/store/userStore'; const CVExperimentTracker: React.FC = () => { const { isDark } = useUserStore(); const { trainingJobs, activeDatasetId, setActiveJobId, setActiveTab } = useCVStore(); const [selectedJobs, setSelectedJobs] = useState([]); const [showComparison, setShowComparison] = useState(false); const bgCard = isDark ? 'bg-white/[0.03]' : 'bg-white'; const border = isDark ? 'border-white/[0.06]' : 'border-slate-200'; const textH = isDark ? 'text-white' : 'text-slate-900'; const textM = isDark ? 'text-slate-400' : 'text-slate-600'; const textS = isDark ? 'text-slate-500' : 'text-slate-500'; const datasetJobs = trainingJobs.filter(j => j.datasetId === activeDatasetId); const completedJobs = datasetJobs.filter(j => j.status === 'completed'); const toggleSelect = (id: string) => { setSelectedJobs(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]); }; const bestJob = completedJobs.length > 0 ? completedJobs.reduce((best, j) => (j.metrics?.mAP50 || 0) > (best.metrics?.mAP50 || 0) ? j : best, completedJobs[0]) : null; if (completedJobs.length === 0) { return (

No Experiments Yet

Run your first training job to start tracking experiments and comparing models side-by-side.

); } const selectedForComparison = completedJobs.filter(j => selectedJobs.includes(j.id)); return (
{/* Header */}

Experiment History

{completedJobs.length} completed runs ยท {datasetJobs.filter(j => j.status === 'running').length} running

{selectedJobs.length >= 2 && ( )}
{/* Comparison Panel */} {showComparison && selectedForComparison.length >= 2 && (

Side-by-Side Comparison

{selectedForComparison.map(j => ( ))} {[ { label: 'mAP@50', key: 'mAP50', fmt: (v: number) => `${(v * 100).toFixed(1)}%` }, { label: 'Precision', key: 'precision', fmt: (v: number) => `${(v * 100).toFixed(1)}%` }, { label: 'Recall', key: 'recall', fmt: (v: number) => `${(v * 100).toFixed(1)}%` }, { label: 'F1 Score', key: 'f1', fmt: (v: number) => v.toFixed(4) }, { label: 'Inference', key: 'inferenceTime', fmt: (v: number) => `${v}ms` }, { label: 'Model Size', key: 'modelSizeMB', fmt: (v: number) => `${v}MB` }, ].map((metric, i) => { const values = selectedForComparison.map(j => (j.metrics as any)?.[metric.key] || 0); const maxVal = Math.max(...values); return ( {selectedForComparison.map((j, idx) => { const val = (j.metrics as any)?.[metric.key] || 0; const isBest = val === maxVal && metric.key !== 'inferenceTime' && metric.key !== 'modelSizeMB'; return ( ); })} ); })} {selectedForComparison.map(j => ( ))} {selectedForComparison.map(j => ( ))}
Metric {j.config.model.toUpperCase()} {j.id === bestJob?.id && }
{metric.label} {metric.fmt(val)} {isBest && ๐Ÿ†}
Epochs{j.progress.totalEpochs}
Mode {j.mode}
)} {/* Experiments Table */}
{completedJobs.map((job) => ( ))}
{ if (e.target.checked) setSelectedJobs(completedJobs.map(j => j.id)); else setSelectedJobs([]); }} /> Run ID Architecture Mode Epochs mAP@50 Precision Recall Actions
toggleSelect(job.id)} className="rounded text-emerald-500 focus:ring-emerald-500 bg-transparent" />
{job.id.substring(0, 10)}
{job.id === bestJob?.id && }
{new Date(job.startedAt).toLocaleString()}
{job.config.model.toUpperCase()}
img: {job.config.imageSize}px
{job.mode} {job.progress.totalEpochs} {((job.metrics?.mAP50 || 0) * 100).toFixed(1)}% {((job.metrics?.precision || 0) * 100).toFixed(1)}% {((job.metrics?.recall || 0) * 100).toFixed(1)}%
); }; export default CVExperimentTracker;