diff --git a/opentrons-ai-client/src/assets/localization/en/protocol_generator.json b/opentrons-ai-client/src/assets/localization/en/protocol_generator.json
index 65fa6653667..21db34105db 100644
--- a/opentrons-ai-client/src/assets/localization/en/protocol_generator.json
+++ b/opentrons-ai-client/src/assets/localization/en/protocol_generator.json
@@ -4,9 +4,11 @@
"application": "Application: Your protocol's name, describing what it does.",
"commands": "Commands: List the protocol's steps, specifying quantities in microliters (uL) and giving exact source and destination locations.",
"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 browse 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.",
"got_feedback": "Got feedback? We love to hear it.",
"key_info": "Here are some key pieces of information to provide in your prompt:",
"labware_and_tipracks": "Labware and tip racks: Use names from the Opentrons Labware Library.",
@@ -25,6 +27,7 @@
"pipettes": "Pipettes: Specify your pipettes, including the volume, number of channels, and whether they’re mounted on the left or right.",
"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.",
diff --git a/opentrons-ai-client/src/molecules/FileUpload/index.tsx b/opentrons-ai-client/src/molecules/FileUpload/index.tsx
new file mode 100644
index 00000000000..551c3d0bd05
--- /dev/null
+++ b/opentrons-ai-client/src/molecules/FileUpload/index.tsx
@@ -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 (
+
+
+
+
+ {truncateString(file.name, 34, 19)}
+
+
+
+
+ {fileError != null ? (
+
+ {fileError}
+
+ ) : null}
+
+ )
+}
diff --git a/opentrons-ai-client/src/organisms/UpdateProtocol/index.tsx b/opentrons-ai-client/src/organisms/UpdateProtocol/index.tsx
index dd19fd73722..2c4baa5fd41 100644
--- a/opentrons-ai-client/src/organisms/UpdateProtocol/index.tsx
+++ b/opentrons-ai-client/src/organisms/UpdateProtocol/index.tsx
@@ -3,7 +3,6 @@ import {
COLORS,
DIRECTION_COLUMN,
DIRECTION_ROW,
- DropdownField,
Flex,
InputField,
JUSTIFY_CENTER,
@@ -11,14 +10,17 @@ import {
LargeButton,
StyledText,
Link as LinkComponent,
+ DropdownMenu,
} from '@opentrons/components'
+import type { DropdownOption } from '@opentrons/components'
import { UploadInput } from '../../molecules/UploadInput'
import { HeaderWithMeter } from '../../molecules/HeaderWithMeter'
import { useEffect, useState } from 'react'
import type { ChangeEvent } from 'react'
import { Trans, useTranslation } from 'react-i18next'
+import { FileUpload } from '../../molecules/FileUpload'
-const updateOptions = [
+const updateOptions: DropdownOption[] = [
{
name: 'Adapt Python protocol from OT-2 to Flex',
value: 'adapt_python_protocol',
@@ -47,11 +49,6 @@ const ContentBox = styled(Flex)`
width: 60%;
`
-const ContentFlex = styled(Flex)`
- flex-direction: ${DIRECTION_COLUMN};
- justify-content: ${JUSTIFY_CENTER};
-`
-
const HeadingText = styled(StyledText).attrs({
desktopStyle: 'headingSmallBold',
})``
@@ -67,15 +64,19 @@ const isValidProtocolFileName = (protocolFileName: string): boolean => {
}
export function UpdateProtocol(): JSX.Element {
- const { t } = useTranslation('protocol_generator')
+ const { t }: { t: (key: string) => string } = useTranslation(
+ 'protocol_generator'
+ )
const [progressPercentage, setProgressPercentage] = useState(0.0)
- const [updateType, setUpdateType] = useState('')
+ const [updateType, setUpdateType] = useState(null)
const [detailsValue, setDetailsValue] = useState('')
+ const [fileValue, setFile] = useState(null)
const [pythonText, setPythonTextValue] = useState('')
+ const [errorText, setErrorText] = useState(null)
useEffect(() => {
let progress = 0.0
- if (updateType !== '') {
+ if (updateType !== null) {
progress += 0.33
}
@@ -83,45 +84,38 @@ export function UpdateProtocol(): JSX.Element {
progress += 0.33
}
- if (pythonText !== '') {
+ if (pythonText !== '' && fileValue !== null && errorText === null) {
progress += 0.34
}
setProgressPercentage(progress)
- }, [updateType, detailsValue, pythonText])
+ }, [updateType, detailsValue, pythonText, errorText, fileValue])
const handleInputChange = (event: ChangeEvent): void => {
setDetailsValue(event.target.value)
}
- const handleUpload = async (
- file: File2 & { name: string }
+ const handleFileUpload = async (
+ file: File & { name: string }
): Promise => {
- if (file.path === null) {
- // logger.warn('Failed to upload file, path not found')
- }
if (isValidProtocolFileName(file.name)) {
- // todo convert to text
-
- console.log('compatible_file_type')
- console.log(file.name)
- // const text = await new Response(new Blob([await file.text()])).text()
const text = await file.text().catch(error => {
console.error('Error reading file:', error)
+ setErrorText(t('python_file_read_error'))
})
- console.log(text)
- if (text) {
+ if (typeof text === 'string' && text !== '') {
+ setErrorText(null)
setPythonTextValue(text)
+ } else {
+ setErrorText(t('file_length_error'))
}
+
+ setFile(file)
} else {
- console.log('incompatible_file_type')
+ setErrorText(t('python_file_type_error'))
+ setFile(file)
}
- // props.onUpload?.()
- // trackEvent({
- // name: ANALYTICS_IMPORT_PROTOCOL_TO_APP,
- // properties: { protocolFileName: file.name },
- // })
}
return (
@@ -131,17 +125,28 @@ export function UpdateProtocol(): JSX.Element {
>
-
- {t('update_existing_protocol')}
- {t('protocol_file')}
-
+ {t('update_existing_protocol')}
+ {t('protocol_file')}
+
+ {fileValue !== null ? (
+
+
+
+ ) : (
-
-
- {t('type_of_update')}
- ): void {
- setUpdateType(event.target.value)
+ />
+ )}
+
+
+ {t('type_of_update')}
+
+ {
+ const selectedOption = updateOptions.find(v => v.value === value)
+ if (selectedOption != null) {
+ setUpdateType(selectedOption)
+ }
}}
/>
- {t('provide_details_of_changes')}
-
+ {t('provide_details_of_changes')}
+
+
+
-
-
-
-
+
)
}
-
-interface File2 {
- /**
- * The real path to the file on the users filesystem
- */
- path?: string
- /**
- * The name of the file
- */
- name: string
-
- /**
- * The contents of the file
- */
- // contents: string
- text: () => Promise
-}