Skip to content

Commit

Permalink
[OPIK-564] Create new home page (#874)
Browse files Browse the repository at this point in the history
  • Loading branch information
andriidudar authored Dec 12, 2024
1 parent c4acf28 commit b476478
Show file tree
Hide file tree
Showing 16 changed files with 533 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const PartialPageLayout = ({
<main>
<nav className="comet-header-height flex w-full items-center justify-between gap-6 border-b pl-4 pr-6">
<div className="flex-1">
<Link to="/$workspaceName/projects" params={{ workspaceName }}>
<Link to="/$workspaceName/home" params={{ workspaceName }}>
{logo}
</Link>
</div>
Expand Down
10 changes: 9 additions & 1 deletion apps/opik-frontend/src/components/layout/SideBar/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
PanelLeftClose,
MessageCircleQuestion,
FileTerminal,
LucideHome,
} from "lucide-react";
import { keepPreviousData } from "@tanstack/react-query";

Expand Down Expand Up @@ -47,6 +48,13 @@ type MenuItem = {
};

const MAIN_MENU_ITEMS: MenuItem[] = [
{
id: "home",
path: "/$workspaceName/home",
type: MENU_ITEM_TYPE.router,
icon: LucideHome,
label: "Home",
},
{
id: "projects",
path: "/$workspaceName/projects",
Expand Down Expand Up @@ -89,7 +97,7 @@ const MAIN_MENU_ITEMS: MenuItem[] = [
},
];

const HOME_PATH = "/$workspaceName/projects";
const HOME_PATH = "/$workspaceName/home";

type SideBarProps = {
expanded: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from "@/types/shared";
import { convertColumnDataToColumn } from "@/lib/table";
import ColumnsButton from "@/components/shared/ColumnsButton/ColumnsButton";
import AddExperimentDialog from "@/components/pages/ExperimentsPage/AddExperimentDialog";
import AddExperimentDialog from "@/components/pages/ExperimentsShared/AddExperimentDialog";
import ExperimentsActionsPanel from "@/components/pages/ExperimentsShared/ExperimentsActionsPanel";
import ExperimentsFiltersButton from "@/components/pages/ExperimentsShared/ExperimentsFiltersButton";
import ExperimentRowActionsCell from "@/components/pages/ExperimentsPage/ExperimentRowActionsCell";
Expand Down
183 changes: 183 additions & 0 deletions apps/opik-frontend/src/components/pages/HomePage/EvaluationSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import React, { useCallback, useMemo, useRef, useState } from "react";
import { keepPreviousData } from "@tanstack/react-query";
import useLocalStorageState from "use-local-storage-state";
import { ColumnPinningState } from "@tanstack/react-table";
import { Link } from "@tanstack/react-router";
import { Book } from "lucide-react";
import get from "lodash/get";

import DataTable from "@/components/shared/DataTable/DataTable";
import DataTableNoData from "@/components/shared/DataTableNoData/DataTableNoData";
import ResourceCell from "@/components/shared/DataTableCells/ResourceCell";
import useExperimentsList from "@/api/datasets/useExperimentsList";
import Loader from "@/components/shared/Loader/Loader";
import AddExperimentDialog from "@/components/pages/ExperimentsShared/AddExperimentDialog";
import { Button } from "@/components/ui/button";
import useAppStore from "@/store/AppStore";
import { COLUMN_NAME_ID, COLUMN_SELECT_ID, COLUMN_TYPE } from "@/types/shared";
import { RESOURCE_TYPE } from "@/components/shared/ResourceLink/ResourceLink";
import { Experiment } from "@/types/datasets";
import { convertColumnDataToColumn } from "@/lib/table";
import { buildDocsUrl } from "@/lib/utils";
import { formatDate } from "@/lib/date";

const COLUMNS_WIDTH_KEY = "home-experiments-columns-width";

export const COLUMNS = convertColumnDataToColumn<Experiment, Experiment>(
[
{
id: COLUMN_NAME_ID,
label: "Experiment",
type: COLUMN_TYPE.string,
cell: ResourceCell as never,
sortable: true,
customMeta: {
nameKey: "name",
idKey: "id",
resource: RESOURCE_TYPE.experiment,
},
},
{
id: "dataset",
label: "Dataset",
type: COLUMN_TYPE.string,
cell: ResourceCell as never,
customMeta: {
nameKey: "dataset_name",
idKey: "dataset_id",
resource: RESOURCE_TYPE.dataset,
},
},
{
id: "prompt",
label: "Prompt commit",
type: COLUMN_TYPE.string,
cell: ResourceCell as never,
customMeta: {
nameKey: "prompt_version.commit",
idKey: "prompt_version.prompt_id",
resource: RESOURCE_TYPE.prompt,
getSearch: (data: Experiment) => ({
activeVersionId: get(data, "prompt_version.id", null),
}),
},
},
{
id: "trace_count",
label: "Trace count",
type: COLUMN_TYPE.number,
},
{
id: "created_at",
label: "Created",
type: COLUMN_TYPE.time,
accessorFn: (row) => formatDate(row.created_at),
sortable: true,
},
{
id: "last_updated_at",
label: "Last updated",
type: COLUMN_TYPE.time,
accessorFn: (row) => formatDate(row.last_updated_at),
sortable: true,
},
],
{},
);

export const DEFAULT_COLUMN_PINNING: ColumnPinningState = {
left: [COLUMN_SELECT_ID, COLUMN_NAME_ID],
right: [],
};

const EvaluationSection: React.FunctionComponent = () => {
const workspaceName = useAppStore((state) => state.activeWorkspaceName);

const resetDialogKeyRef = useRef(0);
const [openDialog, setOpenDialog] = useState<boolean>(false);

const { data, isPending } = useExperimentsList(
{
workspaceName,
page: 1,
size: 5,
},
{
placeholderData: keepPreviousData,
},
);

const experiments = useMemo(() => data?.content ?? [], [data?.content]);
const noDataText = "There are no experiments yet";

const [columnsWidth, setColumnsWidth] = useLocalStorageState<
Record<string, number>
>(COLUMNS_WIDTH_KEY, {
defaultValue: {},
});

const resizeConfig = useMemo(
() => ({
enabled: true,
columnSizing: columnsWidth,
onColumnResize: setColumnsWidth,
}),
[columnsWidth, setColumnsWidth],
);

const handleNewExperimentClick = useCallback(() => {
setOpenDialog(true);
resetDialogKeyRef.current = resetDialogKeyRef.current + 1;
}, []);

if (isPending) {
return <Loader />;
}

return (
<div className="pb-4">
<div className="flex items-center justify-between gap-8 pb-4 pt-2">
<div className="flex items-center gap-2">
<h2 className="comet-body-accented truncate break-words">
Evaluation
</h2>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" asChild>
<a
href={buildDocsUrl("/evaluation/concepts")}
target="_blank"
rel="noreferrer"
>
<Book className="mr-2 size-4 shrink-0" />
Learn more
</a>
</Button>
<Link to="/$workspaceName/experiments" params={{ workspaceName }}>
<Button variant="outline">View all experiments</Button>
</Link>
</div>
</div>
<DataTable
columns={COLUMNS}
data={experiments}
resizeConfig={resizeConfig}
columnPinning={DEFAULT_COLUMN_PINNING}
noData={
<DataTableNoData title={noDataText}>
<Button variant="link" onClick={handleNewExperimentClick}>
Create new experiment
</Button>
</DataTableNoData>
}
/>
<AddExperimentDialog
key={resetDialogKeyRef.current}
open={openDialog}
setOpen={setOpenDialog}
/>
</div>
);
};

export default EvaluationSection;
102 changes: 102 additions & 0 deletions apps/opik-frontend/src/components/pages/HomePage/GetStartedSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React from "react";
import { ArrowRight, MessageCircle, MousePointer, X } from "lucide-react";
import useLocalStorageState from "use-local-storage-state";

import { Separator } from "@/components/ui/separator";
import { Button } from "@/components/ui/button";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Link } from "@tanstack/react-router";
import useAppStore from "@/store/AppStore";
import { DEMO_PROJECT_NAME } from "@/constants/shared";
import { buildDocsUrl } from "@/lib/utils";

const GetStartedSection = () => {
const workspaceName = useAppStore((state) => state.activeWorkspaceName);
const [hide, setHide] = useLocalStorageState<boolean>(
"home-get-started-section",
{
defaultValue: false,
},
);

if (hide) return null;

return (
<div>
<div className="flex items-center justify-between gap-8 pb-4 pt-2">
<div className="flex items-center gap-2">
<h2 className="comet-body-accented truncate break-words">
Get started with Opik
</h2>
</div>
<div className="flex items-center gap-2">
<Button
variant="minimal"
size="icon-xs"
onClick={() => setHide(true)}
>
<X className="size-4" />
</Button>
</div>
</div>
<div className="flex gap-x-4">
<Alert className="mt-4">
<MousePointer className="size-4" />
<AlertTitle>Explore our demo project</AlertTitle>
<AlertDescription>
Browse our curated examples to draw inspiration for your own LLM
projects.
</AlertDescription>
<Link
to="/$workspaceName/redirect/projects"
params={{ workspaceName }}
search={{ name: DEMO_PROJECT_NAME }}
>
<Button variant="ghost" size="sm" className="-ml-2">
Go to demo project
<ArrowRight className="ml-1 size-4 shrink-0" />
</Button>
</Link>
</Alert>
<Alert className="mt-4">
<MessageCircle className="size-4" />
<AlertTitle>Log a trace</AlertTitle>
<AlertDescription>
The first step in integrating Opik with your codebase is to track
your LLM calls.
</AlertDescription>
<Button variant="ghost" size="sm" asChild>
<a
href={buildDocsUrl("/tracing/log_traces")}
target="_blank"
rel="noreferrer"
>
Start logging traces
<ArrowRight className="ml-1 size-4 shrink-0" />
</a>
</Button>
</Alert>
<Alert className="mt-4">
<MessageCircle className="size-4" />
<AlertTitle>Run an experiment</AlertTitle>
<AlertDescription>
An experiment is a single evaluation of your LLM application.
</AlertDescription>
<Button variant="ghost" size="sm" asChild>
<a
href={buildDocsUrl("/evaluation/evaluate_your_llm")}
target="_blank"
rel="noreferrer"
>
Start running experiments
<ArrowRight className="ml-1 size-4 shrink-0" />
</a>
</Button>
</Alert>
</div>
<Separator className="my-6" />
</div>
);
};

export default GetStartedSection;
19 changes: 17 additions & 2 deletions apps/opik-frontend/src/components/pages/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import React from "react";
import useAppStore from "@/store/AppStore";
import { Navigate } from "@tanstack/react-router";
import GetStartedSection from "@/components/pages/HomePage/GetStartedSection";
import ObservabilitySection from "@/components/pages/HomePage/ObservabilitySection";
import EvaluationSection from "@/components/pages/HomePage/EvaluationSection";

const HomePage = () => {
const workspaceName = useAppStore((state) => state.activeWorkspaceName);

return <Navigate to="/$workspaceName/projects" params={{ workspaceName }} />;
return (
<div className="pt-6">
<div className="mb-4 flex items-center justify-between">
<h1 className="comet-title-l truncate break-words">
Welcome to {workspaceName}
</h1>
</div>
<GetStartedSection />
<ObservabilitySection />
<div className="h-6"></div>
<EvaluationSection />
</div>
);
};

export default HomePage;
Loading

0 comments on commit b476478

Please sign in to comment.