From 7971582d5110422cca56bb8e3bab5746f39ff999 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 22 Apr 2026 17:14:49 -0700 Subject: [PATCH] add EvalViewer component for structured eval display in log detail drawer --- .../view_logs/EvalViewer/EvalViewer.tsx | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx diff --git a/ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx new file mode 100644 index 00000000000..19659370541 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/EvalViewer/EvalViewer.tsx @@ -0,0 +1,146 @@ +import React from "react"; +import { Card, Tag, Table, Typography, Space, Tooltip } from "antd"; +import { CheckCircleOutlined, CloseCircleOutlined, ExperimentOutlined } from "@ant-design/icons"; + +const { Text } = Typography; + +interface EvalVerdict { + criterion_name: string; + score: number; + reasoning: string; + passed: boolean; +} + +interface EvalInformation { + eval_id?: string; + eval_name: string; + overall_score: number; + passed: boolean; + judge_model: string; + iteration?: number; + eval_error?: string | null; + verdicts?: EvalVerdict[]; + threshold?: number; +} + +interface EvalViewerProps { + data: EvalInformation | EvalInformation[]; +} + +export default function EvalViewer({ data }: EvalViewerProps) { + const entries: EvalInformation[] = Array.isArray(data) ? data : [data]; + + if (!entries.length) return null; + + return ( +
+
+ + + Evals + + + Beta + +
+ + {entries.map((entry, idx) => ( + + ))} +
+ ); +} + +function EvalEntryCard({ entry }: { entry: EvalInformation }) { + const passed = entry.passed; + const scoreColor = passed ? "#52c41a" : "#ff4d4f"; + + const columns = [ + { + title: "Criterion", + dataIndex: "criterion_name", + key: "criterion_name", + render: (v: string) => {v}, + }, + { + title: "Score", + dataIndex: "score", + key: "score", + width: 80, + render: (v: number) => ( + = 70 ? "#52c41a" : v >= 50 ? "#faad14" : "#ff4d4f", fontWeight: 600 }}> + {v} + + ), + }, + { + title: "Comment", + dataIndex: "reasoning", + key: "reasoning", + render: (v: string) => ( + + + {v} + + + ), + }, + ]; + + return ( + + {passed ? ( + + ) : ( + + )} + {entry.eval_name} + {passed ? "PASSED" : "FAILED"} + + {entry.overall_score?.toFixed(0)} / 100 + {entry.threshold != null && ` (threshold: ${entry.threshold})`} + + + } + extra={ + + {entry.judge_model && ( + + Judge: {entry.judge_model} + + )} + {entry.iteration != null && ( + + Iter: {entry.iteration + 1} + + )} + + } + > + {entry.eval_error && ( + + Judge error: {entry.eval_error} + + )} + + {entry.verdicts && entry.verdicts.length > 0 ? ( + + ) : ( + + Score: {entry.overall_score?.toFixed(1)} — no per-criterion breakdown available. + + )} + + ); +}