import { type LangfuseColumnDef } from "@/src/components/table/types"; import { Skeleton } from "@/src/components/ui/skeleton"; import { DatasetAggregateTableCell } from "@/src/features/datasets/components/DatasetAggregateTableCell"; import { type DatasetCompareRunRowData } from "@/src/features/datasets/components/DatasetCompareRunsTable"; import { PopoverFilterBuilder } from "@/src/features/filters/components/filter-builder"; import { type ColumnDefinition } from "@langfuse/shared"; import { type FilterState } from "@langfuse/shared"; import { type EnrichedDatasetRunItem } from "@langfuse/shared/src/server"; import { type Row } from "@tanstack/react-table"; import React, { useEffect, useRef, useState } from "react"; import { useDebounce } from "@/src/hooks/useDebounce"; import { type ScoreColumn } from "@/src/features/scores/types"; import { Toggle } from "@/src/components/ui/toggle"; import { useRouter } from "next/router"; import { cn } from "@/src/utils/tailwind"; function DatasetAggregateCellWithBaselineDetection({ value, runData, runId, projectId, serverScoreColumns, }: { value: EnrichedDatasetRunItem; runData: Record; runId: string; projectId: string; serverScoreColumns?: ScoreColumn[]; }) { const router = useRouter(); const baselineRunId = router.query.baseline as string | undefined; const baselineRunValue = baselineRunId ? runData[baselineRunId] : undefined; const isBaselineRun = baselineRunId === runId; return ( ); } function BaselineToggle({ runId }: { runId: string }) { const router = useRouter(); const [isHovered, setIsHovered] = useState(false); const justSetBaselineRef = useRef(false); const previousBaselineRef = useRef(undefined); const baselineRunId = router.query.baseline as string | undefined; const hasBaseline = Boolean(baselineRunId); const isBaseline = baselineRunId === runId; useEffect(() => { if (baselineRunId === runId && previousBaselineRef.current !== runId) { justSetBaselineRef.current = true; } previousBaselineRef.current = baselineRunId; }, [baselineRunId, runId]); const handleClick = () => { if (isBaseline) { const { baseline, ...restQuery } = router.query; void router.push({ pathname: router.pathname, query: restQuery, }); } else { void router.push({ pathname: router.pathname, query: { ...router.query, baseline: runId }, }); } }; const handleMouseEnter = () => { setIsHovered(true); }; const handleMouseLeave = () => { setIsHovered(false); justSetBaselineRef.current = false; }; let text: string; if (!hasBaseline) { text = "Set as baseline"; } else if (isBaseline) { text = isHovered && !justSetBaselineRef.current ? "Clear baseline" : "Baseline"; } else { text = isHovered ? "Set as baseline" : "Comparison"; } return ( {text} ); } function RunAggregateHeader({ runId, runName, columns, updateRunFilters, getFiltersForRun, }: { runId: string; runName: string; columns: ColumnDefinition[]; updateRunFilters: (runId: string, filters: FilterState) => void; getFiltersForRun: (runId: string) => FilterState; }) { // Debounce updateRunFilters with 500ms delay to prevent immediate table re-renders const debouncedUpdateRunFilters = useDebounce( (runId: string, filters: FilterState) => updateRunFilters(runId, filters), 500, false, // Don't execute first call immediately ); return (
{runName}
debouncedUpdateRunFilters(runId, filters) } />
); } type RunAggregateColumnProps = { id: string; name: string; description?: string; createdAt?: Date; }; const isScoreColumnsAvailable = ( scoreColumns?: ScoreColumn[], ): scoreColumns is ScoreColumn[] => { return scoreColumns !== undefined; }; export const constructDatasetRunAggregateColumns = ({ runAggregateColumnProps, projectId, datasetColumns, updateRunFilters, getFiltersForRun, serverScoreColumns, }: { runAggregateColumnProps: RunAggregateColumnProps[]; projectId: string; datasetColumns: ColumnDefinition[]; updateRunFilters: (runId: string, filters: FilterState) => void; getFiltersForRun: (runId: string) => FilterState; serverScoreColumns?: ScoreColumn[]; }): LangfuseColumnDef[] => { const isDataLoading = !isScoreColumnsAvailable(serverScoreColumns); return runAggregateColumnProps.map((col) => { const { id, name, createdAt } = col; return { id, accessorKey: id, header: () => ( ), size: 250, cell: ({ row }: { row: Row }) => { const runData: Record = row.getValue("runs") ?? {}; // if cell is loading or if run created at timestamp is less than 20 seconds ago, show skeleton if ( isDataLoading || (createdAt && createdAt.getTime() + 20000 > Date.now()) ) return ; if (!Boolean(Object.keys(runData).length)) return null; if (!runData.hasOwnProperty(id)) return null; const value: EnrichedDatasetRunItem | undefined = runData[id]; if (!value) return null; return ( ); }, }; }); }; export const getDatasetRunAggregateColumnProps = (isLoading: boolean) => ({ accessorKey: "runs", header: "Runs", id: "runs", isFixedPosition: true, cell: () => { return isLoading ? : null; }, });