| import { useEffect, useState } from "react"; | |
| import type { ExampleDetailData, RlmIterDetail } from "../types"; | |
| import IterationDetail from "./IterationDetail"; | |
| interface ExampleDetailLevelProps { | |
| datasetId: string; | |
| exampleIdx: number; | |
| fetchExampleDetail: (dsId: string, exampleIdx: number) => Promise<ExampleDetailData>; | |
| fetchIterDetail: (dsId: string, exampleIdx: number, rlmIter: number) => Promise<RlmIterDetail>; | |
| } | |
| export default function ExampleDetailLevel({ | |
| datasetId, | |
| exampleIdx, | |
| fetchExampleDetail, | |
| fetchIterDetail, | |
| }: ExampleDetailLevelProps) { | |
| const [data, setData] = useState<ExampleDetailData | null>(null); | |
| const [expandedIter, setExpandedIter] = useState<number | null>(null); | |
| const [iterDetail, setIterDetail] = useState<RlmIterDetail | null>(null); | |
| useEffect(() => { | |
| fetchExampleDetail(datasetId, exampleIdx).then(setData).catch(() => {}); | |
| }, [datasetId, exampleIdx, fetchExampleDetail]); | |
| useEffect(() => { | |
| if (expandedIter === null) { | |
| setIterDetail(null); | |
| return; | |
| } | |
| fetchIterDetail(datasetId, exampleIdx, expandedIter) | |
| .then(setIterDetail) | |
| .catch(() => {}); | |
| }, [datasetId, exampleIdx, expandedIter, fetchIterDetail]); | |
| if (!data) return <div className="p-4 text-gray-400">Loading example detail...</div>; | |
| return ( | |
| <div className="p-4 space-y-4 overflow-y-auto"> | |
| {/* Question text */} | |
| <div className="bg-gray-800 border border-gray-700 rounded-lg p-4"> | |
| <div className="flex items-center gap-2 mb-2"> | |
| <div className="text-xs font-semibold text-emerald-400">Question</div> | |
| {data.eval_correct === true && ( | |
| <span className="text-emerald-400 text-xs font-bold">✓ Correct</span> | |
| )} | |
| {data.eval_correct === false && ( | |
| <span className="text-red-400 text-xs font-bold">✗ Incorrect</span> | |
| )} | |
| </div> | |
| <div className="text-sm text-gray-200 whitespace-pre-wrap max-h-40 overflow-y-auto"> | |
| {data.question_text} | |
| </div> | |
| </div> | |
| {/* Stats row */} | |
| <div className="flex gap-4 text-xs text-gray-400"> | |
| <span> | |
| Total tokens:{" "} | |
| <span className="text-gray-200"> | |
| {((data.total_input_tokens + data.total_output_tokens) / 1000).toFixed(1)}k | |
| </span> | |
| </span> | |
| <span> | |
| Time: <span className="text-gray-200">{data.total_execution_time.toFixed(1)}s</span> | |
| </span> | |
| <span> | |
| Iterations: <span className="text-gray-200">{data.iterations.length}</span> | |
| </span> | |
| </div> | |
| {/* Iteration timeline */} | |
| <div> | |
| <div className="text-xs font-semibold text-gray-400 mb-2">Iteration Timeline</div> | |
| <div className="flex gap-2 overflow-x-auto pb-2"> | |
| {data.iterations.map((it) => ( | |
| <div | |
| key={it.rlm_iter} | |
| className={`flex-shrink-0 w-56 bg-gray-800 border rounded-lg p-3 cursor-pointer transition-colors hover:border-emerald-500 ${ | |
| expandedIter === it.rlm_iter | |
| ? "border-emerald-500 ring-1 ring-emerald-500" | |
| : it.has_final_answer | |
| ? "border-emerald-600" | |
| : "border-gray-700" | |
| }`} | |
| onClick={() => | |
| setExpandedIter(expandedIter === it.rlm_iter ? null : it.rlm_iter) | |
| } | |
| > | |
| <div className="flex items-center justify-between mb-2"> | |
| <span className="bg-gray-700 text-gray-200 text-xs font-mono px-2 py-0.5 rounded"> | |
| iter {it.rlm_iter} | |
| </span> | |
| <div className="flex gap-1"> | |
| {it.has_code_blocks && ( | |
| <span className="bg-emerald-900 text-emerald-300 text-xs px-1.5 py-0.5 rounded"> | |
| {it.n_code_blocks} code | |
| </span> | |
| )} | |
| {it.has_final_answer && ( | |
| <span className="bg-amber-900 text-amber-300 text-xs px-1.5 py-0.5 rounded"> | |
| FINAL | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| <div className="flex justify-between text-xs text-gray-500 mb-2"> | |
| <span>{((it.input_tokens + it.output_tokens) / 1000).toFixed(1)}k tok</span> | |
| <span>{it.execution_time.toFixed(1)}s</span> | |
| </div> | |
| <div className="text-xs text-gray-400 line-clamp-3 leading-relaxed"> | |
| {it.response_preview || "(empty)"} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Expanded iteration detail */} | |
| {expandedIter !== null && iterDetail && ( | |
| <IterationDetail data={iterDetail} /> | |
| )} | |
| {/* Final answer if present */} | |
| {data.final_answer && ( | |
| <div className="bg-emerald-950 border border-emerald-700 rounded-lg p-4"> | |
| <div className="text-xs font-semibold text-emerald-400 mb-2">Final Answer</div> | |
| <div className="text-sm text-gray-200 whitespace-pre-wrap max-h-60 overflow-y-auto"> | |
| {data.final_answer} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |