diff --git a/opentrons-ai-client/Makefile b/opentrons-ai-client/Makefile index 9c15fa32e41..8afd804af4c 100644 --- a/opentrons-ai-client/Makefile +++ b/opentrons-ai-client/Makefile @@ -6,6 +6,9 @@ SHELL := bash # add node_modules/.bin to PATH PATH := $(shell cd .. && yarn bin):$(PATH) +# dev server port +PORT ?= 5173 + benchmark_output := $(shell node -e 'console.log(new Date());') # These variables can be overriden when make is invoked to customize the @@ -42,6 +45,7 @@ build: .PHONY: dev dev: export NODE_ENV := development +dev: export PORT := $(PORT) dev: vite serve diff --git a/opentrons-ai-client/index.html b/opentrons-ai-client/index.html index 57e7f83f591..4e8e60a4121 100644 --- a/opentrons-ai-client/index.html +++ b/opentrons-ai-client/index.html @@ -2,7 +2,8 @@ - + + Opentrons AI diff --git a/opentrons-ai-client/package.json b/opentrons-ai-client/package.json index d8ea50136ff..dfd8069c7b1 100644 --- a/opentrons-ai-client/package.json +++ b/opentrons-ai-client/package.json @@ -21,7 +21,9 @@ "dependencies": { "@fontsource/public-sans": "5.0.3", "@opentrons/components": "link:../components", + "axios": "^0.21.1", "i18next": "^19.8.3", + "jotai": "2.8.0", "react": "18.2.0", "react-dom": "18.2.0", "react-error-boundary": "^4.0.10", diff --git a/opentrons-ai-client/src/assets/images/favicon/android-chrome-192x192.png b/opentrons-ai-client/src/assets/images/favicon/android-chrome-192x192.png new file mode 100644 index 00000000000..468479c76e7 Binary files /dev/null and b/opentrons-ai-client/src/assets/images/favicon/android-chrome-192x192.png differ diff --git a/opentrons-ai-client/src/assets/images/favicon/android-chrome-512x512.png b/opentrons-ai-client/src/assets/images/favicon/android-chrome-512x512.png new file mode 100644 index 00000000000..bab2df65fdb Binary files /dev/null and b/opentrons-ai-client/src/assets/images/favicon/android-chrome-512x512.png differ diff --git a/opentrons-ai-client/src/assets/images/favicon/apple-touch-icon.png b/opentrons-ai-client/src/assets/images/favicon/apple-touch-icon.png new file mode 100644 index 00000000000..ccbd74a497b Binary files /dev/null and b/opentrons-ai-client/src/assets/images/favicon/apple-touch-icon.png differ diff --git a/opentrons-ai-client/src/assets/images/favicon/favicon-16x16.png b/opentrons-ai-client/src/assets/images/favicon/favicon-16x16.png new file mode 100644 index 00000000000..4edd2e8b352 Binary files /dev/null and b/opentrons-ai-client/src/assets/images/favicon/favicon-16x16.png differ diff --git a/opentrons-ai-client/src/assets/images/favicon/favicon-32x32.png b/opentrons-ai-client/src/assets/images/favicon/favicon-32x32.png new file mode 100644 index 00000000000..eed71c70949 Binary files /dev/null and b/opentrons-ai-client/src/assets/images/favicon/favicon-32x32.png differ diff --git a/opentrons-ai-client/src/assets/images/favicon/favicon.ico b/opentrons-ai-client/src/assets/images/favicon/favicon.ico new file mode 100644 index 00000000000..d1266c550bf Binary files /dev/null and b/opentrons-ai-client/src/assets/images/favicon/favicon.ico differ diff --git a/opentrons-ai-client/src/assets/images/favicon/site.webmanifest b/opentrons-ai-client/src/assets/images/favicon/site.webmanifest new file mode 100644 index 00000000000..fe3af17b5d1 --- /dev/null +++ b/opentrons-ai-client/src/assets/images/favicon/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "opentrons_favicon", + "short_name": "favicon", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/opentrons-ai-client/src/atoms/SendButton/__tests__/SendButton.test.tsx b/opentrons-ai-client/src/atoms/SendButton/__tests__/SendButton.test.tsx new file mode 100644 index 00000000000..dcf90ec1022 --- /dev/null +++ b/opentrons-ai-client/src/atoms/SendButton/__tests__/SendButton.test.tsx @@ -0,0 +1,53 @@ +import React from 'react' +import { describe, it, vi, beforeEach, expect } from 'vitest' +import { fireEvent, screen } from '@testing-library/react' +import { renderWithProviders } from '../../../__testing-utils__' + +import { SendButton } from '../index' + +const mockHandleClick = vi.fn() +const render = (props: React.ComponentProps) => { + return renderWithProviders() +} + +describe('SendButton', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + handleClick: mockHandleClick, + disabled: true, + isLoading: false, + } + }) + it('should render button with send icon and its initially disabled', () => { + render(props) + const button = screen.getByRole('button') + expect(button).toBeDisabled() + screen.getByTestId('SendButton_icon_send') + }) + + it('should render button and its not disabled when disabled false', () => { + props = { ...props, disabled: false } + render(props) + const button = screen.getByRole('button') + expect(button).not.toBeDisabled() + screen.getByTestId('SendButton_icon_send') + }) + + it('should render button with spinner icon when isLoading', () => { + props = { ...props, isLoading: true } + render(props) + const button = screen.getByRole('button') + expect(button).toBeDisabled() + screen.getByTestId('SendButton_icon_ot-spinner') + }) + + it('should call a mock function when clicking the button', () => { + props = { ...props, disabled: false } + render(props) + const button = screen.getByRole('button') + fireEvent.click(button) + expect(mockHandleClick).toHaveBeenCalled() + }) +}) diff --git a/opentrons-ai-client/src/atoms/SendButton/index.tsx b/opentrons-ai-client/src/atoms/SendButton/index.tsx new file mode 100644 index 00000000000..e165762b2ab --- /dev/null +++ b/opentrons-ai-client/src/atoms/SendButton/index.tsx @@ -0,0 +1,74 @@ +import React from 'react' +import { css } from 'styled-components' + +import { + ALIGN_CENTER, + BORDERS, + Btn, + COLORS, + DISPLAY_FLEX, + Icon, + JUSTIFY_CENTER, +} from '@opentrons/components' + +interface SendButtonProps { + handleClick: () => void + disabled?: boolean + isLoading?: boolean +} + +export function SendButton({ + handleClick, + disabled = false, + isLoading = false, +}: SendButtonProps): 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/main.tsx b/opentrons-ai-client/src/main.tsx index a5719bc94d8..a2f1338bd7b 100644 --- a/opentrons-ai-client/src/main.tsx +++ b/opentrons-ai-client/src/main.tsx @@ -2,7 +2,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' import { I18nextProvider } from 'react-i18next' import { GlobalStyle } from './atoms/GlobalStyle' -import { PromptProvider } from './organisms/PromptButton/PromptProvider' import { i18n } from './i18n' import { App } from './App' @@ -13,9 +12,7 @@ if (rootElement != null) { - - - + ) diff --git a/opentrons-ai-client/src/molecules/ChatDisplay/ChatDisplay.stories.tsx b/opentrons-ai-client/src/molecules/ChatDisplay/ChatDisplay.stories.tsx index ae03a25f754..e3e0a1a6f36 100644 --- a/opentrons-ai-client/src/molecules/ChatDisplay/ChatDisplay.stories.tsx +++ b/opentrons-ai-client/src/molecules/ChatDisplay/ChatDisplay.stories.tsx @@ -24,7 +24,9 @@ type Story = StoryObj export const OpentronsAI: Story = { args: { - content: ` + chat: { + role: 'assistant', + content: ` ## sample output from OpentronsAI \`\`\`py @@ -50,13 +52,15 @@ def run(protocol: protocol_api.ProtocolContext): TEMP_DECK_WAIT_TIME = 50 # seconds \`\`\` `, - isUserInput: false, + }, }, } export const User: Story = { args: { - content: ` + chat: { + role: 'user', + content: ` - Application: Reagent transfer - Robot: OT-2 - API: 2.13 @@ -76,6 +80,6 @@ export const User: Story = { to first well in the destination labware. Use new tip for each transfer. `, - isUserInput: true, + }, }, } diff --git a/opentrons-ai-client/src/molecules/ChatDisplay/__tests__/ChatDisplay.test.tsx b/opentrons-ai-client/src/molecules/ChatDisplay/__tests__/ChatDisplay.test.tsx index 75b99717abb..98fd30274ee 100644 --- a/opentrons-ai-client/src/molecules/ChatDisplay/__tests__/ChatDisplay.test.tsx +++ b/opentrons-ai-client/src/molecules/ChatDisplay/__tests__/ChatDisplay.test.tsx @@ -15,8 +15,10 @@ describe('ChatDisplay', () => { beforeEach(() => { props = { - content: 'mock text from the backend', - isUserInput: false, + chat: { + role: 'assistant', + content: 'mock text from the backend', + }, } }) it('should display response from the backend and label', () => { @@ -29,8 +31,10 @@ describe('ChatDisplay', () => { }) it('should display input from use and label', () => { props = { - content: 'mock text from user input', - isUserInput: true, + chat: { + role: 'user', + content: 'mock text from user input', + }, } render(props) screen.getByText('You') diff --git a/opentrons-ai-client/src/molecules/ChatDisplay/index.tsx b/opentrons-ai-client/src/molecules/ChatDisplay/index.tsx index c2d52e6a593..3b5c54ebbc6 100644 --- a/opentrons-ai-client/src/molecules/ChatDisplay/index.tsx +++ b/opentrons-ai-client/src/molecules/ChatDisplay/index.tsx @@ -1,4 +1,5 @@ import React from 'react' +// import { css } from 'styled-components' import { useTranslation } from 'react-i18next' import Markdown from 'react-markdown' import { @@ -10,37 +11,82 @@ import { StyledText, } from '@opentrons/components' +import type { ChatData } from '../../resources/types' + interface ChatDisplayProps { - content: string - isUserInput: boolean + chat: ChatData } -export function ChatDisplay({ - content, - isUserInput, -}: ChatDisplayProps): JSX.Element { +export function ChatDisplay({ chat }: ChatDisplayProps): JSX.Element { const { t } = useTranslation('protocol_generator') + const { role, content } = chat + const isUser = role === 'user' return ( - {isUserInput ? t('you') : t('opentronsai')} + {isUser ? t('you') : t('opentronsai')} {/* text should be markdown so this component will have a package or function to parse markdown */} - {/* ToDo (kk:04/19/2024) I will get feedback for additional styling from the design team. */} + {/* ToDo (kk:05/02/2024) This part is waiting for Mel's design */} + {/* + {content} + */} {content} ) } + +// ToDo (kk:05/02/2024) This part is waiting for Mel's design +// function ExternalLink(props: JSX.IntrinsicAttributes): JSX.Element { +// return +// } + +// function ParagraphText(props: JSX.IntrinsicAttributes): JSX.Element { +// return +// } + +// function HeaderText(props: JSX.IntrinsicAttributes): JSX.Element { +// return +// } + +// function ListItemText(props: JSX.IntrinsicAttributes): JSX.Element { +// return +// } + +// function UnnumberedListText(props: JSX.IntrinsicAttributes): JSX.Element { +// return +// } + +// const CODE_TEXT_STYLE = css` +// padding: ${SPACING.spacing16}; +// font-family: monospace; +// color: ${COLORS.white}; +// background-color: ${COLORS.black90}; +// ` + +// function CodeText(props: JSX.IntrinsicAttributes): JSX.Element { +// return +// } diff --git a/opentrons-ai-client/src/molecules/InputPrompt/index.tsx b/opentrons-ai-client/src/molecules/InputPrompt/index.tsx index cdff4e7c44b..e8a8dda0c20 100644 --- a/opentrons-ai-client/src/molecules/InputPrompt/index.tsx +++ b/opentrons-ai-client/src/molecules/InputPrompt/index.tsx @@ -2,65 +2,115 @@ import React from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' import { useForm } from 'react-hook-form' +import { useAtom } from 'jotai' +import axios from 'axios' import { ALIGN_CENTER, BORDERS, - Btn, COLORS, DIRECTION_ROW, - DISPLAY_FLEX, Flex, - Icon, JUSTIFY_CENTER, SPACING, TYPOGRAPHY, } from '@opentrons/components' -import { promptContext } from '../../organisms/PromptButton/PromptProvider' -import type { SubmitHandler } from 'react-hook-form' +import { SendButton } from '../../atoms/SendButton' +import { preparedPromptAtom, chatDataAtom } from '../../resources/atoms' -// ToDo (kk:04/19/2024) Note this interface will be used by prompt buttons in SidePanel -// interface InputPromptProps {} +import type { ChatData } from '../../resources/types' + +// ToDo (kk:05/02/2024) This url is temporary +const url = 'http://localhost:8000/streaming/ask' interface InputType { userPrompt: string } -export function InputPrompt(/* props: InputPromptProps */): JSX.Element { +export function InputPrompt(): JSX.Element { const { t } = useTranslation('protocol_generator') - const { register, handleSubmit, watch, setValue } = useForm({ + const { register, watch, setValue, reset } = useForm({ defaultValues: { userPrompt: '', }, }) - const usePromptValue = (): string => React.useContext(promptContext) - const promptFromButton = usePromptValue() - const userPrompt = watch('userPrompt') ?? '' + const [preparedPrompt] = useAtom(preparedPromptAtom) + const [, setChatData] = useAtom(chatDataAtom) + const [submitted, setSubmitted] = React.useState(false) - const onSubmit: SubmitHandler = async data => { - // ToDo (kk: 04/19/2024) call api - const { userPrompt } = data - console.log('user prompt', userPrompt) - } + const [data, setData] = React.useState(null) + const [loading, setLoading] = React.useState(false) + const [error, setError] = React.useState('') + + const userPrompt = watch('userPrompt') ?? '' const calcTextAreaHeight = (): number => { const rowsNum = userPrompt.split('\n').length return rowsNum } + const fetchData = async (prompt: string): Promise => { + if (prompt !== '') { + setLoading(true) + try { + const response = await axios.post(url, { + headers: { + 'Content-Type': 'application/json', + }, + query: prompt, + }) + setData(response.data) + } catch (err) { + setError('Error fetching data from the API.') + } finally { + setLoading(false) + } + } + } + + const handleClick = (): void => { + const userInput: ChatData = { + role: 'user', + content: userPrompt, + } + setChatData(chatData => [...chatData, userInput]) + void fetchData(userPrompt) + setSubmitted(true) + reset() + } + + React.useEffect(() => { + if (preparedPrompt !== '') setValue('userPrompt', preparedPrompt as string) + }, [preparedPrompt, setValue]) + React.useEffect(() => { - if (promptFromButton !== '') setValue('userPrompt', promptFromButton) - }, [promptFromButton, setValue]) + if (submitted && data && !loading) { + const { role, content } = data.data + const assistantResponse: ChatData = { + role, + content, + } + setChatData(chatData => [...chatData, assistantResponse]) + setSubmitted(false) + } + }, [data, loading, submitted]) + + // ToDo (kk:05/02/2024) This is also temp. Asking the design about error. + console.error('error', error) return ( - handleSubmit(onSubmit)}> + - + ) @@ -106,65 +156,3 @@ const StyledTextarea = styled.textarea` 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/organisms/ChatContainer/index.tsx b/opentrons-ai-client/src/organisms/ChatContainer/index.tsx index be6c4d619da..8a120c65112 100644 --- a/opentrons-ai-client/src/organisms/ChatContainer/index.tsx +++ b/opentrons-ai-client/src/organisms/ChatContainer/index.tsx @@ -1,6 +1,8 @@ import React from 'react' import { useTranslation } from 'react-i18next' -import { css } from 'styled-components' +import styled, { css } from 'styled-components' +import { useAtom } from 'jotai' + import { COLORS, DIRECTION_COLUMN, @@ -13,10 +15,13 @@ import { } from '@opentrons/components' import { PromptGuide } from '../../molecules/PromptGuide' import { InputPrompt } from '../../molecules/InputPrompt' +import { ChatDisplay } from '../../molecules/ChatDisplay' +import { chatDataAtom } from '../../resources/atoms' export function ChatContainer(): JSX.Element { const { t } = useTranslation('protocol_generator') - const isDummyInitial = true + const [chatData] = useAtom(chatDataAtom) + return ( {/* This will be updated when input textbox and function are implemented */} - {isDummyInitial ? ( + + + + {t('opentronsai')} + {/* Prompt Guide remain as a reference for users. */} + + {chatData.length > 0 + ? chatData.map((chat, index) => ( + + )) + : null} + - - {t('opentronsai')} - - - - - - {t('disclaimer')} - - + + {t('disclaimer')} - ) : null} + ) } +const ChatDataContainer = styled(Flex)` + max-height: calc(100vh); + overflow-y: auto; + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing12}; + width: 100%; +` + const DISCLAIMER_TEXT_STYLE = css` color: ${COLORS.grey55}; font-size: ${TYPOGRAPHY.fontSize20}; diff --git a/opentrons-ai-client/src/organisms/PromptButton/PromptProvider.tsx b/opentrons-ai-client/src/organisms/PromptButton/PromptProvider.tsx deleted file mode 100644 index f148e4fdd94..00000000000 --- a/opentrons-ai-client/src/organisms/PromptButton/PromptProvider.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' - -export const promptContext = React.createContext('') -export const setPromptContext = React.createContext< - React.Dispatch> ->(() => undefined) - -interface PromptProviderProps { - children: React.ReactNode -} - -export function PromptProvider({ - children, -}: PromptProviderProps): React.ReactElement { - const [prompt, setPrompt] = React.useState('') - - return ( - - - {children} - - - ) -} diff --git a/opentrons-ai-client/src/organisms/PromptButton/__tests__/PromptButton.test.tsx b/opentrons-ai-client/src/organisms/PromptButton/__tests__/PromptButton.test.tsx index b4dadfcc931..d4659cc3c84 100644 --- a/opentrons-ai-client/src/organisms/PromptButton/__tests__/PromptButton.test.tsx +++ b/opentrons-ai-client/src/organisms/PromptButton/__tests__/PromptButton.test.tsx @@ -1,20 +1,15 @@ import React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, vi, beforeEach, expect } from 'vitest' +import { useAtom } from 'jotai' +import { fireEvent, screen, renderHook } from '@testing-library/react' +import { describe, it, beforeEach, expect } from 'vitest' import { renderWithProviders } from '../../../__testing-utils__' -import { setPromptContext } from '../PromptProvider' import { reagentTransfer } from '../../../assets/prompts' +import { preparedPromptAtom } from '../../../resources/atoms' import { PromptButton } from '../index' -const mockSetPrompt = vi.fn() - const render = (props: React.ComponentProps) => { - return renderWithProviders( - - s - - ) + return renderWithProviders() } describe('PromptButton', () => { @@ -34,6 +29,8 @@ describe('PromptButton', () => { render(props) const button = screen.getByRole('button', { name: 'Reagent Transfer' }) fireEvent.click(button) - expect(mockSetPrompt).toHaveBeenCalledWith(reagentTransfer) + const { result } = renderHook(() => useAtom(preparedPromptAtom)) + fireEvent.click(button) + expect(result.current[0]).toBe(reagentTransfer) }) }) diff --git a/opentrons-ai-client/src/organisms/PromptButton/__tests__/PromptProvider.test.tsx b/opentrons-ai-client/src/organisms/PromptButton/__tests__/PromptProvider.test.tsx deleted file mode 100644 index 5caedf2c3ad..00000000000 --- a/opentrons-ai-client/src/organisms/PromptButton/__tests__/PromptProvider.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react' -import { describe, it, expect } from 'vitest' -import { fireEvent, screen } from '@testing-library/react' - -import { renderWithProviders } from '../../../__testing-utils__' -import { - PromptProvider, - promptContext, - setPromptContext, -} from '../PromptProvider' - -const TestComponent = () => { - const usePromptValue = (): string => React.useContext(promptContext) - const prompt = usePromptValue() - - const usePromptSetValue = (): React.Dispatch> => - React.useContext(setPromptContext) - const setPrompt = usePromptSetValue() - - return ( -
-
{prompt}
- -
- ) -} - -const render = () => { - return renderWithProviders( - - - - ) -} - -describe('PromptProvider', () => { - it('should render initial value', () => { - render() - const prompt = screen.getByTestId('mock_prompt') - expect(prompt.textContent).toEqual('') - }) - - it('should set a mock prompt', () => { - render() - fireEvent.click(screen.getByRole('button')) - expect(screen.getByText('Test Prompt')).toBeInTheDocument() - }) -}) diff --git a/opentrons-ai-client/src/organisms/PromptButton/index.tsx b/opentrons-ai-client/src/organisms/PromptButton/index.tsx index 452a615e67b..13a12cb492c 100644 --- a/opentrons-ai-client/src/organisms/PromptButton/index.tsx +++ b/opentrons-ai-client/src/organisms/PromptButton/index.tsx @@ -1,13 +1,14 @@ -import React, { useCallback } from 'react' +import React from 'react' import styled from 'styled-components' +import { useAtom } from 'jotai' import { BORDERS, PrimaryButton } from '@opentrons/components' -import { setPromptContext } from './PromptProvider' import { reagentTransfer, flexReagentTransfer, pcr, flexPcr, } from '../../assets/prompts' +import { preparedPromptAtom } from '../../resources/atoms' interface PromptButtonProps { buttonText: string @@ -30,18 +31,11 @@ const PROMPT_BY_NAME: Record = { } export function PromptButton({ buttonText }: PromptButtonProps): JSX.Element { - const usePromptSetValue = (): React.Dispatch> => - React.useContext(setPromptContext) - const setPrompt = usePromptSetValue() - - const handleClick = useCallback( - (event: React.MouseEvent) => { - const { prompt } = PROMPT_BY_NAME[buttonText] - setPrompt(prompt) - event.currentTarget.blur() - }, - [setPrompt, buttonText] - ) + const [, setPreparedPrompt] = useAtom(preparedPromptAtom) + const handleClick = (): void => { + const { prompt } = PROMPT_BY_NAME[buttonText] + setPreparedPrompt(prompt) + } return {buttonText} } diff --git a/opentrons-ai-client/src/resources/atoms.ts b/opentrons-ai-client/src/resources/atoms.ts new file mode 100644 index 00000000000..98ebd7df6f1 --- /dev/null +++ b/opentrons-ai-client/src/resources/atoms.ts @@ -0,0 +1,9 @@ +// jotai's atoms +import { atom } from 'jotai' +import type { ChatData } from './types' + +/** preparedPromptAtom is for PromptButton */ +export const preparedPromptAtom = atom('') + +/** ChatDataAtom is for chat data (user prompt and response from OpenAI API) */ +export const chatDataAtom = atom([]) diff --git a/opentrons-ai-client/src/resources/types.ts b/opentrons-ai-client/src/resources/types.ts new file mode 100644 index 00000000000..a0f5ebca959 --- /dev/null +++ b/opentrons-ai-client/src/resources/types.ts @@ -0,0 +1,6 @@ +export interface ChatData { + /** assistant: ChatGPT API, user: user */ + role: 'assistant' | 'user' + /** content ChatGPT API return or user prompt */ + content: string +} diff --git a/package.json b/package.json index a38a11bdcd3..c9e5ec05d52 100755 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "handlebars-loader": "^1.7.1", "html-webpack-plugin": "^3.2.0", "identity-obj-proxy": "^3.0.0", + "jotai": "2.8.0", "jsdom": "^16.4.0", "lost": "^8.3.1", "madge": "^3.6.0", diff --git a/yarn.lock b/yarn.lock index 9b1d8cc5d27..88db9554138 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12781,6 +12781,11 @@ jmespath@0.16.0: resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.16.0.tgz#b15b0a85dfd4d930d43e69ed605943c802785076" integrity sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw== +jotai@2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.8.0.tgz#5a6585cd5576c400c2c5f8e157b83ad2ba70b2ab" + integrity sha512-yZNMC36FdLOksOr8qga0yLf14miCJlEThlp5DeFJNnqzm2+ZG7wLcJzoOyij5K6U6Xlc5ljQqPDlJRgqW0Y18g== + js-sdsl@4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711"