Skip to content

Commit

Permalink
feat(playground): playground layout (#4978)
Browse files Browse the repository at this point in the history
* playground layout

* WIP

* final changes

* fix styles

* final fix

* cleanup the store

* Update app/src/components/resize/styles.tsx
  • Loading branch information
mikeldking authored Oct 14, 2024
1 parent 39c9940 commit 2c50441
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 158 deletions.
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"license": "None",
"private": true,
"dependencies": {
"@arizeai/components": "^1.8.3",
"@arizeai/components": "^1.8.4",
"@arizeai/openinference-semantic-conventions": "^0.10.0",
"@arizeai/point-cloud": "^3.0.6",
"@codemirror/autocomplete": "6.12.0",
Expand Down
10 changes: 5 additions & 5 deletions app/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

163 changes: 135 additions & 28 deletions app/src/pages/playground/Playground.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import React, { Fragment } from "react";
import React from "react";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import { css } from "@emotion/react";

import { Button, Flex, Heading, Icon, Icons, View } from "@arizeai/components";
import {
Accordion,
AccordionItem,
Button,
Flex,
Heading,
Icon,
Icons,
View,
} from "@arizeai/components";

import { resizeHandleCSS } from "@phoenix/components/resize";
import {
Expand All @@ -13,8 +23,9 @@ import { InitialPlaygroundState } from "@phoenix/store";
import { NUM_MAX_PLAYGROUND_INSTANCES } from "./constants";
import { PlaygroundCredentialsDropdown } from "./PlaygroundCredentialsDropdown";
import { PlaygroundInputTypeTypeRadioGroup } from "./PlaygroundInputModeRadioGroup";
import { PlaygroundInstance } from "./PlaygroundInstance";
import { PlaygroundOutput } from "./PlaygroundOutput";
import { PlaygroundRunButton } from "./PlaygroundRunButton";
import { PlaygroundTemplate } from "./PlaygroundTemplate";

export function Playground(props: InitialPlaygroundState) {
return (
Expand All @@ -35,12 +46,11 @@ export function Playground(props: InitialPlaygroundState) {
<Flex direction="row" gap="size-100" alignItems="center">
<PlaygroundInputTypeTypeRadioGroup />
<PlaygroundCredentialsDropdown />
<AddPromptButton />
<PlaygroundRunButton />
</Flex>
</Flex>
</View>
<PlaygroundInstances />
<PlaygroundContent />
</Flex>
</PlaygroundProvider>
);
Expand All @@ -50,35 +60,132 @@ function AddPromptButton() {
const addInstance = usePlaygroundContext((state) => state.addInstance);
const numInstances = usePlaygroundContext((state) => state.instances.length);
return (
<Button
variant="default"
size="compact"
aria-label="add prompt"
icon={<Icon svg={<Icons.PlusCircleOutline />} />}
disabled={numInstances >= NUM_MAX_PLAYGROUND_INSTANCES}
onClick={() => {
addInstance();
<div
onClick={(e) => {
// Stop propagation to prevent the accordion from closing
e.stopPropagation();
}}
>
Prompt
</Button>
<Button
variant="default"
size="compact"
aria-label="add prompt"
icon={<Icon svg={<Icons.PlusCircleOutline />} />}
disabled={numInstances >= NUM_MAX_PLAYGROUND_INSTANCES}
onClick={() => {
addInstance();
}}
>
Prompt
</Button>
</div>
);
}

function PlaygroundInstances() {
const playgroundPromptPanelContentCSS = css`
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
.ac-accordion {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
flex: 1 1 auto;
.ac-accordion-item {
height: 100%;
overflow: hidden;
flex: 1 1 auto;
.ac-accordion-itemContent {
height: 100%;
overflow: hidden;
flex: 1 1 auto;
& > * {
height: 100%;
flex: 1 1 auto;
overflow: auto;
box-sizing: border-box;
// Fix padding issue with flexbox
padding-bottom: 57px !important;
}
}
}
}
`;

const playgroundInputOutputPanelContentCSS = css`
display: flex;
flex-direction: column;
height: 100%;
overflow: auto;
`;

function PlaygroundContent() {
const instances = usePlaygroundContext((state) => state.instances);
const numInstances = instances.length;
const isSingleInstance = numInstances === 1;

return (
<Flex direction="row" alignItems="stretch" height="100%" flex="1 1 auto">
<PanelGroup direction="horizontal">
{instances.map((instance, i) => (
<Fragment key={i}>
{i !== 0 && <PanelResizeHandle css={resizeHandleCSS} />}
<Panel defaultSize={50}>
<PlaygroundInstance key={i} playgroundInstanceId={instance.id} />
</Panel>
</Fragment>
))}
</PanelGroup>
</Flex>
<PanelGroup
direction={isSingleInstance ? "horizontal" : "vertical"}
autoSaveId={
isSingleInstance ? "playground-horizontal" : "playground-vertical"
}
>
<Panel>
<div css={playgroundPromptPanelContentCSS}>
<Accordion>
<AccordionItem
title="Prompts"
id="prompts"
extra={<AddPromptButton />}
>
<View height="100%" padding="size-200">
<Flex direction="row" gap="size-100">
{instances.map((instance, i) => (
<div
key={i}
css={css`
flex: 1 1 0px;
`}
>
<PlaygroundTemplate
key={i}
playgroundInstanceId={instance.id}
/>
</div>
))}
</Flex>
</View>
</AccordionItem>
</Accordion>
</div>
</Panel>
<PanelResizeHandle css={resizeHandleCSS} />
<Panel>
<div css={playgroundInputOutputPanelContentCSS}>
<Accordion>
<AccordionItem title="Inputs" id="input">
<View padding="size-200">Inputs go here</View>
</AccordionItem>
<AccordionItem title="Output" id="output">
<View padding="size-200" height="100%">
<Flex direction="row" gap="size-200">
{instances.map((instance, i) => (
<View key={i} flex="1 1 0px">
<PlaygroundOutput
key={i}
playgroundInstanceId={instance.id}
/>
</View>
))}
</Flex>
</View>
</AccordionItem>
</Accordion>
</div>
</Panel>
</PanelGroup>
);
}
75 changes: 42 additions & 33 deletions app/src/pages/playground/PlaygroundCredentialsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import { css } from "@emotion/react";

import {
DropdownButton,
Expand Down Expand Up @@ -32,38 +33,46 @@ export function PlaygroundCredentialsDropdown() {
const setCredential = useCredentialsContext((state) => state.setCredential);
const credentials = useCredentialsContext((state) => state);
return (
<DropdownTrigger placement="bottom">
<DropdownButton size="compact">API Keys</DropdownButton>
<DropdownMenu>
<View padding="size-200">
<Flex direction={"column"} gap={"size-100"}>
<Heading level={2} weight="heavy">
API Keys
</Heading>
<Text color="white70">
API keys are stored in your browser and used to communicate with
their respective API&apos;s.
</Text>
<Form>
{currentProviders.map((provider) => {
const credentialKey = ProviderToCredentialKeyMap[provider];
return (
<TextField
key={provider}
label={credentialKey}
type="password"
isRequired
onChange={(value) => {
setCredential({ credential: credentialKey, value });
}}
value={credentials[credentialKey]}
/>
);
})}
</Form>
</Flex>
</View>
</DropdownMenu>
</DropdownTrigger>
<div
css={css`
.ac-dropdown-button {
min-width: 0px;
}
`}
>
<DropdownTrigger placement="bottom">
<DropdownButton size="compact">API Keys</DropdownButton>
<DropdownMenu>
<View padding="size-200">
<Flex direction={"column"} gap={"size-100"}>
<Heading level={2} weight="heavy">
API Keys
</Heading>
<Text color="white70">
API keys are stored in your browser and used to communicate with
their respective API&apos;s.
</Text>
<Form>
{currentProviders.map((provider) => {
const credentialKey = ProviderToCredentialKeyMap[provider];
return (
<TextField
key={provider}
label={credentialKey}
type="password"
isRequired
onChange={(value) => {
setCredential({ credential: credentialKey, value });
}}
value={credentials[credentialKey]}
/>
);
})}
</Form>
</Flex>
</View>
</DropdownMenu>
</DropdownTrigger>
</div>
);
}
60 changes: 0 additions & 60 deletions app/src/pages/playground/PlaygroundInstance.tsx

This file was deleted.

Loading

0 comments on commit 2c50441

Please sign in to comment.