File size: 2,607 Bytes
e6a1f55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { Loader2, CheckCircle2, Circle } from "lucide-react";
import { motion } from "framer-motion";

interface Props {
  isInferring: boolean;
  modelIds: string[];
  completedIds: Set<string>;
}

/** Per-detector progress chips. Smoothly animates from queued → running → done. */
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>
  );
}