-
Notifications
You must be signed in to change notification settings - Fork 178
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, opentrons-ai-server): add folders for opentrons-ai #14788
Changes from all commits
a622d69
ec8339f
aa3d771
4b56c4f
9cb56da
5a0864e
374f9ae
ec56812
46973aa
a2539bf
620cdae
d074bdc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# opentrons ai client makefile | ||
|
||
# using bash instead of /bin/bash in SHELL prevents macOS optimizing away our PATH update | ||
SHELL := bash | ||
|
||
# add node_modules/.bin to PATH | ||
PATH := $(shell cd .. && yarn bin):$(PATH) | ||
|
||
benchmark_output := $(shell node -e 'console.log(new Date());') | ||
|
||
# These variables can be overriden when make is invoked to customize the | ||
# behavior of jest | ||
tests ?= | ||
cov_opts ?= --coverage=true | ||
test_opts ?= | ||
|
||
# standard targets | ||
##################################################################### | ||
|
||
.PHONY: all | ||
all: clean build | ||
|
||
.PHONY: setup | ||
setup: | ||
yarn | ||
|
||
.PHONY: clean | ||
clean: | ||
shx rm -rf dist | ||
|
||
# artifacts | ||
##################################################################### | ||
|
||
.PHONY: build | ||
build: export NODE_ENV := production | ||
build: | ||
vite build | ||
git rev-parse HEAD > dist/.commit | ||
|
||
# development | ||
##################################################################### | ||
|
||
.PHONY: dev | ||
dev: export NODE_ENV := development | ||
dev: | ||
vite serve | ||
|
||
# production assets server | ||
.PHONY: serve | ||
serve: all | ||
node ../scripts/serve-static dist | ||
|
||
.PHONY: test | ||
test: | ||
$(MAKE) -C .. test-js-ai-client tests="$(tests)" test_opts="$(test_opts)" | ||
|
||
.PHONY: test-cov | ||
test-cov: | ||
make -C .. test-js-ai-client tests=$(tests) test_opts="$(test_opts)" cov_opts="$(cov_opts)" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Opentrons AI Frontend | ||
|
||
[![JavaScript Style Guide][style-guide-badge]][style-guide] | ||
|
||
[Download][] | [Support][] | ||
|
||
## Overview | ||
|
||
The Opentrons AI application helps you to create a protocol with natural language. | ||
|
||
## Developing | ||
|
||
To get started: clone the `Opentrons/opentrons` repository, set up your computer for development as specified in the [contributing guide][contributing-guide-setup], and then: | ||
|
||
```shell | ||
# change into the cloned directory | ||
cd opentrons | ||
# prerequisite: install dependencies as specified in project setup | ||
make setup | ||
# launch the dev server | ||
make -C opentrons-ai-client dev | ||
``` | ||
|
||
## Stack and structure | ||
|
||
The UI stack is built using: | ||
|
||
- [React][] | ||
- [Babel][] | ||
- [Vite][] | ||
|
||
Some important directories: | ||
|
||
- `opentrons-ai-server` — Opentrons AI application's server | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: Could we provide hyperlink here so that when we click it we go directly there. |
||
|
||
## Copy management | ||
|
||
We use [i18next](https://www.i18next.com) for copy management and internationalization. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does internationalisation mean here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can be translated into other languages and that is the reason why we use i18next. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you mean English to Japan for example? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah that is one example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i18next will work like this when we add simple ui for language change and json files for languages. |
||
|
||
## Testing | ||
|
||
Tests for the Opentrons App are run from the top level along with all other JS project tests. | ||
|
||
- `make test-js` - Run all JavaScript tests | ||
|
||
Test tasks can also be run with the following arguments: | ||
|
||
| Argument | Default | Description | Example | | ||
| -------- | -------- | ----------------------- | --------------------------------- | | ||
| watch | `false` | Run tests in watch mode | `make test-unit watch=true` | | ||
| cover | `!watch` | Calculate code coverage | `make test watch=true cover=true` | | ||
|
||
## Building | ||
|
||
TBD | ||
|
||
[style-guide]: https://standardjs.com | ||
[style-guide-badge]: https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square&maxAge=3600 | ||
[contributing-guide-setup]: ../CONTRIBUTING.md#development-setup | ||
[contributing-guide-running-the-api]: ../CONTRIBUTING.md#opentrons-api | ||
[react]: https://react.dev/ | ||
[babel]: https://babeljs.io/ | ||
[vite]: https://vitejs.dev/ | ||
[bundle-analyzer]: https://github.com/webpack-contrib/webpack-bundle-analyzer |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use strict' | ||
|
||
module.exports = { | ||
env: { | ||
// Must have babel-plugin-styled-components in each env, | ||
// see here for further details: s https://styled-components.com/docs/tooling#babel-plugin | ||
production: { | ||
plugins: ['babel-plugin-styled-components', 'babel-plugin-unassert'], | ||
}, | ||
development: { | ||
plugins: ['babel-plugin-styled-components'], | ||
}, | ||
test: { | ||
plugins: [ | ||
// disable ssr, displayName to fix toHaveStyleRule | ||
// https://github.com/styled-components/jest-styled-components/issues/294 | ||
['babel-plugin-styled-components', { ssr: false, displayName: false }], | ||
], | ||
}, | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Opentrons AI</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="/src/main.tsx"></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"name": "opentrons-ai-client", | ||
"type": "module", | ||
"version": "0.0.0-dev", | ||
"description": "Opentrons AI application UI", | ||
"source": "src/index.tsx", | ||
"types": "lib/index.d.ts", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/Opentrons/opentrons.git" | ||
}, | ||
"author": { | ||
"name": "Opentrons Labworks", | ||
"email": "[email protected]" | ||
}, | ||
"license": "Apache-2.0", | ||
"bugs": { | ||
"url": "https://github.com/Opentrons/opentrons/issues" | ||
}, | ||
"homepage": "https://github.com/Opentrons/opentrons", | ||
"dependencies": { | ||
"@fontsource/dejavu-sans": "5.0.3", | ||
"@fontsource/public-sans": "5.0.3", | ||
"@opentrons/components": "link:../components", | ||
"i18next": "^19.8.3", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good idea to add i18next right off the bat instead of what we did with PD haha |
||
"react": "18.2.0", | ||
"react-dom": "18.2.0", | ||
"react-error-boundary": "^4.0.10", | ||
"react-i18next": "13.5.0", | ||
"styled-components": "5.3.6" | ||
}, | ||
"engines": { | ||
"node": ">=18.19.0" | ||
}, | ||
"devDependencies": { | ||
"@types/styled-components": "^5.1.26" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react' | ||
import { screen } from '@testing-library/react' | ||
import { describe, it } from 'vitest' | ||
|
||
import { renderWithProviders } from './__testing-utils__' | ||
|
||
import { App } from './App' | ||
|
||
const render = (): ReturnType<typeof renderWithProviders> => { | ||
return renderWithProviders(<App />) | ||
} | ||
|
||
describe('App', () => { | ||
it('should render text', () => { | ||
render() | ||
screen.getByText('Opentrons AI') | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import React from 'react' | ||
import { Flex, StyledText } from '@opentrons/components' | ||
export function App(): JSX.Element { | ||
return ( | ||
<Flex> | ||
<StyledText as="h1">Opentrons AI</StyledText> | ||
</Flex> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './renderWithProviders' | ||
export * from './matchers' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import type { Matcher } from '@testing-library/react' | ||
|
||
// Match things like <p>Some <strong>nested</strong> text</p> | ||
// Use with either string match: getByText(nestedTextMatcher("Some nested text")) | ||
// or regexp: getByText(nestedTextMatcher(/Some nested text/)) | ||
export const nestedTextMatcher = (textMatch: string | RegExp): Matcher => ( | ||
content, | ||
node | ||
) => { | ||
const hasText = (n: typeof node): boolean => { | ||
if (n == null || n.textContent === null) return false | ||
return typeof textMatch === 'string' | ||
? Boolean(n?.textContent.match(textMatch)) | ||
: textMatch.test(n.textContent) | ||
} | ||
const nodeHasText = hasText(node) | ||
const childrenDontHaveText = | ||
node != null && Array.from(node.children).every(child => !hasText(child)) | ||
|
||
return nodeHasText && childrenDontHaveText | ||
} | ||
|
||
// need componentPropsMatcher | ||
// need partialComponentPropsMatcher |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// render using targetted component using @testing-library/react | ||
// with wrapping providers for i18next and redux | ||
import * as React from 'react' | ||
import { QueryClient, QueryClientProvider } from 'react-query' | ||
import { I18nextProvider } from 'react-i18next' | ||
import { Provider } from 'react-redux' | ||
import { vi } from 'vitest' | ||
import { render } from '@testing-library/react' | ||
import { createStore } from 'redux' | ||
|
||
import type { PreloadedState, Store } from 'redux' | ||
import type { RenderOptions, RenderResult } from '@testing-library/react' | ||
|
||
export interface RenderWithProvidersOptions<State> extends RenderOptions { | ||
initialState?: State | ||
i18nInstance: React.ComponentProps<typeof I18nextProvider>['i18n'] | ||
} | ||
|
||
export function renderWithProviders<State>( | ||
Component: React.ReactElement, | ||
options?: RenderWithProvidersOptions<State> | ||
): [RenderResult, Store<State>] { | ||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions | ||
const { initialState = {}, i18nInstance = null } = options || {} | ||
|
||
const store: Store<State> = createStore( | ||
vi.fn(), | ||
initialState as PreloadedState<State> | ||
) | ||
store.dispatch = vi.fn() | ||
store.getState = vi.fn(() => initialState) as () => State | ||
|
||
const queryClient = new QueryClient() | ||
|
||
const ProviderWrapper: React.ComponentType<React.PropsWithChildren<{}>> = ({ | ||
children, | ||
}) => { | ||
const BaseWrapper = ( | ||
<QueryClientProvider client={queryClient}> | ||
<Provider store={store}>{children}</Provider> | ||
</QueryClientProvider> | ||
) | ||
if (i18nInstance != null) { | ||
return ( | ||
<I18nextProvider i18n={i18nInstance}>{BaseWrapper}</I18nextProvider> | ||
) | ||
} else { | ||
return BaseWrapper | ||
} | ||
} | ||
|
||
return [render(Component, { wrapper: ProviderWrapper }), store] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import shared from './shared.json' | ||
import protocol_generator from './protocol_generator.json' | ||
|
||
export const en = { | ||
shared, | ||
protocol_generator, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"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.", | ||
"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": "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.", | ||
"tipracks_and_labware": "Tip racks and labware: Use names from the Opentrons Labware Library.", | ||
"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?", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since OpenAI cannot follow instruction consistently, this may not happen all the time. So I am happy to remove this line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why everything line by line, why dont we do something like triplet quote in python? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is json file and we use eslint. |
||
"what_typeof_protocol": "What type of protocol do you need?" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"send": "Send" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { en } from './en' | ||
|
||
export const resources = { | ||
en, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import i18n from 'i18next' | ||
import capitalize from 'lodash/capitalize' | ||
import startCase from 'lodash/startCase' | ||
import { initReactI18next } from 'react-i18next' | ||
import { resources } from './assets/localization' | ||
import { titleCase } from '@opentrons/shared-data' | ||
|
||
i18n.use(initReactI18next).init( | ||
{ | ||
resources, | ||
lng: 'en', | ||
fallbackLng: 'en', | ||
debug: process.env.NODE_ENV === 'development', | ||
ns: ['shared'], | ||
defaultNS: 'shared', | ||
interpolation: { | ||
escapeValue: false, // not needed for react as it escapes by default | ||
format: function (value, format, lng) { | ||
if (format === 'upperCase') return value.toUpperCase() | ||
if (format === 'lowerCase') return value.toLowerCase() | ||
if (format === 'capitalize') return capitalize(value) | ||
if (format === 'sentenceCase') return startCase(value) | ||
if (format === 'titleCase') return titleCase(value) | ||
return value | ||
}, | ||
}, | ||
keySeparator: false, // use namespaces and context instead | ||
saveMissing: true, | ||
missingKeyHandler: (lng, ns, key) => { | ||
process.env.NODE_ENV === 'test' | ||
? console.error(`Missing ${lng} Translation: key={${key}} ns={${ns}}`) | ||
: console.warn(`Missing ${lng} Translation: key={${key}} ns={${ns}}`) | ||
}, | ||
}, | ||
err => { | ||
if (err) { | ||
console.error( | ||
'Internationalization was not initialized properly. error: ', | ||
err | ||
) | ||
} | ||
} | ||
) | ||
|
||
export { i18n } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we add a single sentence for each line to know why they are there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, the reason is
monorepo