Spaces:
Sleeping
Sleeping
File size: 4,854 Bytes
4b445f6 | 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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | "use client";
import { motion } from "framer-motion";
import type { Finding, AgentKind } from "@/lib/types";
interface AgentBreakdownProps {
findings: Finding[];
}
const AGENT_META: Record<
AgentKind,
{
icon: React.ReactNode;
label: string;
color: string;
iconBg: string;
border: string;
}
> = {
security: {
icon: (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-5 h-5">
<path fillRule="evenodd" d="M12.516 2.17a.75.75 0 00-1.032 0 11.209 11.209 0 01-7.877 3.08.75.75 0 00-.722.515A12.74 12.74 0 002.25 9.75c0 5.942 4.064 10.933 9.563 12.348a.749.749 0 00.374 0c5.499-1.415 9.563-6.406 9.563-12.348 0-1.39-.223-2.73-.635-3.985a.75.75 0 00-.722-.516 11.209 11.209 0 01-7.877-3.08z" clipRule="evenodd" />
</svg>
),
label: "Security",
color: "text-red-400",
iconBg: "bg-red-500/10 text-red-400",
border: "border-red-500/[0.08]",
},
performance: {
icon: (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-5 h-5">
<path fillRule="evenodd" d="M14.615 1.595a.75.75 0 01.359.852L12.982 9.75h7.268a.75.75 0 01.548 1.262l-10.5 11.25a.75.75 0 01-1.272-.71l1.992-7.302H3.75a.75.75 0 01-.548-1.262l10.5-11.25a.75.75 0 01.913-.143z" clipRule="evenodd" />
</svg>
),
label: "Performance",
color: "text-amber-400",
iconBg: "bg-amber-500/10 text-amber-400",
border: "border-amber-500/[0.08]",
},
style: {
icon: (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-5 h-5">
<path d="M11.7 2.805a.75.75 0 01.6 0A60.65 60.65 0 0122.83 8.72a.75.75 0 01-.231 1.337 49.949 49.949 0 00-9.902 3.912l-.003.002-.34.18a.75.75 0 01-.707 0A50.009 50.009 0 007.5 12.174v-.224c0-.131.067-.248.172-.311a54.614 54.614 0 014.653-2.52.75.75 0 00-.65-1.352 56.129 56.129 0 00-4.78 2.589 1.858 1.858 0 00-.859 1.228 49.803 49.803 0 00-4.634-1.527.75.75 0 01-.231-1.337A60.653 60.653 0 0111.7 2.805z" />
<path d="M13.06 15.473a48.45 48.45 0 017.666-3.282c.134 1.414.22 2.843.255 4.285a.75.75 0 01-.46.71 47.878 47.878 0 00-8.105 4.342.75.75 0 01-.832 0 47.877 47.877 0 00-8.104-4.342.75.75 0 01-.461-.71c.035-1.442.121-2.87.255-4.286A48.4 48.4 0 016 13.18v1.27a1.5 1.5 0 00-.14 2.508c-.09.38-.222.753-.397 1.11.452.213.901.434 1.346.661a6.729 6.729 0 00.551-1.608 1.5 1.5 0 00.14-2.67v-.645a48.549 48.549 0 013.44 1.668 2.25 2.25 0 002.12 0z" />
<path d="M4.462 19.462c.42-.419.753-.89 1-1.394.453.213.902.434 1.347.661a6.743 6.743 0 01-1.286 1.794.75.75 0 11-1.06-1.06z" />
</svg>
),
label: "Style",
color: "text-cyan-400",
iconBg: "bg-cyan-500/10 text-cyan-400",
border: "border-cyan-500/[0.08]",
},
};
export default function AgentBreakdown({ findings }: AgentBreakdownProps) {
const agents: AgentKind[] = ["security", "performance", "style"];
const stats = agents.map((agent) => {
const agentFindings = findings.filter((f) => f.agent === agent);
const catCounts: Record<string, number> = {};
agentFindings.forEach((f) => {
catCounts[f.category] = (catCounts[f.category] ?? 0) + 1;
});
const topCategory =
Object.entries(catCounts).sort((a, b) => b[1] - a[1])[0]?.[0] ?? "—";
return {
agent,
count: agentFindings.length,
topCategory,
meta: AGENT_META[agent],
};
});
return (
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{stats.map(({ agent, count, topCategory, meta }, i) => (
<motion.div
key={agent}
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: i * 0.08 }}
whileHover={{ y: -2, transition: { duration: 0.15 } }}
className={`glass rounded-2xl p-5 border ${meta.border} transition-colors duration-300`}
>
<div className="flex items-center gap-3 mb-4">
<div
className={`w-9 h-9 rounded-xl ${meta.iconBg} flex items-center justify-center`}
>
{meta.icon}
</div>
<h3 className={`text-sm font-semibold ${meta.color}`}>
{meta.label}
</h3>
</div>
<p className="text-3xl font-bold text-white tabular-nums">{count}</p>
<p className="text-[11px] text-zinc-600 mt-0.5 uppercase tracking-wider">
findings
</p>
<div className="mt-4 pt-3 border-t border-white/[0.04]">
<p className="text-[10px] text-zinc-600 uppercase tracking-wider">
Top category
</p>
<p className="text-xs text-zinc-400 font-medium truncate mt-0.5">
{topCategory}
</p>
</div>
</motion.div>
))}
</div>
);
}
|