File size: 2,917 Bytes
5a0e691
 
2f67ecf
349f22c
2f67ecf
5a0e691
 
 
 
 
 
 
9a28cd0
 
 
 
 
 
5a0e691
 
 
 
 
 
 
973dd4c
5a0e691
 
 
88aa370
5a0e691
9a28cd0
 
5a0e691
d54a751
88aa370
d54a751
 
88aa370
c976c65
88aa370
5e98b5a
88aa370
 
 
5a0e691
9a28cd0
5a0e691
9a28cd0
5a0e691
2f67ecf
5a0e691
c976c65
5a0e691
c976c65
5a0e691
5e98b5a
5a0e691
c976c65
5a0e691
c976c65
5a0e691
 
2f67ecf
5a0e691
 
 
 
5e98b5a
5a0e691
 
c976c65
5a0e691
2f67ecf
5a0e691
 
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
'use client';

import { motion } from 'framer-motion';
import { CheckCircle as CheckCircle2, XCircle } from '@phosphor-icons/react';
import { fadeUp, fadeIn } from '@/lib/animations';

interface Step {
  name: string;
  label: string;
}

const STEPS: Step[] = [
  { name: 'submitted_to_ncbi', label: 'Submitted to NCBI BLAST' },
  { name: 'polling_ncbi',      label: 'NCBI is searching' },
  { name: 'parsing',           label: 'Reading results' },
  { name: 'interpreting',      label: 'Writing your explanation' },
  { name: 'fetching_alphafold', label: 'Fetching AlphaFold structure' },
  { name: 'complete',          label: 'Complete' },
];

interface JobProgressProps {
  stepsCompleted: string[];
  status: string;
}

export function JobProgress({ stepsCompleted, status }: JobProgressProps) {
  const isComplete = status === 'complete';
  const isFailed = status === 'failed';
  const stepSet = new Set(stepsCompleted);
  const pct = stepsCompleted.length === 0 ? 0 : Math.round((stepsCompleted.length / STEPS.length) * 100);

  const nextPendingIndex = stepsCompleted.length;

  return (
    <motion.div variants={fadeUp} className="glass-card p-6">
      <div className="flex items-center justify-between mb-4">
        <h3 className="text-sm font-semibold text-text-primary">Pipeline progress</h3>
        <span className="text-xs text-text-muted">{pct}%</span>
      </div>
      <div className="w-full bg-surface-0 rounded-full h-1.5 mb-5">
        <div
          className={`h-1.5 rounded-full transition-all duration-500 ${isFailed ? 'bg-error' : 'bg-accent-cyan'}`}
          style={{ width: `${pct}%` }}
        />
      </div>
      <div className="space-y-3">
        {STEPS.map((step, i) => {
          const done = stepSet.has(step.name);
          const active = status === 'running' && !done && i === nextPendingIndex;
          return (
            <motion.div key={step.name} variants={fadeIn} className="flex items-center gap-3">
              {done ? (
                <CheckCircle2 className="w-5 h-5 text-accent-cyan shrink-0" />
              ) : isFailed ? (
                <XCircle className="w-5 h-5 text-error shrink-0" />
              ) : active ? (
                <div className="w-5 h-5 rounded-full bg-accent-cyan animate-pulse shrink-0" />
              ) : (
                <div className="w-5 h-5 rounded-full border-2 border-glass-border shrink-0" />
              )}
              <span className={`text-sm ${done ? 'text-text-primary font-medium' : active ? 'text-accent-cyan' : 'text-text-muted'}`}>
                {step.label}
              </span>
            </motion.div>
          );
        })}
      </div>
      {isComplete && (
        <p className="text-xs text-accent-cyan mt-3 font-medium">All steps complete</p>
      )}
      {isFailed && (
        <p className="text-xs text-error mt-3 font-medium">Pipeline failed</p>
      )}
    </motion.div>
  );
}