Skip to content

Commit

Permalink
feat(opentrons-ai-client) add input textbox to container (#14968)
Browse files Browse the repository at this point in the history
* feat(opentrons-ai-client) add input textbox to container
  • Loading branch information
koji authored and Carlos-fernandez committed May 20, 2024
1 parent 2c8f7cd commit d020ea8
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 8 deletions.
5 changes: 5 additions & 0 deletions components/src/icons/icon-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,11 @@ export const ICON_DATA_BY_NAME = {
'M8.01487 8.84912C8.47511 8.84912 8.84821 8.47603 8.84821 8.01579C8.84821 7.55555 8.47511 7.18245 8.01487 7.18245C7.55464 7.18245 7.18154 7.55555 7.18154 8.01579C7.18154 8.47603 7.55464 8.84912 8.01487 8.84912Z M8.66654 0.928711V2.36089C11.27 2.66533 13.3354 4.73075 13.6398 7.33418H15.072V8.66751H13.6398C13.3354 11.2709 11.27 13.3363 8.66654 13.6408V15.073H7.3332V13.6408C4.72979 13.3363 2.66437 11.2709 2.35992 8.66751H0.927734V7.33418H2.35992C2.66436 4.73075 4.72978 2.66533 7.3332 2.36089V0.928711H8.66654ZM12.2944 7.33418H11.6184C11.2502 7.33418 10.9518 7.63266 10.9518 8.00085C10.9518 8.36904 11.2502 8.66751 11.6184 8.66751H12.2944C12.0071 10.5336 10.5326 12.008 8.66654 12.2953V11.6194C8.66654 11.2512 8.36806 10.9527 7.99987 10.9527C7.63168 10.9527 7.3332 11.2512 7.3332 11.6194V12.2953C5.46716 12.008 3.99268 10.5336 3.70536 8.66751H4.38132C4.74951 8.66751 5.04798 8.36904 5.04798 8.00085C5.04798 7.63266 4.74951 7.33418 4.38132 7.33418H3.70536C3.99267 5.46812 5.46715 3.99364 7.3332 3.70632V4.38229C7.3332 4.75048 7.63168 5.04896 7.99987 5.04896C8.36806 5.04896 8.66654 4.75048 8.66654 4.38229V3.70632C10.5326 3.99364 12.0071 5.46812 12.2944 7.33418Z',
viewBox: '0 0 16 16',
},
send: {
path:
'M6.96216 26.6667V5.33337L32.2955 16L6.96216 26.6667ZM9.62882 22.6667L25.4288 16L9.62882 9.33337V14L17.6288 16L9.62882 18V22.6667Z',
viewBox: '0 0 32 32',
},
settings: {
path:
'M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z',
Expand Down
1 change: 1 addition & 0 deletions opentrons-ai-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-error-boundary": "^4.0.10",
"react-hook-form": "7.50.1",
"react-i18next": "13.5.0",
"react-markdown": "9.0.1",
"styled-components": "5.3.6"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"api": "API: An API level is 2.15",
"application": "Application: Your protocol's name, describing what it does.",
"commands": "Commands: List the protocol's steps, specifying quantities in microliters and giving exact source and destination locations.",
"disclaimer": "OpentronsAI can make mistakes. Review your protocol before running it on an Opentrons robot.",
"got_feedback": "Got feedback? We love to hear it.",
"make_sure_your_prompt": "Make sure your prompt includes the following:",
"metadata": "Metadata: Three pieces of information.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react'
import { describe, it, expect } from 'vitest'
import { fireEvent, screen } from '@testing-library/react'
import { renderWithProviders } from '../../../__testing-utils__'
import { i18n } from '../../../i18n'
import { InputPrompt } from '../index'

const render = () => {
return renderWithProviders(<InputPrompt />, { i18nInstance: i18n })
}

describe('InputPrompt', () => {
it('should render textarea and disabled button', () => {
render()
screen.getByRole('textbox')
screen.queryByPlaceholderText('Type your prompt...')
screen.getByRole('button')
expect(screen.getByRole('button')).toBeDisabled()
})

it('should make send button not disabled when a user inputs something in textarea', () => {
render()
const textbox = screen.getByRole('textbox')
fireEvent.change(textbox, { target: { value: ['test'] } })
expect(screen.getByRole('button')).not.toBeDisabled()
})

// ToDo (kk:04/19/2024) add more test cases
})
149 changes: 149 additions & 0 deletions opentrons-ai-client/src/molecules/InputPrompt/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import styled, { css } from 'styled-components'
import { useForm } from 'react-hook-form'

import {
ALIGN_CENTER,
BORDERS,
Btn,
COLORS,
DIRECTION_ROW,
DISPLAY_FLEX,
Flex,
Icon,
JUSTIFY_CENTER,
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'

import type { SubmitHandler } from 'react-hook-form'

// ToDo (kk:04/19/2024) Note this interface will be used by prompt buttons in SidePanel
// interface InputPromptProps {}

interface InputType {
userPrompt: string
}

export function InputPrompt(/* props: InputPromptProps */): JSX.Element {
const { t } = useTranslation('protocol_generator')
const { register, handleSubmit, watch } = useForm<InputType>({
defaultValues: {
userPrompt: '',
},
})
const userPrompt = watch('userPrompt') ?? ''

const onSubmit: SubmitHandler<InputType> = async data => {
// ToDo (kk: 04/19/2024) call api
const { userPrompt } = data
console.log('user prompt', userPrompt)
}

return (
<StyledForm id="User_Prompt" onSubmit={() => handleSubmit(onSubmit)}>
<Flex
padding={SPACING.spacing40}
gridGap={SPACING.spacing40}
flexDirection={DIRECTION_ROW}
backgroundColor={COLORS.white}
borderRadius={BORDERS.borderRadius4}
justifyContent={JUSTIFY_CENTER}
alignItems={ALIGN_CENTER}
>
<StyledTextarea
rows={1}
placeholder={t('type_your_prompt')}
{...register('userPrompt')}
/>
<PlayButton disabled={userPrompt.length === 0} />
</Flex>
</StyledForm>
)
}

const StyledForm = styled.form`
width: 100%;
`

const StyledTextarea = styled.textarea`
resize: none;
min-height: 3.75rem;
background-color: ${COLORS.white};
border: none;
outline: none;
padding: 0;
box-shadow: none;
color: ${COLORS.black90};
width: 100%;
font-size: ${TYPOGRAPHY.fontSize20};
line-height: ${TYPOGRAPHY.lineHeight24};
::placeholder {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
`

interface PlayButtonProps {
onPlay?: () => void
disabled?: boolean
isLoading?: boolean
}

function PlayButton({
onPlay,
disabled = false,
isLoading = false,
}: PlayButtonProps): JSX.Element {
const playButtonStyle = css`
-webkit-tap-highlight-color: transparent;
&:focus {
background-color: ${COLORS.blue60};
color: ${COLORS.white};
}
&:hover {
background-color: ${COLORS.blue50};
color: ${COLORS.white};
}
&:focus-visible {
background-color: ${COLORS.blue50};
}
&:active {
background-color: ${COLORS.blue60};
color: ${COLORS.white};
}
&:disabled {
background-color: ${COLORS.grey35};
color: ${COLORS.grey50};
}
`
return (
<Btn
alignItems={ALIGN_CENTER}
backgroundColor={disabled ? COLORS.grey35 : COLORS.blue50}
borderRadius={BORDERS.borderRadiusFull}
display={DISPLAY_FLEX}
justifyContent={JUSTIFY_CENTER}
width="4.25rem"
height="3.75rem"
disabled={disabled || isLoading}
onClick={onPlay}
aria-label="play"
css={playButtonStyle}
type="submit"
>
<Icon
color={disabled ? COLORS.grey50 : COLORS.white}
name={isLoading ? 'ot-spinner' : 'send'}
spin={isLoading}
size="2rem"
/>
</Btn>
)
}
1 change: 0 additions & 1 deletion opentrons-ai-client/src/molecules/PromptGuide/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export function PromptGuide(): JSX.Element {
backgroundColor={COLORS.grey30}
borderRadius={BORDERS.borderRadius12}
gridGap={SPACING.spacing32}
width="58.125rem"
>
<StyledText css={HEADER_TEXT_STYLE}>
{t('what_typeof_protocol')}
Expand Down
1 change: 0 additions & 1 deletion opentrons-ai-client/src/molecules/SidePanel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export function SidePanel(): JSX.Element {
flexDirection={DIRECTION_COLUMN}
backgroundColor={COLORS.black90}
width="24.375rem"
height="64rem"
>
{/* logo */}
<Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { describe, it, vi, beforeEach } from 'vitest'
import { renderWithProviders } from '../../../__testing-utils__'
import { i18n } from '../../../i18n'
import { PromptGuide } from '../../../molecules/PromptGuide'
import { InputPrompt } from '../../../molecules/InputPrompt'
import { ChatContainer } from '../index'

vi.mock('../../../molecules/PromptGuide')
vi.mock('../../../molecules/InputPrompt')

const render = (): ReturnType<typeof renderWithProviders> => {
return renderWithProviders(<ChatContainer />, {
Expand All @@ -17,11 +19,16 @@ const render = (): ReturnType<typeof renderWithProviders> => {
describe('ChatContainer', () => {
beforeEach(() => {
vi.mocked(PromptGuide).mockReturnValue(<div>mock PromptGuide</div>)
vi.mocked(InputPrompt).mockReturnValue(<div>mock InputPrompt</div>)
})
it('should render prompt guide and text', () => {
render()
screen.getByText('OpentronsAI')
screen.getByText('mock PromptGuide')
screen.getByText('mock InputPrompt')
screen.getByText(
'OpentronsAI can make mistakes. Review your protocol before running it on an Opentrons robot.'
)
})

// ToDo (kk:04/16/2024) Add more test cases
Expand Down
41 changes: 35 additions & 6 deletions opentrons-ai-client/src/organisms/ChatContainer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,64 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { css } from 'styled-components'
import {
COLORS,
DIRECTION_COLUMN,
FLEX_MAX_CONTENT,
Flex,
POSITION_ABSOLUTE,
POSITION_RELATIVE,
SPACING,
StyledText,
TYPOGRAPHY,
} from '@opentrons/components'
import { PromptGuide } from '../../molecules/PromptGuide'
import { InputPrompt } from '../../molecules/InputPrompt'

export function ChatContainer(): JSX.Element {
const { t } = useTranslation('protocol_generator')
const isDummyInitial = true
return (
<Flex
padding={SPACING.spacing40}
padding={`${SPACING.spacing40} ${SPACING.spacing40} ${SPACING.spacing24}`}
backgroundColor={COLORS.grey10}
width={FLEX_MAX_CONTENT}
width="100%"
>
{/* This will be updated when input textbox and function are implemented */}
{isDummyInitial ? (
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing12}
position={POSITION_RELATIVE}
width="100%"
>
<StyledText>{t('opentronsai')}</StyledText>
<PromptGuide />
<Flex
flexDirection={DIRECTION_COLUMN}
gridGap={SPACING.spacing12}
width="100%"
>
<StyledText>{t('opentronsai')}</StyledText>
<PromptGuide />
</Flex>
<Flex
position={POSITION_ABSOLUTE}
bottom="0"
width="100%"
gridGap={SPACING.spacing24}
flexDirection={DIRECTION_COLUMN}
>
<InputPrompt />
<StyledText css={DISCLAIMER_TEXT_STYLE}>
{t('disclaimer')}
</StyledText>
</Flex>
</Flex>
) : null}
</Flex>
)
}

const DISCLAIMER_TEXT_STYLE = css`
color: ${COLORS.grey55};
font-size: ${TYPOGRAPHY.fontSize20};
line-height: ${TYPOGRAPHY.lineHeight24};
text-align: ${TYPOGRAPHY.textAlignCenter};
`

0 comments on commit d020ea8

Please sign in to comment.