"use client" import { useState, useMemo } from "react" import { cn } from "@/lib/utils" import { MultiSelect } from "@/components/multi-select" function Tooltip({ text, children }: { text: string; children: React.ReactNode }) { const [show, setShow] = useState(false) return ( setShow(true)} onMouseLeave={() => setShow(false)} > {children} {show && ( {text} )} ) } export interface StatCardProps { label: string value: string | number subtext?: string mono?: boolean } export function StatCard({ label, value, subtext, mono }: StatCardProps) { return (
{label}
{value}
{subtext &&
{subtext}
}
) } export interface StatsGridProps { cards: StatCardProps[] } export function StatsGrid({ cards }: StatsGridProps) { return (
{cards.map((card, idx) => ( ))}
) } export interface QuestionTypeStats { accuracy: number correct: number total: number } export interface AccuracyByTypeProps { byQuestionType: Record } export function AccuracyByType({ byQuestionType }: AccuracyByTypeProps) { if (!byQuestionType || Object.keys(byQuestionType).length === 0) { return null } return (

Accuracy by Question Type

{Object.entries(byQuestionType).map(([type, stats]) => (
{type.replace(/[-_]/g, " ")}
{(stats.accuracy * 100).toFixed(0)}%
{stats.correct}/{stats.total}
))}
) } export interface LatencyStats { min: number max: number mean: number median: number p95: number p99: number } export interface RetrievalStats { hitAtK: number precisionAtK: number recallAtK: number f1AtK: number mrr: number ndcg: number k: number } export interface LatencyTableProps { latency?: { ingest?: LatencyStats indexing?: LatencyStats search?: LatencyStats answer?: LatencyStats evaluate?: LatencyStats total?: LatencyStats } | null } export function LatencyTable({ latency }: LatencyTableProps) { if (!latency) return null return (

Latency Stats (ms)

{(["ingest", "indexing", "search", "answer", "evaluate", "total"] as const).map( (phase) => { const stats = latency[phase] if (!stats) return null return ( ) } )}
phase min max mean median p95 p99
{phase} {stats.min} {stats.max} {stats.mean} {stats.median} {stats.p95} {stats.p99}
) } export interface RetrievalMetricsProps { retrieval?: RetrievalStats | null byQuestionType?: Record | null } export function RetrievalMetrics({ retrieval, byQuestionType }: RetrievalMetricsProps) { if (!retrieval) return null const questionTypes = byQuestionType ? Object.entries(byQuestionType).filter(([_, stats]) => stats.retrieval) : [] return (

Retrieval Quality (K={retrieval.k})

Hit@{retrieval.k}
{(retrieval.hitAtK * 100).toFixed(0)}%
found relevant
MRR
{retrieval.mrr.toFixed(2)}
mean reciprocal rank
NDCG
{retrieval.ndcg.toFixed(2)}
ranking quality
F1@{retrieval.k}
{(retrieval.f1AtK * 100).toFixed(0)}%
precision-recall balance
{questionTypes.map(([type]) => ( ))} {(["hitAtK", "precisionAtK", "recallAtK", "f1AtK", "mrr", "ndcg"] as const).map( (metric) => { const labels: Record = { hitAtK: `Hit@${retrieval.k}`, precisionAtK: "Precision", recallAtK: "Recall", f1AtK: "F1", mrr: "MRR", ndcg: "NDCG", } const tooltips: Record = { hitAtK: "found at least one relevant result", precisionAtK: "relevant results out of retrieved", recallAtK: "found relevant content", f1AtK: "precision-recall balance", mrr: "mean reciprocal rank", ndcg: "ranking quality score", } const isPercentage = ["hitAtK", "precisionAtK", "recallAtK", "f1AtK"].includes( metric ) const format = (v: number) => isPercentage ? `${(v * 100).toFixed(1)}%` : v.toFixed(3) return ( {questionTypes.map(([type, stats]) => ( ))} ) } )}
metric overall {type.replace(/[-_]/g, " ")}
{labels[metric]} {format(retrieval[metric])} {stats.retrieval ? format(stats.retrieval[metric]) : "—"}
) } export interface EvaluationResult { questionId: string questionType: string question?: string groundTruth: string hypothesis?: string score?: number label?: string explanation?: string } export interface EvaluationListProps { evaluations: EvaluationResult[] onViewDetails?: (questionId: string) => void } export function EvaluationList({ evaluations, onViewDetails }: EvaluationListProps) { const [expandedId, setExpandedId] = useState(null) const [search, setSearch] = useState("") const [selectedTypes, setSelectedTypes] = useState([]) const [showFailuresOnly, setShowFailuresOnly] = useState(false) const questionTypes = useMemo(() => { const counts: Record = {} evaluations.forEach((e) => { const type = e.questionType || "unknown" counts[type] = (counts[type] || 0) + 1 }) return Object.entries(counts).map(([value, count]) => ({ value, label: value.replace(/[-_]/g, " "), count, })) }, [evaluations]) const failureCount = useMemo(() => { return evaluations.filter((e) => e.label === "incorrect" || e.score === 0).length }, [evaluations]) const filtered = useMemo(() => { return evaluations.filter((e) => { if (showFailuresOnly && e.label !== "incorrect" && e.score !== 0) { return false } if (search) { const searchLower = search.toLowerCase() const matchesSearch = e.questionId.toLowerCase().includes(searchLower) || (e.question?.toLowerCase().includes(searchLower) ?? false) || e.groundTruth.toLowerCase().includes(searchLower) || (e.hypothesis?.toLowerCase().includes(searchLower) ?? false) if (!matchesSearch) return false } const type = e.questionType || "unknown" if (selectedTypes.length > 0 && !selectedTypes.includes(type)) { return false } return true }) }, [evaluations, search, selectedTypes, showFailuresOnly]) const hasActiveFilters = search || selectedTypes.length > 0 || showFailuresOnly if (evaluations.length === 0) { return
No results available
} return (
Showing {filtered.length} of {evaluations.length}{" "} {evaluations.length === 1 ? "result" : "results"}
setSearch(e.target.value)} className="w-full h-full pl-9 pr-3 text-sm bg-transparent text-text-primary placeholder-text-muted focus:outline-none cursor-text" />
{filtered.length === 0 ? (
{showFailuresOnly ? "No failures found" : "No results match your filters"}
) : (
{filtered.map((evaluation, idx) => { const isExpanded = expandedId === evaluation.questionId const isCorrect = evaluation.score === 1 || evaluation.label === "correct" const isLast = idx === filtered.length - 1 return (
setExpandedId(isExpanded ? null : evaluation.questionId)} >
{evaluation.questionId} {evaluation.questionType?.replace(/[-_]/g, " ")} {evaluation.question || evaluation.groundTruth} {evaluation.label} {onViewDetails && ( )}
{isExpanded && (
{evaluation.question && (
Question
{evaluation.question}
)}
Ground Truth
{evaluation.groundTruth}
Model Answer
{evaluation.hypothesis || "—"}
{evaluation.explanation && (
Explanation
{evaluation.explanation}
)}
)}
) })}
)}
) }