Skip to content

Commit

Permalink
chore: Update markdown component + create avatar component + refactor (
Browse files Browse the repository at this point in the history
…#36832)

/ok-to-test tags="@tag.Anvil"

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
	- Introduced the `Avatar` component for displaying user avatars.
- Added the `Markdown` component for rendering Markdown content with
GitHub Flavored Markdown support.
- Enhanced `AIChat` component with a new modular input system and
improved message display.

- **Bug Fixes**
	- Updated CSS for better styling consistency across components.

- **Refactor**
- Removed outdated components (`ChatDescriptionModal` and
`ThreadMessage`) to streamline the codebase.
	- Simplified the structure of `AIChat` by incorporating new components.

- **Documentation**
- Updated Storybook stories for `AIChat`, `Avatar`, and `Markdown`
components to showcase new features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/11320929997>
> Commit: db2bc5c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=11320929997&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Anvil`
> Spec:
> <hr>Mon, 14 Oct 2024 04:42:54 UTC
<!-- end of auto-generated comment: Cypress test results  -->
  • Loading branch information
jsartisan authored Oct 14, 2024
1 parent ef5a253 commit a7bf302
Show file tree
Hide file tree
Showing 52 changed files with 1,011 additions and 465 deletions.
5 changes: 5 additions & 0 deletions app/client/packages/design-system/widgets/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const esModules = ["remark-gfm"].join("|");

module.exports = {
preset: "ts-jest",
roots: ["<rootDir>/src"],
Expand All @@ -6,6 +8,9 @@ module.exports = {
moduleNameMapper: {
"\\.(css)$": "<rootDir>../../../test/__mocks__/styleMock.js",
},
transformIgnorePatterns: [
`[/\\\\]node_modules[/\\\\](?!${esModules}).+\\.(js|jsx|mjs|cjs|ts|tsx)$`,
],
globals: {
"ts-jest": {
useESM: true,
Expand Down
3 changes: 2 additions & 1 deletion app/client/packages/design-system/widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"lodash": "*",
"react-aria-components": "^1.2.1",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0"
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^4.0.0"
},
"devDependencies": {
"@types/fs-extra": "^11.0.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { Button, Flex, Text, TextArea } from "@appsmith/wds";
import type { FormEvent, ForwardedRef, KeyboardEvent } from "react";
import React, { forwardRef, useCallback } from "react";
import { ChatDescriptionModal } from "./ChatDescriptionModal";
import { ChatTitle } from "./ChatTitle";
import styles from "./styles.module.css";
import { ThreadMessage } from "./ThreadMessage";
import type { AIChatProps, ChatMessage } from "./types";
import { UserAvatar } from "./UserAvatar";
import type { ForwardedRef } from "react";
import React, { forwardRef } from "react";

const MIN_PROMPT_LENGTH = 3;
import styles from "./styles.module.css";
import { ChatHeader } from "./ChatHeader";
import { ChatThread } from "./ChatThread";
import type { AIChatProps } from "./types";
import { ChatInputSection } from "./ChatInputSection";

const _AIChat = (props: AIChatProps, ref: ForwardedRef<HTMLDivElement>) => {
const {
Expand All @@ -25,81 +22,28 @@ const _AIChat = (props: AIChatProps, ref: ForwardedRef<HTMLDivElement>) => {
username,
...rest
} = props;
const [isChatDescriptionModalOpen, setIsChatDescriptionModalOpen] =
React.useState(false);

const handleFormSubmit = useCallback(
(event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
onSubmit?.();
},
[onSubmit],
);

const handlePromptInputKeyDown = useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === "Enter" && event.shiftKey) {
event.preventDefault();
onSubmit?.();
}
},
[onSubmit],
);

return (
<div className={styles.root} ref={ref} {...rest}>
<ChatDescriptionModal
isOpen={isChatDescriptionModalOpen}
setOpen={() =>
setIsChatDescriptionModalOpen(!isChatDescriptionModalOpen)
}
>
{chatDescription}
</ChatDescriptionModal>

<div className={styles.header}>
<Flex alignItems="center" gap="spacing-2">
<ChatTitle title={chatTitle} />
<Button
icon="info-square-rounded"
onPress={() => setIsChatDescriptionModalOpen(true)}
variant="ghost"
/>
</Flex>

<Flex alignItems="center" gap="spacing-2">
<UserAvatar username={username} />
<Text data-testid="t--aichat-username" size="body">
{username}
</Text>
</Flex>
</div>

<ul className={styles.thread} data-testid="t--aichat-thread">
{thread.map((message: ChatMessage) => (
<ThreadMessage
{...message}
key={message.id}
onApplyAssistantSuggestion={onApplyAssistantSuggestion}
username={username}
/>
))}
</ul>

<form className={styles.promptForm} onSubmit={handleFormSubmit}>
<TextArea
// TODO: Handle isWaitingForResponse: true state
isDisabled={isWaitingForResponse}
name="prompt"
onChange={onPromptChange}
onKeyDown={handlePromptInputKeyDown}
placeholder={promptInputPlaceholder}
value={prompt}
/>
<Button isDisabled={prompt.length < MIN_PROMPT_LENGTH} type="submit">
Send
</Button>
</form>
<ChatHeader
chatDescription={chatDescription}
chatTitle={chatTitle}
username={username}
/>

<ChatThread
onApplyAssistantSuggestion={onApplyAssistantSuggestion}
thread={thread}
username={username}
/>

<ChatInputSection
isWaitingForResponse={isWaitingForResponse}
onPromptChange={onPromptChange}
onSubmit={onSubmit}
prompt={prompt}
promptInputPlaceholder={promptInputPlaceholder}
/>
</div>
);
};
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useState } from "react";
import {
Avatar,
Button,
Flex,
Modal,
ModalBody,
ModalContent,
ModalHeader,
Text,
} from "@appsmith/wds";

import styles from "./styles.module.css";

// this value might come from props in future. So keeping a temporary value here.
const LOGO =
"https://app.appsmith.com/static/media/appsmith_logo_square.3867b1959653dabff8dc.png";

export const ChatHeader: React.FC<{
chatTitle?: string;
username: string;
chatDescription?: string;
}> = ({ chatDescription, chatTitle, username }) => {
const [isChatDescriptionModalOpen, setIsChatDescriptionModalOpen] =
useState(false);

return (
<>
<div className={styles.header}>
<Flex alignItems="center" gap="spacing-2">
<Flex alignItems="center" gap="spacing-3">
<Avatar label="Appsmith AI" size="large" src={LOGO} />
<Text fontWeight={600} size="subtitle">
{chatTitle}
</Text>
</Flex>
<Button
icon="info-square-rounded"
onPress={() => setIsChatDescriptionModalOpen(true)}
variant="ghost"
/>
</Flex>
<Flex alignItems="center" gap="spacing-2">
<Avatar label={username} />
<Text data-testid="t--aichat-username" size="body">
{username}
</Text>
</Flex>
</div>

<Modal
isOpen={isChatDescriptionModalOpen}
setOpen={setIsChatDescriptionModalOpen}
>
<ModalContent>
<ModalHeader title="Information about the bot" />
<ModalBody>{chatDescription}</ModalBody>
</ModalContent>
</Modal>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react";
import { Flex, ChatInput, Icon, Text } from "@appsmith/wds";

const MIN_PROMPT_LENGTH = 3;

export const ChatInputSection: React.FC<{
isWaitingForResponse: boolean;
prompt: string;
promptInputPlaceholder?: string;
onPromptChange: (value: string) => void;
onSubmit?: () => void;
}> = ({
isWaitingForResponse,
onPromptChange,
onSubmit,
prompt,
promptInputPlaceholder,
}) => (
<Flex
direction="column"
gap="spacing-3"
paddingBottom="spacing-4"
paddingLeft="spacing-6"
paddingRight="spacing-6"
paddingTop="spacing-4"
>
<ChatInput
isLoading={isWaitingForResponse}
isSubmitDisabled={prompt.length < MIN_PROMPT_LENGTH}
onChange={onPromptChange}
onSubmit={onSubmit}
placeholder={promptInputPlaceholder}
value={prompt}
/>
<Flex
alignItems="center"
flexGrow={1}
gap="spacing-1"
justifyContent="center"
>
<Icon name="alert-circle" size="small" />
<Text color="neutral" size="caption" textAlign="center">
LLM assistant can make mistakes. Answers should be verified before they
are trusted.
</Text>
</Flex>
</Flex>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from "react";
import { Avatar, Flex, Markdown } from "@appsmith/wds";

import styles from "./styles.module.css";
import type { ChatMessage } from "./types";
import { AssistantSuggestionButton } from "./AssistantSuggestionButton";

export const ChatThread: React.FC<{
thread: ChatMessage[];
onApplyAssistantSuggestion?: (suggestion: string) => void;
username: string;
}> = ({ onApplyAssistantSuggestion, thread, username }) => (
<Flex direction="column" gap="spacing-3" padding="spacing-6">
{thread.map((message: ChatMessage) => {
const { content, isAssistant, promptSuggestions = [] } = message;

return (
<Flex direction={isAssistant ? "row" : "row-reverse"} key={message.id}>
{isAssistant && (
<div>
<Markdown>{content}</Markdown>

{promptSuggestions.length > 0 && (
<Flex
className={styles.suggestions}
gap="spacing-5"
paddingTop="spacing-4"
wrap="wrap"
>
{promptSuggestions.map((suggestion) => (
<AssistantSuggestionButton
key={suggestion}
// eslint-disable-next-line react-perf/jsx-no-new-function-as-prop
onPress={() => onApplyAssistantSuggestion?.(suggestion)}
>
{suggestion}
</AssistantSuggestionButton>
))}
</Flex>
)}
</div>
)}
{!isAssistant && (
<Flex direction="row-reverse" gap="spacing-3">
<Avatar label={username} />
<div>{content}</div>
</Flex>
)}
</Flex>
);
})}
</Flex>
);

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit a7bf302

Please sign in to comment.