Skip to content

Commit 4c89730

Browse files
authored
feat(opentrons-ai-client): add prompt button (#14970)
* feat(opentrons-ai-client): add prompt button
1 parent 26929a2 commit 4c89730

File tree

16 files changed

+403
-24
lines changed

16 files changed

+403
-24
lines changed

.eslintcache

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

opentrons-ai-client/src/__testing-utils__/renderWithProviders.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ export function renderWithProviders<State>(
2020
Component: React.ReactElement,
2121
options?: RenderWithProvidersOptions<State>
2222
): [RenderResult, Store<State>] {
23-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
24-
const { initialState = {}, i18nInstance = null } = options || {}
23+
const { initialState = {}, i18nInstance = null } = options ?? {}
2524

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

3332
const queryClient = new QueryClient()
3433

35-
const ProviderWrapper: React.ComponentType<React.PropsWithChildren<{}>> = ({
36-
children,
37-
}) => {
34+
const ProviderWrapper: React.ComponentType<
35+
React.PropsWithChildren<Record<string, unknown>>
36+
> = ({ children }) => {
3837
const BaseWrapper = (
3938
<QueryClientProvider client={queryClient}>
4039
<Provider store={store}>{children}</Provider>

opentrons-ai-client/src/assets/localization/en/protocol_generator.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010
"opentronsai_asks": "OpentronsAI asks you to provide it!",
1111
"opentronsai": "OpentronsAI",
1212
"ot2_pipettes": "OT-2 pipettes: Include volume, number of channels, and generation.",
13-
"prc_flex": "PCR (Flex)",
14-
"prc": "PCR",
13+
"pcr_flex": "PCR (Flex)",
14+
"pcr": "PCR",
1515
"reagent_transfer_flex": "Reagent Transfer (Flex)",
1616
"reagent_transfer": "Reagent Transfer",
1717
"robot": "Robot: OT-2.",
1818
"share_your_thoughts": "Share your thoughts here",
1919
"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.",
2020
"side_panel_header": "Use natural language to generate protocols with OpentronsAI powered by OpenAI",
21+
"simulator_description": "Once OpentronsAI has written your protocol, type \"simulate\" in the prompt box to try it out.",
2122
"tipracks_and_labware": "<span>Tip racks and labware: Use names from the <a>Opentrons Labware Library</a>.</spa>",
2223
"try_example_prompts": "Stuck? Try these example prompts to get started.",
2324
"type_your_prompt": "Type your prompt...",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './prompt-data'
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
export const reagentTransfer = `
2+
Write a protocol for the Opentrons OT-2 as described below:
3+
4+
Metadata:
5+
- Application: Reagent transfer
6+
- Robot: OT-2
7+
- API: 2.15
8+
9+
Pipette mount:
10+
- P1000 Single-Channel GEN2 is mounted on left
11+
- P300 Single-Channel GEN2 is mounted on right
12+
13+
Labware:
14+
- Source Labware: Thermo Scientific Nunc 96 Well Plate 2000 µL on slot 7
15+
- Destination Labware: Opentrons 24 Well Aluminum Block with NEST 0.5 mL Screwcap on slot 3
16+
- Tiprack: Opentrons 96 Filter Tip Rack 1000 µL on slot 4
17+
18+
Commands:
19+
- Using P1000 Single-Channel GEN2 pipette on left mount, transfer 195.0 uL of reagent
20+
from H10, F12, D7, B1, C8 wells in source labware
21+
to first well in the destination labware.
22+
Use new tip for each transfer.
23+
`
24+
25+
export const flexReagentTransfer = `
26+
Write a protocol for the Opentrons Flex as described below:
27+
28+
Metadata and requirements:
29+
- Application: Reagent transfer
30+
- Robot: Flex
31+
- API: 2.15
32+
33+
Pipette Mount:
34+
- Flex 1-Channel 1000 µL Pipette is mounted on the left side
35+
- Flex 1-Channel 50 µL Pipette is mounted on the right side
36+
37+
Labware:
38+
- Source Labware 1: NEST 1 Well Reservoir 195 mL is positioned on slot B1
39+
- Source Labware 2: Bio-Rad 384 Well Plate 50 µL is positioned on slot B2
40+
- Source Labware 3: Bio-Rad 96 Well Plate 200 µL is positioned on slot B3
41+
- Destination Labware 1: Corning 384 Well Plate 112 µL Flat is positioned on slot D1
42+
- Destination Labware 2: Corning 96 Well Plate 360 µL Flat is positioned on slot D2
43+
- Tiprack 1: Opentrons Flex 96 Filter Tip Rack 200 µL is used on slot A1
44+
- Tiprack 2: Opentrons Flex 96 Filter Tip Rack 50 µL is used on slot A2
45+
46+
Commands
47+
- Using Flex 1-Channel 50 µL Pipette on right mount, transfer 15 µL from first of source labware 1 to each well
48+
in destination labware 1 and destination labware 2. Reuse the same tip.
49+
- Again using Flex 1-Channel 50 µL Pipette, transfer 20 µL from each well in source labware 2 to
50+
each well in the destination labware 1. Reuse the same tip.
51+
- Using Flex 1-Channel 1000 µL Pipette on left mount, transfer 100µL liquid from each well in source labware 3
52+
to each well in destination labware 2. Use a new tip each time.
53+
`
54+
55+
export const pcr = `
56+
Write a protocol for the Opentrons OT-2 as described below:
57+
58+
Metadata:
59+
- Application: ThermoPrime Taq DNA Polymerase, with 10x buffer and separate vial of 25 mM MgCl2Thermo Scientific kit PCR amplification
60+
- Robot: OT-2
61+
- API: 2.15
62+
63+
Pipette mount:
64+
- P20 Single Channel is mounted on the right side
65+
66+
Modules:
67+
- Thermocycler module is present on slot 7
68+
- Temperature module is place on slot 3
69+
70+
Labware:
71+
- Source sample labware is Opentrons 96 Well Aluminum Block with NEST Well Plate 100 µL plate placed on slot 1
72+
- Source mastermix labware is Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap, placed on temperature module on slot 3
73+
- Destination Labware is an Opentrons Tough 96 Well Plate 200 µL PCR Full Skirt placed on thermocycler module on slot 7
74+
- 20 ul Filter tiprack is used on slot 4
75+
76+
Well allocation:
77+
- source wells are first 41 wells column wise in both master mix and sample source plates
78+
- destination wells: first 41 wells column wise on thermocycler
79+
80+
Commands:
81+
Note that every step is a single entity. Do not combine. Also, every step should be performed in order.
82+
1. The total number of samples is 41
83+
2. Set the thermocycler such that:
84+
- block temperature is 6 degree C
85+
- lid temperature to 90 degree C
86+
- lid open
87+
3. Set the master mix temperature module at 10 C. The temperature module wait time is 50 seconds.
88+
4. Transfer 10 uL of mastermix from source well to destination well. Use the same pipette tip for all transfers.
89+
5. Transfer 3 ul of sample to destination well reusing tip everytime. After dispensing, mix the sample and mastermix
90+
of 13 ul total volume 4 times and then perform blowout before dropping tip.
91+
6. Close the lid of the thermocycler.
92+
7. Set the thermocycle to following parameters (**note that each step is independent**):
93+
Step 1: 66 degree C for 47 seconds for 1 cycles
94+
Step 2: 88 degree C for 28 seconds, 82 degree C for 14 seconds, 68 degree C for 68 seconds for 15 cycles
95+
Step 3: 70 degree C for 240 seconds for 1 cycles
96+
Then, execute thermocycler profile for each step.
97+
8. After the above three steps are completed, hold thermocycler block at 4 C
98+
9. Open thermocycler lid
99+
10. Deactivate the temperature modules
100+
`
101+
102+
export const flexPcr = `
103+
Write a protocol for the Opentrons Flex as described below:
104+
105+
Metadata and requirements:
106+
- Application: GeneAmp2x PCR amplification
107+
- Robot: Flex
108+
- API: 2.15
109+
110+
Pipette mount:
111+
- Flex 1-Channel 50 µL Pipette is mounted on the right side
112+
113+
Modules and adapters:
114+
- Thermocycler GEN 2 module is present on slot A1+B1
115+
- Temperature module GEN 2 is place on slot D3
116+
117+
Labware:
118+
- Source sample labware is Opentrons 96 Well Aluminum Block with NEST Well Plate 100 µL plate placed on slot D1
119+
- Source mastermix labware is Opentrons 24 Well Aluminum Block with NEST 1.5 mL Snapcap, placed on temperature module on slot D3
120+
- Destination Labware is an Opentrons Tough 96 Well Plate 200 µL PCR Full Skirt placed on thermocycler GEN 2 module
121+
- Opentrons Flex 96 Filter Tip Rack 50 µL is used on slot C1
122+
123+
Sample position:
124+
- source wells are first 64 wells column wise in both master mix and sample source plates
125+
- destination wells: first 64 wells column wise on thermocycler
126+
127+
Commands:
128+
Note that every step is a single entity. Do not combine. Also, every step should be performed in order.
129+
1. The total number of samples is 64
130+
2. Set the thermocycler such that
131+
- block temperature is 6 degree C
132+
- lid temperature to 90 degree C
133+
- lid open
134+
3. Set the master mix temperature module at 10 C. The temperature module wait time is 50 seconds.
135+
4. Transfer 10 uL of mastermix from source well to destination well. Use the same pipette tip for all transfers.
136+
5. Transfer 3 ul of sample to destination well reusing tip everytime. After dispensing, mix the sample and mastermix
137+
of 13 ul total volume 4 times and then perform blowout before dropping tip.
138+
6. Close the lid of the thermocycler.
139+
7. Set the thermocycle to following parameters (**note that each step is independent**):
140+
Step 1: 66 degree C for 47 seconds for 1 cycles
141+
Step 2: 88 degree C for 28 seconds, 82 degree C for 14 seconds, 68 degree C for 68 seconds for 15 cycles
142+
Step 3: 70 degree C for 240 seconds for 1 cycles
143+
Then, execute thermocycler profile for each step.
144+
8. After the above three steps are completed, hold thermocycler block at 4 C
145+
9. Open thermocycler lid
146+
10. Deactivate the temperature modules
147+
`

opentrons-ai-client/src/main.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react'
22
import ReactDOM from 'react-dom/client'
33
import { I18nextProvider } from 'react-i18next'
44
import { GlobalStyle } from './atoms/GlobalStyle'
5+
import { PromptProvider } from './organisms/PromptButton/PromptProvider'
56

67
import { i18n } from './i18n'
78
import { App } from './App'
@@ -12,7 +13,9 @@ if (rootElement != null) {
1213
<React.StrictMode>
1314
<GlobalStyle />
1415
<I18nextProvider i18n={i18n}>
15-
<App />
16+
<PromptProvider>
17+
<App />
18+
</PromptProvider>
1619
</I18nextProvider>
1720
</React.StrictMode>
1821
)

opentrons-ai-client/src/molecules/InputPrompt/index.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
SPACING,
1717
TYPOGRAPHY,
1818
} from '@opentrons/components'
19-
19+
import { promptContext } from '../../organisms/PromptButton/PromptProvider'
2020
import type { SubmitHandler } from 'react-hook-form'
2121

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

2929
export function InputPrompt(/* props: InputPromptProps */): JSX.Element {
3030
const { t } = useTranslation('protocol_generator')
31-
const { register, handleSubmit, watch } = useForm<InputType>({
31+
const { register, handleSubmit, watch, setValue } = useForm<InputType>({
3232
defaultValues: {
3333
userPrompt: '',
3434
},
3535
})
36+
const usePromptValue = (): string => React.useContext(promptContext)
37+
const promptFromButton = usePromptValue()
3638
const userPrompt = watch('userPrompt') ?? ''
3739

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

46+
const calcTextAreaHeight = (): number => {
47+
const rowsNum = userPrompt.split('\n').length
48+
return rowsNum
49+
}
50+
51+
React.useEffect(() => {
52+
if (promptFromButton !== '') setValue('userPrompt', promptFromButton)
53+
}, [promptFromButton, setValue])
54+
4455
return (
4556
<StyledForm id="User_Prompt" onSubmit={() => handleSubmit(onSubmit)}>
4657
<Flex
@@ -51,9 +62,10 @@ export function InputPrompt(/* props: InputPromptProps */): JSX.Element {
5162
borderRadius={BORDERS.borderRadius4}
5263
justifyContent={JUSTIFY_CENTER}
5364
alignItems={ALIGN_CENTER}
65+
maxHeight="21.25rem"
5466
>
5567
<StyledTextarea
56-
rows={1}
68+
rows={calcTextAreaHeight()}
5769
placeholder={t('type_your_prompt')}
5870
{...register('userPrompt')}
5971
/>
@@ -70,6 +82,8 @@ const StyledForm = styled.form`
7082
const StyledTextarea = styled.textarea`
7183
resize: none;
7284
min-height: 3.75rem;
85+
max-height: 17.25rem;
86+
overflow-y: auto;
7387
background-color: ${COLORS.white};
7488
border: none;
7589
outline: none;

opentrons-ai-client/src/molecules/PromptGuide/__tests__/PromptGuide.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ describe('PromptGuide', () => {
3737
'What if you don’t provide all of those pieces of information?'
3838
)
3939
screen.getByText('OpentronsAI asks you to provide it!')
40+
screen.getByText(
41+
'Once OpentronsAI has written your protocol, type "simulate" in the prompt box to try it out.'
42+
)
4043
})
4144
it('should have the right url', () => {
4245
render()

opentrons-ai-client/src/molecules/PromptGuide/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ export function PromptGuide(): JSX.Element {
8686
span: <StyledText css={BODY_TEXT_STYLE} />,
8787
}}
8888
/>
89+
<StyledText css={BODY_TEXT_STYLE}>
90+
{t('simulator_description')}
91+
</StyledText>
8992
</Flex>
9093
)
9194
}

opentrons-ai-client/src/molecules/SidePanel/index.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@ import React from 'react'
22
import styled, { css } from 'styled-components'
33
import { useTranslation } from 'react-i18next'
44
import {
5-
BORDERS,
65
COLORS,
76
DIRECTION_COLUMN,
87
Flex,
98
Link,
10-
PrimaryButton,
119
SPACING,
1210
StyledText,
1311
TYPOGRAPHY,
1412
WRAP,
1513
} from '@opentrons/components'
14+
import { PromptButton } from '../../organisms/PromptButton'
1615
import LOGO_PATH from '../../assets/images/opentrons_logo.svg'
1716

1817
const IMAGE_ALT = 'Opentrons logo'
@@ -47,11 +46,10 @@ export function SidePanel(): JSX.Element {
4746
</StyledText>
4847

4948
<Flex gridGap={SPACING.spacing16} flexWrap={WRAP}>
50-
{/* ToDo(kk:04/11/2024) add a button component */}
51-
<PromptButton>{t('reagent_transfer')}</PromptButton>
52-
<PromptButton>{t('reagent_transfer_flex')}</PromptButton>
53-
<PromptButton>{t('prc')}</PromptButton>
54-
<PromptButton>{t('prc_flex')}</PromptButton>
49+
<PromptButton buttonText={t('reagent_transfer')} />
50+
<PromptButton buttonText={t('reagent_transfer_flex')} />
51+
<PromptButton buttonText={t('pcr')} />
52+
<PromptButton buttonText={t('pcr_flex')} />
5553
</Flex>
5654
</Flex>
5755
<Flex flexDirection={DIRECTION_COLUMN}>
@@ -89,11 +87,6 @@ const BUTTON_GUIDE_TEXT_STYLE = css`
8987
color: ${COLORS.white};
9088
`
9189

92-
const PromptButton = styled(PrimaryButton)`
93-
border-radius: ${BORDERS.borderRadiusFull};
94-
white-space: nowrap;
95-
`
96-
9790
const FeedbackLink = styled(Link)`
9891
font-size: ${TYPOGRAPHY.fontSize20};
9992
line-height: ${TYPOGRAPHY.lineHeight24};

0 commit comments

Comments
 (0)