'use client' import { useState, useMemo, useCallback } from 'react' import type { GuardrailData } from '@/lib/types' import { fmtPct, guardrailCreatorColor } from '@/lib/utils' import { LabLogo } from '@/components/LabLogo' type SortKey = 'rank' | 'recall' | 'precision' | 'f1' interface Props { guardrails: GuardrailData[] } function scoreColor(v: number | null): string { if (v === null) return 'var(--text-3)' if (v >= 0.9) return '#22C55E' if (v >= 0.75) return '#4ADE80' if (v >= 0.6) return '#F0A030' return '#F4333D' } function ScoreCell({ value, color, format, }: { value: number | null color: string format: (v: number | null) => string }) { const barWidth = value === null ? 0 : value return ( {value === null ? ( - ) : ( <>
{format(value)}
)} ) } function ExpandedRow({ guardrail }: { guardrail: GuardrailData }) { return (
Detections: {guardrail.detections} Last Run: {guardrail.lastRun}
) } export default function GuardrailsTable({ guardrails }: Props) { const [sortKey, setSortKey] = useState('recall') const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc') const [query, setQuery] = useState('') const [expandedRank, setExpandedRank] = useState(null) const [activeCreator, setActiveCreator] = useState(null) const creators = useMemo(() => [...new Set(guardrails.map(g => g.creator))].sort(), [guardrails]) const handleSort = useCallback((key: SortKey) => { if (sortKey === key) { setSortDir(d => (d === 'asc' ? 'desc' : 'asc')) } else { setSortKey(key) setSortDir('desc') } }, [sortKey]) const sorted = useMemo(() => { let list = [...guardrails] if (activeCreator) list = list.filter(g => g.creator === activeCreator) if (query.trim()) { const q = query.toLowerCase() list = list.filter( g => g.guardrail.toLowerCase().includes(q) || g.creator.toLowerCase().includes(q), ) } list.sort((a, b) => { let va: number | null, vb: number | null if (sortKey === 'rank') { va = a.rank; vb = b.rank } else if (sortKey === 'recall') { va = a.recall; vb = b.recall } else if (sortKey === 'precision') { va = a.precision; vb = b.precision } else { va = a.f1; vb = b.f1 } if (va === null && vb === null) return 0 if (va === null) return 1 if (vb === null) return -1 return sortDir === 'asc' ? va - vb : vb - va }) return list }, [guardrails, activeCreator, query, sortKey, sortDir]) function SortArrow({ k }: { k: SortKey }) { if (sortKey !== k) return return {sortDir === 'asc' ? '↑' : '↓'} } return (
{/* Controls */}
setQuery(e.target.value)} />
{creators.map(c => { const cc = guardrailCreatorColor(c) return ( ) })}
{/* Table */}
{sorted.map((g, i) => { const isExpanded = expandedRank === g.rank const cc = guardrailCreatorColor(g.creator) return [ fmtPct(v)} /> fmtPct(v)} /> fmtPct(v)} /> , isExpanded && ( ), ] })}
handleSort('rank')} className={sortKey === 'rank' ? 'sort-active' : ''} style={{ width: 48 }}> # Creator Guardrail handleSort('recall')} className={sortKey === 'recall' ? 'sort-active' : ''}> Recall handleSort('precision')} className={sortKey === 'precision' ? 'sort-active' : ''}> Precision handleSort('f1')} className={sortKey === 'f1' ? 'sort-active' : ''}> F1
{g.rank}
{sorted.length} of {guardrails.length} guardrails
) }