Spaces:
Running
Running
File size: 2,892 Bytes
5a0e691 2f67ecf f42f5a6 2f67ecf 5a0e691 9a28cd0 5a0e691 973dd4c 5a0e691 88aa370 5a0e691 9a28cd0 5a0e691 2f67ecf 88aa370 f42f5a6 88aa370 5a0e691 9a28cd0 5a0e691 9a28cd0 5a0e691 2f67ecf 5a0e691 f42f5a6 5a0e691 f42f5a6 5a0e691 f42f5a6 5a0e691 2f67ecf 5a0e691 f42f5a6 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 { CheckCircle2, XCircle } from 'lucide-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="bg-white rounded-2xl border border-gray-200 p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm font-semibold text-gray-900">Pipeline progress</h3>
<span className="text-xs text-gray-500">{pct}%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5 mb-5">
<div
className={`h-1.5 rounded-full transition-all duration-500 ${isFailed ? 'bg-error' : 'bg-accent'}`}
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-teal-600 shrink-0" />
) : isFailed ? (
<XCircle className="w-5 h-5 text-red-500 shrink-0" />
) : active ? (
<div className="w-5 h-5 rounded-full bg-accent animate-pulse shrink-0" />
) : (
<div className="w-5 h-5 rounded-full border-2 border-gray-300 shrink-0" />
)}
<span className={`text-sm ${done ? 'text-gray-900 font-medium' : active ? 'text-teal-700' : 'text-gray-400'}`}>
{step.label}
</span>
</motion.div>
);
})}
</div>
{isComplete && (
<p className="text-xs text-accent mt-3 font-medium">All steps complete</p>
)}
{isFailed && (
<p className="text-xs text-red-500 mt-3 font-medium">Pipeline failed</p>
)}
</motion.div>
);
}
|