@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.
- package/dist/esm/components/AIConversation/AIConversation.mjs +8 -26
- package/dist/esm/components/AIConversation/AIConversationProvider.mjs +20 -17
- package/dist/esm/components/AIConversation/context/AIContextContext.mjs +8 -0
- package/dist/esm/components/AIConversation/context/AttachmentContext.mjs +12 -3
- package/dist/esm/components/AIConversation/context/ConversationInputContext.mjs +2 -1
- package/dist/esm/components/AIConversation/context/FallbackComponentContext.mjs +8 -0
- package/dist/esm/components/AIConversation/context/ResponseComponentsContext.mjs +6 -2
- package/dist/esm/components/AIConversation/context/elements/IconElement.mjs +2 -2
- package/dist/esm/components/AIConversation/context/elements/definitions.mjs +12 -12
- package/dist/esm/components/AIConversation/createAIConversation.mjs +2 -5
- package/dist/esm/components/AIConversation/displayText.mjs +6 -0
- package/dist/esm/components/AIConversation/utils.mjs +42 -13
- package/dist/esm/components/AIConversation/views/Controls/ActionsBarControl.mjs +3 -2
- package/dist/esm/components/AIConversation/views/Controls/AttachFileControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/Controls/AttachmentListControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/Controls/AvatarControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/Controls/DefaultMessageControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/Controls/FormControl.mjs +44 -8
- package/dist/esm/components/AIConversation/views/Controls/MessagesControl.mjs +24 -31
- package/dist/esm/components/AIConversation/views/Controls/PromptControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/default/Form.mjs +13 -20
- package/dist/esm/components/AIConversation/views/default/MessageList.mjs +31 -16
- package/dist/esm/hooks/contentFromEvents.mjs +22 -0
- package/dist/esm/hooks/createAIHooks.mjs +0 -3
- package/dist/esm/hooks/exhaustivelyListMessages.mjs +19 -0
- package/dist/esm/hooks/shared.mjs +14 -0
- package/dist/esm/hooks/useAIConversation.mjs +246 -106
- package/dist/esm/hooks/useAIGeneration.mjs +1 -8
- package/dist/esm/index.mjs +0 -1
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +508 -280
- package/dist/types/components/AIConversation/AIConversation.d.ts +0 -3
- package/dist/types/components/AIConversation/AIConversationProvider.d.ts +1 -1
- package/dist/types/components/AIConversation/context/AIContextContext.d.ts +6 -0
- package/dist/types/components/AIConversation/context/AttachmentContext.d.ts +5 -5
- package/dist/types/components/AIConversation/context/ControlsContext.d.ts +5 -3
- package/dist/types/components/AIConversation/context/ConversationInputContext.d.ts +4 -2
- package/dist/types/components/AIConversation/context/DisplayTextContext.d.ts +1 -1
- package/dist/types/components/AIConversation/context/FallbackComponentContext.d.ts +7 -0
- package/dist/types/components/AIConversation/context/MessageRenderContext.d.ts +1 -1
- package/dist/types/components/AIConversation/context/ResponseComponentsContext.d.ts +2 -2
- package/dist/types/components/AIConversation/context/elements/IconElement.d.ts +2 -2
- package/dist/types/components/AIConversation/context/elements/definitions.d.ts +12 -12
- package/dist/types/components/AIConversation/context/index.d.ts +4 -2
- package/dist/types/components/AIConversation/createAIConversation.d.ts +0 -3
- package/dist/types/components/AIConversation/displayText.d.ts +2 -0
- package/dist/types/components/AIConversation/index.d.ts +2 -1
- package/dist/types/components/AIConversation/types.d.ts +6 -24
- package/dist/types/components/AIConversation/utils.d.ts +10 -0
- package/dist/types/components/AIConversation/views/Controls/MessagesControl.d.ts +1 -5
- package/dist/types/components/AIConversation/views/default/Attachments.d.ts +2 -2
- package/dist/types/components/AIConversation/views/default/Form.d.ts +1 -1
- package/dist/types/components/AIConversation/views/default/MessageList.d.ts +1 -1
- package/dist/types/components/AIConversation/views/default/PromptList.d.ts +1 -1
- package/dist/types/hooks/contentFromEvents.d.ts +2 -0
- package/dist/types/hooks/createAIHooks.d.ts +0 -3
- package/dist/types/hooks/exhaustivelyListMessages.d.ts +8 -0
- package/dist/types/hooks/index.d.ts +1 -2
- package/dist/types/hooks/shared.d.ts +23 -0
- package/dist/types/hooks/useAIConversation.d.ts +6 -4
- package/dist/types/hooks/useAIGeneration.d.ts +3 -13
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types.d.ts +32 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +6 -6
- package/dist/ai-conversation-styles.css +0 -195
- package/dist/ai-conversation-styles.js +0 -2
- package/dist/esm/hooks/AIContextProvider.mjs +0 -20
- package/dist/types/ai-conversation-styles.d.ts +0 -1
- package/dist/types/hooks/AIContextProvider.d.ts +0 -17
|
@@ -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 {
|
|
23
|
-
const MESSAGES_BLOCK = 'ai-
|
|
24
|
-
const MESSAGE_BLOCK = 'ai-
|
|
25
|
-
const
|
|
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(
|
|
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 (!
|
|
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
|
-
|
|
52
|
-
|
|
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(
|
|
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;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { View, Button, VisuallyHidden, TextAreaField, DropZone } from '@aws-amplify/ui-react';
|
|
2
|
+
import { View, Button, VisuallyHidden, TextAreaField, Message, DropZone } from '@aws-amplify/ui-react';
|
|
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;
|
|
@@ -12,27 +11,24 @@ function isHTMLFormElement(target) {
|
|
|
12
11
|
* Will conditionally render the DropZone if allowAttachments
|
|
13
12
|
* is true
|
|
14
13
|
*/
|
|
15
|
-
const FormWrapper = ({ children, allowAttachments,
|
|
14
|
+
const FormWrapper = ({ children, allowAttachments, onValidate, }) => {
|
|
16
15
|
if (allowAttachments) {
|
|
17
16
|
return (React.createElement(DropZone, { className: ComponentClassName.AIConversationFormDropzone, onDropComplete: ({ acceptedFiles }) => {
|
|
18
|
-
|
|
19
|
-
...prevInput,
|
|
20
|
-
files: [...(prevInput?.files ?? []), ...acceptedFiles],
|
|
21
|
-
}));
|
|
17
|
+
onValidate(acceptedFiles);
|
|
22
18
|
} }, children));
|
|
23
19
|
}
|
|
24
20
|
else {
|
|
25
21
|
return children;
|
|
26
22
|
}
|
|
27
23
|
};
|
|
28
|
-
const Form = ({ setInput, input, handleSubmit, allowAttachments, }) => {
|
|
24
|
+
const Form = ({ setInput, input, handleSubmit, allowAttachments, onValidate, isLoading, error, }) => {
|
|
29
25
|
const icons = useIcons('aiConversation');
|
|
30
26
|
const sendIcon = icons?.send ?? React.createElement(IconSend, null);
|
|
31
27
|
const attachIcon = icons?.attach ?? React.createElement(IconAttach, null);
|
|
32
28
|
const hiddenInput = React.useRef(null);
|
|
33
|
-
const
|
|
29
|
+
const [composing, setComposing] = React.useState(false);
|
|
34
30
|
const isInputEmpty = !input?.text?.length && !input?.files?.length;
|
|
35
|
-
return (React.createElement(FormWrapper, {
|
|
31
|
+
return (React.createElement(FormWrapper, { onValidate: onValidate, allowAttachments: allowAttachments },
|
|
36
32
|
React.createElement(View, { as: "form", className: ComponentClassName.AIConversationForm, onSubmit: handleSubmit },
|
|
37
33
|
allowAttachments ? (React.createElement(Button, { className: ComponentClassName.AIConversationFormAttach, onClick: () => {
|
|
38
34
|
hiddenInput?.current?.click();
|
|
@@ -43,24 +39,20 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, }) => {
|
|
|
43
39
|
React.createElement("span", null, attachIcon),
|
|
44
40
|
React.createElement(VisuallyHidden, null,
|
|
45
41
|
React.createElement("input", { type: "file", tabIndex: -1, ref: hiddenInput, onChange: (e) => {
|
|
46
|
-
|
|
47
|
-
if (!files || files.length === 0) {
|
|
42
|
+
if (!e.target.files || e.target.files.length === 0) {
|
|
48
43
|
return;
|
|
49
44
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}));
|
|
54
|
-
}, 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) => {
|
|
45
|
+
onValidate(Array.from(e.target.files));
|
|
46
|
+
}, multiple: true, accept: ".jpeg,.png,.webp,.gif", "data-testid": "hidden-file-input" })))) : null,
|
|
47
|
+
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
48
|
// Submit on enter key if shift is not pressed also
|
|
57
|
-
const shouldSubmit = !e.shiftKey && e.key === 'Enter';
|
|
49
|
+
const shouldSubmit = !e.shiftKey && e.key === 'Enter' && !composing;
|
|
58
50
|
if (shouldSubmit && isHTMLFormElement(e.target)) {
|
|
59
51
|
e.target.form.requestSubmit();
|
|
60
52
|
e.preventDefault();
|
|
61
53
|
}
|
|
62
54
|
}, onChange: (e) => {
|
|
63
|
-
setInput((prevValue) => ({
|
|
55
|
+
setInput?.((prevValue) => ({
|
|
64
56
|
...prevValue,
|
|
65
57
|
text: e.target.value,
|
|
66
58
|
}));
|
|
@@ -70,6 +62,7 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, }) => {
|
|
|
70
62
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
71
63
|
isDisabled: isLoading || isInputEmpty },
|
|
72
64
|
React.createElement("span", null, sendIcon))),
|
|
65
|
+
error ? (React.createElement(Message, { className: ComponentClassName.AIConversationFormError, variation: "plain", colorTheme: "warning" }, error)) : null,
|
|
73
66
|
React.createElement(Attachments, { setInput: setInput, files: input?.files })));
|
|
74
67
|
};
|
|
75
68
|
|
|
@@ -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/
|
|
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
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return (React.createElement(View, { className:
|
|
37
|
-
React.createElement(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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,
|
|
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
|
-
|
|
63
|
-
|
|
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 };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const contentFromEvents = (contentBlocks) => {
|
|
2
|
+
if (!contentBlocks)
|
|
3
|
+
return [];
|
|
4
|
+
return contentBlocks.map((contentBlock) => {
|
|
5
|
+
const isTextBlock = contentBlock.some((event) => event.text);
|
|
6
|
+
if (isTextBlock) {
|
|
7
|
+
return {
|
|
8
|
+
text: contentBlock
|
|
9
|
+
.map((event) => {
|
|
10
|
+
return event.text;
|
|
11
|
+
})
|
|
12
|
+
.join(''),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// tool use is never chunked
|
|
16
|
+
if (contentBlock[0].toolUse) {
|
|
17
|
+
return { toolUse: contentBlock[0].toolUse };
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export { contentFromEvents };
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { createUseAIGeneration } from './useAIGeneration.mjs';
|
|
2
2
|
import { createUseAIConversation } from './useAIConversation.mjs';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* @experimental
|
|
6
|
-
*/
|
|
7
4
|
function createAIHooks(_client) {
|
|
8
5
|
const useAIConversation = createUseAIConversation(_client);
|
|
9
6
|
const useAIGeneration = createUseAIGeneration(_client);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
async function exhaustivelyListMessages({ conversation, messages = [], nextToken, }) {
|
|
2
|
+
const result = await conversation.listMessages({ nextToken });
|
|
3
|
+
if (result.data) {
|
|
4
|
+
messages?.push(...result.data);
|
|
5
|
+
}
|
|
6
|
+
if (result.nextToken) {
|
|
7
|
+
return exhaustivelyListMessages({
|
|
8
|
+
conversation,
|
|
9
|
+
messages,
|
|
10
|
+
nextToken: result.nextToken,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
...result,
|
|
15
|
+
data: messages,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { exhaustivelyListMessages };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// default state
|
|
2
|
+
const INITIAL_STATE = {
|
|
3
|
+
hasError: false,
|
|
4
|
+
isLoading: false,
|
|
5
|
+
messages: undefined,
|
|
6
|
+
};
|
|
7
|
+
const LOADING_STATE = {
|
|
8
|
+
hasError: false,
|
|
9
|
+
isLoading: true,
|
|
10
|
+
messages: undefined,
|
|
11
|
+
};
|
|
12
|
+
const ERROR_STATE = { hasError: true, isLoading: false };
|
|
13
|
+
|
|
14
|
+
export { ERROR_STATE, INITIAL_STATE, LOADING_STATE };
|