@aws-amplify/ui-react-ai 0.3.2 → 1.0.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 (71) hide show
  1. package/dist/esm/components/AIConversation/AIConversation.mjs +21 -33
  2. package/dist/esm/components/AIConversation/AIConversationProvider.mjs +22 -16
  3. package/dist/esm/components/AIConversation/context/AIContextContext.mjs +8 -0
  4. package/dist/esm/components/AIConversation/context/FallbackComponentContext.mjs +8 -0
  5. package/dist/esm/components/AIConversation/context/MessageRenderContext.mjs +9 -0
  6. package/dist/esm/components/AIConversation/context/ResponseComponentsContext.mjs +6 -2
  7. package/dist/esm/components/AIConversation/context/WelcomeMessageContext.mjs +8 -0
  8. package/dist/esm/components/AIConversation/createAIConversation.mjs +18 -22
  9. package/dist/esm/components/AIConversation/views/Controls/ActionsBarControl.mjs +6 -2
  10. package/dist/esm/components/AIConversation/views/Controls/AttachFileControl.mjs +5 -0
  11. package/dist/esm/components/AIConversation/views/Controls/AttachmentListControl.mjs +5 -0
  12. package/dist/esm/components/AIConversation/views/Controls/AvatarControl.mjs +5 -0
  13. package/dist/esm/components/AIConversation/views/Controls/DefaultMessageControl.mjs +31 -0
  14. package/dist/esm/components/AIConversation/views/Controls/{FieldControl.mjs → FormControl.mjs} +22 -13
  15. package/dist/esm/components/AIConversation/views/Controls/MessagesControl.mjs +42 -42
  16. package/dist/esm/components/AIConversation/views/Controls/PromptControl.mjs +9 -36
  17. package/dist/esm/components/AIConversation/views/default/Form.mjs +4 -5
  18. package/dist/esm/components/AIConversation/views/default/MessageList.mjs +34 -16
  19. package/dist/esm/components/AIConversation/views/default/PromptList.mjs +1 -1
  20. package/dist/esm/hooks/contentFromEvents.mjs +22 -0
  21. package/dist/esm/hooks/createAIHooks.mjs +0 -3
  22. package/dist/esm/hooks/exhaustivelyListMessages.mjs +19 -0
  23. package/dist/esm/hooks/shared.mjs +14 -0
  24. package/dist/esm/hooks/useAIConversation.mjs +246 -106
  25. package/dist/esm/hooks/useAIGeneration.mjs +1 -8
  26. package/dist/esm/index.mjs +0 -1
  27. package/dist/esm/version.mjs +3 -0
  28. package/dist/index.js +569 -444
  29. package/dist/types/components/AIConversation/AIConversation.d.ts +2 -19
  30. package/dist/types/components/AIConversation/AIConversationProvider.d.ts +2 -3
  31. package/dist/types/components/AIConversation/context/AIContextContext.d.ts +6 -0
  32. package/dist/types/components/AIConversation/context/ControlsContext.d.ts +1 -0
  33. package/dist/types/components/AIConversation/context/FallbackComponentContext.d.ts +7 -0
  34. package/dist/types/components/AIConversation/context/MessageRenderContext.d.ts +5 -0
  35. package/dist/types/components/AIConversation/context/ResponseComponentsContext.d.ts +2 -2
  36. package/dist/types/components/AIConversation/context/WelcomeMessageContext.d.ts +8 -0
  37. package/dist/types/components/AIConversation/context/elements/definitions.d.ts +1 -1
  38. package/dist/types/components/AIConversation/context/index.d.ts +5 -0
  39. package/dist/types/components/AIConversation/createAIConversation.d.ts +0 -3
  40. package/dist/types/components/AIConversation/index.d.ts +2 -1
  41. package/dist/types/components/AIConversation/types.d.ts +24 -36
  42. package/dist/types/components/AIConversation/utils.d.ts +2 -2
  43. package/dist/types/components/AIConversation/views/Controls/DefaultMessageControl.d.ts +2 -0
  44. package/dist/types/components/AIConversation/views/Controls/{FieldControl.d.ts → FormControl.d.ts} +2 -2
  45. package/dist/types/components/AIConversation/views/Controls/MessagesControl.d.ts +3 -9
  46. package/dist/types/components/AIConversation/views/Controls/PromptControl.d.ts +0 -3
  47. package/dist/types/components/AIConversation/views/Controls/index.d.ts +3 -4
  48. package/dist/types/components/AIConversation/views/default/Form.d.ts +1 -1
  49. package/dist/types/components/AIConversation/views/default/MessageList.d.ts +1 -1
  50. package/dist/types/components/AIConversation/views/default/PromptList.d.ts +1 -1
  51. package/dist/types/components/AIConversation/views/index.d.ts +2 -3
  52. package/dist/types/hooks/contentFromEvents.d.ts +2 -0
  53. package/dist/types/hooks/createAIHooks.d.ts +0 -3
  54. package/dist/types/hooks/exhaustivelyListMessages.d.ts +8 -0
  55. package/dist/types/hooks/index.d.ts +1 -2
  56. package/dist/types/hooks/shared.d.ts +23 -0
  57. package/dist/types/hooks/useAIConversation.d.ts +6 -4
  58. package/dist/types/hooks/useAIGeneration.d.ts +3 -13
  59. package/dist/types/index.d.ts +1 -1
  60. package/dist/types/types.d.ts +38 -7
  61. package/dist/types/version.d.ts +1 -0
  62. package/package.json +20 -6
  63. package/dist/ai-conversation-styles.css +0 -195
  64. package/dist/ai-conversation-styles.js +0 -2
  65. package/dist/esm/components/AIConversation/views/Controls/HeaderControl.mjs +0 -34
  66. package/dist/esm/components/AIConversation/views/ConversationView.mjs +0 -20
  67. package/dist/esm/hooks/AIContextProvider.mjs +0 -20
  68. package/dist/types/ai-conversation-styles.d.ts +0 -1
  69. package/dist/types/components/AIConversation/views/Controls/HeaderControl.d.ts +0 -9
  70. package/dist/types/components/AIConversation/views/ConversationView.d.ts +0 -2
  71. package/dist/types/hooks/AIContextProvider.d.ts +0 -17
package/dist/index.js CHANGED
@@ -29,92 +29,9 @@ function _interopNamespace(e) {
29
29
 
30
30
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
31
31
 
32
- const DEFAULT_ICON_PATHS = {
33
- attach: 'M720-330q0 104-73 177T470-80q-104 0-177-73t-73-177v-370q0-75 52.5-127.5T400-880q75 0 127.5 52.5T580-700v350q0 46-32 78t-78 32q-46 0-78-32t-32-78v-370h80v370q0 13 8.5 21.5T470-320q13 0 21.5-8.5T500-350v-350q-1-42-29.5-71T400-800q-42 0-71 29t-29 71v370q-1 71 49 120.5T470-160q70 0 119-49.5T640-330v-390h80v390Z',
34
- close: 'm256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z',
35
- image: 'M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm40-80h480L570-480 450-320l-90-120-120 160Zm-40 80v-560 560Z',
36
- 'send-message': 'M120-160v-640l760 320-760 320Zm80-120 474-200-474-200v140l240 60-240 60v140Zm0 0v-400 400Z',
37
- 'user-avatar': 'M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-160v-112q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v112H160Zm80-80h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z',
38
- };
39
- const DEFAULT_ICON_ATTRIBUTES = {
40
- 'aria-hidden': true,
41
- width: '24',
42
- height: '24',
43
- // `viewBox` coordinates map to `path` data in DEFAULT_ICON_PATHS
44
- viewBox: '0 -960 960 960',
45
- fill: 'none',
46
- xmlns: 'http://www.w3.org/2000/svg',
47
- };
48
- const BaseIconElement = elements.defineBaseElement({
49
- type: 'svg',
50
- displayName: 'Icon',
51
- });
52
- const getIconProps = ({ variant, ...props }) => {
53
- const pathData = variant ? DEFAULT_ICON_PATHS[variant] : undefined;
54
- const children = pathData ? (React__namespace["default"].createElement("path", { d: pathData, fill: "currentColor" })) : undefined;
55
- return {
56
- ...DEFAULT_ICON_ATTRIBUTES,
57
- ...props,
58
- children: props.children ?? children,
59
- variant,
60
- };
61
- };
62
- const IconElement = elements.withBaseElementProps(BaseIconElement, getIconProps);
63
-
64
- const LabelElement$1 = elements.defineBaseElement({
65
- type: 'label',
66
- displayName: 'Label',
67
- });
68
- const TextElement = elements.defineBaseElement({
69
- type: 'p',
70
- displayName: 'Text',
71
- });
72
- const UnorderedListElement = elements.defineBaseElement({
73
- type: 'ul',
74
- displayName: 'UnorderedList',
75
- });
76
- const ListItemElement = elements.defineBaseElement({
77
- type: 'li',
78
- displayName: 'ListItem',
79
- });
80
- const HeadingElement = elements.defineBaseElement({
81
- type: 'h2',
82
- displayName: 'Title',
83
- });
84
- const ImageElement = elements.defineBaseElement({
85
- type: 'img',
86
- displayName: 'Image',
87
- });
88
- const InputElement = elements.defineBaseElement({
89
- type: 'input',
90
- displayName: 'Input',
91
- });
92
- const ButtonElement = elements.defineBaseElement({ type: 'button', displayName: 'Button' });
93
- const ViewElement = elements.defineBaseElement({
94
- type: 'div',
95
- displayName: 'View',
96
- });
97
- const SpanElement = elements.defineBaseElement({
98
- type: 'span',
99
- displayName: 'Span',
100
- });
101
- const TextAreaElement = elements.defineBaseElement({
102
- type: 'textarea',
103
- displayName: 'TextArea',
104
- });
105
- const AIConversationElements = {
106
- Button: ButtonElement,
107
- Heading: HeadingElement,
108
- Icon: IconElement,
109
- Input: InputElement,
110
- Image: ImageElement,
111
- Label: LabelElement$1,
112
- ListItem: ListItemElement,
113
- Span: SpanElement,
114
- Text: TextElement,
115
- TextArea: TextAreaElement,
116
- UnorderedList: UnorderedListElement,
117
- View: ViewElement,
32
+ const AIContextContext = React__namespace["default"].createContext(undefined);
33
+ const AIContextProvider = ({ children, aiContext, }) => {
34
+ return (React__namespace["default"].createElement(AIContextContext.Provider, { value: aiContext }, children));
118
35
  };
119
36
 
120
37
  const ActionsContext = React__namespace["default"].createContext(undefined);
@@ -229,8 +146,12 @@ const convertResponseComponentsToToolConfiguration = (responseComponents) => {
229
146
  const { props } = responseComponents[toolName];
230
147
  const requiredProps = [];
231
148
  Object.keys(props).forEach((propName) => {
232
- if (props[propName].required)
149
+ if (props[propName].required) {
233
150
  requiredProps.push(propName);
151
+ // The inputSchema for a tool needs to not
152
+ // have `required` in the properties
153
+ props[propName].required = undefined;
154
+ }
234
155
  });
235
156
  tools[toolName] = {
236
157
  description: responseComponents[toolName].description,
@@ -253,46 +174,154 @@ const SendMessageContextProvider = ({ children, handleSendMessage, }) => {
253
174
  return (React__namespace["default"].createElement(SendMessageContext.Provider, { value: handleSendMessage }, children));
254
175
  };
255
176
 
256
- const { Button: Button$5, Span: Span$3, View: View$7 } = AIConversationElements;
177
+ const { MessageRendererContext, MessageRendererProvider, useMessageRenderer, } = uiReactCore.createContextUtilities({
178
+ contextName: 'MessageRenderer',
179
+ defaultValue: undefined,
180
+ errorMessage: '`useMessageRenderer` must be used with an AIConversation component',
181
+ });
182
+
183
+ const AttachmentContext = React__namespace.createContext(false);
184
+ const AttachmentProvider = ({ children, allowAttachments, }) => {
185
+ return (React__namespace.createElement(AttachmentContext.Provider, { value: allowAttachments ?? false }, children));
186
+ };
187
+
188
+ const WelcomeMessageContext = React__namespace.createContext(undefined);
189
+ const WelcomeMessageProvider = ({ children, welcomeMessage, }) => {
190
+ return (React__namespace.createElement(WelcomeMessageContext.Provider, { value: welcomeMessage }, children));
191
+ };
192
+
193
+ const FallbackComponentContext = React__namespace["default"].createContext(undefined);
194
+ const FallbackComponentProvider = ({ children, FallbackComponent, }) => {
195
+ return (React__namespace["default"].createElement(FallbackComponentContext.Provider, { value: FallbackComponent }, children));
196
+ };
197
+
198
+ const DEFAULT_ICON_PATHS = {
199
+ attach: 'M720-330q0 104-73 177T470-80q-104 0-177-73t-73-177v-370q0-75 52.5-127.5T400-880q75 0 127.5 52.5T580-700v350q0 46-32 78t-78 32q-46 0-78-32t-32-78v-370h80v370q0 13 8.5 21.5T470-320q13 0 21.5-8.5T500-350v-350q-1-42-29.5-71T400-800q-42 0-71 29t-29 71v370q-1 71 49 120.5T470-160q70 0 119-49.5T640-330v-390h80v390Z',
200
+ close: 'm256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z',
201
+ image: 'M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm40-80h480L570-480 450-320l-90-120-120 160Zm-40 80v-560 560Z',
202
+ 'send-message': 'M120-160v-640l760 320-760 320Zm80-120 474-200-474-200v140l240 60-240 60v140Zm0 0v-400 400Z',
203
+ 'user-avatar': 'M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-160v-112q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v112H160Zm80-80h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z',
204
+ };
205
+ const DEFAULT_ICON_ATTRIBUTES = {
206
+ 'aria-hidden': true,
207
+ width: '24',
208
+ height: '24',
209
+ // `viewBox` coordinates map to `path` data in DEFAULT_ICON_PATHS
210
+ viewBox: '0 -960 960 960',
211
+ fill: 'none',
212
+ xmlns: 'http://www.w3.org/2000/svg',
213
+ };
214
+ const BaseIconElement = elements.defineBaseElement({
215
+ type: 'svg',
216
+ displayName: 'Icon',
217
+ });
218
+ const getIconProps = ({ variant, ...props }) => {
219
+ const pathData = variant ? DEFAULT_ICON_PATHS[variant] : undefined;
220
+ const children = pathData ? (React__namespace["default"].createElement("path", { d: pathData, fill: "currentColor" })) : undefined;
221
+ return {
222
+ ...DEFAULT_ICON_ATTRIBUTES,
223
+ ...props,
224
+ children: props.children ?? children,
225
+ variant,
226
+ };
227
+ };
228
+ const IconElement = elements.withBaseElementProps(BaseIconElement, getIconProps);
229
+
230
+ const LabelElement$1 = elements.defineBaseElement({
231
+ type: 'label',
232
+ displayName: 'Label',
233
+ });
234
+ const TextElement = elements.defineBaseElement({
235
+ type: 'p',
236
+ displayName: 'Text',
237
+ });
238
+ const UnorderedListElement = elements.defineBaseElement({
239
+ type: 'ul',
240
+ displayName: 'UnorderedList',
241
+ });
242
+ const ListItemElement = elements.defineBaseElement({
243
+ type: 'li',
244
+ displayName: 'ListItem',
245
+ });
246
+ const HeadingElement = elements.defineBaseElement({
247
+ type: 'h2',
248
+ displayName: 'Title',
249
+ });
250
+ const ImageElement = elements.defineBaseElement({
251
+ type: 'img',
252
+ displayName: 'Image',
253
+ });
254
+ const InputElement = elements.defineBaseElement({
255
+ type: 'input',
256
+ displayName: 'Input',
257
+ });
258
+ const ButtonElement = elements.defineBaseElement({ type: 'button', displayName: 'Button' });
259
+ const ViewElement = elements.defineBaseElement({
260
+ type: 'div',
261
+ displayName: 'View',
262
+ });
263
+ const SpanElement = elements.defineBaseElement({
264
+ type: 'span',
265
+ displayName: 'Span',
266
+ });
267
+ const TextAreaElement = elements.defineBaseElement({
268
+ type: 'textarea',
269
+ displayName: 'TextArea',
270
+ });
271
+ const AIConversationElements = {
272
+ Button: ButtonElement,
273
+ Heading: HeadingElement,
274
+ Icon: IconElement,
275
+ Input: InputElement,
276
+ Image: ImageElement,
277
+ Label: LabelElement$1,
278
+ ListItem: ListItemElement,
279
+ Span: SpanElement,
280
+ Text: TextElement,
281
+ TextArea: TextAreaElement,
282
+ UnorderedList: UnorderedListElement,
283
+ View: ViewElement,
284
+ };
285
+
286
+ const { Button: Button$4, Span: Span$2, View: View$6 } = AIConversationElements;
257
287
  const ACTIONS_BAR_BLOCK = 'ai-actions-bar';
258
- const ActionIcon = elements.withBaseElementProps(Span$3, {
288
+ const ActionIcon = elements.withBaseElementProps(Span$2, {
259
289
  'aria-hidden': 'true',
260
290
  className: `${ACTIONS_BAR_BLOCK}__icon`,
261
291
  });
262
- const ActionButtonBase = elements.withBaseElementProps(Button$5, {
292
+ const ActionButtonBase = elements.withBaseElementProps(Button$4, {
263
293
  className: `${ACTIONS_BAR_BLOCK}__button`,
264
294
  });
265
295
  const ActionButton = React__namespace["default"].forwardRef(function ActionButton(props, ref) {
266
296
  return React__namespace["default"].createElement(ActionButtonBase, { ...props, ref: ref });
267
297
  });
268
- const Container$4 = elements.withBaseElementProps(View$7, {
298
+ const Container$3 = elements.withBaseElementProps(View$6, {
269
299
  className: `${ACTIONS_BAR_BLOCK}__container`,
270
300
  });
271
301
  const ActionsBarControl = ({ message, focusable, }) => {
272
302
  const actions = React__namespace["default"].useContext(ActionsContext);
273
- return (React__namespace["default"].createElement(Container$4, null, actions?.map((action, index) => (React__namespace["default"].createElement(ActionButton, { "aria-label": action.displayName, key: index, onClick: () => action.handler(message), tabIndex: focusable ? 0 : -1 },
274
- React__namespace["default"].createElement(ActionIcon, { "data-testid": `action-icon-${action.displayName}` }, action.icon))))));
303
+ return (React__namespace["default"].createElement(Container$3, null, actions?.map((action, index) => (React__namespace["default"].createElement(ActionButton, { key: index, onClick: () => action.handler(message), tabIndex: focusable ? 0 : -1 }, action.component)))));
275
304
  };
276
305
  ActionsBarControl.Button = ActionButton;
277
- ActionsBarControl.Container = Container$4;
306
+ ActionsBarControl.Container = Container$3;
278
307
  ActionsBarControl.Icon = ActionIcon;
279
308
 
280
- const { Icon: Icon$5, Span: Span$2, Text: Text$4, View: View$6 } = AIConversationElements;
309
+ const { Icon: Icon$3, Span: Span$1, Text: Text$2, View: View$5 } = AIConversationElements;
281
310
  const AVATAR_BLOCK = 'ai-avatar';
282
- const DEFAULT_USER_ICON = elements.withBaseElementProps(Icon$5, {
311
+ const DEFAULT_USER_ICON = elements.withBaseElementProps(Icon$3, {
283
312
  variant: 'user-avatar',
284
313
  });
285
314
  const DEFAULT_AI_ICON = () => (React__namespace["default"].createElement("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", xmlns: "http://www.w3.org/2000/svg" },
286
315
  React__namespace["default"].createElement("g", { id: "raven-logo" },
287
316
  React__namespace["default"].createElement("path", { id: "Subtract", fillRule: "evenodd", clipRule: "evenodd", d: "M16 1.29833C14.7624 0.583803 13.2376 0.583804 12 1.29833L4.00006 5.91711C2.76246 6.63165 2.00006 7.95216 2.00006 9.38122V18.6188C2.00006 20.0478 2.76246 21.3684 4.00006 22.0829L12 26.7017C13.2376 27.4162 14.7624 27.4162 16 26.7017L24 22.0829C25.2376 21.3684 26 20.0478 26 18.6188V9.38122C26 7.95215 25.2376 6.63164 24 5.91711L16 1.29833ZM14.9379 6.37317C14.6157 5.50255 13.3843 5.50255 13.0622 6.37317L11.4151 10.8243C11.3138 11.098 11.098 11.3138 10.8243 11.4151L6.37317 13.0621C5.50256 13.3843 5.50256 14.6157 6.37317 14.9378L10.8243 16.5849C11.098 16.6862 11.3138 16.902 11.4151 17.1757L13.0622 21.6268C13.3843 22.4974 14.6157 22.4974 14.9379 21.6268L16.5849 17.1757C16.6862 16.902 16.902 16.6862 17.1757 16.5849L21.6268 14.9378C22.4974 14.6157 22.4974 13.3843 21.6268 13.0621L17.1757 11.4151C16.902 11.3138 16.6862 11.098 16.5849 10.8243L14.9379 6.37317Z", fill: "#0D1A26" }))));
288
- const AvatarDisplayName = elements.withBaseElementProps(Text$4, {
317
+ const AvatarDisplayName = elements.withBaseElementProps(Text$2, {
289
318
  className: `${AVATAR_BLOCK}__display-name`,
290
319
  });
291
- const AvatarIcon = elements.withBaseElementProps(Span$2, {
320
+ const AvatarIcon = elements.withBaseElementProps(Span$1, {
292
321
  'aria-hidden': true,
293
322
  className: `${AVATAR_BLOCK}__icon`,
294
323
  });
295
- const Container$3 = elements.withBaseElementProps(View$6, {
324
+ const Container$2 = elements.withBaseElementProps(View$5, {
296
325
  className: `${AVATAR_BLOCK}__container`,
297
326
  });
298
327
  const AvatarControl = () => {
@@ -301,47 +330,18 @@ const AvatarControl = () => {
301
330
  const avatar = role === 'assistant' ? avatars?.ai : avatars?.user;
302
331
  const defaultIcon = role === 'assistant' ? React__namespace["default"].createElement(DEFAULT_AI_ICON, null) : React__namespace["default"].createElement(DEFAULT_USER_ICON, null);
303
332
  const defaultDisplayName = role === 'user' ? 'User' : 'Assistant';
304
- return (React__namespace["default"].createElement(Container$3, { "data-testid": 'avatar' },
333
+ return (React__namespace["default"].createElement(Container$2, { "data-testid": 'avatar' },
305
334
  React__namespace["default"].createElement(AvatarIcon, { "data-testid": `avatar-icon-${role}` }, avatar?.avatar ?? defaultIcon),
306
335
  React__namespace["default"].createElement(AvatarDisplayName, null, avatar?.username ?? defaultDisplayName)));
307
336
  };
308
- AvatarControl.Container = Container$3;
337
+ AvatarControl.Container = Container$2;
309
338
  AvatarControl.DisplayName = AvatarDisplayName;
310
339
  AvatarControl.Icon = AvatarIcon;
311
340
 
312
- const { View: View$5, Button: Button$4, Icon: Icon$4, Text: Text$3 } = AIConversationElements;
313
- const HEADER_BLOCK = 'ai-header';
314
- const HeaderTextBase = elements.withBaseElementProps(Text$3, {
315
- className: `${HEADER_BLOCK}__text`,
316
- });
317
- const HeaderText$1 = React__namespace["default"].forwardRef(function HeaderText(props, ref) {
318
- return React__namespace["default"].createElement(HeaderTextBase, { ...props, ref: ref });
319
- });
320
- const CloseIcon = elements.withBaseElementProps(Icon$4, {
321
- className: `${HEADER_BLOCK}__icon`,
322
- variant: 'close',
323
- });
324
- const CloseButtonBase = elements.withBaseElementProps(Button$4, {
325
- className: `${HEADER_BLOCK}__button`,
326
- });
327
- const CloseButton = React__namespace["default"].forwardRef(function CloseButton(props, ref) {
328
- return React__namespace["default"].createElement(CloseButtonBase, { ...props, ref: ref });
329
- });
330
- const Container$2 = elements.withBaseElementProps(View$5, {
331
- className: `${HEADER_BLOCK}__container`,
332
- });
333
- const HeaderControl = () => (React__namespace["default"].createElement(Container$2, null,
334
- React__namespace["default"].createElement(HeaderText$1, null, "Raven Chat"),
335
- React__namespace["default"].createElement(CloseButton, null,
336
- React__namespace["default"].createElement(CloseIcon, null))));
337
- HeaderControl.Container = Container$2;
338
- HeaderControl.Text = HeaderText$1;
339
- HeaderControl.Button = CloseButton;
340
-
341
- const { Button: Button$3, Icon: Icon$3, View: View$4 } = AIConversationElements;
341
+ const { Button: Button$3, Icon: Icon$2, View: View$4 } = AIConversationElements;
342
342
  const ATTACH_FILE_BLOCK = 'ai-attach-file';
343
343
  const FIELD_BLOCK$1 = 'ai-field';
344
- const AttachFileIcon = elements.withBaseElementProps(Icon$3, {
344
+ const AttachFileIcon = elements.withBaseElementProps(Icon$2, {
345
345
  className: `${ATTACH_FILE_BLOCK}__icon`,
346
346
  variant: 'attach',
347
347
  });
@@ -398,12 +398,12 @@ AttachFileControl.Icon = AttachFileIcon;
398
398
  AttachFileControl.Button = AttachFileButton;
399
399
  AttachFileControl.Container = AttachFileContainer;
400
400
 
401
- const { Button: Button$2, Icon: Icon$2, ListItem, UnorderedList: ListElement, Span: Span$1, Text: Text$2, View: View$3, } = AIConversationElements;
401
+ const { Button: Button$2, Icon: Icon$1, ListItem, UnorderedList: ListElement, Span, Text: Text$1, View: View$3, } = AIConversationElements;
402
402
  const IMAGE_LIST_BLOCK = 'ai-attachment-list';
403
403
  const IMAGE_ITEM_BLOCK = 'ai-attachment';
404
404
  const REMOVE_IMAGE_BLOCK = 'ai-remove-attachment';
405
405
  const IMAGE_TEXT_BLOCK = 'ai-attachment-text';
406
- const RemoveIcon = elements.withBaseElementProps(Icon$2, {
406
+ const RemoveIcon = elements.withBaseElementProps(Icon$1, {
407
407
  className: `${REMOVE_IMAGE_BLOCK}__icon`,
408
408
  variant: 'close',
409
409
  });
@@ -419,17 +419,17 @@ const RemoveButtonControl = ({ onRemove }) => {
419
419
  };
420
420
  RemoveButtonControl.Icon = RemoveIcon;
421
421
  RemoveButtonControl.Button = RemoveButton;
422
- const ImageIcon = elements.withBaseElementProps(Icon$2, {
422
+ const ImageIcon = elements.withBaseElementProps(Icon$1, {
423
423
  className: `${IMAGE_ITEM_BLOCK}__icon`,
424
424
  variant: 'image',
425
425
  });
426
- const FileNameText = elements.withBaseElementProps(Text$2, {
426
+ const FileNameText = elements.withBaseElementProps(Text$1, {
427
427
  className: `${IMAGE_TEXT_BLOCK}__file-name`,
428
428
  });
429
- const FileSizeText = elements.withBaseElementProps(Text$2, {
429
+ const FileSizeText = elements.withBaseElementProps(Text$1, {
430
430
  className: `${IMAGE_TEXT_BLOCK}__file-size`,
431
431
  });
432
- const Separator$1 = elements.withBaseElementProps(Span$1, {
432
+ const Separator = elements.withBaseElementProps(Span, {
433
433
  'aria-hidden': true,
434
434
  className: `${IMAGE_TEXT_BLOCK}__separator`,
435
435
  children: '|',
@@ -440,13 +440,13 @@ const TextContainer = elements.withBaseElementProps(View$3, {
440
440
  const TextControl = ({ fileName, fileSize }) => {
441
441
  return (React__namespace["default"].createElement(TextContainer, null,
442
442
  React__namespace["default"].createElement(FileNameText, null, fileName),
443
- React__namespace["default"].createElement(Separator$1, null),
443
+ React__namespace["default"].createElement(Separator, null),
444
444
  React__namespace["default"].createElement(FileSizeText, null, fileSize)));
445
445
  };
446
446
  TextControl.Container = TextContainer;
447
447
  TextControl.FileName = FileNameText;
448
448
  TextControl.FileSize = FileSizeText;
449
- TextControl.Separator = Separator$1;
449
+ TextControl.Separator = Separator;
450
450
  const Container$1 = elements.withBaseElementProps(ListItem, {
451
451
  className: `${IMAGE_ITEM_BLOCK}__list-item`,
452
452
  });
@@ -480,14 +480,9 @@ const AttachmentListControl = () => {
480
480
  AttachmentListControl.List = UnorderedList;
481
481
  AttachmentListControl.Item = AttachmentControl;
482
482
 
483
- const AttachmentContext = React__namespace.createContext(false);
484
- const AttachmentProvider = ({ children, allowAttachments, }) => {
485
- return (React__namespace.createElement(AttachmentContext.Provider, { value: allowAttachments ?? false }, children));
486
- };
487
-
488
- const { Button: Button$1, Icon: Icon$1, Label: LabelElement, TextArea, View: View$2, } = AIConversationElements;
483
+ const { Button: Button$1, Icon, Label: LabelElement, TextArea, View: View$2, } = AIConversationElements;
489
484
  const FIELD_BLOCK = 'ai-field';
490
- const SendIcon = elements.withBaseElementProps(Icon$1, {
485
+ const SendIcon = elements.withBaseElementProps(Icon, {
491
486
  className: `${FIELD_BLOCK}__icon`,
492
487
  variant: 'send-message',
493
488
  });
@@ -565,13 +560,16 @@ const TextInput = React__namespace["default"].forwardRef(function TextInput(prop
565
560
  const InputContainer = elements.withBaseElementProps(View$2, {
566
561
  className: `${FIELD_BLOCK}__input-container`,
567
562
  });
568
- const FieldControl = () => {
563
+ const FormControl = () => {
569
564
  const { input, setInput } = React__namespace["default"].useContext(ConversationInputContext);
570
565
  const handleSendMessage = React__namespace["default"].useContext(SendMessageContext);
571
566
  const allowAttachments = React__namespace["default"].useContext(AttachmentContext);
572
- const ref = React__namespace["default"].useRef(null);
573
567
  const responseComponents = React__namespace["default"].useContext(ResponseComponentsContext);
568
+ const isLoading = React__namespace["default"].useContext(LoadingContext);
569
+ const aiContext = React__namespace["default"].useContext(AIContextContext);
570
+ const ref = React__namespace["default"].useRef(null);
574
571
  const controls = React__namespace["default"].useContext(ControlsContext);
572
+ const [composing, setComposing] = React__namespace["default"].useState(false);
575
573
  const submitMessage = async () => {
576
574
  ref.current?.reset();
577
575
  const submittedContent = [];
@@ -596,6 +594,7 @@ const FieldControl = () => {
596
594
  if (handleSendMessage) {
597
595
  handleSendMessage({
598
596
  content: submittedContent,
597
+ aiContext: ui.isFunction(aiContext) ? aiContext() : undefined,
599
598
  toolConfiguration: convertResponseComponentsToToolConfiguration(responseComponents),
600
599
  });
601
600
  }
@@ -608,7 +607,7 @@ const FieldControl = () => {
608
607
  };
609
608
  const handleOnKeyDown = (event) => {
610
609
  const { key, shiftKey } = event;
611
- if (key === 'Enter' && !shiftKey) {
610
+ if (key === 'Enter' && !shiftKey && !composing) {
612
611
  event.preventDefault();
613
612
  const hasInput = !!input?.text || (input?.files?.length && input?.files?.length > 0);
614
613
  if (hasInput) {
@@ -617,77 +616,72 @@ const FieldControl = () => {
617
616
  }
618
617
  };
619
618
  if (controls?.Form) {
620
- return (React__namespace["default"].createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, allowAttachments: allowAttachments }));
619
+ return (React__namespace["default"].createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, allowAttachments: allowAttachments, isLoading: isLoading }));
621
620
  }
622
621
  return (React__namespace["default"].createElement("form", { className: `${FIELD_BLOCK}__form`, onSubmit: handleSubmit, method: "post", ref: ref },
623
622
  allowAttachments ? React__namespace["default"].createElement(AttachFileControl, null) : null,
624
623
  React__namespace["default"].createElement(InputContainer, null,
625
624
  React__namespace["default"].createElement(VisuallyHidden, null,
626
625
  React__namespace["default"].createElement(Label, null)),
627
- React__namespace["default"].createElement(TextInput, { onKeyDown: handleOnKeyDown }),
626
+ React__namespace["default"].createElement(TextInput, { onKeyDown: handleOnKeyDown, onCompositionStart: () => setComposing(true), onCompositionEnd: () => setComposing(false) }),
628
627
  React__namespace["default"].createElement(AttachmentListControl, null)),
629
628
  React__namespace["default"].createElement(SendButton, null,
630
629
  React__namespace["default"].createElement(SendIcon, null))));
631
630
  };
632
- FieldControl.AttachFile = AttachFileControl;
633
- FieldControl.InputContainer = InputContainer;
634
- FieldControl.Label = Label;
635
- FieldControl.TextInput = TextInput;
636
- FieldControl.SendButton = SendButton;
637
- FieldControl.SendIcon = SendIcon;
631
+ FormControl.AttachFile = AttachFileControl;
632
+ FormControl.InputContainer = InputContainer;
633
+ FormControl.Label = Label;
634
+ FormControl.TextInput = TextInput;
635
+ FormControl.SendButton = SendButton;
636
+ FormControl.SendIcon = SendIcon;
638
637
 
639
- const { Image, Span, Text: Text$1, View: View$1 } = AIConversationElements;
640
- const MESSAGES_BLOCK = 'ai-messages';
641
- const MESSAGE_BLOCK = 'ai-message';
642
- const MediaContentBase = elements.withBaseElementProps(Image, {
643
- alt: 'Image attachment',
644
- });
645
- const MediaContent = React__namespace["default"].forwardRef(function MediaContent(props, ref) {
638
+ const { Text, View: View$1 } = AIConversationElements;
639
+ const MESSAGES_BLOCK = 'amplify-ai-conversation__message__list';
640
+ const MESSAGE_BLOCK = 'amplify-ai-conversation__message';
641
+ const MediaContent = (props) => {
646
642
  const variant = React__namespace["default"].useContext(MessageVariantContext);
647
643
  const role = React__namespace["default"].useContext(RoleContext);
648
- return (React__namespace["default"].createElement(MediaContentBase, { ref: ref, className: `${MESSAGE_BLOCK}__image ${MESSAGE_BLOCK}__image--${variant} ${MESSAGE_BLOCK}__image--${role}`, ...props }));
649
- });
644
+ return (React__namespace["default"].createElement(uiReact.Image, { className: ui.classNames(`${MESSAGE_BLOCK}__image`, variant && `${MESSAGE_BLOCK}__image--${variant}`, `${MESSAGE_BLOCK}__image--${role}`), ...props }));
645
+ };
650
646
  const TextContent = React__namespace["default"].forwardRef(function TextContent(props, ref) {
651
- return React__namespace["default"].createElement(Text$1, { ref: ref, className: `${MESSAGE_BLOCK}__text`, ...props });
652
- });
653
- const ContentContainer = React__namespace["default"].forwardRef(function ContentContainer(props, ref) {
654
- const variant = React__namespace["default"].useContext(MessageVariantContext);
655
- return (React__namespace["default"].createElement(View$1, { "data-testid": 'content', className: `${MESSAGE_BLOCK}__content ${MESSAGE_BLOCK}__content--${variant}`, ref: ref, ...props }));
647
+ return React__namespace["default"].createElement(Text, { ref: ref, className: `${MESSAGE_BLOCK}__text`, ...props });
656
648
  });
649
+ const ToolContent = ({ toolUse, }) => {
650
+ const responseComponents = React__namespace["default"].useContext(ResponseComponentsContext) ?? {};
651
+ const FallbackComponent = React__namespace["default"].useContext(FallbackComponentContext);
652
+ // For now tool use is limited to custom response components
653
+ const { name, input } = toolUse;
654
+ if (!name || !name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
655
+ return;
656
+ }
657
+ else {
658
+ const response = responseComponents[name];
659
+ if (response) {
660
+ const CustomComponent = response.component;
661
+ return React__namespace["default"].createElement(CustomComponent, { ...input });
662
+ }
663
+ // fallback if there is a UI component message but we don't have
664
+ // a React component that matches
665
+ if (FallbackComponent) {
666
+ return React__namespace["default"].createElement(FallbackComponent, { ...input });
667
+ }
668
+ }
669
+ };
657
670
  const MessageControl = ({ message }) => {
658
- const responseComponents = React__namespace["default"].useContext(ResponseComponentsContext);
659
- return (React__namespace["default"].createElement(ContentContainer, null, message.content.map((content, index) => {
671
+ const messageRenderer = React__namespace["default"].useContext(MessageRendererContext);
672
+ return (React__namespace["default"].createElement(React__namespace["default"].Fragment, null, message.content.map((content, index) => {
660
673
  if (content.text) {
661
- return (React__namespace["default"].createElement(TextContent, { "data-testid": 'text-content', key: index }, content.text));
674
+ return messageRenderer?.text ? (React__namespace["default"].createElement(React__namespace["default"].Fragment, { key: index }, messageRenderer.text({ text: content.text }))) : (React__namespace["default"].createElement(TextContent, { "data-testid": 'text-content', key: index }, content.text));
662
675
  }
663
676
  else if (content.image) {
664
- return (React__namespace["default"].createElement(MediaContent, { "data-testid": 'image-content', key: index, src: convertBufferToBase64(content.image?.source.bytes, content.image?.format) }));
677
+ return messageRenderer?.image ? (React__namespace["default"].createElement(React__namespace["default"].Fragment, { key: index }, messageRenderer?.image({ image: content.image }))) : (React__namespace["default"].createElement(MediaContent, { "data-testid": 'image-content', key: index, alt: "", src: convertBufferToBase64(content.image?.source.bytes, content.image?.format) }));
665
678
  }
666
679
  else if (content.toolUse) {
667
- // For now tool use is limited to custom response components
668
- const { name, input } = content.toolUse;
669
- if (!responseComponents ||
670
- !name ||
671
- !name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
672
- return;
673
- }
674
- else {
675
- const response = responseComponents[name];
676
- const CustomComponent = response.component;
677
- return React__namespace["default"].createElement(CustomComponent, { ...input, key: index });
678
- }
680
+ return React__namespace["default"].createElement(ToolContent, { toolUse: content.toolUse, key: index });
679
681
  }
680
682
  })));
681
683
  };
682
- MessageControl.Container = ContentContainer;
683
- MessageControl.MediaContent = MediaContent;
684
- MessageControl.TextContent = TextContent;
685
- const Separator = elements.withBaseElementProps(Span, {
686
- 'aria-hidden': true,
687
- children: '|',
688
- className: `${MESSAGE_BLOCK}__separator`,
689
- });
690
- const Timestamp = elements.withBaseElementProps(Text$1, {
684
+ const Timestamp = elements.withBaseElementProps(Text, {
691
685
  className: `${MESSAGE_BLOCK}__timestamp`,
692
686
  });
693
687
  const HeaderContainer = React__namespace["default"].forwardRef(function HeaderContainer(props, ref) {
@@ -703,7 +697,7 @@ const Layout = React__namespace["default"].forwardRef(function Layout(props, ref
703
697
  const variant = React__namespace["default"].useContext(MessageVariantContext);
704
698
  return (React__namespace["default"].createElement(View$1, { ref: ref, className: `${MESSAGES_BLOCK}__container ${MESSAGES_BLOCK}__container--${variant}`, "aria-live": 'assertive', ...props }));
705
699
  });
706
- const MessagesControl = ({ renderMessage }) => {
700
+ const MessagesControl = () => {
707
701
  const messages = React__namespace["default"].useContext(MessagesContext);
708
702
  const controls = React__namespace["default"].useContext(ControlsContext);
709
703
  const { getMessageTimestampText } = useConversationDisplayText();
@@ -743,11 +737,10 @@ const MessagesControl = ({ renderMessage }) => {
743
737
  content.text ??
744
738
  content.toolUse?.name.startsWith(RESPONSE_COMPONENT_PREFIX))) ?? [];
745
739
  return (React__namespace["default"].createElement(Layout, null, messagesWithRenderableContent?.map((message, index) => {
746
- return renderMessage ? (renderMessage(message)) : (React__namespace["default"].createElement(RoleContext.Provider, { value: message.role, key: `message-${index}` },
740
+ return (React__namespace["default"].createElement(RoleContext.Provider, { value: message.role, key: `message-${index}` },
747
741
  React__namespace["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) },
748
742
  React__namespace["default"].createElement(HeaderContainer, null,
749
743
  React__namespace["default"].createElement(AvatarControl, null),
750
- React__namespace["default"].createElement(Separator, null),
751
744
  React__namespace["default"].createElement(Timestamp, null, getMessageTimestampText(new Date(message.createdAt)))),
752
745
  React__namespace["default"].createElement(MessageControl, { message: message }),
753
746
  message.role === 'assistant' ? (React__namespace["default"].createElement(ActionsBarControl, { message: message, focusable: focusedItemIndex === index })) : null)));
@@ -759,9 +752,8 @@ MessagesControl.Container = MessageContainer;
759
752
  MessagesControl.HeaderContainer = HeaderContainer;
760
753
  MessagesControl.Layout = Layout;
761
754
  MessagesControl.Message = MessageControl;
762
- MessagesControl.Separator = Separator;
763
755
 
764
- const { View, Button, Text, Heading, Icon } = AIConversationElements;
756
+ const { View, Button } = AIConversationElements;
765
757
  const PROMPT_BLOCK = 'ai-prompts';
766
758
  const PROMPT_CONTROL = `${PROMPT_BLOCK}__prompt`;
767
759
  const PROMPT_CARD = `${PROMPT_CONTROL}__card`;
@@ -769,25 +761,6 @@ const PromptCard = elements.withBaseElementProps(Button, {
769
761
  className: PROMPT_CARD,
770
762
  type: 'button',
771
763
  });
772
- const AIIconProps = () => ({
773
- children: (React__namespace["default"].createElement(React__namespace["default"].Fragment, null,
774
- React__namespace["default"].createElement("path", { d: "M17.5 1.64858C19.047 0.755412 20.953 0.755412 22.5 1.64858L34.6428 8.65923C36.1898 9.55239 37.1428 11.203 37.1428 12.9894V27.0107C37.1428 28.797 36.1898 30.4476 34.6428 31.3408L22.5 38.3514C20.953 39.2446 19.047 39.2446 17.5 38.3514L5.35718 31.3408C3.81017 30.4476 2.85718 28.797 2.85718 27.0107V12.9894C2.85718 11.203 3.81017 9.55239 5.35718 8.65923L17.5 1.64858Z", fill: "white" }),
775
- React__namespace["default"].createElement("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M22.5 1.64851C20.953 0.755347 19.047 0.755347 17.5 1.64851L5.35718 8.65916C3.81017 9.55233 2.85718 11.203 2.85718 12.9893V27.0106C2.85718 28.7969 3.81017 30.4476 5.35718 31.3407L17.5 38.3514C19.047 39.2445 20.953 39.2445 22.5 38.3514L34.6428 31.3407C36.1898 30.4476 37.1428 28.7969 37.1428 27.0106V12.9893C37.1428 11.203 36.1898 9.55233 34.6428 8.65916L22.5 1.64851ZM20.9378 8.01826C20.6156 7.14764 19.3843 7.14764 19.0621 8.01825L16.2388 15.648C16.1375 15.9217 15.9217 16.1375 15.648 16.2388L8.01826 19.0621C7.14765 19.3842 7.14765 20.6156 8.01826 20.9378L15.648 23.7611C15.9217 23.8623 16.1375 24.0782 16.2388 24.3519L19.0621 31.9816C19.3843 32.8522 20.6156 32.8522 20.9378 31.9816L23.7611 24.3519C23.8624 24.0782 24.0782 23.8623 24.3519 23.7611L31.9816 20.9378C32.8523 20.6156 32.8523 19.3842 31.9816 19.0621L24.3519 16.2388C24.0782 16.1375 23.8624 15.9217 23.7611 15.648L20.9378 8.01826Z", fill: "url(#paint0_linear_395_1815)" }),
776
- React__namespace["default"].createElement("defs", null,
777
- React__namespace["default"].createElement("linearGradient", { id: "paint0_linear_395_1815", x1: "20", y1: "0.978638", x2: "20", y2: "39.0213", gradientUnits: "userSpaceOnUse" },
778
- React__namespace["default"].createElement("stop", { stopColor: "#7DD6E8" }),
779
- React__namespace["default"].createElement("stop", { offset: "1", stopColor: "#BF40BF" }))))),
780
- className: `${PROMPT_CONTROL}__icon`,
781
- width: '40',
782
- height: '40',
783
- viewBox: '0 0 40 40',
784
- fill: 'none',
785
- xmlns: 'http://www.w3.org/2000/svg',
786
- });
787
- const AIIcon = elements.withBaseElementProps(Icon, AIIconProps);
788
- const HeaderText = elements.withBaseElementProps(Heading, {
789
- className: `${PROMPT_CONTROL}__header`,
790
- });
791
764
  const PromptGroupBase = elements.withBaseElementProps(View, {
792
765
  className: `${PROMPT_CONTROL}__buttongroup`,
793
766
  });
@@ -802,9 +775,7 @@ const PromptGroup = React__namespace["default"].forwardRef(function ButtonGroup(
802
775
  setInput((prevInput) => ({
803
776
  ...prevInput,
804
777
  text: prompt.inputText,
805
- })) },
806
- React__namespace["default"].createElement(Text, { className: ui.classNames(`${PROMPT_CARD}__header`, `${PROMPT_CARD}__text`) }, prompt.header),
807
- React__namespace["default"].createElement(Text, { className: `${PROMPT_CARD}__text` }, prompt.inputText)));
778
+ })) }, prompt.component));
808
779
  })));
809
780
  });
810
781
  const Container = elements.withBaseElementProps(View, {
@@ -818,61 +789,50 @@ const PromptControl = () => {
818
789
  return (React__namespace["default"].createElement(controls.PromptList, { setInput: setInput, suggestedPrompts: suggestedPromptsArray }));
819
790
  }
820
791
  return (React__namespace["default"].createElement(Container, null,
821
- React__namespace["default"].createElement(AIIcon, null),
822
- React__namespace["default"].createElement(HeaderText, null, "How can I help you today?"),
823
792
  React__namespace["default"].createElement(PromptGroup, null)));
824
793
  };
825
- const AutoHidablePromptControl = () => {
826
- const messages = React__namespace["default"].useContext(MessagesContext);
827
- if (!messages || messages.length === 0) {
828
- return React__namespace["default"].createElement(PromptControl, null);
829
- }
830
- };
831
794
  PromptControl.Container = Container;
832
- PromptControl.Header = HeaderText;
833
- PromptControl.Icon = AIIcon;
834
795
  PromptControl.PromptGroup = PromptGroup;
835
796
  PromptControl.PromptCard = PromptCard;
836
797
 
837
- function Conversation() {
838
- return (React__namespace["default"].createElement(ViewElement, null,
839
- React__namespace["default"].createElement(HeaderControl, null),
840
- React__namespace["default"].createElement(ViewElement, null,
841
- React__namespace["default"].createElement(AutoHidablePromptControl, null),
842
- React__namespace["default"].createElement(MessagesControl, null)),
843
- React__namespace["default"].createElement(ViewElement, null,
844
- React__namespace["default"].createElement(FieldControl, null))));
845
- }
846
-
847
- const AIConversationProvider = ({ elements: elements$1, actions, suggestedPrompts, responseComponents, variant, controls, displayText, allowAttachments, messages, handleSendMessage, avatars, isLoading, children, }) => {
798
+ const AIConversationProvider = ({ aiContext, actions, allowAttachments, avatars, children, controls, displayText, handleSendMessage, isLoading, messages, messageRenderer, responseComponents, suggestedPrompts, variant, welcomeMessage, FallbackResponseComponent, }) => {
848
799
  const _displayText = {
849
800
  ...defaultAIConversationDisplayTextEn,
850
801
  ...displayText,
851
802
  };
852
- return (React__namespace["default"].createElement(elements.ElementsProvider, { elements: elements$1 },
853
- React__namespace["default"].createElement(ControlsProvider, { controls: controls },
854
- React__namespace["default"].createElement(SuggestedPromptProvider, { suggestedPrompts: suggestedPrompts },
855
- React__namespace["default"].createElement(ResponseComponentsProvider, { responseComponents: responseComponents },
856
- React__namespace["default"].createElement(AttachmentProvider, { allowAttachments: allowAttachments },
857
- React__namespace["default"].createElement(ConversationDisplayTextProvider, { ..._displayText },
858
- React__namespace["default"].createElement(ConversationInputContextProvider, null,
859
- React__namespace["default"].createElement(SendMessageContextProvider, { handleSendMessage: handleSendMessage },
860
- React__namespace["default"].createElement(AvatarsProvider, { avatars: avatars },
861
- React__namespace["default"].createElement(ActionsProvider, { actions: actions },
862
- React__namespace["default"].createElement(MessageVariantProvider, { variant: variant },
863
- React__namespace["default"].createElement(MessagesProvider, { messages: messages },
864
- React__namespace["default"].createElement(LoadingContextProvider, { isLoading: isLoading }, children))))))))))))));
803
+ return (React__namespace["default"].createElement(ControlsProvider, { controls: controls },
804
+ React__namespace["default"].createElement(SuggestedPromptProvider, { suggestedPrompts: suggestedPrompts },
805
+ React__namespace["default"].createElement(WelcomeMessageProvider, { welcomeMessage: welcomeMessage },
806
+ React__namespace["default"].createElement(FallbackComponentProvider, { FallbackComponent: FallbackResponseComponent },
807
+ React__namespace["default"].createElement(MessageRendererProvider, { ...messageRenderer },
808
+ React__namespace["default"].createElement(ResponseComponentsProvider, { responseComponents: responseComponents },
809
+ React__namespace["default"].createElement(AttachmentProvider, { allowAttachments: allowAttachments },
810
+ React__namespace["default"].createElement(ConversationDisplayTextProvider, { ..._displayText },
811
+ React__namespace["default"].createElement(ConversationInputContextProvider, null,
812
+ React__namespace["default"].createElement(SendMessageContextProvider, { handleSendMessage: handleSendMessage },
813
+ React__namespace["default"].createElement(AvatarsProvider, { avatars: avatars },
814
+ React__namespace["default"].createElement(ActionsProvider, { actions: actions },
815
+ React__namespace["default"].createElement(MessageVariantProvider, { variant: variant },
816
+ React__namespace["default"].createElement(MessagesProvider, { messages: messages },
817
+ React__namespace["default"].createElement(AIContextProvider, { aiContext: aiContext },
818
+ React__namespace["default"].createElement(LoadingContextProvider, { isLoading: isLoading }, children)))))))))))))))));
819
+ };
820
+
821
+ const DefaultMessageControl = () => {
822
+ const messages = React__namespace.useContext(MessagesContext);
823
+ const welcomeMessage = React__namespace.useContext(WelcomeMessageContext);
824
+ if (!messages || messages.length === 0) {
825
+ return (React__namespace.createElement(React__namespace.Fragment, null,
826
+ welcomeMessage,
827
+ React__namespace.createElement(PromptControl, null)));
828
+ }
865
829
  };
866
830
 
867
- /**
868
- * @experimental
869
- */
870
831
  function createAIConversation(input = {}) {
871
- const { elements, suggestedPrompts, actions, responseComponents, variant, controls, displayText, allowAttachments, } = input;
832
+ const { suggestedPrompts, actions, responseComponents, variant, controls, displayText, allowAttachments, messageRenderer, FallbackResponseComponent, } = input;
872
833
  function AIConversation(props) {
873
834
  const { messages, avatars, handleSendMessage, isLoading } = props;
874
835
  const providerProps = {
875
- elements,
876
836
  actions,
877
837
  suggestedPrompts,
878
838
  responseComponents,
@@ -884,24 +844,34 @@ function createAIConversation(input = {}) {
884
844
  avatars,
885
845
  handleSendMessage,
886
846
  isLoading,
847
+ messageRenderer,
848
+ FallbackResponseComponent,
887
849
  };
888
850
  return (React__namespace["default"].createElement(AIConversationProvider, { ...providerProps },
889
- React__namespace["default"].createElement(Conversation, null)));
851
+ React__namespace["default"].createElement(ViewElement, null,
852
+ React__namespace["default"].createElement(ViewElement, null,
853
+ React__namespace["default"].createElement(DefaultMessageControl, null),
854
+ React__namespace["default"].createElement(MessagesControl, null)),
855
+ React__namespace["default"].createElement(ViewElement, null,
856
+ React__namespace["default"].createElement(FormControl, null)))));
890
857
  }
891
- const Controls = {
892
- ActionsBar: ActionsBarControl,
893
- Avatars: AvatarControl,
894
- Field: FieldControl,
895
- Header: HeaderControl,
896
- Messages: MessagesControl,
897
- SuggestedPrompts: PromptControl,
898
- };
899
858
  AIConversation.Provider = AIConversationProvider;
900
- AIConversation.Conversation = Conversation;
901
- AIConversation.Controls = Controls;
859
+ AIConversation.DefaultMessage = DefaultMessageControl;
860
+ AIConversation.Messages = MessagesControl;
861
+ AIConversation.Form = FormControl;
902
862
  return { AIConversation };
903
863
  }
904
864
 
865
+ const PlaceholderMessage = ({ role }) => {
866
+ const variant = React__namespace.useContext(MessageVariantContext);
867
+ return (React__namespace.createElement(uiReact.View, { className: ui.classNames(ui.ComponentClassName.AIConversationMessage, ui.classNameModifier(ui.ComponentClassName.AIConversationMessage, variant), ui.classNameModifier(ui.ComponentClassName.AIConversationMessage, role)) },
868
+ React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageAvatar },
869
+ React__namespace.createElement(uiReact.Avatar, null)),
870
+ React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageBody },
871
+ React__namespace.createElement(uiReact.Placeholder, { width: "25%" }),
872
+ React__namespace.createElement(uiReact.Placeholder, { width: "50%" }),
873
+ React__namespace.createElement(uiReact.Placeholder, { width: "25%" }))));
874
+ };
905
875
  const MessageMeta = ({ message }) => {
906
876
  // need to pass this in as props in order for it to be overridable
907
877
  const avatars = React__namespace.useContext(AvatarsContext);
@@ -913,29 +883,30 @@ const MessageMeta = ({ message }) => {
913
883
  React__namespace.createElement(uiReact.Text, { className: ui.ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username),
914
884
  React__namespace.createElement(uiReact.Text, { className: ui.ComponentClassName.AIConversationMessageSenderTimestamp }, getMessageTimestampText(new Date(message.createdAt)))));
915
885
  };
916
- const LoadingMessage = () => {
917
- const avatars = React__namespace.useContext(AvatarsContext);
918
- const variant = React__namespace.useContext(MessageVariantContext);
919
- const avatar = avatars?.ai;
920
- return (React__namespace.createElement(uiReact.View, { className: ui.classNames(ui.ComponentClassName.AIConversationMessage, ui.classNameModifier(ui.ComponentClassName.AIConversationMessage, variant), ui.classNameModifier(ui.ComponentClassName.AIConversationMessage, 'assistant')) },
921
- React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageAvatar },
922
- React__namespace.createElement(uiReact.Avatar, { isLoading: true }, avatar?.avatar)),
923
- React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageBody },
924
- React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageSender },
925
- React__namespace.createElement(uiReact.Text, { className: ui.ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username)))));
886
+ const MessageActions = ({ message }) => {
887
+ const actions = React__namespace.useContext(ActionsContext);
888
+ if (!actions)
889
+ return null;
890
+ return (React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageActions }, actions.map((action, i) => {
891
+ return (React__namespace.createElement(uiReact.Button, { key: i, size: "small", onClick: () => {
892
+ action.handler(message);
893
+ } }, action.component));
894
+ })));
926
895
  };
927
896
  const Message = ({ message }) => {
928
897
  const avatars = React__namespace.useContext(AvatarsContext);
929
898
  const variant = React__namespace.useContext(MessageVariantContext);
899
+ const { isLoading } = message;
930
900
  const avatar = message.role === 'assistant' ? avatars?.ai : avatars?.user;
931
901
  return (React__namespace.createElement(RoleContext.Provider, { value: message.role },
932
902
  React__namespace.createElement(uiReact.View, { className: ui.classNames(ui.ComponentClassName.AIConversationMessage, ui.classNameModifier(ui.ComponentClassName.AIConversationMessage, variant), ui.classNameModifier(ui.ComponentClassName.AIConversationMessage, message.role)) },
933
903
  React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageAvatar },
934
- React__namespace.createElement(uiReact.Avatar, null, avatar?.avatar)),
904
+ React__namespace.createElement(uiReact.Avatar, { isLoading: isLoading }, avatar?.avatar)),
935
905
  React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageBody },
936
906
  React__namespace.createElement(MessageMeta, { message: message }),
937
907
  React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageContent },
938
- React__namespace.createElement(MessageControl, { message: message }))))));
908
+ React__namespace.createElement(MessageControl, { message: message })),
909
+ message.role === 'assistant' ? (React__namespace.createElement(MessageActions, { message: message })) : null))));
939
910
  };
940
911
  const MessageList = ({ messages, }) => {
941
912
  const isLoading = React__namespace.useContext(LoadingContext);
@@ -943,8 +914,10 @@ const MessageList = ({ messages, }) => {
943
914
  content.text ??
944
915
  content.toolUse?.name.startsWith(RESPONSE_COMPONENT_PREFIX))) ?? [];
945
916
  return (React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageList },
946
- messagesWithRenderableContent.map((message, i) => (React__namespace.createElement(Message, { key: `message-${i}`, message: message }))),
947
- isLoading ? React__namespace.createElement(LoadingMessage, null) : null));
917
+ isLoading ? (React__namespace.createElement(React__namespace.Fragment, null,
918
+ React__namespace.createElement(PlaceholderMessage, { role: "user" }),
919
+ React__namespace.createElement(PlaceholderMessage, { role: "assistant" }))) : null,
920
+ messagesWithRenderableContent.map((message, i) => (React__namespace.createElement(Message, { key: `message-${i}`, message: message })))));
948
921
  };
949
922
 
950
923
  const Attachment = ({ file, handleRemove, }) => {
@@ -990,12 +963,12 @@ const FormWrapper = ({ children, allowAttachments, setInput, }) => {
990
963
  return children;
991
964
  }
992
965
  };
993
- const Form = ({ setInput, input, handleSubmit, allowAttachments, }) => {
966
+ const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) => {
994
967
  const icons = internal.useIcons('aiConversation');
995
968
  const sendIcon = icons?.send ?? React__namespace.createElement(internal.IconSend, null);
996
969
  const attachIcon = icons?.attach ?? React__namespace.createElement(internal.IconAttach, null);
997
970
  const hiddenInput = React__namespace.useRef(null);
998
- const isLoading = React__namespace.useContext(LoadingContext);
971
+ const [composing, setComposing] = React__namespace.useState(false);
999
972
  const isInputEmpty = !input?.text?.length && !input?.files?.length;
1000
973
  return (React__namespace.createElement(FormWrapper, { allowAttachments: allowAttachments, setInput: setInput },
1001
974
  React__namespace.createElement(uiReact.View, { as: "form", className: ui.ComponentClassName.AIConversationForm, onSubmit: handleSubmit },
@@ -1017,9 +990,9 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, }) => {
1017
990
  files: [...(prevValue?.files ?? []), ...Array.from(files)],
1018
991
  }));
1019
992
  }, multiple: true, accept: "*", "data-testid": "hidden-file-input" })))) : null,
1020
- 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) => {
993
+ 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", onCompositionStart: () => setComposing(true), onCompositionEnd: () => setComposing(false), onKeyDown: (e) => {
1021
994
  // Submit on enter key if shift is not pressed also
1022
- const shouldSubmit = !e.shiftKey && e.key === 'Enter';
995
+ const shouldSubmit = !e.shiftKey && e.key === 'Enter' && !composing;
1023
996
  if (shouldSubmit && isHTMLFormElement(e.target)) {
1024
997
  e.target.form.requestSubmit();
1025
998
  e.preventDefault();
@@ -1045,89 +1018,69 @@ const PromptList = ({ setInput, suggestedPrompts = [], }) => {
1045
1018
  ...prevInput,
1046
1019
  text: prompt.inputText,
1047
1020
  }));
1048
- } }, prompt.header));
1021
+ } }, prompt.component));
1049
1022
  })));
1050
1023
  };
1051
1024
 
1052
- function AIConversationBase({ actions, avatars, controls, handleSendMessage, messages, responseComponents, suggestedPrompts, variant, isLoading, displayText, allowAttachments, }) {
1025
+ const VERSION = '1.0.0';
1026
+
1027
+ function AIConversationBase({ avatars, controls, ...rest }) {
1028
+ uiReactCore.useSetUserAgent({
1029
+ componentName: 'AIConversation',
1030
+ packageName: 'react-ai',
1031
+ version: VERSION,
1032
+ });
1053
1033
  const icons = internal.useIcons('aiConversation');
1054
1034
  const defaultAvatars = {
1055
1035
  ai: {
1056
1036
  username: 'Assistant',
1057
- avatar: icons?.assistant ?? React__namespace.createElement(internal.IconAssistant, null),
1037
+ avatar: icons?.assistant ?? React__namespace.createElement(internal.IconAssistant, { testId: "icon-assistant" }),
1058
1038
  },
1059
1039
  user: {
1060
1040
  username: 'User',
1061
- avatar: icons?.user ?? React__namespace.createElement(internal.IconUser, null),
1041
+ avatar: icons?.user ?? React__namespace.createElement(internal.IconUser, { testId: "icon-user" }),
1062
1042
  },
1063
1043
  };
1064
1044
  const providerProps = {
1065
- messages,
1066
- handleSendMessage,
1045
+ ...rest,
1067
1046
  avatars: {
1068
1047
  ...defaultAvatars,
1069
1048
  ...avatars,
1070
1049
  },
1071
- isLoading,
1072
- elements: {
1073
- Text: React__namespace.forwardRef(function _Text(props, ref) {
1074
- return React__namespace.createElement(uiReact.Text, { ...props, ref: ref });
1075
- }),
1076
- },
1077
- actions,
1078
- suggestedPrompts,
1079
- responseComponents,
1080
- variant,
1081
1050
  controls: {
1082
1051
  MessageList,
1083
1052
  PromptList,
1084
1053
  Form,
1085
1054
  ...controls,
1086
1055
  },
1087
- displayText,
1088
- allowAttachments,
1089
1056
  };
1090
1057
  return (React__namespace.createElement(AIConversationProvider, { ...providerProps },
1091
- React__namespace.createElement(uiReact.Flex, { className: ui.ComponentClassName.AIConversation },
1058
+ React__namespace.createElement(uiReact.Flex, { className: ui.ComponentClassName.AIConversation, testId: "ai-conversation" },
1092
1059
  React__namespace.createElement(uiReact.ScrollView, { autoScroll: "smooth", flex: "1" },
1093
- React__namespace.createElement(AutoHidablePromptControl, null),
1060
+ React__namespace.createElement(DefaultMessageControl, null),
1094
1061
  React__namespace.createElement(MessagesControl, null)),
1095
- React__namespace.createElement(FieldControl, null))));
1062
+ React__namespace.createElement(FormControl, null))));
1096
1063
  }
1097
- /**
1098
- * @experimental
1099
- */
1100
1064
  const AIConversation = Object.assign(AIConversationBase, {
1101
- MessageList,
1102
- PromptList,
1103
- Form,
1065
+ Provider: AIConversationProvider,
1066
+ DefaultMessage: DefaultMessageControl,
1067
+ Messages: MessagesControl,
1068
+ Form: FormControl,
1104
1069
  });
1105
1070
 
1106
- const AIContext = React__namespace["default"].createContext(undefined);
1107
- const useAIContext = () => {
1108
- const context = React__namespace["default"].useContext(AIContext);
1109
- const [routeToConversationsMap, setRouteToConversationsMap] = React__namespace["default"].useState({});
1110
- if (context) {
1111
- return context;
1112
- }
1113
- return { routeToConversationsMap, setRouteToConversationsMap };
1114
- };
1115
- /**
1116
- * @experimental
1117
- */
1118
- const AIContextProvider = ({ children, }) => {
1119
- const context = useAIContext();
1120
- return React__namespace["default"].createElement(AIContext.Provider, { value: context }, children);
1121
- };
1122
-
1123
1071
  // default state
1124
1072
  const INITIAL_STATE = {
1125
1073
  hasError: false,
1126
1074
  isLoading: false,
1127
1075
  messages: undefined,
1128
1076
  };
1129
- const LOADING_STATE = { hasError: false, isLoading: true, messages: undefined };
1077
+ const LOADING_STATE = {
1078
+ hasError: false,
1079
+ isLoading: true,
1080
+ messages: undefined,
1081
+ };
1130
1082
  const ERROR_STATE = { hasError: true, isLoading: false };
1083
+
1131
1084
  function createUseAIGeneration(client) {
1132
1085
  const useAIGeneration = (routeName) => {
1133
1086
  const [dataState, setDataState] = React__namespace.useState(() => ({
@@ -1154,142 +1107,314 @@ function createUseAIGeneration(client) {
1154
1107
  return useAIGeneration;
1155
1108
  }
1156
1109
 
1157
- function createNewConversationMessageInRoute({ previousValue, routeName, conversationId, messages, }) {
1110
+ const contentFromEvents = (contentBlocks) => {
1111
+ if (!contentBlocks)
1112
+ return [];
1113
+ return contentBlocks.map((contentBlock) => {
1114
+ const isTextBlock = contentBlock.some((event) => event.text);
1115
+ if (isTextBlock) {
1116
+ return {
1117
+ text: contentBlock
1118
+ .map((event) => {
1119
+ return event.text;
1120
+ })
1121
+ .join(''),
1122
+ };
1123
+ }
1124
+ // tool use is never chunked
1125
+ if (contentBlock[0].toolUse) {
1126
+ return { toolUse: contentBlock[0].toolUse };
1127
+ }
1128
+ });
1129
+ };
1130
+
1131
+ async function exhaustivelyListMessages({ conversation, messages = [], nextToken, }) {
1132
+ const result = await conversation.listMessages({ nextToken });
1133
+ if (result.data) {
1134
+ messages?.push(...result.data);
1135
+ }
1136
+ if (result.nextToken) {
1137
+ return exhaustivelyListMessages({
1138
+ conversation,
1139
+ messages,
1140
+ nextToken: result.nextToken,
1141
+ });
1142
+ }
1158
1143
  return {
1159
- ...previousValue,
1160
- [routeName]: {
1161
- ...previousValue[routeName],
1162
- [conversationId]: messages,
1163
- },
1144
+ ...result,
1145
+ data: messages,
1164
1146
  };
1165
1147
  }
1148
+
1149
+ function hasStarted(state) {
1150
+ return ['initialLoading', 'initialized'].includes(state);
1151
+ }
1166
1152
  function createUseAIConversation(client) {
1153
+ // This is a bit complicated so buckle up.
1154
+ // The way the data client works is conversation.get() or conversation.create()
1155
+ // is an async function because it makes a graphql call to appsync
1156
+ // then it returns a conversation object, which is like a normal
1157
+ // data client record, except that it also has functions on it,
1158
+ // like sendMessage and onStreamEvent. onStreamEvent sets up a
1159
+ // subscription using a websocket connection, which ideally we only want to
1160
+ // do once per conversation. Because we can only subscribe AFTER the
1161
+ // async call to get/create the conversation is made, the cleanup
1162
+ // function in the effect will won't actually unsubscribe
1167
1163
  const useAIConversation = (routeName, input = {}) => {
1168
1164
  const clientRoute = client.conversations[routeName];
1169
- const { routeToConversationsMap, setRouteToConversationsMap } = useAIContext();
1170
- const messagesFromAIContext = input.id
1171
- ? routeToConversationsMap[routeName]?.[input.id]
1172
- : undefined;
1173
- const [localMessages, setLocalMessages] = React__namespace["default"].useState(messagesFromAIContext ?? []);
1174
- const [conversation, setConversation] = React__namespace["default"].useState(undefined);
1175
- const [waitingForAIResponse, setWaitingForAIResponse] = React__namespace["default"].useState(false);
1176
- const [errorMessage, setErrorMessage] = React__namespace["default"].useState();
1177
- const [hasError, setHasError] = React__namespace["default"].useState(false);
1178
- // On hook initialization get conversation and load all messages
1165
+ // We need to keep track of the stream events as the come in
1166
+ // for an assistant message, but don't need to keep them in state
1167
+ const contentBlocksRef = React__namespace["default"].useRef();
1168
+ // Using this hook without an existing conversation id means
1169
+ // it will create a new conversation when it is executed
1170
+ // we don't want to create 2 conversations
1171
+ const initRef = React__namespace["default"].useRef('initial');
1172
+ const [dataState, setDataState] = React__namespace["default"].useState(() => ({
1173
+ ...INITIAL_STATE,
1174
+ data: { messages: [], conversation: undefined },
1175
+ }));
1176
+ const { conversation } = dataState.data;
1177
+ const { id, onInitialize, onMessage } = input;
1179
1178
  React__namespace["default"].useEffect(() => {
1180
1179
  async function initialize() {
1181
- const { data: conversation } = input.id
1182
- ? await clientRoute.get({ id: input.id })
1183
- : await clientRoute.create();
1184
- if (!conversation) {
1185
- const errorString = 'No conversation found';
1186
- setHasError(true);
1187
- setErrorMessage(errorString);
1188
- throw new Error(errorString);
1180
+ // We don't want to run the effect multiple times
1181
+ // because that could create multiple conversation records
1182
+ if (hasStarted(initRef.current))
1183
+ return;
1184
+ initRef.current = 'initialLoading';
1185
+ // Only show component loading state if we are
1186
+ // actually loading messages
1187
+ if (id) {
1188
+ setDataState({
1189
+ ...LOADING_STATE,
1190
+ data: { messages: [], conversation: undefined },
1191
+ });
1189
1192
  }
1190
- const { data: messages } = await conversation.listMessages();
1191
- setLocalMessages(messages);
1192
- setConversation(conversation);
1193
- setRouteToConversationsMap((previousValue) => {
1194
- return createNewConversationMessageInRoute({
1195
- previousValue,
1196
- routeName: routeName,
1197
- conversationId: conversation.id,
1198
- messages,
1193
+ const { data: conversation, errors } = id
1194
+ ? await clientRoute.get({ id })
1195
+ : await clientRoute.create();
1196
+ if (errors ?? !conversation) {
1197
+ setDataState({
1198
+ ...ERROR_STATE,
1199
+ data: { messages: [] },
1200
+ messages: errors,
1199
1201
  });
1202
+ }
1203
+ else {
1204
+ if (id) {
1205
+ const { data: messages } = await exhaustivelyListMessages({
1206
+ conversation,
1207
+ });
1208
+ setDataState({
1209
+ ...INITIAL_STATE,
1210
+ data: { messages, conversation },
1211
+ });
1212
+ }
1213
+ else {
1214
+ setDataState({
1215
+ ...INITIAL_STATE,
1216
+ data: { conversation, messages: [] },
1217
+ });
1218
+ }
1219
+ initRef.current = 'initialized';
1220
+ }
1221
+ }
1222
+ // this is a runtime guard to make catch an error if
1223
+ // the route name wrong, or there is a mismatch
1224
+ // between the gen2 schema definition and
1225
+ // whats in amplify_outputs
1226
+ if (!clientRoute) {
1227
+ setDataState({
1228
+ ...ERROR_STATE,
1229
+ data: { messages: [] },
1230
+ messages: [
1231
+ {
1232
+ message: 'Conversation route does not exist',
1233
+ errorInfo: null,
1234
+ errorType: '',
1235
+ },
1236
+ ],
1200
1237
  });
1238
+ return;
1201
1239
  }
1202
1240
  initialize();
1203
- }, [clientRoute, input.id, routeName, setRouteToConversationsMap]);
1204
- // Update messages to match what is in AIContext if they aren't equal
1241
+ return () => {
1242
+ contentBlocksRef.current = undefined;
1243
+ if (hasStarted(initRef.current))
1244
+ return;
1245
+ setDataState({
1246
+ ...INITIAL_STATE,
1247
+ data: { messages: [], conversation: undefined },
1248
+ });
1249
+ };
1250
+ }, [clientRoute, id, setDataState]);
1251
+ // Run a separate effect that is triggered by the conversation state
1252
+ // so that we know we have a conversation object to set up the subscription
1253
+ // and also unsubscribe on cleanup
1205
1254
  React__namespace["default"].useEffect(() => {
1206
- if (!!messagesFromAIContext && messagesFromAIContext !== localMessages)
1207
- setLocalMessages(messagesFromAIContext);
1208
- }, [messagesFromAIContext, localMessages]);
1209
- const sendMessage = React__namespace["default"].useCallback((input) => {
1210
- const { content, aiContext, toolConfiguration } = input;
1211
- conversation
1212
- ?.sendMessage({ content, aiContext, toolConfiguration })
1213
- .then((value) => {
1214
- const { data: sentMessage } = value;
1215
- if (sentMessage) {
1216
- setWaitingForAIResponse(true);
1217
- setLocalMessages((previousLocalMessages) => [
1218
- ...previousLocalMessages,
1219
- sentMessage,
1220
- ]);
1221
- setRouteToConversationsMap((previousValue) => {
1222
- return createNewConversationMessageInRoute({
1223
- previousValue,
1224
- routeName: routeName,
1225
- conversationId: conversation.id,
1226
- messages: [
1227
- ...previousValue[routeName][conversation.id],
1228
- sentMessage,
1229
- ],
1255
+ if (!conversation)
1256
+ return;
1257
+ const subscription = conversation.onStreamEvent({
1258
+ next: (event) => {
1259
+ const {
1260
+ // messages have a content block array,
1261
+ // this is the index of the content block that was updated
1262
+ contentBlockIndex,
1263
+ // this is the index of the content chunk, ensure these are in order!
1264
+ contentBlockDeltaIndex,
1265
+ // this is sent after the last content chunk, verify this matches the
1266
+ // previous contentBlockDeltaIndex
1267
+ contentBlockDoneAtIndex,
1268
+ // this is the final event of the conversation turn
1269
+ stopReason, conversationId, id, } = event;
1270
+ // return early for content blocks being done
1271
+ // or conversation turn being over
1272
+ if (contentBlockDoneAtIndex) {
1273
+ return;
1274
+ }
1275
+ // stop reason will signify end of conversation turn
1276
+ if (stopReason) {
1277
+ // remove loading state from streamed message
1278
+ setDataState((prev) => {
1279
+ return {
1280
+ ...prev,
1281
+ data: {
1282
+ ...prev.data,
1283
+ messages: prev.data.messages.map((message) => ({
1284
+ ...message,
1285
+ isLoading: false,
1286
+ })),
1287
+ },
1288
+ };
1230
1289
  });
1231
- });
1232
- }
1233
- })
1234
- .catch((reason) => {
1235
- setHasError(true);
1236
- setErrorMessage(`error sending message ${reason}`);
1237
- });
1238
- }, [conversation, routeName, setRouteToConversationsMap]);
1239
- const subscribe = React__namespace["default"].useCallback((handleStoreChange) => {
1240
- const subscription = conversation &&
1241
- conversation.onMessage((message) => {
1242
- if (input.onResponse)
1243
- input.onResponse(message);
1244
- setWaitingForAIResponse(false);
1245
- setLocalMessages((previousLocalMessages) => [
1246
- ...previousLocalMessages,
1247
- message,
1248
- ]);
1249
- setRouteToConversationsMap((previousValue) => {
1250
- return createNewConversationMessageInRoute({
1251
- previousValue,
1252
- routeName: routeName,
1253
- conversationId: conversation.id,
1254
- messages: [
1255
- ...previousValue[routeName][conversation.id],
1256
- message,
1257
- ],
1290
+ onMessage?.({
1291
+ id,
1292
+ conversationId,
1293
+ content: contentFromEvents(contentBlocksRef.current),
1294
+ createdAt: new Date().toISOString(),
1295
+ role: 'assistant',
1296
+ isLoading: true,
1258
1297
  });
1298
+ // clear out the stream cache
1299
+ contentBlocksRef.current = undefined;
1300
+ return;
1301
+ }
1302
+ // no ref means its the first event for the message stream
1303
+ // so lets create the contentBlocks ref or else we will
1304
+ // add the incoming event to the right content content block
1305
+ if (!contentBlocksRef.current) {
1306
+ contentBlocksRef.current = [[event]];
1307
+ }
1308
+ else {
1309
+ // place the incoming event in the right content block
1310
+ // and order. message content is an array so a single message
1311
+ // can have multiple content blocks, and each content block
1312
+ // can have multiple events/chunks
1313
+ const currentBlock = contentBlocksRef.current[contentBlockIndex];
1314
+ if (!currentBlock) {
1315
+ contentBlocksRef.current[contentBlockIndex] = [event];
1316
+ }
1317
+ else {
1318
+ contentBlocksRef.current[contentBlockIndex] = [
1319
+ ...currentBlock.slice(0, contentBlockDeltaIndex),
1320
+ event,
1321
+ ...currentBlock.slice(contentBlockDeltaIndex),
1322
+ ];
1323
+ }
1324
+ }
1325
+ setDataState((prev) => {
1326
+ const message = {
1327
+ id,
1328
+ conversationId,
1329
+ content: contentFromEvents(contentBlocksRef.current),
1330
+ createdAt: new Date().toISOString(),
1331
+ role: 'assistant',
1332
+ isLoading: true,
1333
+ };
1334
+ return {
1335
+ ...prev,
1336
+ data: {
1337
+ ...prev.data,
1338
+ // TODO: we are assuming we only update the last
1339
+ // message, but maybe we should match it by message ID?
1340
+ messages: [...prev.data.messages.slice(0, -1), message],
1341
+ },
1342
+ };
1259
1343
  });
1260
- handleStoreChange(); // should cause a re-render
1261
- });
1344
+ },
1345
+ error: (error) => {
1346
+ setDataState((prev) => {
1347
+ return {
1348
+ ...prev,
1349
+ ...ERROR_STATE,
1350
+ messages: error.errors,
1351
+ };
1352
+ });
1353
+ },
1354
+ });
1355
+ if (ui.isFunction(onInitialize)) {
1356
+ onInitialize(conversation);
1357
+ }
1262
1358
  return () => {
1263
- subscription?.unsubscribe();
1359
+ contentBlocksRef.current = undefined;
1360
+ subscription.unsubscribe();
1264
1361
  };
1265
- }, [conversation, routeName, setRouteToConversationsMap, input]);
1266
- const getSnapshot = React__namespace["default"].useCallback(() => localMessages, [localMessages]);
1267
- // Using useSyncExternalStore to subscribe to external data updates
1268
- // Have to provide third optional argument in next - https://github.com/vercel/next.js/issues/54685
1269
- const messagesFromStore = React__namespace["default"].useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1270
- return [
1271
- {
1272
- data: { messages: messagesFromStore },
1273
- isLoading: waitingForAIResponse,
1274
- message: errorMessage,
1275
- hasError,
1276
- },
1277
- sendMessage,
1278
- ];
1362
+ }, [conversation, onInitialize, onMessage, setDataState]);
1363
+ const handleSendMessage = React__namespace["default"].useCallback((input) => {
1364
+ const { content } = input;
1365
+ if (conversation) {
1366
+ setDataState((prevState) => ({
1367
+ ...prevState,
1368
+ data: {
1369
+ ...prevState.data,
1370
+ // optimistically add user and assistant messages
1371
+ messages: [
1372
+ ...prevState.data.messages,
1373
+ {
1374
+ content,
1375
+ role: 'user',
1376
+ createdAt: new Date().toISOString(),
1377
+ id: 'temp-id',
1378
+ conversationId: conversation.id ?? '',
1379
+ },
1380
+ {
1381
+ content: [{ text: ' ' }],
1382
+ role: 'assistant',
1383
+ createdAt: new Date().toISOString(),
1384
+ id: 'temp-id-2',
1385
+ conversationId: conversation.id ?? '',
1386
+ isLoading: true,
1387
+ },
1388
+ ],
1389
+ },
1390
+ }));
1391
+ conversation.sendMessage(input);
1392
+ }
1393
+ else {
1394
+ setDataState((prev) => ({
1395
+ ...prev,
1396
+ ...ERROR_STATE,
1397
+ messages: [
1398
+ {
1399
+ message: 'No conversation found',
1400
+ errorInfo: null,
1401
+ errorType: '',
1402
+ },
1403
+ ],
1404
+ }));
1405
+ }
1406
+ }, [conversation]);
1407
+ return [dataState, handleSendMessage];
1279
1408
  };
1280
1409
  return useAIConversation;
1281
1410
  }
1282
1411
 
1283
- /**
1284
- * @experimental
1285
- */
1286
1412
  function createAIHooks(_client) {
1287
1413
  const useAIConversation = createUseAIConversation(_client);
1288
1414
  const useAIGeneration = createUseAIGeneration(_client);
1289
1415
  return { useAIConversation, useAIGeneration };
1290
1416
  }
1291
1417
 
1292
- exports.AIContextProvider = AIContextProvider;
1293
1418
  exports.AIConversation = AIConversation;
1294
1419
  exports.createAIConversation = createAIConversation;
1295
1420
  exports.createAIHooks = createAIHooks;