Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OPIK-220] [UX Improvement] Add quickstart page #398

Merged
merged 1 commit into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading