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

feat(opentron-ai-client): add Side Panel component #14886

Merged
merged 6 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module.exports = {
stories: [
'../components/**/*.stories.@(js|jsx|ts|tsx)',
'../app/**/*.stories.@(js|jsx|ts|tsx)',
'../opentrons-ai-client/**/*.stories.@(js|jsx|ts|tsx)',
],

addons: [
Expand Down
2 changes: 1 addition & 1 deletion .storybook/preview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const parameters = {
options: {
storySort: {
method: 'alphabetical',
order: ['Design Tokens', 'Library', 'App', 'ODD'],
order: ['Design Tokens', 'Library', 'App', 'ODD', 'AI'],
},
},
}
Expand Down
1 change: 1 addition & 0 deletions components/src/styles/flexbox.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const FLEX_NONE = 'none'
export const FLEX_AUTO = 'auto'
export const FLEX_MIN_CONTENT = 'min-content'
export const FLEX_MAX_CONTENT = 'max-content'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh good idea


export const ALIGN_NORMAL = 'normal'
export const ALIGN_START = 'start'
Expand Down
5 changes: 2 additions & 3 deletions opentrons-ai-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

[![JavaScript Style Guide][style-guide-badge]][style-guide]

[Download][] | [Support][]

## Overview

The Opentrons AI application helps you to create a protocol with natural language.
Expand Down Expand Up @@ -31,7 +29,7 @@ The UI stack is built using:

Some important directories:

- `opentrons-ai-server` — Opentrons AI application's server
- [opentrons-ai-server][] — Opentrons AI application's server

## Copy management

Expand Down Expand Up @@ -62,3 +60,4 @@ TBD
[babel]: https://babeljs.io/
[vite]: https://vitejs.dev/
[bundle-analyzer]: https://github.com/webpack-contrib/webpack-bundle-analyzer
[opentrons-ai-server]: https://github.com/Opentrons/opentrons/tree/edge/opentrons-ai-server
1 change: 1 addition & 0 deletions opentrons-ai-client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import { Flex, StyledText } from '@opentrons/components'

koji marked this conversation as resolved.
Show resolved Hide resolved
export function App(): JSX.Element {
return (
<Flex>
Expand Down
51 changes: 51 additions & 0 deletions opentrons-ai-client/src/assets/images/opentrons_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@
"api": "API: An API level is 2.15",
"application": "Application: Your protocol's name, describing what it does.",
"commands": "Commands: List the protocol's steps, specifying quantities in microliters and giving exact source and destination locations.",
"got_feedback": "Got feedback? We love to hear it.",
"make_sure_your_prompt": "Make sure your prompt includes the following:",
"metadata": "Metadata: Three pieces of information.",
"modules": "Modules: Thermocycler or Temperature Module.",
"opentronsai_asks_you": "OpentronsAI asks you to provide it!",
"ot2_pipettes": "OT-2 pipettes: Include volume, number of channels, and generation.",
"prc_flex": "PRC (Flex)",
"prc_flex": "PCR (Flex)",
"prc": "PCR",
"reagent_transfer_flex": "Reagent Transfer (Flex)",
"reagent_transfer": "Reagent Transfer",
"robot": "Robot: OT-2.",
"sidebar_body": "Write a prompt in natural language to generate a Reagent Transfer or a PCR protocol for the OT-2 or Opentrons Flex using the Opentrons Python Protocol API.",
"sidebar_header": "Use natural language to generate protocols with OpentronsAI powered by OpenAI",
"stuck": "Stuck? Try these example prompts to get started.",
"share_your_thoughts": "Share your thoughts here",
"side_panel_body": "Write a prompt in natural language to generate a Reagent Transfer or a PCR protocol for the OT-2 or Opentrons Flex using the Opentrons Python Protocol API.",
"side_panel_header": "Use natural language to generate protocols with OpentronsAI powered by OpenAI",
"tipracks_and_labware": "Tip racks and labware: Use names from the Opentrons Labware Library.",
"try_example_prompts": "Stuck? Try these example prompts to get started.",
"type_your_prompt": "Type your prompt...",
"well_allocations": "Well allocations: Describe where liquids should go in labware.",
"what_if_you": "What if you don’t provide all of those pieces of information?",
Expand Down
9 changes: 7 additions & 2 deletions opentrons-ai-client/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { I18nextProvider } from 'react-i18next'

import { i18n } from './i18n'
import { App } from './App'

const rootElement = document.getElementById('root')
if (rootElement) {
if (rootElement != null) {
ReactDOM.createRoot(rootElement).render(
<React.StrictMode>
<App />
<I18nextProvider i18n={i18n}>
<App />
</I18nextProvider>
</React.StrictMode>
)
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react'
import { I18nextProvider } from 'react-i18next'
import { i18n } from '../../i18n'
import { SidePanel as SidePanelComponent } from './index'

import type { Meta, StoryObj } from '@storybook/react'

const meta: Meta<typeof SidePanelComponent> = {
title: 'AI/molecules/SidePanel',
component: SidePanelComponent,
decorators: [
Story => (
<I18nextProvider i18n={i18n}>
<Story />
</I18nextProvider>
),
],
}
export default meta
type Story = StoryObj<typeof SidePanelComponent>
export const SidePanel: Story = {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import { screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'

import { renderWithProviders } from '../../../__testing-utils__'
import { i18n } from '../../../i18n'

import { SidePanel } from '../index'

const LOGO_FILE_NAME =
'/opentrons-ai-client/src/assets/images/opentrons_logo.svg'

const render = (): ReturnType<typeof renderWithProviders> => {
return renderWithProviders(<SidePanel />, {
i18nInstance: i18n,
})
}

describe('SidePanel', () => {
it('should render logo and text', () => {
render()
const image = screen.getByRole('img')
expect(image.getAttribute('src')).toEqual(LOGO_FILE_NAME)
screen.getByText(
'Use natural language to generate protocols with OpentronsAI powered by OpenAI'
)
screen.getByText(
'Write a prompt in natural language to generate a Reagent Transfer or a PCR protocol for the OT-2 or Opentrons Flex using the Opentrons Python Protocol API.'
)
screen.getByText('Stuck? Try these example prompts to get started.')
})

it('should render buttons', () => {
render()
screen.getByRole('button', { name: 'PCR' })
screen.getByRole('button', { name: 'PCR (Flex)' })
screen.getByRole('button', { name: 'Reagent Transfer' })
screen.getByRole('button', { name: 'Reagent Transfer (Flex)' })
})
it.todo('should call a mock function when clicking a button')
})
103 changes: 103 additions & 0 deletions opentrons-ai-client/src/molecules/SidePanel/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React from 'react'
import styled, { css } from 'styled-components'
import { useTranslation } from 'react-i18next'
import {
COLORS,
DIRECTION_COLUMN,
Flex,
Link,
PrimaryButton,
SPACING,
StyledText,
TYPOGRAPHY,
WRAP,
} from '@opentrons/components'
import LOGO_PATH from '../../assets/images/opentrons_logo.svg'

const IMAGE_ALT = 'Opentrons logo'
const FEEDBACK_FORM_LINK = ''
koji marked this conversation as resolved.
Show resolved Hide resolved
export function SidePanel(): JSX.Element {
const { t } = useTranslation('protocol_generator')
return (
<Flex
padding={SPACING.spacing40}
gridGap={SPACING.spacing80}
flexDirection={DIRECTION_COLUMN}
backgroundColor={COLORS.black90}
width="24.375rem"
height="64rem"
>
{/* logo */}
<Flex>
<img src={LOGO_PATH} width="194.192" height="48.133" alt={IMAGE_ALT} />
</Flex>

{/* body text */}
<Flex gridGap={SPACING.spacing24} flexDirection={DIRECTION_COLUMN}>
<StyledText css={HEADER_TEXT_STYLE}>
{t('side_panel_header')}
</StyledText>
<StyledText css={BODY_TEXT_STYLE}>{t('side_panel_body')}</StyledText>
</Flex>

{/* buttons */}
<Flex gridGap={SPACING.spacing24} flexDirection={DIRECTION_COLUMN}>
<StyledText css={BUTTON_GUIDE_TEXT_STYLE}>
{t('try_example_prompts')}
</StyledText>

<Flex gridGap={SPACING.spacing16} flexWrap={WRAP}>
{/* ToDo(kk:04/11/2024) add a button component */}
<PromptButton>{t('reagent_transfer')}</PromptButton>
<PromptButton>{t('reagent_transfer_flex')}</PromptButton>
<PromptButton>{t('prc')}</PromptButton>
<PromptButton>{t('prc_flex')}</PromptButton>
</Flex>
</Flex>
<Flex flexDirection={DIRECTION_COLUMN}>
<StyledText
fontSize={TYPOGRAPHY.fontSize20}
lineHeight={TYPOGRAPHY.lineHeight24}
color={COLORS.white}
>
{t('got_feedback')}
</StyledText>
<FeedbackLink external href={FEEDBACK_FORM_LINK}>
{t('share_your_thoughts')}
</FeedbackLink>
</Flex>
</Flex>
)
}

const HEADER_TEXT_STYLE = css`
font-size: ${TYPOGRAPHY.fontSize32};
line-height: ${TYPOGRAPHY.lineHeight42};
font-weight: ${TYPOGRAPHY.fontWeightBold};
color: ${COLORS.white};
`
const BODY_TEXT_STYLE = css`
font-size: ${TYPOGRAPHY.fontSize20};
line-height: ${TYPOGRAPHY.lineHeight24};
font-weight: ${TYPOGRAPHY.fontWeightRegular};
color: ${COLORS.white};
`
const BUTTON_GUIDE_TEXT_STYLE = css`
font-size: ${TYPOGRAPHY.fontSize20};
line-height: ${TYPOGRAPHY.lineHeight24};
font-weight: ${TYPOGRAPHY.fontWeightSemiBold};
color: ${COLORS.white};
`

const PromptButton = styled(PrimaryButton)`
border-radius: 2rem;
white-space: nowrap;
`

const FeedbackLink = styled(Link)`
font-size: ${TYPOGRAPHY.fontSize20};
line-height: ${TYPOGRAPHY.lineHeight24};
font-weight: ${TYPOGRAPHY.fontWeightBold};
color: ${COLORS.white};
text-decoration: ${TYPOGRAPHY.textDecorationUnderline};
`
1 change: 1 addition & 0 deletions opentrons-ai-client/src/molecules/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SidePanel'
Loading