| import { Loader2, CheckCircle2, Circle } from "lucide-react"; |
| import { motion } from "framer-motion"; |
|
|
| interface Props { |
| isInferring: boolean; |
| modelIds: string[]; |
| completedIds: Set<string>; |
| } |
|
|
| |
| export default function InferenceProgress({ |
| isInferring, |
| modelIds, |
| completedIds, |
| }: Props) { |
| if (!isInferring && completedIds.size === 0) { |
| return null; |
| } |
|
|
| const totalDone = completedIds.size; |
| const totalActive = modelIds.length; |
| const progressPct = |
| totalActive > 0 ? (totalDone / totalActive) * 100 : 0; |
|
|
| return ( |
| <motion.div |
| initial={{ opacity: 0, y: -4 }} |
| animate={{ opacity: 1, y: 0 }} |
| className="panel-alt p-3" |
| > |
| <div className="flex items-center justify-between gap-2"> |
| <div className="flex items-center gap-2"> |
| {isInferring ? ( |
| <Loader2 className="h-3.5 w-3.5 animate-spin text-cyber" /> |
| ) : ( |
| <CheckCircle2 className="h-3.5 w-3.5 text-cyber" /> |
| )} |
| <span className="font-mono text-xs text-ink-dim"> |
| {isInferring |
| ? `running ${totalDone}/${totalActive} detectors…` |
| : `complete · ${totalActive}/${totalActive}`} |
| </span> |
| </div> |
| <span className="font-mono text-[10px] text-ink-mute"> |
| {progressPct.toFixed(0)}% |
| </span> |
| </div> |
| |
| {/* Overall progress bar */} |
| <div className="mt-2 h-1 overflow-hidden rounded-full bg-bg/50"> |
| <motion.div |
| initial={{ width: 0 }} |
| animate={{ width: `${progressPct}%` }} |
| transition={{ duration: 0.3 }} |
| className="h-full bg-cyber" |
| /> |
| </div> |
| |
| {/* Per-detector chips */} |
| <div className="mt-2 grid gap-1.5 sm:grid-cols-2 lg:grid-cols-4"> |
| {modelIds.map((id) => { |
| const done = completedIds.has(id); |
| return ( |
| <div |
| key={id} |
| className="flex items-center justify-between gap-2 rounded-md border border-line bg-bg/40 px-2 py-1 font-mono text-[10px]" |
| > |
| <span className="truncate text-ink">{id}</span> |
| {done ? ( |
| <CheckCircle2 className="h-3 w-3 shrink-0 text-cyber" /> |
| ) : isInferring ? ( |
| <Loader2 className="h-3 w-3 shrink-0 animate-spin text-ink-dim" /> |
| ) : ( |
| <Circle className="h-3 w-3 shrink-0 text-ink-mute" /> |
| )} |
| </div> |
| ); |
| })} |
| </div> |
| </motion.div> |
| ); |
| } |
|
|