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(opentrons-ai-client): add prompt button #14970

Merged
merged 16 commits into from
Apr 23, 2024
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,8 +10,8 @@
"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.",
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
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',
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'

export const promptContext = React.createContext<string>('')
export const setPromptContext = React.createContext<
React.Dispatch<React.SetStateAction<string>>
>(() => undefined)

interface PromptProviderProps {
children: React.ReactNode
}

export function PromptProvider({
children,
}: PromptProviderProps): React.ReactElement {
const [prompt, setPrompt] = React.useState<string>('')

return (
<promptContext.Provider value={prompt}>
<setPromptContext.Provider value={setPrompt}>
{children}
</setPromptContext.Provider>
</promptContext.Provider>
)
}
Loading
Loading