@aws-amplify/ui-react-ai 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/esm/components/AIConversation/AIConversation.mjs +8 -26
  2. package/dist/esm/components/AIConversation/AIConversationProvider.mjs +20 -17
  3. package/dist/esm/components/AIConversation/context/AIContextContext.mjs +8 -0
  4. package/dist/esm/components/AIConversation/context/FallbackComponentContext.mjs +8 -0
  5. package/dist/esm/components/AIConversation/context/ResponseComponentsContext.mjs +6 -2
  6. package/dist/esm/components/AIConversation/createAIConversation.mjs +2 -5
  7. package/dist/esm/components/AIConversation/views/Controls/ActionsBarControl.mjs +3 -2
  8. package/dist/esm/components/AIConversation/views/Controls/AttachFileControl.mjs +2 -0
  9. package/dist/esm/components/AIConversation/views/Controls/AttachmentListControl.mjs +2 -0
  10. package/dist/esm/components/AIConversation/views/Controls/AvatarControl.mjs +2 -0
  11. package/dist/esm/components/AIConversation/views/Controls/DefaultMessageControl.mjs +2 -0
  12. package/dist/esm/components/AIConversation/views/Controls/FormControl.mjs +11 -4
  13. package/dist/esm/components/AIConversation/views/Controls/MessagesControl.mjs +24 -31
  14. package/dist/esm/components/AIConversation/views/Controls/PromptControl.mjs +2 -0
  15. package/dist/esm/components/AIConversation/views/default/Form.mjs +4 -5
  16. package/dist/esm/components/AIConversation/views/default/MessageList.mjs +31 -16
  17. package/dist/esm/hooks/contentFromEvents.mjs +22 -0
  18. package/dist/esm/hooks/createAIHooks.mjs +0 -3
  19. package/dist/esm/hooks/exhaustivelyListMessages.mjs +19 -0
  20. package/dist/esm/hooks/shared.mjs +14 -0
  21. package/dist/esm/hooks/useAIConversation.mjs +246 -106
  22. package/dist/esm/hooks/useAIGeneration.mjs +1 -8
  23. package/dist/esm/index.mjs +0 -1
  24. package/dist/esm/version.mjs +1 -1
  25. package/dist/index.js +396 -236
  26. package/dist/types/components/AIConversation/AIConversation.d.ts +0 -3
  27. package/dist/types/components/AIConversation/AIConversationProvider.d.ts +1 -1
  28. package/dist/types/components/AIConversation/context/AIContextContext.d.ts +6 -0
  29. package/dist/types/components/AIConversation/context/ControlsContext.d.ts +1 -0
  30. package/dist/types/components/AIConversation/context/FallbackComponentContext.d.ts +7 -0
  31. package/dist/types/components/AIConversation/context/ResponseComponentsContext.d.ts +2 -2
  32. package/dist/types/components/AIConversation/context/elements/definitions.d.ts +1 -1
  33. package/dist/types/components/AIConversation/context/index.d.ts +2 -0
  34. package/dist/types/components/AIConversation/createAIConversation.d.ts +0 -3
  35. package/dist/types/components/AIConversation/index.d.ts +2 -1
  36. package/dist/types/components/AIConversation/types.d.ts +4 -24
  37. package/dist/types/components/AIConversation/views/Controls/MessagesControl.d.ts +1 -5
  38. package/dist/types/components/AIConversation/views/default/Form.d.ts +1 -1
  39. package/dist/types/components/AIConversation/views/default/MessageList.d.ts +1 -1
  40. package/dist/types/components/AIConversation/views/default/PromptList.d.ts +1 -1
  41. package/dist/types/hooks/contentFromEvents.d.ts +2 -0
  42. package/dist/types/hooks/createAIHooks.d.ts +0 -3
  43. package/dist/types/hooks/exhaustivelyListMessages.d.ts +8 -0
  44. package/dist/types/hooks/index.d.ts +1 -2
  45. package/dist/types/hooks/shared.d.ts +23 -0
  46. package/dist/types/hooks/useAIConversation.d.ts +6 -4
  47. package/dist/types/hooks/useAIGeneration.d.ts +3 -13
  48. package/dist/types/index.d.ts +1 -1
  49. package/dist/types/types.d.ts +32 -1
  50. package/dist/types/version.d.ts +1 -1
  51. package/package.json +4 -4
  52. package/dist/ai-conversation-styles.css +0 -195
  53. package/dist/ai-conversation-styles.js +0 -2
  54. package/dist/esm/hooks/AIContextProvider.mjs +0 -20
  55. package/dist/types/ai-conversation-styles.d.ts +0 -1
  56. package/dist/types/hooks/AIContextProvider.d.ts +0 -17
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { Text, Flex, ScrollView } from '@aws-amplify/ui-react';
2
+ import { Flex, ScrollView } from '@aws-amplify/ui-react';
3
3
  import { useIcons, IconAssistant, IconUser } from '@aws-amplify/ui-react/internal';
4
4
  import { MessagesControl } from './views/Controls/MessagesControl.mjs';
5
5
  import { FormControl } from './views/Controls/FormControl.mjs';
@@ -12,7 +12,7 @@ import { useSetUserAgent } from '@aws-amplify/ui-react-core';
12
12
  import { VERSION } from '../../version.mjs';
13
13
  import { DefaultMessageControl } from './views/Controls/DefaultMessageControl.mjs';
14
14
 
15
- function Provider({ actions, avatars, controls, handleSendMessage, messages, responseComponents, suggestedPrompts, variant, isLoading, displayText, allowAttachments, messageRenderer, children, }) {
15
+ function AIConversationBase({ avatars, controls, ...rest }) {
16
16
  useSetUserAgent({
17
17
  componentName: 'AIConversation',
18
18
  packageName: 'react-ai',
@@ -22,53 +22,35 @@ function Provider({ actions, avatars, controls, handleSendMessage, messages, res
22
22
  const defaultAvatars = {
23
23
  ai: {
24
24
  username: 'Assistant',
25
- avatar: icons?.assistant ?? React.createElement(IconAssistant, null),
25
+ avatar: icons?.assistant ?? React.createElement(IconAssistant, { testId: "icon-assistant" }),
26
26
  },
27
27
  user: {
28
28
  username: 'User',
29
- avatar: icons?.user ?? React.createElement(IconUser, null),
29
+ avatar: icons?.user ?? React.createElement(IconUser, { testId: "icon-user" }),
30
30
  },
31
31
  };
32
32
  const providerProps = {
33
- messages,
34
- handleSendMessage,
33
+ ...rest,
35
34
  avatars: {
36
35
  ...defaultAvatars,
37
36
  ...avatars,
38
37
  },
39
- isLoading,
40
- elements: {
41
- Text,
42
- },
43
- actions,
44
- suggestedPrompts,
45
- responseComponents,
46
- variant,
47
38
  controls: {
48
39
  MessageList,
49
40
  PromptList,
50
41
  Form,
51
42
  ...controls,
52
43
  },
53
- displayText,
54
- allowAttachments,
55
- messageRenderer,
56
44
  };
57
- return (React.createElement(AIConversationProvider, { ...providerProps }, children));
58
- }
59
- function AIConversationBase(props) {
60
- return (React.createElement(Provider, { ...props },
61
- React.createElement(Flex, { className: ComponentClassName.AIConversation },
45
+ return (React.createElement(AIConversationProvider, { ...providerProps },
46
+ React.createElement(Flex, { className: ComponentClassName.AIConversation, testId: "ai-conversation" },
62
47
  React.createElement(ScrollView, { autoScroll: "smooth", flex: "1" },
63
48
  React.createElement(DefaultMessageControl, null),
64
49
  React.createElement(MessagesControl, null)),
65
50
  React.createElement(FormControl, null))));
66
51
  }
67
- /**
68
- * @experimental
69
- */
70
52
  const AIConversation = Object.assign(AIConversationBase, {
71
- Provider,
53
+ Provider: AIConversationProvider,
72
54
  DefaultMessage: DefaultMessageControl,
73
55
  Messages: MessagesControl,
74
56
  Form: FormControl,
@@ -1,6 +1,6 @@
1
1
  import React__default from 'react';
2
- import { ElementsProvider } from '@aws-amplify/ui-react-core/elements';
3
2
  import { defaultAIConversationDisplayTextEn } from './displayText.mjs';
3
+ import { AIContextProvider } from './context/AIContextContext.mjs';
4
4
  import { ActionsProvider } from './context/ActionsContext.mjs';
5
5
  import { AvatarsProvider } from './context/AvatarsContext.mjs';
6
6
  import { ConversationInputContextProvider } from './context/ConversationInputContext.mjs';
@@ -12,30 +12,33 @@ import { ControlsProvider } from './context/ControlsContext.mjs';
12
12
  import { LoadingContextProvider } from './context/LoadingContext.mjs';
13
13
  import { ResponseComponentsProvider } from './context/ResponseComponentsContext.mjs';
14
14
  import { SendMessageContextProvider } from './context/SendMessageContext.mjs';
15
- import './context/MessageRenderContext.mjs';
15
+ import { MessageRendererProvider } from './context/MessageRenderContext.mjs';
16
16
  import { AttachmentProvider } from './context/AttachmentContext.mjs';
17
17
  import { WelcomeMessageProvider } from './context/WelcomeMessageContext.mjs';
18
+ import { FallbackComponentProvider } from './context/FallbackComponentContext.mjs';
18
19
  import './context/elements/definitions.mjs';
19
20
 
20
- const AIConversationProvider = ({ actions, allowAttachments, avatars, children, controls, displayText, elements, handleSendMessage, isLoading, messages, responseComponents, suggestedPrompts, variant, welcomeMessage, }) => {
21
+ const AIConversationProvider = ({ aiContext, actions, allowAttachments, avatars, children, controls, displayText, handleSendMessage, isLoading, messages, messageRenderer, responseComponents, suggestedPrompts, variant, welcomeMessage, FallbackResponseComponent, }) => {
21
22
  const _displayText = {
22
23
  ...defaultAIConversationDisplayTextEn,
23
24
  ...displayText,
24
25
  };
25
- return (React__default.createElement(ElementsProvider, { elements: elements },
26
- React__default.createElement(ControlsProvider, { controls: controls },
27
- React__default.createElement(SuggestedPromptProvider, { suggestedPrompts: suggestedPrompts },
28
- React__default.createElement(WelcomeMessageProvider, { welcomeMessage: welcomeMessage },
29
- React__default.createElement(ResponseComponentsProvider, { responseComponents: responseComponents },
30
- React__default.createElement(AttachmentProvider, { allowAttachments: allowAttachments },
31
- React__default.createElement(ConversationDisplayTextProvider, { ..._displayText },
32
- React__default.createElement(ConversationInputContextProvider, null,
33
- React__default.createElement(SendMessageContextProvider, { handleSendMessage: handleSendMessage },
34
- React__default.createElement(AvatarsProvider, { avatars: avatars },
35
- React__default.createElement(ActionsProvider, { actions: actions },
36
- React__default.createElement(MessageVariantProvider, { variant: variant },
37
- React__default.createElement(MessagesProvider, { messages: messages },
38
- React__default.createElement(LoadingContextProvider, { isLoading: isLoading }, children)))))))))))))));
26
+ return (React__default.createElement(ControlsProvider, { controls: controls },
27
+ React__default.createElement(SuggestedPromptProvider, { suggestedPrompts: suggestedPrompts },
28
+ React__default.createElement(WelcomeMessageProvider, { welcomeMessage: welcomeMessage },
29
+ React__default.createElement(FallbackComponentProvider, { FallbackComponent: FallbackResponseComponent },
30
+ React__default.createElement(MessageRendererProvider, { ...messageRenderer },
31
+ React__default.createElement(ResponseComponentsProvider, { responseComponents: responseComponents },
32
+ React__default.createElement(AttachmentProvider, { allowAttachments: allowAttachments },
33
+ React__default.createElement(ConversationDisplayTextProvider, { ..._displayText },
34
+ React__default.createElement(ConversationInputContextProvider, null,
35
+ React__default.createElement(SendMessageContextProvider, { handleSendMessage: handleSendMessage },
36
+ React__default.createElement(AvatarsProvider, { avatars: avatars },
37
+ React__default.createElement(ActionsProvider, { actions: actions },
38
+ React__default.createElement(MessageVariantProvider, { variant: variant },
39
+ React__default.createElement(MessagesProvider, { messages: messages },
40
+ React__default.createElement(AIContextProvider, { aiContext: aiContext },
41
+ React__default.createElement(LoadingContextProvider, { isLoading: isLoading }, children)))))))))))))))));
39
42
  };
40
43
 
41
44
  export { AIConversationProvider };
@@ -0,0 +1,8 @@
1
+ import React__default from 'react';
2
+
3
+ const AIContextContext = React__default.createContext(undefined);
4
+ const AIContextProvider = ({ children, aiContext, }) => {
5
+ return (React__default.createElement(AIContextContext.Provider, { value: aiContext }, children));
6
+ };
7
+
8
+ export { AIContextContext, AIContextProvider };
@@ -0,0 +1,8 @@
1
+ import React__default from 'react';
2
+
3
+ const FallbackComponentContext = React__default.createContext(undefined);
4
+ const FallbackComponentProvider = ({ children, FallbackComponent, }) => {
5
+ return (React__default.createElement(FallbackComponentContext.Provider, { value: FallbackComponent }, children));
6
+ };
7
+
8
+ export { FallbackComponentContext, FallbackComponentProvider };
@@ -21,8 +21,12 @@ const convertResponseComponentsToToolConfiguration = (responseComponents) => {
21
21
  const { props } = responseComponents[toolName];
22
22
  const requiredProps = [];
23
23
  Object.keys(props).forEach((propName) => {
24
- if (props[propName].required)
24
+ if (props[propName].required) {
25
25
  requiredProps.push(propName);
26
+ // The inputSchema for a tool needs to not
27
+ // have `required` in the properties
28
+ props[propName].required = undefined;
29
+ }
26
30
  });
27
31
  tools[toolName] = {
28
32
  description: responseComponents[toolName].description,
@@ -40,4 +44,4 @@ const convertResponseComponentsToToolConfiguration = (responseComponents) => {
40
44
  return { tools };
41
45
  };
42
46
 
43
- export { RESPONSE_COMPONENT_PREFIX, ResponseComponentsContext, ResponseComponentsProvider, convertResponseComponentsToToolConfiguration };
47
+ export { RESPONSE_COMPONENT_PREFIX, ResponseComponentsContext, ResponseComponentsProvider, convertResponseComponentsToToolConfiguration, prependResponseComponents };
@@ -8,15 +8,11 @@ import { ViewElement } from './context/elements/definitions.mjs';
8
8
  import { AIConversationProvider } from './AIConversationProvider.mjs';
9
9
  import { DefaultMessageControl } from './views/Controls/DefaultMessageControl.mjs';
10
10
 
11
- /**
12
- * @experimental
13
- */
14
11
  function createAIConversation(input = {}) {
15
- const { elements, suggestedPrompts, actions, responseComponents, variant, controls, displayText, allowAttachments, messageRenderer, } = input;
12
+ const { suggestedPrompts, actions, responseComponents, variant, controls, displayText, allowAttachments, messageRenderer, FallbackResponseComponent, } = input;
16
13
  function AIConversation(props) {
17
14
  const { messages, avatars, handleSendMessage, isLoading } = props;
18
15
  const providerProps = {
19
- elements,
20
16
  actions,
21
17
  suggestedPrompts,
22
18
  responseComponents,
@@ -29,6 +25,7 @@ function createAIConversation(input = {}) {
29
25
  handleSendMessage,
30
26
  isLoading,
31
27
  messageRenderer,
28
+ FallbackResponseComponent,
32
29
  };
33
30
  return (React__default.createElement(AIConversationProvider, { ...providerProps },
34
31
  React__default.createElement(ViewElement, null,
@@ -1,5 +1,6 @@
1
1
  import React__default from 'react';
2
2
  import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
3
+ import '../../context/AIContextContext.mjs';
3
4
  import { ActionsContext } from '../../context/ActionsContext.mjs';
4
5
  import '../../context/AvatarsContext.mjs';
5
6
  import '../../context/ConversationInputContext.mjs';
@@ -14,6 +15,7 @@ import '../../context/SendMessageContext.mjs';
14
15
  import '../../context/MessageRenderContext.mjs';
15
16
  import '../../context/AttachmentContext.mjs';
16
17
  import '../../context/WelcomeMessageContext.mjs';
18
+ import '../../context/FallbackComponentContext.mjs';
17
19
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
18
20
 
19
21
  const { Button, Span, View } = AIConversationElements;
@@ -33,8 +35,7 @@ const Container = withBaseElementProps(View, {
33
35
  });
34
36
  const ActionsBarControl = ({ message, focusable, }) => {
35
37
  const actions = React__default.useContext(ActionsContext);
36
- return (React__default.createElement(Container, null, actions?.map((action, index) => (React__default.createElement(ActionButton, { "aria-label": action.displayName, key: index, onClick: () => action.handler(message), tabIndex: focusable ? 0 : -1 },
37
- React__default.createElement(ActionIcon, { "data-testid": `action-icon-${action.displayName}` }, action.icon))))));
38
+ return (React__default.createElement(Container, null, actions?.map((action, index) => (React__default.createElement(ActionButton, { key: index, onClick: () => action.handler(message), tabIndex: focusable ? 0 : -1 }, action.component)))));
38
39
  };
39
40
  ActionsBarControl.Button = ActionButton;
40
41
  ActionsBarControl.Container = Container;
@@ -1,5 +1,6 @@
1
1
  import React__default from 'react';
2
2
  import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
3
+ import '../../context/AIContextContext.mjs';
3
4
  import '../../context/ActionsContext.mjs';
4
5
  import '../../context/AvatarsContext.mjs';
5
6
  import { ConversationInputContext } from '../../context/ConversationInputContext.mjs';
@@ -14,6 +15,7 @@ import '../../context/SendMessageContext.mjs';
14
15
  import '../../context/MessageRenderContext.mjs';
15
16
  import '../../context/AttachmentContext.mjs';
16
17
  import '../../context/WelcomeMessageContext.mjs';
18
+ import '../../context/FallbackComponentContext.mjs';
17
19
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
18
20
  import { useDropZone } from '@aws-amplify/ui-react-core';
19
21
 
@@ -1,5 +1,6 @@
1
1
  import React__default from 'react';
2
2
  import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
3
+ import '../../context/AIContextContext.mjs';
3
4
  import '../../context/ActionsContext.mjs';
4
5
  import '../../context/AvatarsContext.mjs';
5
6
  import { ConversationInputContext } from '../../context/ConversationInputContext.mjs';
@@ -14,6 +15,7 @@ import '../../context/SendMessageContext.mjs';
14
15
  import '../../context/MessageRenderContext.mjs';
15
16
  import '../../context/AttachmentContext.mjs';
16
17
  import '../../context/WelcomeMessageContext.mjs';
18
+ import '../../context/FallbackComponentContext.mjs';
17
19
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
18
20
 
19
21
  const { Button, Icon, ListItem, UnorderedList: ListElement, Span, Text, View, } = AIConversationElements;
@@ -1,5 +1,6 @@
1
1
  import React__default from 'react';
2
2
  import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
3
+ import '../../context/AIContextContext.mjs';
3
4
  import '../../context/ActionsContext.mjs';
4
5
  import { AvatarsContext } from '../../context/AvatarsContext.mjs';
5
6
  import '../../context/ConversationInputContext.mjs';
@@ -14,6 +15,7 @@ import '../../context/SendMessageContext.mjs';
14
15
  import '../../context/MessageRenderContext.mjs';
15
16
  import '../../context/AttachmentContext.mjs';
16
17
  import '../../context/WelcomeMessageContext.mjs';
18
+ import '../../context/FallbackComponentContext.mjs';
17
19
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
18
20
 
19
21
  const { Icon, Span, Text, View } = AIConversationElements;
@@ -1,4 +1,5 @@
1
1
  import * as React from 'react';
2
+ import '../../context/AIContextContext.mjs';
2
3
  import '../../context/ActionsContext.mjs';
3
4
  import '../../context/AvatarsContext.mjs';
4
5
  import '../../context/ConversationInputContext.mjs';
@@ -13,6 +14,7 @@ import '../../context/SendMessageContext.mjs';
13
14
  import '../../context/MessageRenderContext.mjs';
14
15
  import '../../context/AttachmentContext.mjs';
15
16
  import { WelcomeMessageContext } from '../../context/WelcomeMessageContext.mjs';
17
+ import '../../context/FallbackComponentContext.mjs';
16
18
  import '../../context/elements/definitions.mjs';
17
19
  import { PromptControl } from './PromptControl.mjs';
18
20
 
@@ -1,5 +1,6 @@
1
1
  import React__default from 'react';
2
2
  import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
3
+ import { AIContextContext } from '../../context/AIContextContext.mjs';
3
4
  import '../../context/ActionsContext.mjs';
4
5
  import '../../context/AvatarsContext.mjs';
5
6
  import { ConversationInputContext } from '../../context/ConversationInputContext.mjs';
@@ -14,10 +15,12 @@ import { SendMessageContext } from '../../context/SendMessageContext.mjs';
14
15
  import '../../context/MessageRenderContext.mjs';
15
16
  import { AttachmentContext } from '../../context/AttachmentContext.mjs';
16
17
  import '../../context/WelcomeMessageContext.mjs';
18
+ import '../../context/FallbackComponentContext.mjs';
17
19
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
18
20
  import { AttachFileControl } from './AttachFileControl.mjs';
19
21
  import { AttachmentListControl } from './AttachmentListControl.mjs';
20
22
  import { getImageTypeFromMimeType } from '../../utils.mjs';
23
+ import { isFunction } from '@aws-amplify/ui';
21
24
 
22
25
  const { Button, Icon, Label: LabelElement, TextArea, View, } = AIConversationElements;
23
26
  const FIELD_BLOCK = 'ai-field';
@@ -103,9 +106,12 @@ const FormControl = () => {
103
106
  const { input, setInput } = React__default.useContext(ConversationInputContext);
104
107
  const handleSendMessage = React__default.useContext(SendMessageContext);
105
108
  const allowAttachments = React__default.useContext(AttachmentContext);
106
- const ref = React__default.useRef(null);
107
109
  const responseComponents = React__default.useContext(ResponseComponentsContext);
110
+ const isLoading = React__default.useContext(LoadingContext);
111
+ const aiContext = React__default.useContext(AIContextContext);
112
+ const ref = React__default.useRef(null);
108
113
  const controls = React__default.useContext(ControlsContext);
114
+ const [composing, setComposing] = React__default.useState(false);
109
115
  const submitMessage = async () => {
110
116
  ref.current?.reset();
111
117
  const submittedContent = [];
@@ -130,6 +136,7 @@ const FormControl = () => {
130
136
  if (handleSendMessage) {
131
137
  handleSendMessage({
132
138
  content: submittedContent,
139
+ aiContext: isFunction(aiContext) ? aiContext() : undefined,
133
140
  toolConfiguration: convertResponseComponentsToToolConfiguration(responseComponents),
134
141
  });
135
142
  }
@@ -142,7 +149,7 @@ const FormControl = () => {
142
149
  };
143
150
  const handleOnKeyDown = (event) => {
144
151
  const { key, shiftKey } = event;
145
- if (key === 'Enter' && !shiftKey) {
152
+ if (key === 'Enter' && !shiftKey && !composing) {
146
153
  event.preventDefault();
147
154
  const hasInput = !!input?.text || (input?.files?.length && input?.files?.length > 0);
148
155
  if (hasInput) {
@@ -151,14 +158,14 @@ const FormControl = () => {
151
158
  }
152
159
  };
153
160
  if (controls?.Form) {
154
- return (React__default.createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, allowAttachments: allowAttachments }));
161
+ return (React__default.createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, allowAttachments: allowAttachments, isLoading: isLoading }));
155
162
  }
156
163
  return (React__default.createElement("form", { className: `${FIELD_BLOCK}__form`, onSubmit: handleSubmit, method: "post", ref: ref },
157
164
  allowAttachments ? React__default.createElement(AttachFileControl, null) : null,
158
165
  React__default.createElement(InputContainer, null,
159
166
  React__default.createElement(VisuallyHidden, null,
160
167
  React__default.createElement(Label, null)),
161
- React__default.createElement(TextInput, { onKeyDown: handleOnKeyDown }),
168
+ React__default.createElement(TextInput, { onKeyDown: handleOnKeyDown, onCompositionStart: () => setComposing(true), onCompositionEnd: () => setComposing(false) }),
162
169
  React__default.createElement(AttachmentListControl, null)),
163
170
  React__default.createElement(SendButton, null,
164
171
  React__default.createElement(SendIcon, null))));
@@ -1,5 +1,7 @@
1
1
  import React__default from 'react';
2
+ import { Image } from '@aws-amplify/ui-react';
2
3
  import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
4
+ import '../../context/AIContextContext.mjs';
3
5
  import '../../context/ActionsContext.mjs';
4
6
  import '../../context/AvatarsContext.mjs';
5
7
  import '../../context/ConversationInputContext.mjs';
@@ -14,66 +16,59 @@ import '../../context/SendMessageContext.mjs';
14
16
  import { MessageRendererContext } from '../../context/MessageRenderContext.mjs';
15
17
  import '../../context/AttachmentContext.mjs';
16
18
  import '../../context/WelcomeMessageContext.mjs';
19
+ import { FallbackComponentContext } from '../../context/FallbackComponentContext.mjs';
17
20
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
18
21
  import { convertBufferToBase64 } from '../../utils.mjs';
19
22
  import { ActionsBarControl } from './ActionsBarControl.mjs';
20
23
  import { AvatarControl } from './AvatarControl.mjs';
24
+ import { classNames } from '@aws-amplify/ui';
21
25
 
22
- const { Image, Span, Text, View } = AIConversationElements;
23
- const MESSAGES_BLOCK = 'ai-messages';
24
- const MESSAGE_BLOCK = 'ai-message';
25
- const MediaContentBase = withBaseElementProps(Image, {
26
- alt: 'Image attachment',
27
- });
28
- const MediaContent = React__default.forwardRef(function MediaContent(props, ref) {
26
+ const { Text, View } = AIConversationElements;
27
+ const MESSAGES_BLOCK = 'amplify-ai-conversation__message__list';
28
+ const MESSAGE_BLOCK = 'amplify-ai-conversation__message';
29
+ const MediaContent = (props) => {
29
30
  const variant = React__default.useContext(MessageVariantContext);
30
31
  const role = React__default.useContext(RoleContext);
31
- return (React__default.createElement(MediaContentBase, { ref: ref, className: `${MESSAGE_BLOCK}__image ${MESSAGE_BLOCK}__image--${variant} ${MESSAGE_BLOCK}__image--${role}`, ...props }));
32
- });
32
+ return (React__default.createElement(Image, { className: classNames(`${MESSAGE_BLOCK}__image`, variant && `${MESSAGE_BLOCK}__image--${variant}`, `${MESSAGE_BLOCK}__image--${role}`), ...props }));
33
+ };
33
34
  const TextContent = React__default.forwardRef(function TextContent(props, ref) {
34
35
  return React__default.createElement(Text, { ref: ref, className: `${MESSAGE_BLOCK}__text`, ...props });
35
36
  });
36
- const ContentContainer = React__default.forwardRef(function ContentContainer(props, ref) {
37
- const variant = React__default.useContext(MessageVariantContext);
38
- return (React__default.createElement(View, { "data-testid": 'content', className: `${MESSAGE_BLOCK}__content ${MESSAGE_BLOCK}__content--${variant}`, ref: ref, ...props }));
39
- });
40
37
  const ToolContent = ({ toolUse, }) => {
41
- const responseComponents = React__default.useContext(ResponseComponentsContext);
38
+ const responseComponents = React__default.useContext(ResponseComponentsContext) ?? {};
39
+ const FallbackComponent = React__default.useContext(FallbackComponentContext);
42
40
  // For now tool use is limited to custom response components
43
41
  const { name, input } = toolUse;
44
- if (!responseComponents ||
45
- !name ||
46
- !name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
42
+ if (!name || !name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
47
43
  return;
48
44
  }
49
45
  else {
50
46
  const response = responseComponents[name];
51
- const CustomComponent = response.component;
52
- return React__default.createElement(CustomComponent, { ...input });
47
+ if (response) {
48
+ const CustomComponent = response.component;
49
+ return React__default.createElement(CustomComponent, { ...input });
50
+ }
51
+ // fallback if there is a UI component message but we don't have
52
+ // a React component that matches
53
+ if (FallbackComponent) {
54
+ return React__default.createElement(FallbackComponent, { ...input });
55
+ }
53
56
  }
54
57
  };
55
58
  const MessageControl = ({ message }) => {
56
59
  const messageRenderer = React__default.useContext(MessageRendererContext);
57
- return (React__default.createElement(ContentContainer, null, message.content.map((content, index) => {
60
+ return (React__default.createElement(React__default.Fragment, null, message.content.map((content, index) => {
58
61
  if (content.text) {
59
62
  return messageRenderer?.text ? (React__default.createElement(React__default.Fragment, { key: index }, messageRenderer.text({ text: content.text }))) : (React__default.createElement(TextContent, { "data-testid": 'text-content', key: index }, content.text));
60
63
  }
61
64
  else if (content.image) {
62
- return messageRenderer?.image ? (React__default.createElement(React__default.Fragment, { key: index }, messageRenderer?.image({ image: content.image }))) : (React__default.createElement(MediaContent, { "data-testid": 'image-content', key: index, src: convertBufferToBase64(content.image?.source.bytes, content.image?.format) }));
65
+ return messageRenderer?.image ? (React__default.createElement(React__default.Fragment, { key: index }, messageRenderer?.image({ image: content.image }))) : (React__default.createElement(MediaContent, { "data-testid": 'image-content', key: index, alt: "", src: convertBufferToBase64(content.image?.source.bytes, content.image?.format) }));
63
66
  }
64
67
  else if (content.toolUse) {
65
68
  return React__default.createElement(ToolContent, { toolUse: content.toolUse, key: index });
66
69
  }
67
70
  })));
68
71
  };
69
- MessageControl.Container = ContentContainer;
70
- MessageControl.MediaContent = MediaContent;
71
- MessageControl.TextContent = TextContent;
72
- const Separator = withBaseElementProps(Span, {
73
- 'aria-hidden': true,
74
- children: '|',
75
- className: `${MESSAGE_BLOCK}__separator`,
76
- });
77
72
  const Timestamp = withBaseElementProps(Text, {
78
73
  className: `${MESSAGE_BLOCK}__timestamp`,
79
74
  });
@@ -134,7 +129,6 @@ const MessagesControl = () => {
134
129
  React__default.createElement(MessageContainer, { "data-testid": `message`, key: `message-${index}`, tabIndex: focusedItemIndex === index ? 0 : -1, onFocus: () => handleFocus(index), onKeyDown: (event) => onKeyDown(index, event), ref: (el) => (messagesRef.current[index] = el) },
135
130
  React__default.createElement(HeaderContainer, null,
136
131
  React__default.createElement(AvatarControl, null),
137
- React__default.createElement(Separator, null),
138
132
  React__default.createElement(Timestamp, null, getMessageTimestampText(new Date(message.createdAt)))),
139
133
  React__default.createElement(MessageControl, { message: message }),
140
134
  message.role === 'assistant' ? (React__default.createElement(ActionsBarControl, { message: message, focusable: focusedItemIndex === index })) : null)));
@@ -146,6 +140,5 @@ MessagesControl.Container = MessageContainer;
146
140
  MessagesControl.HeaderContainer = HeaderContainer;
147
141
  MessagesControl.Layout = Layout;
148
142
  MessagesControl.Message = MessageControl;
149
- MessagesControl.Separator = Separator;
150
143
 
151
144
  export { MessageControl, MessagesControl };
@@ -1,5 +1,6 @@
1
1
  import React__default from 'react';
2
2
  import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
3
+ import '../../context/AIContextContext.mjs';
3
4
  import '../../context/ActionsContext.mjs';
4
5
  import '../../context/AvatarsContext.mjs';
5
6
  import { ConversationInputContext } from '../../context/ConversationInputContext.mjs';
@@ -14,6 +15,7 @@ import '../../context/SendMessageContext.mjs';
14
15
  import '../../context/MessageRenderContext.mjs';
15
16
  import '../../context/AttachmentContext.mjs';
16
17
  import '../../context/WelcomeMessageContext.mjs';
18
+ import '../../context/FallbackComponentContext.mjs';
17
19
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
18
20
 
19
21
  const { View, Button } = AIConversationElements;
@@ -3,7 +3,6 @@ import { View, Button, VisuallyHidden, TextAreaField, DropZone } from '@aws-ampl
3
3
  import { useIcons, IconSend, IconAttach } from '@aws-amplify/ui-react/internal';
4
4
  import { ComponentClassName } from '@aws-amplify/ui';
5
5
  import { Attachments } from './Attachments.mjs';
6
- import { LoadingContext } from '../../context/LoadingContext.mjs';
7
6
 
8
7
  function isHTMLFormElement(target) {
9
8
  return 'form' in target;
@@ -25,12 +24,12 @@ const FormWrapper = ({ children, allowAttachments, setInput, }) => {
25
24
  return children;
26
25
  }
27
26
  };
28
- const Form = ({ setInput, input, handleSubmit, allowAttachments, }) => {
27
+ const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) => {
29
28
  const icons = useIcons('aiConversation');
30
29
  const sendIcon = icons?.send ?? React.createElement(IconSend, null);
31
30
  const attachIcon = icons?.attach ?? React.createElement(IconAttach, null);
32
31
  const hiddenInput = React.useRef(null);
33
- const isLoading = React.useContext(LoadingContext);
32
+ const [composing, setComposing] = React.useState(false);
34
33
  const isInputEmpty = !input?.text?.length && !input?.files?.length;
35
34
  return (React.createElement(FormWrapper, { allowAttachments: allowAttachments, setInput: setInput },
36
35
  React.createElement(View, { as: "form", className: ComponentClassName.AIConversationForm, onSubmit: handleSubmit },
@@ -52,9 +51,9 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, }) => {
52
51
  files: [...(prevValue?.files ?? []), ...Array.from(files)],
53
52
  }));
54
53
  }, multiple: true, accept: "*", "data-testid": "hidden-file-input" })))) : null,
55
- React.createElement(TextAreaField, { className: ComponentClassName.AIConversationFormField, label: "input", labelHidden: true, autoResize: true, flex: "1", rows: 1, value: input?.text ?? '', testId: "text-input", onKeyDown: (e) => {
54
+ React.createElement(TextAreaField, { className: ComponentClassName.AIConversationFormField, label: "input", labelHidden: true, autoResize: true, flex: "1", rows: 1, value: input?.text ?? '', testId: "text-input", onCompositionStart: () => setComposing(true), onCompositionEnd: () => setComposing(false), onKeyDown: (e) => {
56
55
  // Submit on enter key if shift is not pressed also
57
- const shouldSubmit = !e.shiftKey && e.key === 'Enter';
56
+ const shouldSubmit = !e.shiftKey && e.key === 'Enter' && !composing;
58
57
  if (shouldSubmit && isHTMLFormElement(e.target)) {
59
58
  e.target.form.requestSubmit();
60
59
  e.preventDefault();
@@ -1,7 +1,8 @@
1
1
  import * as React from 'react';
2
- import { View, Avatar, Text } from '@aws-amplify/ui-react';
2
+ import { View, Avatar, Placeholder, Text, Button } from '@aws-amplify/ui-react';
3
3
  import { MessageControl } from '../Controls/MessagesControl.mjs';
4
- import '../../context/ActionsContext.mjs';
4
+ import '../../context/AIContextContext.mjs';
5
+ import { ActionsContext } from '../../context/ActionsContext.mjs';
5
6
  import { AvatarsContext } from '../../context/AvatarsContext.mjs';
6
7
  import '../../context/ConversationInputContext.mjs';
7
8
  import { RoleContext } from '../../context/MessagesContext.mjs';
@@ -15,9 +16,20 @@ import '../../context/SendMessageContext.mjs';
15
16
  import '../../context/MessageRenderContext.mjs';
16
17
  import '../../context/AttachmentContext.mjs';
17
18
  import '../../context/WelcomeMessageContext.mjs';
19
+ import '../../context/FallbackComponentContext.mjs';
18
20
  import '../../context/elements/definitions.mjs';
19
21
  import { ComponentClassName, classNames, classNameModifier } from '@aws-amplify/ui';
20
22
 
23
+ const PlaceholderMessage = ({ role }) => {
24
+ const variant = React.useContext(MessageVariantContext);
25
+ return (React.createElement(View, { className: classNames(ComponentClassName.AIConversationMessage, classNameModifier(ComponentClassName.AIConversationMessage, variant), classNameModifier(ComponentClassName.AIConversationMessage, role)) },
26
+ React.createElement(View, { className: ComponentClassName.AIConversationMessageAvatar },
27
+ React.createElement(Avatar, null)),
28
+ React.createElement(View, { className: ComponentClassName.AIConversationMessageBody },
29
+ React.createElement(Placeholder, { width: "25%" }),
30
+ React.createElement(Placeholder, { width: "50%" }),
31
+ React.createElement(Placeholder, { width: "25%" }))));
32
+ };
21
33
  const MessageMeta = ({ message }) => {
22
34
  // need to pass this in as props in order for it to be overridable
23
35
  const avatars = React.useContext(AvatarsContext);
@@ -29,29 +41,30 @@ const MessageMeta = ({ message }) => {
29
41
  React.createElement(Text, { className: ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username),
30
42
  React.createElement(Text, { className: ComponentClassName.AIConversationMessageSenderTimestamp }, getMessageTimestampText(new Date(message.createdAt)))));
31
43
  };
32
- const LoadingMessage = () => {
33
- const avatars = React.useContext(AvatarsContext);
34
- const variant = React.useContext(MessageVariantContext);
35
- const avatar = avatars?.ai;
36
- return (React.createElement(View, { className: classNames(ComponentClassName.AIConversationMessage, classNameModifier(ComponentClassName.AIConversationMessage, variant), classNameModifier(ComponentClassName.AIConversationMessage, 'assistant')) },
37
- React.createElement(View, { className: ComponentClassName.AIConversationMessageAvatar },
38
- React.createElement(Avatar, { isLoading: true }, avatar?.avatar)),
39
- React.createElement(View, { className: ComponentClassName.AIConversationMessageBody },
40
- React.createElement(View, { className: ComponentClassName.AIConversationMessageSender },
41
- React.createElement(Text, { className: ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username)))));
44
+ const MessageActions = ({ message }) => {
45
+ const actions = React.useContext(ActionsContext);
46
+ if (!actions)
47
+ return null;
48
+ return (React.createElement(View, { className: ComponentClassName.AIConversationMessageActions }, actions.map((action, i) => {
49
+ return (React.createElement(Button, { key: i, size: "small", onClick: () => {
50
+ action.handler(message);
51
+ } }, action.component));
52
+ })));
42
53
  };
43
54
  const Message = ({ message }) => {
44
55
  const avatars = React.useContext(AvatarsContext);
45
56
  const variant = React.useContext(MessageVariantContext);
57
+ const { isLoading } = message;
46
58
  const avatar = message.role === 'assistant' ? avatars?.ai : avatars?.user;
47
59
  return (React.createElement(RoleContext.Provider, { value: message.role },
48
60
  React.createElement(View, { className: classNames(ComponentClassName.AIConversationMessage, classNameModifier(ComponentClassName.AIConversationMessage, variant), classNameModifier(ComponentClassName.AIConversationMessage, message.role)) },
49
61
  React.createElement(View, { className: ComponentClassName.AIConversationMessageAvatar },
50
- React.createElement(Avatar, null, avatar?.avatar)),
62
+ React.createElement(Avatar, { isLoading: isLoading }, avatar?.avatar)),
51
63
  React.createElement(View, { className: ComponentClassName.AIConversationMessageBody },
52
64
  React.createElement(MessageMeta, { message: message }),
53
65
  React.createElement(View, { className: ComponentClassName.AIConversationMessageContent },
54
- React.createElement(MessageControl, { message: message }))))));
66
+ React.createElement(MessageControl, { message: message })),
67
+ message.role === 'assistant' ? (React.createElement(MessageActions, { message: message })) : null))));
55
68
  };
56
69
  const MessageList = ({ messages, }) => {
57
70
  const isLoading = React.useContext(LoadingContext);
@@ -59,8 +72,10 @@ const MessageList = ({ messages, }) => {
59
72
  content.text ??
60
73
  content.toolUse?.name.startsWith(RESPONSE_COMPONENT_PREFIX))) ?? [];
61
74
  return (React.createElement(View, { className: ComponentClassName.AIConversationMessageList },
62
- messagesWithRenderableContent.map((message, i) => (React.createElement(Message, { key: `message-${i}`, message: message }))),
63
- isLoading ? React.createElement(LoadingMessage, null) : null));
75
+ isLoading ? (React.createElement(React.Fragment, null,
76
+ React.createElement(PlaceholderMessage, { role: "user" }),
77
+ React.createElement(PlaceholderMessage, { role: "assistant" }))) : null,
78
+ messagesWithRenderableContent.map((message, i) => (React.createElement(Message, { key: `message-${i}`, message: message })))));
64
79
  };
65
80
 
66
81
  export { MessageList };