import { motion } from "framer-motion"; import { ChevronDown, ChevronUp, AlertTriangle, CheckCircle2, Clock, Cpu, Beaker, Sparkle, } from "lucide-react"; import { useState } from "react"; import type { ModelResult } from "@/types/inference"; import { classifyStatus, cn, confidenceColor, formatMs, formatPercent, formatParams, } from "@/lib/utils"; import ConfidenceGauge from "./ConfidenceGauge"; interface Props { result: ModelResult; } const PAPER_IDS = new Set([ "nes2net", "sonar", "bicrossmamba_st", "voiceradar", "holi_antispoof", ]); export default function ModelCard({ result }: Props) { const [open, setOpen] = useState(false); const isFake = result.prediction === "fake"; const status = classifyStatus(result.status); const confColor = confidenceColor(result.confidence, result.prediction); const isPaper = PAPER_IDS.has(result.model_id); const ribbon = isPaper ? "bg-warn" : "bg-cyber"; const familyLabel = isPaper ? "paper architecture" : "production"; const FamilyIcon = isPaper ? Beaker : Sparkle; return ( {/* Family ribbon along the left edge */}
{/* Soft accent glow */}
{/* ── Header ────────────────────────────────────────────────── */}
{result.display_name}
{familyLabel} · {result.paradigm}
{result.prediction.toUpperCase()}
{/* ── Status pill row ───────────────────────────────────────── */}
{status === "live" && } {status === "fallback" && } {status} {formatMs(result.inference_time_ms)} {formatParams(result.params_k)}
{/* ── Gauge + numbers ──────────────────────────────────────── */}
confidence {formatPercent(result.confidence)}
{result.eer_reference?.df21 != null && (
DF21 EER:{" "} {result.eer_reference.df21}%
)} {result.eer_reference?.in_the_wild != null && (
ITW EER:{" "} {result.eer_reference.in_the_wild}%
)} {result.eer_reference?.la21 != null && (
LA21 EER:{" "} {result.eer_reference.la21}%
)}
{/* Spacer pushes the details block to the bottom for visual alignment */}
{/* ── Status detail strip ──────────────────────────────────── */} {status === "fallback" && (
No checkpoint — routed to{" "} {result.fallback_to ?? "fallback"}. Drop weights in{" "} backend/checkpoints/ to go live.
)} {/* ── Why? expander ───────────────────────────────────────── */} {open && ( {result.notes && (

{result.notes}

)}
)} ); } function FeatureGrid({ features }: { features: Record }) { const flat = Object.entries(features).filter( ([, v]) => typeof v === "number" || typeof v === "string" || typeof v === "boolean" ); if (!flat.length) return null; return (
{flat.slice(0, 12).map(([k, v]) => (
{k} {String(v)}
))}
); }