diff --git a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/CompareExperimentsDetails.tsx b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/CompareExperimentsDetails.tsx new file mode 100644 index 0000000000..d513e2372c --- /dev/null +++ b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/CompareExperimentsDetails.tsx @@ -0,0 +1,229 @@ +import React, { useEffect, useMemo } from "react"; +import sortBy from "lodash/sortBy"; +import { + ArrowUpRight, + Clock, + Database, + FlaskConical, + Maximize2, + Minimize2, + PenLine, +} from "lucide-react"; + +import useAppStore from "@/store/AppStore"; +import useBreadcrumbsStore from "@/store/BreadcrumbsStore"; +import FeedbackScoreTag from "@/components/shared/FeedbackScoreTag/FeedbackScoreTag"; +import { Experiment } from "@/types/datasets"; +import { TableBody, TableCell, TableRow } from "@/components/ui/table"; +import { Tag } from "@/components/ui/tag"; +import { Link } from "@tanstack/react-router"; +import { formatDate } from "@/lib/date"; +import uniq from "lodash/uniq"; +import isUndefined from "lodash/isUndefined"; +import { Button } from "@/components/ui/button"; +import { BooleanParam, useQueryParam } from "use-query-params"; + +type CompareExperimentsDetailsProps = { + experimentsIds: string[]; + experiments: Experiment[]; +}; + +const CompareExperimentsDetails: React.FunctionComponent< + CompareExperimentsDetailsProps +> = ({ experiments, experimentsIds }) => { + const workspaceName = useAppStore((state) => state.activeWorkspaceName); + const setBreadcrumbParam = useBreadcrumbsStore((state) => state.setParam); + + const isCompare = experimentsIds.length > 1; + + const experiment = experiments[0]; + + const title = !isCompare + ? experiment?.name + : `Compare (${experimentsIds.length})`; + + const [showCompareFeedback = false, setShowCompareFeedback] = useQueryParam( + "scoreTable", + BooleanParam, + { + updateType: "replaceIn", + }, + ); + + useEffect(() => { + title && setBreadcrumbParam("compare", "compare", title); + return () => setBreadcrumbParam("compare", "compare", ""); + }, [title, setBreadcrumbParam]); + + const scoreMap = useMemo(() => { + return !isCompare + ? {} + : experiments.reduce>>((acc, e) => { + acc[e.id] = (e.feedback_scores || [])?.reduce>( + (a, f) => { + a[f.name] = f.value; + return a; + }, + {}, + ); + + return acc; + }, {}); + }, [isCompare, experiments]); + + const scoreColumns = useMemo(() => { + return uniq( + Object.values(scoreMap).reduce( + (acc, m) => acc.concat(Object.keys(m)), + [], + ), + ).sort(); + }, [scoreMap]); + + const renderCompareFeedbackScoresButton = () => { + if (!isCompare) return null; + + const text = showCompareFeedback + ? "Collapse feedback scores" + : "Expand feedback scores"; + const Icon = showCompareFeedback ? Minimize2 : Maximize2; + + return ( + + ); + }; + + const renderSubSection = () => { + if (isCompare) { + const tag = + experimentsIds.length === 2 ? ( + + +
{experiments[1]?.name}
+
+ ) : ( + + {`${experimentsIds.length - 1} experiments`} + + ); + + return ( +
+ Baseline of + + +
{experiment?.name}
+
+ compared against + {tag} +
+ ); + } else { + return ( +
+ +
+ {sortBy(experiment?.feedback_scores ?? [], "name").map( + (feedbackScore) => { + return ( + + ); + }, + )} +
+
+ ); + } + }; + + const renderCompareFeedbackScores = () => { + if (!isCompare || !showCompareFeedback) return null; + + return ( +
+ {experiments.length ? ( + + + {experiments.map((e) => ( + + +
+ {e.name} +
+
+ {scoreColumns.map((id) => { + const value = scoreMap[e.id]?.[id]; + + return ( + +
+ {isUndefined(value) ? ( + "–" + ) : ( + + )} +
+
+ ); + })} +
+ ))} +
+
+ ) : ( +
+ No feedback scores for selected experiments +
+ )} +
+ ); + }; + + return ( +
+
+

{title}

+ {renderCompareFeedbackScoresButton()} +
+
+ {!isCompare && ( + + +
{formatDate(experiment?.created_at)}
+
+ )} + + + +
{experiment?.dataset_name}
+ +
+ +
+ {renderSubSection()} + {renderCompareFeedbackScores()} +
+ ); +}; + +export default CompareExperimentsDetails; diff --git a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/CompareExperimentsPage.tsx b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/CompareExperimentsPage.tsx index d2d9c3f513..7f268804b4 100644 --- a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/CompareExperimentsPage.tsx +++ b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/CompareExperimentsPage.tsx @@ -1,18 +1,16 @@ -import React, { useEffect } from "react"; +import React from "react"; import isUndefined from "lodash/isUndefined"; import { JsonParam, StringParam, useQueryParam } from "use-query-params"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import CompareExperimentsDetails from "@/components/pages/CompareExperimentsPage/CompareExperimentsDetails"; import ExperimentItemsTab from "@/components/pages/CompareExperimentsPage/ExperimentItemsTab/ExperimentItemsTab"; import ConfigurationTab from "@/components/pages/CompareExperimentsPage/ConfigurationTab/ConfigurationTab"; import useExperimentsByIds from "@/api/datasets/useExperimenstByIds"; -import useBreadcrumbsStore from "@/store/BreadcrumbsStore"; import useDeepMemo from "@/hooks/useDeepMemo"; import { Experiment } from "@/types/datasets"; const CompareExperimentsPage: React.FunctionComponent = () => { - const setBreadcrumbParam = useBreadcrumbsStore((state) => state.setParam); - const [tab = "items", setTab] = useQueryParam("tab", StringParam, { updateType: "replaceIn", }); @@ -21,8 +19,6 @@ const CompareExperimentsPage: React.FunctionComponent = () => { updateType: "replaceIn", }); - const isCompare = experimentsIds.length > 1; - const response = useExperimentsByIds({ experimentsIds, }); @@ -40,22 +36,12 @@ const CompareExperimentsPage: React.FunctionComponent = () => { return experiments ?? []; }, [experiments]); - const experiment = memorizedExperiments[0]; - - const title = !isCompare - ? experiment?.name - : `Compare (${experimentsIds.length})`; - - useEffect(() => { - title && setBreadcrumbParam("compare", "compare", title); - return () => setBreadcrumbParam("compare", "compare", ""); - }, [title, setBreadcrumbParam]); - return ( -
-
-

{title}

-
+
+ diff --git a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ConfigurationTab/CompareConfigCell.tsx b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ConfigurationTab/CompareConfigCell.tsx index b94b240a2c..ae5323c24f 100644 --- a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ConfigurationTab/CompareConfigCell.tsx +++ b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ConfigurationTab/CompareConfigCell.tsx @@ -26,7 +26,9 @@ const CompareConfigCell: React.FunctionComponent< rowHeightClass: "min-h-14", }} > -
{String(data)}
+
+ {String(data)} +
); }; diff --git a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ConfigurationTab/ConfigurationTab.tsx b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ConfigurationTab/ConfigurationTab.tsx index 3c615adf6c..dca0c89c96 100644 --- a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ConfigurationTab/ConfigurationTab.tsx +++ b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ConfigurationTab/ConfigurationTab.tsx @@ -80,6 +80,7 @@ const ConfigurationTab: React.FunctionComponent = ({ header: CompareExperimentsHeader as never, cell: CompareConfigCell as never, size, + minSize: 120, }); }); @@ -146,7 +147,9 @@ const ConfigurationTab: React.FunctionComponent = ({ const noDataText = search ? "No search results" - : "There is no data for the selected experiments"; + : isCompare + ? "These experiments have no configuration" + : "This experiment has no configuration"; const resizeConfig = useMemo( () => ({ diff --git a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ExperimentItemsTab/ExperimentItemsTab.tsx b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ExperimentItemsTab/ExperimentItemsTab.tsx index cb6c41b91a..5007246345 100644 --- a/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ExperimentItemsTab/ExperimentItemsTab.tsx +++ b/apps/opik-frontend/src/components/pages/CompareExperimentsPage/ExperimentItemsTab/ExperimentItemsTab.tsx @@ -180,6 +180,7 @@ const ExperimentItemsTab: React.FunctionComponent = ({ }, }, size, + minSize: 120, }); }); diff --git a/apps/opik-frontend/src/components/shared/DataTableNoData/DataTableNoData.tsx b/apps/opik-frontend/src/components/shared/DataTableNoData/DataTableNoData.tsx index de79e78f8d..aa10bd2d3c 100644 --- a/apps/opik-frontend/src/components/shared/DataTableNoData/DataTableNoData.tsx +++ b/apps/opik-frontend/src/components/shared/DataTableNoData/DataTableNoData.tsx @@ -2,14 +2,17 @@ import React from "react"; type DataTableNoDataProps = { title: string; + children?: React.ReactNode; }; const DataTableNoData: React.FunctionComponent = ({ title, + children, }) => { return ( -
+
{title} +
{children}
); };