@aws-amplify/ui-react-ai 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/AIConversation/AIConversationProvider.mjs +2 -2
- package/dist/esm/components/AIConversation/context/AttachmentContext.mjs +12 -3
- package/dist/esm/components/AIConversation/context/ConversationInputContext.mjs +2 -1
- package/dist/esm/components/AIConversation/context/elements/IconElement.mjs +2 -2
- package/dist/esm/components/AIConversation/context/elements/definitions.mjs +12 -12
- package/dist/esm/components/AIConversation/displayText.mjs +6 -0
- package/dist/esm/components/AIConversation/utils.mjs +42 -13
- package/dist/esm/components/AIConversation/views/Controls/FormControl.mjs +35 -6
- package/dist/esm/components/AIConversation/views/default/Form.mjs +10 -16
- package/dist/esm/version.mjs +1 -1
- package/dist/index.js +117 -49
- package/dist/types/components/AIConversation/AIConversationProvider.d.ts +1 -1
- package/dist/types/components/AIConversation/context/AttachmentContext.d.ts +5 -5
- package/dist/types/components/AIConversation/context/ControlsContext.d.ts +4 -3
- package/dist/types/components/AIConversation/context/ConversationInputContext.d.ts +4 -2
- package/dist/types/components/AIConversation/context/DisplayTextContext.d.ts +1 -1
- package/dist/types/components/AIConversation/context/MessageRenderContext.d.ts +1 -1
- package/dist/types/components/AIConversation/context/elements/IconElement.d.ts +2 -2
- package/dist/types/components/AIConversation/context/elements/definitions.d.ts +11 -11
- package/dist/types/components/AIConversation/context/index.d.ts +2 -2
- package/dist/types/components/AIConversation/displayText.d.ts +2 -0
- package/dist/types/components/AIConversation/types.d.ts +2 -0
- package/dist/types/components/AIConversation/utils.d.ts +10 -0
- package/dist/types/components/AIConversation/views/default/Attachments.d.ts +2 -2
- package/dist/types/version.d.ts +1 -1
- package/package.json +6 -6
|
@@ -18,7 +18,7 @@ import { WelcomeMessageProvider } from './context/WelcomeMessageContext.mjs';
|
|
|
18
18
|
import { FallbackComponentProvider } from './context/FallbackComponentContext.mjs';
|
|
19
19
|
import './context/elements/definitions.mjs';
|
|
20
20
|
|
|
21
|
-
const AIConversationProvider = ({ aiContext, actions, allowAttachments, avatars, children, controls, displayText, handleSendMessage, isLoading, messages, messageRenderer, responseComponents, suggestedPrompts, variant, welcomeMessage, FallbackResponseComponent, }) => {
|
|
21
|
+
const AIConversationProvider = ({ aiContext, actions, allowAttachments, avatars, children, controls, displayText, handleSendMessage, isLoading, maxAttachmentSize, maxAttachments, messages, messageRenderer, responseComponents, suggestedPrompts, variant, welcomeMessage, FallbackResponseComponent, }) => {
|
|
22
22
|
const _displayText = {
|
|
23
23
|
...defaultAIConversationDisplayTextEn,
|
|
24
24
|
...displayText,
|
|
@@ -29,7 +29,7 @@ const AIConversationProvider = ({ aiContext, actions, allowAttachments, avatars,
|
|
|
29
29
|
React__default.createElement(FallbackComponentProvider, { FallbackComponent: FallbackResponseComponent },
|
|
30
30
|
React__default.createElement(MessageRendererProvider, { ...messageRenderer },
|
|
31
31
|
React__default.createElement(ResponseComponentsProvider, { responseComponents: responseComponents },
|
|
32
|
-
React__default.createElement(AttachmentProvider, { allowAttachments: allowAttachments },
|
|
32
|
+
React__default.createElement(AttachmentProvider, { allowAttachments: allowAttachments, maxAttachmentSize: maxAttachmentSize, maxAttachments: maxAttachments },
|
|
33
33
|
React__default.createElement(ConversationDisplayTextProvider, { ..._displayText },
|
|
34
34
|
React__default.createElement(ConversationInputContextProvider, null,
|
|
35
35
|
React__default.createElement(SendMessageContextProvider, { handleSendMessage: handleSendMessage },
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
const AttachmentContext = React.createContext(
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const AttachmentContext = React.createContext({
|
|
4
|
+
allowAttachments: false,
|
|
5
|
+
// We save attachments as base64 strings into dynamodb for conversation history
|
|
6
|
+
// DynamoDB has a max size of 400kb for records
|
|
7
|
+
// This can be overridden so cutsomers could provide a lower number
|
|
8
|
+
// or a higher number if in the future we support larger sizes.
|
|
9
|
+
maxAttachmentSize: 400000,
|
|
10
|
+
maxAttachments: 20,
|
|
11
|
+
});
|
|
12
|
+
const AttachmentProvider = ({ children, allowAttachments = false, maxAttachmentSize = 400000, maxAttachments = 20, }) => {
|
|
13
|
+
const providerValue = React.useMemo(() => ({ maxAttachmentSize, maxAttachments, allowAttachments }), [maxAttachmentSize, maxAttachments, allowAttachments]);
|
|
14
|
+
return (React.createElement(AttachmentContext.Provider, { value: providerValue }, children));
|
|
6
15
|
};
|
|
7
16
|
|
|
8
17
|
export { AttachmentContext, AttachmentProvider };
|
|
@@ -3,7 +3,8 @@ import React__default from 'react';
|
|
|
3
3
|
const ConversationInputContext = React__default.createContext({});
|
|
4
4
|
const ConversationInputContextProvider = ({ children, }) => {
|
|
5
5
|
const [input, setInput] = React__default.useState();
|
|
6
|
-
const
|
|
6
|
+
const [error, setError] = React__default.useState();
|
|
7
|
+
const providerValue = React__default.useMemo(() => ({ input, setInput, error, setError }), [input, setInput, error, setError]);
|
|
7
8
|
return (React__default.createElement(ConversationInputContext.Provider, { value: providerValue }, children));
|
|
8
9
|
};
|
|
9
10
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineBaseElementWithRef, withBaseElementProps } from '@aws-amplify/ui-react-core/elements';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
|
|
4
4
|
const DEFAULT_ICON_PATHS = {
|
|
@@ -17,7 +17,7 @@ const DEFAULT_ICON_ATTRIBUTES = {
|
|
|
17
17
|
fill: 'none',
|
|
18
18
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
19
19
|
};
|
|
20
|
-
const BaseIconElement =
|
|
20
|
+
const BaseIconElement = defineBaseElementWithRef({
|
|
21
21
|
type: 'svg',
|
|
22
22
|
displayName: 'Icon',
|
|
23
23
|
});
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineBaseElementWithRef } from '@aws-amplify/ui-react-core/elements';
|
|
2
2
|
import { IconElement } from './IconElement.mjs';
|
|
3
3
|
|
|
4
|
-
const LabelElement =
|
|
4
|
+
const LabelElement = defineBaseElementWithRef({
|
|
5
5
|
type: 'label',
|
|
6
6
|
displayName: 'Label',
|
|
7
7
|
});
|
|
8
|
-
const TextElement =
|
|
8
|
+
const TextElement = defineBaseElementWithRef({
|
|
9
9
|
type: 'p',
|
|
10
10
|
displayName: 'Text',
|
|
11
11
|
});
|
|
12
|
-
const UnorderedListElement =
|
|
12
|
+
const UnorderedListElement = defineBaseElementWithRef({
|
|
13
13
|
type: 'ul',
|
|
14
14
|
displayName: 'UnorderedList',
|
|
15
15
|
});
|
|
16
|
-
const ListItemElement =
|
|
16
|
+
const ListItemElement = defineBaseElementWithRef({
|
|
17
17
|
type: 'li',
|
|
18
18
|
displayName: 'ListItem',
|
|
19
19
|
});
|
|
20
|
-
const HeadingElement =
|
|
20
|
+
const HeadingElement = defineBaseElementWithRef({
|
|
21
21
|
type: 'h2',
|
|
22
22
|
displayName: 'Title',
|
|
23
23
|
});
|
|
24
|
-
const ImageElement =
|
|
24
|
+
const ImageElement = defineBaseElementWithRef({
|
|
25
25
|
type: 'img',
|
|
26
26
|
displayName: 'Image',
|
|
27
27
|
});
|
|
28
|
-
const InputElement =
|
|
28
|
+
const InputElement = defineBaseElementWithRef({
|
|
29
29
|
type: 'input',
|
|
30
30
|
displayName: 'Input',
|
|
31
31
|
});
|
|
32
|
-
const ButtonElement =
|
|
33
|
-
const ViewElement =
|
|
32
|
+
const ButtonElement = defineBaseElementWithRef({ type: 'button', displayName: 'Button' });
|
|
33
|
+
const ViewElement = defineBaseElementWithRef({
|
|
34
34
|
type: 'div',
|
|
35
35
|
displayName: 'View',
|
|
36
36
|
});
|
|
37
|
-
const SpanElement =
|
|
37
|
+
const SpanElement = defineBaseElementWithRef({
|
|
38
38
|
type: 'span',
|
|
39
39
|
displayName: 'Span',
|
|
40
40
|
});
|
|
41
|
-
const TextAreaElement =
|
|
41
|
+
const TextAreaElement = defineBaseElementWithRef({
|
|
42
42
|
type: 'textarea',
|
|
43
43
|
displayName: 'TextArea',
|
|
44
44
|
});
|
|
@@ -2,6 +2,12 @@ import { formatDate } from './utils.mjs';
|
|
|
2
2
|
|
|
3
3
|
const defaultAIConversationDisplayTextEn = {
|
|
4
4
|
getMessageTimestampText: (date) => formatDate(date),
|
|
5
|
+
getMaxAttachmentErrorText(count) {
|
|
6
|
+
return `Cannot choose more than ${count} ${count === 1 ? 'file' : 'files'}. `;
|
|
7
|
+
},
|
|
8
|
+
getAttachmentSizeErrorText(sizeText) {
|
|
9
|
+
return `File size must be below ${sizeText}.`;
|
|
10
|
+
},
|
|
5
11
|
};
|
|
6
12
|
|
|
7
13
|
export { defaultAIConversationDisplayTextEn };
|
|
@@ -12,28 +12,57 @@ function formatDate(date) {
|
|
|
12
12
|
return `${dateString} at ${timeString}`;
|
|
13
13
|
}
|
|
14
14
|
function arrayBufferToBase64(buffer) {
|
|
15
|
-
let binary = '';
|
|
16
|
-
const bytes = new Uint8Array(buffer);
|
|
17
|
-
const len = bytes.byteLength;
|
|
18
|
-
for (let i = 0; i < len; i++) {
|
|
19
|
-
binary += String.fromCharCode(bytes[i]);
|
|
20
|
-
}
|
|
21
|
-
return window.btoa(binary);
|
|
22
|
-
}
|
|
23
|
-
function convertBufferToBase64(buffer, format) {
|
|
24
|
-
let base64string = '';
|
|
25
15
|
// Use node-based buffer if available
|
|
26
16
|
// fall back on browser if not
|
|
27
17
|
if (typeof Buffer !== 'undefined') {
|
|
28
|
-
|
|
18
|
+
return Buffer.from(new Uint8Array(buffer)).toString('base64');
|
|
29
19
|
}
|
|
30
20
|
else {
|
|
31
|
-
|
|
21
|
+
let binary = '';
|
|
22
|
+
const bytes = new Uint8Array(buffer);
|
|
23
|
+
const len = bytes.byteLength;
|
|
24
|
+
for (let i = 0; i < len; i++) {
|
|
25
|
+
binary += String.fromCharCode(bytes[i]);
|
|
26
|
+
}
|
|
27
|
+
return window.btoa(binary);
|
|
32
28
|
}
|
|
29
|
+
}
|
|
30
|
+
function convertBufferToBase64(buffer, format) {
|
|
31
|
+
const base64string = arrayBufferToBase64(buffer);
|
|
33
32
|
return `data:image/${format};base64,${base64string}`;
|
|
34
33
|
}
|
|
35
34
|
function getImageTypeFromMimeType(mimeType) {
|
|
36
35
|
return mimeType.split('/')[1];
|
|
37
36
|
}
|
|
37
|
+
async function attachmentsValidator({ files, maxAttachments, maxAttachmentSize, }) {
|
|
38
|
+
const acceptedFiles = [];
|
|
39
|
+
const rejectedFiles = [];
|
|
40
|
+
let hasMaxSizeError = false;
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
43
|
+
const base64 = arrayBufferToBase64(arrayBuffer);
|
|
44
|
+
if (base64.length < maxAttachmentSize) {
|
|
45
|
+
acceptedFiles.push(file);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
rejectedFiles.push(file);
|
|
49
|
+
hasMaxSizeError = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (acceptedFiles.length > maxAttachments) {
|
|
53
|
+
return {
|
|
54
|
+
acceptedFiles: acceptedFiles.slice(0, maxAttachments),
|
|
55
|
+
rejectedFiles: [...acceptedFiles.slice(maxAttachments), ...rejectedFiles],
|
|
56
|
+
hasMaxAttachmentsError: true,
|
|
57
|
+
hasMaxAttachmentSizeError: hasMaxSizeError,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
acceptedFiles,
|
|
62
|
+
rejectedFiles,
|
|
63
|
+
hasMaxAttachmentsError: false,
|
|
64
|
+
hasMaxAttachmentSizeError: hasMaxSizeError,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
38
67
|
|
|
39
|
-
export { convertBufferToBase64, formatDate, getImageTypeFromMimeType };
|
|
68
|
+
export { attachmentsValidator, convertBufferToBase64, formatDate, getImageTypeFromMimeType };
|
|
@@ -7,7 +7,7 @@ import { ConversationInputContext } from '../../context/ConversationInputContext
|
|
|
7
7
|
import { MessagesContext } from '../../context/MessagesContext.mjs';
|
|
8
8
|
import '../../context/SuggestedPromptsContext.mjs';
|
|
9
9
|
import '../../context/MessageVariantContext.mjs';
|
|
10
|
-
import '../../context/DisplayTextContext.mjs';
|
|
10
|
+
import { useConversationDisplayText } from '../../context/DisplayTextContext.mjs';
|
|
11
11
|
import { ControlsContext } from '../../context/ControlsContext.mjs';
|
|
12
12
|
import { LoadingContext } from '../../context/LoadingContext.mjs';
|
|
13
13
|
import { ResponseComponentsContext, convertResponseComponentsToToolConfiguration } from '../../context/ResponseComponentsContext.mjs';
|
|
@@ -19,8 +19,8 @@ import '../../context/FallbackComponentContext.mjs';
|
|
|
19
19
|
import { AIConversationElements } from '../../context/elements/definitions.mjs';
|
|
20
20
|
import { AttachFileControl } from './AttachFileControl.mjs';
|
|
21
21
|
import { AttachmentListControl } from './AttachmentListControl.mjs';
|
|
22
|
-
import { getImageTypeFromMimeType } from '../../utils.mjs';
|
|
23
|
-
import { isFunction } from '@aws-amplify/ui';
|
|
22
|
+
import { attachmentsValidator, getImageTypeFromMimeType } from '../../utils.mjs';
|
|
23
|
+
import { humanFileSize, isFunction } from '@aws-amplify/ui';
|
|
24
24
|
|
|
25
25
|
const { Button, Icon, Label: LabelElement, TextArea, View, } = AIConversationElements;
|
|
26
26
|
const FIELD_BLOCK = 'ai-field';
|
|
@@ -103,9 +103,10 @@ const InputContainer = withBaseElementProps(View, {
|
|
|
103
103
|
className: `${FIELD_BLOCK}__input-container`,
|
|
104
104
|
});
|
|
105
105
|
const FormControl = () => {
|
|
106
|
-
const { input, setInput } = React__default.useContext(ConversationInputContext);
|
|
106
|
+
const { input, setInput, error, setError } = React__default.useContext(ConversationInputContext);
|
|
107
107
|
const handleSendMessage = React__default.useContext(SendMessageContext);
|
|
108
|
-
const allowAttachments = React__default.useContext(AttachmentContext);
|
|
108
|
+
const { allowAttachments, maxAttachmentSize, maxAttachments } = React__default.useContext(AttachmentContext);
|
|
109
|
+
const displayText = useConversationDisplayText();
|
|
109
110
|
const responseComponents = React__default.useContext(ResponseComponentsContext);
|
|
110
111
|
const isLoading = React__default.useContext(LoadingContext);
|
|
111
112
|
const aiContext = React__default.useContext(AIContextContext);
|
|
@@ -157,8 +158,36 @@ const FormControl = () => {
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
};
|
|
161
|
+
const onValidate = React__default.useCallback(async (files) => {
|
|
162
|
+
const previousFiles = input?.files ?? [];
|
|
163
|
+
const { acceptedFiles, hasMaxAttachmentsError, hasMaxAttachmentSizeError, } = await attachmentsValidator({
|
|
164
|
+
files: [...files, ...previousFiles],
|
|
165
|
+
maxAttachments,
|
|
166
|
+
maxAttachmentSize,
|
|
167
|
+
});
|
|
168
|
+
if (hasMaxAttachmentsError || hasMaxAttachmentSizeError) {
|
|
169
|
+
const errors = [];
|
|
170
|
+
if (hasMaxAttachmentsError) {
|
|
171
|
+
errors.push(displayText.getMaxAttachmentErrorText(maxAttachments));
|
|
172
|
+
}
|
|
173
|
+
if (hasMaxAttachmentSizeError) {
|
|
174
|
+
errors.push(displayText.getAttachmentSizeErrorText(
|
|
175
|
+
// base64 size is about 137% that of the file size
|
|
176
|
+
// https://en.wikipedia.org/wiki/Base64#MIME
|
|
177
|
+
humanFileSize((maxAttachmentSize - 814) / 1.37, true)));
|
|
178
|
+
}
|
|
179
|
+
setError?.(errors.join(' '));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
setError?.(undefined);
|
|
183
|
+
}
|
|
184
|
+
setInput?.((prevValue) => ({
|
|
185
|
+
...prevValue,
|
|
186
|
+
files: acceptedFiles,
|
|
187
|
+
}));
|
|
188
|
+
}, [setInput, input, displayText, maxAttachmentSize, maxAttachments, setError]);
|
|
160
189
|
if (controls?.Form) {
|
|
161
|
-
return (React__default.createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, allowAttachments: allowAttachments, isLoading: isLoading }));
|
|
190
|
+
return (React__default.createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, onValidate: onValidate, allowAttachments: allowAttachments, isLoading: isLoading, error: error, setError: setError }));
|
|
162
191
|
}
|
|
163
192
|
return (React__default.createElement("form", { className: `${FIELD_BLOCK}__form`, onSubmit: handleSubmit, method: "post", ref: ref },
|
|
164
193
|
allowAttachments ? React__default.createElement(AttachFileControl, null) : null,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { View, Button, VisuallyHidden, TextAreaField, DropZone } from '@aws-amplify/ui-react';
|
|
2
|
+
import { View, Button, VisuallyHidden, TextAreaField, Message, DropZone } from '@aws-amplify/ui-react';
|
|
3
3
|
import { useIcons, IconSend, IconAttach } from '@aws-amplify/ui-react/internal';
|
|
4
4
|
import { ComponentClassName } from '@aws-amplify/ui';
|
|
5
5
|
import { Attachments } from './Attachments.mjs';
|
|
@@ -11,27 +11,24 @@ function isHTMLFormElement(target) {
|
|
|
11
11
|
* Will conditionally render the DropZone if allowAttachments
|
|
12
12
|
* is true
|
|
13
13
|
*/
|
|
14
|
-
const FormWrapper = ({ children, allowAttachments,
|
|
14
|
+
const FormWrapper = ({ children, allowAttachments, onValidate, }) => {
|
|
15
15
|
if (allowAttachments) {
|
|
16
16
|
return (React.createElement(DropZone, { className: ComponentClassName.AIConversationFormDropzone, onDropComplete: ({ acceptedFiles }) => {
|
|
17
|
-
|
|
18
|
-
...prevInput,
|
|
19
|
-
files: [...(prevInput?.files ?? []), ...acceptedFiles],
|
|
20
|
-
}));
|
|
17
|
+
onValidate(acceptedFiles);
|
|
21
18
|
} }, children));
|
|
22
19
|
}
|
|
23
20
|
else {
|
|
24
21
|
return children;
|
|
25
22
|
}
|
|
26
23
|
};
|
|
27
|
-
const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) => {
|
|
24
|
+
const Form = ({ setInput, input, handleSubmit, allowAttachments, onValidate, isLoading, error, }) => {
|
|
28
25
|
const icons = useIcons('aiConversation');
|
|
29
26
|
const sendIcon = icons?.send ?? React.createElement(IconSend, null);
|
|
30
27
|
const attachIcon = icons?.attach ?? React.createElement(IconAttach, null);
|
|
31
28
|
const hiddenInput = React.useRef(null);
|
|
32
29
|
const [composing, setComposing] = React.useState(false);
|
|
33
30
|
const isInputEmpty = !input?.text?.length && !input?.files?.length;
|
|
34
|
-
return (React.createElement(FormWrapper, {
|
|
31
|
+
return (React.createElement(FormWrapper, { onValidate: onValidate, allowAttachments: allowAttachments },
|
|
35
32
|
React.createElement(View, { as: "form", className: ComponentClassName.AIConversationForm, onSubmit: handleSubmit },
|
|
36
33
|
allowAttachments ? (React.createElement(Button, { className: ComponentClassName.AIConversationFormAttach, onClick: () => {
|
|
37
34
|
hiddenInput?.current?.click();
|
|
@@ -42,15 +39,11 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) =
|
|
|
42
39
|
React.createElement("span", null, attachIcon),
|
|
43
40
|
React.createElement(VisuallyHidden, null,
|
|
44
41
|
React.createElement("input", { type: "file", tabIndex: -1, ref: hiddenInput, onChange: (e) => {
|
|
45
|
-
|
|
46
|
-
if (!files || files.length === 0) {
|
|
42
|
+
if (!e.target.files || e.target.files.length === 0) {
|
|
47
43
|
return;
|
|
48
44
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
files: [...(prevValue?.files ?? []), ...Array.from(files)],
|
|
52
|
-
}));
|
|
53
|
-
}, multiple: true, accept: "*", "data-testid": "hidden-file-input" })))) : null,
|
|
45
|
+
onValidate(Array.from(e.target.files));
|
|
46
|
+
}, multiple: true, accept: ".jpeg,.png,.webp,.gif", "data-testid": "hidden-file-input" })))) : null,
|
|
54
47
|
React.createElement(TextAreaField, { className: ComponentClassName.AIConversationFormField, label: "input", labelHidden: true, autoResize: true, flex: "1", rows: 1, value: input?.text ?? '', testId: "text-input", onCompositionStart: () => setComposing(true), onCompositionEnd: () => setComposing(false), onKeyDown: (e) => {
|
|
55
48
|
// Submit on enter key if shift is not pressed also
|
|
56
49
|
const shouldSubmit = !e.shiftKey && e.key === 'Enter' && !composing;
|
|
@@ -59,7 +52,7 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) =
|
|
|
59
52
|
e.preventDefault();
|
|
60
53
|
}
|
|
61
54
|
}, onChange: (e) => {
|
|
62
|
-
setInput((prevValue) => ({
|
|
55
|
+
setInput?.((prevValue) => ({
|
|
63
56
|
...prevValue,
|
|
64
57
|
text: e.target.value,
|
|
65
58
|
}));
|
|
@@ -69,6 +62,7 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) =
|
|
|
69
62
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
70
63
|
isDisabled: isLoading || isInputEmpty },
|
|
71
64
|
React.createElement("span", null, sendIcon))),
|
|
65
|
+
error ? (React.createElement(Message, { className: ComponentClassName.AIConversationFormError, variation: "plain", colorTheme: "warning" }, error)) : null,
|
|
72
66
|
React.createElement(Attachments, { setInput: setInput, files: input?.files })));
|
|
73
67
|
};
|
|
74
68
|
|
package/dist/esm/version.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -47,7 +47,8 @@ const AvatarsProvider = ({ children, avatars, }) => {
|
|
|
47
47
|
const ConversationInputContext = React__namespace["default"].createContext({});
|
|
48
48
|
const ConversationInputContextProvider = ({ children, }) => {
|
|
49
49
|
const [input, setInput] = React__namespace["default"].useState();
|
|
50
|
-
const
|
|
50
|
+
const [error, setError] = React__namespace["default"].useState();
|
|
51
|
+
const providerValue = React__namespace["default"].useMemo(() => ({ input, setInput, error, setError }), [input, setInput, error, setError]);
|
|
51
52
|
return (React__namespace["default"].createElement(ConversationInputContext.Provider, { value: providerValue }, children));
|
|
52
53
|
};
|
|
53
54
|
|
|
@@ -82,32 +83,67 @@ function formatDate(date) {
|
|
|
82
83
|
return `${dateString} at ${timeString}`;
|
|
83
84
|
}
|
|
84
85
|
function arrayBufferToBase64(buffer) {
|
|
85
|
-
let binary = '';
|
|
86
|
-
const bytes = new Uint8Array(buffer);
|
|
87
|
-
const len = bytes.byteLength;
|
|
88
|
-
for (let i = 0; i < len; i++) {
|
|
89
|
-
binary += String.fromCharCode(bytes[i]);
|
|
90
|
-
}
|
|
91
|
-
return window.btoa(binary);
|
|
92
|
-
}
|
|
93
|
-
function convertBufferToBase64(buffer, format) {
|
|
94
|
-
let base64string = '';
|
|
95
86
|
// Use node-based buffer if available
|
|
96
87
|
// fall back on browser if not
|
|
97
88
|
if (typeof Buffer !== 'undefined') {
|
|
98
|
-
|
|
89
|
+
return Buffer.from(new Uint8Array(buffer)).toString('base64');
|
|
99
90
|
}
|
|
100
91
|
else {
|
|
101
|
-
|
|
92
|
+
let binary = '';
|
|
93
|
+
const bytes = new Uint8Array(buffer);
|
|
94
|
+
const len = bytes.byteLength;
|
|
95
|
+
for (let i = 0; i < len; i++) {
|
|
96
|
+
binary += String.fromCharCode(bytes[i]);
|
|
97
|
+
}
|
|
98
|
+
return window.btoa(binary);
|
|
102
99
|
}
|
|
100
|
+
}
|
|
101
|
+
function convertBufferToBase64(buffer, format) {
|
|
102
|
+
const base64string = arrayBufferToBase64(buffer);
|
|
103
103
|
return `data:image/${format};base64,${base64string}`;
|
|
104
104
|
}
|
|
105
105
|
function getImageTypeFromMimeType(mimeType) {
|
|
106
106
|
return mimeType.split('/')[1];
|
|
107
107
|
}
|
|
108
|
+
async function attachmentsValidator({ files, maxAttachments, maxAttachmentSize, }) {
|
|
109
|
+
const acceptedFiles = [];
|
|
110
|
+
const rejectedFiles = [];
|
|
111
|
+
let hasMaxSizeError = false;
|
|
112
|
+
for (const file of files) {
|
|
113
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
114
|
+
const base64 = arrayBufferToBase64(arrayBuffer);
|
|
115
|
+
if (base64.length < maxAttachmentSize) {
|
|
116
|
+
acceptedFiles.push(file);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
rejectedFiles.push(file);
|
|
120
|
+
hasMaxSizeError = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (acceptedFiles.length > maxAttachments) {
|
|
124
|
+
return {
|
|
125
|
+
acceptedFiles: acceptedFiles.slice(0, maxAttachments),
|
|
126
|
+
rejectedFiles: [...acceptedFiles.slice(maxAttachments), ...rejectedFiles],
|
|
127
|
+
hasMaxAttachmentsError: true,
|
|
128
|
+
hasMaxAttachmentSizeError: hasMaxSizeError,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
acceptedFiles,
|
|
133
|
+
rejectedFiles,
|
|
134
|
+
hasMaxAttachmentsError: false,
|
|
135
|
+
hasMaxAttachmentSizeError: hasMaxSizeError,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
108
138
|
|
|
109
139
|
const defaultAIConversationDisplayTextEn = {
|
|
110
140
|
getMessageTimestampText: (date) => formatDate(date),
|
|
141
|
+
getMaxAttachmentErrorText(count) {
|
|
142
|
+
return `Cannot choose more than ${count} ${count === 1 ? 'file' : 'files'}. `;
|
|
143
|
+
},
|
|
144
|
+
getAttachmentSizeErrorText(sizeText) {
|
|
145
|
+
return `File size must be below ${sizeText}.`;
|
|
146
|
+
},
|
|
111
147
|
};
|
|
112
148
|
|
|
113
149
|
const { ConversationDisplayTextContext, ConversationDisplayTextProvider, useConversationDisplayText, } = uiReactCore.createContextUtilities({
|
|
@@ -180,9 +216,18 @@ const { MessageRendererContext, MessageRendererProvider, useMessageRenderer, } =
|
|
|
180
216
|
errorMessage: '`useMessageRenderer` must be used with an AIConversation component',
|
|
181
217
|
});
|
|
182
218
|
|
|
183
|
-
const AttachmentContext = React__namespace.createContext(
|
|
184
|
-
|
|
185
|
-
|
|
219
|
+
const AttachmentContext = React__namespace.createContext({
|
|
220
|
+
allowAttachments: false,
|
|
221
|
+
// We save attachments as base64 strings into dynamodb for conversation history
|
|
222
|
+
// DynamoDB has a max size of 400kb for records
|
|
223
|
+
// This can be overridden so cutsomers could provide a lower number
|
|
224
|
+
// or a higher number if in the future we support larger sizes.
|
|
225
|
+
maxAttachmentSize: 400000,
|
|
226
|
+
maxAttachments: 20,
|
|
227
|
+
});
|
|
228
|
+
const AttachmentProvider = ({ children, allowAttachments = false, maxAttachmentSize = 400000, maxAttachments = 20, }) => {
|
|
229
|
+
const providerValue = React__namespace.useMemo(() => ({ maxAttachmentSize, maxAttachments, allowAttachments }), [maxAttachmentSize, maxAttachments, allowAttachments]);
|
|
230
|
+
return (React__namespace.createElement(AttachmentContext.Provider, { value: providerValue }, children));
|
|
186
231
|
};
|
|
187
232
|
|
|
188
233
|
const WelcomeMessageContext = React__namespace.createContext(undefined);
|
|
@@ -211,7 +256,7 @@ const DEFAULT_ICON_ATTRIBUTES = {
|
|
|
211
256
|
fill: 'none',
|
|
212
257
|
xmlns: 'http://www.w3.org/2000/svg',
|
|
213
258
|
};
|
|
214
|
-
const BaseIconElement = elements.
|
|
259
|
+
const BaseIconElement = elements.defineBaseElementWithRef({
|
|
215
260
|
type: 'svg',
|
|
216
261
|
displayName: 'Icon',
|
|
217
262
|
});
|
|
@@ -227,44 +272,44 @@ const getIconProps = ({ variant, ...props }) => {
|
|
|
227
272
|
};
|
|
228
273
|
const IconElement = elements.withBaseElementProps(BaseIconElement, getIconProps);
|
|
229
274
|
|
|
230
|
-
const LabelElement$1 = elements.
|
|
275
|
+
const LabelElement$1 = elements.defineBaseElementWithRef({
|
|
231
276
|
type: 'label',
|
|
232
277
|
displayName: 'Label',
|
|
233
278
|
});
|
|
234
|
-
const TextElement = elements.
|
|
279
|
+
const TextElement = elements.defineBaseElementWithRef({
|
|
235
280
|
type: 'p',
|
|
236
281
|
displayName: 'Text',
|
|
237
282
|
});
|
|
238
|
-
const UnorderedListElement = elements.
|
|
283
|
+
const UnorderedListElement = elements.defineBaseElementWithRef({
|
|
239
284
|
type: 'ul',
|
|
240
285
|
displayName: 'UnorderedList',
|
|
241
286
|
});
|
|
242
|
-
const ListItemElement = elements.
|
|
287
|
+
const ListItemElement = elements.defineBaseElementWithRef({
|
|
243
288
|
type: 'li',
|
|
244
289
|
displayName: 'ListItem',
|
|
245
290
|
});
|
|
246
|
-
const HeadingElement = elements.
|
|
291
|
+
const HeadingElement = elements.defineBaseElementWithRef({
|
|
247
292
|
type: 'h2',
|
|
248
293
|
displayName: 'Title',
|
|
249
294
|
});
|
|
250
|
-
const ImageElement = elements.
|
|
295
|
+
const ImageElement = elements.defineBaseElementWithRef({
|
|
251
296
|
type: 'img',
|
|
252
297
|
displayName: 'Image',
|
|
253
298
|
});
|
|
254
|
-
const InputElement = elements.
|
|
299
|
+
const InputElement = elements.defineBaseElementWithRef({
|
|
255
300
|
type: 'input',
|
|
256
301
|
displayName: 'Input',
|
|
257
302
|
});
|
|
258
|
-
const ButtonElement = elements.
|
|
259
|
-
const ViewElement = elements.
|
|
303
|
+
const ButtonElement = elements.defineBaseElementWithRef({ type: 'button', displayName: 'Button' });
|
|
304
|
+
const ViewElement = elements.defineBaseElementWithRef({
|
|
260
305
|
type: 'div',
|
|
261
306
|
displayName: 'View',
|
|
262
307
|
});
|
|
263
|
-
const SpanElement = elements.
|
|
308
|
+
const SpanElement = elements.defineBaseElementWithRef({
|
|
264
309
|
type: 'span',
|
|
265
310
|
displayName: 'Span',
|
|
266
311
|
});
|
|
267
|
-
const TextAreaElement = elements.
|
|
312
|
+
const TextAreaElement = elements.defineBaseElementWithRef({
|
|
268
313
|
type: 'textarea',
|
|
269
314
|
displayName: 'TextArea',
|
|
270
315
|
});
|
|
@@ -561,9 +606,10 @@ const InputContainer = elements.withBaseElementProps(View$2, {
|
|
|
561
606
|
className: `${FIELD_BLOCK}__input-container`,
|
|
562
607
|
});
|
|
563
608
|
const FormControl = () => {
|
|
564
|
-
const { input, setInput } = React__namespace["default"].useContext(ConversationInputContext);
|
|
609
|
+
const { input, setInput, error, setError } = React__namespace["default"].useContext(ConversationInputContext);
|
|
565
610
|
const handleSendMessage = React__namespace["default"].useContext(SendMessageContext);
|
|
566
|
-
const allowAttachments = React__namespace["default"].useContext(AttachmentContext);
|
|
611
|
+
const { allowAttachments, maxAttachmentSize, maxAttachments } = React__namespace["default"].useContext(AttachmentContext);
|
|
612
|
+
const displayText = useConversationDisplayText();
|
|
567
613
|
const responseComponents = React__namespace["default"].useContext(ResponseComponentsContext);
|
|
568
614
|
const isLoading = React__namespace["default"].useContext(LoadingContext);
|
|
569
615
|
const aiContext = React__namespace["default"].useContext(AIContextContext);
|
|
@@ -615,8 +661,36 @@ const FormControl = () => {
|
|
|
615
661
|
}
|
|
616
662
|
}
|
|
617
663
|
};
|
|
664
|
+
const onValidate = React__namespace["default"].useCallback(async (files) => {
|
|
665
|
+
const previousFiles = input?.files ?? [];
|
|
666
|
+
const { acceptedFiles, hasMaxAttachmentsError, hasMaxAttachmentSizeError, } = await attachmentsValidator({
|
|
667
|
+
files: [...files, ...previousFiles],
|
|
668
|
+
maxAttachments,
|
|
669
|
+
maxAttachmentSize,
|
|
670
|
+
});
|
|
671
|
+
if (hasMaxAttachmentsError || hasMaxAttachmentSizeError) {
|
|
672
|
+
const errors = [];
|
|
673
|
+
if (hasMaxAttachmentsError) {
|
|
674
|
+
errors.push(displayText.getMaxAttachmentErrorText(maxAttachments));
|
|
675
|
+
}
|
|
676
|
+
if (hasMaxAttachmentSizeError) {
|
|
677
|
+
errors.push(displayText.getAttachmentSizeErrorText(
|
|
678
|
+
// base64 size is about 137% that of the file size
|
|
679
|
+
// https://en.wikipedia.org/wiki/Base64#MIME
|
|
680
|
+
ui.humanFileSize((maxAttachmentSize - 814) / 1.37, true)));
|
|
681
|
+
}
|
|
682
|
+
setError?.(errors.join(' '));
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
setError?.(undefined);
|
|
686
|
+
}
|
|
687
|
+
setInput?.((prevValue) => ({
|
|
688
|
+
...prevValue,
|
|
689
|
+
files: acceptedFiles,
|
|
690
|
+
}));
|
|
691
|
+
}, [setInput, input, displayText, maxAttachmentSize, maxAttachments, setError]);
|
|
618
692
|
if (controls?.Form) {
|
|
619
|
-
return (React__namespace["default"].createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, allowAttachments: allowAttachments, isLoading: isLoading }));
|
|
693
|
+
return (React__namespace["default"].createElement(controls.Form, { handleSubmit: handleSubmit, input: input, setInput: setInput, onValidate: onValidate, allowAttachments: allowAttachments, isLoading: isLoading, error: error, setError: setError }));
|
|
620
694
|
}
|
|
621
695
|
return (React__namespace["default"].createElement("form", { className: `${FIELD_BLOCK}__form`, onSubmit: handleSubmit, method: "post", ref: ref },
|
|
622
696
|
allowAttachments ? React__namespace["default"].createElement(AttachFileControl, null) : null,
|
|
@@ -795,7 +869,7 @@ PromptControl.Container = Container;
|
|
|
795
869
|
PromptControl.PromptGroup = PromptGroup;
|
|
796
870
|
PromptControl.PromptCard = PromptCard;
|
|
797
871
|
|
|
798
|
-
const AIConversationProvider = ({ aiContext, actions, allowAttachments, avatars, children, controls, displayText, handleSendMessage, isLoading, messages, messageRenderer, responseComponents, suggestedPrompts, variant, welcomeMessage, FallbackResponseComponent, }) => {
|
|
872
|
+
const AIConversationProvider = ({ aiContext, actions, allowAttachments, avatars, children, controls, displayText, handleSendMessage, isLoading, maxAttachmentSize, maxAttachments, messages, messageRenderer, responseComponents, suggestedPrompts, variant, welcomeMessage, FallbackResponseComponent, }) => {
|
|
799
873
|
const _displayText = {
|
|
800
874
|
...defaultAIConversationDisplayTextEn,
|
|
801
875
|
...displayText,
|
|
@@ -806,7 +880,7 @@ const AIConversationProvider = ({ aiContext, actions, allowAttachments, avatars,
|
|
|
806
880
|
React__namespace["default"].createElement(FallbackComponentProvider, { FallbackComponent: FallbackResponseComponent },
|
|
807
881
|
React__namespace["default"].createElement(MessageRendererProvider, { ...messageRenderer },
|
|
808
882
|
React__namespace["default"].createElement(ResponseComponentsProvider, { responseComponents: responseComponents },
|
|
809
|
-
React__namespace["default"].createElement(AttachmentProvider, { allowAttachments: allowAttachments },
|
|
883
|
+
React__namespace["default"].createElement(AttachmentProvider, { allowAttachments: allowAttachments, maxAttachmentSize: maxAttachmentSize, maxAttachments: maxAttachments },
|
|
810
884
|
React__namespace["default"].createElement(ConversationDisplayTextProvider, { ..._displayText },
|
|
811
885
|
React__namespace["default"].createElement(ConversationInputContextProvider, null,
|
|
812
886
|
React__namespace["default"].createElement(SendMessageContextProvider, { handleSendMessage: handleSendMessage },
|
|
@@ -950,27 +1024,24 @@ function isHTMLFormElement(target) {
|
|
|
950
1024
|
* Will conditionally render the DropZone if allowAttachments
|
|
951
1025
|
* is true
|
|
952
1026
|
*/
|
|
953
|
-
const FormWrapper = ({ children, allowAttachments,
|
|
1027
|
+
const FormWrapper = ({ children, allowAttachments, onValidate, }) => {
|
|
954
1028
|
if (allowAttachments) {
|
|
955
1029
|
return (React__namespace.createElement(uiReact.DropZone, { className: ui.ComponentClassName.AIConversationFormDropzone, onDropComplete: ({ acceptedFiles }) => {
|
|
956
|
-
|
|
957
|
-
...prevInput,
|
|
958
|
-
files: [...(prevInput?.files ?? []), ...acceptedFiles],
|
|
959
|
-
}));
|
|
1030
|
+
onValidate(acceptedFiles);
|
|
960
1031
|
} }, children));
|
|
961
1032
|
}
|
|
962
1033
|
else {
|
|
963
1034
|
return children;
|
|
964
1035
|
}
|
|
965
1036
|
};
|
|
966
|
-
const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) => {
|
|
1037
|
+
const Form = ({ setInput, input, handleSubmit, allowAttachments, onValidate, isLoading, error, }) => {
|
|
967
1038
|
const icons = internal.useIcons('aiConversation');
|
|
968
1039
|
const sendIcon = icons?.send ?? React__namespace.createElement(internal.IconSend, null);
|
|
969
1040
|
const attachIcon = icons?.attach ?? React__namespace.createElement(internal.IconAttach, null);
|
|
970
1041
|
const hiddenInput = React__namespace.useRef(null);
|
|
971
1042
|
const [composing, setComposing] = React__namespace.useState(false);
|
|
972
1043
|
const isInputEmpty = !input?.text?.length && !input?.files?.length;
|
|
973
|
-
return (React__namespace.createElement(FormWrapper, {
|
|
1044
|
+
return (React__namespace.createElement(FormWrapper, { onValidate: onValidate, allowAttachments: allowAttachments },
|
|
974
1045
|
React__namespace.createElement(uiReact.View, { as: "form", className: ui.ComponentClassName.AIConversationForm, onSubmit: handleSubmit },
|
|
975
1046
|
allowAttachments ? (React__namespace.createElement(uiReact.Button, { className: ui.ComponentClassName.AIConversationFormAttach, onClick: () => {
|
|
976
1047
|
hiddenInput?.current?.click();
|
|
@@ -981,15 +1052,11 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) =
|
|
|
981
1052
|
React__namespace.createElement("span", null, attachIcon),
|
|
982
1053
|
React__namespace.createElement(uiReact.VisuallyHidden, null,
|
|
983
1054
|
React__namespace.createElement("input", { type: "file", tabIndex: -1, ref: hiddenInput, onChange: (e) => {
|
|
984
|
-
|
|
985
|
-
if (!files || files.length === 0) {
|
|
1055
|
+
if (!e.target.files || e.target.files.length === 0) {
|
|
986
1056
|
return;
|
|
987
1057
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
files: [...(prevValue?.files ?? []), ...Array.from(files)],
|
|
991
|
-
}));
|
|
992
|
-
}, multiple: true, accept: "*", "data-testid": "hidden-file-input" })))) : null,
|
|
1058
|
+
onValidate(Array.from(e.target.files));
|
|
1059
|
+
}, multiple: true, accept: ".jpeg,.png,.webp,.gif", "data-testid": "hidden-file-input" })))) : null,
|
|
993
1060
|
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) => {
|
|
994
1061
|
// Submit on enter key if shift is not pressed also
|
|
995
1062
|
const shouldSubmit = !e.shiftKey && e.key === 'Enter' && !composing;
|
|
@@ -998,7 +1065,7 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) =
|
|
|
998
1065
|
e.preventDefault();
|
|
999
1066
|
}
|
|
1000
1067
|
}, onChange: (e) => {
|
|
1001
|
-
setInput((prevValue) => ({
|
|
1068
|
+
setInput?.((prevValue) => ({
|
|
1002
1069
|
...prevValue,
|
|
1003
1070
|
text: e.target.value,
|
|
1004
1071
|
}));
|
|
@@ -1008,6 +1075,7 @@ const Form = ({ setInput, input, handleSubmit, allowAttachments, isLoading, }) =
|
|
|
1008
1075
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
1009
1076
|
isDisabled: isLoading || isInputEmpty },
|
|
1010
1077
|
React__namespace.createElement("span", null, sendIcon))),
|
|
1078
|
+
error ? (React__namespace.createElement(uiReact.Message, { className: ui.ComponentClassName.AIConversationFormError, variation: "plain", colorTheme: "warning" }, error)) : null,
|
|
1011
1079
|
React__namespace.createElement(Attachments, { setInput: setInput, files: input?.files })));
|
|
1012
1080
|
};
|
|
1013
1081
|
|
|
@@ -1022,7 +1090,7 @@ const PromptList = ({ setInput, suggestedPrompts = [], }) => {
|
|
|
1022
1090
|
})));
|
|
1023
1091
|
};
|
|
1024
1092
|
|
|
1025
|
-
const VERSION = '1.
|
|
1093
|
+
const VERSION = '1.1.0';
|
|
1026
1094
|
|
|
1027
1095
|
function AIConversationBase({ avatars, controls, ...rest }) {
|
|
1028
1096
|
uiReactCore.useSetUserAgent({
|
|
@@ -3,4 +3,4 @@ import { AIConversationInput, AIConversationProps } from './types';
|
|
|
3
3
|
export interface AIConversationProviderProps extends AIConversationInput, AIConversationProps {
|
|
4
4
|
children?: React.ReactNode;
|
|
5
5
|
}
|
|
6
|
-
export declare const AIConversationProvider: ({ aiContext, actions, allowAttachments, avatars, children, controls, displayText, handleSendMessage, isLoading, messages, messageRenderer, responseComponents, suggestedPrompts, variant, welcomeMessage, FallbackResponseComponent, }: AIConversationProviderProps) => React.JSX.Element;
|
|
6
|
+
export declare const AIConversationProvider: ({ aiContext, actions, allowAttachments, avatars, children, controls, displayText, handleSendMessage, isLoading, maxAttachmentSize, maxAttachments, messages, messageRenderer, responseComponents, suggestedPrompts, variant, welcomeMessage, FallbackResponseComponent, }: AIConversationProviderProps) => React.JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}) => JSX.Element;
|
|
2
|
+
import { AIConversationInput } from '../types';
|
|
3
|
+
export interface AttachmentContextProps extends Pick<AIConversationInput, 'allowAttachments' | 'maxAttachments' | 'maxAttachmentSize'> {
|
|
4
|
+
}
|
|
5
|
+
export declare const AttachmentContext: React.Context<Required<AttachmentContextProps>>;
|
|
6
|
+
export declare const AttachmentProvider: ({ children, allowAttachments, maxAttachmentSize, maxAttachments, }: React.PropsWithChildren<AttachmentContextProps>) => JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ConversationInputContextProps } from './ConversationInputContext';
|
|
3
3
|
import { SuggestedPrompt } from '../types';
|
|
4
4
|
import { ConversationMessage } from '../../../types';
|
|
5
5
|
export interface ControlsContextProps {
|
|
@@ -7,13 +7,14 @@ export interface ControlsContextProps {
|
|
|
7
7
|
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
|
8
8
|
allowAttachments?: boolean;
|
|
9
9
|
isLoading?: boolean;
|
|
10
|
-
|
|
10
|
+
onValidate: (files: File[]) => Promise<void>;
|
|
11
|
+
} & ConversationInputContextProps>;
|
|
11
12
|
MessageList?: React.ComponentType<{
|
|
12
13
|
messages: ConversationMessage[];
|
|
13
14
|
}>;
|
|
14
15
|
PromptList?: React.ComponentType<{
|
|
15
16
|
suggestedPrompts?: SuggestedPrompt[];
|
|
16
|
-
setInput:
|
|
17
|
+
setInput: ConversationInputContextProps['setInput'];
|
|
17
18
|
}>;
|
|
18
19
|
}
|
|
19
20
|
export declare const ControlsContext: React.Context<ControlsContextProps | undefined>;
|
|
@@ -3,11 +3,13 @@ export interface ConversationInput {
|
|
|
3
3
|
text?: string;
|
|
4
4
|
files?: File[];
|
|
5
5
|
}
|
|
6
|
-
export interface
|
|
6
|
+
export interface ConversationInputContextProps {
|
|
7
7
|
input?: ConversationInput;
|
|
8
8
|
setInput?: React.Dispatch<React.SetStateAction<ConversationInput | undefined>>;
|
|
9
|
+
error?: string;
|
|
10
|
+
setError?: React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
9
11
|
}
|
|
10
|
-
export declare const ConversationInputContext: React.Context<
|
|
12
|
+
export declare const ConversationInputContext: React.Context<ConversationInputContextProps>;
|
|
11
13
|
export declare const ConversationInputContextProvider: ({ children, }: {
|
|
12
14
|
children?: React.ReactNode;
|
|
13
15
|
}) => JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { ConversationDisplayText } from '../displayText';
|
|
3
|
-
export declare const ConversationDisplayTextContext: import("react").Context<Required<ConversationDisplayText
|
|
3
|
+
export declare const ConversationDisplayTextContext: import("react").Context<Required<ConversationDisplayText>>, ConversationDisplayTextProvider: import("react").ComponentType<import("react").PropsWithChildren<Required<ConversationDisplayText>>>, useConversationDisplayText: (params?: {
|
|
4
4
|
errorMessage?: string | undefined;
|
|
5
5
|
} | undefined) => Required<ConversationDisplayText>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { MessageRenderer } from '../types';
|
|
3
|
-
export declare const MessageRendererContext: import("react").Context<MessageRenderer
|
|
3
|
+
export declare const MessageRendererContext: import("react").Context<MessageRenderer>, MessageRendererProvider: import("react").ComponentType<import("react").PropsWithChildren<MessageRenderer>>, useMessageRenderer: (params?: {
|
|
4
4
|
errorMessage?: string | undefined;
|
|
5
5
|
} | undefined) => MessageRenderer;
|
|
@@ -2,5 +2,5 @@ import React from 'react';
|
|
|
2
2
|
export type IconElementProps = React.ComponentProps<typeof BaseIconElement>;
|
|
3
3
|
export type IconVariant = 'attach' | 'close' | 'image' | 'send-message' | 'user-avatar';
|
|
4
4
|
export declare const DEFAULT_ICON_PATHS: Record<IconVariant, string>;
|
|
5
|
-
export declare const BaseIconElement: import("@aws-amplify/ui-react-core/
|
|
6
|
-
export declare const IconElement: import("@aws-amplify/ui-react-core/
|
|
5
|
+
export declare const BaseIconElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<never, IconVariant, React.SVGProps<SVGSVGElement>>, SVGSVGElement>;
|
|
6
|
+
export declare const IconElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<Omit<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<never, IconVariant, React.SVGProps<SVGSVGElement>>, "ref"> & React.RefAttributes<SVGSVGElement>, SVGSVGElement>;
|
|
@@ -14,22 +14,22 @@ export interface AIConversationElements {
|
|
|
14
14
|
UnorderedList: typeof UnorderedListElement;
|
|
15
15
|
View: typeof ViewElement;
|
|
16
16
|
}
|
|
17
|
-
export declare const LabelElement: import("@aws-amplify/ui-react-core/
|
|
18
|
-
export declare const TextElement: import("@aws-amplify/ui-react-core/
|
|
19
|
-
export declare const UnorderedListElement: import("@aws-amplify/ui-react-core/
|
|
20
|
-
export declare const ListItemElement: import("@aws-amplify/ui-react-core/
|
|
21
|
-
export declare const HeadingElement: import("@aws-amplify/ui-react-core/
|
|
17
|
+
export declare const LabelElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<"htmlFor", string, import("react").DetailedHTMLProps<import("react").LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>>, HTMLLabelElement>;
|
|
18
|
+
export declare const TextElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<never, string, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>>, HTMLParagraphElement>;
|
|
19
|
+
export declare const UnorderedListElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<never, string, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLUListElement>, HTMLUListElement>>, HTMLUListElement>;
|
|
20
|
+
export declare const ListItemElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<never, string, import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>>, HTMLLIElement>;
|
|
21
|
+
export declare const HeadingElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<never, string, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>, HTMLHeadingElement>;
|
|
22
22
|
export type IconElementProps = React.ComponentProps<typeof IconElement>;
|
|
23
23
|
type ImageElementProps = 'src' | 'alt';
|
|
24
|
-
export declare const ImageElement: import("@aws-amplify/ui-react-core/
|
|
25
|
-
export declare const InputElement: import("@aws-amplify/ui-react-core/
|
|
24
|
+
export declare const ImageElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<ImageElementProps, string, import("react").DetailedHTMLProps<import("react").ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>>, HTMLImageElement>;
|
|
25
|
+
export declare const InputElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<"type", string, import("react").DetailedHTMLProps<import("react").InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>>, HTMLInputElement>;
|
|
26
26
|
type ButtonElementProps = 'disabled' | 'onClick' | 'type' | 'tabIndex';
|
|
27
27
|
type ButtonElementVariant = 'attach' | 'remove' | 'send-message';
|
|
28
|
-
export declare const ButtonElement: import("@aws-amplify/ui-react-core/
|
|
28
|
+
export declare const ButtonElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<ButtonElementProps, ButtonElementVariant, import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>>, HTMLButtonElement>;
|
|
29
29
|
type ViewElementProps = 'onFocus' | 'tabIndex' | 'onKeyDown';
|
|
30
|
-
export declare const ViewElement: import("@aws-amplify/ui-react-core/
|
|
31
|
-
export declare const SpanElement: import("@aws-amplify/ui-react-core/
|
|
30
|
+
export declare const ViewElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<ViewElementProps, string, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>>, HTMLDivElement>;
|
|
31
|
+
export declare const SpanElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<never, string, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>>, HTMLSpanElement>;
|
|
32
32
|
type TextAreaElementProps = 'id' | 'name' | 'onChange' | 'placeholder' | 'autoFocus' | 'onCompositionStart' | 'onCompositionEnd' | 'onKeyDown';
|
|
33
|
-
export declare const TextAreaElement: import("@aws-amplify/ui-react-core/
|
|
33
|
+
export declare const TextAreaElement: import("@aws-amplify/ui-react-core/elements").BaseElementWithRef<import("@aws-amplify/ui-react-core/elements").BaseElementWithRefProps<TextAreaElementProps, string, import("react").DetailedHTMLProps<import("react").TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>>, HTMLTextAreaElement>;
|
|
34
34
|
export declare const AIConversationElements: AIConversationElements;
|
|
35
35
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { AIContextContext, AIContextProvider } from './AIContextContext';
|
|
2
2
|
export { ActionsContext, ActionsProvider } from './ActionsContext';
|
|
3
3
|
export { AvatarsContext, AvatarsProvider } from './AvatarsContext';
|
|
4
|
-
export { ConversationInputContext, ConversationInput, ConversationInputContextProvider, } from './ConversationInputContext';
|
|
4
|
+
export { ConversationInputContextProps, ConversationInputContext, ConversationInput, ConversationInputContextProvider, } from './ConversationInputContext';
|
|
5
5
|
export { MessagesContext, RoleContext, MessagesProvider, } from './MessagesContext';
|
|
6
6
|
export { SuggestedPromptsContext, SuggestedPromptProvider, } from './SuggestedPromptsContext';
|
|
7
7
|
export { MessageVariantContext, MessageVariantProvider, } from './MessageVariantContext';
|
|
@@ -11,7 +11,7 @@ export { LoadingContextProvider } from './LoadingContext';
|
|
|
11
11
|
export { ResponseComponentsProvider, RESPONSE_COMPONENT_PREFIX, } from './ResponseComponentsContext';
|
|
12
12
|
export { SendMessageContextProvider } from './SendMessageContext';
|
|
13
13
|
export { MessageRendererProvider, MessageRendererContext, useMessageRenderer, } from './MessageRenderContext';
|
|
14
|
-
export { AttachmentProvider, AttachmentContext } from './AttachmentContext';
|
|
14
|
+
export { AttachmentProvider, AttachmentContext, AttachmentContextProps, } from './AttachmentContext';
|
|
15
15
|
export { WelcomeMessageContext, WelcomeMessageProvider, } from './WelcomeMessageContext';
|
|
16
16
|
export { FallbackComponentContext, FallbackComponentProvider, } from './FallbackComponentContext';
|
|
17
17
|
export * from './elements';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DisplayTextTemplate } from '@aws-amplify/ui';
|
|
2
2
|
export type ConversationDisplayText = {
|
|
3
3
|
getMessageTimestampText?: (date: Date) => string;
|
|
4
|
+
getMaxAttachmentErrorText?: (count: number) => string;
|
|
5
|
+
getAttachmentSizeErrorText?: (sizeText: string) => string;
|
|
4
6
|
};
|
|
5
7
|
export declare const defaultAIConversationDisplayTextEn: Required<AIConversationDisplayText>;
|
|
6
8
|
export type AIConversationDisplayText = DisplayTextTemplate<ConversationDisplayText>;
|
|
@@ -22,6 +22,8 @@ export interface AIConversationInput {
|
|
|
22
22
|
variant?: MessageVariant;
|
|
23
23
|
controls?: ControlsContextProps;
|
|
24
24
|
allowAttachments?: boolean;
|
|
25
|
+
maxAttachments?: number;
|
|
26
|
+
maxAttachmentSize?: number;
|
|
25
27
|
messageRenderer?: MessageRenderer;
|
|
26
28
|
}
|
|
27
29
|
export interface AIConversationProps {
|
|
@@ -2,3 +2,13 @@ import { ImageContentBlock } from '../../types';
|
|
|
2
2
|
export declare function formatDate(date: Date): string;
|
|
3
3
|
export declare function convertBufferToBase64(buffer: ArrayBuffer, format: ImageContentBlock['format']): string;
|
|
4
4
|
export declare function getImageTypeFromMimeType(mimeType: string): 'png' | 'jpeg' | 'gif' | 'webp';
|
|
5
|
+
export declare function attachmentsValidator({ files, maxAttachments, maxAttachmentSize, }: {
|
|
6
|
+
files: File[];
|
|
7
|
+
maxAttachments: number;
|
|
8
|
+
maxAttachmentSize: number;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
acceptedFiles: File[];
|
|
11
|
+
rejectedFiles: File[];
|
|
12
|
+
hasMaxAttachmentSizeError: boolean;
|
|
13
|
+
hasMaxAttachmentsError: boolean;
|
|
14
|
+
}>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
import {
|
|
2
|
+
import { ConversationInputContextProps } from '../../context';
|
|
3
3
|
export declare const Attachments: ({ files, setInput, }: {
|
|
4
4
|
files?: File[] | undefined;
|
|
5
|
-
setInput:
|
|
5
|
+
setInput: ConversationInputContextProps['setInput'];
|
|
6
6
|
}) => JSX.Element | null;
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
1
|
+
export declare const VERSION = "1.1.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-amplify/ui-react-ai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/esm/index.mjs",
|
|
6
6
|
"exports": {
|
|
@@ -42,15 +42,15 @@
|
|
|
42
42
|
"typecheck": "tsc --noEmit"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@aws-amplify/api-graphql": "
|
|
46
|
-
"aws-amplify": "^6.
|
|
45
|
+
"@aws-amplify/api-graphql": "unstable",
|
|
46
|
+
"aws-amplify": "^6.9.0",
|
|
47
47
|
"react": "^16.14.0 || ^17.0 || ^18.0",
|
|
48
48
|
"react-dom": "^16.14.0 || ^17.0 || ^18.0"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@aws-amplify/ui": "^6.
|
|
52
|
-
"@aws-amplify/ui-react": "^6.
|
|
53
|
-
"@aws-amplify/ui-react-core": "^3.
|
|
51
|
+
"@aws-amplify/ui": "^6.7.1",
|
|
52
|
+
"@aws-amplify/ui-react": "^6.7.1",
|
|
53
|
+
"@aws-amplify/ui-react-core": "^3.1.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/jest-when": "^3.5.0",
|