Skip to content

Commit

Permalink
feat(opentrons-ai-client): add prompt button (#14970)
Browse files Browse the repository at this point in the history
* feat(opentrons-ai-client): add prompt button
  • Loading branch information
koji authored and Carlos-fernandez committed May 20, 2024
1 parent 6849d64 commit c0ac048
Show file tree
Hide file tree
Showing 16 changed files with 403 additions and 24 deletions.
1 change: 1 addition & 0 deletions .eslintcache

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ export function renderWithProviders<State>(
Component: React.ReactElement,
options?: RenderWithProvidersOptions<State>
): [RenderResult, Store<State>] {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const { initialState = {}, i18nInstance = null } = options || {}
const { initialState = {}, i18nInstance = null } = options ?? {}

const store: Store<State> = createStore(
vi.fn(),
Expand All @@ -32,9 +31,9 @@ export function renderWithProviders<State>(

const queryClient = new QueryClient()

const ProviderWrapper: React.ComponentType<React.PropsWithChildren<{}>> = ({
children,
}) => {
const ProviderWrapper: React.ComponentType<
React.PropsWithChildren<Record<string, unknown>>
> = ({ children }) => {
const BaseWrapper = (
<QueryClientProvider client={queryClient}>
<Provider store={store}>{children}</Provider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
"opentronsai_asks": "OpentronsAI asks you to provide it!",
"opentronsai": "OpentronsAI",
"ot2_pipettes": "OT-2 pipettes: Include volume, number of channels, and generation.",
"prc_flex": "PCR (Flex)",
"prc": "PCR",
"pcr_flex": "PCR (Flex)",
"pcr": "PCR",
"reagent_transfer_flex": "Reagent Transfer (Flex)",
"reagent_transfer": "Reagent Transfer",
"robot": "Robot: OT-2.",
"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",
"simulator_description": "Once OpentronsAI has written your protocol, type \"simulate\" in the prompt box to try it out.",
"tipracks_and_labware": "<span>Tip racks and labware: Use names from the <a>Opentrons Labware Library</a>.</spa>",
"try_example_prompts": "Stuck? Try these example prompts to get started.",
"type_your_prompt": "Type your prompt...",
Expand Down
1 change: 1 addition & 0 deletions opentrons-ai-client/src/assets/prompts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './prompt-data'
147 changes: 147 additions & 0 deletions opentrons-ai-client/src/assets/prompts/prompt-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
export const reagentTransfer = `
Write a protocol for the Opentrons OT-2 as described below:
Metadata:
- Application: Reagent transfer
- Robot: OT-2
- API: 2.15
Pipette mount:
- P1000 Single-Channel GEN2 is mounted on left
- P300 Single-Channel GEN2 is mounted on right
Labware:
- Source Labware: Thermo Scientific Nunc 96 Well Plate 2000 µL on slot 7
- Destination Labware: Opentrons 24 Well Aluminum Block with NEST 0.5 mL Screwcap on slot 3
- Tiprack: Opentrons 96 Filter Tip Rack 1000 µL on slot 4
Commands:
- Using P1000 Single-Channel GEN2 pipette on left mount, transfer 195.0 uL of reagent
from H10, F12, D7, B1, C8 wells in source labware
to first well in the destination labware.
Use new tip for each transfer.
`

export const flexReagentTransfer = `
Write a protocol for the Opentrons Flex as described below:
Metadata and requirements:
- Application: Reagent transfer
- Robot: Flex
- API: 2.15
Pipette Mount:
- Flex 1-Channel 1000 µL Pipette is mounted on the left side
- Flex 1-Channel 50 µL Pipette is mounted on the right side
Labware:
- Source Labware 1: NEST 1 Well Reservoir 195 mL is positioned on slot B1
- Source Labware 2: Bio-Rad 384 Well Plate 50 µL is positioned on slot B2
- Source Labware 3: Bio-Rad 96 Well Plate 200 µL is positioned on slot B3
- Destination Labware 1: Corning 384 Well Plate 112 µL Flat is positioned on slot D1
- Destination Labware 2: Corning 96 Well Plate 360 µL Flat is positioned on slot D2
- Tiprack 1: Opentrons Flex 96 Filter Tip Rack 200 µL is used on slot A1
- Tiprack 2: Opentrons Flex 96 Filter Tip Rack 50 µL is used on slot A2
Commands
- Using Flex 1-Channel 50 µL Pipette on right mount, transfer 15 µL from first of source labware 1 to each well
in destination labware 1 and destination labware 2. Reuse the same tip.
- Again using Flex 1-Channel 50 µL Pipette, transfer 20 µL from each well in source labware 2 to
each well in the destination labware 1. Reuse the same tip.
- Using Flex 1-Channel 1000 µL Pipette on left mount, transfer 100µL liquid from each well in source labware 3
to each well in destination labware 2. Use a new tip each time.
`

export const pcr = `
Write a protocol for the Opentrons OT-2 as described below:
Metadata:
- Application: ThermoPrime Taq DNA Polymerase, with 10x buffer and separate vial of 25 mM MgCl2Thermo Scientific kit PCR amplification
- Robot: OT-2
- API: 2.15
Pipette mount:
- P20 Single Channel is mounted on the right side
Modules:
- Thermocycler module is present on slot 7
- Temperature module is place on slot 3
Labware:
- Source sample labware is Opentrons 96 Well Aluminum Block with NEST Well Plate 100 µL plate placed on slot 1
- Source mastermix labware is Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap, placed on temperature module on slot 3
- Destination Labware is an Opentrons Tough 96 Well Plate 200 µL PCR Full Skirt placed on thermocycler module on slot 7
- 20 ul Filter tiprack is used on slot 4
Well allocation:
- source wells are first 41 wells column wise in both master mix and sample source plates
- destination wells: first 41 wells column wise on thermocycler
Commands:
Note that every step is a single entity. Do not combine. Also, every step should be performed in order.
1. The total number of samples is 41
2. Set the thermocycler such that:
- block temperature is 6 degree C
- lid temperature to 90 degree C
- lid open
3. Set the master mix temperature module at 10 C. The temperature module wait time is 50 seconds.
4. Transfer 10 uL of mastermix from source well to destination well. Use the same pipette tip for all transfers.
5. Transfer 3 ul of sample to destination well reusing tip everytime. After dispensing, mix the sample and mastermix
of 13 ul total volume 4 times and then perform blowout before dropping tip.
6. Close the lid of the thermocycler.
7. Set the thermocycle to following parameters (**note that each step is independent**):
Step 1: 66 degree C for 47 seconds for 1 cycles
Step 2: 88 degree C for 28 seconds, 82 degree C for 14 seconds, 68 degree C for 68 seconds for 15 cycles
Step 3: 70 degree C for 240 seconds for 1 cycles
Then, execute thermocycler profile for each step.
8. After the above three steps are completed, hold thermocycler block at 4 C
9. Open thermocycler lid
10. Deactivate the temperature modules
`

export const flexPcr = `
Write a protocol for the Opentrons Flex as described below:
Metadata and requirements:
- Application: GeneAmp2x PCR amplification
- Robot: Flex
- API: 2.15
Pipette mount:
- Flex 1-Channel 50 µL Pipette is mounted on the right side
Modules and adapters:
- Thermocycler GEN 2 module is present on slot A1+B1
- Temperature module GEN 2 is place on slot D3
Labware:
- Source sample labware is Opentrons 96 Well Aluminum Block with NEST Well Plate 100 µL plate placed on slot D1
- Source mastermix labware is Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap, placed on temperature module on slot D3
- Destination Labware is an Opentrons Tough 96 Well Plate 200 µL PCR Full Skirt placed on thermocycler GEN 2 module
- Opentrons Flex 96 Filter Tip Rack 50 µL is used on slot C1
Sample position:
- source wells are first 64 wells column wise in both master mix and sample source plates
- destination wells: first 64 wells column wise on thermocycler
Commands:
Note that every step is a single entity. Do not combine. Also, every step should be performed in order.
1. The total number of samples is 64
2. Set the thermocycler such that
- block temperature is 6 degree C
- lid temperature to 90 degree C
- lid open
3. Set the master mix temperature module at 10 C. The temperature module wait time is 50 seconds.
4. Transfer 10 uL of mastermix from source well to destination well. Use the same pipette tip for all transfers.
5. Transfer 3 ul of sample to destination well reusing tip everytime. After dispensing, mix the sample and mastermix
of 13 ul total volume 4 times and then perform blowout before dropping tip.
6. Close the lid of the thermocycler.
7. Set the thermocycle to following parameters (**note that each step is independent**):
Step 1: 66 degree C for 47 seconds for 1 cycles
Step 2: 88 degree C for 28 seconds, 82 degree C for 14 seconds, 68 degree C for 68 seconds for 15 cycles
Step 3: 70 degree C for 240 seconds for 1 cycles
Then, execute thermocycler profile for each step.
8. After the above three steps are completed, hold thermocycler block at 4 C
9. Open thermocycler lid
10. Deactivate the temperature modules
`
5 changes: 4 additions & 1 deletion opentrons-ai-client/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import ReactDOM from 'react-dom/client'
import { I18nextProvider } from 'react-i18next'
import { GlobalStyle } from './atoms/GlobalStyle'
import { PromptProvider } from './organisms/PromptButton/PromptProvider'

import { i18n } from './i18n'
import { App } from './App'
Expand All @@ -12,7 +13,9 @@ if (rootElement != null) {
<React.StrictMode>
<GlobalStyle />
<I18nextProvider i18n={i18n}>
<App />
<PromptProvider>
<App />
</PromptProvider>
</I18nextProvider>
</React.StrictMode>
)
Expand Down
20 changes: 17 additions & 3 deletions opentrons-ai-client/src/molecules/InputPrompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'

import { promptContext } from '../../organisms/PromptButton/PromptProvider'
import type { SubmitHandler } from 'react-hook-form'

// ToDo (kk:04/19/2024) Note this interface will be used by prompt buttons in SidePanel
Expand All @@ -28,11 +28,13 @@ interface InputType {

export function InputPrompt(/* props: InputPromptProps */): JSX.Element {
const { t } = useTranslation('protocol_generator')
const { register, handleSubmit, watch } = useForm<InputType>({
const { register, handleSubmit, watch, setValue } = useForm<InputType>({
defaultValues: {
userPrompt: '',
},
})
const usePromptValue = (): string => React.useContext(promptContext)
const promptFromButton = usePromptValue()
const userPrompt = watch('userPrompt') ?? ''

const onSubmit: SubmitHandler<InputType> = async data => {
Expand All @@ -41,6 +43,15 @@ export function InputPrompt(/* props: InputPromptProps */): JSX.Element {
console.log('user prompt', userPrompt)
}

const calcTextAreaHeight = (): number => {
const rowsNum = userPrompt.split('\n').length
return rowsNum
}

React.useEffect(() => {
if (promptFromButton !== '') setValue('userPrompt', promptFromButton)
}, [promptFromButton, setValue])

return (
<StyledForm id="User_Prompt" onSubmit={() => handleSubmit(onSubmit)}>
<Flex
Expand All @@ -51,9 +62,10 @@ export function InputPrompt(/* props: InputPromptProps */): JSX.Element {
borderRadius={BORDERS.borderRadius4}
justifyContent={JUSTIFY_CENTER}
alignItems={ALIGN_CENTER}
maxHeight="21.25rem"
>
<StyledTextarea
rows={1}
rows={calcTextAreaHeight()}
placeholder={t('type_your_prompt')}
{...register('userPrompt')}
/>
Expand All @@ -70,6 +82,8 @@ const StyledForm = styled.form`
const StyledTextarea = styled.textarea`
resize: none;
min-height: 3.75rem;
max-height: 17.25rem;
overflow-y: auto;
background-color: ${COLORS.white};
border: none;
outline: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ describe('PromptGuide', () => {
'What if you don’t provide all of those pieces of information?'
)
screen.getByText('OpentronsAI asks you to provide it!')
screen.getByText(
'Once OpentronsAI has written your protocol, type "simulate" in the prompt box to try it out.'
)
})
it('should have the right url', () => {
render()
Expand Down
3 changes: 3 additions & 0 deletions opentrons-ai-client/src/molecules/PromptGuide/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ export function PromptGuide(): JSX.Element {
span: <StyledText css={BODY_TEXT_STYLE} />,
}}
/>
<StyledText css={BODY_TEXT_STYLE}>
{t('simulator_description')}
</StyledText>
</Flex>
)
}
Expand Down
17 changes: 5 additions & 12 deletions opentrons-ai-client/src/molecules/SidePanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ import React from 'react'
import styled, { css } from 'styled-components'
import { useTranslation } from 'react-i18next'
import {
BORDERS,
COLORS,
DIRECTION_COLUMN,
Flex,
Link,
PrimaryButton,
SPACING,
StyledText,
TYPOGRAPHY,
WRAP,
} from '@opentrons/components'
import { PromptButton } from '../../organisms/PromptButton'
import LOGO_PATH from '../../assets/images/opentrons_logo.svg'

const IMAGE_ALT = 'Opentrons logo'
Expand Down Expand Up @@ -47,11 +46,10 @@ export function SidePanel(): JSX.Element {
</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>
<PromptButton buttonText={t('reagent_transfer')} />
<PromptButton buttonText={t('reagent_transfer_flex')} />
<PromptButton buttonText={t('pcr')} />
<PromptButton buttonText={t('pcr_flex')} />
</Flex>
</Flex>
<Flex flexDirection={DIRECTION_COLUMN}>
Expand Down Expand Up @@ -89,11 +87,6 @@ const BUTTON_GUIDE_TEXT_STYLE = css`
color: ${COLORS.white};
`

const PromptButton = styled(PrimaryButton)`
border-radius: ${BORDERS.borderRadiusFull};
white-space: nowrap;
`

const FeedbackLink = styled(Link)`
font-size: ${TYPOGRAPHY.fontSize20};
line-height: ${TYPOGRAPHY.lineHeight24};
Expand Down
1 change: 0 additions & 1 deletion opentrons-ai-client/src/molecules/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react'
import { Flex, SPACING } from '@opentrons/components'
import { PromptProvider, promptContext } from './PromptProvider'
import { PromptButton as PromptButtonComponent } from '.'

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

const buttonTextOptions = [
'Reagent Transfer',
'Reagent Transfer (Flex)',
'PCR',
'PCR (Flex)',
]

// ToDo (kk:04/22/2024) fix this stories
const meta: Meta<typeof PromptButtonComponent> = {
title: 'AI/organisms/PromptButton',
component: PromptButtonComponent,
argTypes: {
buttonText: {
control: {
type: 'select',
},
options: buttonTextOptions,
},
},
decorators: [
Story => {
return (
<PromptProvider>
<Story />
<PromptDisplay />
</PromptProvider>
)
},
],
}
export default meta

const PromptDisplay = (): JSX.Element => {
const usePromptValue = (): string => React.useContext(promptContext)
const promptFromButton = usePromptValue()
return <Flex padding={SPACING.spacing16}>{promptFromButton}</Flex>
}

type Story = StoryObj<typeof PromptButtonComponent>

export const PromptButton: Story = {
args: {
buttonText: 'Reagent Transfer',
},
}
Loading

0 comments on commit c0ac048

Please sign in to comment.