Skip to content

Commit

Permalink
[OPIK-220] [UX Improvement] Add quickstart page
Browse files Browse the repository at this point in the history
  • Loading branch information
andriidudar committed Oct 16, 2024
1 parent 6e5bb09 commit 6fb70d5
Show file tree
Hide file tree
Showing 20 changed files with 785 additions and 188 deletions.
Binary file added apps/opik-frontend/public/images/colab-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
126 changes: 94 additions & 32 deletions apps/opik-frontend/src/components/layout/SideBar/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import React from "react";
import isNumber from "lodash/isNumber";
import { Link, useMatchRoute } from "@tanstack/react-router";
import {
Book,
Database,
FlaskConical,
GraduationCap,
LayoutGrid,
LucideIcon,
MessageSquare,
PanelRightOpen,
} from "lucide-react";
Expand All @@ -17,45 +20,78 @@ import useExperimentsList from "@/api/datasets/useExperimentsList";
import useFeedbackDefinitionsList from "@/api/feedback-definitions/useFeedbackDefinitionsList";
import { OnChangeFn } from "@/types/shared";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import TooltipWrapper from "@/components/shared/TooltipWrapper/TooltipWrapper";
import { cn } from "@/lib/utils";
import { buildDocsUrl, cn } from "@/lib/utils";
import Logo from "@/components/layout/Logo/Logo";
import usePluginsStore from "@/store/PluginsStore";

const ITEMS = [
enum MENU_ITEM_TYPE {
link = "link",
router = "router",
}

type MenuItem = {
path: string;
type: MENU_ITEM_TYPE;
icon: LucideIcon;
label: string;
count?: string;
};

const MAIN_MENU_ITEMS: MenuItem[] = [
{
path: "/$workspaceName/projects",
type: MENU_ITEM_TYPE.router,
icon: LayoutGrid,
label: "Projects",
count: "projects",
},
{
path: "/$workspaceName/datasets",
type: MENU_ITEM_TYPE.router,
icon: Database,
label: "Datasets",
count: "datasets",
},
{
path: "/$workspaceName/experiments",
type: MENU_ITEM_TYPE.router,
icon: FlaskConical,
label: "Experiments",
count: "experiments",
},
{
path: "/$workspaceName/feedback-definitions",
type: MENU_ITEM_TYPE.router,
icon: MessageSquare,
label: "Feedback definitions",
count: "feedbackDefinitions",
},
];

const BOTTOM_MENU_ITEMS = [
{
path: buildDocsUrl(),
type: MENU_ITEM_TYPE.link,
icon: Book,
label: "Documentation",
},
{
path: "/$workspaceName/quickstart",
type: MENU_ITEM_TYPE.router,
icon: GraduationCap,
label: "Quickstart guide",
},
];

const HOME_PATH = "/$workspaceName/projects";

type SideBarProps = {
expanded: boolean;
setExpanded: OnChangeFn<boolean | undefined>;
};

const HOME_PATH = "/$workspaceName/projects";

const SideBar: React.FunctionComponent<SideBarProps> = ({
expanded,
setExpanded,
Expand Down Expand Up @@ -141,35 +177,55 @@ const SideBar: React.FunctionComponent<SideBarProps> = ({
<Logo expanded={expanded} />
);

const renderItems = () => {
return ITEMS.map((item) => {
const renderItems = (items: MenuItem[]) => {
return items.map((item) => {
const hasCount = item.count && isNumber(countDataMap[item.count]);
const count = hasCount ? countDataMap[item.count] : "";

const itemElement = (
<li key={item.path} className="flex">
<Link
to={item.path}
params={{ workspaceName }}
className={cn(
"comet-body-s flex h-9 w-full items-center gap-2 text-foreground rounded-md hover:bg-primary-foreground data-[status=active]:bg-primary-100 data-[status=active]:text-primary",
expanded ? "pl-[10px] pr-3" : "w-9 justify-center",
)}
onClick={linkClickHandler as never}
>
<item.icon className="size-4 shrink-0" />
{expanded && (
<>
<div className="ml-1 grow truncate">{item.label}</div>
{hasCount && (
<div className="h-6 shrink-0 leading-6">{count}</div>
)}
</>
)}
</Link>
</li>
const count = hasCount ? countDataMap[item.count!] : "";

const content = (
<>
<item.icon className="size-4 shrink-0" />
{expanded && (
<>
<div className="ml-1 grow truncate">{item.label}</div>
{hasCount && (
<div className="h-6 shrink-0 leading-6">{count}</div>
)}
</>
)}
</>
);

const linkClasses = cn(
"comet-body-s flex h-9 w-full items-center gap-2 text-foreground rounded-md hover:bg-primary-foreground data-[status=active]:bg-primary-100 data-[status=active]:text-primary",
expanded ? "pl-[10px] pr-3" : "w-9 justify-center",
);

const itemElement =
item.type === MENU_ITEM_TYPE.router ? (
<li key={item.path} className="flex">
<Link
to={item.path}
params={{ workspaceName }}
className={linkClasses}
onClick={linkClickHandler as never}
>
{content}
</Link>
</li>
) : (
<li key={item.path} className="flex">
<a
href={item.path}
target="_blank"
rel="noreferrer"
className={linkClasses}
>
{content}
</a>
</li>
);

if (expanded) {
return itemElement;
}
Expand Down Expand Up @@ -203,8 +259,14 @@ const SideBar: React.FunctionComponent<SideBarProps> = ({
</Button>
)}
</div>
<div className="flex h-full flex-col justify-between px-3 py-6">
<ul className="flex flex-col gap-2">{renderItems()}</ul>
<div className="flex h-[calc(100%-var(--header-height))] flex-col justify-between px-3 py-6">
<ul className="flex flex-col gap-1">{renderItems(MAIN_MENU_ITEMS)}</ul>
<div className="flex flex-col gap-4">
<Separator />
<ul className="flex flex-col gap-1">
{renderItems(BOTTOM_MENU_ITEMS)}
</ul>
</div>
</div>
</aside>
);
Expand Down
146 changes: 146 additions & 0 deletions apps/opik-frontend/src/components/pages/GetStartedPage/GetStarted.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { MoveRight, SquareArrowOutUpRight } from "lucide-react";
import { Link } from "@tanstack/react-router";
import { Button } from "@/components/ui/button";
import useAppStore from "@/store/AppStore";
import demoProjectImageUrl from "/images/demo-project.png";
import langChainLogoUrl from "/images/integrations/langchain.png";
import liteLLMLogoUrl from "/images/integrations/litellm.png";
import openAILogoUrl from "/images/integrations/openai.png";
import pythonLogoUrl from "/images/integrations/python.png";
import ragasLogoUrl from "/images/integrations/ragas.png";
import ApiKeyInput from "@/components/shared/ApiKeyInput/ApiKeyInput";
import React from "react";

const LOGO_IMAGES = [
pythonLogoUrl,
liteLLMLogoUrl,
openAILogoUrl,
ragasLogoUrl,
langChainLogoUrl,
];

type GetStartedProps = {
apiKey?: string;
userName: string;
};

const GetStarted: React.FunctionComponent<GetStartedProps> = ({
apiKey,
userName,
}) => {
const workspaceName = useAppStore((state) => state.activeWorkspaceName);

return (
<div className="flex w-full justify-center px-4 pb-2 pt-12">
<div className="flex max-w-[1200px] flex-col gap-8">
<div className="flex flex-col gap-3">
<div className="flex flex-row justify-between">
<div className="flex flex-col gap-3">
<div className="comet-body-s text-muted-slate">
Welcome {userName}
</div>

<h1 className="comet-title-l text-foreground-secondary">
Get Started with Opik
</h1>
</div>

<Link to="/$workspaceName/projects" params={{ workspaceName }}>
<Button variant="secondary">
Explore the platform on my own
</Button>
</Link>
</div>

<div className="comet-body-s text-muted-slate">
Start with one of our integrations or a few lines of code to log,
view and evaluate your LLM traces during both development and
production.
</div>
</div>

<div className="flex w-full flex-col gap-14">
<div className="flex w-full flex-row gap-5">
<div className="flex min-w-[65%] flex-1 flex-row justify-between gap-20 rounded-md border bg-white px-8 py-10">
<div className="flex flex-col gap-4">
<div className="comet-title-s text-nowrap text-foreground-secondary">
Integrate Opik
</div>
<div className="comet-body-s text-muted-slate">
A step by step guide for adding Opik into your existing
application. Includes detailed examples.
</div>
<div>
<Link
to="/$workspaceName/quickstart"
params={{ workspaceName }}
search={{ from: "get-started" }}
>
<Button>
To the Quickstart guide
<SquareArrowOutUpRight className="ml-2 size-4" />
</Button>
</Link>
</div>
</div>

<div className="flex min-w-[180px] flex-row flex-wrap content-start items-center gap-3 self-center">
{LOGO_IMAGES.map((url) => {
return <img className="size-[50px]" key={url} src={url} />;
})}
</div>
</div>

{apiKey && (
<div className="flex flex-1 flex-col justify-between gap-5 rounded-md border bg-white px-8 py-10">
<div className="flex flex-col gap-4">
<div className="comet-title-s text-nowrap text-foreground-secondary">
Copy your API key
</div>
<div className="comet-body-s text-muted-slate">
API keys are used to send traces to the Opik platform.
</div>
</div>

<ApiKeyInput apiKey={apiKey} />
</div>
)}
</div>

<div className="flex flex-col gap-5">
<div className="comet-body-accented">Explore demo project</div>

<Link
className="flex cursor-pointer flex-row justify-between rounded-md border bg-white lg:w-[65%]"
to="/$workspaceName/projects"
params={{ workspaceName }}
target="_blank"
>
<img
className="min-w-[340px] object-cover"
src={demoProjectImageUrl}
/>
<div className="flex flex-col justify-between px-8 py-10">
<div className="flex flex-col gap-4">
<div className="comet-title-s text-foreground-secondary">
SQL query generation
</div>
<div className="comet-body-s text-muted-slate">
Perform a text to SQL query generation using LangChain. The
example uses the Chinook database of a music store, with
both employee, customer and invoice data.
</div>
</div>
<div className="comet-body-s flex flex-row items-center justify-end text-[#5155F5]">
Explore project <MoveRight className="ml-2 size-4" />
</div>
</div>
</Link>
</div>
</div>
</div>
</div>
);
};

export default GetStarted;
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import useAppStore from "@/store/AppStore";
import usePluginsStore from "@/store/PluginsStore";
import { Navigate } from "@tanstack/react-router";
import GetStarted from "@/components/pages/GetStartedPage/GetStarted";

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

if (GetStartedPage) {
return <GetStartedPage />;
}

return (
<Navigate to={"/$workspaceName/projects"} params={{ workspaceName }} />
);
return <GetStarted userName="User" />;
};

export default GetStartedPage;
Loading

0 comments on commit 6fb70d5

Please sign in to comment.