"use client" import { useState, useMemo } from "react" import Link from "next/link" import { cn } from "@/lib/utils" import { MultiSelect } from "./multi-select" import type { QuestionCheckpoint, QuestionTypeRegistry } from "@/lib/api" interface QuestionListProps { runId: string questions: QuestionCheckpoint[] questionTypeRegistry?: QuestionTypeRegistry | null } export function QuestionList({ runId, questions, questionTypeRegistry }: QuestionListProps) { const [search, setSearch] = useState("") const [selectedTypes, setSelectedTypes] = useState([]) const [showFailuresOnly, setShowFailuresOnly] = useState(false) const [expanded, setExpanded] = useState(null) // Get unique question types with counts const questionTypes = useMemo(() => { const counts: Record = {} questions.forEach((q) => { counts[q.questionType] = (counts[q.questionType] || 0) + 1 }) return Object.entries(counts).map(([value, count]) => ({ value, label: questionTypeRegistry?.[value]?.alias || value.replace(/[-_]/g, " "), count, })) }, [questions, questionTypeRegistry]) // Count failures const failureCount = useMemo(() => { return questions.filter((q) => q.phases.evaluate.label === "incorrect").length }, [questions]) // Filter questions const filtered = useMemo(() => { return questions.filter((q) => { // Failures filter if (showFailuresOnly && q.phases.evaluate.label !== "incorrect") { return false } // Search filter if (search) { const searchLower = search.toLowerCase() const matchesSearch = q.questionId.toLowerCase().includes(searchLower) || q.question.toLowerCase().includes(searchLower) || q.groundTruth.toLowerCase().includes(searchLower) if (!matchesSearch) return false } // Type filter if (selectedTypes.length > 0 && !selectedTypes.includes(q.questionType)) { return false } return true }) }, [questions, search, selectedTypes, showFailuresOnly]) const hasActiveFilters = search || selectedTypes.length > 0 || showFailuresOnly if (questions.length === 0) { return
No questions found
} return (
{/* Filter Bar */}
{/* Header row */}
Showing {filtered.length} of {questions.length}{" "} {questions.length === 1 ? "question" : "questions"}
{/* Filter bar */}
{/* Search input */}
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" />
{/* Type filter */}
{/* Failures toggle */}
{/* Questions - Table style */} {filtered.length === 0 ? (
{showFailuresOnly ? "No failures found" : "No questions match your filters"}
) : (
{filtered.map((q, idx) => { const isCorrect = q.phases.evaluate.label === "correct" const isExpanded = expanded === q.questionId const isLast = idx === filtered.length - 1 return (
{/* Row */}
setExpanded(isExpanded ? null : q.questionId)} > {/* Status indicator */}
{/* Question ID */} {q.questionId} {/* Type badge */} {questionTypeRegistry?.[q.questionType]?.alias || q.questionType.replace(/[-_]/g, " ")} {/* Question text */} {q.question} {/* Status label */} {isCorrect ? "correct" : "incorrect"} {/* Expand icon */}
{/* Expanded content */} {isExpanded && (
{/* Question */}
Question
{q.question}
Ground Truth
{q.groundTruth}
Model Answer
{q.phases.answer.hypothesis || "—"}
{q.phases.evaluate.explanation && (
Explanation
{q.phases.evaluate.explanation}
)}
e.stopPropagation()} > View full details →
)}
) })}
)}
) }