import { type EvalTemplate } from "@langfuse/shared"; import { CheckIcon, ChevronDown, Cog, ExternalLink, AlertCircle, ExternalLinkIcon, } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger, } from "@/src/components/ui/popover"; import { InputCommand, InputCommandEmpty, InputCommandGroup, InputCommandInput, InputCommandItem, InputCommandList, InputCommandSeparator, } from "@/src/components/ui/input-command"; import { cn } from "@/src/utils/tailwind"; import { Button } from "@/src/components/ui/button"; import { useState } from "react"; import Link from "next/link"; import { useExperimentEvaluatorSelection } from "@/src/features/experiments/hooks/useExperimentEvaluatorSelection"; import { useTemplatesValidation } from "@/src/features/evals/hooks/useTemplatesValidation"; 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"; type TemplateSelectorProps = { projectId: string; datasetId: string; evalTemplates: EvalTemplate[]; disabled?: boolean; activeTemplateIds?: string[]; inactiveTemplateIds?: string[]; onConfigureTemplate?: (templateId: string) => void; onSelectEvaluator?: (templateId: string) => void; onEvaluatorToggled?: () => void; className?: string; }; export const TemplateSelector = ({ projectId, datasetId, evalTemplates, activeTemplateIds, inactiveTemplateIds, onConfigureTemplate, onSelectEvaluator, onEvaluatorToggled, className, disabled = false, }: TemplateSelectorProps) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [search, setSearch] = useState(""); const { activeTemplates, isTemplateActive, isTemplateInactive, handleRowClick, } = useExperimentEvaluatorSelection({ projectId: projectId, datasetId: datasetId, initialActiveTemplateIds: activeTemplateIds, initialInactiveTemplateIds: inactiveTemplateIds, onSelectEvaluator, onEvaluatorToggled, }); // Validation for templates requiring default model const { isTemplateValid, hasDefaultModel } = useTemplatesValidation({ projectId: projectId, selectedTemplateIds: activeTemplates, }); // 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, }, ); // 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)), }; const hasResults = filteredTemplates.langfuse.length > 0 || filteredTemplates.custom.length > 0; // Handle cog button click - configure template const handleConfigureTemplate = (e: React.MouseEvent, templateId: string) => { e.stopPropagation(); // Check if this template requires a default model if (!isTemplateValid(templateId)) { // If it requires a default model and none is set, show a warning instead return; } if (onConfigureTemplate) { onConfigureTemplate(templateId); } }; const { isTemplateInvalid } = useSingleTemplateValidation({ projectId: projectId, }); return ( <>
{ // Prevent the wheel event from being captured by parent elements e.stopPropagation(); }} > {!hasResults && ( No evaluator found. )} {filteredTemplates.custom.length > 0 && ( <> {filteredTemplates.custom.map(([name, templateData]) => { const latestTemplate = templateData[templateData.length - 1]; const isActive = isTemplateActive(latestTemplate.id); const isInactive = isTemplateInactive( latestTemplate.id, ); const isInvalid = isTemplateInvalid(latestTemplate); return ( { handleRowClick(latestTemplate.id); }} disabled={isInvalid || disabled} > {isActive ? ( ) : (
)} {name} {isInvalid && (

Requires project-level evaluation model

Configure default model
)} {isInactive && (
Paused
)} {isActive && ( )} ); })} {filteredTemplates.custom.length > 0 && ( )} )} {filteredTemplates.langfuse.length > 0 && ( {filteredTemplates.langfuse.map(([name, templateData]) => { const latestTemplate = templateData[templateData.length - 1]; const isActive = isTemplateActive(latestTemplate.id); const isInactive = isTemplateInactive(latestTemplate.id); const isInvalid = isTemplateInvalid(latestTemplate); return ( { handleRowClick(latestTemplate.id); }} disabled={isInvalid || disabled} > {isActive ? ( ) : (
)}
{name}
{isInvalid && (

Requires project-level evaluation model

Configure default model
)} {isInactive && (
Paused
)} {isActive && ( )} ); })} )} { if (disabled) return; window.open( `/project/${projectId}/evals/templates/new`, "_blank", ); }} > Create custom evaluator {!hasDefaultModel && ( { if (disabled) return; window.open( `/project/${projectId}/evals/default-model`, "_blank", ); }} > Configure default model )}
); }; TemplateSelector.displayName = "TemplateSelector";