@aws-amplify/ui-react-ai 0.4.0 → 1.1.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 (70) 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/AttachmentContext.mjs +12 -3
  5. package/dist/esm/components/AIConversation/context/ConversationInputContext.mjs +2 -1
  6. package/dist/esm/components/AIConversation/context/FallbackComponentContext.mjs +8 -0
  7. package/dist/esm/components/AIConversation/context/ResponseComponentsContext.mjs +6 -2
  8. package/dist/esm/components/AIConversation/context/elements/IconElement.mjs +2 -2
  9. package/dist/esm/components/AIConversation/context/elements/definitions.mjs +12 -12
  10. package/dist/esm/components/AIConversation/createAIConversation.mjs +2 -5
  11. package/dist/esm/components/AIConversation/displayText.mjs +6 -0
  12. package/dist/esm/components/AIConversation/utils.mjs +42 -13
  13. package/dist/esm/components/AIConversation/views/Controls/ActionsBarControl.mjs +3 -2
  14. package/dist/esm/components/AIConversation/views/Controls/AttachFileControl.mjs +2 -0
  15. package/dist/esm/components/AIConversation/views/Controls/AttachmentListControl.mjs +2 -0
  16. package/dist/esm/components/AIConversation/views/Controls/AvatarControl.mjs +2 -0
  17. package/dist/esm/components/AIConversation/views/Controls/DefaultMessageControl.mjs +2 -0
  18. package/dist/esm/components/AIConversation/views/Controls/FormControl.mjs +44 -8
  19. package/dist/esm/components/AIConversation/views/Controls/MessagesControl.mjs +24 -31
  20. package/dist/esm/components/AIConversation/views/Controls/PromptControl.mjs +2 -0
  21. package/dist/esm/components/AIConversation/views/default/Form.mjs +13 -20
  22. package/dist/esm/components/AIConversation/views/default/MessageList.mjs +31 -16
  23. package/dist/esm/hooks/contentFromEvents.mjs +22 -0
  24. package/dist/esm/hooks/createAIHooks.mjs +0 -3
  25. package/dist/esm/hooks/exhaustivelyListMessages.mjs +19 -0
  26. package/dist/esm/hooks/shared.mjs +14 -0
  27. package/dist/esm/hooks/useAIConversation.mjs +246 -106
  28. package/dist/esm/hooks/useAIGeneration.mjs +1 -8
  29. package/dist/esm/index.mjs +0 -1
  30. package/dist/esm/version.mjs +1 -1
  31. package/dist/index.js +508 -280
  32. package/dist/types/components/AIConversation/AIConversation.d.ts +0 -3
  33. package/dist/types/components/AIConversation/AIConversationProvider.d.ts +1 -1
  34. package/dist/types/components/AIConversation/context/AIContextContext.d.ts +6 -0
  35. package/dist/types/components/AIConversation/context/AttachmentContext.d.ts +5 -5
  36. package/dist/types/components/AIConversation/context/ControlsContext.d.ts +5 -3
  37. package/dist/types/components/AIConversation/context/ConversationInputContext.d.ts +4 -2
  38. package/dist/types/components/AIConversation/context/DisplayTextContext.d.ts +1 -1
  39. package/dist/types/components/AIConversation/context/FallbackComponentContext.d.ts +7 -0
  40. package/dist/types/components/AIConversation/context/MessageRenderContext.d.ts +1 -1
  41. package/dist/types/components/AIConversation/context/ResponseComponentsContext.d.ts +2 -2
  42. package/dist/types/components/AIConversation/context/elements/IconElement.d.ts +2 -2
  43. package/dist/types/components/AIConversation/context/elements/definitions.d.ts +12 -12
  44. package/dist/types/components/AIConversation/context/index.d.ts +4 -2
  45. package/dist/types/components/AIConversation/createAIConversation.d.ts +0 -3
  46. package/dist/types/components/AIConversation/displayText.d.ts +2 -0
  47. package/dist/types/components/AIConversation/index.d.ts +2 -1
  48. package/dist/types/components/AIConversation/types.d.ts +6 -24
  49. package/dist/types/components/AIConversation/utils.d.ts +10 -0
  50. package/dist/types/components/AIConversation/views/Controls/MessagesControl.d.ts +1 -5
  51. package/dist/types/components/AIConversation/views/default/Attachments.d.ts +2 -2
  52. package/dist/types/components/AIConversation/views/default/Form.d.ts +1 -1
  53. package/dist/types/components/AIConversation/views/default/MessageList.d.ts +1 -1
  54. package/dist/types/components/AIConversation/views/default/PromptList.d.ts +1 -1
  55. package/dist/types/hooks/contentFromEvents.d.ts +2 -0
  56. package/dist/types/hooks/createAIHooks.d.ts +0 -3
  57. package/dist/types/hooks/exhaustivelyListMessages.d.ts +8 -0
  58. package/dist/types/hooks/index.d.ts +1 -2
  59. package/dist/types/hooks/shared.d.ts +23 -0
  60. package/dist/types/hooks/useAIConversation.d.ts +6 -4
  61. package/dist/types/hooks/useAIGeneration.d.ts +3 -13
  62. package/dist/types/index.d.ts +1 -1
  63. package/dist/types/types.d.ts +32 -1
  64. package/dist/types/version.d.ts +1 -1
  65. package/package.json +6 -6
  66. package/dist/ai-conversation-styles.css +0 -195
  67. package/dist/ai-conversation-styles.js +0 -2
  68. package/dist/esm/hooks/AIContextProvider.mjs +0 -20
  69. package/dist/types/ai-conversation-styles.d.ts +0 -1
  70. 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, maxAttachmentSize, maxAttachments, 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, maxAttachmentSize: maxAttachmentSize, maxAttachments: maxAttachments },
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 };
@@ -1,8 +1,17 @@
1
1
  import * as React from 'react';
2
2
 
3
- const AttachmentContext = React.createContext(false);
4
- const AttachmentProvider = ({ children, allowAttachments, }) => {
5
- return (React.createElement(AttachmentContext.Provider, { value: allowAttachments ?? false }, children));
3
+ const AttachmentContext = React.createContext({
4
+ allowAttachments: false,
5
+ // We save attachments as base64 strings into dynamodb for conversation history
6
+ // DynamoDB has a max size of 400kb for records
7
+ // This can be overridden so cutsomers could provide a lower number
8
+ // or a higher number if in the future we support larger sizes.
9
+ maxAttachmentSize: 400000,
10
+ maxAttachments: 20,
11
+ });
12
+ const AttachmentProvider = ({ children, allowAttachments = false, maxAttachmentSize = 400000, maxAttachments = 20, }) => {
13
+ const providerValue = React.useMemo(() => ({ maxAttachmentSize, maxAttachments, allowAttachments }), [maxAttachmentSize, maxAttachments, allowAttachments]);
14
+ return (React.createElement(AttachmentContext.Provider, { value: providerValue }, children));
6
15
  };
7
16
 
8
17
  export { AttachmentContext, AttachmentProvider };
@@ -3,7 +3,8 @@ import React__default from 'react';
3
3
  const ConversationInputContext = React__default.createContext({});
4
4
  const ConversationInputContextProvider = ({ children, }) => {
5
5
  const [input, setInput] = React__default.useState();
6
- const providerValue = React__default.useMemo(() => ({ input, setInput }), [input, setInput]);
6
+ const [error, setError] = React__default.useState();
7
+ const providerValue = React__default.useMemo(() => ({ input, setInput, error, setError }), [input, setInput, error, setError]);
7
8
  return (React__default.createElement(ConversationInputContext.Provider, { value: providerValue }, children));
8
9
  };
9
10
 
@@ -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 };
@@ -1,4 +1,4 @@
1
- import { defineBaseElement, withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
1
+ import { defineBaseElementWithRef, withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
2
2
  import React__default from 'react';
3
3
 
4
4
  const DEFAULT_ICON_PATHS = {
@@ -17,7 +17,7 @@ const DEFAULT_ICON_ATTRIBUTES = {
17
17
  fill: 'none',
18
18
  xmlns: 'http://www.w3.org/2000/svg',
19
19
  };
20
- const BaseIconElement = defineBaseElement({
20
+ const BaseIconElement = defineBaseElementWithRef({
21
21
  type: 'svg',
22
22
  displayName: 'Icon',
23
23
  });
@@ -1,44 +1,44 @@
1
- import { defineBaseElement } from '@aws-amplify/ui-react-core/elements';
1
+ import { defineBaseElementWithRef } from '@aws-amplify/ui-react-core/elements';
2
2
  import { IconElement } from './IconElement.mjs';
3
3
 
4
- const LabelElement = defineBaseElement({
4
+ const LabelElement = defineBaseElementWithRef({
5
5
  type: 'label',
6
6
  displayName: 'Label',
7
7
  });
8
- const TextElement = defineBaseElement({
8
+ const TextElement = defineBaseElementWithRef({
9
9
  type: 'p',
10
10
  displayName: 'Text',
11
11
  });
12
- const UnorderedListElement = defineBaseElement({
12
+ const UnorderedListElement = defineBaseElementWithRef({
13
13
  type: 'ul',
14
14
  displayName: 'UnorderedList',
15
15
  });
16
- const ListItemElement = defineBaseElement({
16
+ const ListItemElement = defineBaseElementWithRef({
17
17
  type: 'li',
18
18
  displayName: 'ListItem',
19
19
  });
20
- const HeadingElement = defineBaseElement({
20
+ const HeadingElement = defineBaseElementWithRef({
21
21
  type: 'h2',
22
22
  displayName: 'Title',
23
23
  });
24
- const ImageElement = defineBaseElement({
24
+ const ImageElement = defineBaseElementWithRef({
25
25
  type: 'img',
26
26
  displayName: 'Image',
27
27
  });
28
- const InputElement = defineBaseElement({
28
+ const InputElement = defineBaseElementWithRef({
29
29
  type: 'input',
30
30
  displayName: 'Input',
31
31
  });
32
- const ButtonElement = defineBaseElement({ type: 'button', displayName: 'Button' });
33
- const ViewElement = defineBaseElement({
32
+ const ButtonElement = defineBaseElementWithRef({ type: 'button', displayName: 'Button' });
33
+ const ViewElement = defineBaseElementWithRef({
34
34
  type: 'div',
35
35
  displayName: 'View',
36
36
  });
37
- const SpanElement = defineBaseElement({
37
+ const SpanElement = defineBaseElementWithRef({
38
38
  type: 'span',
39
39
  displayName: 'Span',
40
40
  });
41
- const TextAreaElement = defineBaseElement({
41
+ const TextAreaElement = defineBaseElementWithRef({
42
42
  type: 'textarea',
43
43
  displayName: 'TextArea',
44
44
  });
@@ -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,
@@ -2,6 +2,12 @@ import { formatDate } from './utils.mjs';
2
2
 
3
3
  const defaultAIConversationDisplayTextEn = {
4
4
  getMessageTimestampText: (date) => formatDate(date),
5
+ getMaxAttachmentErrorText(count) {
6
+ return `Cannot choose more than ${count} ${count === 1 ? 'file' : 'files'}. `;
7
+ },
8
+ getAttachmentSizeErrorText(sizeText) {
9
+ return `File size must be below ${sizeText}.`;
10
+ },
5
11
  };
6
12
 
7
13
  export { defaultAIConversationDisplayTextEn };
@@ -12,28 +12,57 @@ function formatDate(date) {
12
12
  return `${dateString} at ${timeString}`;
13
13
  }
14
14
  function arrayBufferToBase64(buffer) {
15
- let binary = '';
16
- const bytes = new Uint8Array(buffer);
17
- const len = bytes.byteLength;
18
- for (let i = 0; i < len; i++) {
19
- binary += String.fromCharCode(bytes[i]);
20
- }
21
- return window.btoa(binary);
22
- }
23
- function convertBufferToBase64(buffer, format) {
24
- let base64string = '';
25
15
  // Use node-based buffer if available
26
16
  // fall back on browser if not
27
17
  if (typeof Buffer !== 'undefined') {
28
- base64string = Buffer.from(new Uint8Array(buffer)).toString('base64');
18
+ return Buffer.from(new Uint8Array(buffer)).toString('base64');
29
19
  }
30
20
  else {
31
- base64string = arrayBufferToBase64(buffer);
21
+ let binary = '';
22
+ const bytes = new Uint8Array(buffer);
23
+ const len = bytes.byteLength;
24
+ for (let i = 0; i < len; i++) {
25
+ binary += String.fromCharCode(bytes[i]);
26
+ }
27
+ return window.btoa(binary);
32
28
  }
29
+ }
30
+ function convertBufferToBase64(buffer, format) {
31
+ const base64string = arrayBufferToBase64(buffer);
33
32
  return `data:image/${format};base64,${base64string}`;
34
33
  }
35
34
  function getImageTypeFromMimeType(mimeType) {
36
35
  return mimeType.split('/')[1];
37
36
  }
37
+ async function attachmentsValidator({ files, maxAttachments, maxAttachmentSize, }) {
38
+ const acceptedFiles = [];
39
+ const rejectedFiles = [];
40
+ let hasMaxSizeError = false;
41
+ for (const file of files) {
42
+ const arrayBuffer = await file.arrayBuffer();
43
+ const base64 = arrayBufferToBase64(arrayBuffer);
44
+ if (base64.length < maxAttachmentSize) {
45
+ acceptedFiles.push(file);
46
+ }
47
+ else {
48
+ rejectedFiles.push(file);
49
+ hasMaxSizeError = true;
50
+ }
51
+ }
52
+ if (acceptedFiles.length > maxAttachments) {
53
+ return {
54
+ acceptedFiles: acceptedFiles.slice(0, maxAttachments),
55
+ rejectedFiles: [...acceptedFiles.slice(maxAttachments), ...rejectedFiles],
56
+ hasMaxAttachmentsError: true,
57
+ hasMaxAttachmentSizeError: hasMaxSizeError,
58
+ };
59
+ }
60
+ return {
61
+ acceptedFiles,
62
+ rejectedFiles,
63
+ hasMaxAttachmentsError: false,
64
+ hasMaxAttachmentSizeError: hasMaxSizeError,
65
+ };
66
+ }
38
67
 
39
- export { convertBufferToBase64, formatDate, getImageTypeFromMimeType };
68
+ export { attachmentsValidator, convertBufferToBase64, formatDate, getImageTypeFromMimeType };
@@ -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,12 +1,13 @@
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';
6
7
  import { MessagesContext } from '../../context/MessagesContext.mjs';
7
8
  import '../../context/SuggestedPromptsContext.mjs';
8
9
  import '../../context/MessageVariantContext.mjs';
9
- import '../../context/DisplayTextContext.mjs';
10
+ import { useConversationDisplayText } from '../../context/DisplayTextContext.mjs';
10
11
  import { ControlsContext } from '../../context/ControlsContext.mjs';
11
12
  import { LoadingContext } from '../../context/LoadingContext.mjs';
12
13
  import { ResponseComponentsContext, convertResponseComponentsToToolConfiguration } from '../../context/ResponseComponentsContext.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
- import { getImageTypeFromMimeType } from '../../utils.mjs';
22
+ import { attachmentsValidator, getImageTypeFromMimeType } from '../../utils.mjs';
23
+ import { humanFileSize, isFunction } from '@aws-amplify/ui';
21
24
 
22
25
  const { Button, Icon, Label: LabelElement, TextArea, View, } = AIConversationElements;
23
26
  const FIELD_BLOCK = 'ai-field';
@@ -100,12 +103,16 @@ const InputContainer = withBaseElementProps(View, {
100
103
  className: `${FIELD_BLOCK}__input-container`,
101
104
  });
102
105
  const FormControl = () => {
103
- const { input, setInput } = React__default.useContext(ConversationInputContext);
106
+ const { input, setInput, error, setError } = React__default.useContext(ConversationInputContext);
104
107
  const handleSendMessage = React__default.useContext(SendMessageContext);
105
- const allowAttachments = React__default.useContext(AttachmentContext);
106
- const ref = React__default.useRef(null);
108
+ const { allowAttachments, maxAttachmentSize, maxAttachments } = React__default.useContext(AttachmentContext);
109
+ const displayText = useConversationDisplayText();
107
110
  const responseComponents = React__default.useContext(ResponseComponentsContext);
111
+ const isLoading = React__default.useContext(LoadingContext);
112
+ const aiContext = React__default.useContext(AIContextContext);
113
+ const ref = React__default.useRef(null);
108
114
  const controls = React__default.useContext(ControlsContext);
115
+ const [composing, setComposing] = React__default.useState(false);
109
116
  const submitMessage = async () => {
110
117
  ref.current?.reset();
111
118
  const submittedContent = [];
@@ -130,6 +137,7 @@ const FormControl = () => {
130
137
  if (handleSendMessage) {
131
138
  handleSendMessage({
132
139
  content: submittedContent,
140
+ aiContext: isFunction(aiContext) ? aiContext() : undefined,
133
141
  toolConfiguration: convertResponseComponentsToToolConfiguration(responseComponents),
134
142
  });
135
143
  }
@@ -142,7 +150,7 @@ const FormControl = () => {
142
150
  };
143
151
  const handleOnKeyDown = (event) => {
144
152
  const { key, shiftKey } = event;
145
- if (key === 'Enter' && !shiftKey) {
153
+ if (key === 'Enter' && !shiftKey && !composing) {
146
154
  event.preventDefault();
147
155
  const hasInput = !!input?.text || (input?.files?.length && input?.files?.length > 0);
148
156
  if (hasInput) {
@@ -150,15 +158,43 @@ const FormControl = () => {
150
158
  }
151
159
  }
152
160
  };
161
+ const onValidate = React__default.useCallback(async (files) => {
162
+ const previousFiles = input?.files ?? [];
163
+ const { acceptedFiles, hasMaxAttachmentsError, hasMaxAttachmentSizeError, } = await attachmentsValidator({
164
+ files: [...files, ...previousFiles],
165
+ maxAttachments,
166
+ maxAttachmentSize,
167
+ });
168
+ if (hasMaxAttachmentsError || hasMaxAttachmentSizeError) {
169
+ const errors = [];
170
+ if (hasMaxAttachmentsError) {
171
+ errors.push(displayText.getMaxAttachmentErrorText(maxAttachments));
172
+ }
173
+ if (hasMaxAttachmentSizeError) {
174
+ errors.push(displayText.getAttachmentSizeErrorText(
175
+ // base64 size is about 137% that of the file size
176
+ // https://en.wikipedia.org/wiki/Base64#MIME
177
+ humanFileSize((maxAttachmentSize - 814) / 1.37, true)));
178
+ }
179
+ setError?.(errors.join(' '));
180
+ }
181
+ else {
182
+ setError?.(undefined);
183
+ }
184
+ setInput?.((prevValue) => ({
185
+ ...prevValue,
186
+ files: acceptedFiles,
187
+ }));
188
+ }, [setInput, input, displayText, maxAttachmentSize, maxAttachments, setError]);
153
189
  if (controls?.Form) {
154
- return (React__default.createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, allowAttachments: allowAttachments }));
190
+ return (React__default.createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, onValidate: onValidate, allowAttachments: allowAttachments, isLoading: isLoading, error: error, setError: setError }));
155
191
  }
156
192
  return (React__default.createElement("form", { className: `${FIELD_BLOCK}__form`, onSubmit: handleSubmit, method: "post", ref: ref },
157
193
  allowAttachments ? React__default.createElement(AttachFileControl, null) : null,
158
194
  React__default.createElement(InputContainer, null,
159
195
  React__default.createElement(VisuallyHidden, null,
160
196
  React__default.createElement(Label, null)),
161
- React__default.createElement(TextInput, { onKeyDown: handleOnKeyDown }),
197
+ React__default.createElement(TextInput, { onKeyDown: handleOnKeyDown, onCompositionStart: () => setComposing(true), onCompositionEnd: () => setComposing(false) }),
162
198
  React__default.createElement(AttachmentListControl, null)),
163
199
  React__default.createElement(SendButton, null,
164
200
  React__default.createElement(SendIcon, null))));