Skip to content

Commit

Permalink
feat(opentrons-ai-client): Integrated create protocol API into the cr…
Browse files Browse the repository at this point in the history
…eate new protocol flow (#16786)

<!--
Thanks for taking the time to open a Pull Request (PR)! Please make sure
you've read the "Opening Pull Requests" section of our Contributing
Guide:


https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md#opening-pull-requests

GitHub provides robust markdown to format your PR. Links, diagrams,
pictures, and videos along with text formatting make it possible to
create a rich and informative PR. For more information on GitHub
markdown, see:


https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax

To ensure your code is reviewed quickly and thoroughly, please fill out
the sections below to the best of your ability!
-->

# Overview

Added in the functionality of successfully calling the new
createProtocol API endpoint.

Passes in all the information to the AI model and successfully received
a response from the LLM.

<img width="951" alt="image"
src="https://github.com/user-attachments/assets/d1f8b21e-a431-4c0f-8b3b-6e7ecc9dafcd">

<!--
Describe your PR at a high level. State acceptance criteria and how this
PR fits into other work. Link issues, PRs, and other relevant resources.
-->

## Test Plan and Hands on Testing

Tested the happy path scenario by running thru the create protocol page
a few times with different options. Checked to see if the server
received all the information required and was able to process the
requests.

<!--
Describe your testing of the PR. Emphasize testing not reflected in the
code. Attach protocols, logs, screenshots and any other assets that
support your testing.
-->

## Changelog

<!--
List changes introduced by this PR considering future developers and the
end user. Give careful thought and clear documentation to breaking
changes.
-->

## Review requests

<!--
- What do you need from reviewers to feel confident this PR is ready to
merge?
- Ask questions.
-->

## Risk assessment

<!--
- Indicate the level of attention this PR needs.
- Provide context to guide reviewers.
- Discuss trade-offs, coupling, and side effects.
- Look for the possibility, even if you think it's small, that your
change may affect some other part of the system.
- For instance, changing return tip behavior may also change the
behavior of labware calibration.
- How do your unit tests and on hands on testing mitigate this PR's
risks and the risk of future regressions?
- Especially in high risk PRs, explain how you know your testing is
enough.
-->

---------

Co-authored-by: Felipe Belgine <[email protected]>
  • Loading branch information
connected-znaim and fbelginetw authored Nov 13, 2024
1 parent cec0a10 commit 5421cd6
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 122 deletions.
95 changes: 51 additions & 44 deletions opentrons-ai-client/src/molecules/InputPrompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { SendButton } from '../../atoms/SendButton'
import {
chatDataAtom,
chatHistoryAtom,
chatPromptAtom,
createProtocolChatAtom,
tokenAtom,
updatePromptAtom,
updateProtocolChatAtom,
} from '../../resources/atoms'
import { useApiCall } from '../../resources/hooks'
import { calcTextAreaHeight } from '../../resources/utils'
Expand All @@ -43,23 +43,45 @@ import type { ChatData } from '../../resources/types'
export function InputPrompt(): JSX.Element {
const { t } = useTranslation('protocol_generator')
const { register, watch, reset, setValue } = useFormContext()
const [chatPromptAtomValue] = useAtom(chatPromptAtom)
const [updatePrompt] = useAtom(updatePromptAtom)

const [updateProtocol] = useAtom(updateProtocolChatAtom)
const [createProtocol] = useAtom(createProtocolChatAtom)
const isNewProtocol = createProtocol.prompt !== ''
const [sendAutoFilledPrompt, setSendAutoFilledPrompt] = useState<boolean>(
false
)

const [, setChatData] = useAtom(chatDataAtom)
const [chatHistory, setChatHistory] = useAtom(chatHistoryAtom)
const [token] = useAtom(tokenAtom)
const [submitted, setSubmitted] = useState<boolean>(false)
const watchUserPrompt = watch('userPrompt') ?? ''
const [sendAutoFilledPrompt, setSendAutoFilledPrompt] = useState<boolean>(
false
)

const { data, isLoading, callApi } = useApiCall()
const [requestId, setRequestId] = useState<string>(uuidv4())

// This is to autofill the input field for when we navigate to the chat page from the existing/new protocol generator pages
useEffect(() => {
const prefilledPrompt = isNewProtocol
? createProtocol.prompt
: updateProtocol.prompt
if (prefilledPrompt !== '') {
setValue('userPrompt', prefilledPrompt)
setSendAutoFilledPrompt(true)
}
}, [])

useEffect(() => {
if (sendAutoFilledPrompt) {
handleClick(true)
setSendAutoFilledPrompt(false)
}
}, [watchUserPrompt])

const handleClick = async (
isUpdateOrCreate: boolean = false
isUpdateOrCreateRequest: boolean = false
): Promise<void> => {
setRequestId(uuidv4() + getPreFixText(isUpdateOrCreate))
setRequestId(uuidv4() + getPreFixText(isUpdateOrCreateRequest))

const userInput: ChatData = {
requestId,
Expand All @@ -75,15 +97,15 @@ export function InputPrompt(): JSX.Element {
'Content-Type': 'application/json',
}

const url = isUpdateOrCreate
? getCreateOrUpdateEndpoint(chatPromptAtomValue.isNewProtocol)
const url = isUpdateOrCreateRequest
? getCreateOrUpdateEndpoint()
: getChatEndpoint()

const config = {
url,
method: 'POST',
headers,
data: isUpdateOrCreate
data: isUpdateOrCreateRequest
? getUpdateOrCreatePrompt()
: {
message: watchUserPrompt,
Expand All @@ -105,7 +127,23 @@ export function InputPrompt(): JSX.Element {
}

const getUpdateOrCreatePrompt = (): any => {
return chatPromptAtomValue.isNewProtocol ? updatePrompt : updatePrompt // to do: add the create prompt
return isNewProtocol ? createProtocol : updateProtocol
}

const getPreFixText = (isUpdateOrCreate: boolean): string => {
let appendCreateOrUpdate = ''
if (isUpdateOrCreate) {
if (isNewProtocol) {
appendCreateOrUpdate = 'NewProtocol'
} else {
appendCreateOrUpdate = 'UpdateProtocol'
}
}
return appendCreateOrUpdate
}

const getCreateOrUpdateEndpoint = (): string => {
return isNewProtocol ? getCreateEndpoint() : getUpdateEndpoint()
}

useEffect(() => {
Expand All @@ -125,21 +163,6 @@ export function InputPrompt(): JSX.Element {
}
}, [data, isLoading, submitted])

// This is to autofill the input field for when we navigate to the chat page from the existing/new protocol generator pages
useEffect(() => {
if (chatPromptAtomValue.prompt !== '') {
setValue('userPrompt', chatPromptAtomValue.prompt)
setSendAutoFilledPrompt(true)
}
}, [])

useEffect(() => {
if (sendAutoFilledPrompt) {
handleClick(true)
setSendAutoFilledPrompt(false)
}
}, [watchUserPrompt])

return (
<StyledForm id="User_Prompt">
<Flex css={CONTAINER_STYLE}>
Expand All @@ -158,22 +181,6 @@ export function InputPrompt(): JSX.Element {
</Flex>
</StyledForm>
)

function getPreFixText(isUpdateOrCreate: boolean): string {
let appendCreateOrUpdate = ''
if (isUpdateOrCreate) {
if (chatPromptAtomValue.isNewProtocol) {
appendCreateOrUpdate = 'NewProtocol'
} else {
appendCreateOrUpdate = 'UpdateProtocol'
}
}
return appendCreateOrUpdate
}
}

const getCreateOrUpdateEndpoint = (isCreateNewProtocol: boolean): string => {
return isCreateNewProtocol ? getCreateEndpoint() : getUpdateEndpoint()
}

const getChatEndpoint = (): string => {
Expand Down
40 changes: 34 additions & 6 deletions opentrons-ai-client/src/organisms/UpdateProtocol/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@ import { Trans, useTranslation } from 'react-i18next'
import { FileUpload } from '../../molecules/FileUpload'
import { useNavigate } from 'react-router-dom'
import {
chatPromptAtom,
createProtocolChatAtom,
headerWithMeterAtom,
updatePromptAtom,
updateProtocolChatAtom,
} from '../../resources/atoms'
import { CSSTransition } from 'react-transition-group'
import { useAtom } from 'jotai'
import { useTrackEvent } from '../../resources/hooks/useTrackEvent'

interface UpdateOptionsDropdown extends DropdownOption {
value: UpdateOptions
Expand Down Expand Up @@ -97,18 +98,37 @@ const isValidProtocolFileName = (protocolFileName: string): boolean => {

export function UpdateProtocol(): JSX.Element {
const navigate = useNavigate()
const trackEvent = useTrackEvent()
const { t }: { t: (key: string) => string } = useTranslation(
'protocol_generator'
)
const [, setChatPrompt] = useAtom(chatPromptAtom)
const [headerState, setHeaderWithMeterAtom] = useAtom(headerWithMeterAtom)
const [updateType, setUpdateType] = useState<DropdownOption | null>(null)
const [detailsValue, setDetailsValue] = useState<string>('')
const [, setUpdatePromptAtom] = useAtom(updatePromptAtom)
const [, setUpdatePromptAtom] = useAtom(updateProtocolChatAtom)
const [, setCreateProtocolChatAtom] = useAtom(createProtocolChatAtom)
const [fileValue, setFile] = useState<File | null>(null)
const [pythonText, setPythonTextValue] = useState<string>('')
const [errorText, setErrorText] = useState<string | null>(null)

// Reset the create protocol chat atom when navigating to the update protocol page
useEffect(() => {
setCreateProtocolChatAtom({
prompt: '',
scientific_application_type: '',
description: '',
robots: 'opentrons_flex',
mounts: [],
flexGripper: false,
modules: [],
labware: [],
liquids: [],
steps: [],
fake: false,
fake_id: 0,
})
}, [])

useEffect(() => {
let progress = 0.0
if (updateType !== null) {
Expand Down Expand Up @@ -177,14 +197,22 @@ export function UpdateProtocol(): JSX.Element {
console.log(chatPrompt)

setUpdatePromptAtom({
prompt: chatPrompt,
protocol_text: pythonText,
regenerate: false,
update_type: (updateType?.value ?? 'other') as UpdateOptions,
update_details: detailsValue,
fake: true,
fake: false,
fake_id: 0,
})
setChatPrompt({ prompt: chatPrompt, isNewProtocol: false })

trackEvent({
name: 'submit-prompt',
properties: {
prompt: chatPrompt,
},
})

navigate('/chat')
}

Expand Down
30 changes: 22 additions & 8 deletions opentrons-ai-client/src/pages/CreateProtocol/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import { useEffect } from 'react'
import { PromptPreview } from '../../molecules/PromptPreview'
import { useForm, FormProvider } from 'react-hook-form'
import {
chatPromptAtom,
createProtocolAtom,
createProtocolChatAtom,
headerWithMeterAtom,
updateProtocolChatAtom,
} from '../../resources/atoms'
import { useAtom } from 'jotai'
import { ProtocolSectionsContainer } from '../../organisms/ProtocolSectionsContainer'
Expand Down Expand Up @@ -49,7 +50,8 @@ export function CreateProtocol(): JSX.Element | null {
const { t } = useTranslation('create_protocol')
const [, setHeaderWithMeterAtom] = useAtom(headerWithMeterAtom)
const [{ currentStep }, setCreateProtocolAtom] = useAtom(createProtocolAtom)
const [, setChatPrompt] = useAtom(chatPromptAtom)
const [, setCreateProtocolChatAtom] = useAtom(createProtocolChatAtom)
const [, setUpdateProtocolChatAtom] = useAtom(updateProtocolChatAtom)
const navigate = useNavigate()
const trackEvent = useTrackEvent()

Expand All @@ -72,6 +74,19 @@ export function CreateProtocol(): JSX.Element | null {
return currentStep > 0 ? currentStep / TOTAL_STEPS : 0
}

// Reset the update protocol chat atom when navigating to the create protocol page
useEffect(() => {
setUpdateProtocolChatAtom({
prompt: '',
protocol_text: '',
regenerate: false,
update_type: 'adapt_python_protocol',
update_details: '',
fake: false,
fake_id: 0,
})
}, [])

useEffect(() => {
setHeaderWithMeterAtom({
displayHeaderWithMeter: true,
Expand Down Expand Up @@ -107,12 +122,11 @@ export function CreateProtocol(): JSX.Element | null {
<ProtocolSectionsContainer />
<PromptPreview
handleSubmit={() => {
const chatPromptData = generateChatPrompt(methods.getValues(), t)

setChatPrompt({
prompt: chatPromptData,
isNewProtocol: true,
})
const chatPromptData = generateChatPrompt(
methods.getValues(),
t,
setCreateProtocolChatAtom
)

trackEvent({
name: 'submit-prompt',
Expand Down
22 changes: 17 additions & 5 deletions opentrons-ai-client/src/resources/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { atom } from 'jotai'
import type {
Chat,
ChatData,
ChatPromptAtomProps,
CreatePrompt,
createProtocolAtomProps,
HeaderWithMeterAtomProps,
Mixpanel,
Expand All @@ -13,13 +13,25 @@ import type {
/** ChatDataAtom is for chat data (user prompt and response from OpenAI API) */
export const chatDataAtom = atom<ChatData[]>([])

/** ChatPromptAtom is for the prefilled userprompt when navigating to the chat page from existing/new protocol pages */
export const chatPromptAtom = atom<ChatPromptAtomProps>({
/** CreateProtocolChatAtom is for the prefilled userprompt when navigating to the chat page from Create New protocol page */
export const createProtocolChatAtom = atom<CreatePrompt>({
prompt: '',
isNewProtocol: true,
scientific_application_type: '',
description: '',
robots: 'opentrons_flex',
mounts: [],
flexGripper: false,
modules: [],
labware: [],
liquids: [],
steps: [],
fake: false,
fake_id: 0,
})

export const updatePromptAtom = atom<UpdatePrompt>({
/** CreateProtocolChatAtom is for the prefilled userprompt when navigating to the chat page from Update Protocol page */
export const updateProtocolChatAtom = atom<UpdatePrompt>({
prompt: '',
protocol_text: '',
regenerate: false,
update_type: 'adapt_python_protocol',
Expand Down
18 changes: 14 additions & 4 deletions opentrons-ai-client/src/resources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,20 @@ export interface ChatData {
requestId: string
}

export interface ChatPromptAtomProps {
/** the prompt that is generated by the update or create new protocol pages */
export interface CreatePrompt {
/** the prompt that is generated by the create protocol page */
prompt: string
/** boolean to determine if the prompt is a new protocol or an update */
isNewProtocol: boolean
scientific_application_type: string
description: string
robots: 'opentrons_flex' | 'opentrons_ot2' | string
mounts: string[]
flexGripper: boolean
modules: string[]
labware: string[]
liquids: string[]
steps: string[]
fake?: boolean
fake_id?: number
}

export type UpdateOptions =
Expand All @@ -27,6 +36,7 @@ export type UpdateOptions =

export interface UpdatePrompt {
/** the prompt that is generated by the update protocol page */
prompt: string
protocol_text: string
regenerate: boolean
update_type: UpdateOptions
Expand Down
Loading

0 comments on commit 5421cd6

Please sign in to comment.