Skip to content

Commit

Permalink
feat!: AI Chat styling changes, ImageAdapter, image imports (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristopherChudzicki authored Jan 31, 2025
1 parent 3eea32f commit 5945741
Show file tree
Hide file tree
Showing 21 changed files with 375 additions and 103 deletions.
3 changes: 1 addition & 2 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ const config: Config.InitialOptions = {
"^.+\\.(t|j)sx?$": "@swc/jest",
},
moduleNameMapper: {
"\\.(svg|jpg|jpeg|png)$": "ol-test-utilities/filemocks/imagemock.js",
"\\.(css|scss)$": "ol-test-utilities/filemocks/filemock.js",
"\\.(css|scss|svg|jpg|jpeg|png)$": "<rootDir>/test-utils/filemock.js",
},
rootDir: "./src",
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
"typecheck": "tsc --noEmit",
"build:esm": "tsc",
"build:cjs": "tsc --module commonjs --outDir dist/cjs",
"build:static": "cp -r ./static dist/static",
"build:type-augmentation": "cp -r src/type-augmentation dist/type-augmentation",
"build": "rm -rf dist && rm -f .tsbuildinfo && npm run build:esm && npm run build:cjs && npm run build:type-augmentation",
"build": "./scripts/build.sh",
"lint-check": "eslint src/ .storybook/",
"lint-fix": "yarn lint-check --fix",
"fmt-check": "prettier --ignore-path .gitignore --ignore-path .prettierignore --check .",
Expand Down
9 changes: 9 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e -o pipefail

rm -rf dist &&
rm -f .tsbuildinfo &&
npm run build:esm &&
npm run build:cjs &&
npm run build:type-augmentation &&
npm run build:static
15 changes: 15 additions & 0 deletions src/Installation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Meta } from "@storybook/blocks"

<Meta title="Smoot-Design/Installation/Installation" />

# Installation

## ThemeProvider

All components must be descendants of a `ThemeProvider` component. This component provides the theme to all styled components in the tree. See [https://mitodl.github.io/smoot-design/?path=/docs/smoot-design-themeprovider--docs] for more.

## Image Imports

Some components import images in their source code. Your bundler must be configured to handle these imports.

- **NextJS:** If using NextJS, provide `ImgAdapter: Image` to the theme and specify `transpilePackages: ["@mitodl/smoot-design/ai"],` in the `next.config.js` file.
7 changes: 5 additions & 2 deletions src/components/AiChat/AiChat.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AiChat } from "./AiChat"
import type { AiChatProps } from "./types"
import { mockJson, mockStreaming } from "./story-utils"
import styled from "@emotion/styled"
import { fn } from "@storybook/test"

const TEST_API_STREAMING = "http://localhost:4567/streaming"
const TEST_API_JSON = "http://localhost:4567/json"
Expand All @@ -23,11 +24,11 @@ const STARTERS = [

const Container = styled.div({
width: "100%",
height: "350px",
height: "500px",
})

const meta: Meta<typeof AiChat> = {
title: "smoot-design/AiChat",
title: "smoot-design/ai/AiChat",
component: AiChat,
render: (args) => <AiChat {...args} />,
decorators: (Story) => {
Expand All @@ -41,6 +42,8 @@ const meta: Meta<typeof AiChat> = {
initialMessages: INITIAL_MESSAGES,
requestOpts: { apiUrl: TEST_API_STREAMING },
conversationStarters: STARTERS,
title: "Chat with AI",
onClose: fn(),
},
argTypes: {
conversationStarters: {
Expand Down
63 changes: 56 additions & 7 deletions src/components/AiChat/AiChat.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ jest.mock("react-markdown", () => {
}
})

const msg = {
ai: (text: string) => `Assistant said: ${text}`,
you: (text: string) => `You said: ${text}`,
}

const getMessages = (): HTMLElement[] => {
return Array.from(document.querySelectorAll(".MitAiChat--message"))
}
Expand All @@ -55,8 +60,9 @@ describe("AiChat", () => {
{ content: faker.lorem.sentence() },
{ content: faker.lorem.sentence() },
]
render(
const view = render(
<AiChat
data-testid="ai-chat"
initialMessages={initialMessages}
conversationStarters={conversationStarters}
requestOpts={{ apiUrl: "http://localhost:4567/test" }}
Expand All @@ -65,7 +71,19 @@ describe("AiChat", () => {
{ wrapper: ThemeProvider },
)

return { initialMessages, conversationStarters }
const rerender = (newProps: Partial<AiChatProps>) => {
view.rerender(
<AiChat
data-testid="ai-chat"
initialMessages={initialMessages}
conversationStarters={conversationStarters}
requestOpts={{ apiUrl: "http://localhost:4567/test" }}
{...newProps}
/>,
)
}

return { initialMessages, conversationStarters, rerender }
}

test("Clicking conversation starters and sending chats", async () => {
Expand Down Expand Up @@ -106,6 +124,25 @@ describe("AiChat", () => {
expect(afterSending[4]).toHaveTextContent("AI Response 1")
})

test("Messages persist if chat has same chatId", async () => {
const { rerender } = setup({ chatId: "test-123" })
const starterEls = getConversationStarters()
const chosen = faker.helpers.arrayElement([0, 1])

await user.click(starterEls[chosen])
await whenCount(getMessages, 3)

// New chat ... starters should be shown
rerender({ chatId: "test-345" })
expect(getConversationStarters().length).toBeGreaterThan(0)
await whenCount(getMessages, 1)

// existing chat ... starters should not be shown, messages should be restored
rerender({ chatId: "test-123" })
expect(getConversationStarters().length).toBe(0)
await whenCount(getMessages, 3)
})

test("transformBody is called before sending requests", async () => {
const fakeBody = { message: faker.lorem.sentence() }
const apiUrl = faker.internet.url()
Expand Down Expand Up @@ -151,12 +188,24 @@ describe("AiChat", () => {
await whenCount(getMessages, initialMessages.length + 4)

const messagesTexts = getMessages().map((el) => el.textContent)

expect(messagesTexts).toEqual([
initialMessages[0].content,
conversationStarters[0].content,
"Parsed: AI Response 0",
"User message",
"Parsed: AI Response 1",
msg.ai(initialMessages[0].content),
msg.you(conversationStarters[0].content),
msg.ai("Parsed: AI Response 0"),
msg.you("User message"),
msg.ai("Parsed: AI Response 1"),
])
})

test("Passes extra attributes to root", () => {
const fakeBody = { message: faker.lorem.sentence() }
const apiUrl = faker.internet.url()
const transformBody = jest.fn(() => fakeBody)
setup({
requestOpts: { apiUrl, transformBody },
parseContent: jest.fn((content) => `Parsed: ${content}`),
})
expect(screen.getByTestId("ai-chat")).toBeInTheDocument()
})
})
Loading

0 comments on commit 5945741

Please sign in to comment.