'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 */}
{creators.map(c => {
const cc = guardrailCreatorColor(c)
return (
)
})}
{/* Table */}
| 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
|
{sorted.map((g, i) => {
const isExpanded = expandedRank === g.rank
const cc = guardrailCreatorColor(g.creator)
return [
|
{g.rank}
|
|
|
fmtPct(v)}
/>
fmtPct(v)}
/>
fmtPct(v)}
/>
,
isExpanded && (
),
]
})}
{sorted.length} of {guardrails.length} guardrails
)
}