import { type EvalTemplate } from "@langfuse/shared"; import { AlertCircle, CheckIcon, ExternalLink, ExternalLinkIcon, } from "lucide-react"; import { InputCommand, InputCommandEmpty, InputCommandGroup, InputCommandInput, InputCommandItem, InputCommandList, InputCommandSeparator, } from "@/src/components/ui/input-command"; import { useState } from "react"; import { cn } from "@/src/utils/tailwind"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/src/components/ui/tooltip"; import { useSingleTemplateValidation } from "@/src/features/evals/hooks/useSingleTemplateValidation"; import { getMaintainer } from "@/src/features/evals/utils/typeHelpers"; import { MaintainerTooltip } from "@/src/features/evals/components/maintainer-tooltip"; import Link from "next/link"; interface EvaluatorSelectorProps { projectId: string; evalTemplates: EvalTemplate[]; selectedTemplateId?: string; onTemplateSelect: ( templateId: string, name: string, version?: number, ) => void; onCreateNew?: () => void; } export function EvaluatorSelector({ projectId, evalTemplates, selectedTemplateId, onTemplateSelect, onCreateNew, }: EvaluatorSelectorProps) { const [search, setSearch] = useState(""); // Group templates by name and whether they are managed by Langfuse const groupedTemplates = evalTemplates.reduce( (acc, template) => { const group = template.projectId ? "custom" : "langfuse"; if (!acc[group][template.name]) { acc[group][template.name] = []; } acc[group][template.name].push(template); return acc; }, { langfuse: {} as Record, custom: {} as Record, }, ); // Ensure per-name arrays are sorted by createdAt ascending so last is latest const sortByCreatedAt = (arr: EvalTemplate[]) => arr.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); Object.values(groupedTemplates.custom).forEach(sortByCreatedAt); Object.values(groupedTemplates.langfuse).forEach(sortByCreatedAt); // Filter templates based on search const filteredTemplates = { langfuse: Object.entries(groupedTemplates.langfuse) .filter(([name]) => name.toLowerCase().includes(search.toLowerCase())) .sort(([nameA, templatesA], [nameB, templatesB]) => { // Get partners const partnerA = templatesA[0]?.partner; const partnerB = templatesB[0]?.partner; // No partner comes before partner if (!partnerA && partnerB) return -1; if (partnerA && !partnerB) return 1; // Sort by name within each group return nameA.localeCompare(nameB); }), custom: Object.entries(groupedTemplates.custom) .filter(([name]) => name.toLowerCase().includes(search.toLowerCase())) .sort(([a], [b]) => a.localeCompare(b)), }; // Check if we have results const hasResults = filteredTemplates.langfuse.length > 0 || filteredTemplates.custom.length > 0; const { isTemplateInvalid } = useSingleTemplateValidation({ projectId, }); return ( {!hasResults && ( No evaluator found. )} {filteredTemplates.custom.length > 0 && ( <> {filteredTemplates.custom.map(([name, templateData]) => { const latestVersion = templateData[templateData.length - 1]; const isInvalid = isTemplateInvalid(latestVersion); return ( { onTemplateSelect( latestVersion.id, name, latestVersion.version, ); }} className={cn( "group", templateData.some((t) => t.id === selectedTemplateId) && "bg-secondary", )} > {name} {isInvalid && (

Requires project-level evaluation model

Configure default model
)} {templateData.some((t) => t.id === selectedTemplateId) ? ( <> { e.stopPropagation(); }} > ) : ( { e.stopPropagation(); }} > )}
); })}
{filteredTemplates.custom.length > 0 && } )} {filteredTemplates.langfuse.length > 0 && ( <> {filteredTemplates.langfuse.map(([name, templateData]) => { const latestVersion = templateData[templateData.length - 1]; const isInvalid = isTemplateInvalid(latestVersion); return ( { onTemplateSelect( latestVersion.id, name, latestVersion.version, ); }} className={cn( "group", templateData.some((t) => t.id === selectedTemplateId) && "bg-secondary", )} >
{name}
{isInvalid && (

Requires project-level evaluation model

Configure default model
)} {templateData.some((t) => t.id === selectedTemplateId) ? ( <> { e.stopPropagation(); }} > ) : ( { e.stopPropagation(); }} > )}
); })}
)} {onCreateNew && ( <> Create custom evaluator )}
); }