Spaces:
Running
Running
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>
);
}
|