jayvatliq's picture
Upload folder using huggingface_hub
16533c4 verified
Raw
History Blame Contribute Delete
4.3 kB
import { CheckCircle2, XCircle, ChevronDown, ChevronUp } from "lucide-react";
import { useState } from "react";
import type { AngleSummary } from "@/types";
import { ANGLE_DESCRIPTIONS } from "@/utils/constants";
interface AngleCardProps {
summary: AngleSummary;
}
export function AngleCard({ summary }: AngleCardProps) {
const [expanded, setExpanded] = useState(false);
const statusColor = summary.detected
? "text-emerald-400 border-emerald-500/30 bg-emerald-500/5"
: "text-red-400 border-red-500/30 bg-red-500/5";
const scorePercent = Math.round(summary.bestScore * 100);
return (
<div
className={[
"rounded-xl border transition-all duration-200",
statusColor,
"hover:shadow-lg cursor-pointer",
].join(" ")}
onClick={() => summary.detected && setExpanded((e) => !e)}
>
<div className="flex items-center gap-4 p-4">
{/* Icon */}
<div className="flex-shrink-0">
{summary.detected ? (
<CheckCircle2 className="w-6 h-6 text-emerald-400" />
) : (
<XCircle className="w-6 h-6 text-red-400" />
)}
</div>
{/* Info */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-semibold text-white text-sm">{summary.angle}</span>
{summary.detected && (
<span className="text-xs px-2 py-0.5 rounded-full bg-emerald-500/20 text-emerald-300 font-medium">
Detected
</span>
)}
{!summary.detected && (
<span className="text-xs px-2 py-0.5 rounded-full bg-red-500/20 text-red-300 font-medium">
Missing
</span>
)}
</div>
<p className="text-slate-500 text-xs mt-0.5 truncate">
{ANGLE_DESCRIPTIONS[summary.angle]}
</p>
</div>
{/* Score bar */}
{summary.detected && (
<div className="flex-shrink-0 flex flex-col items-end gap-1">
<span className="text-xs font-mono text-emerald-400">{scorePercent}%</span>
<div className="w-20 h-1.5 rounded-full bg-surface-500 overflow-hidden">
<div
className="h-full rounded-full bg-emerald-400 transition-all duration-700"
style={{ width: `${scorePercent}%` }}
/>
</div>
</div>
)}
{/* Expand toggle */}
{summary.detected && summary.detectedIn.length > 0 && (
<div className="flex-shrink-0 text-slate-500">
{expanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
</div>
)}
</div>
{/* Expanded — show images and filenames */}
{expanded && summary.detectedIn.length > 0 && (
<div className="px-4 pb-4 border-t border-emerald-500/10 pt-3 animate-fade-in">
<p className="text-xs text-slate-400 mb-3 font-medium uppercase tracking-wider">Found in</p>
{summary.previewUrls.length > 0 ? (
<div className={`grid gap-3 ${summary.previewUrls.length === 1 ? "grid-cols-1" : "grid-cols-2"}`}>
{summary.previewUrls.map((url, i) => (
<div key={summary.detectedIn[i] ?? url} className="rounded-lg overflow-hidden border border-emerald-500/20">
<img
src={url}
alt={summary.detectedIn[i]}
className="w-full object-cover max-h-52"
/>
<p className="text-xs text-slate-400 px-2 py-1.5 bg-surface-800 font-mono truncate">
{summary.detectedIn[i]}
</p>
</div>
))}
</div>
) : (
<div className="flex flex-col gap-1">
{summary.detectedIn.map((fname) => (
<div key={fname} className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-400 flex-shrink-0" />
<span className="text-sm text-slate-300 font-mono truncate">{fname}</span>
</div>
))}
</div>
)}
</div>
)}
</div>
);
}