From 556debaf694b51f93c1475da9c87a467f65869e3 Mon Sep 17 00:00:00 2001 From: Mikyo King Date: Wed, 18 Sep 2024 10:44:15 -0600 Subject: [PATCH] feat(playground): add skeleton playground page (#4648) * chore: add skeleton playground page * chore: playground scaffolding --- app/src/Routes.tsx | 8 +++ app/src/contexts/FeatureFlagsContext.tsx | 4 +- app/src/contexts/PlaygroundContext.tsx | 35 ++++++++++++ app/src/pages/Layout.tsx | 11 ++++ app/src/pages/index.tsx | 1 + app/src/pages/playground/Playground.tsx | 55 +++++++++++++++++++ app/src/pages/playground/PlaygroundInput.tsx | 11 ++++ .../PlaygroundOperationTypeRadioGroup.tsx | 35 ++++++++++++ app/src/pages/playground/PlaygroundOutput.tsx | 11 ++++ app/src/pages/playground/PlaygroundPage.tsx | 7 +++ .../pages/playground/PlaygroundTemplate.tsx | 18 ++++++ app/src/pages/playground/PlaygroundTools.tsx | 11 ++++ app/src/pages/playground/index.tsx | 1 + app/src/store/index.tsx | 1 + app/src/store/playgroundStore.tsx | 34 ++++++++++++ 15 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 app/src/contexts/PlaygroundContext.tsx create mode 100644 app/src/pages/playground/Playground.tsx create mode 100644 app/src/pages/playground/PlaygroundInput.tsx create mode 100644 app/src/pages/playground/PlaygroundOperationTypeRadioGroup.tsx create mode 100644 app/src/pages/playground/PlaygroundOutput.tsx create mode 100644 app/src/pages/playground/PlaygroundPage.tsx create mode 100644 app/src/pages/playground/PlaygroundTemplate.tsx create mode 100644 app/src/pages/playground/PlaygroundTools.tsx create mode 100644 app/src/pages/playground/index.tsx create mode 100644 app/src/store/playgroundStore.tsx diff --git a/app/src/Routes.tsx b/app/src/Routes.tsx index 4b68d29ca0..65dc68309b 100644 --- a/app/src/Routes.tsx +++ b/app/src/Routes.tsx @@ -29,6 +29,7 @@ import { LoginPage, ModelPage, ModelRoot, + PlaygroundPage, ProfilePage, projectLoader, ProjectPage, @@ -147,6 +148,13 @@ const router = createBrowserRouter( /> + } + handle={{ + crumb: () => "Playground", + }} + /> } diff --git a/app/src/contexts/FeatureFlagsContext.tsx b/app/src/contexts/FeatureFlagsContext.tsx index 30676619de..70c7eb7a0e 100644 --- a/app/src/contexts/FeatureFlagsContext.tsx +++ b/app/src/contexts/FeatureFlagsContext.tsx @@ -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; setFeatureFlags: (featureFlags: Record) => void; @@ -12,7 +12,7 @@ export type FeatureFlagsContextType = { export const LOCAL_STORAGE_FEATURE_FLAGS_KEY = "arize-phoenix-feature-flags"; const DEFAULT_FEATURE_FLAGS: Record = { - __CLEAR__: true, + playground: false, }; function getFeatureFlags(): Record { diff --git a/app/src/contexts/PlaygroundContext.tsx b/app/src/contexts/PlaygroundContext.tsx new file mode 100644 index 0000000000..a9f7ecdcea --- /dev/null +++ b/app/src/contexts/PlaygroundContext.tsx @@ -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(null); + +export function PlaygroundProvider({ + children, + ...props +}: PropsWithChildren>) { + const storeRef = useRef(); + if (!storeRef.current) { + storeRef.current = createPlaygroundStore(props); + } + return ( + + {children} + + ); +} + +export function usePlaygroundContext( + 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); +} diff --git a/app/src/pages/Layout.tsx b/app/src/pages/Layout.tsx index ad8b1df504..9fb98b75dd 100644 --- a/app/src/pages/Layout.tsx +++ b/app/src/pages/Layout.tsx @@ -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` @@ -95,6 +96,7 @@ function SideNav() { message: "Failed to log out: " + response.statusText, }); }, [navigate, notifyError]); + const playgroundEnabled = useFeatureFlag("playground"); return ( @@ -123,6 +125,15 @@ function SideNav() { icon={} />} /> + {playgroundEnabled && ( +
  • + } />} + /> +
  • + )}
  • + + + + + Playground + + + + + + + + + + + + + + + + + + + ); +} diff --git a/app/src/pages/playground/PlaygroundInput.tsx b/app/src/pages/playground/PlaygroundInput.tsx new file mode 100644 index 0000000000..aa72e5ba8f --- /dev/null +++ b/app/src/pages/playground/PlaygroundInput.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +import { Card } from "@arizeai/components"; + +export function PlaygroundInput() { + return ( + + Input goes here + + ); +} diff --git a/app/src/pages/playground/PlaygroundOperationTypeRadioGroup.tsx b/app/src/pages/playground/PlaygroundOperationTypeRadioGroup.tsx new file mode 100644 index 0000000000..c93c4de4d1 --- /dev/null +++ b/app/src/pages/playground/PlaygroundOperationTypeRadioGroup.tsx @@ -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 ( + { + if (isGenAIOperationType(v)) { + setOperationType(v); + } + }} + > + + Chat + + + Completion + + + ); +} diff --git a/app/src/pages/playground/PlaygroundOutput.tsx b/app/src/pages/playground/PlaygroundOutput.tsx new file mode 100644 index 0000000000..6c521a707d --- /dev/null +++ b/app/src/pages/playground/PlaygroundOutput.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +import { Card } from "@arizeai/components"; + +export function PlaygroundOutput() { + return ( + + Output goes here + + ); +} diff --git a/app/src/pages/playground/PlaygroundPage.tsx b/app/src/pages/playground/PlaygroundPage.tsx new file mode 100644 index 0000000000..9a2301b6c3 --- /dev/null +++ b/app/src/pages/playground/PlaygroundPage.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +import { Playground } from "./Playground"; + +export function PlaygroundPage() { + return ; +} diff --git a/app/src/pages/playground/PlaygroundTemplate.tsx b/app/src/pages/playground/PlaygroundTemplate.tsx new file mode 100644 index 0000000000..579cd47f7e --- /dev/null +++ b/app/src/pages/playground/PlaygroundTemplate.tsx @@ -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 ( + + {operationType === "chat" ? ( +
    Chat Template goes here
    + ) : ( +
    Completion Template goes here
    + )} +
    + ); +} diff --git a/app/src/pages/playground/PlaygroundTools.tsx b/app/src/pages/playground/PlaygroundTools.tsx new file mode 100644 index 0000000000..b9e5d81bbe --- /dev/null +++ b/app/src/pages/playground/PlaygroundTools.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +import { Card } from "@arizeai/components"; + +export function PlaygroundTools() { + return ( + + Tools go here + + ); +} diff --git a/app/src/pages/playground/index.tsx b/app/src/pages/playground/index.tsx new file mode 100644 index 0000000000..9bc400653a --- /dev/null +++ b/app/src/pages/playground/index.tsx @@ -0,0 +1 @@ +export * from "./PlaygroundPage"; diff --git a/app/src/store/index.tsx b/app/src/store/index.tsx index f478dd1bd0..98fbe30497 100644 --- a/app/src/store/index.tsx +++ b/app/src/store/index.tsx @@ -1,2 +1,3 @@ export * from "./pointCloudStore"; export * from "./tracingStore"; +export * from "./playgroundStore"; diff --git a/app/src/store/playgroundStore.tsx b/app/src/store/playgroundStore.tsx new file mode 100644 index 0000000000..8e4bb96d16 --- /dev/null +++ b/app/src/store/playgroundStore.tsx @@ -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 +) => { + const playgroundStore: StateCreator = (set) => ({ + operationType: "chat", + setOperationType: (operationType: GenAIOperationType) => + set({ operationType }), + ...initialProps, + }); + return create(devtools(playgroundStore)); +}; + +export type PlaygroundStore = ReturnType;