Files
reTraining/frontend/src/components/Sidebar.jsx
T

191 lines
5.8 KiB
React

import { useEffect, useState } from 'react'
import { api } from '../api'
import { navigate } from '../App'
import {
FolderIcon,
DatabaseIcon,
RocketIcon,
TagIcon,
BarChartIcon,
SunIcon,
MoonIcon,
CpuIcon,
LayersIcon,
ZapIcon,
} from './Icons'
export default function Sidebar({ route, currentProject, theme, onToggleTheme }) {
const [health, setHealth] = useState(null)
const [notice, setNotice] = useState('')
useEffect(() => {
let timer
const fetchHealth = () => {
api.health().then(setHealth).catch(() => {})
}
fetchHealth()
timer = setInterval(fetchHealth, 3000)
return () => clearInterval(timer)
}, [])
// REQ-192: free the SAM3 model's VRAM on demand; the 3 s health poll is what
// confirms it, this only flips the badge straight away when it worked.
const releaseSam3 = async () => {
try {
await api.releaseSam3()
setHealth((current) => (current ? { ...current, model_loaded: false } : current))
} catch (exc) {
setNotice(exc.message)
setTimeout(() => setNotice(''), 4000)
}
}
const pId = currentProject?.id || route.projectId || 1
const handleNav = (e, path) => {
e.preventDefault()
navigate(path)
}
return (
<header className="roboflow-topbar">
<div className="topbar-logo" onClick={(e) => handleNav(e, '/projects')} style={{ cursor: 'pointer' }}>
<span style={{ fontWeight: 700, fontSize: '0.95rem', letterSpacing: '-0.01em', color: '#f4f4f5' }}>
Dataset Enrichment
</span>
</div>
<nav className="topbar-nav">
<a
href="#/projects"
onClick={(e) => handleNav(e, '/projects')}
className={`topbar-item ${route.name === 'projects' ? 'active' : ''}`}
>
<FolderIcon size={15} />
<span>Projects</span>
</a>
<a
href={`#/projects/${pId}`}
onClick={(e) => handleNav(e, `/projects/${pId}`)}
className={`topbar-item ${route.name === 'library' ? 'active' : ''}`}
>
<DatabaseIcon size={15} />
<span>Video Archive</span>
</a>
<a
href={`#/projects/${pId}/batches`}
onClick={(e) => handleNav(e, `/projects/${pId}/batches`)}
className={`topbar-item ${route.name === 'batches' || route.name === 'review' ? 'active' : ''}`}
>
<TagIcon size={15} />
<span>Batches</span>
</a>
<a
href={`#/projects/${pId}/data-prep`}
onClick={(e) => handleNav(e, `/projects/${pId}/data-prep`)}
className={`topbar-item ${route.name === 'data-prep' ? 'active' : ''}`}
>
<BarChartIcon size={15} />
<span>Data Preparation</span>
</a>
<a
href={`#/projects/${pId}/datasets`}
onClick={(e) => handleNav(e, `/projects/${pId}/datasets`)}
className={`topbar-item ${route.name === 'datasets' ? 'active' : ''}`}
>
<LayersIcon size={15} />
<span>Datasets</span>
</a>
<a
href={`#/projects/${pId}/models`}
onClick={(e) => handleNav(e, `/projects/${pId}/models`)}
className={`topbar-item ${route.name === 'models' ? 'active' : ''}`}
>
<RocketIcon size={15} />
<span>Models & Training</span>
</a>
<a
href={`#/projects/${pId}/live-count`}
onClick={(e) => handleNav(e, `/projects/${pId}/live-count`)}
className={`topbar-item ${route.name === 'live-count' ? 'active' : ''}`}
>
<ZapIcon size={15} />
<span>Live Counting</span>
</a>
<a
href={`#/projects/${pId}/counting-bench`}
onClick={(e) => handleNav(e, `/projects/${pId}/counting-bench`)}
className={`topbar-item ${route.name === 'counting-bench' ? 'active' : ''}`}
>
<DatabaseIcon size={15} />
<span>Counting Accuracy</span>
</a>
<a
href="#/sam3-playground"
onClick={(e) => handleNav(e, '/sam3-playground')}
className={`topbar-item ${route.name === 'sam3-playground' ? 'active' : ''}`}
>
<CpuIcon size={15} />
<span>SAM3 Playground</span>
</a>
</nav>
<div className="topbar-right">
{health && (
<div className="topbar-health">
<span className="health-badge" title={health.gpu || ''}>
GPU: {health.gpu ? health.gpu.replace('NVIDIA GeForce ', '').replace(' Laptop GPU', '') : 'CPU'}
</span>
<span className="health-badge">
VRAM: {health.vram_free_gb ? `${health.vram_free_gb.toFixed(1)}GB` : 'N/A'}
</span>
{notice && <span className="health-badge">{notice}</span>}
{!health.sam3_ready ? (
<span className="health-badge" title="Not enough free VRAM for SAM3 — free the card first">
SAM3: Off
</span>
) : health.model_loaded ? (
<button
type="button"
className="health-badge ready"
title="SAM3 is in VRAM (~3.9 GB) — click to free it now"
onClick={releaseSam3}
>
SAM3: Loaded
</button>
) : (
<span
className="health-badge ready"
title="SAM3 not in VRAM — it loads on the next use (~12 s)"
>
SAM3: Idle
</span>
)}
</div>
)}
<button
className="topbar-theme-toggle"
onClick={onToggleTheme}
title={theme === 'dark' ? 'Switch to Light Mode' : 'Switch to Dark Mode'}
>
{theme === 'dark' ? <SunIcon size={15} /> : <MoonIcon size={15} />}
</button>
</div>
</header>
)
}