@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,7 @@
1
1
  import React__default from 'react';
2
+ import { Image } from '@aws-amplify/ui-react';
2
3
  import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
4
+ import '../../context/AIContextContext.mjs';
3
5
  import '../../context/ActionsContext.mjs';
4
6
  import '../../context/AvatarsContext.mjs';
5
7
  import '../../context/ConversationInputContext.mjs';
@@ -14,66 +16,59 @@ import '../../context/SendMessageContext.mjs';
14
16
  import { MessageRendererContext } from '../../context/MessageRenderContext.mjs';
15
17
  import '../../context/AttachmentContext.mjs';
16
18
  import '../../context/WelcomeMessageContext.mjs';
19
+ import { FallbackComponentContext } from '../../context/FallbackComponentContext.mjs';
17
20
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
18
21
  import { convertBufferToBase64 } from '../../utils.mjs';
19
22
  import { ActionsBarControl } from './ActionsBarControl.mjs';
20
23
  import { AvatarControl } from './AvatarControl.mjs';
24
+ import { classNames } from '@aws-amplify/ui';
21
25
 
22
- const { Image, Span, Text, View } = AIConversationElements;
23
- const MESSAGES_BLOCK = 'ai-messages';
24
- const MESSAGE_BLOCK = 'ai-message';
25
- const MediaContentBase = withBaseElementProps(Image, {
26
- alt: 'Image attachment',
27
- });
28
- const MediaContent = React__default.forwardRef(function MediaContent(props, ref) {
26
+ const { Text, View } = AIConversationElements;
27
+ const MESSAGES_BLOCK = 'amplify-ai-conversation__message__list';
28
+ const MESSAGE_BLOCK = 'amplify-ai-conversation__message';
29
+ const MediaContent = (props) => {
29
30
  const variant = React__default.useContext(MessageVariantContext);
30
31
  const role = React__default.useContext(RoleContext);
31
- return (React__default.createElement(MediaContentBase, { ref: ref, className: `${MESSAGE_BLOCK}__image ${MESSAGE_BLOCK}__image--${variant} ${MESSAGE_BLOCK}__image--${role}`, ...props }));
32
- });
32
+ return (React__default.createElement(Image, { className: classNames(`${MESSAGE_BLOCK}__image`, variant && `${MESSAGE_BLOCK}__image--${variant}`, `${MESSAGE_BLOCK}__image--${role}`), ...props }));
33
+ };
33
34
  const TextContent = React__default.forwardRef(function TextContent(props, ref) {
34
35
  return React__default.createElement(Text, { ref: ref, className: `${MESSAGE_BLOCK}__text`, ...props });
35
36
  });
36
- const ContentContainer = React__default.forwardRef(function ContentContainer(props, ref) {
37
- const variant = React__default.useContext(MessageVariantContext);
38
- return (React__default.createElement(View, { "data-testid": 'content', className: `${MESSAGE_BLOCK}__content ${MESSAGE_BLOCK}__content--${variant}`, ref: ref, ...props }));
39
- });
40
37
  const ToolContent = ({ toolUse, }) => {
41
- const responseComponents = React__default.useContext(ResponseComponentsContext);
38
+ const responseComponents = React__default.useContext(ResponseComponentsContext) ?? {};
39
+ const FallbackComponent = React__default.useContext(FallbackComponentContext);
42
40
  // For now tool use is limited to custom response components
43
41
  const { name, input } = toolUse;
44
- if (!responseComponents ||
45
- !name ||
46
- !name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
42
+ if (!name || !name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
47
43
  return;
48
44
  }
49
45
  else {
50
46
  const response = responseComponents[name];
51
- const CustomComponent = response.component;
52
- return React__default.createElement(CustomComponent, { ...input });
47
+ if (response) {
48
+ const CustomComponent = response.component;
49
+ return React__default.createElement(CustomComponent, { ...input });
50
+ }
51
+ // fallback if there is a UI component message but we don't have
52
+ // a React component that matches
53
+ if (FallbackComponent) {
54
+ return React__default.createElement(FallbackComponent, { ...input });
55
+ }
53
56
  }
54
57
  };
55
58
  const MessageControl = ({ message }) => {
56
59
  const messageRenderer = React__default.useContext(MessageRendererContext);
57
- return (React__default.createElement(ContentContainer, null, message.content.map((content, index) => {
60
+ return (React__default.createElement(React__default.Fragment, null, message.content.map((content, index) => {
58
61
  if (content.text) {
59
62
  return messageRenderer?.text ? (React__default.createElement(React__default.Fragment, { key: index }, messageRenderer.text({ text: content.text }))) : (React__default.createElement(TextContent, { "data-testid": 'text-content', key: index }, content.text));
60
63
  }
61
64
  else if (content.image) {
62
- return messageRenderer?.image ? (React__default.createElement(React__default.Fragment, { key: index }, messageRenderer?.image({ image: content.image }))) : (React__default.createElement(MediaContent, { "data-testid": 'image-content', key: index, src: convertBufferToBase64(content.image?.source.bytes, content.image?.format) }));
65
+ return messageRenderer?.image ? (React__default.createElement(React__default.Fragment, { key: index }, messageRenderer?.image({ image: content.image }))) : (React__default.createElement(MediaContent, { "data-testid": 'image-content', key: index, alt: "", src: convertBufferToBase64(content.image?.source.bytes, content.image?.format) }));
63
66
  }
64
67
  else if (content.toolUse) {
65
68
  return React__default.createElement(ToolContent, { toolUse: content.toolUse, key: index });
66
69
  }
67
70
  })));
68
71
  };
69
- MessageControl.Container = ContentContainer;
70
- MessageControl.MediaContent = MediaContent;
71
- MessageControl.TextContent = TextContent;
72
- const Separator = withBaseElementProps(Span, {
73
- 'aria-hidden': true,
74
- children: '|',
75
- className: `${MESSAGE_BLOCK}__separator`,
76
- });
77
72
  const Timestamp = withBaseElementProps(Text, {
78
73
  className: `${MESSAGE_BLOCK}__timestamp`,
79
74
  });
@@ -134,7 +129,6 @@ const MessagesControl = () => {
134
129
  React__default.createElement(MessageContainer, { "data-testid": `message`, key: `message-${index}`, tabIndex: focusedItemIndex === index ? 0 : -1, onFocus: () => handleFocus(index), onKeyDown: (event) => onKeyDown(index, event), ref: (el) => (messagesRef.current[index] = el) },
135
130
  React__default.createElement(HeaderContainer, null,
136
131
  React__default.createElement(AvatarControl, null),
137
- React__default.createElement(Separator, null),
138
132
  React__default.createElement(Timestamp, null, getMessageTimestampText(new Date(message.createdAt)))),
139
133
  React__default.createElement(MessageControl, { message: message }),
140
134
  message.role === 'assistant' ? (React__default.createElement(ActionsBarControl, { message: message, focusable: focusedItemIndex === index })) : null)));
@@ -146,6 +140,5 @@ MessagesControl.Container = MessageContainer;
146
140
  MessagesControl.HeaderContainer = HeaderContainer;
147
141
  MessagesControl.Layout = Layout;
148
142
  MessagesControl.Message = MessageControl;
149
- MessagesControl.Separator = Separator;
150
143
 
151
144
  export { MessageControl, MessagesControl };
@@ -1,5 +1,6 @@
1
1
  import React__default from 'react';
2
2
  import { withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
3
+ import '../../context/AIContextContext.mjs';
3
4
  import '../../context/ActionsContext.mjs';
4
5
  import '../../context/AvatarsContext.mjs';
5
6
  import { ConversationInputContext } from '../../context/ConversationInputContext.mjs';
@@ -14,6 +15,7 @@ import '../../context/SendMessageContext.mjs';
14
15
  import '../../context/MessageRenderContext.mjs';
15
16
  import '../../context/AttachmentContext.mjs';
16
17
  import '../../context/WelcomeMessageContext.mjs';
18
+ import '../../context/FallbackComponentContext.mjs';
17
19
  import { AIConversationElements } from '../../context/elements/definitions.mjs';
18
20
 
19
21
  const { View, Button } = AIConversationElements;
@@ -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, setInput, }) => {
14
+ const FormWrapper = ({ children, allowAttachments, onValidate, }) => {
16
15
  if (allowAttachments) {
17
16
  return (React.createElement(DropZone, { className: ComponentClassName.AIConversationFormDropzone, onDropComplete: ({ acceptedFiles }) => {
18
- setInput?.((prevInput) => ({
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 isLoading = React.useContext(LoadingContext);
29
+ const [composing, setComposing] = React.useState(false);
34
30
  const isInputEmpty = !input?.text?.length && !input?.files?.length;
35
- return (React.createElement(FormWrapper, { allowAttachments: allowAttachments, setInput: setInput },
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
- const { files } = e.target;
47
- if (!files || files.length === 0) {
42
+ if (!e.target.files || e.target.files.length === 0) {
48
43
  return;
49
44
  }
50
- setInput((prevValue) => ({
51
- ...prevValue,
52
- files: [...(prevValue?.files ?? []), ...Array.from(files)],
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/ActionsContext.mjs';
4
+ import '../../context/AIContextContext.mjs';
5
+ import { ActionsContext } from '../../context/ActionsContext.mjs';
5
6
  import { AvatarsContext } from '../../context/AvatarsContext.mjs';
6
7
  import '../../context/ConversationInputContext.mjs';
7
8
  import { RoleContext } from '../../context/MessagesContext.mjs';
@@ -15,9 +16,20 @@ import '../../context/SendMessageContext.mjs';
15
16
  import '../../context/MessageRenderContext.mjs';
16
17
  import '../../context/AttachmentContext.mjs';
17
18
  import '../../context/WelcomeMessageContext.mjs';
19
+ import '../../context/FallbackComponentContext.mjs';
18
20
  import '../../context/elements/definitions.mjs';
19
21
  import { ComponentClassName, classNames, classNameModifier } from '@aws-amplify/ui';
20
22
 
23
+ const PlaceholderMessage = ({ role }) => {
24
+ const variant = React.useContext(MessageVariantContext);
25
+ return (React.createElement(View, { className: classNames(ComponentClassName.AIConversationMessage, classNameModifier(ComponentClassName.AIConversationMessage, variant), classNameModifier(ComponentClassName.AIConversationMessage, role)) },
26
+ React.createElement(View, { className: ComponentClassName.AIConversationMessageAvatar },
27
+ React.createElement(Avatar, null)),
28
+ React.createElement(View, { className: ComponentClassName.AIConversationMessageBody },
29
+ React.createElement(Placeholder, { width: "25%" }),
30
+ React.createElement(Placeholder, { width: "50%" }),
31
+ React.createElement(Placeholder, { width: "25%" }))));
32
+ };
21
33
  const MessageMeta = ({ message }) => {
22
34
  // need to pass this in as props in order for it to be overridable
23
35
  const avatars = React.useContext(AvatarsContext);
@@ -29,29 +41,30 @@ const MessageMeta = ({ message }) => {
29
41
  React.createElement(Text, { className: ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username),
30
42
  React.createElement(Text, { className: ComponentClassName.AIConversationMessageSenderTimestamp }, getMessageTimestampText(new Date(message.createdAt)))));
31
43
  };
32
- const LoadingMessage = () => {
33
- const avatars = React.useContext(AvatarsContext);
34
- const variant = React.useContext(MessageVariantContext);
35
- const avatar = avatars?.ai;
36
- return (React.createElement(View, { className: classNames(ComponentClassName.AIConversationMessage, classNameModifier(ComponentClassName.AIConversationMessage, variant), classNameModifier(ComponentClassName.AIConversationMessage, 'assistant')) },
37
- React.createElement(View, { className: ComponentClassName.AIConversationMessageAvatar },
38
- React.createElement(Avatar, { isLoading: true }, avatar?.avatar)),
39
- React.createElement(View, { className: ComponentClassName.AIConversationMessageBody },
40
- React.createElement(View, { className: ComponentClassName.AIConversationMessageSender },
41
- React.createElement(Text, { className: ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username)))));
44
+ const MessageActions = ({ message }) => {
45
+ const actions = React.useContext(ActionsContext);
46
+ if (!actions)
47
+ return null;
48
+ return (React.createElement(View, { className: ComponentClassName.AIConversationMessageActions }, actions.map((action, i) => {
49
+ return (React.createElement(Button, { key: i, size: "small", onClick: () => {
50
+ action.handler(message);
51
+ } }, action.component));
52
+ })));
42
53
  };
43
54
  const Message = ({ message }) => {
44
55
  const avatars = React.useContext(AvatarsContext);
45
56
  const variant = React.useContext(MessageVariantContext);
57
+ const { isLoading } = message;
46
58
  const avatar = message.role === 'assistant' ? avatars?.ai : avatars?.user;
47
59
  return (React.createElement(RoleContext.Provider, { value: message.role },
48
60
  React.createElement(View, { className: classNames(ComponentClassName.AIConversationMessage, classNameModifier(ComponentClassName.AIConversationMessage, variant), classNameModifier(ComponentClassName.AIConversationMessage, message.role)) },
49
61
  React.createElement(View, { className: ComponentClassName.AIConversationMessageAvatar },
50
- React.createElement(Avatar, null, avatar?.avatar)),
62
+ React.createElement(Avatar, { isLoading: isLoading }, avatar?.avatar)),
51
63
  React.createElement(View, { className: ComponentClassName.AIConversationMessageBody },
52
64
  React.createElement(MessageMeta, { message: message }),
53
65
  React.createElement(View, { className: ComponentClassName.AIConversationMessageContent },
54
- React.createElement(MessageControl, { message: message }))))));
66
+ React.createElement(MessageControl, { message: message })),
67
+ message.role === 'assistant' ? (React.createElement(MessageActions, { message: message })) : null))));
55
68
  };
56
69
  const MessageList = ({ messages, }) => {
57
70
  const isLoading = React.useContext(LoadingContext);
@@ -59,8 +72,10 @@ const MessageList = ({ messages, }) => {
59
72
  content.text ??
60
73
  content.toolUse?.name.startsWith(RESPONSE_COMPONENT_PREFIX))) ?? [];
61
74
  return (React.createElement(View, { className: ComponentClassName.AIConversationMessageList },
62
- messagesWithRenderableContent.map((message, i) => (React.createElement(Message, { key: `message-${i}`, message: message }))),
63
- isLoading ? React.createElement(LoadingMessage, null) : null));
75
+ isLoading ? (React.createElement(React.Fragment, null,
76
+ React.createElement(PlaceholderMessage, { role: "user" }),
77
+ React.createElement(PlaceholderMessage, { role: "assistant" }))) : null,
78
+ messagesWithRenderableContent.map((message, i) => (React.createElement(Message, { key: `message-${i}`, message: message })))));
64
79
  };
65
80
 
66
81
  export { MessageList };
@@ -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 };