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): Created an update protocol page #16569

Merged
merged 59 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c7123ad
feat: initial commit add landing page and replace App
fbelginetw Oct 17, 2024
e6071a8
refactor: cleanup code and add basic tests
fbelginetw Oct 18, 2024
8d5fd11
feat: add useIsMobile hook
fbelginetw Oct 18, 2024
64d1996
feat: add tests and button onClicks
fbelginetw Oct 18, 2024
5b2022d
feat: add mixpanel analytics
fbelginetw Oct 18, 2024
567b39f
Merge branch 'edge' into opentrons-ai-client-landing-page
fbelginetw Oct 21, 2024
2514383
refactor: add and fix tests
fbelginetw Oct 21, 2024
fde8b2c
Merge branch 'edge' into opentrons-ai-client-landing-page
fbelginetw Oct 21, 2024
ddb7ebc
Merge branch 'edge' into opentrons-ai-client-landing-page
fbelginetw Oct 21, 2024
77ab903
feat: add create protocol page
fbelginetw Oct 22, 2024
4173ea2
refactor: separate translation file and adjust layout
fbelginetw Oct 22, 2024
002550e
refactor: create controlled dropdown and input field
fbelginetw Oct 22, 2024
ee9c997
Moved a copy of uploadinput into the ai-client and added UpdateProtoc…
connected-znaim Oct 22, 2024
9a3a48a
added upload input styling for text
connected-znaim Oct 22, 2024
54a6eca
fixed import issue
connected-znaim Oct 22, 2024
16cd6af
refactor: simplify accordion to avoid issues with dropdowns
fbelginetw Oct 22, 2024
6295513
fixed build
connected-znaim Oct 22, 2024
73ec6c2
feat: add application section, update translation files
fbelginetw Oct 22, 2024
4562b25
refactor: add new section and adjust layout
fbelginetw Oct 22, 2024
94cea0f
Merge branch 'edge' into opentrons-ai-client-landing-page
fbelginetw Oct 22, 2024
7ed7f2b
refactor: add header and adjust for wide screens
fbelginetw Oct 22, 2024
f126f11
added incomplete file upload functionality
connected-znaim Oct 23, 2024
b4bc268
Merge branch 'edge' into opentrons-ai-client-landing-page
fbelginetw Oct 23, 2024
76bd334
refactor: add Footer, update analytics capture for logout, and add tests
fbelginetw Oct 23, 2024
a010d83
refactor: mock useTrackEvent to stop warnings
fbelginetw Oct 23, 2024
760e940
refactor: alphabetical order
fbelginetw Oct 23, 2024
fe51246
Merge branch 'opentrons-ai-client-landing-page' into opentrons-ai-cli…
fbelginetw Oct 23, 2024
2016a5b
feat: add atom to control header with meter
fbelginetw Oct 23, 2024
4031568
Merge branch 'edge' into opentrons-ai-client-application
fbelginetw Oct 23, 2024
db352a4
fix: pr comments from lading page
fbelginetw Oct 23, 2024
a660cd9
feat: add isInitialized to mixpanel to avoid calling it twice
fbelginetw Oct 23, 2024
17fc268
fixed the dropdown menu and added error states to file upload
connected-znaim Oct 24, 2024
5d1ff6c
Merge branch 'edge' into ai_client_update_protocol
connected-znaim Oct 24, 2024
8bc408a
feat: add atom to control current section
fbelginetw Oct 24, 2024
20e8814
added route to updateprotocol and fixed some design issues
connected-znaim Oct 24, 2024
8eb5b9f
fix: remove unused imports
fbelginetw Oct 24, 2024
9b85412
feat: add logic for focus/current steps, progress bar and isCompleted…
fbelginetw Oct 24, 2024
493125e
refactor: protocol section container and create protocol tests
fbelginetw Oct 24, 2024
e4e0eb6
refactor: add more section tests
fbelginetw Oct 24, 2024
2bdde5e
refactor: sticky header
fbelginetw Oct 24, 2024
5d0df2b
refactor: remame createProtocolTestUtils
fbelginetw Oct 24, 2024
d40330a
added chat page and passed in the result of the existing protocol flow
connected-znaim Oct 24, 2024
e406b00
fixed chat screen
connected-znaim Oct 25, 2024
1736018
refactor: update renderWithProviders to use jotai
fbelginetw Oct 25, 2024
73faf1c
refactor: remove todo
fbelginetw Oct 25, 2024
a512b8a
test file for existing protocol
connected-znaim Oct 25, 2024
66632df
update protocol fixes for test
connected-znaim Oct 28, 2024
33e5300
Merge branch 'edge' into ai_client_update_protocol
connected-znaim Oct 28, 2024
96b5fc3
Merge remote-tracking branch 'origin/opentrons-ai-client-application'…
connected-znaim Oct 28, 2024
cc49be8
switched to apps progress header
connected-znaim Oct 28, 2024
73a89f9
added a csstransition to fade the components in and out
connected-znaim Oct 28, 2024
f7e5912
added a new atom to pass in the generated prompt from the existing an…
connected-znaim Oct 28, 2024
d372bc3
removed some spacing and text
connected-znaim Oct 28, 2024
2082bcd
Merge branch 'edge' into ai_client_update_protocol
connected-znaim Oct 28, 2024
63acf35
fixed unused imports
connected-znaim Oct 28, 2024
7f1de84
fixed broken test
connected-znaim Oct 28, 2024
7c86e8d
fixed bug that showed progress bar when navigating back to the landin…
connected-znaim Oct 28, 2024
96a57c5
moved the prompt code to translation files
connected-znaim Nov 5, 2024
06d09b6
Merge branch 'edge' into ai_client_update_protocol
connected-znaim Nov 6, 2024
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
10 changes: 9 additions & 1 deletion opentrons-ai-client/src/OpentronsAIRoutes.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import { Route, Navigate, Routes } from 'react-router-dom'
import { Landing } from './pages/Landing'
import { UpdateProtocol } from './organisms/UpdateProtocol'

import type { RouteProps } from './resources/types'
import { Chat } from './pages/Chat'
import { CreateProtocol } from './pages/CreateProtocol'

const opentronsAIRoutes: RouteProps[] = [
{
Component: Chat,
name: 'Chat',
navLinkTo: '/chat',
path: '/chat',
},
{
Component: CreateProtocol,
name: 'Create A New Protocol',
navLinkTo: '/new-protocol',
path: '/new-protocol',
},
{
Component: Landing,
Component: UpdateProtocol,
name: 'Update An Existing Protocol',
navLinkTo: '/update-protocol',
path: '/update-protocol',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
"commands": "Commands: List the protocol's steps, specifying quantities in microliters (uL) and giving exact source and destination locations.",
"copyright": "Copyright © 2024 Opentrons",
"copy_code": "Copy code",
"choose_file": "Choose file",
"disclaimer": "OpentronsAI can make mistakes. Review your protocol before running it on an Opentrons robot.",
"drag_and_drop": "Drag and drop or <a>browse</a> your files",
"example": "For example prompts, click the buttons in the left panel.",
"file_length_error": "The length of the file contents is 0. Please upload a file with content.",
"exit": "Exit",
"exit_confirmation_title": "Are you sure you want to exit?",
"exit_confirmation_body": "Exiting now will discard your progress.",
Expand All @@ -26,6 +29,10 @@
"login": "Login",
"logout": "Logout",
"make_sure_your_prompt": "Write a prompt in a natural language for OpentronsAI to generate a protocol using the Opentrons Python Protocol API v2. The better the prompt, the better the quality of the protocol produced by OpentronsAI.",
"modify_intro": "Modify the following Python code using the Opentrons Python Protocol API v2. Ensure that the new labware and pipettes are compatible with the Flex robot. Ensure that you perform the correct Type of Update use the Details of Changes.\n\n",
"modify_python_code": "Original Python Code:\n",
"modify_type_of_update": "Type of update:\n- ",
"modify_details_of_change": "Details of Changes:\n- ",
"modules_and_adapters": "Modules and adapters: Specify the modules and labware adapters required by your protocol.",
"notes": "A few important things to note:",
"opentrons": "Opentrons",
Expand All @@ -35,6 +42,9 @@
"pcr": "PCR",
"pipettes": "Pipettes: Specify your pipettes, including the volume, number of channels, and whether they’re mounted on the left or right.",
"privacy_policy": "By continuing, you agree to the Opentrons <privacyPolicyLink>Privacy Policy</privacyPolicyLink> and <EULALink>End user license agreement</EULALink>",
"protocol_file": "Protocol file",
"provide_details_of_changes": "Provide details of changes you want to make",
"python_file_type_error": "Python file type required",
"reagent_transfer_flex": "Reagent Transfer (Flex)",
"reagent_transfer": "Reagent Transfer",
"reload_page": "To start over and create a new protocol, simply reload the page.",
Expand All @@ -44,8 +54,11 @@
"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",
"simulate_description": "Once OpentronsAI has written your protocol, type `simulate` in the prompt box to try it out.",
"submit_prompt": "Submit prompt",
"try_example_prompts": "Stuck? Try these example prompts to get started.",
"type_of_update": "Type of update",
"type_your_prompt": "Type your prompt...",
"update_existing_protocol": "Update an existing protocol",
"well_allocations": "Well allocations: Describe where liquids should go in labware.",
"what_if_you": "<span>What if you don’t provide all of those pieces of information? <bold>OpentronsAI asks you to provide it!</bold></span>",
"what_typeof_protocol": "What type of protocol do you need?",
Expand Down
2 changes: 2 additions & 0 deletions opentrons-ai-client/src/molecules/ChatDisplay/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
SPACING,
LegacyStyledText,
TYPOGRAPHY,
OVERFLOW_AUTO,
} from '@opentrons/components'

import type { ChatData } from '../../resources/types'
Expand Down Expand Up @@ -63,6 +64,7 @@ export function ChatDisplay({ chat, chatId }: ChatDisplayProps): JSX.Element {
data-testid={`ChatDisplay_from_${isUser ? 'user' : 'backend'}`}
borderRadius={BORDERS.borderRadius12}
width="100%"
overflowY={OVERFLOW_AUTO}
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing16}
position={POSITION_RELATIVE}
Expand Down
74 changes: 74 additions & 0 deletions opentrons-ai-client/src/molecules/FileUpload/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { css } from 'styled-components'

import {
ALIGN_CENTER,
BORDERS,
Btn,
COLORS,
DIRECTION_COLUMN,
Flex,
Icon,
JUSTIFY_SPACE_BETWEEN,
SPACING,
LegacyStyledText,
truncateString,
} from '@opentrons/components'

const FILE_UPLOAD_STYLE = css`

&:hover > svg {
background: ${COLORS.black90}${COLORS.opacity20HexCode};
}
&:active > svg {
background: ${COLORS.black90}${COLORS.opacity20HexCode}};
}
`

const FILE_UPLOAD_FOCUS_VISIBLE = css`
&:focus-visible {
border-radius: ${BORDERS.borderRadius4};
box-shadow: 0 0 0 ${SPACING.spacing2} ${COLORS.blue50};
}
`

interface FileUploadProps {
file: File
fileError: string | null
handleClick: () => unknown
}

export function FileUpload({
file,
fileError,
handleClick,
}: FileUploadProps): JSX.Element {
return (
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
<Btn
onClick={handleClick}
aria-label="remove_file"
css={FILE_UPLOAD_FOCUS_VISIBLE}
>
<Flex
alignItems={ALIGN_CENTER}
backgroundColor={fileError == null ? COLORS.grey20 : COLORS.red30}
borderRadius={BORDERS.borderRadius4}
height={SPACING.spacing44}
justifyContent={JUSTIFY_SPACE_BETWEEN}
padding={SPACING.spacing8}
css={FILE_UPLOAD_STYLE}
>
<LegacyStyledText as="p">
{truncateString(file.name, 34, 19)}
</LegacyStyledText>
<Icon name="close" size="1.5rem" borderRadius="50%" />
</Flex>
</Btn>
{fileError != null ? (
<LegacyStyledText as="label" color={COLORS.red50}>
{fileError}
</LegacyStyledText>
) : null}
</Flex>
)
}
14 changes: 12 additions & 2 deletions opentrons-ai-client/src/molecules/InputPrompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ import {
TYPOGRAPHY,
} from '@opentrons/components'
import { SendButton } from '../../atoms/SendButton'
import { chatDataAtom, chatHistoryAtom, tokenAtom } from '../../resources/atoms'
import {
chatDataAtom,
chatHistoryAtom,
chatPromptAtom,
tokenAtom,
} from '../../resources/atoms'
import { useApiCall } from '../../resources/hooks'
import { calcTextAreaHeight } from '../../resources/utils/utils'
import {
Expand All @@ -29,14 +34,19 @@ import type { ChatData } from '../../resources/types'

export function InputPrompt(): JSX.Element {
const { t } = useTranslation('protocol_generator')
const { register, watch, reset } = useFormContext()
const { register, watch, reset, setValue } = useFormContext()
const [chatPromptAtomValue] = useAtom(chatPromptAtom)
const [, setChatData] = useAtom(chatDataAtom)
const [chatHistory, setChatHistory] = useAtom(chatHistoryAtom)
const [token] = useAtom(tokenAtom)
const [submitted, setSubmitted] = useState<boolean>(false)
const userPrompt = watch('userPrompt') ?? ''
const { data, isLoading, callApi } = useApiCall()

useEffect(() => {
setValue('userPrompt', chatPromptAtomValue)
}, [chatPromptAtomValue, setValue])

const handleClick = async (): Promise<void> => {
const userInput: ChatData = {
role: 'user',
Expand Down
168 changes: 168 additions & 0 deletions opentrons-ai-client/src/molecules/UploadInput/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import * as React from 'react'
import styled, { css } from 'styled-components'
import { useTranslation } from 'react-i18next'
import {
ALIGN_CENTER,
BORDERS,
COLORS,
CURSOR_POINTER,
DIRECTION_COLUMN,
DISPLAY_FLEX,
Flex,
Icon,
JUSTIFY_CENTER,
LegacyStyledText,
POSITION_FIXED,
PrimaryButton,
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'

const StyledLabel = styled.label`
display: ${DISPLAY_FLEX};
cursor: ${CURSOR_POINTER};
flex-direction: ${DIRECTION_COLUMN};
align-items: ${ALIGN_CENTER};
width: 100%;
padding: ${SPACING.spacing32};
border: 2px dashed ${COLORS.grey30};
border-radius: ${BORDERS.borderRadius4};
text-align: ${TYPOGRAPHY.textAlignCenter};
background-color: ${COLORS.white};

&:hover {
border: 2px dashed ${COLORS.blue50};
}
`
const DRAG_OVER_STYLES = css`
border: 2px dashed ${COLORS.blue50};
`

const StyledInput = styled.input`
position: ${POSITION_FIXED};
clip: rect(1px 1px 1px 1px);
`

export interface UploadInputProps {
/** Callback function that is called when a file is uploaded. */
onUpload: (file: File) => unknown
/** Optional callback function that is called when the upload button is clicked. */
onClick?: () => void
/** Optional text for the upload button. If undefined, the button displays Upload */
uploadButtonText?: string
/** Optional text or JSX element that is displayed above the upload button. */
uploadText?: string | JSX.Element
/** Optional text or JSX element that is displayed in the drag and drop area. */
dragAndDropText?: string | JSX.Element
}

export function UploadInput(props: UploadInputProps): JSX.Element | null {
const {
dragAndDropText,
onClick,
onUpload,
uploadButtonText,
uploadText,
} = props
const { t } = useTranslation('protocol_info')

const fileInput = React.useRef<HTMLInputElement>(null)
const [isFileOverDropZone, setIsFileOverDropZone] = React.useState<boolean>(
false
)
const [isHover, setIsHover] = React.useState<boolean>(false)
const handleDrop: React.DragEventHandler<HTMLLabelElement> = e => {
e.preventDefault()
e.stopPropagation()
Array.from(e.dataTransfer.files).forEach(f => onUpload(f))
setIsFileOverDropZone(false)
}
const handleDragEnter: React.DragEventHandler<HTMLLabelElement> = e => {
e.preventDefault()
e.stopPropagation()
}
const handleDragLeave: React.DragEventHandler<HTMLLabelElement> = e => {
e.preventDefault()
e.stopPropagation()
setIsFileOverDropZone(false)
setIsHover(false)
}
const handleDragOver: React.DragEventHandler<HTMLLabelElement> = e => {
e.preventDefault()
e.stopPropagation()
setIsFileOverDropZone(true)
setIsHover(true)
}

const handleClick: React.MouseEventHandler<HTMLButtonElement> = _event => {
onClick != null ? onClick() : fileInput.current?.click()
}

const onChange: React.ChangeEventHandler<HTMLInputElement> = event => {
;[...(event.target.files ?? [])].forEach(f => onUpload(f))
if ('value' in event.currentTarget) event.currentTarget.value = ''
}

return (
<Flex
height="100%"
flexDirection={DIRECTION_COLUMN}
justifyContent={JUSTIFY_CENTER}
alignItems={ALIGN_CENTER}
gridGap={SPACING.spacing24}
>
{uploadText != null ? (
<>
{typeof uploadText === 'string' ? (
<LegacyStyledText
as="p"
textAlign={TYPOGRAPHY.textAlignCenter}
marginTop={SPACING.spacing16}
>
{uploadText}
</LegacyStyledText>
) : (
<>{uploadText}</>
fbelginetw marked this conversation as resolved.
Show resolved Hide resolved
)}
</>
) : null}
<PrimaryButton
onClick={handleClick}
id="UploadInput_protocolUploadButton"
>
{uploadButtonText ?? t('upload')}
</PrimaryButton>

<StyledLabel
data-testid="file_drop_zone"
onDrop={handleDrop}
onDragOver={handleDragOver}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onMouseEnter={() => {
setIsHover(true)
}}
onMouseLeave={() => {
setIsHover(false)
}}
css={isFileOverDropZone ? DRAG_OVER_STYLES : undefined}
>
<Icon
width="4rem"
color={isHover ? COLORS.blue50 : COLORS.grey60}
name="upload"
marginBottom={SPACING.spacing24}
/>
{dragAndDropText}
<StyledInput
id="file_input"
data-testid="file_input"
ref={fileInput}
type="file"
onChange={onChange}
multiple
/>
</StyledLabel>
</Flex>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ describe('MainContentContainer', () => {

it('should render prompt guide and text', () => {
render()
screen.getByText('OpentronsAI')
screen.getByText('mock PromptGuide')
screen.getByText('mock ChatFooter')
})
Expand Down
Loading
Loading