@aws-amplify/ui-react-ai 0.1.1 → 0.2.1

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.
@@ -1,8 +1,16 @@
1
1
  import React__default from 'react';
2
2
 
3
+ const RESPONSE_COMPONENT_PREFIX = 'AMPLIFY_UI_';
3
4
  const ResponseComponentsContext = React__default.createContext(undefined);
5
+ const prependResponseComponents = (responseComponents) => {
6
+ if (!responseComponents)
7
+ return responseComponents;
8
+ return Object.keys(responseComponents).reduce((prev, key) => ((prev[`${RESPONSE_COMPONENT_PREFIX}${key}`] = responseComponents[key]),
9
+ prev), {});
10
+ };
4
11
  const ResponseComponentsProvider = ({ children, responseComponents, }) => {
5
- return (React__default.createElement(ResponseComponentsContext.Provider, { value: responseComponents }, children));
12
+ const _responseComponents = React__default.useMemo(() => prependResponseComponents(responseComponents), [responseComponents]);
13
+ return (React__default.createElement(ResponseComponentsContext.Provider, { value: _responseComponents }, children));
6
14
  };
7
15
  const convertResponseComponentsToToolConfiguration = (responseComponents) => {
8
16
  if (!responseComponents) {
@@ -32,4 +40,4 @@ const convertResponseComponentsToToolConfiguration = (responseComponents) => {
32
40
  return { tools };
33
41
  };
34
42
 
35
- export { ResponseComponentsContext, ResponseComponentsProvider, convertResponseComponentsToToolConfiguration };
43
+ export { RESPONSE_COMPONENT_PREFIX, ResponseComponentsContext, ResponseComponentsProvider, convertResponseComponentsToToolConfiguration };
@@ -9,7 +9,7 @@ import { MessageVariantContext } from '../../context/MessageVariantContext.mjs';
9
9
  import { useConversationDisplayText } from '../../context/DisplayTextContext.mjs';
10
10
  import { ControlsContext } from '../../context/ControlsContext.mjs';
11
11
  import '../../context/LoadingContext.mjs';
12
- import { ResponseComponentsContext } from '../../context/ResponseComponentsContext.mjs';
12
+ import { ResponseComponentsContext, RESPONSE_COMPONENT_PREFIX } from '../../context/ResponseComponentsContext.mjs';
13
13
  import '../../context/SendMessageContext.mjs';
14
14
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
15
15
  import { convertBufferToBase64 } from '../../utils.mjs';
@@ -46,7 +46,9 @@ const MessageControl = ({ message }) => {
46
46
  else if (content.toolUse) {
47
47
  // For now tool use is limited to custom response components
48
48
  const { name, input } = content.toolUse;
49
- if (!responseComponents || !name) {
49
+ if (!responseComponents ||
50
+ !name ||
51
+ !name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
50
52
  return;
51
53
  }
52
54
  else {
@@ -117,7 +119,10 @@ const MessagesControl = ({ renderMessage }) => {
117
119
  if (controls?.MessageList) {
118
120
  return React__default.createElement(controls.MessageList, { messages: messages });
119
121
  }
120
- return (React__default.createElement(Layout, null, messages?.map((message, index) => {
122
+ const messagesWithRenderableContent = messages?.filter((message) => message.content.some((content) => content.image ??
123
+ content.text ??
124
+ content.toolUse?.name.startsWith(RESPONSE_COMPONENT_PREFIX))) ?? [];
125
+ return (React__default.createElement(Layout, null, messagesWithRenderableContent?.map((message, index) => {
121
126
  return renderMessage ? (renderMessage(message)) : (React__default.createElement(RoleContext.Provider, { value: message.role, key: `message-${index}` },
122
127
  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) },
123
128
  React__default.createElement(HeaderContainer, null,
@@ -24,6 +24,9 @@ const Form = ({ setInput, input, handleSubmit, }) => {
24
24
  React.createElement(View, { as: "form", className: ComponentClassName.AIConversationForm, onSubmit: handleSubmit },
25
25
  React.createElement(Button, { className: ComponentClassName.AIConversationFormAttach, onClick: () => {
26
26
  hiddenInput?.current?.click();
27
+ if (hiddenInput?.current) {
28
+ hiddenInput.current.value = '';
29
+ }
27
30
  } },
28
31
  React.createElement("span", null, attachIcon),
29
32
  React.createElement(VisuallyHidden, null,
@@ -36,8 +39,8 @@ const Form = ({ setInput, input, handleSubmit, }) => {
36
39
  ...prevValue,
37
40
  files: [...(prevValue?.files ?? []), ...Array.from(files)],
38
41
  }));
39
- }, multiple: true, accept: "*" }))),
40
- React.createElement(TextAreaField, { className: ComponentClassName.AIConversationFormField, label: "input", labelHidden: true, autoResize: true, flex: "1", rows: 1, value: input?.text ?? '', onKeyDown: (e) => {
42
+ }, multiple: true, accept: "*", "data-testid": "hidden-file-input" }))),
43
+ React.createElement(TextAreaField, { className: ComponentClassName.AIConversationFormField, label: "input", labelHidden: true, autoResize: true, flex: "1", rows: 1, value: input?.text ?? '', testId: "text-input", onKeyDown: (e) => {
41
44
  // Submit on enter key if shift is not pressed also
42
45
  const shouldSubmit = !e.shiftKey && e.key === 'Enter';
43
46
  if (shouldSubmit && isHTMLFormElement(e.target)) {
@@ -9,8 +9,8 @@ import '../../context/SuggestedPromptsContext.mjs';
9
9
  import { MessageVariantContext } from '../../context/MessageVariantContext.mjs';
10
10
  import { useConversationDisplayText } from '../../context/DisplayTextContext.mjs';
11
11
  import '../../context/ControlsContext.mjs';
12
- import '../../context/LoadingContext.mjs';
13
- import '../../context/ResponseComponentsContext.mjs';
12
+ import { LoadingContext } from '../../context/LoadingContext.mjs';
13
+ import { RESPONSE_COMPONENT_PREFIX } from '../../context/ResponseComponentsContext.mjs';
14
14
  import '../../context/SendMessageContext.mjs';
15
15
  import '../../context/elements/definitions.mjs';
16
16
  import { ComponentClassName, classNames, classNameModifier } from '@aws-amplify/ui';
@@ -26,6 +26,17 @@ const MessageMeta = ({ message }) => {
26
26
  React.createElement(Text, { className: ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username),
27
27
  React.createElement(Text, { className: ComponentClassName.AIConversationMessageSenderTimestamp }, getMessageTimestampText(new Date(message.createdAt)))));
28
28
  };
29
+ const LoadingMessage = () => {
30
+ const avatars = React.useContext(AvatarsContext);
31
+ const variant = React.useContext(MessageVariantContext);
32
+ const avatar = avatars?.ai;
33
+ return (React.createElement(View, { className: classNames(ComponentClassName.AIConversationMessage, classNameModifier(ComponentClassName.AIConversationMessage, variant), classNameModifier(ComponentClassName.AIConversationMessage, 'assistant')) },
34
+ React.createElement(View, { className: ComponentClassName.AIConversationMessageAvatar },
35
+ React.createElement(Avatar, { isLoading: true }, avatar?.avatar)),
36
+ React.createElement(View, { className: ComponentClassName.AIConversationMessageBody },
37
+ React.createElement(View, { className: ComponentClassName.AIConversationMessageSender },
38
+ React.createElement(Text, { className: ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username)))));
39
+ };
29
40
  const Message = ({ message }) => {
30
41
  const avatars = React.useContext(AvatarsContext);
31
42
  const variant = React.useContext(MessageVariantContext);
@@ -40,7 +51,13 @@ const Message = ({ message }) => {
40
51
  React.createElement(MessageControl, { message: message }))))));
41
52
  };
42
53
  const MessageList = ({ messages, }) => {
43
- return (React.createElement(View, { className: ComponentClassName.AIConversationMessageList }, messages.map((message, i) => (React.createElement(Message, { key: `message-${i}`, message: message })))));
54
+ const isLoading = React.useContext(LoadingContext);
55
+ const messagesWithRenderableContent = messages?.filter((message) => message.content.some((content) => content.image ??
56
+ content.text ??
57
+ content.toolUse?.name.startsWith(RESPONSE_COMPONENT_PREFIX))) ?? [];
58
+ return (React.createElement(View, { className: ComponentClassName.AIConversationMessageList },
59
+ messagesWithRenderableContent.map((message, i) => (React.createElement(Message, { key: `message-${i}`, message: message }))),
60
+ isLoading ? React.createElement(LoadingMessage, null) : null));
44
61
  };
45
62
 
46
63
  export { MessageList };
package/dist/index.js CHANGED
@@ -211,9 +211,17 @@ const LoadingContextProvider = ({ children, isLoading, }) => {
211
211
  return (React__default["default"].createElement(LoadingContext.Provider, { value: isLoading }, children));
212
212
  };
213
213
 
214
+ const RESPONSE_COMPONENT_PREFIX = 'AMPLIFY_UI_';
214
215
  const ResponseComponentsContext = React__default["default"].createContext(undefined);
216
+ const prependResponseComponents = (responseComponents) => {
217
+ if (!responseComponents)
218
+ return responseComponents;
219
+ return Object.keys(responseComponents).reduce((prev, key) => ((prev[`${RESPONSE_COMPONENT_PREFIX}${key}`] = responseComponents[key]),
220
+ prev), {});
221
+ };
215
222
  const ResponseComponentsProvider = ({ children, responseComponents, }) => {
216
- return (React__default["default"].createElement(ResponseComponentsContext.Provider, { value: responseComponents }, children));
223
+ const _responseComponents = React__default["default"].useMemo(() => prependResponseComponents(responseComponents), [responseComponents]);
224
+ return (React__default["default"].createElement(ResponseComponentsContext.Provider, { value: _responseComponents }, children));
217
225
  };
218
226
  const convertResponseComponentsToToolConfiguration = (responseComponents) => {
219
227
  if (!responseComponents) {
@@ -644,7 +652,9 @@ const MessageControl = ({ message }) => {
644
652
  else if (content.toolUse) {
645
653
  // For now tool use is limited to custom response components
646
654
  const { name, input } = content.toolUse;
647
- if (!responseComponents || !name) {
655
+ if (!responseComponents ||
656
+ !name ||
657
+ !name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
648
658
  return;
649
659
  }
650
660
  else {
@@ -715,7 +725,10 @@ const MessagesControl = ({ renderMessage }) => {
715
725
  if (controls?.MessageList) {
716
726
  return React__default["default"].createElement(controls.MessageList, { messages: messages });
717
727
  }
718
- return (React__default["default"].createElement(Layout, null, messages?.map((message, index) => {
728
+ const messagesWithRenderableContent = messages?.filter((message) => message.content.some((content) => content.image ??
729
+ content.text ??
730
+ content.toolUse?.name.startsWith(RESPONSE_COMPONENT_PREFIX))) ?? [];
731
+ return (React__default["default"].createElement(Layout, null, messagesWithRenderableContent?.map((message, index) => {
719
732
  return renderMessage ? (renderMessage(message)) : (React__default["default"].createElement(RoleContext.Provider, { value: message.role, key: `message-${index}` },
720
733
  React__default["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) },
721
734
  React__default["default"].createElement(HeaderContainer, null,
@@ -882,6 +895,17 @@ const MessageMeta = ({ message }) => {
882
895
  React__namespace.createElement(uiReact.Text, { className: ui.ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username),
883
896
  React__namespace.createElement(uiReact.Text, { className: ui.ComponentClassName.AIConversationMessageSenderTimestamp }, getMessageTimestampText(new Date(message.createdAt)))));
884
897
  };
898
+ const LoadingMessage = () => {
899
+ const avatars = React__namespace.useContext(AvatarsContext);
900
+ const variant = React__namespace.useContext(MessageVariantContext);
901
+ const avatar = avatars?.ai;
902
+ return (React__namespace.createElement(uiReact.View, { className: ui.classNames(ui.ComponentClassName.AIConversationMessage, ui.classNameModifier(ui.ComponentClassName.AIConversationMessage, variant), ui.classNameModifier(ui.ComponentClassName.AIConversationMessage, 'assistant')) },
903
+ React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageAvatar },
904
+ React__namespace.createElement(uiReact.Avatar, { isLoading: true }, avatar?.avatar)),
905
+ React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageBody },
906
+ React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageSender },
907
+ React__namespace.createElement(uiReact.Text, { className: ui.ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username)))));
908
+ };
885
909
  const Message = ({ message }) => {
886
910
  const avatars = React__namespace.useContext(AvatarsContext);
887
911
  const variant = React__namespace.useContext(MessageVariantContext);
@@ -896,7 +920,13 @@ const Message = ({ message }) => {
896
920
  React__namespace.createElement(MessageControl, { message: message }))))));
897
921
  };
898
922
  const MessageList = ({ messages, }) => {
899
- return (React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageList }, messages.map((message, i) => (React__namespace.createElement(Message, { key: `message-${i}`, message: message })))));
923
+ const isLoading = React__namespace.useContext(LoadingContext);
924
+ const messagesWithRenderableContent = messages?.filter((message) => message.content.some((content) => content.image ??
925
+ content.text ??
926
+ content.toolUse?.name.startsWith(RESPONSE_COMPONENT_PREFIX))) ?? [];
927
+ return (React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageList },
928
+ messagesWithRenderableContent.map((message, i) => (React__namespace.createElement(Message, { key: `message-${i}`, message: message }))),
929
+ isLoading ? React__namespace.createElement(LoadingMessage, null) : null));
900
930
  };
901
931
 
902
932
  const Attachment = ({ file, handleRemove, }) => {
@@ -941,6 +971,9 @@ const Form = ({ setInput, input, handleSubmit, }) => {
941
971
  React__namespace.createElement(uiReact.View, { as: "form", className: ui.ComponentClassName.AIConversationForm, onSubmit: handleSubmit },
942
972
  React__namespace.createElement(uiReact.Button, { className: ui.ComponentClassName.AIConversationFormAttach, onClick: () => {
943
973
  hiddenInput?.current?.click();
974
+ if (hiddenInput?.current) {
975
+ hiddenInput.current.value = '';
976
+ }
944
977
  } },
945
978
  React__namespace.createElement("span", null, attachIcon),
946
979
  React__namespace.createElement(uiReact.VisuallyHidden, null,
@@ -953,8 +986,8 @@ const Form = ({ setInput, input, handleSubmit, }) => {
953
986
  ...prevValue,
954
987
  files: [...(prevValue?.files ?? []), ...Array.from(files)],
955
988
  }));
956
- }, multiple: true, accept: "*" }))),
957
- React__namespace.createElement(uiReact.TextAreaField, { className: ui.ComponentClassName.AIConversationFormField, label: "input", labelHidden: true, autoResize: true, flex: "1", rows: 1, value: input?.text ?? '', onKeyDown: (e) => {
989
+ }, multiple: true, accept: "*", "data-testid": "hidden-file-input" }))),
990
+ React__namespace.createElement(uiReact.TextAreaField, { className: ui.ComponentClassName.AIConversationFormField, label: "input", labelHidden: true, autoResize: true, flex: "1", rows: 1, value: input?.text ?? '', testId: "text-input", onKeyDown: (e) => {
958
991
  // Submit on enter key if shift is not pressed also
959
992
  const shouldSubmit = !e.shiftKey && e.key === 'Enter';
960
993
  if (shouldSubmit && isHTMLFormElement(e.target)) {
@@ -14,8 +14,8 @@ export declare const AIConversation: typeof AIConversationBase & {
14
14
  suggestedPrompts?: import("./types").SuggestedPrompt[] | undefined;
15
15
  setInput: React.Dispatch<React.SetStateAction<import("./context").ConversationInput | undefined>> | undefined;
16
16
  }> | undefined;
17
- Form: React.ComponentType<{
17
+ Form: NonNullable<React.ComponentType<{
18
18
  handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
19
- } & Required<import("./context").ConversationInputContext>> | undefined;
19
+ } & Required<import("./context").ConversationInputContext>> | undefined>;
20
20
  };
21
21
  export {};
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { ResponseComponents } from '../types';
3
3
  import { ToolConfiguration } from '../../../types';
4
4
  type ResponseComponentsContextProps = ResponseComponents | undefined;
5
+ export declare const RESPONSE_COMPONENT_PREFIX = "AMPLIFY_UI_";
5
6
  export declare const ResponseComponentsContext: React.Context<ResponseComponentsContextProps>;
6
7
  export declare const ResponseComponentsProvider: ({ children, responseComponents, }: {
7
8
  children?: React.ReactNode;
@@ -7,6 +7,6 @@ export { MessageVariantContext, MessageVariantProvider, } from './MessageVariant
7
7
  export { ConversationDisplayTextContext, useConversationDisplayText, ConversationDisplayTextProvider, } from './DisplayTextContext';
8
8
  export { ControlsContext, ControlsContextProps, ControlsProvider, } from './ControlsContext';
9
9
  export { LoadingContextProvider } from './LoadingContext';
10
- export { ResponseComponentsProvider } from './ResponseComponentsContext';
10
+ export { ResponseComponentsProvider, RESPONSE_COMPONENT_PREFIX, } from './ResponseComponentsContext';
11
11
  export { SendMessageContextProvider } from './SendMessageContext';
12
12
  export * from './elements';
@@ -1,2 +1,2 @@
1
1
  import { ControlsContextProps } from '../../context/ControlsContext';
2
- export declare const Form: ControlsContextProps['Form'];
2
+ export declare const Form: NonNullable<ControlsContextProps['Form']>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-amplify/ui-react-ai",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/esm/index.mjs",
6
6
  "exports": {
@@ -42,14 +42,15 @@
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "peerDependencies": {
45
- "aws-amplify": "^6.3.2",
45
+ "@aws-amplify/api-graphql": "^4.3.0",
46
+ "aws-amplify": "^6.6.0",
46
47
  "react": "^16.14.0 || ^17.0 || ^18.0",
47
48
  "react-dom": "^16.14.0 || ^17.0 || ^18.0"
48
49
  },
49
50
  "dependencies": {
50
- "@aws-amplify/ui": "^6.4.1",
51
- "@aws-amplify/ui-react": "^6.3.1",
52
- "@aws-amplify/ui-react-core": "^3.0.22"
51
+ "@aws-amplify/ui": "^6.6.1",
52
+ "@aws-amplify/ui-react": "^6.5.1",
53
+ "@aws-amplify/ui-react-core": "^3.0.25"
53
54
  },
54
55
  "devDependencies": {
55
56
  "@rollup/plugin-commonjs": "^22.0.1",