Skip to content

Commit

Permalink
feat(ai-conversation): add allowAttachments prop (#5802)
Browse files Browse the repository at this point in the history
Co-authored-by: Scott Rees <[email protected]>
  • Loading branch information
dbanksdesign and reesscot authored Sep 30, 2024
1 parent 22e285f commit 8b4a28b
Show file tree
Hide file tree
Showing 26 changed files with 217 additions and 99 deletions.
15 changes: 15 additions & 0 deletions .changeset/seven-bats-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"@aws-amplify/ui-react-ai": minor
---

feat(ai-conversation): add allowAttachments prop

BREAKING - This is a breaking change to an experimental API. Previously, the AIConversation component always allowed attachments. Now you will need to provide the `allowAttachments` prop to get the same behavior. The reason for this change is that attachments can quickly cost a lot based on the token use and we didn't want the default behavior to have that.

```jsx
<AIConversation
allowAttachments
messages={messages}
handleSendMessage={handleSendMessage}
/>
```
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function Chat() {
messages={messages}
handleSendMessage={sendMessage}
isLoading={isLoading}
allowAttachments
suggestedPrompts={[
{
inputText: 'hello',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function AIConversationBase({
variant,
isLoading,
displayText,
allowAttachments,
}: AIConversationBaseProps): JSX.Element {
const icons = useIcons('aiConversation');
const defaultAvatars: Avatars = {
Expand Down Expand Up @@ -72,6 +73,7 @@ function AIConversationBase({
...avatars,
},
isLoading,
allowAttachments,
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react';

export const AttachmentContext = React.createContext(false);

export const AttachmentProvider = ({
children,
allowAttachments,
}: {
children?: React.ReactNode;
allowAttachments?: boolean;
}): JSX.Element => {
return (
<AttachmentContext.Provider value={allowAttachments ?? false}>
{children}
</AttachmentContext.Provider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface ControlsContextProps {
Form?: React.ComponentType<
{
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
allowAttachments?: boolean;
} & Required<ConversationInputContext>
>;
MessageList?: React.ComponentType<{ messages: ConversationMessage[] }>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function createAIConversation(input: AIConversationInput = {}): {
variant,
controls,
displayText,
allowAttachments,
} = input;

const Provider = createProvider({
Expand All @@ -40,6 +41,7 @@ export function createAIConversation(input: AIConversationInput = {}): {
variant,
controls,
displayText,
allowAttachments,
});

function AIConversation(props: AIConversationProps): JSX.Element {
Expand Down
43 changes: 24 additions & 19 deletions packages/react-ai/src/components/AIConversation/createProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ResponseComponentsProvider,
SendMessageContextProvider,
} from './context';
import { AttachmentProvider } from './context/AttachmentContext';

export default function createProvider({
elements,
Expand All @@ -26,6 +27,7 @@ export default function createProvider({
variant,
controls,
displayText,
allowAttachments,
}: Pick<
AIConversationInput,
| 'elements'
Expand All @@ -35,6 +37,7 @@ export default function createProvider({
| 'variant'
| 'controls'
| 'displayText'
| 'allowAttachments'
>) {
return function Provider({
children,
Expand All @@ -57,25 +60,27 @@ export default function createProvider({
<ControlsProvider controls={controls}>
<SuggestedPromptProvider suggestedPrompts={suggestedPrompts}>
<ResponseComponentsProvider responseComponents={responseComponents}>
<ConversationDisplayTextProvider {..._displayText}>
<ConversationInputContextProvider>
<SendMessageContextProvider
handleSendMessage={handleSendMessage}
>
<AvatarsProvider avatars={avatars}>
<ActionsProvider actions={actions}>
<MessageVariantProvider variant={variant}>
<MessagesProvider messages={messages}>
<LoadingContextProvider isLoading={isLoading}>
{children}
</LoadingContextProvider>
</MessagesProvider>
</MessageVariantProvider>
</ActionsProvider>
</AvatarsProvider>
</SendMessageContextProvider>
</ConversationInputContextProvider>
</ConversationDisplayTextProvider>
<AttachmentProvider allowAttachments={allowAttachments}>
<ConversationDisplayTextProvider {..._displayText}>
<ConversationInputContextProvider>
<SendMessageContextProvider
handleSendMessage={handleSendMessage}
>
<AvatarsProvider avatars={avatars}>
<ActionsProvider actions={actions}>
<MessageVariantProvider variant={variant}>
<MessagesProvider messages={messages}>
<LoadingContextProvider isLoading={isLoading}>
{children}
</LoadingContextProvider>
</MessagesProvider>
</MessageVariantProvider>
</ActionsProvider>
</AvatarsProvider>
</SendMessageContextProvider>
</ConversationInputContextProvider>
</ConversationDisplayTextProvider>
</AttachmentProvider>
</ResponseComponentsProvider>
</SuggestedPromptProvider>
</ControlsProvider>
Expand Down
1 change: 1 addition & 0 deletions packages/react-ai/src/components/AIConversation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export interface AIConversationInput {
responseComponents?: ResponseComponents;
variant?: MessageVariant;
controls?: ControlsContextProps;
allowAttachments?: boolean;
}

export interface AIConversationProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from 'react';
import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
import { ConversationInputContext } from '../../context';
import { AIConversationElements } from '../../context/elements';
import { useDropZone } from '@aws-amplify/ui-react-core';

const { Button, Icon, View } = AIConversationElements;

Expand Down Expand Up @@ -32,6 +33,17 @@ const AttachFileButton = withBaseElementProps(Button, {
export const AttachFileControl: AttachFileControl = () => {
const hiddenInput = React.useRef<HTMLInputElement>(null);
const { setInput } = React.useContext(ConversationInputContext);
const { dragState, ...dropHandlers } = useDropZone({
acceptedFileTypes: ['.jpeg'],
onDropComplete: ({ acceptedFiles }) => {
if (acceptedFiles && acceptedFiles?.length > 0 && setInput) {
setInput((prevInput) => ({
...prevInput,
files: [...(prevInput?.files ?? []), ...acceptedFiles],
}));
}
},
});

function handleButtonClick() {
if (hiddenInput.current) {
Expand All @@ -53,7 +65,7 @@ export const AttachFileControl: AttachFileControl = () => {
}

return (
<AttachFileContainer>
<AttachFileContainer {...dropHandlers}>
<AttachFileButton onClick={handleButtonClick}>
<AttachFileIcon />
</AttachFileButton>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { ControlsContext } from '../../context/ControlsContext';
import { getImageTypeFromMimeType } from '../../utils';
import { LoadingContext } from '../../context/LoadingContext';
import { AttachmentContext } from '../../context/AttachmentContext';

const {
Button,
Expand Down Expand Up @@ -148,6 +149,7 @@ const InputContainer = withBaseElementProps(View, {
export const FieldControl: FieldControl = () => {
const { input, setInput } = React.useContext(ConversationInputContext);
const handleSendMessage = React.useContext(SendMessageContext);
const allowAttachments = React.useContext(AttachmentContext);
const ref = React.useRef<HTMLFormElement | null>(null);
const responseComponents = React.useContext(ResponseComponentsContext);
const controls = React.useContext(ControlsContext);
Expand Down Expand Up @@ -212,6 +214,7 @@ export const FieldControl: FieldControl = () => {
handleSubmit={handleSubmit}
input={input!}
setInput={setInput!}
allowAttachments={allowAttachments}
/>
);
}
Expand All @@ -223,7 +226,7 @@ export const FieldControl: FieldControl = () => {
method="post"
ref={ref}
>
<AttachFileControl />
{allowAttachments ? <AttachFileControl /> : null}
<InputContainer>
<VisuallyHidden>
<Label />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { FieldControl } from '../FieldControl';
import { ConversationInputContextProvider } from '../../../context/ConversationInputContext';
import userEvent from '@testing-library/user-event';
import { SendMessageContextProvider } from '../../../context/SendMessageContext';
import { AttachmentProvider } from '../../../context/AttachmentContext';

describe('FieldControl', () => {
it('renders a FieldControl component with the correct elements', () => {
const result = render(<FieldControl />);
const result = render(
<AttachmentProvider allowAttachments>
<FieldControl />
</AttachmentProvider>
);
expect(result.container).toBeDefined();

const form = screen.findByRole('form');
Expand All @@ -23,7 +28,11 @@ describe('FieldControl', () => {
});

it('renders FieldControl with the correct accessibility roles', () => {
render(<FieldControl />);
render(
<AttachmentProvider allowAttachments>
<FieldControl />
</AttachmentProvider>
);

const actionButtons = screen.getAllByRole('button');
const sendButton = actionButtons[1];
Expand Down
Loading

0 comments on commit 8b4a28b

Please sign in to comment.