@aws-amplify/ui-react-ai 0.4.0 → 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.
- package/dist/esm/components/AIConversation/AIConversation.mjs +8 -26
- package/dist/esm/components/AIConversation/AIConversationProvider.mjs +20 -17
- package/dist/esm/components/AIConversation/context/AIContextContext.mjs +8 -0
- package/dist/esm/components/AIConversation/context/FallbackComponentContext.mjs +8 -0
- package/dist/esm/components/AIConversation/context/ResponseComponentsContext.mjs +6 -2
- package/dist/esm/components/AIConversation/createAIConversation.mjs +2 -5
- package/dist/esm/components/AIConversation/views/Controls/ActionsBarControl.mjs +3 -2
- package/dist/esm/components/AIConversation/views/Controls/AttachFileControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/Controls/AttachmentListControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/Controls/AvatarControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/Controls/DefaultMessageControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/Controls/FormControl.mjs +11 -4
- package/dist/esm/components/AIConversation/views/Controls/MessagesControl.mjs +24 -31
- package/dist/esm/components/AIConversation/views/Controls/PromptControl.mjs +2 -0
- package/dist/esm/components/AIConversation/views/default/Form.mjs +4 -5
- package/dist/esm/components/AIConversation/views/default/MessageList.mjs +31 -16
- package/dist/esm/hooks/contentFromEvents.mjs +22 -0
- package/dist/esm/hooks/createAIHooks.mjs +0 -3
- package/dist/esm/hooks/exhaustivelyListMessages.mjs +19 -0
- package/dist/esm/hooks/shared.mjs +14 -0
- package/dist/esm/hooks/useAIConversation.mjs +246 -106
- package/dist/esm/hooks/useAIGeneration.mjs +1 -8
- package/dist/esm/index.mjs +0 -1
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +396 -236
- package/dist/types/components/AIConversation/AIConversation.d.ts +0 -3
- package/dist/types/components/AIConversation/AIConversationProvider.d.ts +1 -1
- package/dist/types/components/AIConversation/context/AIContextContext.d.ts +6 -0
- package/dist/types/components/AIConversation/context/ControlsContext.d.ts +1 -0
- package/dist/types/components/AIConversation/context/FallbackComponentContext.d.ts +7 -0
- package/dist/types/components/AIConversation/context/ResponseComponentsContext.d.ts +2 -2
- package/dist/types/components/AIConversation/context/elements/definitions.d.ts +1 -1
- package/dist/types/components/AIConversation/context/index.d.ts +2 -0
- package/dist/types/components/AIConversation/createAIConversation.d.ts +0 -3
- package/dist/types/components/AIConversation/index.d.ts +2 -1
- package/dist/types/components/AIConversation/types.d.ts +4 -24
- package/dist/types/components/AIConversation/views/Controls/MessagesControl.d.ts +1 -5
- package/dist/types/components/AIConversation/views/default/Form.d.ts +1 -1
- package/dist/types/components/AIConversation/views/default/MessageList.d.ts +1 -1
- package/dist/types/components/AIConversation/views/default/PromptList.d.ts +1 -1
- package/dist/types/hooks/contentFromEvents.d.ts +2 -0
- package/dist/types/hooks/createAIHooks.d.ts +0 -3
- package/dist/types/hooks/exhaustivelyListMessages.d.ts +8 -0
- package/dist/types/hooks/index.d.ts +1 -2
- package/dist/types/hooks/shared.d.ts +23 -0
- package/dist/types/hooks/useAIConversation.d.ts +6 -4
- package/dist/types/hooks/useAIGeneration.d.ts +3 -13
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types.d.ts +32 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +4 -4
- package/dist/ai-conversation-styles.css +0 -195
- package/dist/ai-conversation-styles.js +0 -2
- package/dist/esm/hooks/AIContextProvider.mjs +0 -20
- package/dist/types/ai-conversation-styles.d.ts +0 -1
- package/dist/types/hooks/AIContextProvider.d.ts +0 -17
package/dist/index.js
CHANGED
|
@@ -5,9 +5,9 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var elements = require('@aws-amplify/ui-react-core/elements');
|
|
7
7
|
var uiReactCore = require('@aws-amplify/ui-react-core');
|
|
8
|
+
var ui = require('@aws-amplify/ui');
|
|
8
9
|
var uiReact = require('@aws-amplify/ui-react');
|
|
9
10
|
var internal = require('@aws-amplify/ui-react/internal');
|
|
10
|
-
var ui = require('@aws-amplify/ui');
|
|
11
11
|
|
|
12
12
|
function _interopNamespace(e) {
|
|
13
13
|
if (e && e.__esModule) return e;
|
|
@@ -29,6 +29,11 @@ function _interopNamespace(e) {
|
|
|
29
29
|
|
|
30
30
|
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
31
31
|
|
|
32
|
+
const AIContextContext = React__namespace["default"].createContext(undefined);
|
|
33
|
+
const AIContextProvider = ({ children, aiContext, }) => {
|
|
34
|
+
return (React__namespace["default"].createElement(AIContextContext.Provider, { value: aiContext }, children));
|
|
35
|
+
};
|
|
36
|
+
|
|
32
37
|
const ActionsContext = React__namespace["default"].createContext(undefined);
|
|
33
38
|
const ActionsProvider = ({ children, actions, }) => {
|
|
34
39
|
return (React__namespace["default"].createElement(ActionsContext.Provider, { value: actions }, children));
|
|
@@ -141,8 +146,12 @@ const convertResponseComponentsToToolConfiguration = (responseComponents) => {
|
|
|
141
146
|
const { props } = responseComponents[toolName];
|
|
142
147
|
const requiredProps = [];
|
|
143
148
|
Object.keys(props).forEach((propName) => {
|
|
144
|
-
if (props[propName].required)
|
|
149
|
+
if (props[propName].required) {
|
|
145
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
|
+
}
|
|
146
155
|
});
|
|
147
156
|
tools[toolName] = {
|
|
148
157
|
description: responseComponents[toolName].description,
|
|
@@ -181,6 +190,11 @@ const WelcomeMessageProvider = ({ children, welcomeMessage, }) => {
|
|
|
181
190
|
return (React__namespace.createElement(WelcomeMessageContext.Provider, { value: welcomeMessage }, children));
|
|
182
191
|
};
|
|
183
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
|
+
|
|
184
198
|
const DEFAULT_ICON_PATHS = {
|
|
185
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',
|
|
186
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',
|
|
@@ -269,9 +283,9 @@ const AIConversationElements = {
|
|
|
269
283
|
View: ViewElement,
|
|
270
284
|
};
|
|
271
285
|
|
|
272
|
-
const { Button: Button$4, Span: Span$
|
|
286
|
+
const { Button: Button$4, Span: Span$2, View: View$6 } = AIConversationElements;
|
|
273
287
|
const ACTIONS_BAR_BLOCK = 'ai-actions-bar';
|
|
274
|
-
const ActionIcon = elements.withBaseElementProps(Span$
|
|
288
|
+
const ActionIcon = elements.withBaseElementProps(Span$2, {
|
|
275
289
|
'aria-hidden': 'true',
|
|
276
290
|
className: `${ACTIONS_BAR_BLOCK}__icon`,
|
|
277
291
|
});
|
|
@@ -286,14 +300,13 @@ const Container$3 = elements.withBaseElementProps(View$6, {
|
|
|
286
300
|
});
|
|
287
301
|
const ActionsBarControl = ({ message, focusable, }) => {
|
|
288
302
|
const actions = React__namespace["default"].useContext(ActionsContext);
|
|
289
|
-
return (React__namespace["default"].createElement(Container$3, null, actions?.map((action, index) => (React__namespace["default"].createElement(ActionButton, {
|
|
290
|
-
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)))));
|
|
291
304
|
};
|
|
292
305
|
ActionsBarControl.Button = ActionButton;
|
|
293
306
|
ActionsBarControl.Container = Container$3;
|
|
294
307
|
ActionsBarControl.Icon = ActionIcon;
|
|
295
308
|
|
|
296
|
-
const { Icon: Icon$3, Span: Span$
|
|
309
|
+
const { Icon: Icon$3, Span: Span$1, Text: Text$2, View: View$5 } = AIConversationElements;
|
|
297
310
|
const AVATAR_BLOCK = 'ai-avatar';
|
|
298
311
|
const DEFAULT_USER_ICON = elements.withBaseElementProps(Icon$3, {
|
|
299
312
|
variant: 'user-avatar',
|
|
@@ -304,7 +317,7 @@ const DEFAULT_AI_ICON = () => (React__namespace["default"].createElement("svg",
|
|
|
304
317
|
const AvatarDisplayName = elements.withBaseElementProps(Text$2, {
|
|
305
318
|
className: `${AVATAR_BLOCK}__display-name`,
|
|
306
319
|
});
|
|
307
|
-
const AvatarIcon = elements.withBaseElementProps(Span$
|
|
320
|
+
const AvatarIcon = elements.withBaseElementProps(Span$1, {
|
|
308
321
|
'aria-hidden': true,
|
|
309
322
|
className: `${AVATAR_BLOCK}__icon`,
|
|
310
323
|
});
|
|
@@ -385,7 +398,7 @@ AttachFileControl.Icon = AttachFileIcon;
|
|
|
385
398
|
AttachFileControl.Button = AttachFileButton;
|
|
386
399
|
AttachFileControl.Container = AttachFileContainer;
|
|
387
400
|
|
|
388
|
-
const { Button: Button$2, Icon: Icon$1, ListItem, UnorderedList: ListElement, Span
|
|
401
|
+
const { Button: Button$2, Icon: Icon$1, ListItem, UnorderedList: ListElement, Span, Text: Text$1, View: View$3, } = AIConversationElements;
|
|
389
402
|
const IMAGE_LIST_BLOCK = 'ai-attachment-list';
|
|
390
403
|
const IMAGE_ITEM_BLOCK = 'ai-attachment';
|
|
391
404
|
const REMOVE_IMAGE_BLOCK = 'ai-remove-attachment';
|
|
@@ -416,7 +429,7 @@ const FileNameText = elements.withBaseElementProps(Text$1, {
|
|
|
416
429
|
const FileSizeText = elements.withBaseElementProps(Text$1, {
|
|
417
430
|
className: `${IMAGE_TEXT_BLOCK}__file-size`,
|
|
418
431
|
});
|
|
419
|
-
const Separator
|
|
432
|
+
const Separator = elements.withBaseElementProps(Span, {
|
|
420
433
|
'aria-hidden': true,
|
|
421
434
|
className: `${IMAGE_TEXT_BLOCK}__separator`,
|
|
422
435
|
children: '|',
|
|
@@ -427,13 +440,13 @@ const TextContainer = elements.withBaseElementProps(View$3, {
|
|
|
427
440
|
const TextControl = ({ fileName, fileSize }) => {
|
|
428
441
|
return (React__namespace["default"].createElement(TextContainer, null,
|
|
429
442
|
React__namespace["default"].createElement(FileNameText, null, fileName),
|
|
430
|
-
React__namespace["default"].createElement(Separator
|
|
443
|
+
React__namespace["default"].createElement(Separator, null),
|
|
431
444
|
React__namespace["default"].createElement(FileSizeText, null, fileSize)));
|
|
432
445
|
};
|
|
433
446
|
TextControl.Container = TextContainer;
|
|
434
447
|
TextControl.FileName = FileNameText;
|
|
435
448
|
TextControl.FileSize = FileSizeText;
|
|
436
|
-
TextControl.Separator = Separator
|
|
449
|
+
TextControl.Separator = Separator;
|
|
437
450
|
const Container$1 = elements.withBaseElementProps(ListItem, {
|
|
438
451
|
className: `${IMAGE_ITEM_BLOCK}__list-item`,
|
|
439
452
|
});
|
|
@@ -551,9 +564,12 @@ const FormControl = () => {
|
|
|
551
564
|
const { input, setInput } = React__namespace["default"].useContext(ConversationInputContext);
|
|
552
565
|
const handleSendMessage = React__namespace["default"].useContext(SendMessageContext);
|
|
553
566
|
const allowAttachments = React__namespace["default"].useContext(AttachmentContext);
|
|
554
|
-
const ref = React__namespace["default"].useRef(null);
|
|
555
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);
|
|
556
571
|
const controls = React__namespace["default"].useContext(ControlsContext);
|
|
572
|
+
const [composing, setComposing] = React__namespace["default"].useState(false);
|
|
557
573
|
const submitMessage = async () => {
|
|
558
574
|
ref.current?.reset();
|
|
559
575
|
const submittedContent = [];
|
|
@@ -578,6 +594,7 @@ const FormControl = () => {
|
|
|
578
594
|
if (handleSendMessage) {
|
|
579
595
|
handleSendMessage({
|
|
580
596
|
content: submittedContent,
|
|
597
|
+
aiContext: ui.isFunction(aiContext) ? aiContext() : undefined,
|
|
581
598
|
toolConfiguration: convertResponseComponentsToToolConfiguration(responseComponents),
|
|
582
599
|
});
|
|
583
600
|
}
|
|
@@ -590,7 +607,7 @@ const FormControl = () => {
|
|
|
590
607
|
};
|
|
591
608
|
const handleOnKeyDown = (event) => {
|
|
592
609
|
const { key, shiftKey } = event;
|
|
593
|
-
if (key === 'Enter' && !shiftKey) {
|
|
610
|
+
if (key === 'Enter' && !shiftKey && !composing) {
|
|
594
611
|
event.preventDefault();
|
|
595
612
|
const hasInput = !!input?.text || (input?.files?.length && input?.files?.length > 0);
|
|
596
613
|
if (hasInput) {
|
|
@@ -599,14 +616,14 @@ const FormControl = () => {
|
|
|
599
616
|
}
|
|
600
617
|
};
|
|
601
618
|
if (controls?.Form) {
|
|
602
|
-
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 }));
|
|
603
620
|
}
|
|
604
621
|
return (React__namespace["default"].createElement("form", { className: `${FIELD_BLOCK}__form`, onSubmit: handleSubmit, method: "post", ref: ref },
|
|
605
622
|
allowAttachments ? React__namespace["default"].createElement(AttachFileControl, null) : null,
|
|
606
623
|
React__namespace["default"].createElement(InputContainer, null,
|
|
607
624
|
React__namespace["default"].createElement(VisuallyHidden, null,
|
|
608
625
|
React__namespace["default"].createElement(Label, null)),
|
|
609
|
-
React__namespace["default"].createElement(TextInput, { onKeyDown: handleOnKeyDown }),
|
|
626
|
+
React__namespace["default"].createElement(TextInput, { onKeyDown: handleOnKeyDown, onCompositionStart: () => setComposing(true), onCompositionEnd: () => setComposing(false) }),
|
|
610
627
|
React__namespace["default"].createElement(AttachmentListControl, null)),
|
|
611
628
|
React__namespace["default"].createElement(SendButton, null,
|
|
612
629
|
React__namespace["default"].createElement(SendIcon, null))));
|
|
@@ -618,61 +635,52 @@ FormControl.TextInput = TextInput;
|
|
|
618
635
|
FormControl.SendButton = SendButton;
|
|
619
636
|
FormControl.SendIcon = SendIcon;
|
|
620
637
|
|
|
621
|
-
const {
|
|
622
|
-
const MESSAGES_BLOCK = 'ai-
|
|
623
|
-
const MESSAGE_BLOCK = 'ai-
|
|
624
|
-
const
|
|
625
|
-
alt: 'Image attachment',
|
|
626
|
-
});
|
|
627
|
-
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) => {
|
|
628
642
|
const variant = React__namespace["default"].useContext(MessageVariantContext);
|
|
629
643
|
const role = React__namespace["default"].useContext(RoleContext);
|
|
630
|
-
return (React__namespace["default"].createElement(
|
|
631
|
-
}
|
|
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
|
+
};
|
|
632
646
|
const TextContent = React__namespace["default"].forwardRef(function TextContent(props, ref) {
|
|
633
647
|
return React__namespace["default"].createElement(Text, { ref: ref, className: `${MESSAGE_BLOCK}__text`, ...props });
|
|
634
648
|
});
|
|
635
|
-
const ContentContainer = React__namespace["default"].forwardRef(function ContentContainer(props, ref) {
|
|
636
|
-
const variant = React__namespace["default"].useContext(MessageVariantContext);
|
|
637
|
-
return (React__namespace["default"].createElement(View$1, { "data-testid": 'content', className: `${MESSAGE_BLOCK}__content ${MESSAGE_BLOCK}__content--${variant}`, ref: ref, ...props }));
|
|
638
|
-
});
|
|
639
649
|
const ToolContent = ({ toolUse, }) => {
|
|
640
|
-
const responseComponents = React__namespace["default"].useContext(ResponseComponentsContext);
|
|
650
|
+
const responseComponents = React__namespace["default"].useContext(ResponseComponentsContext) ?? {};
|
|
651
|
+
const FallbackComponent = React__namespace["default"].useContext(FallbackComponentContext);
|
|
641
652
|
// For now tool use is limited to custom response components
|
|
642
653
|
const { name, input } = toolUse;
|
|
643
|
-
if (!
|
|
644
|
-
!name ||
|
|
645
|
-
!name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
|
|
654
|
+
if (!name || !name.startsWith(RESPONSE_COMPONENT_PREFIX)) {
|
|
646
655
|
return;
|
|
647
656
|
}
|
|
648
657
|
else {
|
|
649
658
|
const response = responseComponents[name];
|
|
650
|
-
|
|
651
|
-
|
|
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
|
+
}
|
|
652
668
|
}
|
|
653
669
|
};
|
|
654
670
|
const MessageControl = ({ message }) => {
|
|
655
671
|
const messageRenderer = React__namespace["default"].useContext(MessageRendererContext);
|
|
656
|
-
return (React__namespace["default"].createElement(
|
|
672
|
+
return (React__namespace["default"].createElement(React__namespace["default"].Fragment, null, message.content.map((content, index) => {
|
|
657
673
|
if (content.text) {
|
|
658
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));
|
|
659
675
|
}
|
|
660
676
|
else if (content.image) {
|
|
661
|
-
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, 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) }));
|
|
662
678
|
}
|
|
663
679
|
else if (content.toolUse) {
|
|
664
680
|
return React__namespace["default"].createElement(ToolContent, { toolUse: content.toolUse, key: index });
|
|
665
681
|
}
|
|
666
682
|
})));
|
|
667
683
|
};
|
|
668
|
-
MessageControl.Container = ContentContainer;
|
|
669
|
-
MessageControl.MediaContent = MediaContent;
|
|
670
|
-
MessageControl.TextContent = TextContent;
|
|
671
|
-
const Separator = elements.withBaseElementProps(Span, {
|
|
672
|
-
'aria-hidden': true,
|
|
673
|
-
children: '|',
|
|
674
|
-
className: `${MESSAGE_BLOCK}__separator`,
|
|
675
|
-
});
|
|
676
684
|
const Timestamp = elements.withBaseElementProps(Text, {
|
|
677
685
|
className: `${MESSAGE_BLOCK}__timestamp`,
|
|
678
686
|
});
|
|
@@ -733,7 +741,6 @@ const MessagesControl = () => {
|
|
|
733
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) },
|
|
734
742
|
React__namespace["default"].createElement(HeaderContainer, null,
|
|
735
743
|
React__namespace["default"].createElement(AvatarControl, null),
|
|
736
|
-
React__namespace["default"].createElement(Separator, null),
|
|
737
744
|
React__namespace["default"].createElement(Timestamp, null, getMessageTimestampText(new Date(message.createdAt)))),
|
|
738
745
|
React__namespace["default"].createElement(MessageControl, { message: message }),
|
|
739
746
|
message.role === 'assistant' ? (React__namespace["default"].createElement(ActionsBarControl, { message: message, focusable: focusedItemIndex === index })) : null)));
|
|
@@ -745,7 +752,6 @@ MessagesControl.Container = MessageContainer;
|
|
|
745
752
|
MessagesControl.HeaderContainer = HeaderContainer;
|
|
746
753
|
MessagesControl.Layout = Layout;
|
|
747
754
|
MessagesControl.Message = MessageControl;
|
|
748
|
-
MessagesControl.Separator = Separator;
|
|
749
755
|
|
|
750
756
|
const { View, Button } = AIConversationElements;
|
|
751
757
|
const PROMPT_BLOCK = 'ai-prompts';
|
|
@@ -789,25 +795,27 @@ PromptControl.Container = Container;
|
|
|
789
795
|
PromptControl.PromptGroup = PromptGroup;
|
|
790
796
|
PromptControl.PromptCard = PromptCard;
|
|
791
797
|
|
|
792
|
-
const AIConversationProvider = ({ actions, allowAttachments, avatars, children, controls, displayText,
|
|
798
|
+
const AIConversationProvider = ({ aiContext, actions, allowAttachments, avatars, children, controls, displayText, handleSendMessage, isLoading, messages, messageRenderer, responseComponents, suggestedPrompts, variant, welcomeMessage, FallbackResponseComponent, }) => {
|
|
793
799
|
const _displayText = {
|
|
794
800
|
...defaultAIConversationDisplayTextEn,
|
|
795
801
|
...displayText,
|
|
796
802
|
};
|
|
797
|
-
return (React__namespace["default"].createElement(
|
|
798
|
-
React__namespace["default"].createElement(
|
|
799
|
-
React__namespace["default"].createElement(
|
|
800
|
-
React__namespace["default"].createElement(
|
|
801
|
-
React__namespace["default"].createElement(
|
|
802
|
-
React__namespace["default"].createElement(
|
|
803
|
-
React__namespace["default"].createElement(
|
|
804
|
-
React__namespace["default"].createElement(
|
|
805
|
-
React__namespace["default"].createElement(
|
|
806
|
-
React__namespace["default"].createElement(
|
|
807
|
-
React__namespace["default"].createElement(
|
|
808
|
-
React__namespace["default"].createElement(
|
|
809
|
-
React__namespace["default"].createElement(
|
|
810
|
-
React__namespace["default"].createElement(
|
|
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)))))))))))))))));
|
|
811
819
|
};
|
|
812
820
|
|
|
813
821
|
const DefaultMessageControl = () => {
|
|
@@ -820,15 +828,11 @@ const DefaultMessageControl = () => {
|
|
|
820
828
|
}
|
|
821
829
|
};
|
|
822
830
|
|
|
823
|
-
/**
|
|
824
|
-
* @experimental
|
|
825
|
-
*/
|
|
826
831
|
function createAIConversation(input = {}) {
|
|
827
|
-
const {
|
|
832
|
+
const { suggestedPrompts, actions, responseComponents, variant, controls, displayText, allowAttachments, messageRenderer, FallbackResponseComponent, } = input;
|
|
828
833
|
function AIConversation(props) {
|
|
829
834
|
const { messages, avatars, handleSendMessage, isLoading } = props;
|
|
830
835
|
const providerProps = {
|
|
831
|
-
elements,
|
|
832
836
|
actions,
|
|
833
837
|
suggestedPrompts,
|
|
834
838
|
responseComponents,
|
|
@@ -841,6 +845,7 @@ function createAIConversation(input = {}) {
|
|
|
841
845
|
handleSendMessage,
|
|
842
846
|
isLoading,
|
|
843
847
|
messageRenderer,
|
|
848
|
+
FallbackResponseComponent,
|
|
844
849
|
};
|
|
845
850
|
return (React__namespace["default"].createElement(AIConversationProvider, { ...providerProps },
|
|
846
851
|
React__namespace["default"].createElement(ViewElement, null,
|
|
@@ -857,6 +862,16 @@ function createAIConversation(input = {}) {
|
|
|
857
862
|
return { AIConversation };
|
|
858
863
|
}
|
|
859
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
|
+
};
|
|
860
875
|
const MessageMeta = ({ message }) => {
|
|
861
876
|
// need to pass this in as props in order for it to be overridable
|
|
862
877
|
const avatars = React__namespace.useContext(AvatarsContext);
|
|
@@ -868,29 +883,30 @@ const MessageMeta = ({ message }) => {
|
|
|
868
883
|
React__namespace.createElement(uiReact.Text, { className: ui.ComponentClassName.AIConversationMessageSenderUsername }, avatar?.username),
|
|
869
884
|
React__namespace.createElement(uiReact.Text, { className: ui.ComponentClassName.AIConversationMessageSenderTimestamp }, getMessageTimestampText(new Date(message.createdAt)))));
|
|
870
885
|
};
|
|
871
|
-
const
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
return (React__namespace.createElement(uiReact.View, { className: ui.
|
|
876
|
-
React__namespace.createElement(uiReact.
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
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
|
+
})));
|
|
881
895
|
};
|
|
882
896
|
const Message = ({ message }) => {
|
|
883
897
|
const avatars = React__namespace.useContext(AvatarsContext);
|
|
884
898
|
const variant = React__namespace.useContext(MessageVariantContext);
|
|
899
|
+
const { isLoading } = message;
|
|
885
900
|
const avatar = message.role === 'assistant' ? avatars?.ai : avatars?.user;
|
|
886
901
|
return (React__namespace.createElement(RoleContext.Provider, { value: message.role },
|
|
887
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)) },
|
|
888
903
|
React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageAvatar },
|
|
889
|
-
React__namespace.createElement(uiReact.Avatar,
|
|
904
|
+
React__namespace.createElement(uiReact.Avatar, { isLoading: isLoading }, avatar?.avatar)),
|
|
890
905
|
React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageBody },
|
|
891
906
|
React__namespace.createElement(MessageMeta, { message: message }),
|
|
892
907
|
React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageContent },
|
|
893
|
-
React__namespace.createElement(MessageControl, { message: message }))
|
|
908
|
+
React__namespace.createElement(MessageControl, { message: message })),
|
|
909
|
+
message.role === 'assistant' ? (React__namespace.createElement(MessageActions, { message: message })) : null))));
|
|
894
910
|
};
|
|
895
911
|
const MessageList = ({ messages, }) => {
|
|
896
912
|
const isLoading = React__namespace.useContext(LoadingContext);
|
|
@@ -898,8 +914,10 @@ const MessageList = ({ messages, }) => {
|
|
|
898
914
|
content.text ??
|
|
899
915
|
content.toolUse?.name.startsWith(RESPONSE_COMPONENT_PREFIX))) ?? [];
|
|
900
916
|
return (React__namespace.createElement(uiReact.View, { className: ui.ComponentClassName.AIConversationMessageList },
|
|
901
|
-
|
|
902
|
-
|
|
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 })))));
|
|
903
921
|
};
|
|
904
922
|
|
|
905
923
|
const Attachment = ({ file, handleRemove, }) => {
|
|
@@ -945,12 +963,12 @@ const FormWrapper = ({ children, allowAttachments, setInput, }) => {
|
|
|
945
963
|
return children;
|
|
946
964
|
}
|
|
947
965
|
};
|
|
948
|
-
const Form = ({ setInput, input, handleSubmit, allowAttachments, }) => {
|
|
966
|
+
const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) => {
|
|
949
967
|
const icons = internal.useIcons('aiConversation');
|
|
950
968
|
const sendIcon = icons?.send ?? React__namespace.createElement(internal.IconSend, null);
|
|
951
969
|
const attachIcon = icons?.attach ?? React__namespace.createElement(internal.IconAttach, null);
|
|
952
970
|
const hiddenInput = React__namespace.useRef(null);
|
|
953
|
-
const
|
|
971
|
+
const [composing, setComposing] = React__namespace.useState(false);
|
|
954
972
|
const isInputEmpty = !input?.text?.length && !input?.files?.length;
|
|
955
973
|
return (React__namespace.createElement(FormWrapper, { allowAttachments: allowAttachments, setInput: setInput },
|
|
956
974
|
React__namespace.createElement(uiReact.View, { as: "form", className: ui.ComponentClassName.AIConversationForm, onSubmit: handleSubmit },
|
|
@@ -972,9 +990,9 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, }) => {
|
|
|
972
990
|
files: [...(prevValue?.files ?? []), ...Array.from(files)],
|
|
973
991
|
}));
|
|
974
992
|
}, multiple: true, accept: "*", "data-testid": "hidden-file-input" })))) : null,
|
|
975
|
-
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) => {
|
|
976
994
|
// Submit on enter key if shift is not pressed also
|
|
977
|
-
const shouldSubmit = !e.shiftKey && e.key === 'Enter';
|
|
995
|
+
const shouldSubmit = !e.shiftKey && e.key === 'Enter' && !composing;
|
|
978
996
|
if (shouldSubmit && isHTMLFormElement(e.target)) {
|
|
979
997
|
e.target.form.requestSubmit();
|
|
980
998
|
e.preventDefault();
|
|
@@ -1004,9 +1022,9 @@ const PromptList = ({ setInput, suggestedPrompts = [], }) => {
|
|
|
1004
1022
|
})));
|
|
1005
1023
|
};
|
|
1006
1024
|
|
|
1007
|
-
const VERSION = '0.
|
|
1025
|
+
const VERSION = '1.0.0';
|
|
1008
1026
|
|
|
1009
|
-
function
|
|
1027
|
+
function AIConversationBase({ avatars, controls, ...rest }) {
|
|
1010
1028
|
uiReactCore.useSetUserAgent({
|
|
1011
1029
|
componentName: 'AIConversation',
|
|
1012
1030
|
packageName: 'react-ai',
|
|
@@ -1016,83 +1034,53 @@ function Provider({ actions, avatars, controls, handleSendMessage, messages, res
|
|
|
1016
1034
|
const defaultAvatars = {
|
|
1017
1035
|
ai: {
|
|
1018
1036
|
username: 'Assistant',
|
|
1019
|
-
avatar: icons?.assistant ?? React__namespace.createElement(internal.IconAssistant,
|
|
1037
|
+
avatar: icons?.assistant ?? React__namespace.createElement(internal.IconAssistant, { testId: "icon-assistant" }),
|
|
1020
1038
|
},
|
|
1021
1039
|
user: {
|
|
1022
1040
|
username: 'User',
|
|
1023
|
-
avatar: icons?.user ?? React__namespace.createElement(internal.IconUser,
|
|
1041
|
+
avatar: icons?.user ?? React__namespace.createElement(internal.IconUser, { testId: "icon-user" }),
|
|
1024
1042
|
},
|
|
1025
1043
|
};
|
|
1026
1044
|
const providerProps = {
|
|
1027
|
-
|
|
1028
|
-
handleSendMessage,
|
|
1045
|
+
...rest,
|
|
1029
1046
|
avatars: {
|
|
1030
1047
|
...defaultAvatars,
|
|
1031
1048
|
...avatars,
|
|
1032
1049
|
},
|
|
1033
|
-
isLoading,
|
|
1034
|
-
elements: {
|
|
1035
|
-
Text: uiReact.Text,
|
|
1036
|
-
},
|
|
1037
|
-
actions,
|
|
1038
|
-
suggestedPrompts,
|
|
1039
|
-
responseComponents,
|
|
1040
|
-
variant,
|
|
1041
1050
|
controls: {
|
|
1042
1051
|
MessageList,
|
|
1043
1052
|
PromptList,
|
|
1044
1053
|
Form,
|
|
1045
1054
|
...controls,
|
|
1046
1055
|
},
|
|
1047
|
-
displayText,
|
|
1048
|
-
allowAttachments,
|
|
1049
|
-
messageRenderer,
|
|
1050
1056
|
};
|
|
1051
|
-
return (React__namespace.createElement(AIConversationProvider, { ...providerProps },
|
|
1052
|
-
}
|
|
1053
|
-
function AIConversationBase(props) {
|
|
1054
|
-
return (React__namespace.createElement(Provider, { ...props },
|
|
1055
|
-
React__namespace.createElement(uiReact.Flex, { className: ui.ComponentClassName.AIConversation },
|
|
1057
|
+
return (React__namespace.createElement(AIConversationProvider, { ...providerProps },
|
|
1058
|
+
React__namespace.createElement(uiReact.Flex, { className: ui.ComponentClassName.AIConversation, testId: "ai-conversation" },
|
|
1056
1059
|
React__namespace.createElement(uiReact.ScrollView, { autoScroll: "smooth", flex: "1" },
|
|
1057
1060
|
React__namespace.createElement(DefaultMessageControl, null),
|
|
1058
1061
|
React__namespace.createElement(MessagesControl, null)),
|
|
1059
1062
|
React__namespace.createElement(FormControl, null))));
|
|
1060
1063
|
}
|
|
1061
|
-
/**
|
|
1062
|
-
* @experimental
|
|
1063
|
-
*/
|
|
1064
1064
|
const AIConversation = Object.assign(AIConversationBase, {
|
|
1065
|
-
Provider,
|
|
1065
|
+
Provider: AIConversationProvider,
|
|
1066
1066
|
DefaultMessage: DefaultMessageControl,
|
|
1067
1067
|
Messages: MessagesControl,
|
|
1068
1068
|
Form: FormControl,
|
|
1069
1069
|
});
|
|
1070
1070
|
|
|
1071
|
-
const AIContext = React__namespace["default"].createContext(undefined);
|
|
1072
|
-
const useAIContext = () => {
|
|
1073
|
-
const context = React__namespace["default"].useContext(AIContext);
|
|
1074
|
-
const [routeToConversationsMap, setRouteToConversationsMap] = React__namespace["default"].useState({});
|
|
1075
|
-
if (context) {
|
|
1076
|
-
return context;
|
|
1077
|
-
}
|
|
1078
|
-
return { routeToConversationsMap, setRouteToConversationsMap };
|
|
1079
|
-
};
|
|
1080
|
-
/**
|
|
1081
|
-
* @experimental
|
|
1082
|
-
*/
|
|
1083
|
-
const AIContextProvider = ({ children, }) => {
|
|
1084
|
-
const context = useAIContext();
|
|
1085
|
-
return React__namespace["default"].createElement(AIContext.Provider, { value: context }, children);
|
|
1086
|
-
};
|
|
1087
|
-
|
|
1088
1071
|
// default state
|
|
1089
1072
|
const INITIAL_STATE = {
|
|
1090
1073
|
hasError: false,
|
|
1091
1074
|
isLoading: false,
|
|
1092
1075
|
messages: undefined,
|
|
1093
1076
|
};
|
|
1094
|
-
const LOADING_STATE = {
|
|
1077
|
+
const LOADING_STATE = {
|
|
1078
|
+
hasError: false,
|
|
1079
|
+
isLoading: true,
|
|
1080
|
+
messages: undefined,
|
|
1081
|
+
};
|
|
1095
1082
|
const ERROR_STATE = { hasError: true, isLoading: false };
|
|
1083
|
+
|
|
1096
1084
|
function createUseAIGeneration(client) {
|
|
1097
1085
|
const useAIGeneration = (routeName) => {
|
|
1098
1086
|
const [dataState, setDataState] = React__namespace.useState(() => ({
|
|
@@ -1119,142 +1107,314 @@ function createUseAIGeneration(client) {
|
|
|
1119
1107
|
return useAIGeneration;
|
|
1120
1108
|
}
|
|
1121
1109
|
|
|
1122
|
-
|
|
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
|
+
}
|
|
1123
1143
|
return {
|
|
1124
|
-
...
|
|
1125
|
-
|
|
1126
|
-
...previousValue[routeName],
|
|
1127
|
-
[conversationId]: messages,
|
|
1128
|
-
},
|
|
1144
|
+
...result,
|
|
1145
|
+
data: messages,
|
|
1129
1146
|
};
|
|
1130
1147
|
}
|
|
1148
|
+
|
|
1149
|
+
function hasStarted(state) {
|
|
1150
|
+
return ['initialLoading', 'initialized'].includes(state);
|
|
1151
|
+
}
|
|
1131
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
|
|
1132
1163
|
const useAIConversation = (routeName, input = {}) => {
|
|
1133
1164
|
const clientRoute = client.conversations[routeName];
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
const
|
|
1141
|
-
const [
|
|
1142
|
-
|
|
1143
|
-
|
|
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;
|
|
1144
1178
|
React__namespace["default"].useEffect(() => {
|
|
1145
1179
|
async function initialize() {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
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
|
+
});
|
|
1154
1192
|
}
|
|
1155
|
-
const { data:
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
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,
|
|
1164
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
|
+
],
|
|
1165
1237
|
});
|
|
1238
|
+
return;
|
|
1166
1239
|
}
|
|
1167
1240
|
initialize();
|
|
1168
|
-
|
|
1169
|
-
|
|
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
|
|
1170
1254
|
React__namespace["default"].useEffect(() => {
|
|
1171
|
-
if (
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
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
|
+
};
|
|
1195
1289
|
});
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
}, [conversation, routeName, setRouteToConversationsMap]);
|
|
1204
|
-
const subscribe = React__namespace["default"].useCallback((handleStoreChange) => {
|
|
1205
|
-
const subscription = conversation &&
|
|
1206
|
-
conversation.onMessage((message) => {
|
|
1207
|
-
if (input.onResponse)
|
|
1208
|
-
input.onResponse(message);
|
|
1209
|
-
setWaitingForAIResponse(false);
|
|
1210
|
-
setLocalMessages((previousLocalMessages) => [
|
|
1211
|
-
...previousLocalMessages,
|
|
1212
|
-
message,
|
|
1213
|
-
]);
|
|
1214
|
-
setRouteToConversationsMap((previousValue) => {
|
|
1215
|
-
return createNewConversationMessageInRoute({
|
|
1216
|
-
previousValue,
|
|
1217
|
-
routeName: routeName,
|
|
1218
|
-
conversationId: conversation.id,
|
|
1219
|
-
messages: [
|
|
1220
|
-
...previousValue[routeName][conversation.id],
|
|
1221
|
-
message,
|
|
1222
|
-
],
|
|
1290
|
+
onMessage?.({
|
|
1291
|
+
id,
|
|
1292
|
+
conversationId,
|
|
1293
|
+
content: contentFromEvents(contentBlocksRef.current),
|
|
1294
|
+
createdAt: new Date().toISOString(),
|
|
1295
|
+
role: 'assistant',
|
|
1296
|
+
isLoading: true,
|
|
1223
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
|
+
};
|
|
1224
1343
|
});
|
|
1225
|
-
|
|
1226
|
-
|
|
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
|
+
}
|
|
1227
1358
|
return () => {
|
|
1228
|
-
|
|
1359
|
+
contentBlocksRef.current = undefined;
|
|
1360
|
+
subscription.unsubscribe();
|
|
1229
1361
|
};
|
|
1230
|
-
}, [conversation,
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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];
|
|
1244
1408
|
};
|
|
1245
1409
|
return useAIConversation;
|
|
1246
1410
|
}
|
|
1247
1411
|
|
|
1248
|
-
/**
|
|
1249
|
-
* @experimental
|
|
1250
|
-
*/
|
|
1251
1412
|
function createAIHooks(_client) {
|
|
1252
1413
|
const useAIConversation = createUseAIConversation(_client);
|
|
1253
1414
|
const useAIGeneration = createUseAIGeneration(_client);
|
|
1254
1415
|
return { useAIConversation, useAIGeneration };
|
|
1255
1416
|
}
|
|
1256
1417
|
|
|
1257
|
-
exports.AIContextProvider = AIContextProvider;
|
|
1258
1418
|
exports.AIConversation = AIConversation;
|
|
1259
1419
|
exports.createAIConversation = createAIConversation;
|
|
1260
1420
|
exports.createAIHooks = createAIHooks;
|