diff --git a/packages/aws-amplify-react/package.json b/packages/aws-amplify-react/package.json
index b7fac5521ed..603c1a22f02 100644
--- a/packages/aws-amplify-react/package.json
+++ b/packages/aws-amplify-react/package.json
@@ -108,6 +108,7 @@
}
},
"dependencies": {
+ "pako": "^2.0.4",
"qrcode.react": "^0.8.0",
"regenerator-runtime": "^0.11.1"
},
diff --git a/packages/aws-amplify-react/src/Amplify-UI/data-test-attributes.tsx b/packages/aws-amplify-react/src/Amplify-UI/data-test-attributes.tsx
index 0fa40c3a59b..c23379b206a 100644
--- a/packages/aws-amplify-react/src/Amplify-UI/data-test-attributes.tsx
+++ b/packages/aws-amplify-react/src/Amplify-UI/data-test-attributes.tsx
@@ -150,6 +150,13 @@ export const chatBot = {
sendMessageButton: 'chatbot-send-message-button',
};
+export const chatBotLex2 = {
+ title: 'chatbot-lex-2-title',
+ dialog: 'chatbot-lex-2-dialog',
+ messageInput: 'chatbot-lex-2-message-input',
+ sendMessageButton: 'chatbot-lex-2-send-message-button',
+};
+
export const auth = {
signIn,
signOut,
diff --git a/packages/aws-amplify-react/src/Interactions/Lex2ChatBot.tsx b/packages/aws-amplify-react/src/Interactions/Lex2ChatBot.tsx
new file mode 100644
index 00000000000..5a60ac6d7d9
--- /dev/null
+++ b/packages/aws-amplify-react/src/Interactions/Lex2ChatBot.tsx
@@ -0,0 +1,696 @@
+/*
+ * Copyright 2017-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
+ * the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+import * as React from 'react';
+import {
+ FormSection,
+ SectionHeader,
+ SectionBody,
+ SectionFooter,
+} from '../AmplifyUI';
+import { Input, Button } from '../AmplifyTheme';
+
+import { I18n } from '@aws-amplify/core';
+import { Interactions } from '@aws-amplify/interactions';
+import { ConsoleLogger as Logger } from '@aws-amplify/core';
+import { chatBotLex2 } from '../Amplify-UI/data-test-attributes';
+const logger = new Logger('ChatBot');
+
+// @ts-ignore
+const styles: { [pname: string]: React.CSSProperties } = {
+ itemMe: {
+ padding: 10,
+ fontSize: 12,
+ color: 'gray',
+ marginTop: 4,
+ textAlign: 'right',
+ margin: 10,
+ },
+ itemBot: {
+ fontSize: 12,
+ textAlign: 'left',
+ margin: 10,
+ },
+ list: {
+ height: '300px',
+ overflow: 'auto',
+ },
+ // @ts-ignore
+ textInput: Object.assign({}, Input, {
+ display: 'inline-block',
+ width: 'calc(100% - 90px - 15px)',
+ }),
+ // @ts-ignore
+ button: Object.assign({}, Button, {
+ width: '60px',
+ float: 'right',
+ }),
+ // @ts-ignore
+ mic: Object.assign({}, Button, {
+ width: '40px',
+ float: 'right',
+ }),
+};
+
+const STATES = {
+ INITIAL: { MESSAGE: 'Type your message or click 🎤', ICON: '🎤' },
+ LISTENING: { MESSAGE: 'Listening... click 🔴 again to cancel', ICON: '🔴' },
+ SENDING: { MESSAGE: 'Please wait...', ICON: '🔊' },
+ SPEAKING: { MESSAGE: 'Speaking...', ICON: '...' },
+};
+
+const defaultVoiceConfig = {
+ silenceDetectionConfig: {
+ time: 2000,
+ amplitude: 0.2,
+ },
+};
+
+// @ts-ignore
+let audioControl;
+
+export interface IChatBotProps {
+ botName?: string;
+ clearOnComplete?: boolean;
+ conversationModeOn?: boolean;
+ onComplete: any;
+ textEnabled?: boolean;
+ theme?: any;
+ title?: string;
+ voiceConfig?: any;
+ voiceEnabled?: boolean;
+ welcomeMessage?: string;
+ initialMessage?: string;
+}
+
+export interface IChatBotDialog {
+ message: object | string;
+ from: string;
+}
+
+export interface IChatBotState {
+ audioInput?: any;
+ continueConversation: boolean;
+ currentVoiceState: any;
+ dialog: IChatBotDialog[];
+ inputDisabled: boolean;
+ inputText: string;
+ lexResponse?: any;
+ micButtonDisabled: boolean;
+ micText: string;
+}
+interface Lex2MessageRenderProps {
+ message: any;
+ enabled: boolean;
+ sendMessage: (message: string) => void;
+}
+
+const Lex2MessageRender = ({
+ message,
+}: Lex2MessageRenderProps): React.ReactElement => {
+ switch (message.contentType) {
+ case 'CustomPayload':
+ return
CustomPayload
;
+ case 'ImageResponseCard':
+ return ImageResponseCard
;
+ case 'PlainText':
+ return PlainText: {message.content}
;
+ case 'SSML':
+ return SSML
;
+ }
+ return UnknownMessageContentType
;
+};
+
+export class Lex2ChatBot extends React.Component {
+ private listItemsRef: any;
+
+ // @ts-ignore
+ constructor(props) {
+ super(props);
+ if (this.props.voiceEnabled) {
+ require('./aws-lex-audio');
+ // @ts-ignore
+ audioControl = new global.LexAudio.audioControl();
+ }
+ if (!this.props.textEnabled && this.props.voiceEnabled) {
+ STATES.INITIAL.MESSAGE = 'Click the mic button';
+ // @ts-ignore
+ styles.textInput = Object.assign({}, Input, {
+ display: 'inline-block',
+ width: 'calc(100% - 40px - 15px)',
+ });
+ }
+ if (this.props.textEnabled && !this.props.voiceEnabled) {
+ STATES.INITIAL.MESSAGE = 'Type a message';
+ // @ts-ignore
+ styles.textInput = Object.assign({}, Input, {
+ display: 'inline-block',
+ width: 'calc(100% - 60px - 15px)',
+ });
+ }
+ if (!this.props.voiceConfig.silenceDetectionConfig) {
+ throw new Error('voiceConfig prop is missing silenceDetectionConfig');
+ }
+
+ this.state = {
+ dialog: this.props.welcomeMessage
+ ? [
+ {
+ message: this.props.welcomeMessage,
+ from: 'system',
+ },
+ ]
+ : [],
+ inputText: '',
+ currentVoiceState: STATES.INITIAL,
+ inputDisabled: false,
+ micText: STATES.INITIAL.ICON,
+ continueConversation: false,
+ micButtonDisabled: false,
+ };
+ this.micButtonHandler = this.micButtonHandler.bind(this);
+ this.changeInputText = this.changeInputText.bind(this);
+ this.listItems = this.listItems.bind(this);
+ this.submit = this.submit.bind(this);
+ // @ts-ignore
+ this.listItemsRef = React.createRef();
+ this.onSilenceHandler = this.onSilenceHandler.bind(this);
+ this.doneSpeakingHandler = this.doneSpeakingHandler.bind(this);
+ this.lexResponseHandler = this.lexResponseHandler.bind(this);
+ this.sendMessage = this.sendMessage.bind(this);
+ }
+
+ async micButtonHandler() {
+ if (this.state.continueConversation) {
+ this.reset();
+ } else {
+ this.setState(
+ {
+ inputDisabled: true,
+ continueConversation: true,
+ currentVoiceState: STATES.LISTENING,
+ micText: STATES.LISTENING.ICON,
+ micButtonDisabled: false,
+ },
+ () => {
+ // @ts-ignore
+ audioControl.startRecording(
+ this.onSilenceHandler,
+ null,
+ this.props.voiceConfig.silenceDetectionConfig
+ );
+ }
+ );
+ }
+ }
+
+ onSilenceHandler() {
+ // @ts-ignore
+ audioControl.stopRecording();
+ if (!this.state.continueConversation) {
+ return;
+ }
+ // @ts-ignore
+ audioControl.exportWAV(blob => {
+ this.setState(
+ {
+ currentVoiceState: STATES.SENDING,
+ audioInput: blob,
+ micText: STATES.SENDING.ICON,
+ micButtonDisabled: true,
+ },
+ () => {
+ this.lexResponseHandler();
+ }
+ );
+ });
+ }
+
+ async lexResponseHandler() {
+ if (!Interactions || typeof Interactions.send !== 'function') {
+ throw new Error(
+ 'No Interactions module found, please ensure @aws-amplify/interactions is imported'
+ );
+ }
+ if (!this.state.continueConversation) {
+ return;
+ }
+
+ const interactionsMessage = {
+ content: this.state.audioInput,
+ options: {
+ messageType: 'voice',
+ },
+ };
+ const response = await Interactions.send(
+ // @ts-ignore
+ this.props.botName,
+ interactionsMessage
+ );
+ const decodedMessages = response.messages
+ ? unGzipBase64AsJson(response.messages)
+ : [];
+ const decodedTranscript = response.inputTranscript
+ ? unGzipBase64AsJson(response.inputTranscript)
+ : 'Voice Message';
+ const messages = decodedMessages?.map(i => ({
+ from: 'bot',
+ message: i,
+ }));
+ this.setState(
+ {
+ lexResponse: response,
+ currentVoiceState: STATES.SPEAKING,
+ micText: STATES.SPEAKING.ICON,
+ micButtonDisabled: true,
+ dialog: [
+ ...this.state.dialog,
+ { message: decodedTranscript, from: 'me' },
+ ...messages,
+ ],
+ inputText: '',
+ },
+ () => {
+ this.doneSpeakingHandler();
+ }
+ );
+ this.listItemsRef.current.scrollTop =
+ this.listItemsRef.current.scrollHeight;
+ }
+
+ doneSpeakingHandler() {
+ if (!this.state.continueConversation) {
+ return;
+ }
+ if (this.state.lexResponse.contentType === 'audio/mpeg') {
+ // @ts-ignore
+ audioControl.play(this.state.lexResponse.audioStream, () => {
+ if (
+ this.state.lexResponse.dialogState === 'ReadyForFulfillment' ||
+ this.state.lexResponse.dialogState === 'Fulfilled' ||
+ this.state.lexResponse.dialogState === 'Failed' ||
+ !this.props.conversationModeOn
+ ) {
+ this.setState({
+ inputDisabled: false,
+ currentVoiceState: STATES.INITIAL,
+ micText: STATES.INITIAL.ICON,
+ micButtonDisabled: false,
+ continueConversation: false,
+ });
+ } else {
+ this.setState(
+ {
+ currentVoiceState: STATES.LISTENING,
+ micText: STATES.LISTENING.ICON,
+ micButtonDisabled: false,
+ },
+ () => {
+ // @ts-ignore
+ audioControl.startRecording(
+ this.onSilenceHandler,
+ null,
+ this.props.voiceConfig.silenceDetectionConfig
+ );
+ }
+ );
+ }
+ });
+ } else {
+ this.setState({
+ inputDisabled: false,
+ currentVoiceState: STATES.INITIAL,
+ micText: STATES.INITIAL.ICON,
+ micButtonDisabled: false,
+ continueConversation: false,
+ });
+ }
+ }
+
+ reset() {
+ this.setState(
+ {
+ inputText: '',
+ currentVoiceState: STATES.INITIAL,
+ inputDisabled: false,
+ micText: STATES.INITIAL.ICON,
+ continueConversation: false,
+ micButtonDisabled: false,
+ },
+ () => {
+ // @ts-ignore
+ audioControl.clear();
+ }
+ );
+ }
+
+ listItems() {
+ const lastIndex = this.state.dialog.length - 1;
+ return this.state.dialog.map((m, i) => {
+ if (m.from === 'me') {
+ return (
+
+ {typeof m.message === 'string' ? (
+
{m.message}
+ ) : (
+
+ )}
+
+ );
+ } else if (m.from === 'system') {
+ return (
+
+ {typeof m.message === 'string' ? (
+
{m.message}
+ ) : (
+
+ )}
+
+ );
+ } else {
+ return (
+
+ {typeof m.message === 'string' ? (
+
{m.message}
+ ) : (
+
+ )}
+
+ );
+ }
+ });
+ }
+
+ async sendMessage(text: string) {
+ const response = await Interactions.send(
+ // @ts-ignore
+ this.props.botName,
+ text
+ );
+ const messages = response?.messages?.map(i => ({
+ from: 'bot',
+ message: i,
+ }));
+ this.setState({
+ // @ts-ignore
+ dialog: [
+ ...this.state.dialog,
+ // @ts-ignore
+ ...messages,
+ ],
+ inputText: '',
+ });
+ this.listItemsRef.current.scrollTop =
+ this.listItemsRef.current.scrollHeight;
+ }
+
+ // @ts-ignore
+ async submit(e) {
+ e?.preventDefault();
+
+ if (!this.state.inputText) {
+ return;
+ }
+
+ await new Promise(resolve =>
+ this.setState(
+ {
+ dialog: [
+ ...this.state.dialog,
+ { message: this.state.inputText, from: 'me' },
+ ],
+ },
+ // @ts-ignore
+ resolve
+ )
+ );
+
+ if (!Interactions || typeof Interactions.send !== 'function') {
+ throw new Error(
+ 'No Interactions module found, please ensure @aws-amplify/interactions is imported'
+ );
+ }
+
+ this.sendMessage(this.state.inputText);
+ }
+
+ // @ts-ignore
+ async changeInputText(event) {
+ await this.setState({ inputText: event.target.value });
+ }
+ // @ts-ignore
+ getOnComplete(fn) {
+ // @ts-ignore
+ return (...args) => {
+ const { clearOnComplete } = this.props;
+ const message = fn(...args);
+
+ this.setState(
+ {
+ dialog: [
+ ...(clearOnComplete ? [] : this.state.dialog),
+ message && { from: 'bot', message },
+ ].filter(Boolean),
+ },
+ () => {
+ this.listItemsRef.current.scrollTop =
+ this.listItemsRef.current.scrollHeight;
+ }
+ );
+ };
+ }
+
+ componentDidMount() {
+ const { onComplete, botName, initialMessage } = this.props;
+
+ if (onComplete && botName) {
+ if (!Interactions || typeof Interactions.onComplete !== 'function') {
+ throw new Error(
+ 'No Interactions module found, please ensure @aws-amplify/interactions is imported'
+ );
+ }
+ // @ts-ignore
+ Interactions.onComplete(botName, this.getOnComplete(onComplete, this));
+ }
+ if (initialMessage) this.sendMessage(initialMessage);
+ }
+
+ // @ts-ignore
+ componentDidUpdate(prevProps) {
+ const { onComplete, botName } = this.props;
+
+ if (botName && this.props.onComplete !== prevProps.onComplete) {
+ if (!Interactions || typeof Interactions.onComplete !== 'function') {
+ throw new Error(
+ 'No Interactions module found, please ensure @aws-amplify/interactions is imported'
+ );
+ }
+ // @ts-ignore
+ Interactions.onComplete(botName, this.getOnComplete(onComplete, this));
+ }
+ }
+
+ render() {
+ const { title, theme } = this.props;
+
+ return (
+
+ {title && (
+
+ {I18n.get(title)}
+
+ )}
+
+
+ {this.listItems()}
+
+
+
+
+
+
+ );
+ }
+}
+
+// @ts-ignore
+function ChatBotTextInput(props) {
+ const styles = props.styles;
+ const onChange = props.onChange;
+ const inputText = props.inputText;
+ const inputDisabled = props.inputDisabled;
+ const currentVoiceState = props.currentVoiceState;
+
+ return (
+
+ );
+}
+
+// @ts-ignore
+function ChatBotMicButton(props) {
+ const voiceEnabled = props.voiceEnabled;
+ const styles = props.styles;
+ const micButtonDisabled = props.micButtonDisabled;
+ const handleMicButton = props.handleMicButton;
+ const micText = props.micText;
+
+ if (!voiceEnabled) {
+ return null;
+ }
+
+ return (
+
+ );
+}
+
+// @ts-ignore
+function ChatBotTextButton(props) {
+ const textEnabled = props.textEnabled;
+ const styles = props.styles;
+ const inputDisabled = props.inputDisabled;
+
+ if (!textEnabled) {
+ return null;
+ }
+
+ return (
+
+ );
+}
+
+// @ts-ignore
+function ChatBotInputs(props) {
+ const voiceEnabled = props.voiceEnabled;
+ const textEnabled = props.textEnabled;
+ const styles = props.styles;
+ const onChange = props.onChange;
+ let inputDisabled = props.inputDisabled;
+ const micButtonDisabled = props.micButtonDisabled;
+ const inputText = props.inputText;
+ const onSubmit = props.onSubmit;
+ const handleMicButton = props.handleMicButton;
+ const micText = props.micText;
+ const currentVoiceState = props.currentVoiceState;
+
+ if (voiceEnabled && !textEnabled) {
+ inputDisabled = true;
+ }
+
+ if (!voiceEnabled && !textEnabled) {
+ return (
+
+ No Chatbot inputs enabled. Set at least one of voiceEnabled or
+ textEnabled in the props.{' '}
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+// @ts-ignore
+Lex2ChatBot.defaultProps = {
+ title: '',
+ botName: '',
+ onComplete: undefined,
+ clearOnComplete: false,
+ voiceConfig: defaultVoiceConfig,
+ conversationModeOn: false,
+ voiceEnabled: false,
+ textEnabled: true,
+};
+
+export default Lex2ChatBot;
diff --git a/packages/aws-amplify-react/src/Interactions/helpers/convert.ts b/packages/aws-amplify-react/src/Interactions/helpers/convert.ts
new file mode 100644
index 00000000000..591a5f3701e
--- /dev/null
+++ b/packages/aws-amplify-react/src/Interactions/helpers/convert.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2017-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
+ * the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+import pako from 'pako';
+
+export const base64ToArrayBuffer = (base64: string): Uint8Array => {
+ const binary_string = window.atob(base64);
+ const len = binary_string.length;
+ const bytes = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binary_string.charCodeAt(i);
+ }
+ return bytes;
+};
+
+export const unGzipBase64AsJson = (gzipBase64: string): T => {
+ return JSON.parse(
+ pako.ungzip(base64ToArrayBuffer(gzipBase64), { to: 'string' })
+ ) as T;
+};
diff --git a/packages/interactions/package.json b/packages/interactions/package.json
index d881be828ef..90b060289e0 100644
--- a/packages/interactions/package.json
+++ b/packages/interactions/package.json
@@ -42,7 +42,9 @@
"homepage": "https://aws-amplify.github.io/",
"dependencies": {
"@aws-amplify/core": "4.6.1",
- "@aws-sdk/client-lex-runtime-service": "3.6.1"
+ "@aws-sdk/client-lex-runtime-service": "3.6.1",
+ "@aws-sdk/client-lex-runtime-v2": "^3.31.0",
+ "pako": "^2.0.4"
},
"jest": {
"globals": {
diff --git a/packages/interactions/src/Providers/AWSLex2Provider.ts b/packages/interactions/src/Providers/AWSLex2Provider.ts
new file mode 100644
index 00000000000..ea432a7faaa
--- /dev/null
+++ b/packages/interactions/src/Providers/AWSLex2Provider.ts
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
+ * the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+import { AbstractInteractionsProvider } from './InteractionsProvider';
+import {
+ InteractionsOptions,
+ InteractionsResponse,
+ InteractionsMessage,
+} from '../types';
+import {
+ LexRuntimeV2Client,
+ RecognizeTextCommand,
+ RecognizeTextCommandInput,
+ RecognizeUtteranceCommand,
+ RecognizeUtteranceCommandInput,
+ SessionState,
+} from '@aws-sdk/client-lex-runtime-v2';
+import {
+ ConsoleLogger as Logger,
+ Credentials,
+ getAmplifyUserAgent,
+} from '@aws-amplify/core';
+import { convert, unGzipBase64AsJson } from './AWSLexProviderHelper/convert';
+
+const logger = new Logger('AWSLex2Provider');
+
+export class AWSLex2Provider extends AbstractInteractionsProvider {
+ // @ts-ignore
+ private lex2RuntimeServiceClient: LexRuntimeV2Client;
+ private _botsCompleteCallback: object;
+
+ constructor(options: InteractionsOptions = {}) {
+ super(options);
+ this._botsCompleteCallback = {};
+ }
+
+ getProviderName() {
+ return 'AWSLex2Provider';
+ }
+
+ reportBotStatus(botname: string, sessionState?: SessionState) {
+ // Check if state is fulfilled to resolve onFullfilment promise
+ logger.debug('postContent state', sessionState?.intent?.state);
+ if (
+ sessionState?.intent?.state === 'ReadyForFulfillment' ||
+ sessionState?.intent?.state === 'Fulfilled'
+ ) {
+ // @ts-ignore
+ if (typeof this._botsCompleteCallback[botname] === 'function') {
+ setTimeout(
+ () =>
+ // @ts-ignore
+ this._botsCompleteCallback[botname](null, {
+ slots: sessionState?.intent?.slots,
+ }),
+ 0
+ );
+ }
+
+ if (
+ this._config &&
+ typeof this._config[botname].onComplete === 'function'
+ ) {
+ setTimeout(
+ () =>
+ this._config[botname].onComplete(null, {
+ slots: sessionState?.intent?.slots,
+ }),
+ 0
+ );
+ }
+ }
+
+ if (sessionState?.intent?.state === 'Failed') {
+ // @ts-ignore
+ if (typeof this._botsCompleteCallback[botname] === 'function') {
+ setTimeout(
+ // @ts-ignore
+ () => this._botsCompleteCallback[botname]('Bot conversation failed'),
+ 0
+ );
+ }
+
+ if (
+ this._config &&
+ typeof this._config[botname].onComplete === 'function'
+ ) {
+ setTimeout(
+ () => this._config[botname].onComplete('Bot conversation failed'),
+ 0
+ );
+ }
+ }
+ }
+
+ async sendMessage(
+ botname: string,
+ message: string | InteractionsMessage
+ ): Promise {
+ if (!this._config[botname]) {
+ return Promise.reject('Bot ' + botname + ' does not exist');
+ }
+ const credentials = await Credentials.get();
+ if (!credentials) {
+ return Promise.reject('No credentials');
+ }
+ this.lex2RuntimeServiceClient = new LexRuntimeV2Client({
+ region: this._config[botname].region,
+ credentials,
+ customUserAgent: getAmplifyUserAgent(),
+ });
+ let params: RecognizeTextCommandInput | RecognizeUtteranceCommandInput;
+ if (typeof message === 'string') {
+ params = {
+ botAliasId: this._config[botname].botAliasId,
+ botId: this._config[botname].botId,
+ localeId: this._config[botname].localeId,
+ text: message,
+ sessionId: credentials.identityId,
+ };
+
+ logger.debug('postText to lex2', message);
+
+ try {
+ const postTextCommand = new RecognizeTextCommand(params);
+ const data = await this.lex2RuntimeServiceClient.send(postTextCommand);
+ this.reportBotStatus(botname, data.sessionState);
+ return data;
+ } catch (err) {
+ return Promise.reject(err);
+ }
+ } else {
+ const {
+ content,
+ options: { messageType },
+ } = message;
+
+ if (messageType === 'voice') {
+ params = {
+ botAliasId: this._config[botname].botAliasId,
+ botId: this._config[botname].botId,
+ localeId: this._config[botname].localeId,
+ sessionId: credentials.identityId,
+ requestContentType: 'audio/x-l16; sample-rate=16000; channel-count=1',
+ inputStream: await convert(content as Blob),
+ } as RecognizeUtteranceCommandInput;
+ } else {
+ params = {
+ botAliasId: this._config[botname].botAliasId,
+ botId: this._config[botname].botId,
+ localeId: this._config[botname].localeId,
+ sessionId: credentials.identityId,
+ requestContentType: 'text/plain; charset=utf-8',
+ inputStream: content as string,
+ } as RecognizeUtteranceCommandInput;
+ }
+ logger.debug('postContent to lex2', message);
+ try {
+ // @ts-ignore
+ const postContentCommand = new RecognizeUtteranceCommand(params);
+ const data = await this.lex2RuntimeServiceClient.send(
+ postContentCommand
+ );
+ // @ts-ignore
+ const audioArray = await convert(data.audioStream);
+ this.reportBotStatus(
+ botname,
+ data.sessionState
+ ? unGzipBase64AsJson(data.sessionState)
+ : undefined
+ );
+ return { ...data, ...{ audioStream: audioArray } };
+ } catch (err) {
+ return Promise.reject(err);
+ }
+ }
+ }
+
+ // @ts-ignore
+ onComplete(botname: string, callback) {
+ if (!this._config[botname]) {
+ throw new ErrorEvent('Bot ' + botname + ' does not exist');
+ }
+ // @ts-ignore
+ this._botsCompleteCallback[botname] = callback;
+ }
+}
diff --git a/packages/interactions/src/Providers/AWSLexProviderHelper/convert.native.ts b/packages/interactions/src/Providers/AWSLexProviderHelper/convert.native.ts
index 15521d87ad8..a32b64710b0 100644
--- a/packages/interactions/src/Providers/AWSLexProviderHelper/convert.native.ts
+++ b/packages/interactions/src/Providers/AWSLexProviderHelper/convert.native.ts
@@ -1,3 +1,18 @@
+/*
+ * Copyright 2017-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
+ * the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+import pako from 'pako';
+
export const convert = (stream: Blob): Promise => {
return new Promise(async (res, rej) => {
const blobURL = URL.createObjectURL(stream);
@@ -11,3 +26,19 @@ export const convert = (stream: Blob): Promise => {
request.send();
});
};
+
+export const base64ToArrayBuffer = (base64: string): Uint8Array => {
+ const binary_string = window.atob(base64);
+ const len = binary_string.length;
+ const bytes = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binary_string.charCodeAt(i);
+ }
+ return bytes;
+};
+
+export const unGzipBase64AsJson = (gzipBase64: string): T => {
+ return JSON.parse(
+ pako.ungzip(base64ToArrayBuffer(gzipBase64), { to: 'string' })
+ ) as T;
+};
diff --git a/packages/interactions/src/Providers/AWSLexProviderHelper/convert.ts b/packages/interactions/src/Providers/AWSLexProviderHelper/convert.ts
index 6582f677ca9..7d5c1726a75 100644
--- a/packages/interactions/src/Providers/AWSLexProviderHelper/convert.ts
+++ b/packages/interactions/src/Providers/AWSLexProviderHelper/convert.ts
@@ -1,4 +1,19 @@
+/*
+ * Copyright 2017-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with
+ * the License. A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0/
+ *
+ * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
import { Readable } from 'stream';
+import pako from 'pako';
+
export const convert = (
stream: Blob | Readable | ReadableStream
): Promise => {
@@ -10,3 +25,19 @@ export const convert = (
throw new Error('Readable is not supported.');
}
};
+
+export const base64ToArrayBuffer = (base64: string): Uint8Array => {
+ const binary_string = window.atob(base64);
+ const len = binary_string.length;
+ const bytes = new Uint8Array(len);
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binary_string.charCodeAt(i);
+ }
+ return bytes;
+};
+
+export const unGzipBase64AsJson = (gzipBase64: string): T => {
+ return JSON.parse(
+ pako.ungzip(base64ToArrayBuffer(gzipBase64), { to: 'string' })
+ ) as T;
+};