Skip to content

Commit

Permalink
AIConversation composable (#5924)
Browse files Browse the repository at this point in the history
  • Loading branch information
dbanksdesign authored Oct 22, 2024
1 parent 3a697ea commit d65cea0
Show file tree
Hide file tree
Showing 24 changed files with 311 additions and 329 deletions.
41 changes: 41 additions & 0 deletions .changeset/good-planets-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
"@aws-amplify/ui-react-ai": minor
---


The AIConversation component is now composable if you are using the default component or the headless component using `createAIConversation()`. There are 4 parts:

* Provider: provides all the necessary data/handlers for the composable components
* Messages: the message history for the conversation
* DefaultMessage: contains an optional welcome message and prompt suggestions, only shown if no messages present
* Form: the form for sending messages, includes the text input, submit button, and attachments

```jsx
function Chat() {
const [
{
data: { messages },
isLoading,
},
sendMessage,
] = useAIConversation('pirateChat');

return (
<AIConversation.Provider
messages={messages}
handleSendMessage={sendMessage}
isLoading={isLoading}
>
<Flex direction="row">
<Card variation="outlined" width="50%" flex="1">
<AIConversation.DefaultMessage />
<AIConversation.Messages />
</Card>
<Card variation="outlined" width="50%" flex="1">
<AIConversation.Form />
</Card>
</Flex>
</AIConversation.Provider>
);
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import amplifyOutputs from '@environments/ai/gen2/amplify_outputs';
export default amplifyOutputs;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from 'react';
import { Amplify } from 'aws-amplify';
import { createAIHooks, AIConversation } from '@aws-amplify/ui-react-ai';
import { generateClient } from 'aws-amplify/api';
import '@aws-amplify/ui-react/styles.css';
import '@aws-amplify/ui-react-ai/ai-conversation-styles.css';

import outputs from './amplify_outputs';
import type { Schema } from '@environments/ai/gen2/amplify/data/resource';
import { Authenticator, Card, Flex } from '@aws-amplify/ui-react';

const client = generateClient<Schema>({ authMode: 'userPool' });
const { useAIConversation } = createAIHooks(client);

Amplify.configure(outputs);

function Chat() {
const [
{
data: { messages },
isLoading,
},
sendMessage,
] = useAIConversation('pirateChat');

return (
<AIConversation.Provider
messages={messages}
handleSendMessage={sendMessage}
isLoading={isLoading}
avatars={{
user: {
username: 'XXXX',
},
}}
>
<Flex direction="row">
<Card variation="outlined" width="50%" flex="1">
<AIConversation.DefaultMessage />
<AIConversation.Messages />
</Card>
<Card variation="outlined" width="50%" flex="1">
<AIConversation.Form />
</Card>
</Flex>
</AIConversation.Provider>
);
}

export default function Example() {
return (
<Authenticator>
<Chat />
</Authenticator>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ function Chat() {
suggestedPrompts={[
{
inputText: 'hello',
header: 'hello',
component: 'hello',
},
{
inputText: 'how are you?',
header: 'how are you?',
component: 'how are you?',
},
]}
variant="bubble"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ function Chat() {
suggestedPrompts={[
{
inputText: 'hello',
header: 'hello',
component: 'hello',
},
{
inputText: 'how are you?',
header: 'how are you?',
component: 'how are you?',
},
]}
variant="bubble"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,14 @@ export default function Example() {

return (
<Authenticator>
{({ user, signOut }) => {
return (
<>
<h1>Hello {user.username}</h1>
<AIConversation
avatars={AVATARS}
messages={messages}
handleSendMessage={sendMessage}
/>
<AIConversation.Controls.Messages.Message.Container
className={'whateveriwant'}
/>
</>
);
}}
<AIConversation.Provider
avatars={AVATARS}
messages={messages}
handleSendMessage={sendMessage}
>
<AIConversation.Messages />
<AIConversation.Form />
</AIConversation.Provider>
</Authenticator>
);
}

// export default function Example() {
// return <div>hello world</div>;
// }
64 changes: 40 additions & 24 deletions packages/react-ai/src/components/AIConversation/AIConversation.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
import * as React from 'react';
import { Flex, ScrollView, Text, TextProps } from '@aws-amplify/ui-react';
import { Flex, ScrollView, Text } from '@aws-amplify/ui-react';
import {
IconAssistant,
IconUser,
useIcons,
} from '@aws-amplify/ui-react/internal';
import { AIConversationInput, AIConversationProps, Avatars } from './types';
import {
AIConversation as AIConversationType,
AIConversationInput,
AIConversationProps,
Avatars,
} from './types';
import { MessagesControl } from './views/Controls/MessagesControl';
import { FieldControl } from './views';
import { FormControl } from './views/Controls/FormControl';
import { MessageList } from './views/default/MessageList';
import { Form } from './views/default/Form';
import { PromptList } from './views/default/PromptList';
import { AutoHidablePromptControl } from './views/Controls';
import { ComponentClassName } from '@aws-amplify/ui';
import { AIConversationProvider } from './AIConversationProvider';
import {
AIConversationProvider,
AIConversationProviderProps,
} from './AIConversationProvider';
import { useSetUserAgent } from '@aws-amplify/ui-react-core';
import { VERSION } from '../../version';
import { DefaultMessageControl } from './views/Controls/DefaultMessageControl';

interface AIConversationBaseProps
extends AIConversationProps,
AIConversationInput {}

function AIConversationBase({
function Provider({
actions,
avatars,
controls,
Expand All @@ -34,12 +38,14 @@ function AIConversationBase({
displayText,
allowAttachments,
messageRenderer,
}: AIConversationBaseProps): JSX.Element {
children,
}: AIConversationProviderProps): JSX.Element {
useSetUserAgent({
componentName: 'AIConversation',
packageName: 'react-ai',
version: VERSION,
});

const icons = useIcons('aiConversation');
const defaultAvatars: Avatars = {
ai: {
Expand All @@ -61,11 +67,7 @@ function AIConversationBase({
},
isLoading,
elements: {
Text: React.forwardRef<HTMLParagraphElement, TextProps>(
function _Text(props, ref) {
return <Text {...props} ref={ref} />;
}
),
Text,
},
actions,
suggestedPrompts,
Expand All @@ -84,22 +86,36 @@ function AIConversationBase({

return (
<AIConversationProvider {...providerProps}>
{children}
</AIConversationProvider>
);
}

interface AIConversationBaseProps
extends AIConversationProps,
AIConversationInput {}

function AIConversationBase(props: AIConversationBaseProps): JSX.Element {
return (
<Provider {...props}>
<Flex className={ComponentClassName.AIConversation}>
<ScrollView autoScroll="smooth" flex="1">
<AutoHidablePromptControl />
<DefaultMessageControl />
<MessagesControl />
</ScrollView>
<FieldControl />
<FormControl />
</Flex>
</AIConversationProvider>
</Provider>
);
}

/**
* @experimental
*/
export const AIConversation = Object.assign(AIConversationBase, {
MessageList,
PromptList,
Form,
});
export const AIConversation: AIConversationType<AIConversationBaseProps> =
Object.assign(AIConversationBase, {
Provider,
DefaultMessage: DefaultMessageControl,
Messages: MessagesControl,
Form: FormControl,
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,31 @@ import {
LoadingContextProvider,
ResponseComponentsProvider,
SendMessageContextProvider,
WelcomeMessageProvider,
} from './context';
import { AttachmentProvider } from './context/AttachmentContext';

interface AIConversationProviderProps
export interface AIConversationProviderProps
extends AIConversationInput,
AIConversationProps {
children?: React.ReactNode;
}

export const AIConversationProvider = ({
elements,
actions,
suggestedPrompts,
responseComponents,
variant,
allowAttachments,
avatars,
children,
controls,
displayText,
allowAttachments,
messages,
elements,
handleSendMessage,
avatars,
isLoading,
children,
messages,
responseComponents,
suggestedPrompts,
variant,
welcomeMessage,
}: AIConversationProviderProps): React.JSX.Element => {
const _displayText = {
...defaultAIConversationDisplayTextEn,
Expand All @@ -48,29 +50,31 @@ export const AIConversationProvider = ({
<ElementsProvider elements={elements}>
<ControlsProvider controls={controls}>
<SuggestedPromptProvider suggestedPrompts={suggestedPrompts}>
<ResponseComponentsProvider responseComponents={responseComponents}>
<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>
<WelcomeMessageProvider welcomeMessage={welcomeMessage}>
<ResponseComponentsProvider responseComponents={responseComponents}>
<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>
</WelcomeMessageProvider>
</SuggestedPromptProvider>
</ControlsProvider>
</ElementsProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as React from 'react';

type WelcomeMessageContextProps = React.ReactNode | undefined;

export const WelcomeMessageContext =
React.createContext<WelcomeMessageContextProps>(undefined);

export const WelcomeMessageProvider = ({
children,
welcomeMessage,
}: {
children?: React.ReactNode;
welcomeMessage?: React.ReactNode;
}): JSX.Element => {
return (
<WelcomeMessageContext.Provider value={welcomeMessage}>
{children}
</WelcomeMessageContext.Provider>
);
};
Loading

0 comments on commit d65cea0

Please sign in to comment.