@aws-amplify/ui-react-ai 0.1.1 → 0.2.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.
- package/dist/esm/components/AIConversation/context/ResponseComponentsContext.mjs +10 -2
- package/dist/esm/components/AIConversation/views/Controls/MessagesControl.mjs +8 -3
- package/dist/esm/components/AIConversation/views/default/Form.mjs +5 -2
- package/dist/esm/components/AIConversation/views/default/MessageList.mjs +20 -3
- package/dist/index.js +39 -6
- package/dist/types/components/AIConversation/AIConversation.d.ts +2 -2
- package/dist/types/components/AIConversation/context/ResponseComponentsContext.d.ts +1 -0
- package/dist/types/components/AIConversation/context/index.d.ts +1 -1
- package/dist/types/components/AIConversation/views/default/Form.d.ts +1 -1
- package/package.json +4 -4
|
@@ -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
|
-
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/esm/index.mjs",
|
|
6
6
|
"exports": {
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
"react-dom": "^16.14.0 || ^17.0 || ^18.0"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@aws-amplify/ui": "^6.
|
|
51
|
-
"@aws-amplify/ui-react": "^6.
|
|
52
|
-
"@aws-amplify/ui-react-core": "^3.0.
|
|
50
|
+
"@aws-amplify/ui": "^6.5.0",
|
|
51
|
+
"@aws-amplify/ui-react": "^6.4.0",
|
|
52
|
+
"@aws-amplify/ui-react-core": "^3.0.23"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@rollup/plugin-commonjs": "^22.0.1",
|