diff --git a/.storybook/main.js b/.storybook/main.js index e9fc91cdf48..985486d5d4e 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -2,6 +2,7 @@ module.exports = { stories: [ '../components/**/*.stories.@(js|jsx|ts|tsx)', '../app/**/*.stories.@(js|jsx|ts|tsx)', + '../opentrons-ai-client/**/*.stories.@(js|jsx|ts|tsx)', ], addons: [ diff --git a/.storybook/preview.jsx b/.storybook/preview.jsx index d8537e57827..32864c9abcb 100644 --- a/.storybook/preview.jsx +++ b/.storybook/preview.jsx @@ -20,7 +20,7 @@ export const parameters = { options: { storySort: { method: 'alphabetical', - order: ['Design Tokens', 'Library', 'App', 'ODD'], + order: ['Design Tokens', 'Library', 'App', 'ODD', 'AI'], }, }, } diff --git a/components/src/styles/flexbox.ts b/components/src/styles/flexbox.ts index bc588372e96..2c36936b200 100644 --- a/components/src/styles/flexbox.ts +++ b/components/src/styles/flexbox.ts @@ -1,6 +1,7 @@ export const FLEX_NONE = 'none' export const FLEX_AUTO = 'auto' export const FLEX_MIN_CONTENT = 'min-content' +export const FLEX_MAX_CONTENT = 'max-content' export const ALIGN_NORMAL = 'normal' export const ALIGN_START = 'start' diff --git a/opentrons-ai-client/README.md b/opentrons-ai-client/README.md index c2ff2908418..d4c80c2bb23 100644 --- a/opentrons-ai-client/README.md +++ b/opentrons-ai-client/README.md @@ -2,8 +2,6 @@ [![JavaScript Style Guide][style-guide-badge]][style-guide] -[Download][] | [Support][] - ## Overview The Opentrons AI application helps you to create a protocol with natural language. @@ -31,7 +29,7 @@ The UI stack is built using: Some important directories: -- `opentrons-ai-server` — Opentrons AI application's server +- [opentrons-ai-server][] — Opentrons AI application's server ## Copy management @@ -62,3 +60,4 @@ TBD [babel]: https://babeljs.io/ [vite]: https://vitejs.dev/ [bundle-analyzer]: https://github.com/webpack-contrib/webpack-bundle-analyzer +[opentrons-ai-server]: https://github.com/Opentrons/opentrons/tree/edge/opentrons-ai-server diff --git a/opentrons-ai-client/src/assets/images/opentrons_logo.svg b/opentrons-ai-client/src/assets/images/opentrons_logo.svg new file mode 100644 index 00000000000..b183d161e81 --- /dev/null +++ b/opentrons-ai-client/src/assets/images/opentrons_logo.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 c8ac35504bb..f19455ad47e 100644 --- a/opentrons-ai-client/src/assets/localization/en/protocol_generator.json +++ b/opentrons-ai-client/src/assets/localization/en/protocol_generator.json @@ -2,20 +2,22 @@ "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.", + "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.", "modules": "Modules: Thermocycler or Temperature Module.", "opentronsai_asks_you": "OpentronsAI asks you to provide it!", "ot2_pipettes": "OT-2 pipettes: Include volume, number of channels, and generation.", - "prc_flex": "PRC (Flex)", + "prc_flex": "PCR (Flex)", "prc": "PCR", "reagent_transfer_flex": "Reagent Transfer (Flex)", "reagent_transfer": "Reagent Transfer", "robot": "Robot: OT-2.", - "sidebar_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.", - "sidebar_header": "Use natural language to generate protocols with OpentronsAI powered by OpenAI", - "stuck": "Stuck? Try these example prompts to get started.", + "share_your_thoughts": "Share your thoughts here", + "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", "tipracks_and_labware": "Tip racks and labware: Use names from the Opentrons Labware Library.", + "try_example_prompts": "Stuck? Try these example prompts to get started.", "type_your_prompt": "Type your prompt...", "well_allocations": "Well allocations: Describe where liquids should go in labware.", "what_if_you": "What if you don’t provide all of those pieces of information?", diff --git a/opentrons-ai-client/src/main.tsx b/opentrons-ai-client/src/main.tsx index 466bd35e081..bf46623695e 100644 --- a/opentrons-ai-client/src/main.tsx +++ b/opentrons-ai-client/src/main.tsx @@ -1,12 +1,17 @@ import React from 'react' import ReactDOM from 'react-dom/client' +import { I18nextProvider } from 'react-i18next' + +import { i18n } from './i18n' import { App } from './App' const rootElement = document.getElementById('root') -if (rootElement) { +if (rootElement != null) { ReactDOM.createRoot(rootElement).render( - + + + ) } else { diff --git a/opentrons-ai-client/src/molecules/SidePanel/SidePanel.stories.tsx b/opentrons-ai-client/src/molecules/SidePanel/SidePanel.stories.tsx new file mode 100644 index 00000000000..1c1d30b7548 --- /dev/null +++ b/opentrons-ai-client/src/molecules/SidePanel/SidePanel.stories.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { I18nextProvider } from 'react-i18next' +import { i18n } from '../../i18n' +import { SidePanel as SidePanelComponent } from './index' + +import type { Meta, StoryObj } from '@storybook/react' + +const meta: Meta = { + title: 'AI/molecules/SidePanel', + component: SidePanelComponent, + decorators: [ + Story => ( + + + + ), + ], +} +export default meta +type Story = StoryObj +export const SidePanel: Story = {} diff --git a/opentrons-ai-client/src/molecules/SidePanel/__tests__/SidePanel.test.tsx b/opentrons-ai-client/src/molecules/SidePanel/__tests__/SidePanel.test.tsx new file mode 100644 index 00000000000..56cb50f73fc --- /dev/null +++ b/opentrons-ai-client/src/molecules/SidePanel/__tests__/SidePanel.test.tsx @@ -0,0 +1,48 @@ +import React from 'react' +import { screen } from '@testing-library/react' +import { describe, it, expect } from 'vitest' + +import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../i18n' + +import { SidePanel } from '../index' + +const LOGO_FILE_NAME = + '/opentrons-ai-client/src/assets/images/opentrons_logo.svg' + +const FEEDBACK_FORM_LINK = 'https://opentrons-ai-beta.paperform.co/' + +const render = (): ReturnType => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('SidePanel', () => { + it('should render logo and text', () => { + render() + const image = screen.getByRole('img') + expect(image.getAttribute('src')).toEqual(LOGO_FILE_NAME) + screen.getByText( + 'Use natural language to generate protocols with OpentronsAI powered by OpenAI' + ) + screen.getByText( + '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.' + ) + screen.getByText('Stuck? Try these example prompts to get started.') + screen.getByText('Got feedback? We love to hear it.') + const link = screen.getByRole('link', { + name: 'Share your thoughts here', + }) + expect(link).toHaveAttribute('href', FEEDBACK_FORM_LINK) + }) + + it('should render buttons', () => { + render() + screen.getByRole('button', { name: 'PCR' }) + screen.getByRole('button', { name: 'PCR (Flex)' }) + screen.getByRole('button', { name: 'Reagent Transfer' }) + screen.getByRole('button', { name: 'Reagent Transfer (Flex)' }) + }) + it.todo('should call a mock function when clicking a button') +}) diff --git a/opentrons-ai-client/src/molecules/SidePanel/index.tsx b/opentrons-ai-client/src/molecules/SidePanel/index.tsx new file mode 100644 index 00000000000..536c0709a8b --- /dev/null +++ b/opentrons-ai-client/src/molecules/SidePanel/index.tsx @@ -0,0 +1,103 @@ +import React from 'react' +import styled, { css } from 'styled-components' +import { useTranslation } from 'react-i18next' +import { + COLORS, + DIRECTION_COLUMN, + Flex, + Link, + PrimaryButton, + SPACING, + StyledText, + TYPOGRAPHY, + WRAP, +} from '@opentrons/components' +import LOGO_PATH from '../../assets/images/opentrons_logo.svg' + +const IMAGE_ALT = 'Opentrons logo' +const FEEDBACK_FORM_LINK = 'https://opentrons-ai-beta.paperform.co/' +export function SidePanel(): JSX.Element { + const { t } = useTranslation('protocol_generator') + return ( + + {/* logo */} + + {IMAGE_ALT} + + + {/* body text */} + + + {t('side_panel_header')} + + {t('side_panel_body')} + + + {/* buttons */} + + + {t('try_example_prompts')} + + + + {/* ToDo(kk:04/11/2024) add a button component */} + {t('reagent_transfer')} + {t('reagent_transfer_flex')} + {t('prc')} + {t('prc_flex')} + + + + + {t('got_feedback')} + + + {t('share_your_thoughts')} + + + + ) +} + +const HEADER_TEXT_STYLE = css` + font-size: ${TYPOGRAPHY.fontSize32}; + line-height: ${TYPOGRAPHY.lineHeight42}; + font-weight: ${TYPOGRAPHY.fontWeightBold}; + color: ${COLORS.white}; +` +const BODY_TEXT_STYLE = css` + font-size: ${TYPOGRAPHY.fontSize20}; + line-height: ${TYPOGRAPHY.lineHeight24}; + font-weight: ${TYPOGRAPHY.fontWeightRegular}; + color: ${COLORS.white}; +` +const BUTTON_GUIDE_TEXT_STYLE = css` + font-size: ${TYPOGRAPHY.fontSize20}; + line-height: ${TYPOGRAPHY.lineHeight24}; + font-weight: ${TYPOGRAPHY.fontWeightSemiBold}; + color: ${COLORS.white}; +` + +const PromptButton = styled(PrimaryButton)` + border-radius: 2rem; + white-space: nowrap; +` + +const FeedbackLink = styled(Link)` + font-size: ${TYPOGRAPHY.fontSize20}; + line-height: ${TYPOGRAPHY.lineHeight24}; + font-weight: ${TYPOGRAPHY.fontWeightBold}; + color: ${COLORS.white}; + text-decoration: ${TYPOGRAPHY.textDecorationUnderline}; +` diff --git a/opentrons-ai-client/src/molecules/index.ts b/opentrons-ai-client/src/molecules/index.ts new file mode 100644 index 00000000000..80fcd68f91a --- /dev/null +++ b/opentrons-ai-client/src/molecules/index.ts @@ -0,0 +1 @@ +export * from './SidePanel'