diff --git a/components/src/icons/icon-data.ts b/components/src/icons/icon-data.ts
index c805a8bbfba..e4f43123e13 100644
--- a/components/src/icons/icon-data.ts
+++ b/components/src/icons/icon-data.ts
@@ -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',
diff --git a/opentrons-ai-client/package.json b/opentrons-ai-client/package.json
index 39d4f6d275c..d8ea50136ff 100644
--- a/opentrons-ai-client/package.json
+++ b/opentrons-ai-client/package.json
@@ -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"
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 80d273abffe..7911774f748 100644
--- a/opentrons-ai-client/src/assets/localization/en/protocol_generator.json
+++ b/opentrons-ai-client/src/assets/localization/en/protocol_generator.json
@@ -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.",
diff --git a/opentrons-ai-client/src/molecules/InputPrompt/__tests__/InputPrompt.test.tsx b/opentrons-ai-client/src/molecules/InputPrompt/__tests__/InputPrompt.test.tsx
new file mode 100644
index 00000000000..f46d0722119
--- /dev/null
+++ b/opentrons-ai-client/src/molecules/InputPrompt/__tests__/InputPrompt.test.tsx
@@ -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(, { 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
+})
diff --git a/opentrons-ai-client/src/molecules/InputPrompt/index.tsx b/opentrons-ai-client/src/molecules/InputPrompt/index.tsx
new file mode 100644
index 00000000000..c9702b7773d
--- /dev/null
+++ b/opentrons-ai-client/src/molecules/InputPrompt/index.tsx
@@ -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({
+ defaultValues: {
+ userPrompt: '',
+ },
+ })
+ const userPrompt = watch('userPrompt') ?? ''
+
+ const onSubmit: SubmitHandler = async data => {
+ // ToDo (kk: 04/19/2024) call api
+ const { userPrompt } = data
+ console.log('user prompt', userPrompt)
+ }
+
+ return (
+ handleSubmit(onSubmit)}>
+
+
+
+
+
+ )
+}
+
+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 (
+
+
+
+ )
+}
diff --git a/opentrons-ai-client/src/molecules/PromptGuide/index.tsx b/opentrons-ai-client/src/molecules/PromptGuide/index.tsx
index 16d995d5cfa..3cb4c69cc51 100644
--- a/opentrons-ai-client/src/molecules/PromptGuide/index.tsx
+++ b/opentrons-ai-client/src/molecules/PromptGuide/index.tsx
@@ -24,7 +24,6 @@ export function PromptGuide(): JSX.Element {
backgroundColor={COLORS.grey30}
borderRadius={BORDERS.borderRadius12}
gridGap={SPACING.spacing32}
- width="58.125rem"
>
{t('what_typeof_protocol')}
diff --git a/opentrons-ai-client/src/molecules/SidePanel/index.tsx b/opentrons-ai-client/src/molecules/SidePanel/index.tsx
index a53927c0293..9a408e2a732 100644
--- a/opentrons-ai-client/src/molecules/SidePanel/index.tsx
+++ b/opentrons-ai-client/src/molecules/SidePanel/index.tsx
@@ -26,7 +26,6 @@ export function SidePanel(): JSX.Element {
flexDirection={DIRECTION_COLUMN}
backgroundColor={COLORS.black90}
width="24.375rem"
- height="64rem"
>
{/* logo */}
diff --git a/opentrons-ai-client/src/organisms/ChatContainer/__tests__/ChatContainer.test.tsx b/opentrons-ai-client/src/organisms/ChatContainer/__tests__/ChatContainer.test.tsx
index 26eb7b0a2b5..406e7889878 100644
--- a/opentrons-ai-client/src/organisms/ChatContainer/__tests__/ChatContainer.test.tsx
+++ b/opentrons-ai-client/src/organisms/ChatContainer/__tests__/ChatContainer.test.tsx
@@ -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 => {
return renderWithProviders(, {
@@ -17,11 +19,16 @@ const render = (): ReturnType => {
describe('ChatContainer', () => {
beforeEach(() => {
vi.mocked(PromptGuide).mockReturnValue(mock PromptGuide
)
+ vi.mocked(InputPrompt).mockReturnValue(mock InputPrompt
)
})
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
diff --git a/opentrons-ai-client/src/organisms/ChatContainer/index.tsx b/opentrons-ai-client/src/organisms/ChatContainer/index.tsx
index 2a6542c8e68..be6c4d619da 100644
--- a/opentrons-ai-client/src/organisms/ChatContainer/index.tsx
+++ b/opentrons-ai-client/src/organisms/ChatContainer/index.tsx
@@ -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 (
{/* This will be updated when input textbox and function are implemented */}
{isDummyInitial ? (
- {t('opentronsai')}
-
+
+ {t('opentronsai')}
+
+
+
+
+
+ {t('disclaimer')}
+
+
) : null}
)
}
+
+const DISCLAIMER_TEXT_STYLE = css`
+ color: ${COLORS.grey55};
+ font-size: ${TYPOGRAPHY.fontSize20};
+ line-height: ${TYPOGRAPHY.lineHeight24};
+ text-align: ${TYPOGRAPHY.textAlignCenter};
+`