| import { useEffect, useState } from "react"; | |
| import type { OverviewData, PanelNav } from "../types"; | |
| interface OverviewLevelProps { | |
| datasetId: string; | |
| fetchOverview: (dsId: string) => Promise<OverviewData>; | |
| onDrillDown: (nav: PanelNav) => void; | |
| } | |
| export default function OverviewLevel({ datasetId, fetchOverview, onDrillDown }: OverviewLevelProps) { | |
| const [data, setData] = useState<OverviewData | null>(null); | |
| useEffect(() => { | |
| fetchOverview(datasetId).then(setData).catch(() => {}); | |
| }, [datasetId, fetchOverview]); | |
| if (!data) return <div className="p-4 text-gray-400">Loading overview...</div>; | |
| const correctCount = data.examples.filter((ex) => ex.eval_correct === true).length; | |
| const incorrectCount = data.examples.filter((ex) => ex.eval_correct === false).length; | |
| const unknownCount = data.examples.filter((ex) => ex.eval_correct === null || ex.eval_correct === undefined).length; | |
| return ( | |
| <div className="p-4 space-y-3"> | |
| {/* Experiment metadata */} | |
| <div className="flex gap-3 text-xs text-gray-400"> | |
| <span>Model: <span className="text-gray-200">{data.metadata.model}</span></span> | |
| <span>Method: <span className="text-gray-200">{data.metadata.method}</span></span> | |
| <span>Run: <span className="text-gray-200">{data.metadata.run_id}</span></span> | |
| </div> | |
| {/* Summary stats */} | |
| <div className="flex gap-3 text-xs text-gray-400"> | |
| <span>{data.examples.length} examples</span> | |
| {correctCount > 0 && ( | |
| <span className="text-emerald-400">{correctCount} correct</span> | |
| )} | |
| {incorrectCount > 0 && ( | |
| <span className="text-red-400">{incorrectCount} incorrect</span> | |
| )} | |
| {unknownCount > 0 && ( | |
| <span className="text-gray-500">{unknownCount} unknown</span> | |
| )} | |
| </div> | |
| {/* Example cards */} | |
| <div className="space-y-2"> | |
| {data.examples.map((ex) => ( | |
| <div | |
| key={ex.example_idx} | |
| className="bg-gray-800 border border-gray-700 rounded-lg p-4 hover:border-emerald-500 cursor-pointer transition-colors" | |
| onClick={() => | |
| onDrillDown({ | |
| datasetId, | |
| level: 2, | |
| exampleIdx: ex.example_idx, | |
| }) | |
| } | |
| > | |
| <div className="flex items-center justify-between mb-2"> | |
| <div className="flex items-center gap-3"> | |
| <span className="bg-emerald-600 text-white text-xs font-bold px-2 py-0.5 rounded-full"> | |
| Ex {ex.example_idx} | |
| </span> | |
| <span className="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded"> | |
| {ex.n_iterations} iter{ex.n_iterations !== 1 ? "s" : ""} | |
| </span> | |
| {ex.eval_correct === true && ( | |
| <span className="text-emerald-400 text-sm font-bold" title="Correct"> | |
| ✓ | |
| </span> | |
| )} | |
| {ex.eval_correct === false && ( | |
| <span className="text-red-400 text-sm font-bold" title="Incorrect"> | |
| ✗ | |
| </span> | |
| )} | |
| </div> | |
| <span className="text-xs text-gray-400"> | |
| {ex.total_execution_time.toFixed(1)}s | |
| </span> | |
| </div> | |
| {/* Question preview */} | |
| <div className="text-sm text-gray-300 line-clamp-2 mb-2 leading-relaxed"> | |
| {ex.question_text || "(no question text)"} | |
| </div> | |
| <div className="flex gap-4 text-xs text-gray-400"> | |
| <span> | |
| {((ex.total_input_tokens + ex.total_output_tokens) / 1000).toFixed(1)}k tokens | |
| </span> | |
| </div> | |
| {ex.final_answer_preview && ( | |
| <div className="mt-2 text-xs text-gray-500 truncate"> | |
| Answer: {ex.final_answer_preview} | |
| </div> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| } | |