├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── NOTICE ├── README-connect-live-chat.md ├── README-css-style.md ├── README-file-upload.md ├── README-qbusiness.md ├── README-streaming-responses.md ├── README.md ├── babel.config.js ├── build ├── Makefile ├── create-custom-css.js ├── create-iframe-snippet-file.sh ├── release.sh ├── update-lex-web-ui-config.js └── upload-bootstrap.sh ├── config ├── base.env.js ├── dist.env.js ├── env.mk ├── full.env.js ├── index.js └── utils │ └── merge-config.js ├── dist ├── 3.5.13_dist_vue.global.prod.js ├── 3.8.3_dist_vuetify.min.css ├── 3.8.3_dist_vuetify.min.js ├── 4.1.0_dist_vuex.min.js ├── Makefile ├── aws-sdk-2.903.0.min.js ├── custom-chatbot-style.css ├── index.html ├── initiate-loader.js ├── lex-web-ui-loader.css ├── lex-web-ui-loader.css.map ├── lex-web-ui-loader.js ├── lex-web-ui-loader.js.map ├── lex-web-ui-loader.min.css ├── lex-web-ui-loader.min.css.map ├── lex-web-ui-loader.min.js ├── lex-web-ui-loader.min.js.map ├── lex-web-ui.js ├── lex-web-ui.js.map ├── lex-web-ui.min.css ├── lex-web-ui.min.js ├── material_icons.css ├── parent.html ├── wav-worker.js ├── wav-worker.js.map └── wav-worker.min.js ├── example-css ├── bright-yellow.css ├── coral-pink.css ├── dark-mode-theme.css ├── elegant-purple-theme.css ├── forest-green.css ├── professional-blue.css ├── sky-blue.css └── sunset-orange.css ├── img ├── ExampleBuildForLexWebUi.png ├── Lex-Streaming-Architecture.png ├── LexWebUiStyle.png ├── QBusiness.gif ├── aud-claim.PNG ├── cfn-stack.png ├── connect-contact-flows.png ├── connect-flow-details.png ├── example-css.png ├── f.0.14.0_buttonA.png ├── f.0.14.0_buttonB.png ├── f.0.14.0_login.png ├── f.0.14.0_markdown.png ├── f.0.14.0_multimessages.png ├── feedbackButtons.png ├── identity-propagation.PNG ├── interactive-message-datepicker.png ├── interactive-message-datetimepicker.png ├── interactive-message-listpicker.png ├── lex-streaming-demo-2.gif ├── pipeline.png ├── sample_disconnect_flow.png ├── token-issuer.PNG ├── toolbar.png ├── upload.png ├── webapp-diagram.png ├── webapp-full.gif └── webapp-iframe.gif ├── lex-web-ui ├── .browserslistrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── README.md ├── READMECONFIGSCREENS.md ├── babel.config.js ├── current │ ├── user-custom-chatbot-style.css │ └── user-lex-web-ui-loader-config.json ├── package-lock.json ├── package.json ├── public │ ├── img │ │ ├── flowers.jpeg │ │ └── note.md │ └── index.html ├── readmeimages │ ├── identitypoolauthenticationproviders.png │ ├── userpoolappclientsettings.png │ ├── userpoolappdomainname.png │ ├── userpoolcreatecomplete.png │ ├── userpoolstep1.png │ ├── userpoolstep10.png │ ├── userpoolstep2.png │ ├── userpoolstep3.png │ ├── userpoolstep4.png │ ├── userpoolstep5a.png │ ├── userpoolstep5b.png │ ├── userpoolstep6.png │ ├── userpoolstep7.png │ ├── userpoolstep8.png │ ├── userpoolstep8a.png │ ├── userpoolstep8b.png │ └── userpoolstep9.png ├── src │ ├── App.vue │ ├── LexApp.vue │ ├── assets │ │ ├── silent.mp3 │ │ └── silent.ogg │ ├── components │ │ ├── InputContainer.vue │ │ ├── LexWeb.vue │ │ ├── Message.vue │ │ ├── MessageList.vue │ │ ├── MessageLoading.vue │ │ ├── MessageText.vue │ │ ├── MinButton.vue │ │ ├── RecorderStatus.vue │ │ ├── ResponseCard.vue │ │ └── ToolbarContainer.vue │ ├── config │ │ ├── .gitattributes │ │ ├── config.awstest.json │ │ ├── config.current.json │ │ ├── config.dev.json │ │ ├── config.prod.json │ │ ├── config.test.json │ │ └── index.js │ ├── init.js │ ├── lex-web-ui.js │ ├── lib │ │ └── lex │ │ │ ├── client.js │ │ │ ├── recorder.js │ │ │ └── wav-worker.js │ ├── main.js │ ├── router │ │ └── index.js │ └── store │ │ ├── actions.js │ │ ├── getters.js │ │ ├── index.js │ │ ├── live-chat-handlers.js │ │ ├── mutations.js │ │ ├── recorder-handlers.js │ │ ├── state.js │ │ └── talkdesk-live-chat-handlers.js ├── test │ ├── e2e │ │ ├── custom-assertions │ │ │ └── elementCount.js │ │ ├── nightwatch.conf.js │ │ ├── runner.js │ │ └── specs │ │ │ └── test.js │ └── unit │ │ ├── .eslintrc │ │ ├── index.js │ │ ├── karma.conf.js │ │ └── specs │ │ ├── InputContainer.spec.js │ │ ├── LexWeb.spec.js │ │ ├── Message.spec.js │ │ ├── MessageList.spec.js │ │ ├── RecorderStatus.spec.js │ │ ├── lex-web-ui.spec.js │ │ └── store.spec.js └── vue.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── server.js ├── src ├── README.md ├── config │ ├── .gitattributes │ ├── default-lex-web-ui-loader-config.json │ └── lex-web-ui-loader-config.json ├── dependencies │ ├── 3.5.13_dist_vue.global.prod.js │ ├── 3.8.3_dist_vuetify.min.css │ ├── 3.8.3_dist_vuetify.min.js │ ├── 4.1.0_dist_vuex.min.js │ ├── initiate-loader.js │ └── material_icons.css ├── initiate-chat-lambda │ └── index.js ├── lex-web-ui-loader │ ├── css │ │ ├── lex-web-ui-fullpage.css │ │ └── lex-web-ui-iframe.css │ └── js │ │ ├── defaults │ │ ├── dependencies.js │ │ ├── lex-web-ui.js │ │ └── loader.js │ │ ├── index.js │ │ └── lib │ │ ├── config-loader.js │ │ ├── dependency-loader.js │ │ ├── fullpage-component-loader.js │ │ ├── iframe-component-loader.js │ │ └── loginutil.js ├── qbusiness-lambda │ └── index.py ├── streaming-lambda │ └── index.js └── website │ ├── custom-chatbot-style.css │ ├── iframeparent.html │ ├── index.css │ ├── index.html │ └── parent.html ├── templates ├── Makefile ├── README.md ├── codebuild-deploy.yaml ├── cognito.yaml ├── cognitouserpoolconfig.yaml ├── custom-resources │ ├── cfnresponse.py │ ├── codebuild-start.py │ ├── requirements.txt │ └── s3-cleanup.py ├── lexbot.yaml ├── master.yaml ├── restapi.yaml └── streaming-support.yaml └── webpack.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | }, 10 | extends: 'airbnb-base', 11 | rules: { 12 | // allow debugger during development 13 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | dist/*.js -diff 2 | dist/*.css -diff 3 | dist/*.map -diff 4 | dist/*.zip -diff 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | node_modules/ 3 | .DS_Store 4 | .idea/ 5 | py_modules/ 6 | */*/py_modules/ 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | build 2 | config 3 | img 4 | lex-web-ui 5 | Makefile 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Amazon Software License 2 | 1. Definitions 3 | “Licensor” means any person or entity that distributes its Work. 4 | 5 | “Software” means the original work of authorship made available under this License. 6 | 7 | “Work” means the Software and any additions to or derivative works of the Software that are made available under this License. 8 | 9 | The terms “reproduce,” “reproduction,” “derivative works,” and “distribution” have the meaning as provided under U.S. copyright law; provided, however, that for the purposes of this License, derivative works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work. 10 | 11 | Works, including the Software, are “made available” under this License by including in or with the Work either (a) a copyright notice referencing the applicability of this License to the Work, or (b) a copy of this License. 12 | 2. License Grants 13 | 2.1 Copyright Grant. Subject to the terms and conditions of this License, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free, copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense and distribute its Work and any resulting derivative works in any form. 14 | 2.2 Patent Grant. Subject to the terms and conditions of this License, each Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free patent license to make, have made, use, sell, offer for sale, import, and otherwise transfer its Work, in whole or in part. The foregoing license applies only to the patent claims licensable by Licensor that would be infringed by Licensor’s Work (or portion thereof) individually and excluding any combinations with any other materials or technology. 15 | 3. Limitations 16 | 3.1 Redistribution. You may reproduce or distribute the Work only if (a) you do so under this License, (b) you include a complete copy of this License with your distribution, and (c) you retain without modification any copyright, patent, trademark, or attribution notices that are present in the Work. 17 | 3.2 Derivative Works. You may specify that additional or different terms apply to the use, reproduction, and distribution of your derivative works of the Work (“Your Terms”) only if (a) Your Terms provide that the use limitation in Section 3.3 applies to your derivative works, and (b) you identify the specific derivative works that are subject to Your Terms. Notwithstanding Your Terms, this License (including the redistribution requirements in Section 3.1) will continue to apply to the Work itself. 18 | 3.3 Use Limitation. The Work and any derivative works thereof only may be used or intended for use with the web services, computing platforms or applications provided by Amazon.com, Inc. or its affiliates, including Amazon Web Services, Inc. 19 | 3.4 Patent Claims. If you bring or threaten to bring a patent claim against any Licensor (including any claim, cross-claim or counterclaim in a lawsuit) to enforce any patents that you allege are infringed by any Work, then your rights under this License from such Licensor (including the grants in Sections 2.1 and 2.2) will terminate immediately. 20 | 3.5 Trademarks. This License does not grant any rights to use any Licensor’s or its affiliates’ names, logos, or trademarks, except as necessary to reproduce the notices described in this License. 21 | 3.6 Termination. If you violate any term of this License, then your rights under this License (including the grants in Sections 2.1 and 2.2) will terminate immediately. 22 | 4. Disclaimer of Warranty. 23 | THE WORK IS PROVIDED “AS IS” WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF M ERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER THIS LICENSE. SOME STATES’ CONSUMER LAWS DO NOT ALLOW EXCLUSION OF AN IMPLIED WARRANTY, SO THIS DISCLAIMER MAY NOT APPLY TO YOU. 24 | 5. Limitation of Liability. 25 | EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER COMM ERCIAL DAMAGES OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 26 | Effective Date – April 18, 2008 © 2008 Amazon.com, Inc. or its affiliates. All rights reserved. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | aws-lex-web-ui 2 | Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README-file-upload.md: -------------------------------------------------------------------------------- 1 | # Lex File Uploads 2 | 3 | This feature allows users of the Lex Web UI to upload documents directly to a designated S3 bucket. This feature can be used to analyize documents or extract specific information using other AWS services through Lambda fulfillment in your Lex bot. When the user uploads a document, it is stored in the designated S3 bucket at a path location specific to that user. The location of the document in S3 is then set as a sessionAttribute in the Lex bot so that downstream processes can locate and retrieve the document. 4 | 5 | 6 | 7 | **Note** - For security, upload functionality is only available for logged in users. To support anonymous uploading, you will need to configure your own IAM policies in Cognito and manually set `uploadRequireLogin` to false in your `lex-web-ui-loader-config.json` file. Allowing anonymous uploading of documents is not recommended. 8 | 9 | ## Deploy or update the Lex Web UI Stack 10 | 11 | To turn on support for uploading documents into S3 via the Lex Web UI, you will need a pre-existing S3 bucket to serve as the repository for these documents. This repostiory will need a CORS configuration that allows the uploaded documents to be sent from the source of your Lex Web UI (instructions below for configuration). 12 | 13 | To enable uploading via the Cloud Formation template, update the following parameters: 14 | - `ShouldEnableUpload` to true 15 | - `UploadBucket` to the bucket name of the S3 bucket that will store the documents 16 | 17 | ## Working with uploaded documents in session attributes 18 | 19 | When a document is uploaded via the UI, its location and filename are added as a JSON object into the sessionAttributes of the bot as shown below. A timestamp is added to the document name and a folder structure is created so individual users and files can be properly differentiated. The original filename is retained in the session attributes. Your fulfillment Lambda can add to this object as needed, but the `s3Path` and `fileName` properties should remain to ensure the Web UI functions propertly. 20 | 21 | ``` 22 | { 23 | "s3Path": "s3://atj-demo-faqs/{sessionId}/{filename}-{timestamp}.txt", 24 | "fileName": "todo-logging.notes.txt" 25 | } 26 | ``` 27 | 28 | ## Configuring CORS in the destination S3 bucket 29 | 30 | CORS configuration is **not** part of the CloudFormation template deployment and must be done by the user for uploading of documents to work. For CloudFormation deployments, the allowed origin for the CORS policy is the location of the deployed Web UI files in CloudFront, not the parent origin. You can find this origin in the outputs of your template deployment, including in the `WebAppUrl` output. 31 | 32 | Here is an example CORS policy for an S3 bucket: 33 | ``` 34 | [ 35 | { 36 | "AllowedHeaders": [ 37 | "*" 38 | ], 39 | "AllowedMethods": [ 40 | "PUT" 41 | ], 42 | "AllowedOrigins": [ 43 | "https://xxxxxxxxxxxx.cloudfront.net" 44 | ], 45 | "ExposeHeaders": [], 46 | "MaxAgeSeconds": 3000 47 | } 48 | ] 49 | ``` 50 | 51 | ## Additional post deployment configuration 52 | 53 | To update the success & failure messages associated with document upload, add the following configuration items to the `ui` section of your `lex-web-ui-loader-config.json` file (this file is deployed into the webapp bucket during Cloudformation deployment): 54 | - uploadSuccessMessage (blank by default, will only display if a value is provided) 55 | - uploadFailureMessage 56 | 57 | If a failure message is not set, users will see the default failure message. 58 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { 4 | // debug: true, 5 | useBuiltIns: 'usage', 6 | corejs: 3, 7 | }], 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /build/Makefile: -------------------------------------------------------------------------------- 1 | # This Makefile is used to update the bootstrap bucket containing 2 | # the project source and CloudFormation templates 3 | 4 | # environment file controls config parameters 5 | CONFIG_ENV := ../config/env.mk 6 | include $(CONFIG_ENV) 7 | 8 | # cfn templates 9 | TEMPLATES_DIR := ../templates 10 | TEMPLATES := $(wildcard $(TEMPLATES_DIR)/*.yaml) 11 | 12 | # lambda dir 13 | SOURCE_DIR := ../src 14 | 15 | # build output directory 16 | OUT := out 17 | # put output dir in VPATH to simplify finding dependencies 18 | VPATH := $(OUT) 19 | 20 | .DELETE_ON_ERROR: 21 | 22 | # upload files to bootstrap bucket 23 | # NOTE: files uploaded with public read permissions 24 | upload: upload-templates upload-custom-resources-zip upload-src-zip \ 25 | upload-response-card-image upload-initiate-chat-lambda upload-streaming-lambda upload-qbusiness-lambda 26 | .PHONY: upload 27 | 28 | # create the output directory for tracking dependencies 29 | $(OUT): 30 | mkdir -p "$(@)" 31 | 32 | # upload cfn templatess 33 | upload-templates: $(TEMPLATES) | $(OUT) 34 | #@echo "[INFO] Validating templates" 35 | #@$(MAKE) -C $(TEMPLATES_DIR) 36 | @echo "[INFO] Uploading templates" 37 | aws s3 sync --acl public-read --exclude "*" --include "*.yaml" \ 38 | "$(TEMPLATES_DIR)" "s3://$(BOOTSTRAP_BUCKET_PATH)/templates/" \ 39 | | tee "$(OUT)/$(@)" 40 | @echo "[INFO] master template: https://s3.amazonaws.com/$(BOOTSTRAP_BUCKET_PATH)/templates/master.yaml" 41 | 42 | 43 | # cfn custom resource lambda files are found under this directory 44 | CUSTOM_RESOURCES_DIR := $(TEMPLATES_DIR)/custom-resources 45 | 46 | # zip cfn custom resource lambda files 47 | PY_MODULES := $(CUSTOM_RESOURCES_DIR)/py_modules 48 | CUSTOM_RESOURCES_ZIP := custom-resources-$(VERSION).zip 49 | CUSTOM_RESOURCES_FILES := $(wildcard $(CUSTOM_RESOURCES_DIR)/*.py) 50 | CUSTOM_RESOURCES_FILES += $(PY_MODULES) 51 | $(PY_MODULES): 52 | pushd $(CUSTOM_RESOURCES_DIR) ; \ 53 | [ -f requirements.txt ] && \ 54 | python3 -m pip install --upgrade -r requirements.txt -t ./py_modules || true ; \ 55 | popd ; 56 | $(CUSTOM_RESOURCES_ZIP): $(CUSTOM_RESOURCES_FILES) | $(OUT) 57 | @echo "[INFO] Creating custom resource Lambda zip file" 58 | zip -u -j "$(OUT)/$(@)" $(?) ; \ 59 | pushd $(CUSTOM_RESOURCES_DIR)/py_modules ; \ 60 | zip -r -q "../../../build/$(OUT)/$(@)" . ; \ 61 | popd ; 62 | upload-custom-resources-zip: $(CUSTOM_RESOURCES_ZIP) | $(OUT) 63 | @echo "[INFO] Uploading custom resources Lambda zip file" 64 | aws s3 cp --acl public-read \ 65 | "$(OUT)/$(CUSTOM_RESOURCES_ZIP)" \ 66 | "s3://$(BOOTSTRAP_BUCKET_PATH)/$(CUSTOM_RESOURCES_ZIP)" \ 67 | | tee -a "$(OUT)/$(@)" 68 | 69 | # initiate chat lambda function 70 | 71 | INITIATE_CHAT_LAMBDA_DIR := $(SOURCE_DIR)/initiate-chat-lambda 72 | INITIATE_CHAT_LAMBDA_ZIP := initiate-chat-lambda-$(VERSION).zip 73 | INITIATE_CHAT_LAMBDA_RESOURCES_FILES := $(wildcard $(INITIATE_CHAT_LAMBDA_DIR)/*.js) 74 | 75 | $(INITIATE_CHAT_LAMBDA_ZIP): $(INITIATE_CHAT_LAMBDA_DIR)/index.js 76 | @echo "[INFO] Creating initiate chat Lambda zip file" 77 | zip -r -j "$(OUT)/$(INITIATE_CHAT_LAMBDA_ZIP)" $(INITIATE_CHAT_LAMBDA_DIR) ; 78 | 79 | upload-initiate-chat-lambda: 80 | @echo "[INFO] uploading initiate chat lambda" 81 | aws s3 cp --acl public-read \ 82 | "$(OUT)/$(INITIATE_CHAT_LAMBDA_ZIP)" "s3://$(BOOTSTRAP_BUCKET_PATH)/$(INITIATE_CHAT_LAMBDA_ZIP)" \ 83 | | tee -a "$(OUT)/$(@)" 84 | 85 | # initiate chat lambda function 86 | 87 | STREAMING_LAMBDA_DIR := $(SOURCE_DIR)/streaming-lambda 88 | STREAMING_LAMBDA_ZIP := streaming-lambda-$(VERSION).zip 89 | STREAMING_LAMBDA_RESOURCES_FILES := $(wildcard $(STREAMING_LAMBDA_DIR)/*.js) 90 | 91 | $(STREAMING_LAMBDA_ZIP): $(STREAMING_LAMBDA_DIR)/index.js 92 | @echo "[INFO] Creating streaming Lambda zip file" 93 | zip -r -j "$(OUT)/$(STREAMING_LAMBDA_ZIP)" $(STREAMING_LAMBDA_DIR) ; 94 | 95 | upload-streaming-lambda: 96 | @echo "[INFO] uploading streaming lambda" 97 | aws s3 cp --acl public-read \ 98 | "$(OUT)/$(STREAMING_LAMBDA_ZIP)" "s3://$(BOOTSTRAP_BUCKET_PATH)/$(STREAMING_LAMBDA_ZIP)" \ 99 | | tee -a "$(OUT)/$(@)" 100 | 101 | QBUSINESS_LAMBDA_DIR := $(SOURCE_DIR)/qbusiness-lambda 102 | QBUSINESS_LAMBDA_ZIP := qbusiness-lambda-$(VERSION).zip 103 | QBUSINESS_LAMBDA_RESOURCES_FILES := $(wildcard $(QBUSINESS_LAMBDA_DIR)/*.py) 104 | 105 | $(QBUSINESS_LAMBDA_ZIP): $(QBUSINESS_LAMBDA_DIR)/index.py 106 | @echo "[INFO] Creating qbusiness Lambda zip file" 107 | zip -r -j "$(OUT)/$(QBUSINESS_LAMBDA_ZIP)" $(QBUSINESS_LAMBDA_DIR) ; 108 | 109 | upload-qbusiness-lambda: 110 | @echo "[INFO] uploading qbusiness lambda" 111 | aws s3 cp --acl public-read \ 112 | "$(OUT)/$(QBUSINESS_LAMBDA_ZIP)" "s3://$(BOOTSTRAP_BUCKET_PATH)/$(QBUSINESS_LAMBDA_ZIP)" \ 113 | | tee -a "$(OUT)/$(@)" 114 | 115 | # files in this repo are bundled in a zip file to boostrap the codecommit repo 116 | SRC_ZIP := src-$(VERSION).zip 117 | SRC_FILES := $(shell git ls-files ..) 118 | $(SRC_ZIP): $(SRC_FILES) | $(OUT) 119 | @echo "[INFO] creating git repo archive" 120 | cd .. && git archive --format=zip HEAD > "build/$(OUT)/$(@)" 121 | 122 | upload-src-zip: $(SRC_ZIP) | $(OUT) 123 | @echo "[INFO] uploading git repo archive" 124 | aws s3 cp --acl public-read \ 125 | "$(OUT)/$(SRC_ZIP)" "s3://$(BOOTSTRAP_BUCKET_PATH)/$(SRC_ZIP)" \ 126 | | tee -a "$(OUT)/$(@)" 127 | 128 | RESPONSE_CARD_IMAGE := ../lex-web-ui/public/img/flowers.jpeg 129 | upload-response-card-image: $(RESPONSE_CARD_IMAGE) | $(OUT) 130 | @echo "[INFO] uploading response card image" 131 | aws s3 cp --acl public-read --content-type 'image/jpg' \ 132 | "$(RESPONSE_CARD_IMAGE)" "s3://$(BOOTSTRAP_BUCKET_PATH)/flowers.jpeg" \ 133 | | tee -a "$(OUT)/$(@)" 134 | 135 | clean: 136 | -rm -f $(OUT)/* 137 | .PHONY: clean 138 | -------------------------------------------------------------------------------- /build/create-custom-css.js: -------------------------------------------------------------------------------- 1 | const jsdom = require("jsdom"); 2 | const fs = require("fs"); 3 | const { JSDOM } = jsdom; 4 | 5 | function modifyRule(styleSheet, selector, props) { 6 | 7 | let rule = null; 8 | if(styleSheet.cssRules) { 9 | for(var cssrule of styleSheet.cssRules){ 10 | console.log(cssrule.cssText); 11 | if (cssrule.selectorText == selector) { 12 | rule = cssrule; 13 | } 14 | } 15 | } 16 | 17 | const propsArr = props.sup 18 | ? props.split(/\s*;\s*/).map(i => i.split(/\s*:\s*/)) // from string 19 | : Object.entries(props); // from Object 20 | 21 | if (rule) for (let [prop, val] of propsArr){ 22 | // rule.style[prop] = val; is against the spec, and does not support !important. 23 | rule.style.setProperty(prop, ...val.split(/ *!(?=important)/)); 24 | } 25 | else { 26 | if (!props.sup) { 27 | props = propsArr.reduce((str, [k, v]) => `${k}: ${v}`, ''); 28 | } 29 | console.log("Adding rule"); 30 | styleSheet.insertRule(`${selector} { ${props} }`, styleSheet.cssRules.length); 31 | const css = Array.from(document.styleSheets[document.styleSheets.length - 1].cssRules).map(rule => rule.cssText).join(' '); 32 | console.log(css); 33 | } 34 | return styleSheet; 35 | } 36 | 37 | // Reading the current CSS and adding it into an in-memory DOM object for easier manipulation 38 | var css_location = process.argv[2] 39 | var current_css = fs.readFileSync(css_location,{ encoding: 'utf8' }); 40 | const dom = new JSDOM(''); 41 | 42 | document = dom.window.document; 43 | styleSheet = document.styleSheets[document.styleSheets.length - 1]; 44 | 45 | if (process.env['MESSAGE_TEXT_COLOR'] && process.env['MESSAGE_TEXT_COLOR'].length > 0) { 46 | modifyRule(styleSheet, '.message-text', { color: process.env['MESSAGE_TEXT_COLOR'] + ' !important'}); 47 | } 48 | if (process.env['MESSAGE_FONT'] && process.env['MESSAGE_FONT'].length > 0) { 49 | modifyRule(styleSheet, '.message-text', { "font-family": process.env['MESSAGE_FONT'] + ' !important'}); 50 | } 51 | if (process.env['CHAT_BACKGROUND_COLOR'] && process.env['CHAT_BACKGROUND_COLOR'].length > 0) { 52 | modifyRule(styleSheet, '.message-list-container', { "background-color": process.env['CHAT_BACKGROUND_COLOR'] + ' !important'}); 53 | } 54 | if (process.env['TOOLBAR_COLOR'] && process.env['TOOLBAR_COLOR'].length > 0) { 55 | modifyRule(styleSheet, '.bg-red', { "background-color": process.env['TOOLBAR_COLOR'] + ' !important'}); 56 | } 57 | if (process.env['AGENT_CHAT_BUBBLE'] && process.env['AGENT_CHAT_BUBBLE'].length > 0) { 58 | modifyRule(styleSheet, '.message-bot .message-bubble', { "background-color": process.env['AGENT_CHAT_BUBBLE'] + ' !important'}); 59 | } 60 | if (process.env['CUSTOMER_CHAT_BUBBLE'] && process.env['CUSTOMER_CHAT_BUBBLE'].length > 0) { 61 | modifyRule(styleSheet, '.message-human .message-bubble', { "background-color": process.env['CUSTOMER_CHAT_BUBBLE'] + ' !important'}); 62 | } 63 | if (process.env['MINIMIZED_BUTTON_COLOR'] && process.env['MINIMIZED_BUTTON_COLOR'].length > 0) { 64 | modifyRule(styleSheet, 'button.min-button', { "background-color": process.env['MINIMIZED_BUTTON_COLOR'] + ' !important'}); 65 | } 66 | 67 | //Write the CSS back to the file (formatting will be changed if it had manual inputs but rules/properties should remain) 68 | const css = Array.from(styleSheet.cssRules).map(rule => rule.cssText).join('\r\n\r\n'); 69 | console.log(css); 70 | fs.writeFileSync(css_location, css) -------------------------------------------------------------------------------- /build/create-iframe-snippet-file.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is used at build time to generate a page with 4 | # config info and an HTML snippet showing how to load the 5 | # chatbot UI as an iframe 6 | # This is called from the main Makefile used by CodeBuild 7 | 8 | if [ -z "${IFRAME_SNIPPET_FILE}" ]; then 9 | echo "[ERROR] IFRAME_SNIPPET_FILE environment variable not defined" >&2 10 | exit 1 11 | fi 12 | 13 | if [ -z "${CLOUDFRONT_DOMAIN}" ]; then 14 | echo "[WARN] CLOUDFRONT_DOMAIN environment variable not defined" >&2 15 | WEBAPP_URL='' 16 | else 17 | WEBAPP_URL="https://${CLOUDFRONT_DOMAIN}" 18 | fi 19 | 20 | [ -z "${PARENT_ORIGIN}" ] && \ 21 | echo "[WARN] PARENT_ORIGIN environment variable not defined" >&2 22 | 23 | cat < ${IFRAME_SNIPPET_FILE} 24 | 25 | 26 | Lex Web UI Iframe Snippet 27 | 31 | 32 | 33 | 34 |

Lex Web UI Iframe Snippet

35 | 36 |

37 | Include the snippet listed below in your web page to embed the chatbot 38 | UI. The snippet loads the chatbot UI as an iframe using the config 39 | shown in the 40 | JSON File Config section below. 41 | The Origin Configuration 42 | section below shows the values of the iframe URL and parent 43 | 44 | origin set in the config. 45 |

46 | 47 |

Snippet

48 |
 49 | <script src="${WEBAPP_URL}/lex-web-ui-loader.min.js"></script>
 50 | <script>
 51 |   var loaderOpts = {
 52 |     baseUrl: '${WEBAPP_URL}/',
 53 |     shouldLoadMinDeps: true
 54 |   };
 55 |   var loader = new ChatBotUiLoader.IframeLoader(loaderOpts);
 56 |   var chatbotUiConfig = {
 57 |           /* Example of setting session attributes on parent page
 58 |           lex: {
 59 |             sessionAttributes: {
 60 |               userAgent: navigator.userAgent,
 61 |               QNAClientFilter: ''
 62 |             }
 63 |           }
 64 |           */
 65 |         };
 66 |   loader.load(chatbotUiConfig)
 67 |     .catch(function (error) { console.error(error); });
 68 | </script>
 69 |   
70 | 71 |

Origin Configuration

72 |

73 | The values of the iframeOrigin and 74 | parentOrigin config fields determine where the iframe 75 | is loaded from and the parent origin that is authorized to load 76 | the iframe. The JSON config file is set to use the iframe with the 77 | following values: 78 |

79 | 84 | 85 |

JSON File Config

86 |

87 | The JSON config file is fetched from: 88 | 89 | ${WEBAPP_URL}/lex-web-ui-loader-config.json 90 | . Here is its content: 91 |

92 |

 93 | 
 94 |   
115 | 
116 | 
117 | EOF
118 | 


--------------------------------------------------------------------------------
/build/release.sh:
--------------------------------------------------------------------------------
 1 | #! /bin/bash
 2 | timestamp=$(date +%s)
 3 | unamestr=$(uname)
 4 | export VERSION=v$(node -p "require('../package.json').version")
 5 | echo version is "$VERSION"
 6 | case $unamestr in
 7 | "Darwin" | "FreeBSD")
 8 | sed -i '' -e "s/(v.*)/($VERSION)/g" \
 9 | -e "s/Timestamp:.*/Timestamp: $timestamp/g" \
10 | -e "s/custom-resources-.*zip/custom-resources-$VERSION.zip/g" \
11 | -e "s/src-.*zip/src-$VERSION.zip/g" \
12 | -e "s/initiate-chat-lambda-.*zip/initiate-chat-lambda-$VERSION.zip/g" \
13 | ../templates/master.yaml;;
14 | 
15 | "Linux")
16 | sed -i -e "s/(v.*)/($VERSION)/g" \
17 | -e "s/Timestamp:.*/Timestamp: $timestamp/g" \
18 | -e "s/src-.*zip/src-$VERSION.zip/g" \
19 | -e "s/initiate-chat-lambda-.*zip/initiate-chat-lambda-$VERSION.zip/g" \
20 | -e "s/streaming-lambda-.*zip/streaming-lambda-$VERSION.zip/g" \
21 | -e "s/custom-resources-.*zip/custom-resources-$VERSION.zip/g" \
22 | -e "s/qbusiness-lambda-.*zip/qbusiness-lambda-$VERSION.zip/g" \
23 | ../templates/master.yaml;;
24 | 
25 | *)
26 | sed -i -e "s/(v.*)/($VERSION)/g" \
27 | -e "s/Timestamp:.*/Timestamp: $timestamp/g" \
28 | -e "s/src-.*zip/src-$VERSION.zip/g" \
29 | -e "s/initiate-chat-lambda-.*zip/initiate-chat-lambda-$VERSION.zip/g" \
30 | -e "s/streaming-lambda-.*zip/streaming-lambda-$VERSION.zip/g" \
31 | -e "s/custom-resources-.*zip/custom-resources-$VERSION.zip/g" \
32 | -e "s/qbusiness-lambda-.*zip/qbusiness-lambda-$VERSION.zip/g" \
33 | ../templates/master.yaml;;
34 | 
35 | esac
36 | cd ../lex-web-ui
37 | npm run build
38 | npm run build-dist
39 | cd .. 
40 | make
41 | cd build
42 | make "custom-resources-$VERSION.zip"
43 | make "initiate-chat-lambda-$VERSION.zip"
44 | make "streaming-lambda-$VERSION.zip"
45 | make "qbusiness-lambda-$VERSION.zip"
46 | cd ..
47 | cd dist
48 | make
49 | 
50 | 


--------------------------------------------------------------------------------
/build/upload-bootstrap.sh:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | # utility to manually bootstrap a bucket with source files
 3 | # this is intended for testing - use the Makefile for prod
 4 | export version=v$(node -p "require('../package.json').version")
 5 | echo version is $version
 6 | BUCKET=${BUCKET:-""}
 7 | BOOTSTRAP_BUCKET_PATH="${BUCKET}/artifacts"
 8 | 
 9 | [ "$BUCKET" ] || {
10 |   echo "[ERROR] bucket variable is not set"
11 |   exit 1
12 | }
13 | 
14 | if ! test -d out; then
15 | mkdir out
16 | fi
17 | 
18 | # assumes that it is running from build dir
19 | rm -f out/src-$version.zip
20 | # no longer removes custom-resources.zip - this is created in build using ./release.sh as a required step
21 | 
22 | pushd .
23 | cd ..
24 | git ls-files | xargs zip -u build/out/src-$version.zip
25 | popd
26 | aws s3 cp out/src-$version.zip \
27 |   "s3://${BOOTSTRAP_BUCKET_PATH}/src-$version.zip"
28 | 
29 | aws s3 cp out/custom-resources-$version.zip \
30 |   "s3://${BOOTSTRAP_BUCKET_PATH}/custom-resources-$version.zip"
31 | 
32 | aws s3 cp out/initiate-chat-lambda-$version.zip \
33 |   "s3://${BOOTSTRAP_BUCKET_PATH}/initiate-chat-lambda-$version.zip"
34 | 
35 | aws s3 cp out/streaming-lambda-$version.zip \
36 |   "s3://${BOOTSTRAP_BUCKET_PATH}/streaming-lambda-$version.zip"
37 | 
38 | aws s3 cp out/qbusiness-lambda-$version.zip \
39 |   "s3://${BOOTSTRAP_BUCKET_PATH}/qbusiness-lambda-$version.zip"
40 | 
41 | aws s3 sync --exclude "*" --include "*.yaml" \
42 |   ../templates "s3://${BOOTSTRAP_BUCKET_PATH}/templates/"
43 | 
44 | aws s3 cp ..templates/layers.zip \
45 |   "s3://${BOOTSTRAP_BUCKET_PATH}/layers.zip"
46 | 
47 | echo "[INFO] master template: https://s3.amazonaws.com/${BOOTSTRAP_BUCKET_PATH}/templates/master.yaml"
48 | 


--------------------------------------------------------------------------------
/config/base.env.js:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Base config common to builds
 3 |  */
 4 | module.exports = {
 5 |   region: process.env.AWS_DEFAULT_REGION,
 6 |   cognito: {
 7 |     poolId: process.env.POOL_ID,
 8 |     appUserPoolClientId: process.env.APP_USER_POOL_CLIENT_ID,
 9 |     appUserPoolName: process.env.APP_USER_POOL_NAME,
10 |     appDomainName: process.env.APP_DOMAIN_NAME,
11 |     aws_cognito_region: process.env.AWS_DEFAULT_REGION,
12 |     region: process.env.AWS_DEFAULT_REGION,
13 |   },
14 |   connect: {
15 |     contactFlowId : process.env.CONNECT_CONTACT_FLOW_ID,
16 |     instanceId : process.env.CONNECT_INSTANCE_ID,
17 |     apiGatewayEndpoint : process.env.CONNECT_API_GATEWAY_ENDPOINT,
18 |     promptForNameMessage: process.env.CONNECT_PROMPT_FOR_NAME_MESSAGE,
19 |     waitingForAgentMessage: process.env.CONNECT_WAIT_FOR_AGENT_MESSAGE,
20 |     waitingForAgentMessageIntervalSeconds: process.env.CONNECT_WAIT_FOR_AGENT_MESSAGE_INTERVAL_IN_SECONDS,
21 |     agentJoinedMessage: process.env.CONNECT_AGENT_JOINED_MESSAGE,
22 |     agentLeftMessage: process.env.CONNECT_AGENT_LEFT_MESSAGE,
23 |     chatEndedMessage: process.env.CONNECT_CHAT_ENDED_MESSAGE,
24 |     attachChatTranscript: process.env.CONNECT_ATTACH_CHAT_TRANSCRIPT,
25 |     liveChatTerms: process.env.CONNECT_LIVE_CHAT_TERMS,
26 |     endLiveChatUtterance: process.env.CONNECT_END_LIVE_CHAT_UTTERANCE,
27 |     transcriptMessageDelayInMsec: process.env.CONNECT_TRANSCRIPT_MESSAGE_DELAY_IN_MSEC,
28 |     transcriptRedactRegex: process.env.CONNECT_TRANSCRIPT_REDACT_REGEX,
29 |   },
30 |   lex: {
31 |     v2BotId: process.env.V2_BOT_ID,
32 |     v2BotAliasId: process.env.V2_BOT_ALIAS_ID,
33 |     v2BotLocaleId: process.env.V2_BOT_LOCALE_ID,
34 |     initialText: process.env.BOT_INITIAL_TEXT,
35 |     initialSpeechInstruction: process.env.BOT_INITIAL_SPEECH,
36 |     initialUtterance: process.env.BOT_INITIAL_UTTERANCE,
37 |     reInitSessionAttributesOnRestart: (process.env.REINIT_SESSION_ATTRIBUTES_ON_RESTART === undefined) ? undefined : (process.env.REINIT_SESSION_ATTRIBUTES_ON_RESTART === 'true') ? true : false,
38 |     region: process.env.AWS_DEFAULT_REGION,
39 |     retryOnLexPostTextTimeout: process.env.BOT_RETRY_ON_LEX_POST_TEXT_TIMEOUT,
40 |     retryCountPostTextTimeout: process.env.BOT_RETRY_COUNT_POST_TEXT_TIMEOUT,
41 |     allowStreamingResponses: (process.env.ALLOW_STREAMING_RESPONSES === undefined) ? undefined : (process.env.ALLOW_STREAMING_RESPONSES === 'true') ? true : false,
42 |     streamingWebSocketEndpoint: process.env.STREAMING_WEB_SOCKET_ENDPOINT,
43 |     streamingDynamoDbTable: process.env.STREAMING_DYNAMO_TABLE,
44 |   },
45 |   ui: {
46 |     parentOrigin: process.env.PARENT_ORIGIN,
47 |     toolbarTitle: process.env.UI_TOOLBAR_TITLE,
48 |     toolbarLogo: process.env.UI_TOOLBAR_LOGO,
49 |     toolbarStartLiveChatLabel: process.env.CONNECT_START_LIVE_CHAT_LABEL,
50 |     toolbarEndLiveChatLabel:  process.env.CONNECT_END_LIVE_CHAT_LABEL,
51 |     toolbarStartLiveChatIcon: process.env.CONNECT_START_LIVE_CHAT_ICON,
52 |     toolbarEndLiveChatIcon: process.env.CONNECT_END_LIVE_CHAT_ICON,
53 |     positiveFeedbackIntent: process.env.POSITIVE_INTENT,
54 |     negativeFeedbackIntent: process.env.NEGATIVE_INTENT,
55 |     helpIntent: process.env.HELP_INTENT,
56 |     minButtonToolTipContent: process.env.MIN_BUTTON_TOOLTIP_CONTENT,
57 |     minButtonContent: process.env.MIN_BUTTON_CONTENT,
58 |     avatarImageUrl: process.env.BOT_AVATAR_IMG_URL,
59 |     backButton: (process.env.BACK_BUTTON === undefined) ? undefined : (process.env.BACK_BUTTON === 'true') ? true : false,
60 |     messageMenu: (process.env.MESSAGE_MENU === undefined) ? undefined : (process.env.MESSAGE_MENU === 'true') ? true : false,
61 |     hideButtonMessageBubble: (process.env.HIDE_BUTTON_MESSAGE_BUBBLE === undefined) ? undefined : (process.env.HIDE_BUTTON_MESSAGE_BUBBLE === 'true') ? true : false,
62 |     enableLogin: (process.env.ENABLE_LOGIN === undefined) ? undefined : (process.env.ENABLE_LOGIN === 'true') ? true : false,
63 |     enableLiveChat: (process.env.ENABLE_LIVE_CHAT === undefined) ? undefined : (process.env.ENABLE_LIVE_CHAT === 'true') ? true : false,
64 |     forceLogin: (process.env.FORCE_LOGIN === undefined) ? undefined : (process.env.FORCE_LOGIN === 'true') ? true : false,
65 |     enableUpload: (process.env.ENABLE_UPLOAD === undefined) ? undefined : (process.env.ENABLE_UPLOAD === 'true') ? true : false,
66 |     uploadS3BucketName: process.env.UPLOAD_BUCKET_NAME,
67 |     AllowSuperDangerousHTMLInMessage: (process.env.ENABLE_MARKDOWN_SUPPORT === undefined) ? undefined : (process.env.ENABLE_MARKDOWN_SUPPORT === 'true') ? true : false,
68 |     shouldDisplayResponseCardTitle: (process.env.SHOW_RESPONSE_CARD_TITLE === undefined) ? undefined : (process.env.SHOW_RESPONSE_CARD_TITLE === 'true') ? true : false,
69 |     saveHistory:
70 |       process.env.SAVE_HISTORY === undefined
71 |         ? undefined
72 |         : process.env.SAVE_HISTORY === "true"
73 |         ? true
74 |         : false,
75 |   },
76 |   polly: {},
77 |   recorder: {},
78 |   iframe: {
79 |     iframeOrigin: process.env.IFRAME_ORIGIN,
80 |     shouldLoadIframeMinimized: (process.env.IFRAME_LOAD_MINIMIZED === undefined) ? undefined : (process.env.IFRAME_LOAD_MINIMIZED === 'true') ? true : false,
81 |   },
82 | };
83 | 


--------------------------------------------------------------------------------
/config/dist.env.js:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Config used when deploying the app using the pre-built dist library
 3 |  */
 4 | const path = require('path');
 5 | 
 6 | const mergeConfig = require('./utils/merge-config');
 7 | const baseConfig = require('./base.env');
 8 | 
 9 | const loaderConfigFileName =
10 |   process.env.LOADER_CONFIG ||
11 |   path.resolve(__dirname, '../src/config/lex-web-ui-loader-config.json');
12 | const loaderConfig = require(loaderConfigFileName);
13 | 
14 | 
15 | const currentConfigFileName = path.resolve(__dirname, '../' + process.env.CURRENT_CONFIG_FILE);
16 | const currentConfig = require(currentConfigFileName);
17 | /* merge currentConfig with loader default config*/
18 | if (currentConfig['connect'] === undefined) {
19 |   console.log(`adding connect to currentConfig`);
20 |   currentConfig['connect'] = {};
21 |   console.log(`new currentConfig ${JSON.stringify(currentConfig)}`);
22 | }
23 | const userConfig = mergeConfig(currentConfig, baseConfig);
24 | 
25 | module.exports = {
26 |   appPreBuilt: {
27 |     file: loaderConfigFileName,
28 |     conf: mergeConfig(userConfig, baseConfig),
29 |   },
30 | };
31 | 


--------------------------------------------------------------------------------
/config/env.mk:
--------------------------------------------------------------------------------
 1 | # This environment file is sourced from the Makefiles in this project
 2 | # it is in Makefile format (not shell)
 3 | 
 4 | # bucket name and prefix path used to store templates, data, scripts and
 5 | # build artifacts
 6 | # NOTE: S3 path should match the BootstrapBucket and BootstrapPrefix parameters
 7 | # in master.yaml template
 8 | export BOOTSTRAP_BUCKET_PATH ?= aws-bigdata-blog/artifacts/aws-lex-web-ui/artifacts
 9 | 
10 | # S3 bucket hosting the web application
11 | # The Makefile in the root dir can sync the local files to it
12 | export WEBAPP_BUCKET ?= $()
13 | 
14 | # AWS cli env variables used when running/building
15 | # Override by setting it in the environment before running make
16 | export AWS_DEFAULT_PROFILE ?= default
17 | export AWS_DEFAULT_REGION ?= us-east-1
18 | 
19 | # lex-web-ui config variables
20 | export BOT_NAME ?= OrderFlowers
21 | # set to empty if not present in environment
22 | export POOL_ID ?= $()
23 | 
24 | # amazon-connect config variables
25 | export CONNECT_CONTACT_FLOW_ID ?= $()
26 | export CONNECT_INSTANCE_ID ?= $()
27 | export CONNECT_API_GATEWAY_ENDPOINT ?= $()
28 | export CONNECT_WAIT_FOR_AGENT_MESSAGE ?= $()
29 | export CONNECT_PROMPT_FOR_NAME_MESSAGE ?= $()
30 | export CONNECT_WAIT_FOR_AGENT_MESSAGE_INTERVAL_IN_SECONDS ?= $()
31 | export CONNECT_AGENT_JOINED_MESSAGE ?= $()
32 | export CONNECT_AGENT_LEFT_MESSAGE ?= $()
33 | export CONNECT_CHAT_ENDED_MESSAGE ?= $()
34 | export CONNECT_ATTACH_CHAT_TRANSCRIPT ?= $()
35 | export CONNECT_START_LIVE_CHAT_LABEL ?= $()
36 | export CONNECT_START_LIVE_CHAT_ICON ?= $()
37 | export CONNECT_END_LIVE_CHAT_LABEL ?= $()
38 | export CONNECT_END_LIVE_CHAT_ICON ?= $()
39 | export CONNECT_END_LIVE_CHAT_UTTERANCE ?= $()
40 | export CONNECT_TRANSCRIPT_MESSAGE_DELAY_IN_MSEC ?= $()
41 | export CONNECT_TRANSCRIPT_REDACT_REGEX ?= $()
42 | export BOT_INITIAL_TEXT ?= $()
43 | export BOT_INITIAL_SPEECH ?= $()
44 | export BOT_INITIAL_UTTERANCE ?= $()
45 | export UI_TOOLBAR_TITLE ?= $()
46 | export UI_TOOLBAR_LOGO ?= $()
47 | export HIDE_BUTTON_MESSAGE_BUBBLE ?= $()
48 | export MESSAGE_MENU ?= $()
49 | export BACK_BUTTON ?= $()
50 | export MIN_BUTTON_CONTENT ?= $()
51 | 
52 | export IFRAME_ORIGIN ?= $()
53 | export PARENT_ORIGIN ?= $()
54 | 
55 | export VERSION := v$(shell node -p "const fs=require('fs');const path='./package.json';fs.existsSync(path)?require('./package.json').version : require('../package.json').version")
56 | 


--------------------------------------------------------------------------------
/config/full.env.js:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Configs to be updated when performing a full build from source
 3 |  * This module exports an object with a key for each config file
 4 |  * Each key contains the file name and the associated config object
 5 |  */
 6 | 
 7 | const path = require('path');
 8 | 
 9 | const mergeConfig = require('./utils/merge-config');
10 | const baseConfig = require('./base.env');
11 | 
12 | // config files to update
13 | const confFileNames = {
14 |   appProd:
15 |     process.env.WEBAPP_CONFIG_PROD ||
16 |     path.resolve(__dirname, '../lex-web-ui/src/config/config.prod.json'),
17 | 
18 |   appDev:
19 |     process.env.WEBAPP_CONFIG_DEV ||
20 |     path.resolve(__dirname, '../lex-web-ui/src/config/config.dev.json'),
21 | 
22 |   loader:
23 |     process.env.LOADER_CONFIG ||
24 |     path.resolve(__dirname, '../src/config/lex-web-ui-loader-config.json'),
25 | };
26 | 
27 | const appProdConfig = require(confFileNames.appProd);
28 | const appDevConfig = require(confFileNames.appDev);
29 | const loaderConfig = require(confFileNames.loader);
30 | 
31 | module.exports = {
32 |   loader: {
33 |     file: confFileNames.loader,
34 |     conf: mergeConfig(loaderConfig, baseConfig),
35 |   },
36 |   appProd: {
37 |     file: confFileNames.appProd,
38 |     conf: mergeConfig(appProdConfig, baseConfig),
39 |   },
40 |   appDev: {
41 |     file: confFileNames.appDev,
42 |     conf: mergeConfig(appDevConfig, baseConfig),
43 |   },
44 | };
45 | 


--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Provides a config object depending on the build target
 3 |  * Used by ../build/update-lex-web-ui-config.js
 4 |  */
 5 | 
 6 | // controls whether to load the config for the pre-built version or
 7 | // the full build
 8 | const buildType = (process.env.BUILD_TYPE) ? process.env.BUILD_TYPE : 'dist';
 9 | const validBuildTypes = ['dist', 'full'];
10 | 
11 | if (!validBuildTypes.includes(buildType)) {
12 |   throw new Error(`invalid build type: ${buildType}`);
13 | }
14 | 
15 | module.exports = require(`./${buildType}.env`);
16 | 


--------------------------------------------------------------------------------
/config/utils/merge-config.js:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Merges config objects. The initial set of keys to merge are driven by
 3 |  * the baseConfig. The srcConfig values override the baseConfig ones
 4 |  * unless the srcConfig value is empty
 5 |  */
 6 | module.exports = function mergeConfig(baseConfig, srcConfig) {
 7 |   function isEmpty(data) {
 8 |     if(typeof(data) === 'number' || typeof(data) === 'boolean') {
 9 |       return false;
10 |     }
11 |     if(typeof(data) === 'undefined' || data === null) {
12 |       return true;
13 |     }
14 |     if(typeof(data.length) !== 'undefined') {
15 |       return data.length === 0;
16 |     }
17 |     return Object.keys(data).length === 0;
18 |   }
19 | 
20 |   // use the baseConfig first level keys as the base for merging
21 |   return Object.keys(baseConfig)
22 |     .map(function (key) {
23 |       let mergedConfig = {};
24 |       let value = baseConfig[key];
25 |       // merge from source if its value is not empty
26 |       // allow merging of emtpy values from helpIntent, positiveFeedbackIntent, and negativeFeedbackIntent
27 |       if (key in srcConfig ) {
28 |         if (key==='helpIntent' ||
29 |             key==='positiveFeedbackIntent' ||
30 |             key=== 'negativeFeedbackIntent' ||
31 |             key=== 'initialUtterance' ||
32 |             key=== 'minButtonContent' ||
33 |             key=== 'initialText' ||
34 |             key=== 'avatarImageUrl' ||
35 |             key=== 'toolbarLogo' ||
36 |             key=== 'streamingWebSocketEndpoint' ||
37 |             key=== 'streamingDynamoDbTable' ||
38 |             !isEmpty(srcConfig[key]) ) {
39 |             value = (typeof (baseConfig[key]) === 'object') ?
40 |                 // recursively merge sub-objects in both directions
41 |                 Object.assign(
42 |                     mergeConfig(srcConfig[key], baseConfig[key]),
43 |                     mergeConfig(baseConfig[key], srcConfig[key]),
44 |                 ) :
45 |                 srcConfig[key];
46 |         }
47 |       }
48 |       mergedConfig[key] = value;
49 |       return mergedConfig;
50 |     })
51 |     .reduce(function (merged, configItem) {
52 |         return Object.assign({}, merged, configItem);
53 |       },
54 |       {}
55 |     );
56 | };
57 | 


--------------------------------------------------------------------------------
/dist/Makefile:
--------------------------------------------------------------------------------
 1 | all: copy-bundle build-loader
 2 | .PHONY: all
 3 | 
 4 | # build the application bundle
 5 | WEB_UI_DIR := ../lex-web-ui
 6 | WEB_UI_SRC_FILES := $(shell git ls-files $(WEB_UI_DIR)/src)
 7 | WEB_UI_BUNDLE_DIR := $(WEB_UI_DIR)/dist/bundle
 8 | LIBRARY_SRC_FILES := $(wildcard $(WEB_UI_BUNDLE_DIR)/lex-web-ui.*)
 9 | LIBRARY_SRC_FILES += $(wildcard $(WEB_UI_BUNDLE_DIR)/*-worker.*)
10 | LIBRARY_FILES := $(patsubst $(WEB_UI_BUNDLE_DIR)/%,%,$(LIBRARY_SRC_FILES))
11 | 
12 | # build the application bundle
13 | $(LIBRARY_SRC_FILES): $(WEB_UI_SRC_FILES)
14 | 	@echo "[INFO] Building from dir [$(WEB_UI_DIR)]"
15 | 	cd $(WEB_UI_DIR) && npm run build-dist
16 | 
17 | # copy library files to dist dir
18 | $(LIBRARY_FILES): $(LIBRARY_SRC_FILES)
19 | 	@echo "[INFO] Copying library files"
20 | 	cp $(?) .
21 | 
22 | copy-bundle: $(LIBRARY_SRC_FILES) $(LIBRARY_FILES)
23 | 
24 | # copy website bot loader files from source to dist dir
25 | SRC_DIR := ../src
26 | LOADER_SRC_DIR := $(SRC_DIR)/lex-web-ui-loader
27 | LOADER_SRC_DEPENDENCIES := $(wildcard $(SRC_DIR)/dependencies/*.*)
28 | LOADER_SRC_JS_FILES := \
29 | 	$(wildcard $(LOADER_SRC_DIR)/js/*.js) \
30 | 	$(wildcard $(LOADER_SRC_DIR)/lib/*.js)
31 | LOADER_SRC_CSS_FILES := $(wildcard $(LOADER_SRC_DIR)/css/*.css)
32 | LOADER_SRC_FILES := $(LOADER_SRC_JS_FILES) $(LOADER_SRC_CSS_FILES)
33 | 
34 | LOADER_SRC_BASE_NAME := lex-web-ui-loader
35 | LOADER_TARGET_PROD_FILES := \
36 | 	$(LOADER_SRC_BASE_NAME).min.js \
37 | 	$(LOADER_SRC_BASE_NAME).min.js.map \
38 | 	$(LOADER_SRC_BASE_NAME).min.css \
39 | 	$(LOADER_SRC_BASE_NAME).min.css.map
40 | LOADER_TARGET_DEV_FILES := $(subst .min,,$(LOADER_TARGET_PROD_FILES))
41 | 
42 | $(LOADER_TARGET_PROD_FILES): $(LOADER_SRC_FILES)
43 | 	@echo "[INFO] building loader prod library files"
44 | 	npm run build-prod
45 | build-loader-prod: $(LOADER_TARGET_PROD_FILES)
46 | 
47 | $(LOADER_TARGET_DEV_FILES): $(LOADER_SRC_FILES)
48 | 	@echo "[INFO] building loader dev library files"
49 | 	npm run build-dev
50 | build-loader-dev: $(LOADER_TARGET_DEV_FILES)
51 | 
52 | # copy dependency files to dist dir
53 | copy-dependencies: $(LOADER_SRC_DEPENDENCIES)
54 | 	@echo "[INFO] Copying library files"
55 | 	cp $(?) .
56 | 
57 | build-loader: build-loader-dev build-loader-prod copy-dependencies
58 | 
59 | 
60 | clean:
61 | 	-rm -f ./*.{css,js,json,html,map,yml,zip}
62 | .PHONY: clean
63 | 


--------------------------------------------------------------------------------
/dist/custom-chatbot-style.css:
--------------------------------------------------------------------------------
 1 | /* Example custom css file for lex-web-ui. Entire file is commented out as a default. Uncomment and
 2 |  adjust as needed.
 3 | 
 4 | .toolbar.theme--dark {
 5 |     background-color: #2b2b2b !important;
 6 | }
 7 | 
 8 | .toolbar__title {
 9 |   font-family:"Sans-serif" !important;
10 |   font-size: 1.875em !important;
11 |   color: #ffffff !important;
12 | }
13 | 
14 | .message-list-container {
15 |   background-color: #dcdbdc !important
16 | }
17 | 
18 | .message-bot .message-bubble {
19 |     background-color: #eeedeb !important;
20 | }
21 | 
22 | .message-human .message-bubble {
23 |     background-color: #afcffa !important;
24 | }
25 | 
26 | .message-bubble p {
27 |     margin-bottom: 8px;
28 | }
29 | 
30 | .message-bubble p:last-child {
31 |     margin-bottom: 0px;
32 | }
33 | 
34 | .message-bubble .message-text {
35 |     padding-left:  0;
36 |     padding-right: 0;
37 |     line-height: 1.6;
38 |     font-size: 1rem;
39 | }
40 | 
41 | .message-bubble {
42 |     border-radius: 10px !important;
43 |     padding: 2px 18px !important;
44 | }
45 | 
46 | .message-text {
47 |   color: #000000;
48 |   width: 100%;
49 | }
50 | 
51 | .headline {
52 |     font-size: 1.2rem !important;
53 |     line-height: 1.4 !important;
54 | }
55 | 
56 | .card__title {
57 |     padding: 10px 16px !important;
58 | }
59 | 
60 | .card__text {
61 |     padding: 8px 16px 16px !important;
62 |     line-height: 1.4;
63 | }
64 | 
65 | .card__title.card__title--primary {
66 |     background-color: #eeedeb !important;
67 | }
68 | 
69 | 
70 | .input-group--text-field input,
71 | .input-group--text-field textarea,
72 | .input-group--text-field label {
73 |     font-size: 14px !important;
74 | }
75 | 
76 | .card__actions .btn {
77 |   margin: 4px 4px !important;
78 |   font-size: 1em !important;
79 |   min-width: 44px !important;
80 |   background-color: #afcffa !important;
81 | }
82 | 
83 | */
84 | 
85 | button.min-button {
86 |     border-radius: 60px;
87 | }
88 | 
89 | .message-button {
90 |     display: none;
91 | }


--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     
16 |     
17 |     LexWebUi
18 |     
19 |     
20 | 
21 |     
27 |     
28 | 
29 |     
30 |   
31 |   
32 |     
41 |     
42 | 
43 |     
47 |     
48 |   
49 | 
50 | 


--------------------------------------------------------------------------------
/dist/initiate-loader.js:
--------------------------------------------------------------------------------
 1 | // In the most simple form, you can load the component in a single statement:
 2 | //   new ChatBotUiLoader.FullPageLoader().load();
 3 | 
 4 | // The script below break the process into parts to further illustrate
 5 | // the load process.
 6 | 
 7 | // The ChatBotUiLoader variable contains the FullPageLoader field which is a
 8 | // constructor for the loader.
 9 | var Loader = ChatBotUiLoader.FullPageLoader;
10 | 
11 | // The loader constructor supports various configurable options used to
12 | // control how the component configuration and dependencies are retrieved.
13 | // In this case, we are just passing one option (which doesn't changethe
14 | // default) for illustration purposes.
15 | var loaderOpts = {
16 |   // The following option controls if the local config should be ignored
17 |   // when running this page embedded in an iframe.
18 |   // If set to true, only passes the parentOrigin field when run as an
19 |   // iframe and delegates the config to the parent
20 |   shouldIgnoreConfigWhenEmbedded: true,
21 | 
22 |   // Controls if it should load minimized production dependecies
23 |   // defaults to true for production builds and false in development
24 |   shouldLoadMinDeps: true,
25 | };
26 | 
27 | // Instantiate the loader by optionally passing the loader options to
28 | // control its behavior. You may leave the options empty if you wish
29 | // to take the defaults which works in most cases.
30 | var loader = new Loader(loaderOpts);
31 | 
32 | // When loading the chatbot UI component, you can optionally pass it a
33 | // configuration object
34 | var chatbotUiConfig = {
35 |   lex: {
36 |     sessionAttributes: {
37 |       /* QNAClientFilter: '', */
38 |       userAgent: navigator.userAgent
39 |     }
40 |   }
41 | };
42 | 
43 | // Calling the load function of the loader does a few things:
44 | //   1. Loads JavaScript and CSS dependencies to the DOM
45 | //   2. Loads the chatbot UI configuration from various sources
46 | //       (e.g. JSON file, event)
47 | //   3. Instantiates the chatbot UI component in the DOM
48 | loader
49 |   .load(chatbotUiConfig)
50 |   .then(function () { console.log('ChatBotUiLoader loaded'); })
51 |   .catch(function (error) { console.error(error); });


--------------------------------------------------------------------------------
/dist/lex-web-ui-loader.css:
--------------------------------------------------------------------------------
  1 | /*!***************************************************************************************!*\
  2 |   !*** css ../../../node_modules/css-loader/dist/cjs.js!../css/lex-web-ui-fullpage.css ***!
  3 |   \***************************************************************************************/
  4 | #lex-web-ui-fullpage {
  5 |   height: 100%;
  6 |   width: 100%;
  7 | }
  8 | 
  9 | /*!*************************************************************************************!*\
 10 |   !*** css ../../../node_modules/css-loader/dist/cjs.js!../css/lex-web-ui-iframe.css ***!
 11 |   \*************************************************************************************/
 12 | .lex-web-ui-iframe {
 13 |   bottom: 1.5rem;
 14 |   display: none; /* hidden by default changed once iframe is loaded */
 15 |   margin-bottom: 0px;
 16 |   margin-left: 2px;
 17 |   margin-right: 3vw;
 18 |   margin-top: 2px;
 19 |   max-width: 66vw;
 20 |   height: 80vh; /* dynamically changed on iframe maximize/minimize */
 21 |   min-width: calc(50vw - 3vw); /* half viewport width minus margin right */
 22 |   position: fixed;
 23 |   right: 0;
 24 |   z-index: 2147483637; /* max z-index (2147483647) - 10 */
 25 | }
 26 | 
 27 | .lex-web-ui-iframe iframe {
 28 |   box-shadow: 0 15px 50px 0 rgba(0, 0, 0, 0.4);
 29 |   border-radius: 10px;
 30 | }
 31 | 
 32 | .lex-web-ui-iframe--show {
 33 |   display: flex;
 34 | }
 35 | 
 36 | .lex-web-ui-iframe--minimize {
 37 |   max-width: 190px !important;
 38 |   max-height: 85px !important;
 39 |   border-radius: 85px !important;
 40 |   min-width: 190px !important;
 41 | }
 42 | 
 43 | /* disable box shadow when minimized */
 44 | .lex-web-ui-iframe.lex-web-ui-iframe--minimize iframe {
 45 |   box-shadow: none;
 46 |   border-radius: none;
 47 | }
 48 | 
 49 | /* hide on very small resolutions */
 50 | @media only screen and (max-width: 240px),
 51 | only screen and (max-height: 256px)
 52 | {
 53 |   .lex-web-ui-iframe {
 54 |     display: none!important;
 55 |   }
 56 | 
 57 |   .lex-web-ui-iframe--minimize {
 58 |     max-width: 300px !important;
 59 |     max-height: 85px !important;
 60 |   }
 61 | }
 62 | /* take most space on small resolutions (smart phones) */
 63 | @media only screen
 64 | and (min-width: 241px)
 65 | and (max-width: 480px) {
 66 |   .lex-web-ui-iframe {
 67 |     min-width: 96vw;
 68 |     height: 84vh;
 69 |     margin-right: 2vw;
 70 |     align-self: center;
 71 |   }
 72 | 
 73 |   .lex-web-ui-iframe--minimize {
 74 |     max-width: 190px !important;
 75 |     max-height: 85px !important;
 76 |     border-radius: 85px !important;
 77 |   }
 78 | 
 79 | }
 80 | 
 81 | /* adjust down on medium resolutions */
 82 | @media only screen
 83 | and (min-width: 481px)
 84 | and (max-width: 960px) {
 85 |   .lex-web-ui-iframe {
 86 |     min-width: 90vw;
 87 |   }
 88 | 
 89 |   .lex-web-ui-iframe.lex-web-ui-iframe--show.lex-web-ui-iframe--minimize {
 90 |     max-width: 300px !important;
 91 |     max-height: 85px !important;
 92 |     border-radius: 85px !important;
 93 |     min-width: 85px !important;
 94 |   }
 95 | }
 96 | 
 97 | .lex-web-ui-iframe iframe {
 98 |   overflow: hidden;
 99 |   width: 100%;
100 |   height: 100%;
101 | }
102 | 
103 | 
104 | /*# sourceMappingURL=lex-web-ui-loader.css.map*/


--------------------------------------------------------------------------------
/dist/lex-web-ui-loader.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"lex-web-ui-loader.css","mappings":";;;AAAA;AACA;AACA;AACA;;;;;ACHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;;AAGA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;;AAGA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA","sources":["webpack://ChatBotUiLoader/../css/lex-web-ui-fullpage.css","webpack://ChatBotUiLoader/../css/lex-web-ui-iframe.css"],"sourcesContent":["#lex-web-ui-fullpage {\n  height: 100%;\n  width: 100%;\n}\n",".lex-web-ui-iframe {\n  bottom: 1.5rem;\n  display: none; /* hidden by default changed once iframe is loaded */\n  margin-bottom: 0px;\n  margin-left: 2px;\n  margin-right: 3vw;\n  margin-top: 2px;\n  max-width: 66vw;\n  height: 80vh; /* dynamically changed on iframe maximize/minimize */\n  min-width: calc(50vw - 3vw); /* half viewport width minus margin right */\n  position: fixed;\n  right: 0;\n  z-index: 2147483637; /* max z-index (2147483647) - 10 */\n}\n\n.lex-web-ui-iframe iframe {\n  box-shadow: 0 15px 50px 0 rgba(0, 0, 0, 0.4);\n  border-radius: 10px;\n}\n\n.lex-web-ui-iframe--show {\n  display: flex;\n}\n\n.lex-web-ui-iframe--minimize {\n  max-width: 190px !important;\n  max-height: 85px !important;\n  border-radius: 85px !important;\n  min-width: 190px !important;\n}\n\n/* disable box shadow when minimized */\n.lex-web-ui-iframe.lex-web-ui-iframe--minimize iframe {\n  box-shadow: none;\n  border-radius: none;\n}\n\n/* hide on very small resolutions */\n@media only screen and (max-width: 240px),\nonly screen and (max-height: 256px)\n{\n  .lex-web-ui-iframe {\n    display: none!important;\n  }\n\n  .lex-web-ui-iframe--minimize {\n    max-width: 300px !important;\n    max-height: 85px !important;\n  }\n}\n/* take most space on small resolutions (smart phones) */\n@media only screen\nand (min-width: 241px)\nand (max-width: 480px) {\n  .lex-web-ui-iframe {\n    min-width: 96vw;\n    height: 84vh;\n    margin-right: 2vw;\n    align-self: center;\n  }\n\n  .lex-web-ui-iframe--minimize {\n    max-width: 190px !important;\n    max-height: 85px !important;\n    border-radius: 85px !important;\n  }\n\n}\n\n/* adjust down on medium resolutions */\n@media only screen\nand (min-width: 481px)\nand (max-width: 960px) {\n  .lex-web-ui-iframe {\n    min-width: 90vw;\n  }\n\n  .lex-web-ui-iframe.lex-web-ui-iframe--show.lex-web-ui-iframe--minimize {\n    max-width: 300px !important;\n    max-height: 85px !important;\n    border-radius: 85px !important;\n    min-width: 85px !important;\n  }\n}\n\n.lex-web-ui-iframe iframe {\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n}\n"],"names":[],"sourceRoot":""}


--------------------------------------------------------------------------------
/dist/lex-web-ui-loader.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * lex-web-ui v0.23.0
3 | * (c) 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4 | * Released under the Amazon Software License.
5 | */  
6 | #lex-web-ui-fullpage{height:100%;width:100%}
7 | .lex-web-ui-iframe{bottom:1.5rem;display:none;height:80vh;margin:2px 3vw 0 2px;max-width:66vw;min-width:47vw;position:fixed;right:0;z-index:2147483637}.lex-web-ui-iframe iframe{border-radius:10px;box-shadow:0 15px 50px 0 #0006}.lex-web-ui-iframe--show{display:flex}.lex-web-ui-iframe--minimize{border-radius:85px!important;max-height:85px!important;max-width:190px!important;min-width:190px!important}.lex-web-ui-iframe.lex-web-ui-iframe--minimize iframe{border-radius:none;box-shadow:none}@media only screen and (max-height:256px),only screen and (max-width:240px){.lex-web-ui-iframe{display:none!important}.lex-web-ui-iframe--minimize{max-height:85px!important;max-width:300px!important}}@media only screen and (min-width:241px) and (max-width:480px){.lex-web-ui-iframe{align-self:center;height:84vh;margin-right:2vw;min-width:96vw}.lex-web-ui-iframe--minimize{border-radius:85px!important;max-height:85px!important;max-width:190px!important}}@media only screen and (min-width:481px) and (max-width:960px){.lex-web-ui-iframe{min-width:90vw}.lex-web-ui-iframe.lex-web-ui-iframe--show.lex-web-ui-iframe--minimize{border-radius:85px!important;max-height:85px!important;max-width:300px!important;min-width:85px!important}}.lex-web-ui-iframe iframe{height:100%;overflow:hidden;width:100%}
8 | 
9 | /*# sourceMappingURL=lex-web-ui-loader.min.css.map*/


--------------------------------------------------------------------------------
/dist/lex-web-ui-loader.min.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"lex-web-ui-loader.min.css","mappings":";;;;;AAAA,qBACE,WAAY,CACZ,UACF,C;ACHA,mBACE,aAAc,CACd,YAAa,CAMb,WAAY,CAFZ,oBAAe,CACf,cAAe,CAEf,cAA2B,CAC3B,cAAe,CACf,OAAQ,CACR,kBACF,CAEA,0BAEE,kBAAmB,CADnB,8BAEF,CAEA,yBACE,YACF,CAEA,6BAGE,4BAA8B,CAD9B,yBAA2B,CAD3B,yBAA2B,CAG3B,yBACF,CAGA,sDAEE,kBAAmB,CADnB,eAEF,CAGA,4EAGE,mBACE,sBACF,CAEA,6BAEE,yBAA2B,CAD3B,yBAEF,CACF,CAEA,+DAGE,mBAIE,iBAAkB,CAFlB,WAAY,CACZ,gBAAiB,CAFjB,cAIF,CAEA,6BAGE,4BAA8B,CAD9B,yBAA2B,CAD3B,yBAGF,CAEF,CAGA,+DAGE,mBACE,cACF,CAEA,uEAGE,4BAA8B,CAD9B,yBAA2B,CAD3B,yBAA2B,CAG3B,wBACF,CACF,CAEA,0BAGE,WAAY,CAFZ,eAAgB,CAChB,UAEF,C","sources":["webpack://ChatBotUiLoader/../css/lex-web-ui-fullpage.css","webpack://ChatBotUiLoader/../css/lex-web-ui-iframe.css"],"sourcesContent":["#lex-web-ui-fullpage {\n  height: 100%;\n  width: 100%;\n}\n",".lex-web-ui-iframe {\n  bottom: 1.5rem;\n  display: none; /* hidden by default changed once iframe is loaded */\n  margin-bottom: 0px;\n  margin-left: 2px;\n  margin-right: 3vw;\n  margin-top: 2px;\n  max-width: 66vw;\n  height: 80vh; /* dynamically changed on iframe maximize/minimize */\n  min-width: calc(50vw - 3vw); /* half viewport width minus margin right */\n  position: fixed;\n  right: 0;\n  z-index: 2147483637; /* max z-index (2147483647) - 10 */\n}\n\n.lex-web-ui-iframe iframe {\n  box-shadow: 0 15px 50px 0 rgba(0, 0, 0, 0.4);\n  border-radius: 10px;\n}\n\n.lex-web-ui-iframe--show {\n  display: flex;\n}\n\n.lex-web-ui-iframe--minimize {\n  max-width: 190px !important;\n  max-height: 85px !important;\n  border-radius: 85px !important;\n  min-width: 190px !important;\n}\n\n/* disable box shadow when minimized */\n.lex-web-ui-iframe.lex-web-ui-iframe--minimize iframe {\n  box-shadow: none;\n  border-radius: none;\n}\n\n/* hide on very small resolutions */\n@media only screen and (max-width: 240px),\nonly screen and (max-height: 256px)\n{\n  .lex-web-ui-iframe {\n    display: none!important;\n  }\n\n  .lex-web-ui-iframe--minimize {\n    max-width: 300px !important;\n    max-height: 85px !important;\n  }\n}\n/* take most space on small resolutions (smart phones) */\n@media only screen\nand (min-width: 241px)\nand (max-width: 480px) {\n  .lex-web-ui-iframe {\n    min-width: 96vw;\n    height: 84vh;\n    margin-right: 2vw;\n    align-self: center;\n  }\n\n  .lex-web-ui-iframe--minimize {\n    max-width: 190px !important;\n    max-height: 85px !important;\n    border-radius: 85px !important;\n  }\n\n}\n\n/* adjust down on medium resolutions */\n@media only screen\nand (min-width: 481px)\nand (max-width: 960px) {\n  .lex-web-ui-iframe {\n    min-width: 90vw;\n  }\n\n  .lex-web-ui-iframe.lex-web-ui-iframe--show.lex-web-ui-iframe--minimize {\n    max-width: 300px !important;\n    max-height: 85px !important;\n    border-radius: 85px !important;\n    min-width: 85px !important;\n  }\n}\n\n.lex-web-ui-iframe iframe {\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n}\n"],"names":[],"sourceRoot":""}


--------------------------------------------------------------------------------
/example-css/bright-yellow.css:
--------------------------------------------------------------------------------
 1 | /* ========================================
 2 |    BRIGHT YELLOW THEME
 3 |    Bright yellow theme for energetic environments
 4 |    ======================================== */
 5 | 
 6 | /* Toolbar */
 7 | .bg-red {
 8 |   background-color: #ca8a04 !important;
 9 | }
10 | 
11 | .toolbar__title {
12 |   font-family: "Arial", sans-serif !important;
13 |   font-size: 16px !important;
14 |   color: #ffffff !important;
15 | }
16 | 
17 | /* Minimized button */
18 | button.min-button {
19 |   background-color: #ca8a04 !important;
20 |   border-color: #ca8a04 !important;
21 | }
22 | 
23 | /* Message container */
24 | .message-list-container {
25 |   background-color: #fefce8 !important;
26 | }
27 | 
28 | /* Bot messages */
29 | .message-bot .message-bubble {
30 |   background-color: #fef3c7 !important;
31 | }
32 | 
33 | /* Human messages */
34 | .message-human .message-bubble {
35 |   background-color: #ca8a04 !important;
36 |   color: #ffffff !important;
37 | }
38 | 
39 | /* Response card buttons */
40 | .v-card-actions .v-btn {
41 |   background-color: #eab308 !important;
42 |   color: #ffffff !important;
43 | }
44 | 
45 | /* Common styling */
46 | .message-bubble {
47 |   border-radius: 12px !important;
48 |   padding: 8px 16px !important;
49 | }
50 | 
51 | .message-bubble p {
52 |   margin-bottom: 8px;
53 | }
54 | 
55 | .message-bubble p:last-child {
56 |   margin-bottom: 0px;
57 | }
58 | 
59 | .message-bubble .message-text {
60 |   padding-left: 0;
61 |   padding-right: 0;
62 |   line-height: 1.6;
63 |   font-size: 1rem;
64 | }
65 | 
66 | .input-group--text-field input,
67 | .input-group--text-field textarea,
68 | .input-group--text-field label {
69 |   font-size: 14px !important;
70 | }
71 | 
72 | .v-card-actions .v-btn {
73 |   margin: 4px 4px !important;
74 |   font-size: 1em !important;
75 |   min-width: 44px !important;
76 |   border-radius: 8px !important;
77 | }
78 | 
79 | button.end-live-chat-button.btn {
80 |   color: white !important;
81 |   background-color: #dc2626 !important;
82 | }
83 | 
84 | .lex-web-ui-iframe {
85 |   min-width: 25vw !important;
86 |   max-height: 315px !important;
87 |   margin-right: 10vw !important;
88 |   margin-bottom: 10px !important;
89 | }


--------------------------------------------------------------------------------
/example-css/coral-pink.css:
--------------------------------------------------------------------------------
 1 | /* ========================================
 2 |    CORAL PINK THEME
 3 |    Warm coral pink theme for friendly interfaces
 4 |    ======================================== */
 5 | 
 6 | /* Toolbar */
 7 | .bg-red {
 8 |   background-color: #e11d48 !important;
 9 | }
10 | 
11 | .toolbar__title {
12 |   font-family: "Arial", sans-serif !important;
13 |   font-size: 16px !important;
14 |   color: #ffffff !important;
15 | }
16 | 
17 | /* Minimized button */
18 | button.min-button {
19 |   background-color: #e11d48 !important;
20 |   border-color: #e11d48 !important;
21 | }
22 | 
23 | /* Message container */
24 | .message-list-container {
25 |   background-color: #fdf2f8 !important;
26 | }
27 | 
28 | /* Bot messages */
29 | .message-bot .message-bubble {
30 |   background-color: #fce7f3 !important;
31 | }
32 | 
33 | /* Human messages */
34 | .message-human .message-bubble {
35 |   background-color: #e11d48 !important;
36 |   color: #ffffff !important;
37 | }
38 | 
39 | /* Response card buttons */
40 | .v-card-actions .v-btn {
41 |   background-color: #f43f5e !important;
42 |   color: #ffffff !important;
43 | }
44 | 
45 | /* Common styling */
46 | .message-bubble {
47 |   border-radius: 12px !important;
48 |   padding: 8px 16px !important;
49 | }
50 | 
51 | .message-bubble p {
52 |   margin-bottom: 8px;
53 | }
54 | 
55 | .message-bubble p:last-child {
56 |   margin-bottom: 0px;
57 | }
58 | 
59 | .message-bubble .message-text {
60 |   padding-left: 0;
61 |   padding-right: 0;
62 |   line-height: 1.6;
63 |   font-size: 1rem;
64 | }
65 | 
66 | .input-group--text-field input,
67 | .input-group--text-field textarea,
68 | .input-group--text-field label {
69 |   font-size: 14px !important;
70 | }
71 | 
72 | .v-card-actions .v-btn {
73 |   margin: 4px 4px !important;
74 |   font-size: 1em !important;
75 |   min-width: 44px !important;
76 |   border-radius: 8px !important;
77 | }
78 | 
79 | button.end-live-chat-button.btn {
80 |   color: white !important;
81 |   background-color: #dc2626 !important;
82 | }
83 | 
84 | .lex-web-ui-iframe {
85 |   min-width: 25vw !important;
86 |   max-height: 315px !important;
87 |   margin-right: 10vw !important;
88 |   margin-bottom: 10px !important;
89 | }


--------------------------------------------------------------------------------
/example-css/dark-mode-theme.css:
--------------------------------------------------------------------------------
 1 | /* ========================================
 2 |    DARK MODE THEME
 3 |    Modern dark theme for tech-savvy users
 4 |    ======================================== */
 5 | 
 6 | /* Toolbar */
 7 | .bg-red {
 8 |   background-color: #1f2937 !important;
 9 | }
10 | 
11 | .toolbar__title {
12 |   font-family: "Inter", sans-serif !important;
13 |   font-size: 16px !important;
14 |   color: #f9fafb !important;
15 | }
16 | 
17 | /* Minimized button */
18 | button.min-button {
19 |   background-color: #374151 !important;
20 |   border-color: #374151 !important;
21 | }
22 | 
23 | /* Message container */
24 | .message-list-container {
25 |   background-color: #111827 !important;
26 | }
27 | 
28 | /* Bot messages */
29 | .message-bot .message-bubble {
30 |   background-color: #374151 !important;
31 |   color: #f9fafb !important;
32 | }
33 | 
34 | /* Human messages */
35 | .message-human .message-bubble {
36 |   background-color: #6b7280 !important;
37 |   color: #ffffff !important;
38 | }
39 | 
40 | /* Message text color */
41 | .message-text {
42 |   color: #f9fafb !important;
43 | }
44 | 
45 | /* Response card buttons */
46 | .v-card-actions .v-btn {
47 |   background-color: #4b5563 !important;
48 |   color: #ffffff !important;
49 | }
50 | 
51 | /* Common styling */
52 | .message-bubble {
53 |   border-radius: 12px !important;
54 |   padding: 8px 16px !important;
55 | }
56 | 
57 | .message-bubble p {
58 |   margin-bottom: 8px;
59 | }
60 | 
61 | .message-bubble p:last-child {
62 |   margin-bottom: 0px;
63 | }
64 | 
65 | .message-bubble .message-text {
66 |   padding-left: 0;
67 |   padding-right: 0;
68 |   line-height: 1.6;
69 |   font-size: 1rem;
70 | }
71 | 
72 | .input-group--text-field input,
73 | .input-group--text-field textarea,
74 | .input-group--text-field label {
75 |   font-size: 14px !important;
76 | }
77 | 
78 | .v-card-actions .v-btn {
79 |   margin: 4px 4px !important;
80 |   font-size: 1em !important;
81 |   min-width: 44px !important;
82 |   border-radius: 8px !important;
83 | }
84 | 
85 | button.end-live-chat-button.btn {
86 |   color: white !important;
87 |   background-color: #dc2626 !important;
88 | }
89 | 
90 | .lex-web-ui-iframe {
91 |   min-width: 25vw !important;
92 |   max-height: 315px !important;
93 |   margin-right: 10vw !important;
94 |   margin-bottom: 10px !important;
95 | }


--------------------------------------------------------------------------------
/example-css/elegant-purple-theme.css:
--------------------------------------------------------------------------------
 1 | /* ========================================
 2 |    ELEGANT PURPLE THEME
 3 |    Sophisticated purple theme for premium services
 4 |    ======================================== */
 5 | 
 6 | /* Toolbar */
 7 | .bg-red {
 8 |   background-color: #7c3aed !important;
 9 | }
10 | 
11 | .toolbar__title {
12 |   font-family: "Georgia", serif !important;
13 |   font-size: 16px !important;
14 |   color: #ffffff !important;
15 | }
16 | 
17 | /* Minimized button */
18 | button.min-button {
19 |   background-color: #7c3aed !important;
20 |   border-color: #7c3aed !important;
21 | }
22 | 
23 | /* Message container */
24 | .message-list-container {
25 |   background-color: #faf5ff !important;
26 | }
27 | 
28 | /* Bot messages */
29 | .message-bot .message-bubble {
30 |   background-color: #ede9fe !important;
31 | }
32 | 
33 | /* Human messages */
34 | .message-human .message-bubble {
35 |   background-color: #7c3aed !important;
36 |   color: #ffffff !important;
37 | }
38 | 
39 | /* Response card buttons */
40 | .v-card-actions .v-btn {
41 |   background-color: #8b5cf6 !important;
42 |   color: #ffffff !important;
43 | }
44 | 
45 | /* Common styling */
46 | .message-bubble {
47 |   border-radius: 12px !important;
48 |   padding: 8px 16px !important;
49 | }
50 | 
51 | .message-bubble p {
52 |   margin-bottom: 8px;
53 | }
54 | 
55 | .message-bubble p:last-child {
56 |   margin-bottom: 0px;
57 | }
58 | 
59 | .message-bubble .message-text {
60 |   padding-left: 0;
61 |   padding-right: 0;
62 |   line-height: 1.6;
63 |   font-size: 1rem;
64 | }
65 | 
66 | .input-group--text-field input,
67 | .input-group--text-field textarea,
68 | .input-group--text-field label {
69 |   font-size: 14px !important;
70 | }
71 | 
72 | .v-card-actions .v-btn {
73 |   margin: 4px 4px !important;
74 |   font-size: 1em !important;
75 |   min-width: 44px !important;
76 |   border-radius: 8px !important;
77 | }
78 | 
79 | button.end-live-chat-button.btn {
80 |   color: white !important;
81 |   background-color: #dc2626 !important;
82 | }
83 | 
84 | .lex-web-ui-iframe {
85 |   min-width: 25vw !important;
86 |   max-height: 315px !important;
87 |   margin-right: 10vw !important;
88 |   margin-bottom: 10px !important;
89 | }


--------------------------------------------------------------------------------
/example-css/forest-green.css:
--------------------------------------------------------------------------------
 1 | /* ========================================
 2 |    FOREST GREEN THEME
 3 |    Natural forest green theme for eco-friendly environments
 4 |    ======================================== */
 5 | 
 6 | /* Toolbar */
 7 | .bg-red {
 8 |   background-color: #166534 !important;
 9 | }
10 | 
11 | .toolbar__title {
12 |   font-family: "Arial", sans-serif !important;
13 |   font-size: 16px !important;
14 |   color: #ffffff !important;
15 | }
16 | 
17 | /* Minimized button */
18 | button.min-button {
19 |   background-color: #166534 !important;
20 |   border-color: #166534 !important;
21 | }
22 | 
23 | /* Message container */
24 | .message-list-container {
25 |   background-color: #f0fdf4 !important;
26 | }
27 | 
28 | /* Bot messages */
29 | .message-bot .message-bubble {
30 |   background-color: #bbf7d0 !important;
31 | }
32 | 
33 | /* Human messages */
34 | .message-human .message-bubble {
35 |   background-color: #166534 !important;
36 |   color: #ffffff !important;
37 | }
38 | 
39 | /* Response card buttons */
40 | .v-card-actions .v-btn {
41 |   background-color: #22c55e !important;
42 |   color: #ffffff !important;
43 | }
44 | 
45 | /* Common styling */
46 | .message-bubble {
47 |   border-radius: 12px !important;
48 |   padding: 8px 16px !important;
49 | }
50 | 
51 | .message-bubble p {
52 |   margin-bottom: 8px;
53 | }
54 | 
55 | .message-bubble p:last-child {
56 |   margin-bottom: 0px;
57 | }
58 | 
59 | .message-bubble .message-text {
60 |   padding-left: 0;
61 |   padding-right: 0;
62 |   line-height: 1.6;
63 |   font-size: 1rem;
64 | }
65 | 
66 | .input-group--text-field input,
67 | .input-group--text-field textarea,
68 | .input-group--text-field label {
69 |   font-size: 14px !important;
70 | }
71 | 
72 | .v-card-actions .v-btn {
73 |   margin: 4px 4px !important;
74 |   font-size: 1em !important;
75 |   min-width: 44px !important;
76 |   border-radius: 8px !important;
77 | }
78 | 
79 | button.end-live-chat-button.btn {
80 |   color: white !important;
81 |   background-color: #dc2626 !important;
82 | }
83 | 
84 | .lex-web-ui-iframe {
85 |   min-width: 25vw !important;
86 |   max-height: 315px !important;
87 |   margin-right: 10vw !important;
88 |   margin-bottom: 10px !important;
89 | }


--------------------------------------------------------------------------------
/example-css/professional-blue.css:
--------------------------------------------------------------------------------
 1 | /* ========================================
 2 |    CORPORATE BLUE THEME
 3 |    Professional blue theme for business environments
 4 |    ======================================== */
 5 | 
 6 | /* Toolbar */
 7 | .bg-red {
 8 |   background-color: #1e3a8a !important;
 9 | }
10 | 
11 | .toolbar__title {
12 |   font-family: "Arial", sans-serif !important;
13 |   font-size: 16px !important;
14 |   color: #ffffff !important;
15 | }
16 | 
17 | /* Minimized button */
18 | button.min-button {
19 |   background-color: #1e3a8a !important;
20 |   border-color: #1e3a8a !important;
21 | }
22 | 
23 | /* Message container */
24 | .message-list-container {
25 |   background-color: #f8fafc !important;
26 | }
27 | 
28 | /* Bot messages */
29 | .message-bot .message-bubble {
30 |   background-color: #e0f2fe !important;
31 | }
32 | 
33 | /* Human messages */
34 | .message-human .message-bubble {
35 |   background-color: #1e3a8a !important;
36 |   color: #ffffff !important;
37 | }
38 | 
39 | /* Response card buttons */
40 | .v-card-actions .v-btn {
41 |   background-color: #3b82f6 !important;
42 |   color: #ffffff !important;
43 | }
44 | 
45 | /* Common styling */
46 | .message-bubble {
47 |   border-radius: 12px !important;
48 |   padding: 8px 16px !important;
49 | }
50 | 
51 | .message-bubble p {
52 |   margin-bottom: 8px;
53 | }
54 | 
55 | .message-bubble p:last-child {
56 |   margin-bottom: 0px;
57 | }
58 | 
59 | .message-bubble .message-text {
60 |   padding-left: 0;
61 |   padding-right: 0;
62 |   line-height: 1.6;
63 |   font-size: 1rem;
64 | }
65 | 
66 | .input-group--text-field input,
67 | .input-group--text-field textarea,
68 | .input-group--text-field label {
69 |   font-size: 14px !important;
70 | }
71 | 
72 | .v-card-actions .v-btn {
73 |   margin: 4px 4px !important;
74 |   font-size: 1em !important;
75 |   min-width: 44px !important;
76 |   border-radius: 8px !important;
77 | }
78 | 
79 | button.end-live-chat-button.btn {
80 |   color: white !important;
81 |   background-color: #dc2626 !important;
82 | }
83 | 
84 | .lex-web-ui-iframe {
85 |   min-width: 25vw !important;
86 |   max-height: 315px !important;
87 |   margin-right: 10vw !important;
88 |   margin-bottom: 10px !important;
89 | }


--------------------------------------------------------------------------------
/example-css/sky-blue.css:
--------------------------------------------------------------------------------
 1 | /* ========================================
 2 |    SKY BLUE THEME
 3 |    Light sky blue theme for calm interfaces
 4 |    ======================================== */
 5 | 
 6 | /* Toolbar */
 7 | .bg-red {
 8 |   background-color: #0284c7 !important;
 9 | }
10 | 
11 | .toolbar__title {
12 |   font-family: "Arial", sans-serif !important;
13 |   font-size: 16px !important;
14 |   color: #ffffff !important;
15 | }
16 | 
17 | /* Minimized button */
18 | button.min-button {
19 |   background-color: #0284c7 !important;
20 |   border-color: #0284c7 !important;
21 | }
22 | 
23 | /* Message container */
24 | .message-list-container {
25 |   background-color: #f0f9ff !important;
26 | }
27 | 
28 | /* Bot messages */
29 | .message-bot .message-bubble {
30 |   background-color: #e0f2fe !important;
31 | }
32 | 
33 | /* Human messages */
34 | .message-human .message-bubble {
35 |   background-color: #0284c7 !important;
36 |   color: #ffffff !important;
37 | }
38 | 
39 | /* Response card buttons */
40 | .v-card-actions .v-btn {
41 |   background-color: #0ea5e9 !important;
42 |   color: #ffffff !important;
43 | }
44 | 
45 | /* Common styling */
46 | .message-bubble {
47 |   border-radius: 12px !important;
48 |   padding: 8px 16px !important;
49 | }
50 | 
51 | .message-bubble p {
52 |   margin-bottom: 8px;
53 | }
54 | 
55 | .message-bubble p:last-child {
56 |   margin-bottom: 0px;
57 | }
58 | 
59 | .message-bubble .message-text {
60 |   padding-left: 0;
61 |   padding-right: 0;
62 |   line-height: 1.6;
63 |   font-size: 1rem;
64 | }
65 | 
66 | .input-group--text-field input,
67 | .input-group--text-field textarea,
68 | .input-group--text-field label {
69 |   font-size: 14px !important;
70 | }
71 | 
72 | .v-card-actions .v-btn {
73 |   margin: 4px 4px !important;
74 |   font-size: 1em !important;
75 |   min-width: 44px !important;
76 |   border-radius: 8px !important;
77 | }
78 | 
79 | button.end-live-chat-button.btn {
80 |   color: white !important;
81 |   background-color: #dc2626 !important;
82 | }
83 | 
84 | .lex-web-ui-iframe {
85 |   min-width: 25vw !important;
86 |   max-height: 315px !important;
87 |   margin-right: 10vw !important;
88 |   margin-bottom: 10px !important;
89 | }


--------------------------------------------------------------------------------
/example-css/sunset-orange.css:
--------------------------------------------------------------------------------
 1 | /* ========================================
 2 |    SUNSET ORANGE THEME
 3 |    Warm sunset orange theme for creative environments
 4 |    ======================================== */
 5 | 
 6 | /* Toolbar */
 7 | .bg-red {
 8 |   background-color: #ea580c !important;
 9 | }
10 | 
11 | .toolbar__title {
12 |   font-family: "Arial", sans-serif !important;
13 |   font-size: 16px !important;
14 |   color: #ffffff !important;
15 | }
16 | 
17 | /* Minimized button */
18 | button.min-button {
19 |   background-color: #ea580c !important;
20 |   border-color: #ea580c !important;
21 | }
22 | 
23 | /* Message container */
24 | .message-list-container {
25 |   background-color: #fff7ed !important;
26 | }
27 | 
28 | /* Bot messages */
29 | .message-bot .message-bubble {
30 |   background-color: #fed7aa !important;
31 | }
32 | 
33 | /* Human messages */
34 | .message-human .message-bubble {
35 |   background-color: #ea580c !important;
36 |   color: #ffffff !important;
37 | }
38 | 
39 | /* Response card buttons */
40 | .v-card-actions .v-btn {
41 |   background-color: #f97316 !important;
42 |   color: #ffffff !important;
43 | }
44 | 
45 | /* Common styling */
46 | .message-bubble {
47 |   border-radius: 12px !important;
48 |   padding: 8px 16px !important;
49 | }
50 | 
51 | .message-bubble p {
52 |   margin-bottom: 8px;
53 | }
54 | 
55 | .message-bubble p:last-child {
56 |   margin-bottom: 0px;
57 | }
58 | 
59 | .message-bubble .message-text {
60 |   padding-left: 0;
61 |   padding-right: 0;
62 |   line-height: 1.6;
63 |   font-size: 1rem;
64 | }
65 | 
66 | .input-group--text-field input,
67 | .input-group--text-field textarea,
68 | .input-group--text-field label {
69 |   font-size: 14px !important;
70 | }
71 | 
72 | .v-card-actions .v-btn {
73 |   margin: 4px 4px !important;
74 |   font-size: 1em !important;
75 |   min-width: 44px !important;
76 |   border-radius: 8px !important;
77 | }
78 | 
79 | button.end-live-chat-button.btn {
80 |   color: white !important;
81 |   background-color: #dc2626 !important;
82 | }
83 | 
84 | .lex-web-ui-iframe {
85 |   min-width: 25vw !important;
86 |   max-height: 315px !important;
87 |   margin-right: 10vw !important;
88 |   margin-bottom: 10px !important;
89 | }


--------------------------------------------------------------------------------
/img/ExampleBuildForLexWebUi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/ExampleBuildForLexWebUi.png


--------------------------------------------------------------------------------
/img/Lex-Streaming-Architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/Lex-Streaming-Architecture.png


--------------------------------------------------------------------------------
/img/LexWebUiStyle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/LexWebUiStyle.png


--------------------------------------------------------------------------------
/img/QBusiness.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/QBusiness.gif


--------------------------------------------------------------------------------
/img/aud-claim.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/aud-claim.PNG


--------------------------------------------------------------------------------
/img/cfn-stack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/cfn-stack.png


--------------------------------------------------------------------------------
/img/connect-contact-flows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/connect-contact-flows.png


--------------------------------------------------------------------------------
/img/connect-flow-details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/connect-flow-details.png


--------------------------------------------------------------------------------
/img/example-css.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/example-css.png


--------------------------------------------------------------------------------
/img/f.0.14.0_buttonA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/f.0.14.0_buttonA.png


--------------------------------------------------------------------------------
/img/f.0.14.0_buttonB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/f.0.14.0_buttonB.png


--------------------------------------------------------------------------------
/img/f.0.14.0_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/f.0.14.0_login.png


--------------------------------------------------------------------------------
/img/f.0.14.0_markdown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/f.0.14.0_markdown.png


--------------------------------------------------------------------------------
/img/f.0.14.0_multimessages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/f.0.14.0_multimessages.png


--------------------------------------------------------------------------------
/img/feedbackButtons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/feedbackButtons.png


--------------------------------------------------------------------------------
/img/identity-propagation.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/identity-propagation.PNG


--------------------------------------------------------------------------------
/img/interactive-message-datepicker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/interactive-message-datepicker.png


--------------------------------------------------------------------------------
/img/interactive-message-datetimepicker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/interactive-message-datetimepicker.png


--------------------------------------------------------------------------------
/img/interactive-message-listpicker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/interactive-message-listpicker.png


--------------------------------------------------------------------------------
/img/lex-streaming-demo-2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/lex-streaming-demo-2.gif


--------------------------------------------------------------------------------
/img/pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/pipeline.png


--------------------------------------------------------------------------------
/img/sample_disconnect_flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/sample_disconnect_flow.png


--------------------------------------------------------------------------------
/img/token-issuer.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/token-issuer.PNG


--------------------------------------------------------------------------------
/img/toolbar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/toolbar.png


--------------------------------------------------------------------------------
/img/upload.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/upload.png


--------------------------------------------------------------------------------
/img/webapp-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/webapp-diagram.png


--------------------------------------------------------------------------------
/img/webapp-full.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/webapp-full.gif


--------------------------------------------------------------------------------
/img/webapp-iframe.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/img/webapp-iframe.gif


--------------------------------------------------------------------------------
/lex-web-ui/.browserslistrc:
--------------------------------------------------------------------------------
1 | defaults and fully supports es6-module


--------------------------------------------------------------------------------
/lex-web-ui/.editorconfig:
--------------------------------------------------------------------------------
 1 | root = true
 2 | 
 3 | [*]
 4 | charset = utf-8
 5 | indent_style = space
 6 | indent_size = 2
 7 | end_of_line = lf
 8 | insert_final_newline = true
 9 | trim_trailing_whitespace = true
10 | 


--------------------------------------------------------------------------------
/lex-web-ui/.eslintignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/.eslintignore


--------------------------------------------------------------------------------
/lex-web-ui/.eslintrc.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   root: true,
 3 |   env: {
 4 |     node: true,
 5 |   },
 6 |   extends: [
 7 |     'plugin:vue/essential', 
 8 |     'plugin:vue/vue3-recommended'
 9 |   ],
10 |   parserOptions: {
11 |     parser: '@babel/eslint-parser',
12 |   },
13 |   rules: {
14 |     'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
15 |     'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
16 |     // add your custom rules here
17 |     // don't require .vue extension when importing
18 |     'import/extensions': ['error', 'always', {
19 |       'js': 'never',
20 |       'vue': 'never',
21 |     }],
22 |     // allow optionalDependencies
23 |     'import/no-extraneous-dependencies': ['error', {
24 |       'optionalDependencies': ['tests?/unit/index.js'],
25 |     }],
26 |     // allow debugger during development
27 |     'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
28 |   },
29 |   overrides: [
30 |     {
31 |       files: [
32 |         '**/__tests?__/*.{j,t}s?(x)',
33 |         '**/tests?/unit/**/*.spec.{j,t}s?(x)',
34 |       ],
35 |       env: {
36 |         mocha: true,
37 |       },
38 |     },
39 |   ],
40 | }
41 | 


--------------------------------------------------------------------------------
/lex-web-ui/.gitignore:
--------------------------------------------------------------------------------
 1 | .DS_Store
 2 | node_modules/
 3 | dist/
 4 | 
 5 | # local env files
 6 | .env.local
 7 | .env.*.local
 8 | 
 9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 | 
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 | 
24 | # Test artifacts
25 | test/unit/coverage
26 | test/e2e/reports
27 | selenium-debug.log
28 | chromedriver.log
29 | geckodriver.log
30 | 


--------------------------------------------------------------------------------
/lex-web-ui/READMECONFIGSCREENS.md:
--------------------------------------------------------------------------------
 1 | ### Create a user pool flow
 2 | 
 3 | The following images display the flow of configuration screens after selecting 
 4 | create new user pool from the AWS Cognito User Pools console. The changes and selections are
 5 | identified by the red arrows. These settings will support
 6 | use of the new User Pool by the lex-web-ui. 
 7 | 
 8 | ![](./readmeimages/userpoolstep1.png)
 9 | ![](./readmeimages/userpoolstep2.png)
10 | ![](./readmeimages/userpoolstep3.png)
11 | ![](./readmeimages/userpoolstep4.png)
12 | ![](./readmeimages/userpoolstep5a.png)
13 | ![](./readmeimages/userpoolstep5b.png)
14 | ![](./readmeimages/userpoolstep6.png)
15 | ![](./readmeimages/userpoolstep7.png)
16 | ![](./readmeimages/userpoolstep8.png)
17 | ![](./readmeimages/userpoolstep8a.png)
18 | ![](./readmeimages/userpoolstep8b.png)
19 | ![](./readmeimages/userpoolstep9.png)
20 | ![](./readmeimages/userpoolstep10.png)
21 | ![](./readmeimages/userpoolcreatecomplete.png)
22 | 
23 | ### User Pool App Client Settings
24 | 
25 | Once the user pool has been created, the pool's app client settings also need to be configured.
26 | 
27 | ![](./readmeimages/userpoolappclientsettings.png)
28 | 
29 | ### User Pool App Domain Name
30 | 
31 | An app domain name for the User Pool also needs to be specified.
32 | 
33 | ![](./readmeimages/userpoolappdomainname.png)
34 | 
35 | ### Identity Pool Authentication Provider
36 | 
37 | Finally the Identity Pool needs to be configured to utilize the new Cognito User Pool as
38 | the authentication provider.
39 | 
40 | ![](./readmeimages/identitypoolauthenticationproviders.png)
41 | 


--------------------------------------------------------------------------------
/lex-web-ui/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   presets: [
3 |     '@vue/cli-plugin-babel/preset',
4 |   ],
5 | };
6 | 


--------------------------------------------------------------------------------
/lex-web-ui/current/user-custom-chatbot-style.css:
--------------------------------------------------------------------------------
1 | button.min-button {border-radius: 60px;}


--------------------------------------------------------------------------------
/lex-web-ui/current/user-lex-web-ui-loader-config.json:
--------------------------------------------------------------------------------
1 | {}


--------------------------------------------------------------------------------
/lex-web-ui/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "lex-web-ui",
 3 |   "version": "0.23.0",
 4 |   "description": "Amazon Lex Web Interface",
 5 |   "author": "AWS",
 6 |   "license": "Amazon Software License",
 7 |   "private": true,
 8 |   "main": "dist/bundle/lex-web-ui.js",
 9 |   "scripts": {
10 |     "serve": "vue-cli-service serve",
11 |     "dev": "vue-cli-service serve",
12 |     "start": "vue-cli-service serve",
13 |     "build": "vue-cli-service build",
14 |     "build:dev": "vue-cli-service build --mode development",
15 |     "build-dist": "npm run build:lib-dev && npm run build:lib-prod",
16 |     "build:lib-dev": "BUILD_TARGET=lib vue-cli-service build ---no-clean --mode development src/lex-web-ui.js",
17 |     "build:lib-prod": "BUILD_TARGET=lib vue-cli-service build --no-clean ---mode production src/lex-web-ui.js",
18 |     "lint": "vue-cli-service lint"
19 |   },
20 |   "dependencies": {
21 |     "aws-amplify": "^5.3.26",
22 |     "amazon-connect-chatjs": "^2.3.0",
23 |     "@aws-sdk/client-cognito-identity": "3.470.0",
24 |     "@aws-sdk/client-connect": "3.470.0",
25 |     "@aws-sdk/client-lex-runtime-v2": "3.470.0",
26 |     "@aws-sdk/client-polly": "3.470.0",
27 |     "@aws-sdk/client-s3": "3.470.0",
28 |     "@aws-sdk/credential-providers": "3.470.0",
29 |     "browserify-zlib": "^0.2.0",
30 |     "buffer": "^6.0.3",
31 |     "core-js": "^3.6.5",
32 |     "jwt-decode": "^4.0.0",
33 |     "marked": "^4.0.12",
34 |     "material-design-icons": "^3.0.1",
35 |     "node-polyfill-webpack-plugin": "^2.0.1",
36 |     "roboto-fontface": "^0.10.0",
37 |     "stream-browserify": "^3.0.0",
38 |     "vue": "^3.5.13",
39 |     "vue-loader": "^17.3.1",
40 |     "vue-router": "^4.2.5",
41 |     "vuetify": "^3.8.3",
42 |     "vuex": "^4.1.0"
43 |   },
44 |   "devDependencies": {
45 |     "@babel/eslint-parser": "^7.23.3",
46 |     "@mdi/font": "^7.4.47",
47 |     "@vue/cli-plugin-babel": "^5.0.8",
48 |     "@vue/cli-plugin-router": "^5.0.8",
49 |     "@vue/cli-plugin-vuex": "~5.0.8",
50 |     "@vue/cli-service": "^5.0.8",
51 |     "eslint": "^8.55.0",
52 |     "eslint-plugin-import": "^2.20.2",
53 |     "eslint-plugin-vue": "^9.19.2",
54 |     "worker-loader": "^3.0.8"
55 |   },
56 |   "engines": {
57 |     "node": ">=18.0.0",
58 |     "npm": ">=10.0.0"
59 |   },
60 |   "overrides": {
61 |     "postcss": "8.4.32"
62 |   }
63 | }
64 | 


--------------------------------------------------------------------------------
/lex-web-ui/public/img/flowers.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/public/img/flowers.jpeg


--------------------------------------------------------------------------------
/lex-web-ui/public/img/note.md:
--------------------------------------------------------------------------------
1 | # Flowers Image Credit
2 | [Photo](https://www.pexels.com/photo/red-whiet-and-pink-tulips-52571/)
3 | by: [@juhg](https://unsplash.com/@juhg)
4 | [CC0 1.0](https://creativecommons.org/publicdomain/zero/1.0/)
5 | 


--------------------------------------------------------------------------------
/lex-web-ui/public/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     
 7 |     <%= htmlWebpackPlugin.options.title %>
 8 |   
 9 |   
10 |     
11 |     
14 |     
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/identitypoolauthenticationproviders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/identitypoolauthenticationproviders.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolappclientsettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolappclientsettings.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolappdomainname.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolappdomainname.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolcreatecomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolcreatecomplete.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep1.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep10.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep2.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep3.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep4.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep5a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep5a.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep5b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep5b.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep6.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep7.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep8.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep8a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep8a.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep8b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep8b.png -------------------------------------------------------------------------------- /lex-web-ui/readmeimages/userpoolstep9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/readmeimages/userpoolstep9.png -------------------------------------------------------------------------------- /lex-web-ui/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /lex-web-ui/src/LexApp.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /lex-web-ui/src/assets/silent.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/src/assets/silent.mp3 -------------------------------------------------------------------------------- /lex-web-ui/src/assets/silent.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-lex-web-ui/7c2f5481181736fb7729d5345bfac29096447150/lex-web-ui/src/assets/silent.ogg -------------------------------------------------------------------------------- /lex-web-ui/src/components/MessageList.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 85 | 86 | 110 | -------------------------------------------------------------------------------- /lex-web-ui/src/components/MessageLoading.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 69 | 70 | 95 | -------------------------------------------------------------------------------- /lex-web-ui/src/components/MinButton.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 92 | 97 | -------------------------------------------------------------------------------- /lex-web-ui/src/components/RecorderStatus.vue: -------------------------------------------------------------------------------- 1 | 47 | 167 | 198 | -------------------------------------------------------------------------------- /lex-web-ui/src/components/ResponseCard.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 96 | 97 | 128 | -------------------------------------------------------------------------------- /lex-web-ui/src/config/.gitattributes: -------------------------------------------------------------------------------- 1 | config.*.json merge=ours 2 | -------------------------------------------------------------------------------- /lex-web-ui/src/config/config.awstest.json: -------------------------------------------------------------------------------- 1 | { 2 | "cognito": { 3 | "poolId": "us-east-1:aa8c83df-49e8-4a53-adea-c0a90d762a83", 4 | "appUserPoolClientId": "727umf381vhdhorp3qh0dvi72f", 5 | "appUserPoolName": "us-east-1_tz8P8iZcb", 6 | "appDomainName": "lexkwebkuikidentitypoolconfigkjazfpzssdfhe238359442180.auth.us-east-1.amazoncognito.com", 7 | "appUserPoolIdentityProvider": "" 8 | }, 9 | "connect": { 10 | "contactFlowId" : "", 11 | "instanceId" : "", 12 | "apiGatewayEndpoint" : "" 13 | }, 14 | "lex": { 15 | "initialText": "This is from AWS TEST CONFING", 16 | "initialSpeechInstruction": "Say 'Order Flowers' to get started." 17 | }, 18 | "polly": { 19 | "voiceId": "Salli" 20 | }, 21 | "ui": { 22 | "parentOrigin": "", 23 | "pageTitle": "AWS TEST", 24 | "toolbarTitle": "AWS TEST BOT" 25 | }, 26 | "recorder": { 27 | "preset": "speech_recognition" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lex-web-ui/src/config/config.current.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /lex-web-ui/src/config/config.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "region": "us-east-1", 3 | "cognito": { 4 | "poolId": "", 5 | "appUserPoolClientId": "", 6 | "appUserPoolName": "", 7 | "appDomainName": "", 8 | "aws_cognito_region": "us-east-1", 9 | "region": "us-east-1" 10 | }, 11 | "lex": { 12 | "v2BotId": "", 13 | "v2BotAliasId": "", 14 | "v2BotLocaleId": "", 15 | "initialText": "Type 'Buy Flowers' to get started.", 16 | "initialSpeechInstruction": "Say 'Buy Flowers' to get started.", 17 | "initialUtterance": "", 18 | "reInitSessionAttributesOnRestart": false, 19 | "region": "us-east-1", 20 | "retryOnLexPostTextTimeout": "false", 21 | "retryCountPostTextTimeout": "1" 22 | }, 23 | "ui": { 24 | "parentOrigin": "", 25 | "toolbarTitle": "Order Flowers", 26 | "toolbarLogo": "", 27 | "positiveFeedbackIntent": "Thumbs up", 28 | "negativeFeedbackIntent": "Thumbs down", 29 | "helpIntent": "Help", 30 | "enableLogin": false, 31 | "forceLogin": false, 32 | "AllowSuperDangerousHTMLInMessage": true, 33 | "shouldDisplayResponseCardTitle": false, 34 | "saveHistory": false, 35 | "minButtonContent": "", 36 | "hideInputFieldsForButtonResponse": false, 37 | "pushInitialTextOnRestart": false, 38 | "directFocusToBotInput": false, 39 | "showDialogStateIcon": false, 40 | "backButton": false, 41 | "messageMenu": true, 42 | "hideButtonMessageBubble": false, 43 | "enableLiveChat": false 44 | }, 45 | "connect": { 46 | "contactFlowId" : "", 47 | "instanceId" : "", 48 | "apiGatewayEndpoint" : "", 49 | "waitingForAgentMessage": "Thanks for waiting. An agent will be with you when available.", 50 | "waitingForAgentMessageIntervalSeconds": 60, 51 | "agentJoinedMessage": "{Agent} has joined.", 52 | "agentLeftMessage": "{Agent} has left.", 53 | "chatEndedMessage": "Chat ended.", 54 | "attachChatTranscript": true 55 | }, 56 | "polly": { 57 | "voiceId": "Salli" 58 | }, 59 | "recorder": { 60 | "preset": "speech_recognition" 61 | }, 62 | "iframe": { 63 | "iframeOrigin": "", 64 | "shouldLoadIframeMinimized": false, 65 | "iframeSrcPath": "/index.html#/?lexWebUiEmbed=true" 66 | } 67 | } -------------------------------------------------------------------------------- /lex-web-ui/src/config/config.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "cognito": { 3 | "poolId": "" 4 | }, 5 | "lex": { 6 | "initialText": "You can ask me for help ordering flowers. Just type \"order flowers\" or click on the mic and say it.", 7 | "initialSpeechInstruction": "Say 'Order Flowers' to get started." 8 | }, 9 | "polly": { 10 | "voiceId": "Salli" 11 | }, 12 | "ui": { 13 | "parentOrigin": "", 14 | "pageTitle": "Order Flowers Bot", 15 | "toolbarTitle": "Order Flowers", 16 | "enableLiveChat": false 17 | }, 18 | "connect": { 19 | "contactFlowId" : "", 20 | "instanceId" : "", 21 | "apiGatewayEndpoint" : "" 22 | }, 23 | "recorder": { 24 | "preset": "speech_recognition" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lex-web-ui/src/config/config.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "cognito": { 3 | "poolId": "" 4 | }, 5 | "connect": { 6 | "contactFlowId" : "", 7 | "instanceId" : "", 8 | "apiGatewayEndpoint" : "" 9 | }, 10 | "lex": { 11 | "initialText": "You can ask me for help ordering flowers. Just type \"order flowers\" or click on the mic and say it.", 12 | "initialSpeechInstruction": "Say 'Order Flowers' to get started." 13 | }, 14 | "polly": { 15 | "voiceId": "Salli" 16 | }, 17 | "ui": { 18 | "parentOrigin": "http://localhost:8080", 19 | "pageTitle": "Order Flowers Bot", 20 | "toolbarTitle": "Order Flowers" 21 | }, 22 | "recorder": { 23 | "preset": "speech_recognition" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lex-web-ui/src/init.js: -------------------------------------------------------------------------------- 1 | window.global ||= window -------------------------------------------------------------------------------- /lex-web-ui/src/lex-web-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | /* eslint no-console: ["error", { allow: ["warn", "error"] }] */ 15 | 16 | /** 17 | * Entry point to the lex-web-ui Vue plugin 18 | * Exports Loader as the plugin constructor 19 | * and Store as store that can be used with Vuex.Store() 20 | */ 21 | import { LexRuntimeV2Client } from '@aws-sdk/client-lex-runtime-v2'; 22 | import { PollyClient } from '@aws-sdk/client-polly'; 23 | import LexWeb from '@/components/LexWeb'; 24 | import VuexStore from '@/store'; 25 | 26 | import { config as defaultConfig, mergeConfig } from '@/config'; 27 | import { createApp, defineAsyncComponent } from 'vue'; 28 | import { createAppDev } from 'vue/dist/vue.esm-bundler.js'; 29 | import { aliases, md } from 'vuetify/iconsets/md'; 30 | import { createStore } from 'vuex'; 31 | 32 | // Vuetify 33 | import 'vuetify/styles' 34 | import { createVuetify } from 'vuetify' 35 | import * as components from 'vuetify/components' 36 | import * as directives from 'vuetify/directives' 37 | import colors from 'vuetify/lib/util/colors' 38 | 39 | const defineAsyncComponentInstance = (window.Vue) ? window.Vue.defineAsyncComponent : defineAsyncComponent; 40 | /** 41 | * Vue Component 42 | */ 43 | const Component = { 44 | name: 'lex-web-ui', 45 | template: '', 46 | components: { LexWeb }, 47 | }; 48 | 49 | export const testComponent = { 50 | template: '
I am async!
', 51 | }; 52 | const loadingComponent = { 53 | template: '

Loading. Please wait...

', 54 | }; 55 | const errorComponent = { 56 | template: '

An error ocurred...

', 57 | }; 58 | 59 | /** 60 | * Vue Asynchonous Component 61 | */ 62 | export const AsyncComponent = defineAsyncComponentInstance({ 63 | loader: () => Promise.resolve(Component), 64 | delay: 200, 65 | timeout: 10000, 66 | errorComponent: errorComponent, 67 | loadingComponent: loadingComponent 68 | }) 69 | 70 | /** 71 | * Vue Plugin 72 | */ 73 | export const Plugin = { 74 | install(app, { 75 | name = '$lexWebUi', 76 | componentName = 'lex-web-ui', 77 | awsConfig, 78 | lexRuntimeClient, 79 | lexRuntimeV2Client, 80 | pollyClient, 81 | component = AsyncComponent, 82 | config = defaultConfig, 83 | }) { 84 | // values to be added to custom vue property 85 | const value = { 86 | config, 87 | awsConfig, 88 | lexRuntimeClient, 89 | lexRuntimeV2Client, 90 | pollyClient, 91 | }; 92 | // add custom property to Vue 93 | // for example, access this in a component via this.$lexWebUi 94 | app.config.globalProperties[name] = value; 95 | // register as a global component 96 | app.component(componentName, component); 97 | }, 98 | }; 99 | 100 | export const Store = VuexStore; 101 | 102 | /** 103 | * Main Class 104 | */ 105 | export class Loader { 106 | constructor(config = {}) { 107 | const createAppInstance = (window.Vue) ? window.Vue.createApp : createApp; 108 | const vuexCreateStore = (window.Vuex) ? window.Vuex.createStore : createStore; 109 | 110 | const vuetify = createVuetify({ 111 | components, 112 | directives, 113 | icons: { 114 | defaultSet: 'md', 115 | aliases, 116 | sets: { 117 | md, 118 | }, 119 | }, 120 | theme: { 121 | themes: { 122 | light: { 123 | colors: { 124 | primary: colors.blue.darken2, 125 | secondary: colors.grey.darken3, 126 | accent: colors.blue.accent1, 127 | error: colors.red.accent2, 128 | info: colors.blue.base, 129 | success: colors.green.base, 130 | warning: colors.orange.darken1, 131 | }, 132 | }, 133 | dark: { 134 | colors: { 135 | primary: colors.blue.base, 136 | secondary: colors.grey.darken3, 137 | accent: colors.pink.accent1, 138 | error: colors.red.accent2, 139 | info: colors.blue.base, 140 | success: colors.green.base, 141 | warning: colors.orange.darken1, 142 | }, 143 | }, 144 | }, 145 | } 146 | }) 147 | 148 | const app = createAppInstance({ 149 | template: '
', 150 | }) 151 | 152 | app.use(vuetify) 153 | const store = vuexCreateStore(VuexStore) 154 | this.store = store 155 | app.use(store) 156 | this.app = app; 157 | 158 | const mergedConfig = mergeConfig(defaultConfig, config); 159 | let credentials; 160 | if (mergedConfig.cognito.poolId != '' || localStorage.getItem('poolId')) { 161 | credentials = this.store.dispatch('getCredentials', mergedConfig).then((creds) => { 162 | return creds; 163 | }); 164 | } 165 | 166 | const awsConfig = { 167 | region: mergedConfig.region || mergedConfig.cognito.poolId.split(':')[0] || 'us-east-1', 168 | credentials, 169 | }; 170 | 171 | const lexRuntimeV2Client = new LexRuntimeV2Client(awsConfig); 172 | const pollyClient = new PollyClient(awsConfig); 173 | 174 | // /* eslint-disable no-console */ 175 | app.use(Plugin, { 176 | config: mergedConfig, 177 | awsConfig, 178 | lexRuntimeV2Client, 179 | pollyClient 180 | }); 181 | this.app = app; 182 | } 183 | } 184 | 185 | // comment out for prod build 186 | if(process.env.NODE_ENV === "development") 187 | { 188 | const lexWeb = new Loader(); 189 | lexWeb.app.mount('#lex-app'); 190 | } -------------------------------------------------------------------------------- /lex-web-ui/src/main.js: -------------------------------------------------------------------------------- 1 | // import './init' 2 | // import { createApp } from 'vue/dist/vue.esm-bundler'; 3 | // import App from './App.vue' 4 | // import 'vuetify/styles' 5 | // import { createVuetify } from 'vuetify' 6 | // import * as components from 'vuetify/components' 7 | // import * as directives from 'vuetify/directives' 8 | // const { aliases, mdi } = require('vuetify/iconsets/mdi'); 9 | // import VuexStore from './store' 10 | // import { createStore } from 'vuex'; 11 | // import router from './router' 12 | 13 | // const vuetify = createVuetify({ 14 | // components, 15 | // directives, 16 | // icons: { 17 | // defaultSet: 'mdi', 18 | // aliases, 19 | // sets: { 20 | // mdi, 21 | // }, 22 | // }, 23 | // }) 24 | 25 | // const app = createApp(App) 26 | 27 | // app.use(vuetify) 28 | // app.use(router) 29 | // app.use(createStore(VuexStore)) 30 | // app.mount('#lex-app') -------------------------------------------------------------------------------- /lex-web-ui/src/router/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | import { createRouter, createWebHashHistory } from 'vue-router' 15 | import LexWeb from '@/components/LexWeb'; 16 | 17 | const router = createRouter({ 18 | history: createWebHashHistory(process.env.BASE_URL), 19 | routes: [ 20 | { 21 | path: '/', 22 | name: 'LexWeb', 23 | component: LexWeb, 24 | }, 25 | ], 26 | }) 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /lex-web-ui/src/store/getters.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | import { jwtDecode } from "jwt-decode"; 14 | 15 | export default { 16 | canInterruptBotPlayback: state => state.botAudio.canInterrupt, 17 | isBotSpeaking: state => state.botAudio.isSpeaking, 18 | isConversationGoing: state => state.recState.isConversationGoing, 19 | isLexInterrupting: state => state.lex.isInterrupting, 20 | isLexProcessing: state => state.lex.isProcessing, 21 | isMicMuted: state => state.recState.isMicMuted, 22 | isMicQuiet: state => state.recState.isMicQuiet, 23 | isRecorderSupported: state => state.recState.isRecorderSupported, 24 | isRecording: state => state.recState.isRecording, 25 | isBackProcessing: state => state.isBackProcessing, 26 | lastUtterance: state => () => { 27 | if (state.utteranceStack.length === 0) return ''; 28 | return state.utteranceStack[state.utteranceStack.length - 1].t; 29 | }, 30 | userName: state => () => { 31 | let v = ''; 32 | if (state.tokens && state.tokens.idtokenjwt) { 33 | const decoded = jwtDecode(state.tokens.idtokenjwt); 34 | if (decoded) { 35 | if (decoded.email) { 36 | v = decoded.email; 37 | } 38 | if (decoded.preferred_username) { 39 | v = decoded.preferred_username; 40 | } 41 | } 42 | return `[${v}]`; 43 | } 44 | return v; 45 | }, 46 | liveChatUserName: state => () => { 47 | let v = ''; 48 | if (state.tokens && state.tokens.idtokenjwt) { 49 | const decoded = jwtDecode(state.tokens.idtokenjwt); 50 | if (decoded) { 51 | if (decoded.preferred_username) { 52 | v = decoded.preferred_username; 53 | } 54 | } 55 | return `[${v}]`; 56 | } else if (state.liveChat.username) { 57 | return state.liveChat.username; 58 | } 59 | return v; 60 | }, 61 | liveChatTextTranscriptArray: state => () => { 62 | // Support redacting messages delivered to agent based on config.connect.transcriptRedactRegex. 63 | // Use case is to support redacting post chat survey responses from being seen by agents if user 64 | // reconnects with an agent. 65 | const messageTextArray = []; 66 | var text = ""; 67 | let redactionEnabled = false; 68 | if (state.config.connect.transcriptRedactRegex && state.config.connect.transcriptRedactRegex.length > 0) { 69 | redactionEnabled = true; 70 | } 71 | let shouldRedactNextMessage = false; // indicates if the next message to append should be redacted 72 | const regex = redactionEnabled ? new RegExp(`${state.config.connect.transcriptRedactRegex}`, "g") : undefined; 73 | state.messages.forEach((message) => { 74 | var nextMessage = message.date.toLocaleTimeString() + ' ' + (message.type === 'bot' ? 'Bot' : 'Human') + ': ' + message.text + '\n'; 75 | if (redactionEnabled && shouldRedactNextMessage) { 76 | nextMessage = message.date.toLocaleTimeString() + ' ' + (message.type === 'bot' ? 'Bot' : 'Human') + ': ' + '###' + '\n'; 77 | } 78 | if((text + nextMessage).length > 400) { 79 | messageTextArray.push(text); 80 | //this is over 1k chars by itself, so we must break it up. 81 | var subMessageArray = nextMessage.match(/(.|[\r\n]){1,400}/g); 82 | subMessageArray.forEach((subMsg) => { 83 | messageTextArray.push(subMsg); 84 | }); 85 | text = ""; 86 | if (redactionEnabled && regex) { 87 | shouldRedactNextMessage = regex.test(nextMessage); 88 | } 89 | nextMessage = ""; 90 | } else { 91 | if (redactionEnabled && regex) { 92 | // if we are redacting, check if the next message should be redacted 93 | shouldRedactNextMessage = regex.test(nextMessage); 94 | } 95 | } 96 | text = text + nextMessage; 97 | }); 98 | messageTextArray.push(text); 99 | return messageTextArray; 100 | }, 101 | liveChatTranscriptFile: state => () => { 102 | var text = 'Bot Transcript: \n'; 103 | state.messages.forEach((message) => text = text + message.date.toLocaleTimeString() + ' ' + (message.type === 'bot' ? 'Bot' : 'Human') + ': ' + message.text + '\n'); 104 | var blob = new Blob([text], { type: 'text/plain'}); 105 | var file = new File([blob], 'chatTranscript.txt', { lastModified: new Date().getTime(), type: blob.type }); 106 | return file; 107 | }, 108 | 109 | wsMessages:(state)=>()=>{ 110 | return state.streaming.wsMessages; 111 | }, 112 | 113 | wsMessagesCurrentIndex:(state) => () => { 114 | return state.streaming.wsMessagesCurrentIndex; 115 | }, 116 | 117 | wsMessagesLength:(state) => () =>{ 118 | return state.streaming.wsMessages.length; 119 | }, 120 | 121 | isStartingTypingWsMessages:(state)=>()=>{ 122 | return state.streaming.isStartingTypingWsMessages; 123 | } 124 | }; 125 | -------------------------------------------------------------------------------- /lex-web-ui/src/store/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | /* global atob Blob URL */ 15 | /* eslint no-console: ["error", { allow: ["info", "warn", "error"] }] */ 16 | /* eslint no-param-reassign: off */ 17 | 18 | import initialState from '@/store/state'; 19 | import getters from '@/store/getters'; 20 | import mutations from '@/store/mutations'; 21 | import actions from '@/store/actions'; 22 | 23 | export default { 24 | // prevent changes outside of mutation handlers 25 | strict: (process.env.NODE_ENV === 'development'), 26 | state: initialState, 27 | getters, 28 | mutations, 29 | actions, 30 | }; 31 | -------------------------------------------------------------------------------- /lex-web-ui/src/store/state.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | /** 15 | * Sets up the initial state of the store 16 | */ 17 | import { config } from '@/config'; 18 | 19 | export const chatMode = { 20 | BOT: 'bot', 21 | LIVECHAT: 'livechat', 22 | }; 23 | 24 | export const liveChatStatus = { 25 | REQUESTED: 'requested', 26 | REQUEST_USERNAME: 'request_username', 27 | INITIALIZING: 'initializing', 28 | CONNECTING: 'connecting', 29 | ESTABLISHED: 'established', 30 | DISCONNECTED: 'disconnected', 31 | ENDED: 'ended', 32 | }; 33 | 34 | 35 | export default { 36 | version: (process.env.PACKAGE_VERSION) ? 37 | process.env.PACKAGE_VERSION : '0.0.0', 38 | chatMode: chatMode.BOT, 39 | lex: { 40 | acceptFormat: 'audio/ogg', 41 | dialogState: '', 42 | isInterrupting: false, 43 | isProcessing: false, 44 | isPostTextRetry: false, 45 | retryCountPostTextTimeout: 0, 46 | allowStreamingResponses: false, 47 | inputTranscript: '', 48 | intentName: '', 49 | message: '', 50 | responseCard: null, 51 | sessionAttributes: ( 52 | config.lex && 53 | config.lex.sessionAttributes && 54 | typeof config.lex.sessionAttributes === 'object' 55 | ) ? { ...config.lex.sessionAttributes } : {}, 56 | slotToElicit: '', 57 | slots: {}, 58 | }, 59 | liveChat: { 60 | username: '', 61 | isProcessing: false, 62 | status: liveChatStatus.DISCONNECTED, 63 | message: '', 64 | }, 65 | messages: [], 66 | utteranceStack: [], 67 | isBackProcessing: false, 68 | polly: { 69 | outputFormat: 'ogg_vorbis', 70 | voiceId: ( 71 | config.polly && 72 | config.polly.voiceId && 73 | typeof config.polly.voiceId === 'string' 74 | ) ? `${config.polly.voiceId}` : 'Joanna', 75 | }, 76 | botAudio: { 77 | canInterrupt: false, 78 | interruptIntervalId: null, 79 | autoPlay: false, 80 | isInterrupting: false, 81 | isSpeaking: false, 82 | }, 83 | recState: { 84 | isConversationGoing: false, 85 | isInterrupting: false, 86 | isMicMuted: false, 87 | isMicQuiet: true, 88 | isRecorderSupported: false, 89 | isRecorderEnabled: (config.recorder) ? !!config.recorder.enable : true, 90 | isRecording: false, 91 | silentRecordingCount: 0, 92 | }, 93 | 94 | isRunningEmbedded: false, // am I running in an iframe? 95 | isSFXOn: (config.ui) ? (!!config.ui.enableSFX && 96 | !!config.ui.messageSentSFX && !!config.ui.messageReceivedSFX) : false, 97 | isUiMinimized: false, // when running embedded, is the iframe minimized? 98 | initialUtteranceSent: false, // has the initial utterance already been sent 99 | isEnableLogin: false, // true when a login/logout menu should be displayed 100 | isForceLogin: false, // true when a login/logout menu should be displayed 101 | isLoggedIn: false, // when running with login/logout enabled 102 | isSaveHistory: false, // when running with saveHistory enabled 103 | isEnableLiveChat: false, // when running with enableLiveChat enabled 104 | hasButtons: false, // does the response card have buttons? 105 | tokens: {}, 106 | config, 107 | awsCreds: { 108 | provider: 'cognito', // cognito|parentWindow 109 | }, 110 | 111 | streaming:{ 112 | wssEndpointWithStage:'', // wss://{domain}/{stage} 113 | wsMessages:[], 114 | wsMessagesCurrentIndex:0, 115 | wsMessagesString:'', 116 | isStartingTypingWsMessages:true 117 | } 118 | }; 119 | -------------------------------------------------------------------------------- /lex-web-ui/src/store/talkdesk-live-chat-handlers.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | /** 15 | * Vuex store recorder handlers 16 | */ 17 | 18 | /* eslint no-console: ["error", { allow: ["info", "warn", "error", "time", "timeEnd"] }] */ 19 | /* eslint no-param-reassign: ["error", { "props": false }] */ 20 | import { liveChatStatus } from '@/store/state'; 21 | 22 | export const initTalkDeskLiveChat = (context) => { 23 | 24 | console.log('custom initlivechat'); 25 | const liveChatSession = new WebSocket(`${context.state.config.connect.talkDeskWebsocketEndpoint}?conversationId=${context.state.lex.sessionAttributes.talkdesk_conversation_id}`); 26 | 27 | liveChatSession.onopen = (response) => { 28 | console.info(`successful connection: ${JSON.stringify(response)}`); 29 | context.commit('setLiveChatStatus', liveChatStatus.ESTABLISHED); 30 | context.dispatch('pushLiveChatMessage', { 31 | type: 'agent', 32 | text: context.state.config.connect.agentJoinedMessage, 33 | }); 34 | } 35 | 36 | liveChatSession.onerror = (error) => { 37 | console.error(`Error occurred in live chat ${JSON.stringify(error)}`); 38 | context.commit('setLiveChatStatus', liveChatStatus.ENDED); 39 | } 40 | 41 | liveChatSession.onmessage = (event) => { 42 | const { event_type, content, author_name } = JSON.parse(event.data); 43 | console.info('Received message data:', event.data); 44 | console.log(event_type, content); 45 | let type = 'agent'; 46 | if(event_type == 'message_created') { 47 | context.dispatch('liveChatAgentJoined'); 48 | context.commit('setIsLiveChatProcessing', false); 49 | context.dispatch('pushLiveChatMessage', { 50 | type, 51 | text: content, 52 | agentName: author_name 53 | }); 54 | } 55 | if(event_type == 'conversation_ended') { 56 | context.dispatch('agentInitiatedLiveChatEnd'); 57 | } 58 | } 59 | 60 | return liveChatSession; 61 | }; 62 | 63 | export const sendTalkDeskChatMessage = (context, liveChatSession, message) => { 64 | const payload = { 65 | action: "onMessage", 66 | message, 67 | conversationId: context.state.lex.sessionAttributes.talkdesk_conversation_id 68 | } 69 | console.log('sendChatMessage', payload); 70 | liveChatSession.send(JSON.stringify(payload)); 71 | }; 72 | 73 | export const requestTalkDeskLiveChatEnd = (context, liveChatSession, requester) => { 74 | console.info('liveChatHandler: requestLiveChatEnd', liveChatSession); 75 | liveChatSession.close(4000, `conversationId:${context.state.lex.sessionAttributes.talkdesk_conversation_id}`); 76 | context.commit('setLiveChatStatus', liveChatStatus.ENDED); 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /lex-web-ui/test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count; 11 | this.expected = count; 12 | this.pass = function (val) { 13 | return val === this.expected; 14 | } 15 | this.value = function (res) { 16 | return res.value; 17 | } 18 | this.command = function (cb) { 19 | var self = this; 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length; 22 | }, [selector], function (res) { 23 | cb.call(self, res); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lex-web-ui/test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true, 35 | // lex-web-ui: allow microphone 36 | chromeOptions: { 37 | args: [ 38 | 'use-fake-device-for-media-stream', 39 | 'use-fake-ui-for-media-stream', 40 | ] 41 | } 42 | } 43 | }, 44 | 45 | firefox: { 46 | desiredCapabilities: { 47 | browserName: 'firefox', 48 | javascriptEnabled: true, 49 | acceptSslCerts: true 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lex-web-ui/test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | var server; 3 | process.env.NODE_ENV = 'testing'; 4 | 5 | // lex-web-ui: added ability to test using running web server 6 | var config = require('../../config') 7 | var devServerPort = config.dev.port; 8 | var devServerPath = config.dev.assetsPublicPath; 9 | var http = require('http'); 10 | 11 | var request = http.get({ 12 | hostname: 'localhost', 13 | port: devServerPort, 14 | path: devServerPath, 15 | }); 16 | 17 | request.on('response', (response) => { 18 | if (response.statusCode === 200) { 19 | runNightwatch(); 20 | } 21 | }); 22 | 23 | request.on('error', (error) => { 24 | if (error.code === 'ECONNREFUSED' || error.code === 'ECONNRESET') { 25 | startDevServer(); 26 | } else { 27 | throw error; 28 | } 29 | }); 30 | 31 | request.on('timeout', () => { 32 | startDevServer(); 33 | }); 34 | 35 | request.setTimeout(5000); 36 | request.end(); 37 | 38 | function startDevServer() { 39 | server = require('../../build/dev-server.js'); 40 | server.ready.then(() => { 41 | runNightwatch(); 42 | }); 43 | } 44 | 45 | function runNightwatch() { 46 | // 2. run the nightwatch test suite against it 47 | // to run in additional browsers: 48 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 49 | // 2. add it to the --env flag below 50 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 51 | // For more information on Nightwatch's config file, see 52 | // http://nightwatchjs.org/guide#settings-file 53 | var opts = process.argv.slice(2); 54 | if (opts.indexOf('--config') === -1) { 55 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']); 56 | } 57 | if (opts.indexOf('--env') === -1) { 58 | opts = opts.concat(['--env', 'chrome']); 59 | } 60 | 61 | var spawn = require('cross-spawn'); 62 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }); 63 | 64 | runner.on('exit', function (code) { 65 | server && server.close(); 66 | process.exit(code); 67 | }); 68 | 69 | runner.on('error', function (err) { 70 | server && server.close(); 71 | throw err; 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /lex-web-ui/test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | var config = require('../../../src/config/config.test.json'); 5 | 6 | module.exports = { 7 | 'stand-alone app e2e tests': function test(browser) { 8 | // automatically uses dev Server port from /config.index.js 9 | // default: http://localhost:8080 10 | // see nightwatch.conf.js 11 | const devServer = browser.globals.devServerURL; 12 | 13 | browser 14 | .url(devServer) 15 | .waitForElementVisible('#lex-app', 5000) 16 | .waitForElementVisible('#lex-web', 5000) 17 | .assert.title(config.ui.pageTitle) 18 | .assert.elementPresent('.toolbar') 19 | .assert.elementCount('img', 1) 20 | .assert.containsText('.toolbar__title', config.ui.toolbarTitle) 21 | .assert.elementPresent('.message-list') 22 | .waitForElementVisible('.message-text', 5000) 23 | .assert.containsText('.message-text', config.lex.initialText) 24 | .assert.elementPresent('.input-container') 25 | .assert.elementPresent('.recorder-status') 26 | .assert.elementPresent('.status-text') 27 | .assert.elementPresent('.voice-controls') 28 | .getLog('browser', function(logEntriesArray) { 29 | console.log('Log length: ' + logEntriesArray.length); 30 | logEntriesArray.forEach(function(log) { 31 | console.log('[' + log.level + '] ' + log.timestamp + ' : ' + log.message); 32 | }); 33 | }) 34 | .end(); 35 | }, 36 | // TODO move this test to the top loader 37 | 'iframe sample app e2e tests': function test(browser) { 38 | const devServer = browser.globals.devServerURL; 39 | 40 | browser 41 | .url(devServer + '/parent.html') 42 | .waitForElementVisible('.lex-web-ui-iframe', 5000) 43 | .waitForElementPresent('.lex-web-ui-iframe iframe', 5000) 44 | .waitForElementPresent('script#aws-script', 5000) 45 | .waitForElementPresent('script#aws_bots_config-script', 5000) 46 | .waitForElementPresent('link#lex-web-ui-loader-css', 5000) 47 | .getLog('browser', function(logEntriesArray) { 48 | console.log('Log length: ' + logEntriesArray.length); 49 | logEntriesArray.forEach(function(log) { 50 | console.log('[' + log.level + '] ' + log.timestamp + ' : ' + log.message); 51 | }); 52 | }) 53 | .end(); 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /lex-web-ui/test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lex-web-ui/test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | Vue.config.productionTip = false; 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/); 7 | testsContext.keys().forEach(testsContext); 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context( 13 | '../../src', 14 | true, 15 | // lex-web-ui: added LexApp.vue to exception 16 | /^\.\/(?!main(\.js)?|(LexApp(\.vue)?)$)/, 17 | ); 18 | srcContext.keys().forEach(srcContext); 19 | -------------------------------------------------------------------------------- /lex-web-ui/test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build/webpack.test.conf'); 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], 16 | reporters: ['spec', 'coverage'], 17 | files: ['./index.js'], 18 | preprocessors: { 19 | './index.js': ['webpack', 'sourcemap'] 20 | }, 21 | webpack: webpackConfig, 22 | webpackMiddleware: { 23 | noInfo: true, 24 | }, 25 | coverageReporter: { 26 | dir: './coverage', 27 | reporters: [ 28 | { type: 'lcov', subdir: '.' }, 29 | { type: 'text-summary' }, 30 | ] 31 | }, 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /lex-web-ui/test/unit/specs/LexWeb.spec.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import Vuetify from 'vuetify'; 5 | 6 | import LexWeb from '@/components/LexWeb'; 7 | import { Loader as LexWebUi } from '@/lex-web-ui'; 8 | import { config } from '@/config'; 9 | 10 | /* eslint no-console: ["error", { allow: ["warn", "error", "info"] }] */ 11 | 12 | describe('LexWeb.vue', () => { 13 | let lexWebUi; 14 | let vm; 15 | 16 | beforeEach(() => { 17 | Vue.use(Vuex); 18 | Vue.use(Vuetify); 19 | 20 | lexWebUi = new LexWebUi(); 21 | vm = new Vue({ 22 | store: lexWebUi.store, 23 | template: '', 24 | components: { LexWeb }, 25 | }); 26 | 27 | // disable recorder 28 | vm.$store.commit('setIsRecorderEnabled', false); 29 | }); 30 | 31 | afterEach(() => { 32 | vm.$destroy(); 33 | }); 34 | 35 | it('should render sub components', () => { 36 | vm.$mount(); 37 | 38 | const toolbar = vm.$el.querySelector('.toolbar'); 39 | const toolbarTitle = vm.$el.querySelector('.toolbar__title'); 40 | const messageList = vm.$el.querySelector('.message-list'); 41 | const inputContainer = vm.$el.querySelector('.input-container'); 42 | const recorderStatus = vm.$el.querySelector('.recorder-status'); 43 | 44 | expect(toolbar, 'toolbar').is.not.equal(null); 45 | expect(toolbarTitle, 'toolbar title').is.not.equal(null); 46 | expect(toolbarTitle.textContent, 'toolbar title') 47 | .to.contain(config.ui.toolbarTitle); 48 | 49 | expect(messageList, 'message list').is.not.equal(null); 50 | expect(inputContainer, 'input container').is.not.equal(null); 51 | expect(recorderStatus, 'recorder status').is.not.equal(null); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /lex-web-ui/test/unit/specs/MessageList.spec.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import Vuetify from 'vuetify'; 5 | 6 | import MessageList from '@/components/MessageList'; 7 | import { Store } from '@/lex-web-ui'; 8 | 9 | /* eslint no-console: ["error", { allow: ["warn", "error", "info"] }] */ 10 | /* eslint-disable prefer-destructuring */ 11 | /* eslint-disable prefer-arrow-callback */ 12 | /* eslint-disable func-names */ 13 | 14 | describe('MessageList.vue', function () { 15 | let vm; 16 | let store; 17 | 18 | beforeEach(() => { 19 | Vue.use(Vuex); 20 | Vue.use(Vuetify); 21 | 22 | store = new Vuex.Store({ 23 | ...Store, 24 | }); 25 | 26 | vm = new Vue({ 27 | store, 28 | template: '', 29 | components: { MessageList }, 30 | }); 31 | 32 | vm.$mount(); 33 | }); 34 | 35 | afterEach(() => { 36 | vm.$destroy(); 37 | }); 38 | 39 | it('should have the "message-list" class on root element', function () { 40 | const classList = [...vm.$el.classList]; 41 | expect(classList, 'class list').to.contain('message-list'); 42 | }); 43 | 44 | it('should add a message element when pushing a message to the store', function () { 45 | const messageElemCount = vm.$el.querySelectorAll('.message').length; 46 | vm.$store.commit('pushMessage', { text: 'test', type: 'bot' }); 47 | return vm.$nextTick() 48 | .then(() => { 49 | const elemDiff = vm.$el.querySelectorAll('.message').length - messageElemCount; 50 | expect(elemDiff, 'message element difference').to.equal(1); 51 | }); 52 | }); 53 | 54 | it('should add a "message-" class to messages', function () { 55 | const type = 'test123'; 56 | vm.$store.commit('pushMessage', { type, text: 'test' }); 57 | return vm.$nextTick() 58 | .then(() => { 59 | const messagesRefLen = vm.$refs.ml.$refs.messages.length; 60 | const lastMessageEl = vm.$refs.ml.$refs.messages[messagesRefLen - 1]; 61 | const lastMessageClassList = [...lastMessageEl.$el.classList]; 62 | expect(lastMessageClassList, 'last message class list') 63 | .to.contain(`message-${type}`); 64 | }); 65 | }); 66 | 67 | it('should call the scrollDow method when adding a message', function () { 68 | const scrollDownSpy = sinon.spy(vm.$refs.ml, 'scrollDown'); 69 | vm.$store.commit('pushMessage', { text: 'test', type: 'human' }); 70 | return vm.$nextTick() 71 | .then(() => { 72 | expect(scrollDownSpy, 'scrollDown method') 73 | .to.have.callCount(1); 74 | scrollDownSpy.restore(); 75 | }); 76 | }); 77 | 78 | it('should handle the "scrollDown" event by calling scrollDown method', function () { 79 | vm.$store.commit('pushMessage', { text: 'test', type: 'human' }); 80 | const scrollDownSpy = sinon.spy(vm.$refs.ml, 'scrollDown'); 81 | expect(scrollDownSpy, 'scrollDown method').to.have.callCount(0); 82 | vm.$refs.ml.$refs.messages[0].$emit('scrollDown'); 83 | return vm.$nextTick() 84 | .then(() => { 85 | expect(scrollDownSpy, 'scrollDown method') 86 | .to.have.callCount(1); 87 | scrollDownSpy.restore(); 88 | }); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /lex-web-ui/test/unit/specs/lex-web-ui.spec.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | 5 | import { Loader as LexWebUi } from '@/lex-web-ui'; 6 | 7 | /* eslint no-console: ["error", { allow: ["warn", "error", "info"] }] */ 8 | 9 | describe('lex-web-ui.js', () => { 10 | let vm; 11 | let lexWebUi; 12 | 13 | beforeEach(() => { 14 | Vue.use(Vuex); 15 | 16 | lexWebUi = new LexWebUi(); 17 | 18 | vm = new Vue({ 19 | store: lexWebUi.store, 20 | template: '', 21 | }); 22 | }); 23 | 24 | afterEach(() => { 25 | vm.$destroy(); 26 | }); 27 | 28 | it('should initialize $lexWebUi', () => { 29 | expect(vm, 'vue').to.have.a.property('$lexWebUi') 30 | .that.is.an('object') 31 | .that.includes.keys('config', 'awsConfig', 'lexRuntimeClient', 'pollyClient'); 32 | 33 | expect(vm.$lexWebUi.awsConfig, '$lexWebUi awsConfig') 34 | .to.be.an('object') 35 | .that.includes.keys('credentials', 'region'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /lex-web-ui/test/unit/specs/store.spec.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | 3 | import Vue from 'vue'; 4 | import Vuex from 'vuex'; 5 | import { config } from '@/config'; 6 | import Store from '@/store'; 7 | 8 | /* eslint no-console: ["error", { allow: ["warn", "error", "info"] }] */ 9 | 10 | describe('store', () => { 11 | let store; 12 | let vm; 13 | 14 | beforeEach(() => { 15 | store = new Vuex.Store({ ...Store }); 16 | vm = new Vue({ 17 | store, 18 | }); 19 | }); 20 | 21 | afterEach(() => { 22 | vm.$destroy(); 23 | }); 24 | 25 | it('loads the initial config at build time from a JSON file', () => { 26 | expect(vm.$store.state.config, 'state config').to.have.all.key(config); 27 | expect(vm.$store.state.config, 'state config').to.deep.include(config); 28 | }); 29 | 30 | it('inits credentials', () => { 31 | const creds = { accessKeyId: 'AKI' }; 32 | const getPromise = sinon.stub().resolves(creds); 33 | const credsStub = { getPromise, ...creds }; 34 | 35 | return vm.$store.dispatch('initCredentials', credsStub) 36 | .then((c) => { 37 | // sinon-chai expect 38 | expect(getPromise, 'getPromise').to.have.callCount(1); 39 | expect(c, 'credentials').to.have.keys('accessKeyId', 'getPromise'); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-lex-web-ui", 3 | "version": "0.23.0", 4 | "description": "Sample Amazon Lex Web Interface", 5 | "main": "dist/lex-web-ui.min.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/awslabs/aws-lex-web-ui.git" 9 | }, 10 | "keywords": [ 11 | "aws", 12 | "lex", 13 | "bot", 14 | "chatbot", 15 | "web", 16 | "interface", 17 | "ui" 18 | ], 19 | "author": "AWS", 20 | "license": "SEE LICENSE IN LICENSE", 21 | "bugs": { 22 | "url": "https://github.com/awslabs/aws-lex-web-ui/issues" 23 | }, 24 | "homepage": "https://github.com/awslabs/aws-lex-web-ui#readme", 25 | "scripts": { 26 | "build-prod": "webpack --mode production --env production", 27 | "build-dev": "webpack --mode development", 28 | "build": "npm run build-dev && npm run build-prod", 29 | "dev": "webpack-dev-server --mode development --hot", 30 | "lint": "eslint --format node_modules/eslint-formatter-friendly src/lex-web-ui-loader" 31 | }, 32 | "dependencies": { 33 | "amazon-cognito-auth-js": "^1.2.4", 34 | "@aws-sdk/client-cognito-identity": "3.470.0", 35 | "@aws-sdk/credential-providers": "3.470.0", 36 | "core-js": "^3.6.5", 37 | "jsdom": "^22.0.0", 38 | "jwt-decode": "^4.0.0", 39 | "process": "^0.11.10", 40 | "regenerator-runtime": "^0.13.5" 41 | }, 42 | "devDependencies": { 43 | "@babel/core": "^7.23.5", 44 | "@babel/plugin-transform-runtime": "^7.23.4", 45 | "@babel/preset-env": "^7.23.5", 46 | "babel-eslint": "^10.1.0", 47 | "babel-loader": "^9.1.3", 48 | "copy-webpack-plugin": "^11.0.0", 49 | "css-loader": "^6.8.1", 50 | "cssnano": "^6.0.1", 51 | "eslint": "^8.55.0", 52 | "eslint-config-airbnb-base": "^15.0.0", 53 | "eslint-formatter-friendly": "^7.0.0", 54 | "eslint-plugin-import": "^2.20.2", 55 | "eslint-webpack-plugin": "^4.0.1", 56 | "express": "^4.17.1", 57 | "html-webpack-plugin": "^5.5.4", 58 | "mini-css-extract-plugin": "^2.7.6", 59 | "postcss": "^8.4.32", 60 | "postcss-import": "^15.1.0", 61 | "postcss-loader": "^7.3.3", 62 | "postcss-preset-env": "^9.3.0", 63 | "webpack": "^5.89.0", 64 | "webpack-cli": "^5.1.4", 65 | "webpack-dev-server": "^5.2.1" 66 | }, 67 | "engines": { 68 | "node": ">=18.0.0", 69 | "npm": ">=10.0.0" 70 | }, 71 | "browserslist": [ 72 | "defaults and fully supports es6-module" 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'postcss-preset-env': {}, 5 | 'cssnano': {} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // Usage: npm start 2 | // Used for local serving and quick dev/testing of the prebuilt files. 3 | // For heavy development, you should instead use the `npm run dev` command 4 | // under the lex-web-ui dir 5 | const express = require('express'); 6 | const path = require('path'); 7 | 8 | const port = process.env.PORT || 8000; 9 | const publicPath = '/'; 10 | 11 | const distDir = path.join(__dirname, 'dist'); 12 | const configDir = path.join(__dirname, 'src/config'); 13 | const app = express(); 14 | 15 | app.use(publicPath, express.static(configDir)); 16 | app.use(publicPath, express.static(distDir)); 17 | 18 | app.listen(port, function () { 19 | console.log(`App listening on: http://localhost:${port}`); 20 | }); 21 | -------------------------------------------------------------------------------- /src/config/.gitattributes: -------------------------------------------------------------------------------- 1 | *.js* merge=ours 2 | -------------------------------------------------------------------------------- /src/config/default-lex-web-ui-loader-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "region": "us-east-1", 3 | "cognito": { 4 | "poolId": "", 5 | "aws_cognito_region": "us-east-1", 6 | "region": "us-east-1" 7 | }, 8 | "connect": { 9 | "contactFlowId" : "", 10 | "instanceId" : "", 11 | "apiGatewayEndpoint" : "", 12 | "promptForNameMessage": "Before starting a live chat, please tell me your name?", 13 | "waitingForAgentMessage": "Thanks for waiting. An agent will be with you when available.", 14 | "waitingForAgentMessageIntervalSeconds": 60 15 | }, 16 | "lex": { 17 | "initialText": "You can ask me for help ordering flowers. Just type \"order flowers\" or click on the mic and say it.", 18 | "initialSpeechInstruction": "Say 'Order Flowers' to get started.", 19 | "initialUtterance": "", 20 | "region": "us-east-1", 21 | "retryOnLexPostTextTimeout": false, 22 | "retryCountPostTextTimeout": 1, 23 | "v2BotId": "", 24 | "v2BotAliasId": "", 25 | "v2BotLocaleId": "", 26 | "allowStreamingResponses": false, 27 | "streamingWebSocketEndpoint": "", 28 | "streamingDynamoDbTable": "" 29 | }, 30 | "ui": { 31 | "parentOrigin": "http://localhost:8000", 32 | "toolbarTitle": "Order Flowers", 33 | "toolbarLogo": "", 34 | "positiveFeedbackIntent": "Thumbs up", 35 | "negativeFeedbackIntent": "Thumbs down", 36 | "helpIntent": "Help", 37 | "helpContent": {}, 38 | "enableLogin": false, 39 | "enableSFX": false, 40 | "forceLogin": false, 41 | "AllowSuperDangerousHTMLInMessage": true, 42 | "shouldDisplayResponseCardTitle": false, 43 | "minButtonContent": "", 44 | "hideInputFieldsForButtonResponse": false, 45 | "pushInitialTextOnRestart": false, 46 | "directFocusToBotInput": false, 47 | "showDialogStateIcon": false, 48 | "backButton": false, 49 | "messageMenu": true, 50 | "hideButtonMessageBubble": false, 51 | "saveHistory": false, 52 | "enableLiveChat": false, 53 | "toolbarStartLiveChatLabel": "Start Live Chat", 54 | "toolbarEndLiveChatLabel": "End Live Chat", 55 | "toolbarStartLiveChatIcon": "people_alt", 56 | "toolbarEndLiveChatIcon": "call_end" 57 | }, 58 | "polly": { 59 | "voiceId": "Salli" 60 | }, 61 | "recorder": { 62 | "preset": "speech_recognition" 63 | }, 64 | "iframe": { 65 | "iframeOrigin": "", 66 | "shouldLoadIframeMinimized": false, 67 | "iframeSrcPath": "/index.html#/?lexWebUiEmbed=true" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/config/lex-web-ui-loader-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "connect": { 3 | "contactFlowId": "", 4 | "instanceId": "", 5 | "apiGatewayEndpoint": "", 6 | "promptForNameMessage": "", 7 | "waitingForAgentMessage": "", 8 | "waitingForAgentMessageIntervalSeconds": "", 9 | "agentJoinedMessage": "", 10 | "agentLeftMessage": "", 11 | "chatEndedMessage": "", 12 | "attachChatTranscript": "", 13 | "endLiveChatUtterance": "", 14 | "transcriptMessageDelayInMsec": "", 15 | "transcriptRedactRegex": "" 16 | } 17 | } -------------------------------------------------------------------------------- /src/dependencies/initiate-loader.js: -------------------------------------------------------------------------------- 1 | // In the most simple form, you can load the component in a single statement: 2 | // new ChatBotUiLoader.FullPageLoader().load(); 3 | 4 | // The script below break the process into parts to further illustrate 5 | // the load process. 6 | 7 | // The ChatBotUiLoader variable contains the FullPageLoader field which is a 8 | // constructor for the loader. 9 | var Loader = ChatBotUiLoader.FullPageLoader; 10 | 11 | // The loader constructor supports various configurable options used to 12 | // control how the component configuration and dependencies are retrieved. 13 | // In this case, we are just passing one option (which doesn't changethe 14 | // default) for illustration purposes. 15 | var loaderOpts = { 16 | // The following option controls if the local config should be ignored 17 | // when running this page embedded in an iframe. 18 | // If set to true, only passes the parentOrigin field when run as an 19 | // iframe and delegates the config to the parent 20 | shouldIgnoreConfigWhenEmbedded: true, 21 | 22 | // Controls if it should load minimized production dependecies 23 | // defaults to true for production builds and false in development 24 | shouldLoadMinDeps: true, 25 | }; 26 | 27 | // Instantiate the loader by optionally passing the loader options to 28 | // control its behavior. You may leave the options empty if you wish 29 | // to take the defaults which works in most cases. 30 | var loader = new Loader(loaderOpts); 31 | 32 | // When loading the chatbot UI component, you can optionally pass it a 33 | // configuration object 34 | var chatbotUiConfig = { 35 | lex: { 36 | sessionAttributes: { 37 | /* QNAClientFilter: '', */ 38 | userAgent: navigator.userAgent 39 | } 40 | } 41 | }; 42 | 43 | // Calling the load function of the loader does a few things: 44 | // 1. Loads JavaScript and CSS dependencies to the DOM 45 | // 2. Loads the chatbot UI configuration from various sources 46 | // (e.g. JSON file, event) 47 | // 3. Instantiates the chatbot UI component in the DOM 48 | loader 49 | .load(chatbotUiConfig) 50 | .then(function () { console.log('ChatBotUiLoader loaded'); }) 51 | .catch(function (error) { console.error(error); }); -------------------------------------------------------------------------------- /src/initiate-chat-lambda/index.js: -------------------------------------------------------------------------------- 1 | const { ConnectClient, StartChatContactCommand } = require("@aws-sdk/client-connect"); 2 | const client = new ConnectClient({ region: process.env.REGION }); 3 | const parentOrigin = process.env.PARENT_ORIGIN; 4 | 5 | exports.handler = (event, context, callback) => { 6 | console.log("Received event: " + JSON.stringify(event)); 7 | const body = JSON.parse(event["body"]); 8 | console.log(`parent origin in environment: ${parentOrigin}`); 9 | 10 | startChatContact(body).then((startChatResult) => { 11 | callback(null, buildSuccessfulResponse(startChatResult)); 12 | }).catch((err) => { 13 | console.log("caught error " + err); 14 | callback(null, buildResponseFailed(err)); 15 | }); 16 | }; 17 | 18 | async function startChatContact(body) { 19 | let contactFlowId = ""; 20 | if (body.hasOwnProperty('ContactFlowId')) { 21 | contactFlowId = body["ContactFlowId"]; 22 | } 23 | console.log("CF ID: " + contactFlowId); 24 | 25 | let instanceId = ""; 26 | if (body.hasOwnProperty('InstanceId')) { 27 | instanceId = body["InstanceId"]; 28 | } 29 | console.log("Instance ID: " + instanceId); 30 | 31 | let initialMsgContent = ""; 32 | let initialMsgContentType = ""; 33 | if (body.hasOwnProperty("InitialMessage")) { 34 | if (body["InitialMessage"].hasOwnProperty("Content")) { 35 | initialMsgContent = body["InitialMessage"]["Content"]; 36 | 37 | } 38 | if (body["InitialMessage"].hasOwnProperty("ContentType")) { 39 | initialMsgContentType = body["InitialMessage"]["ContentType"]; 40 | } 41 | } 42 | 43 | let attributes = ""; 44 | if (body.hasOwnProperty("Attributes")) { 45 | attributes = body["Attributes"]; 46 | } 47 | 48 | const startChat = { 49 | "InstanceId": instanceId == "" ? process.env.INSTANCE_ID : instanceId, 50 | "ContactFlowId": contactFlowId == "" ? process.env.CONTACT_FLOW_ID : contactFlowId, 51 | "Attributes": attributes, 52 | "ChatDurationInMinutes": 60, 53 | "ParticipantDetails": { 54 | "DisplayName": body["ParticipantDetails"]["DisplayName"] 55 | } 56 | }; 57 | 58 | if (initialMsgContent && initialMsgContentType != "" ){ 59 | startChat.InitialMessage = { 60 | "Content": initialMsgContent, 61 | "ContentType": initialMsgContentType 62 | }; 63 | }; 64 | 65 | console.log('startChat params', startChat); 66 | const command = new StartChatContactCommand(startChat); 67 | try { 68 | const response = await client.send(command); 69 | return response; 70 | } catch (error) { 71 | console.log("Error starting the chat."); 72 | console.log(error, error.stack); 73 | return response; 74 | } 75 | } 76 | 77 | function buildSuccessfulResponse(result) { 78 | const response = { 79 | statusCode: 200, 80 | headers: { 81 | "Access-Control-Allow-Origin": parentOrigin, 82 | 'Content-Type': 'application/json', 83 | 'Access-Control-Allow-Credentials': true, 84 | 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent' 85 | }, 86 | body: JSON.stringify({ 87 | data: { startChatResult: result } 88 | }) 89 | }; 90 | console.log("RESPONSE" + JSON.stringify(response)); 91 | return response; 92 | } 93 | 94 | function buildResponseFailed(err) { 95 | const response = { 96 | statusCode: 500, 97 | headers: { 98 | "Access-Control-Allow-Origin": parentOrigin, 99 | 'Content-Type': 'application/json', 100 | 'Access-Control-Allow-Credentials': true, 101 | 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token' 102 | }, 103 | body: JSON.stringify({ 104 | data: { 105 | "Error": err 106 | } 107 | }) 108 | }; 109 | return response; 110 | } 111 | -------------------------------------------------------------------------------- /src/lex-web-ui-loader/css/lex-web-ui-fullpage.css: -------------------------------------------------------------------------------- 1 | #lex-web-ui-fullpage { 2 | height: 100%; 3 | width: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /src/lex-web-ui-loader/css/lex-web-ui-iframe.css: -------------------------------------------------------------------------------- 1 | .lex-web-ui-iframe { 2 | bottom: 1.5rem; 3 | display: none; /* hidden by default changed once iframe is loaded */ 4 | margin-bottom: 0px; 5 | margin-left: 2px; 6 | margin-right: 3vw; 7 | margin-top: 2px; 8 | max-width: 66vw; 9 | height: 80vh; /* dynamically changed on iframe maximize/minimize */ 10 | min-width: calc(50vw - 3vw); /* half viewport width minus margin right */ 11 | position: fixed; 12 | right: 0; 13 | z-index: 2147483637; /* max z-index (2147483647) - 10 */ 14 | } 15 | 16 | .lex-web-ui-iframe iframe { 17 | box-shadow: 0 15px 50px 0 rgba(0, 0, 0, 0.4); 18 | border-radius: 10px; 19 | } 20 | 21 | .lex-web-ui-iframe--show { 22 | display: flex; 23 | } 24 | 25 | .lex-web-ui-iframe--minimize { 26 | max-width: 190px !important; 27 | max-height: 85px !important; 28 | border-radius: 85px !important; 29 | min-width: 190px !important; 30 | } 31 | 32 | /* disable box shadow when minimized */ 33 | .lex-web-ui-iframe.lex-web-ui-iframe--minimize iframe { 34 | box-shadow: none; 35 | border-radius: none; 36 | } 37 | 38 | /* hide on very small resolutions */ 39 | @media only screen and (max-width: 240px), 40 | only screen and (max-height: 256px) 41 | { 42 | .lex-web-ui-iframe { 43 | display: none!important; 44 | } 45 | 46 | .lex-web-ui-iframe--minimize { 47 | max-width: 300px !important; 48 | max-height: 85px !important; 49 | } 50 | } 51 | /* take most space on small resolutions (smart phones) */ 52 | @media only screen 53 | and (min-width: 241px) 54 | and (max-width: 480px) { 55 | .lex-web-ui-iframe { 56 | min-width: 96vw; 57 | height: 84vh; 58 | margin-right: 2vw; 59 | align-self: center; 60 | } 61 | 62 | .lex-web-ui-iframe--minimize { 63 | max-width: 190px !important; 64 | max-height: 85px !important; 65 | border-radius: 85px !important; 66 | } 67 | 68 | } 69 | 70 | /* adjust down on medium resolutions */ 71 | @media only screen 72 | and (min-width: 481px) 73 | and (max-width: 960px) { 74 | .lex-web-ui-iframe { 75 | min-width: 90vw; 76 | } 77 | 78 | .lex-web-ui-iframe.lex-web-ui-iframe--show.lex-web-ui-iframe--minimize { 79 | max-width: 300px !important; 80 | max-height: 85px !important; 81 | border-radius: 85px !important; 82 | min-width: 85px !important; 83 | } 84 | } 85 | 86 | .lex-web-ui-iframe iframe { 87 | overflow: hidden; 88 | width: 100%; 89 | height: 100%; 90 | } 91 | -------------------------------------------------------------------------------- /src/lex-web-ui-loader/js/defaults/dependencies.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | /** 15 | * Default DependencyLoader dependencies 16 | * 17 | * Loads third-party libraries from CDNs. May want to host your own for production 18 | * 19 | * Relative URLs (not starting with http) are prepended with a base URL at run time 20 | */ 21 | export const dependenciesFullPage = { 22 | script: [ 23 | { 24 | name: 'Vue', 25 | url: './3.5.13_dist_vue.global.prod.js', 26 | canUseMin: false, 27 | }, 28 | { 29 | name: 'Vuex', 30 | url: './4.1.0_dist_vuex.js', 31 | canUseMin: true, 32 | }, 33 | { 34 | name: 'Vuetify', 35 | url: './3.8.3_dist_vuetify.js', 36 | canUseMin: true, 37 | }, 38 | { 39 | name: 'LexWebUi', 40 | url: './lex-web-ui.js', 41 | canUseMin: true, 42 | }, 43 | ], 44 | css: [ 45 | { 46 | name: 'roboto-material-icons', 47 | url: './material_icons.css', 48 | }, 49 | { 50 | name: 'vuetify', 51 | url: './3.8.3_dist_vuetify.css', 52 | canUseMin: true, 53 | }, 54 | { 55 | name: 'lex-web-ui', 56 | url: './lex-web-ui.css', 57 | canUseMin: true, 58 | }, 59 | { 60 | name: 'lex-web-ui-loader', 61 | url: './lex-web-ui-loader.css', 62 | }, 63 | ], 64 | }; 65 | 66 | export const dependenciesIframe = { 67 | css: [ 68 | { 69 | name: 'lex-web-ui-loader', 70 | url: './lex-web-ui-loader.css', 71 | }, 72 | ], 73 | script: [] 74 | }; 75 | -------------------------------------------------------------------------------- /src/lex-web-ui-loader/js/defaults/lex-web-ui.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | /** 15 | * Base configuration object structure 16 | * 17 | * NOTE: you probably don't want to be making config changes here but rather 18 | * use the config loader to override the defaults 19 | */ 20 | 21 | export const configBase = { 22 | region: '', 23 | lex: { }, 24 | cognito: { poolId: '' }, 25 | ui: { parentOrigin: '' }, 26 | polly: {}, 27 | connect: {}, 28 | recorder: {}, 29 | iframe: { 30 | iframeOrigin: '', 31 | iframeSrcPath: '', 32 | }, 33 | }; 34 | 35 | export default configBase; 36 | -------------------------------------------------------------------------------- /src/lex-web-ui-loader/js/defaults/loader.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | /** 15 | * Default options and config structure 16 | * 17 | * NOTE: you probably don't want to be making config changes here but rather 18 | * use the config loader to override the defaults 19 | */ 20 | 21 | /** 22 | * Default loader options 23 | * Apply both to iframe and full page 24 | */ 25 | export const options = { 26 | // base URL to be prepended to relative URLs of dependencies 27 | // if left empty, a relative path will still be used 28 | baseUrl: '/', 29 | 30 | // time to wait for config event 31 | configEventTimeoutInMs: 10000, 32 | 33 | // URL to download config JSON file 34 | // uses baseUrl if set as a relative URL (not starting with http) 35 | configUrl: './lex-web-ui-loader-config.json', 36 | 37 | // controls whether the local config should be ignored when running 38 | // embedded (e.g. iframe) in which case the parent page will pass the config 39 | // Only the parentOrigin config field is kept when set to true 40 | shouldIgnoreConfigWhenEmbedded: true, 41 | 42 | // controls whether the config should be obtained using events 43 | shouldLoadConfigFromEvent: false, 44 | 45 | // controls whether the config should be downloaded from `configUrl` 46 | shouldLoadConfigFromJsonFile: true, 47 | 48 | // Controls if it should load minimized production dependecies 49 | // set to true for production 50 | // NODE_ENV is injected at build time by webpack DefinePlugin 51 | shouldLoadMinDeps: (process.env.NODE_ENV === 'production'), 52 | }; 53 | 54 | /** 55 | * Default full page specific loader options 56 | */ 57 | export const optionsFullPage = { 58 | ...options, 59 | 60 | // DOM element ID where the chatbot UI will be mounted 61 | elementId: 'lex-web-ui-fullpage', 62 | }; 63 | 64 | /** 65 | * Default iframe specific loader options 66 | */ 67 | export const optionsIframe = { 68 | ...options, 69 | 70 | // DOM element ID where the chatbot UI will be mounted 71 | elementId: 'lex-web-ui-iframe', 72 | 73 | // div container class to insert iframe 74 | containerClass: 'lex-web-ui-iframe', 75 | 76 | // iframe source path. this is appended to the iframeOrigin 77 | // must use the LexWebUiEmbed=true query string to enable embedded mode 78 | iframeSrcPath: '/index.html#/?lexWebUiEmbed=true', 79 | }; 80 | -------------------------------------------------------------------------------- /src/lex-web-ui-loader/js/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | /* eslint no-console: ["error", { allow: ["warn", "error"] }] */ 15 | 16 | /** 17 | * Entry point to the chatbot-ui-loader.js library 18 | * Exports the loader classes 19 | */ 20 | 21 | // adds polyfills for ie11 compatibility 22 | import 'core-js/stable'; 23 | import 'regenerator-runtime/runtime'; 24 | 25 | import { configBase } from './defaults/lex-web-ui'; 26 | import { optionsIframe, optionsFullPage } from './defaults/loader'; 27 | import { dependenciesIframe, dependenciesFullPage } from './defaults/dependencies'; 28 | 29 | // import from lib 30 | import { DependencyLoader } from './lib/dependency-loader'; 31 | import { ConfigLoader } from './lib/config-loader'; 32 | import { IframeComponentLoader } from './lib/iframe-component-loader'; 33 | import { FullPageComponentLoader } from './lib/fullpage-component-loader'; 34 | 35 | // import CSS 36 | import '../css/lex-web-ui-fullpage.css'; 37 | import '../css/lex-web-ui-iframe.css'; 38 | 39 | /** 40 | * CustomEvent polyfill for IE11 41 | * https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill 42 | */ 43 | function setCustomEventShim() { 44 | if (typeof window.CustomEvent === 'function') { 45 | return false; 46 | } 47 | 48 | function CustomEvent( 49 | event, 50 | params = { bubbles: false, cancelable: false, detail: undefined }, 51 | ) { 52 | const evt = document.createEvent('CustomEvent'); 53 | evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); 54 | return evt; 55 | } 56 | 57 | CustomEvent.prototype = window.Event.prototype; 58 | window.CustomEvent = CustomEvent; 59 | 60 | return true; 61 | } 62 | 63 | /** 64 | * Base class used by the full page and iframe loaders 65 | */ 66 | class Loader { 67 | /** 68 | * @param {object} options - options controlling how the dependencies and 69 | * component configa are loaded 70 | */ 71 | constructor(options) { 72 | const { baseUrl } = options; 73 | // polyfill needed for IE11 74 | setCustomEventShim(); 75 | this.options = options; 76 | 77 | // append a trailing slash if not present in the baseUrl 78 | this.options.baseUrl = 79 | (this.options.baseUrl && baseUrl[baseUrl.length - 1] === '/') ? 80 | this.options.baseUrl : `${this.options.baseUrl}/`; 81 | 82 | this.confLoader = new ConfigLoader(this.options); 83 | } 84 | 85 | load(configParam = {}) { 86 | // merge empty constructor config and parameter config 87 | this.config = ConfigLoader.mergeConfig(this.config, configParam); 88 | 89 | // load dependencies 90 | return this.depLoader.load() 91 | // load dynamic config 92 | .then(() => this.confLoader.load(this.config)) 93 | // assign and merge dynamic config to this instance config 94 | .then((config) => { 95 | this.config = ConfigLoader.mergeConfig(this.config, config); 96 | }) 97 | .then(() => this.compLoader.load(this.config)); 98 | } 99 | } 100 | 101 | /** 102 | * Class used to to dynamically load the chatbot ui in a full page including its 103 | * dependencies and config 104 | */ 105 | export class FullPageLoader extends Loader { 106 | /** 107 | * @param {object} options - options controlling how the dependencies and 108 | * component config are loaded 109 | */ 110 | constructor(options = {}) { 111 | super({ ...optionsFullPage, ...options }); 112 | 113 | this.config = configBase; 114 | 115 | // run-time dependencies 116 | this.depLoader = new DependencyLoader({ 117 | shouldLoadMinDeps: this.options.shouldLoadMinDeps, 118 | dependencies: dependenciesFullPage, 119 | baseUrl: this.options.baseUrl, 120 | }); 121 | 122 | this.compLoader = new FullPageComponentLoader({ 123 | elementId: this.options.elementId, 124 | config: this.config, 125 | }); 126 | } 127 | 128 | load(configParam = {}) { 129 | return super.load(configParam); 130 | } 131 | } 132 | 133 | /** 134 | * Class used to to dynamically load the chatbot ui in an iframe 135 | */ 136 | export class IframeLoader extends Loader { 137 | /** 138 | * @param {object} options - options controlling how the dependencies and 139 | * component config are loaded 140 | */ 141 | constructor(options = {}) { 142 | super({ ...optionsIframe, ...options }); 143 | 144 | // chatbot UI component config 145 | this.config = configBase; 146 | 147 | // run-time dependencies 148 | this.depLoader = new DependencyLoader({ 149 | shouldLoadMinDeps: this.options.shouldLoadMinDeps, 150 | dependencies: dependenciesIframe, 151 | baseUrl: this.options.baseUrl, 152 | }); 153 | 154 | this.compLoader = new IframeComponentLoader({ 155 | config: this.config, 156 | containerClass: this.options.containerClass || 'lex-web-ui', 157 | elementId: this.options.elementId || 'lex-web-ui', 158 | }); 159 | } 160 | 161 | load(configParam = {}) { 162 | return super.load(configParam) 163 | .then(() => { 164 | // assign API to this object to make calls more succint 165 | this.api = this.compLoader.api; 166 | // make sure iframe and iframeSrcPath are set to values if not 167 | // configured by standard mechanisms. At this point, default 168 | // values from ./defaults/loader.js will be used. 169 | this.config.iframe = this.config.iframe || {}; 170 | this.config.iframe.iframeSrcPath = this.config.iframe.iframeSrcPath || 171 | this.mergeSrcPath(configParam); 172 | }); 173 | } 174 | 175 | /** 176 | * Merges iframe src path from options and iframe config 177 | */ 178 | mergeSrcPath(configParam) { 179 | const { iframe: iframeConfigFromParam } = configParam; 180 | const srcPathFromParam = 181 | iframeConfigFromParam && iframeConfigFromParam.iframeSrcPath; 182 | const { iframe: iframeConfigFromThis } = this.config; 183 | const srcPathFromThis = 184 | iframeConfigFromThis && iframeConfigFromThis.iframeSrcPath; 185 | 186 | return (srcPathFromParam || this.options.iframeSrcPath || srcPathFromThis); 187 | } 188 | } 189 | 190 | /** 191 | * chatbot loader library entry point 192 | */ 193 | export const ChatBotUiLoader = { 194 | FullPageLoader, 195 | IframeLoader, 196 | }; 197 | 198 | export default ChatBotUiLoader; 199 | -------------------------------------------------------------------------------- /src/lex-web-ui-loader/js/lib/loginutil.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Amazon Software License (the "License"). You may not use this file 5 | except in compliance with the License. A copy of the License is located at 6 | 7 | http://aws.amazon.com/asl/ 8 | 9 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" 10 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 11 | License for the specific language governing permissions and limitations under the License. 12 | */ 13 | 14 | /* eslint-disable prefer-template, no-console */ 15 | 16 | import { CognitoAuth } from 'amazon-cognito-auth-js'; 17 | import { jwtDecode } from "jwt-decode"; 18 | 19 | const loopKey = `login_util_loop_count`; 20 | const maxLoopCount = 5; 21 | 22 | function getLoopCount(config) { 23 | let loopCount = localStorage.getItem(`${config.appUserPoolClientId}${loopKey}`); 24 | if (loopCount === undefined || loopCount === null) { 25 | console.warn(`setting loopcount to string 0`); 26 | loopCount = "0"; 27 | } 28 | loopCount = Number.parseInt(loopCount); 29 | return loopCount; 30 | } 31 | 32 | function incrementLoopCount(config) { 33 | let loopCount = getLoopCount(config) 34 | localStorage.setItem(`${config.appUserPoolClientId}${loopKey}`, (loopCount + 1).toString()); 35 | console.warn(`loopCount is now ${loopCount + 1}`); 36 | } 37 | 38 | function getAuth(config) { 39 | const rd1 = window.location.protocol + '//' + window.location.hostname + window.location.pathname + '?loggedin=yes'; 40 | const rd2 = window.location.protocol + '//' + window.location.hostname + window.location.pathname + '?loggedout=yes'; 41 | const authData = { 42 | ClientId: config.appUserPoolClientId, // Your client id here 43 | AppWebDomain: config.appDomainName, 44 | TokenScopesArray: ['email', 'openid', 'profile'], 45 | RedirectUriSignIn: rd1, 46 | RedirectUriSignOut: rd2, 47 | }; 48 | 49 | if (config.appUserPoolIdentityProvider && config.appUserPoolIdentityProvider.length > 0) { 50 | authData.IdentityProvider = config.appUserPoolIdentityProvider; 51 | } 52 | 53 | const auth = new CognitoAuth(authData); 54 | auth.useCodeGrantFlow(); 55 | auth.userhandler = { 56 | onSuccess(session) { 57 | console.debug('Sign in success'); 58 | localStorage.setItem(`${config.appUserPoolClientId}idtokenjwt`, session.getIdToken().getJwtToken()); 59 | localStorage.setItem(`${config.appUserPoolClientId}accesstokenjwt`, session.getAccessToken().getJwtToken()); 60 | localStorage.setItem(`${config.appUserPoolClientId}refreshtoken`, session.getRefreshToken().getToken()); 61 | const myEvent = new CustomEvent('tokensavailable', { detail: 'initialLogin' }); 62 | document.dispatchEvent(myEvent); 63 | localStorage.setItem(`${config.appUserPoolClientId}${loopKey}`, "0"); 64 | }, 65 | onFailure(err) { 66 | console.debug('Sign in failure: ' + JSON.stringify(err, null, 2)); 67 | incrementLoopCount(config); 68 | }, 69 | }; 70 | return auth; 71 | } 72 | 73 | function completeLogin(config) { 74 | const auth = getAuth(config); 75 | const curUrl = window.location.href; 76 | const values = curUrl.split('?'); 77 | const minurl = '/' + values[1]; 78 | try { 79 | auth.parseCognitoWebResponse(curUrl); 80 | return true; 81 | } catch (reason) { 82 | console.debug('failed to parse response: ' + reason); 83 | console.debug('url was: ' + minurl); 84 | return false; 85 | } 86 | } 87 | 88 | function completeLogout(config) { 89 | localStorage.removeItem(`${config.appUserPoolClientId}idtokenjwt`); 90 | localStorage.removeItem(`${config.appUserPoolClientId}accesstokenjwt`); 91 | localStorage.removeItem(`${config.appUserPoolClientId}refreshtoken`); 92 | localStorage.removeItem('cognitoid'); 93 | console.debug('logout complete'); 94 | return true; 95 | } 96 | 97 | function logout(config) { 98 | /* eslint-disable prefer-template, object-shorthand, prefer-arrow-callback */ 99 | const auth = getAuth(config); 100 | auth.signOut(); 101 | localStorage.setItem(`${config.appUserPoolClientId}${loopKey}`, "0"); 102 | } 103 | 104 | const forceLogin = (config) => { 105 | login(config); 106 | } 107 | 108 | function login(config) { 109 | /* eslint-disable prefer-template, object-shorthand, prefer-arrow-callback */ 110 | if (getLoopCount(config) < maxLoopCount) { 111 | const auth = getAuth(config); 112 | const session = auth.getSignInUserSession(); 113 | setTimeout(function () { 114 | if ( !session.isValid()) { 115 | auth.getSession(); 116 | } 117 | }, 500); 118 | } else { 119 | alert("max login tries exceeded"); 120 | localStorage.setItem(`${config.appUserPoolClientId}${loopKey}`, "0"); 121 | } 122 | } 123 | 124 | function refreshLogin(config, token, callback) { 125 | /* eslint-disable prefer-template, object-shorthand, prefer-arrow-callback */ 126 | if (getLoopCount(config) < maxLoopCount) { 127 | const auth = getAuth(config); 128 | auth.userhandler = { 129 | onSuccess(session) { 130 | console.debug('Sign in success'); 131 | localStorage.setItem(`${config.appUserPoolClientId}idtokenjwt`, session.getIdToken().getJwtToken()); 132 | localStorage.setItem(`${config.appUserPoolClientId}accesstokenjwt`, session.getAccessToken().getJwtToken()); 133 | localStorage.setItem(`${config.appUserPoolClientId}refreshtoken`, session.getRefreshToken().getToken()); 134 | const myEvent = new CustomEvent('tokensavailable', {detail: 'refreshLogin'}); 135 | document.dispatchEvent(myEvent); 136 | callback(session); 137 | }, 138 | onFailure(err) { 139 | console.debug('Sign in failure: ' + JSON.stringify(err, null, 2)); 140 | callback(err); 141 | }, 142 | }; 143 | auth.refreshSession(token); 144 | } else { 145 | alert("max login tries exceeded"); 146 | localStorage.setItem(loopKey, "0"); 147 | } 148 | } 149 | 150 | // return true if a valid token and has expired. return false in all other cases 151 | function isTokenExpired(token) { 152 | const decoded = jwtDecode(token); 153 | if (decoded) { 154 | const now = Date.now(); 155 | const expiration = decoded.exp * 1000; 156 | if (now > expiration) { 157 | return true; 158 | } 159 | } 160 | return false; 161 | } 162 | 163 | export { logout, login, forceLogin, completeLogin, completeLogout, getAuth, refreshLogin, isTokenExpired }; 164 | -------------------------------------------------------------------------------- /src/streaming-lambda/index.js: -------------------------------------------------------------------------------- 1 | const { PutCommand, DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb'); 2 | const { DynamoDBClient } = require('@aws-sdk/client-dynamodb'); 3 | 4 | const client = new DynamoDBClient({ apiVersion: '2012-08-10', region: process.env.AWS_REGION }); 5 | const docClient = DynamoDBDocumentClient.from(client); 6 | 7 | exports.handler = async event => { 8 | console.log("Received event: " + JSON.stringify(event)); 9 | const ttlTime = Date.now() / 1000 + 86400; //One day later in epoch time for TTL 10 | 11 | const command = new PutCommand({ 12 | TableName: process.env.TABLE_NAME, 13 | Item: { 14 | connectionId: event.requestContext.connectionId, 15 | sessionId: event.queryStringParameters.sessionId, 16 | ttl: ttlTime 17 | } 18 | }); 19 | 20 | try { 21 | await docClient.send(command); 22 | return { statusCode: 200, body: 'Connected.' }; 23 | } catch (err) { 24 | return { statusCode: 500, body: 'Failed to connect: ' + JSON.stringify(err) }; 25 | } 26 | }; -------------------------------------------------------------------------------- /src/website/custom-chatbot-style.css: -------------------------------------------------------------------------------- 1 | /* Example custom css file for lex-web-ui. Entire file is commented out as a default. Uncomment and 2 | adjust as needed. 3 | 4 | .toolbar.theme--dark { 5 | background-color: #2b2b2b !important; 6 | } 7 | 8 | .toolbar__title { 9 | font-family:"Sans-serif" !important; 10 | font-size: 1.875em !important; 11 | color: #ffffff !important; 12 | } 13 | 14 | .message-list-container { 15 | background-color: #dcdbdc !important 16 | } 17 | 18 | .message-bot .message-bubble { 19 | background-color: #eeedeb !important; 20 | } 21 | 22 | .message-human .message-bubble { 23 | background-color: #afcffa !important; 24 | } 25 | 26 | .message-bubble p { 27 | margin-bottom: 8px; 28 | } 29 | 30 | .message-bubble p:last-child { 31 | margin-bottom: 0px; 32 | } 33 | 34 | .message-bubble .message-text { 35 | padding-left: 0; 36 | padding-right: 0; 37 | line-height: 1.6; 38 | font-size: 1rem; 39 | } 40 | 41 | .message-bubble { 42 | border-radius: 10px !important; 43 | padding: 2px 18px !important; 44 | } 45 | 46 | .message-text { 47 | color: #000000; 48 | width: 100%; 49 | } 50 | 51 | .headline { 52 | font-size: 1.2rem !important; 53 | line-height: 1.4 !important; 54 | } 55 | 56 | .card__title { 57 | padding: 10px 16px !important; 58 | } 59 | 60 | .card__text { 61 | padding: 8px 16px 16px !important; 62 | line-height: 1.4; 63 | } 64 | 65 | .card__title.card__title--primary { 66 | background-color: #eeedeb !important; 67 | } 68 | 69 | 70 | .input-group--text-field input, 71 | .input-group--text-field textarea, 72 | .input-group--text-field label { 73 | font-size: 14px !important; 74 | } 75 | 76 | .card__actions .btn { 77 | margin: 4px 4px !important; 78 | font-size: 1em !important; 79 | min-width: 44px !important; 80 | background-color: #afcffa !important; 81 | } 82 | 83 | */ 84 | 85 | button.min-button { 86 | border-radius: 60px; 87 | } 88 | 89 | .message-button { 90 | display: none; 91 | } -------------------------------------------------------------------------------- /src/website/index.css: -------------------------------------------------------------------------------- 1 | /* put overriding CSS here, 2 | because of the configuration of the code it may be 3 | neccessary to include !important */ 4 | 5 | /* for feedback thumbs up thumbs down */ 6 | /* .feedback-icons-positive{ 7 | color: #E8EAF6 !important; 8 | padding: .125em; 9 | } 10 | 11 | .feedback-icons-positive:hover{ 12 | color:green !important; 13 | } 14 | 15 | .feedback-icons-negative{ 16 | color: #E8EAF6 !important; 17 | padding: .125em; 18 | } 19 | 20 | .feedback-icons-negative:hover{ 21 | color: red !important; 22 | } */ 23 | 24 | .message-text[data-v-4f01cfd4], div.message-text {font-size:15px;} 25 | -------------------------------------------------------------------------------- /src/website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | LexWebUi 18 | 19 | 20 | 21 | 27 | <%= htmlWebpackPlugin.tags.headTags %> 28 | 29 | 30 | 31 | 32 | 41 | <%= htmlWebpackPlugin.tags.bodyTags %> 42 | 43 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /templates/Makefile: -------------------------------------------------------------------------------- 1 | TEMPLATES := $(wildcard *.json *.yml *.yaml) 2 | 3 | # build output directory 4 | OUT := out 5 | # put output dir in VPATH to simplify finding dependencies 6 | VPATH := $(OUT) 7 | 8 | # build output files in the out directory using suffixes (e.g. .lint) 9 | LINT = $(addsuffix .lint, $(TEMPLATES)) 10 | VALIDATE = $(addsuffix .validate, $(TEMPLATES)) 11 | 12 | .DELETE_ON_ERROR: 13 | 14 | all: check 15 | .PHONY: all 16 | 17 | # create the output directory for tracking dependencies 18 | $(OUT): 19 | mkdir -p "$(@)" 20 | 21 | check: $(LINT) $(VALIDATE) | $(OUT) 22 | .PHONY: check 23 | 24 | # parse yaml/json with python to check syntax validity 25 | # creates .lint files in the out dir 26 | # detects if file is json or yaml by the file extension 27 | # imports json or yaml libraries and calls appropriate function 28 | $(LINT): %.lint: % | $(OUT) 29 | $(eval EXT := $(subst ., ,$(suffix $?))) @# remove dot from extension 30 | $(eval LIB := $(strip $(subst yml, yaml, $(EXT)))) @# s/yml/yaml/ 31 | @echo "[INFO] Linting $(LIB) syntax of template: $(?)" 32 | @sed -e 's#\![^[:blank:]]*[[:blank:]]\{0,1\}##gm' < "$(?)" | \ 33 | python -c 'import $(LIB), sys; print $(LIB).load(sys.stdin);' \ 34 | > "$(OUT)/$(?).lint" 35 | 36 | lint: $(LINT) | $(OUT) 37 | .PHONY: lint 38 | 39 | # check validity with cloudformation validate-template api 40 | $(VALIDATE): %.validate: % | $(OUT) 41 | @echo "[INFO] Validating cloudformation template: $(?)" 42 | @aws cloudformation validate-template --template-body \ 43 | "file://$(?)" > "$(OUT)/$(?).validate" 44 | 45 | validate: $(VALIDATE) | $(OUT) 46 | .PHONY: validate 47 | 48 | clean: 49 | -rm -f $(OUT)/* 50 | .PHONY: clean 51 | -------------------------------------------------------------------------------- /templates/custom-resources/cfnresponse.py: -------------------------------------------------------------------------------- 1 | # Copyright 2016-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Amazon Software License (the "License"). You may not use this file 4 | # except in compliance with the License. A copy of the License is located at 5 | # 6 | # http://aws.amazon.com/asl/ 7 | # 8 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" 9 | # BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 10 | # License for the specific language governing permissions and limitations under the License. 11 | 12 | import requests 13 | import json 14 | 15 | SUCCESS = "SUCCESS" 16 | FAILED = "FAILED" 17 | 18 | def send(event, context, responseStatus, responseData, physicalResourceId, reason): 19 | responseUrl = event['ResponseURL'] 20 | 21 | print(responseUrl) 22 | 23 | responseBody = {} 24 | responseBody['Status'] = responseStatus 25 | responseBody['Reason'] = ('Reason: ' + json_dump_format(reason) + 26 | '. See the details in CloudWatch Log Stream: ' + context.log_stream_name) 27 | responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name 28 | responseBody['StackId'] = event['StackId'] 29 | responseBody['RequestId'] = event['RequestId'] 30 | responseBody['LogicalResourceId'] = event['LogicalResourceId'] 31 | responseBody['Data'] = responseData 32 | 33 | json_responseBody = json_dump_format(responseBody) 34 | 35 | print("Response body:\n" + json_responseBody) 36 | 37 | headers = { 38 | 'content-type' : '', 39 | 'content-length' : str(len(json_responseBody)) 40 | } 41 | 42 | try: 43 | response = requests.put(responseUrl, 44 | data=json_responseBody, 45 | headers=headers) 46 | print ("Status code: " + response.reason) 47 | except Exception as e: 48 | print ("send(..) failed executing requests.put(..): " + str(e)) 49 | 50 | def json_dump_format(obj): 51 | return json.dumps(obj, indent=4, sort_keys=True, default=str) 52 | -------------------------------------------------------------------------------- /templates/custom-resources/codebuild-start.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ########################################################################## 4 | # Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | # 6 | # Licensed under the Amazon Software License (the "License"). You may not use this file 7 | # except in compliance with the License. A copy of the License is located at 8 | # 9 | # http://aws.amazon.com/asl/ 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" 12 | # BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 13 | # License for the specific language governing permissions and limitations under the License. 14 | ########################################################################## 15 | """ CodeBuild Starter 16 | 17 | CloudFormation Custom Resource Lambda Function 18 | """ 19 | 20 | import logging 21 | import boto3 22 | import cfnresponse 23 | 24 | 25 | codebuild_client = boto3.client('codebuild') 26 | 27 | DEFAULT_LOGGING_LEVEL = logging.INFO 28 | logging.basicConfig(format='[%(levelname)s] %(message)s', level=DEFAULT_LOGGING_LEVEL) 29 | logger = logging.getLogger(__name__) 30 | logger.setLevel(DEFAULT_LOGGING_LEVEL) 31 | 32 | def start_build(resource_properties): 33 | project_name = resource_properties.get('ProjectName') 34 | if (not project_name): 35 | raise ValueError( 36 | 'missing ProjectName resource property' 37 | ) 38 | 39 | response = codebuild_client.start_build( 40 | projectName=project_name 41 | ) 42 | return response 43 | 44 | def handler(event, context): 45 | logger.info('event: {}'.format(cfnresponse.json_dump_format(event))) 46 | request_type = event.get('RequestType') 47 | resource_properties = event.get('ResourceProperties') 48 | 49 | response = {} 50 | response_status = cfnresponse.SUCCESS 51 | request_id = '' 52 | reason = '' 53 | 54 | if (request_type in ['Create', 'Update']): 55 | try: 56 | start_build_response = start_build(resource_properties) 57 | logger.info( 58 | 'start_build response: {}'.format( 59 | cfnresponse.json_dump_format(start_build_response) 60 | ) 61 | ) 62 | # only return specific fields to prevent "response object is too long" errors 63 | response = { 64 | 'build_id': start_build_response['build']['id'], 65 | 'project_name': start_build_response['build']['projectName'], 66 | 'arn': start_build_response['build']['arn'], 67 | } 68 | response_status = cfnresponse.SUCCESS 69 | request_id = start_build_response['ResponseMetadata']['RequestId'] 70 | reason = 'Create' 71 | except Exception as e: 72 | error = 'failed to start build: {}'.format(e) 73 | logger.error(error) 74 | response_status = cfnresponse.FAILED 75 | reason = error 76 | pass 77 | 78 | cfnresponse.send( 79 | event, 80 | context, 81 | response_status, 82 | response, 83 | request_id, 84 | reason 85 | ) 86 | -------------------------------------------------------------------------------- /templates/custom-resources/requirements.txt: -------------------------------------------------------------------------------- 1 | # Example requirements.txt 2 | requests==2.32.4 3 | boto3==1.16.18 -------------------------------------------------------------------------------- /templates/custom-resources/s3-cleanup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ########################################################################## 4 | # Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | # 6 | # Licensed under the Amazon Software License (the "License"). You may not use this file 7 | # except in compliance with the License. A copy of the License is located at 8 | # 9 | # http://aws.amazon.com/asl/ 10 | # 11 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" 12 | # BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the 13 | # License for the specific language governing permissions and limitations under the License. 14 | ########################################################################## 15 | """ S3 Clean Up 16 | 17 | CloudFormation Custom Resource Lambda Function 18 | """ 19 | 20 | import logging 21 | import boto3 22 | import cfnresponse 23 | 24 | DEFAULT_LOGGING_LEVEL = logging.INFO 25 | logging.basicConfig(format='[%(levelname)s] %(message)s', level=DEFAULT_LOGGING_LEVEL) 26 | logger = logging.getLogger(__name__) 27 | logger.setLevel(DEFAULT_LOGGING_LEVEL) 28 | 29 | boto3.set_stream_logger('boto3', level=DEFAULT_LOGGING_LEVEL) 30 | 31 | s3_client = boto3.client('s3') 32 | s3_resource = boto3.resource('s3') 33 | 34 | def get_buckets_from_properties(resource_properties): 35 | buckets = resource_properties.get('Buckets') 36 | logger.info('buckets in properties {}'.format(buckets)) 37 | if type(buckets) != list: 38 | raise ValueError('invalid Buckets property type - not an array') 39 | if not len(buckets): 40 | raise ValueError('empty Buckets property') 41 | for bucket in buckets: 42 | bucket_type = type(bucket) 43 | if not (bucket_type == str or bucket_type == unicode): 44 | raise ValueError( 45 | 'invalid bucket name type in Buckets property: {}'.format( 46 | bucket_type 47 | ) 48 | ) 49 | s3_client.head_bucket(Bucket=bucket) 50 | 51 | return buckets 52 | 53 | def delete_buckets(buckets): 54 | for bucket in buckets: 55 | bucket_resource = s3_resource.Bucket(bucket) 56 | for object_version in bucket_resource.object_versions.all(): 57 | object_version.delete() 58 | for s3_object in bucket_resource.objects.all(): 59 | s3_object.delete() 60 | 61 | bucket_resource.delete() 62 | 63 | def handler(event, context): 64 | logger.info('event: {}'.format(cfnresponse.json_dump_format(event))) 65 | request_type = event.get('RequestType') 66 | resource_properties = event.get('ResourceProperties') 67 | 68 | response_status = cfnresponse.SUCCESS 69 | response = {} 70 | response_id = event.get('RequestId') 71 | reason = '' 72 | error = '' 73 | 74 | if (request_type == 'Delete'): 75 | try: 76 | buckets = get_buckets_from_properties(resource_properties) 77 | delete_buckets(buckets) 78 | logger.info('delete_buckets completed') 79 | reason = 'Delete' 80 | except Exception as e: 81 | error = 'failed to delete buckets: {}'.format(e) 82 | pass 83 | 84 | if error: 85 | logger.error(error) 86 | response_status = cfnresponse.FAILED 87 | reason = error 88 | 89 | cfnresponse.send( 90 | event, 91 | context, 92 | response_status, 93 | response, 94 | response_id, 95 | reason 96 | ) 97 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 5 | const CopyPlugin = require('copy-webpack-plugin'); 6 | const fs = require('fs'); 7 | 8 | function getAssetPath(filePath, defaultPath) { 9 | const fileExists = fs.existsSync(filePath); 10 | return fileExists ? filePath : defaultPath; 11 | } 12 | 13 | const VERSION = require('./package.json').version; 14 | 15 | const basePath = __dirname; 16 | const distDir = path.join(basePath, 'dist'); 17 | const assetsDir = path.resolve(__dirname, 'lex-web-ui/dist/bundle'); 18 | const devServerPort = (process.env.PORT) ? Number(process.env.PORT) : 8000; 19 | 20 | module.exports = (env) => { 21 | const isProd = (env.production === true); 22 | 23 | return { 24 | mode: (isProd) ? 'production' : 'development', 25 | context: path.join(basePath, 'src/lex-web-ui-loader/js'), 26 | entry: { 27 | 'lex-web-ui-loader': './index.js', 28 | }, 29 | output: { 30 | path: distDir, 31 | filename: (isProd) ? '[name].min.js' : '[name].js', 32 | library: 'ChatBotUiLoader', 33 | libraryExport: 'ChatBotUiLoader', 34 | libraryTarget: 'umd', 35 | }, 36 | resolve: { 37 | fallback: { 38 | 'process/browser': require.resolve('process/browser'), 39 | }, 40 | }, 41 | module: { 42 | rules: [ 43 | /* TODO pending clean-up to re-enable 44 | { 45 | test: /\.js$/, 46 | exclude: /[\\/]node_modules[\\/]/, 47 | enforce: 'pre', 48 | loader: 'eslint-loader', 49 | options: { 50 | formatter: eslintFormatterFriendly, 51 | }, 52 | }, 53 | */ 54 | { 55 | test: /\.js$/, 56 | exclude: /[\\/]node_modules[\\/]/, 57 | loader: 'babel-loader', 58 | options: { 59 | presets: ['@babel/preset-env'] 60 | } 61 | }, 62 | { 63 | test: /\.css$/, 64 | use: (isProd) ? [ 65 | { 66 | loader: MiniCssExtractPlugin.loader, 67 | }, 68 | 'css-loader', 69 | 'postcss-loader', 70 | ] : [ 71 | { 72 | loader: MiniCssExtractPlugin.loader, 73 | }, 74 | 'css-loader', 75 | ], 76 | }, 77 | ], 78 | }, 79 | devtool: (isProd) ? 'source-map' : 'cheap-module-source-map', 80 | performance: { 81 | hints: false, 82 | }, 83 | devServer: { 84 | static: [ 85 | { 86 | directory: path.join(__dirname, 'src/config'), 87 | }, 88 | { 89 | directory: path.join(__dirname, 'src/website'), 90 | }, 91 | { 92 | directory: distDir, 93 | }, 94 | ], 95 | client: { 96 | logging: 'warn', 97 | overlay: { 98 | errors: true, 99 | warnings: false, 100 | runtimeErrors: true, 101 | }, 102 | }, 103 | hot: true, 104 | port: devServerPort, 105 | }, 106 | stats: { 107 | modules: false, 108 | logging: 'error', 109 | }, 110 | optimization: { 111 | minimize: false, 112 | }, 113 | plugins: [ 114 | new webpack.optimize.LimitChunkCountPlugin({ 115 | maxChunks: 1, 116 | }), 117 | new webpack.ProvidePlugin({ 118 | process: "process/browser", 119 | }), 120 | new HtmlWebpackPlugin({ 121 | filename: 'index.html', 122 | template: path.join(basePath, 'src/website/index.html'), 123 | // script is included in template 124 | inject: false, 125 | scriptLoading: 'blocking', 126 | }), 127 | new HtmlWebpackPlugin({ 128 | filename: 'parent.html', 129 | template: path.join(basePath, 'src/website/parent.html'), 130 | // script is included in template 131 | inject: false, 132 | scriptLoading: 'blocking', 133 | }), 134 | isProd && new webpack.BannerPlugin({ 135 | banner: `/*! 136 | * lex-web-ui v${VERSION} 137 | * (c) 2017-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. 138 | * Released under the Amazon Software License. 139 | */ `, 140 | raw: true, 141 | entryOnly: true, 142 | exclude: /[\\/]node_modules[\\/]/, 143 | }), 144 | new MiniCssExtractPlugin({ 145 | filename: (isProd) ? '[name].min.css' : '[name].css', 146 | }), 147 | new CopyPlugin( 148 | { 149 | patterns: [ 150 | // copy parent page 151 | //{ 152 | // from: path.join(basePath, 'src/website/parent.html'), 153 | // to: distDir, 154 | //}, 155 | // copy custom css 156 | { 157 | from: path.join(basePath, 'src/website/custom-chatbot-style.css'), 158 | to: distDir, 159 | }, 160 | // copy lex-web-ui library 161 | { 162 | from: getAssetPath(path.join(basePath, 'lex-web-ui/dist/bundle/**/*'), assetsDir), 163 | to: path.resolve(distDir, '[path][name][ext]'), 164 | globOptions: { 165 | ignore: [ 166 | "**/*.html", 167 | "**.*.txt", 168 | ], 169 | }, 170 | }, 171 | ] 172 | } 173 | ), 174 | ].filter(Boolean), 175 | }; 176 | }; 177 | --------------------------------------------------------------------------------