Skip to content

Commit

Permalink
feat(playground): add skeleton playground page (#4648)
Browse files Browse the repository at this point in the history
* chore: add skeleton playground page

* chore: playground scaffolding
  • Loading branch information
mikeldking authored Sep 18, 2024
1 parent ea707da commit 556deba
Show file tree
Hide file tree
Showing 15 changed files with 241 additions and 2 deletions.
8 changes: 8 additions & 0 deletions app/src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
LoginPage,
ModelPage,
ModelRoot,
PlaygroundPage,
ProfilePage,
projectLoader,
ProjectPage,
Expand Down Expand Up @@ -147,6 +148,13 @@ const router = createBrowserRouter(
/>
</Route>
</Route>
<Route
path="/playground"
element={<PlaygroundPage />}
handle={{
crumb: () => "Playground",
}}
/>
<Route
path="/apis"
element={<APIsPage />}
Expand Down
4 changes: 2 additions & 2 deletions app/src/contexts/FeatureFlagsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useHotkeys } from "react-hotkeys-hook";

import { Dialog, DialogContainer, Switch, View } from "@arizeai/components";

type FeatureFlag = "__CLEAR__";
type FeatureFlag = "playground";
export type FeatureFlagsContextType = {
featureFlags: Record<FeatureFlag, boolean>;
setFeatureFlags: (featureFlags: Record<FeatureFlag, boolean>) => void;
Expand All @@ -12,7 +12,7 @@ export type FeatureFlagsContextType = {
export const LOCAL_STORAGE_FEATURE_FLAGS_KEY = "arize-phoenix-feature-flags";

const DEFAULT_FEATURE_FLAGS: Record<FeatureFlag, boolean> = {
__CLEAR__: true,
playground: false,
};

function getFeatureFlags(): Record<FeatureFlag, boolean> {
Expand Down
35 changes: 35 additions & 0 deletions app/src/contexts/PlaygroundContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { createContext, PropsWithChildren, useRef } from "react";
import { useZustand } from "use-zustand";

import {
createPlaygroundStore,
PlaygroundProps,
PlaygroundState,
PlaygroundStore,
} from "@phoenix/store/playgroundStore";

export const PlaygroundContext = createContext<PlaygroundStore | null>(null);

export function PlaygroundProvider({
children,
...props
}: PropsWithChildren<Partial<PlaygroundProps>>) {
const storeRef = useRef<PlaygroundStore>();
if (!storeRef.current) {
storeRef.current = createPlaygroundStore(props);
}
return (
<PlaygroundContext.Provider value={storeRef.current}>
{children}
</PlaygroundContext.Provider>
);
}

export function usePlaygroundContext<T>(
selector: (state: PlaygroundState) => T,
equalityFn?: (left: T, right: T) => boolean
): T {
const store = React.useContext(PlaygroundContext);
if (!store) throw new Error("Missing PlaygroundContext.Provider in the tree");
return useZustand(store, selector, equalityFn);
}
11 changes: 11 additions & 0 deletions app/src/pages/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
TopNavbar,
} from "@phoenix/components/nav";
import { useNotifyError } from "@phoenix/contexts";
import { useFeatureFlag } from "@phoenix/contexts/FeatureFlagsContext";
import { useFunctionality } from "@phoenix/contexts/FunctionalityContext";

const layoutCSS = css`
Expand Down Expand Up @@ -95,6 +96,7 @@ function SideNav() {
message: "Failed to log out: " + response.statusText,
});
}, [navigate, notifyError]);
const playgroundEnabled = useFeatureFlag("playground");
return (
<SideNavbar>
<Brand />
Expand Down Expand Up @@ -123,6 +125,15 @@ function SideNav() {
icon={<Icon svg={<Icons.DatabaseOutline />} />}
/>
</li>
{playgroundEnabled && (
<li>
<NavLink
to="/playground"
text="Playground"
icon={<Icon svg={<Icons.PlayCircleOutline />} />}
/>
</li>
)}
<li>
<NavLink
to="/apis"
Expand Down
1 change: 1 addition & 0 deletions app/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ export * from "./settings";
export * from "./apis";
export * from "./auth";
export * from "./profile";
export * from "./playground";
export * from "./AuthenticatedRoot";
export * from "./authenticatedRootLoader";
55 changes: 55 additions & 0 deletions app/src/pages/playground/Playground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from "react";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import { css } from "@emotion/react";

import { Button, Flex, Heading, View } from "@arizeai/components";

import { resizeHandleCSS } from "@phoenix/components/resize";
import { PlaygroundProvider } from "@phoenix/contexts/PlaygroundContext";

import { PlaygroundInput } from "./PlaygroundInput";
import { PlaygroundOperationTypeRadioGroup } from "./PlaygroundOperationTypeRadioGroup";
import { PlaygroundOutput } from "./PlaygroundOutput";
import { PlaygroundTemplate } from "./PlaygroundTemplate";
import { PlaygroundTools } from "./PlaygroundTools";

const panelContentCSS = css`
padding: var(--ac-global-dimension-size-200);
overflow: auto;
display: flex;
flex-direction: column;
gap: var(--ac-global-dimension-size-200);
`;

export function Playground() {
return (
<PlaygroundProvider>
<View
borderBottomColor="dark"
borderBottomWidth="thin"
padding="size-200"
>
<Flex direction="row" justifyContent="space-between">
<View>
<Flex direction="row" gap="size-200" alignItems="center">
<Heading level={1}>Playground</Heading>
<PlaygroundOperationTypeRadioGroup />
</Flex>
</View>
<Button variant="default">API Keys</Button>
</Flex>
</View>
<PanelGroup direction="horizontal">
<Panel defaultSize={50} order={1} css={panelContentCSS}>
<PlaygroundTemplate />
<PlaygroundTools />
</Panel>
<PanelResizeHandle css={resizeHandleCSS} />
<Panel defaultSize={50} order={2} css={panelContentCSS}>
<PlaygroundInput />
<PlaygroundOutput />
</Panel>
</PanelGroup>
</PlaygroundProvider>
);
}
11 changes: 11 additions & 0 deletions app/src/pages/playground/PlaygroundInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";

import { Card } from "@arizeai/components";

export function PlaygroundInput() {
return (
<Card title="Input" collapsible variant="compact">
Input goes here
</Card>
);
}
35 changes: 35 additions & 0 deletions app/src/pages/playground/PlaygroundOperationTypeRadioGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";

import { Radio, RadioGroup } from "@arizeai/components";

import { usePlaygroundContext } from "@phoenix/contexts/PlaygroundContext";
import { GenAIOperationType } from "@phoenix/store";

function isGenAIOperationType(v: string): v is GenAIOperationType {
return v === "chat" || v === "text_completion";
}

export function PlaygroundOperationTypeRadioGroup() {
const operationType = usePlaygroundContext((state) => state.operationType);
const setOperationType = usePlaygroundContext(
(state) => state.setOperationType
);
return (
<RadioGroup
value={operationType}
variant="inline-button"
onChange={(v) => {
if (isGenAIOperationType(v)) {
setOperationType(v);
}
}}
>
<Radio label="Chat" value={"chat"}>
Chat
</Radio>
<Radio label="Completion" value={"text_completion"}>
Completion
</Radio>
</RadioGroup>
);
}
11 changes: 11 additions & 0 deletions app/src/pages/playground/PlaygroundOutput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";

import { Card } from "@arizeai/components";

export function PlaygroundOutput() {
return (
<Card title="Output" collapsible variant="compact">
Output goes here
</Card>
);
}
7 changes: 7 additions & 0 deletions app/src/pages/playground/PlaygroundPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";

import { Playground } from "./Playground";

export function PlaygroundPage() {
return <Playground />;
}
18 changes: 18 additions & 0 deletions app/src/pages/playground/PlaygroundTemplate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";

import { Card } from "@arizeai/components";

import { usePlaygroundContext } from "@phoenix/contexts/PlaygroundContext";

export function PlaygroundTemplate() {
const operationType = usePlaygroundContext((state) => state.operationType);
return (
<Card title="Template" collapsible variant="compact">
{operationType === "chat" ? (
<div>Chat Template goes here</div>
) : (
<div>Completion Template goes here</div>
)}
</Card>
);
}
11 changes: 11 additions & 0 deletions app/src/pages/playground/PlaygroundTools.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";

import { Card } from "@arizeai/components";

export function PlaygroundTools() {
return (
<Card title="Tools" collapsible variant="compact">
Tools go here
</Card>
);
}
1 change: 1 addition & 0 deletions app/src/pages/playground/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./PlaygroundPage";
1 change: 1 addition & 0 deletions app/src/store/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./pointCloudStore";
export * from "./tracingStore";
export * from "./playgroundStore";
34 changes: 34 additions & 0 deletions app/src/store/playgroundStore.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { create, StateCreator } from "zustand";
import { devtools } from "zustand/middleware";

export type GenAIOperationType = "chat" | "text_completion";
export interface PlaygroundProps {
/**
* How the LLM API should be invoked. Distinguishes between chat and text_completion.
* @see https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/
* @default "chat"
*/
operationType: GenAIOperationType;
}

export interface PlaygroundState extends PlaygroundProps {
/**
* Setter for the invocation mode
* @param operationType
*/
setOperationType: (operationType: GenAIOperationType) => void;
}

export const createPlaygroundStore = (
initialProps?: Partial<PlaygroundProps>
) => {
const playgroundStore: StateCreator<PlaygroundState> = (set) => ({
operationType: "chat",
setOperationType: (operationType: GenAIOperationType) =>
set({ operationType }),
...initialProps,
});
return create(devtools(playgroundStore));
};

export type PlaygroundStore = ReturnType<typeof createPlaygroundStore>;

0 comments on commit 556deba

Please sign in to comment.