gpt-engineer-app[bot] commited on
Commit ·
999d380
1
Parent(s): 859d450
Changes
Browse files
src/components/status-timeline.tsx
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Check, Loader2, AlertTriangle, Clock } from "lucide-react";
|
| 2 |
+
import { cn } from "@/lib/utils";
|
| 3 |
+
|
| 4 |
+
type Stage = "queued" | "analyzing" | "completed" | "failed";
|
| 5 |
+
|
| 6 |
+
const STAGES: { key: Stage; label: string }[] = [
|
| 7 |
+
{ key: "queued", label: "Queued" },
|
| 8 |
+
{ key: "analyzing", label: "Analyzing" },
|
| 9 |
+
{ key: "completed", label: "Completed" },
|
| 10 |
+
];
|
| 11 |
+
|
| 12 |
+
function mapStatus(status: string): Stage {
|
| 13 |
+
if (status === "complete" || status === "completed") return "completed";
|
| 14 |
+
if (status === "analyzing" || status === "processing") return "analyzing";
|
| 15 |
+
if (status === "failed") return "failed";
|
| 16 |
+
return "queued";
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
export function StatusTimeline({ status }: { status: string }) {
|
| 20 |
+
const current = mapStatus(status);
|
| 21 |
+
const failed = current === "failed";
|
| 22 |
+
const currentIdx = failed ? 1 : STAGES.findIndex((s) => s.key === current);
|
| 23 |
+
|
| 24 |
+
return (
|
| 25 |
+
<div className="rounded-xl border border-border bg-surface/60 p-4">
|
| 26 |
+
<div className="text-[10px] uppercase tracking-widest text-muted-foreground font-mono mb-3">
|
| 27 |
+
Pipeline status
|
| 28 |
+
</div>
|
| 29 |
+
<div className="flex items-center gap-2">
|
| 30 |
+
{STAGES.map((stage, i) => {
|
| 31 |
+
const done = i < currentIdx;
|
| 32 |
+
const active = i === currentIdx && !failed;
|
| 33 |
+
const isFailed = failed && i === 1;
|
| 34 |
+
const Icon = isFailed
|
| 35 |
+
? AlertTriangle
|
| 36 |
+
: done
|
| 37 |
+
? Check
|
| 38 |
+
: active
|
| 39 |
+
? Loader2
|
| 40 |
+
: Clock;
|
| 41 |
+
return (
|
| 42 |
+
<div key={stage.key} className="flex items-center gap-2 flex-1 min-w-0">
|
| 43 |
+
<div
|
| 44 |
+
className={cn(
|
| 45 |
+
"h-7 w-7 rounded-full flex items-center justify-center shrink-0 border",
|
| 46 |
+
isFailed && "bg-destructive/15 border-destructive text-destructive",
|
| 47 |
+
done && "bg-severity-low/20 border-severity-low text-severity-low",
|
| 48 |
+
active && "bg-primary/15 border-primary text-primary",
|
| 49 |
+
!done && !active && !isFailed && "bg-surface border-border text-muted-foreground"
|
| 50 |
+
)}
|
| 51 |
+
>
|
| 52 |
+
<Icon className={cn("h-3.5 w-3.5", active && "animate-spin")} />
|
| 53 |
+
</div>
|
| 54 |
+
<div className="min-w-0 flex-1">
|
| 55 |
+
<div
|
| 56 |
+
className={cn(
|
| 57 |
+
"text-xs font-medium truncate",
|
| 58 |
+
isFailed && "text-destructive",
|
| 59 |
+
active && "text-foreground",
|
| 60 |
+
!active && !isFailed && "text-muted-foreground"
|
| 61 |
+
)}
|
| 62 |
+
>
|
| 63 |
+
{isFailed ? "Failed" : stage.label}
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
{i < STAGES.length - 1 && (
|
| 67 |
+
<div
|
| 68 |
+
className={cn(
|
| 69 |
+
"h-px flex-1 min-w-4",
|
| 70 |
+
done ? "bg-severity-low/60" : isFailed ? "bg-destructive/40" : "bg-border"
|
| 71 |
+
)}
|
| 72 |
+
/>
|
| 73 |
+
)}
|
| 74 |
+
</div>
|
| 75 |
+
);
|
| 76 |
+
})}
|
| 77 |
+
</div>
|
| 78 |
+
</div>
|
| 79 |
+
);
|
| 80 |
+
}
|